Przeglądaj źródła

饼图/数据源设置界面调整

zhuth 7 lat temu
rodzic
commit
5e109832d2
32 zmienionych plików z 595 dodań i 584 usunięć
  1. BIN
      app/assets/chart-pie.png
  2. BIN
      app/assets/selected.png
  3. 2 2
      app/components/chart/chooseDataSourceBox.jsx
  4. 1 0
      app/components/chart/chooseDataSourceBox.less
  5. 0 5
      app/components/chart/list.jsx
  6. 8 31
      app/components/chartDesigner/charts/echartsView.jsx
  7. 8 53
      app/components/chartDesigner/charts/resolveChartOption.js
  8. 2 2
      app/components/chartDesigner/content.jsx
  9. 3 3
      app/components/chartDesigner/header.jsx
  10. 14 0
      app/components/chartDesigner/layout.less
  11. 5 5
      app/components/chartDesigner/sections/aggregateTableConfigForm.jsx
  12. 7 7
      app/components/chartDesigner/sections/barConfigForm.jsx
  13. 0 12
      app/components/chartDesigner/sections/barConfigForm.less
  14. 1 1
      app/components/chartDesigner/sections/baseConfigForm.jsx
  15. 6 1
      app/components/chartDesigner/sections/baseConfigForm.less
  16. 2 2
      app/components/chartDesigner/sections/dataViewConfigForm.jsx
  17. 48 29
      app/components/chartDesigner/sections/gauge.json
  18. 12 14
      app/components/chartDesigner/sections/pieConfigForm.jsx
  19. 1 1
      app/components/chartDesigner/sections/preparingForm.jsx
  20. 1 1
      app/components/chartDesigner/sections/toolbar.jsx
  21. 40 78
      app/components/datasource/baseConfig.jsx
  22. 0 147
      app/components/datasource/connectConfig.jsx
  23. 0 32
      app/components/datasource/dataColumn.jsx
  24. 196 0
      app/components/datasource/dataConnectConfig.jsx
  25. 23 5
      app/components/datasource/dataSourceDetail.jsx
  26. 39 12
      app/components/datasource/dataSourceDetail.less
  27. 84 0
      app/components/datasource/otherConfig.jsx
  28. 2 0
      app/constants/url.js
  29. 6 2
      app/models/chart.js
  30. 83 120
      app/models/chartDesigner.js
  31. 0 18
      app/models/dataConnect.js
  32. 1 1
      index.html

BIN
app/assets/chart-pie.png


BIN
app/assets/selected.png


+ 2 - 2
app/components/chart/chooseDataSourceBox.jsx

@@ -1,5 +1,5 @@
 import React from 'react'
-import { Modal, Form, Row, Col, Input, InputNumber, Select, Icon, Menu, Dropdown, Table, Radio, Tag } from 'antd'
+import { Modal, Form, Row, Col, Input, InputNumber, Select, Icon, Menu, Dropdown, Table, Radio, Tag, message } from 'antd'
 const FormItem = Form.Item
 const SelectOption = Select.Option
 const OptionGroup = Select.OptGroup
@@ -35,7 +35,7 @@ class ChooseDataSourceBox extends React.Component {
                 viewType: 'bar'
             } });
         }else {
-            this.props.hideBox()
+            message.warning('未选中数据源');
         }
     }
 

+ 1 - 0
app/components/chart/chooseDataSourceBox.less

@@ -1,4 +1,5 @@
 .choosedatasource-box {
+    width: 600px !important;
     .ant-modal-body {
         padding: 0;
         max-height: 50vh;

+ 0 - 5
app/components/chart/list.jsx

@@ -123,11 +123,6 @@ class ChartList extends React.Component {
                                         visibleBox: false
                                     });
                                 }}/>
-                                {/* <Button onClick={() => {
-                                    dispatch({ type: 'main/redirect', path: '/chart/create' });
-                                }}>
-                                    <Icon type="area-chart" />创建图表
-                                </Button> */}
                             </Col>
                         </Row>
                     }>

+ 8 - 31
app/components/chartDesigner/charts/echartsView.jsx

@@ -1,11 +1,12 @@
-import React from 'react';
-import Echarts from 'echarts-for-react';
-import { connect } from 'dva';
-import chartDesigner from '../../../models/chartDesigner';
-import resolveChartOption from './resolveChartOption';
-import { hashcode } from '../../../utils/baseUtils';
+import React from 'react'
+import Echarts from 'echarts-for-react'
+import { connect } from 'dva'
+import '../../../models/chartDesigner'
+import resolveChartOption from './resolveChartOption'
+import { hashcode } from '../../../utils/baseUtils'
 
 const EchartsView = ({ chartDesigner, dispatch }) => {
+    console.log(chartDesigner.chartOption);
     const option = resolveChartOption(chartDesigner.chartOption);
     console.log(option);
     return <Echarts
@@ -20,28 +21,4 @@ function mapStateToProps({ present: { chartDesigner } }) {
     return { chartDesigner: chartDesigner }
 }
 
-export default connect(mapStateToProps)(EchartsView);
-
-// class EchartsView extends React.Component {
-//     constructor(props) {
-//         super(props);
-//         this.state = {
-//             option: props.option
-//         }
-//     }
-
-//     componentDidMount() {
-//     }
-
-//     render() {
-//         return (
-//             <Echarts ref={(e) => { this.echarts_react = e; }}
-//                 option={this.state.option}
-//                 className='rc-echarts'
-//                 style={{height: '100%'}}
-//              />
-//         )
-//     }
-// }
-
-// export default EchartsView;
+export default connect(mapStateToProps)(EchartsView);

+ 8 - 53
app/components/chartDesigner/charts/resolveChartOption.js

@@ -1,13 +1,14 @@
 export default (config) => {
     const { viewType, data } = config;
+    console.log(viewType);
     let option = {}
     switch(viewType) {
         case 'bar': {
-            option = barConfig(data.data);
+            option = barConfig(data ? data.data : {});
             break;
         }
         case 'pie': {
-            option = pieConfig(data.data);
+            option = pieConfig(data ? data.data : {});
         }
         default:{
         }
@@ -53,67 +54,21 @@ function barConfig(data) {
 }
 
 function pieConfig(data) {
-    function genData(count) {
-        var nameList = [
-            '赵', '钱', '孙', '李', '周', '吴', '郑', '王', '冯', '陈', '褚', '卫', '蒋', '沈', '韩', '杨', '朱', '秦', '尤', '许', '何', '吕', '施', '张', '孔', '曹', '严', '华', '金', '魏', '陶', '姜', '戚', '谢', '邹', '喻', '柏', '水', '窦', '章', '云', '苏', '潘', '葛', '奚', '范', '彭', '郎', '鲁', '韦', '昌', '马', '苗', '凤', '花', '方', '俞', '任', '袁', '柳', '酆', '鲍', '史', '唐', '费', '廉', '岑', '薛', '雷', '贺', '倪', '汤', '滕', '殷', '罗', '毕', '郝', '邬', '安', '常', '乐', '于', '时', '傅', '皮', '卞', '齐', '康', '伍', '余', '元', '卜', '顾', '孟', '平', '黄', '和', '穆', '萧', '尹', '姚', '邵', '湛', '汪', '祁', '毛', '禹', '狄', '米', '贝', '明', '臧', '计', '伏', '成', '戴', '谈', '宋', '茅', '庞', '熊', '纪', '舒', '屈', '项', '祝', '董', '梁', '杜', '阮', '蓝', '闵', '席', '季', '麻', '强', '贾', '路', '娄', '危'
-        ];
-        var legendData = [];
-        var seriesData = [];
-        var selected = {};
-        for (var i = 0; i < 50; i++) {
-            name = Math.random() > 0.65
-                ? makeWord(4, 1) + '·' + makeWord(3, 0)
-                : makeWord(2, 1);
-            legendData.push(name);
-            seriesData.push({
-                name: name,
-                value: Math.round(Math.random() * 100000)
-            });
-            selected[name] = i < 6;
-        }
-    
-        return {
-            legendData: legendData,
-            seriesData: seriesData,
-            selected: selected
-        };
-    
-        function makeWord(max, min) {
-            var nameLen = Math.ceil(Math.random() * max + min);
-            var name = [];
-            for (var i = 0; i < nameLen; i++) {
-                name.push(nameList[Math.round(Math.random() * nameList.length - 1)]);
-            }
-            return name.join('');
-        }
-    }
     let o = {
-        title : {
-            text: '同名数量统计',
-            subtext: '纯属虚构',
-            x:'center'
-        },
         tooltip : {
             trigger: 'item',
             formatter: "{a} <br/>{b} : {c} ({d}%)"
         },
         legend: {
-            type: 'scroll',
-            orient: 'vertical',
-            right: 10,
-            top: 20,
-            bottom: 20,
-            data: data.legendData,
-    
-            selected: data.selected
+            data: data.xAxis
         },
         series : [
             {
-                name: '姓名',
+                name: data.columnName,
                 type: 'pie',
                 radius : '55%',
-                center: ['40%', '50%'],
-                data: data.seriesData,
+                center: ['50%', '60%'],
+                data: data.serieses[0].value,
                 itemStyle: {
                     emphasis: {
                         shadowBlur: 10,
@@ -123,6 +78,6 @@ function pieConfig(data) {
                 }
             }
         ]
-    }
+    };
     return o;
 }

+ 2 - 2
app/components/chartDesigner/content.jsx

@@ -41,13 +41,13 @@ class ChartDesignerContent extends React.Component {
             chartView = (<TableView />);
         }else if(viewType == 'line') {
             // configForm = (<DataViewConfigForm formItemLayout={formItemLayout}/>);
-            // chartView = (<EchartsView option={chartOption}/>);
+            // chartView = (<EchartsView />);
         }else if(viewType == 'bar') {
             configForm = (<BarConfigForm formItemLayout={formItemLayout}/>);
             chartView = (<EchartsView />);
         }else if(viewType == 'pie') {
             configForm = (<PieConfigForm formItemLayout={formItemLayout}/>);
-            // chartView = (<EchartsView option={chartOption}/>);
+            chartView = (<EchartsView />);
         }
 
         return (

+ 3 - 3
app/components/chartDesigner/header.jsx

@@ -22,9 +22,9 @@ const Header = ({ chartDesigner, dispatch }) => {
                 <Input
                     className='input-title' 
                     addonAfter={<Icon type="edit" 
-                        onClick={() => {
-                            const input = this.refs.titleInput
-                            input.focus()
+                        onClick={(e) => {
+                            const input = e.currentTarget.parentElement.parentElement.getElementsByTagName('input')[0];
+                            input && input.focus()
                         }}
                     />}
                     onChange={(e) => {

+ 14 - 0
app/components/chartDesigner/layout.less

@@ -29,6 +29,20 @@
         }
     }
     .ant-tabs {
+        .chartconfig {
+            .ant-cascader-picker {
+                .ant-cascader-picker-label {
+                    height: 100%;
+                    padding: 0 6px;
+                    .cascader-label {
+                        margin-left: 6px;
+                    }
+                    .empty-label {
+                        color: #bfbfbf;
+                    }
+                }
+            }
+        }
     }
     .ant-tabs-bar {
         margin: 0;

+ 5 - 5
app/components/chartDesigner/sections/aggregateTableConfigForm.jsx

@@ -4,7 +4,7 @@ const FormItem = Form.Item;
 const { Option } = Select;
 const CheckboxGroup = Checkbox.Group;
 import { connect } from 'dva';
-import chartDesigner from '../../../models/chartDesigner';
+import '../../../models/chartDesigner';
 
 class AggregateTableConfigForm extends React.Component {
 	render() {
@@ -34,11 +34,11 @@ class AggregateTableConfigForm extends React.Component {
 			<Form>
 				<FormItem label='分析目标' {...formItemLayout}>
 					<Select
-						value={props.chartDesigner.aggregateTable.targetColumn}
+						value={props.chartDesigner.aggregateTableConfig.targetColumn}
 						mode='multiple'
 						labelInValue={true}
 						onChange={(value) => {
-							props.dispatch({ type: 'chartDesigner/changeField', name: 'aggregateTable', value: { ...props.chartDesigner.aggregateTable, targetColumn: value } });
+							props.dispatch({ type: 'chartDesigner/changeField', name: 'aggregateTableConfig', value: { ...props.chartDesigner.aggregateTableConfig, targetColumn: value } });
 							
 						}}
 					>
@@ -49,10 +49,10 @@ class AggregateTableConfigForm extends React.Component {
 				</FormItem>
 				<FormItem label='显示总体数据' {...formItemLayout}>
 					<CheckboxGroup
-						value={props.chartDesigner.aggregateTable.statistics}
+						value={props.chartDesigner.aggregateTableConfig.statistics}
 						options={statisticsOptions.map((s)=>{return s.label})}
 						onChange={(value) => {
-							props.dispatch({ type: 'chartDesigner/changeField', name: 'aggregateTable', value: { ...props.chartDesigner.aggregateTable, statistics: value } });
+							props.dispatch({ type: 'chartDesigner/changeField', name: 'aggregateTableConfig', value: { ...props.chartDesigner.aggregateTableConfig, statistics: value } });
 						}}
 					/>
 				</FormItem>

+ 7 - 7
app/components/chartDesigner/sections/barConfigForm.jsx

@@ -6,8 +6,7 @@ const { SubMenu, MenuItemGroup } = Menu;
 import { connect } from 'dva';
 import chartDesigner from '../../../models/chartDesigner';
 import GAUGE from './gauge.json';
-import GRACULARITY from './granularity.json';
-import './barConfigForm.less';
+import GRANULARITY from './granularity.json';
 
 const BarConfigForm = ({ chartDesigner, dispatch, formItemLayout }) => {
 	
@@ -39,7 +38,7 @@ const BarConfigForm = ({ chartDesigner, dispatch, formItemLayout }) => {
 							type: c.type,
 							value: c.name,
 							label: c.label,
-							children: GRACULARITY[c.type]
+							children: GRANULARITY[c.type]
 						}
 					})}
 					onChange={(value, items) => {
@@ -73,14 +72,14 @@ const BarConfigForm = ({ chartDesigner, dispatch, formItemLayout }) => {
 			</FormItem>
 			<FormItem label='纵轴' {...formItemLayout}>
 				<Cascader
-					className='barconfig-yaxis'
+					className='gauge-item'
 					value={[chartDesigner.barConfig.yAxis.column.value, chartDesigner.barConfig.yAxis.gauge.value]}
 					allowClear={true}
 					options={columns.map((c, i)=>{
 						return {
 							value: c.name,
 							label: c.label,
-							children: GAUGE.map(g => {
+							children: GAUGE[chartDesigner.baseConfig.viewType].map(g => {
                                 if(g.columnType.indexOf(c.type) != -1) {
                                     return g;
                                 }else {
@@ -136,15 +135,16 @@ const BarConfigForm = ({ chartDesigner, dispatch, formItemLayout }) => {
 			</FormItem>
 			<FormItem label='分组' {...formItemLayout}>
 				<Select
-					mode="multiple"
+					maxTagCount={1}
 					labelInValue={true}
 					placeholder='请选择...'
 					onChange={(value) => {
+						// value = value.splice(-1);
 						dispatch({ type: 'chartDesigner/changeField', name: 'preparing', value: { ...chartDesigner.preparing, groupBy: value } });
 					}}
 					value={chartDesigner.preparing.groupBy}
 				>
-					{columns.map((c, i)=>{
+					{columns.filter(c => c.type === 'categorical').map((c, i)=>{
 						return (<Option key={i} value={c.name}>{c.label}</Option>)
 					})}
 				</Select>

+ 0 - 12
app/components/chartDesigner/sections/barConfigForm.less

@@ -1,12 +0,0 @@
-.barconfig-yaxis {
-    .ant-cascader-picker-label {
-        height: 100%;
-        padding: 0 6px;
-        .cascader-label {
-            margin-left: 6px;
-        }
-        .empty-label {
-            color: #bfbfbf;
-        }
-    }
-}

+ 1 - 1
app/components/chartDesigner/sections/baseConfigForm.jsx

@@ -44,7 +44,7 @@ class baseConfigForm extends React.Component {
 						CHART_TYPE.map( c => (
 							<Option key={c.type} value={c.type} title={c.label}>
 								<div className='viewtype-box'>
-									<div className='viewtype-icon'>
+									<div className={`viewtype-icon viewtype-${c.type}`}>
 									</div>
 									<div className='viewtype-text'>
 										{c.label}

+ 6 - 1
app/components/chartDesigner/sections/baseConfigForm.less

@@ -10,10 +10,15 @@
                 .viewtype-icon {
                     width: 20px;
                     height: 20px;
-                    background-image: url(../../../assets/chart-bar.png);
                     background-size: contain;
                     margin: 0 auto;
                 }
+                .viewtype-bar {
+                    background-image: url(../../../assets/chart-bar.png);
+                }
+                .viewtype-pie {
+                    background-image: url(../../../assets/chart-pie.png);
+                }
             }
         }
     }

+ 2 - 2
app/components/chartDesigner/sections/dataViewConfigForm.jsx

@@ -17,10 +17,10 @@ class DataViewConfigForm extends React.Component {
 					<Select
 						key='hf'
 						mode='multiple'
-						value={props.chartDesigner.dataView.targetColumn}
+						value={props.chartDesigner.dataViewConfig.targetColumn}
 						labelInValue={true}
 						onChange={(value) => {
-							props.dispatch({ type: 'chartDesigner/changeField', name: 'dataView', value: { ...props.chartDesigner.dataView, targetColumn: value }});
+							props.dispatch({ type: 'chartDesigner/changeField', name: 'dataViewConfig', value: { ...props.chartDesigner.dataViewConfig, targetColumn: value }});
 						}}
 					>
 						{columns.map((c, i)=>{

+ 48 - 29
app/components/chartDesigner/sections/gauge.json

@@ -1,29 +1,48 @@
-[{
-    "value": "sum",
-    "label": "累计",
-    "columnType": ["scale", "ordinal"]
-}, {
-    "value": "avg",
-    "label": "平均值",
-    "columnType": ["scale", "ordinal"]
-}, {
-    "value": "median",
-    "label": "中位数",
-    "columnType": ["scale", "ordinal"]
-}, {
-    "value": "count",
-    "label": "计数",
-    "columnType": ["index", "time", "categorical", "scale", "ordinal", "string"]
-}, {
-    "value": "distinctCount",
-    "label": "不重复计数",
-    "columnType": ["index", "time", "categorical", "scale", "ordinal", "string"]
-}, {
-    "value": "max",
-    "label": "最大值",
-    "columnType": ["scale", "ordinal"]
-}, {
-    "value": "min",
-    "label": "最小值",
-    "columnType": ["scale", "ordinal"]
-}]
+{
+    "bar": [{
+        "value": "sum",
+        "label": "累计",
+        "columnType": ["scale", "ordinal"]
+    }, {
+        "value": "avg",
+        "label": "平均值",
+        "columnType": ["scale", "ordinal"]
+    }, {
+        "value": "median",
+        "label": "中位数",
+        "columnType": ["scale", "ordinal"]
+    }, {
+        "value": "count",
+        "label": "计数",
+        "columnType": ["index", "time", "categorical", "scale", "ordinal", "string"]
+    }, {
+        "value": "distinctCount",
+        "label": "不重复计数",
+        "columnType": ["index", "time", "categorical", "scale", "ordinal", "string"]
+    }, {
+        "value": "max",
+        "label": "最大值",
+        "columnType": ["scale", "ordinal"]
+    }, {
+        "value": "min",
+        "label": "最小值",
+        "columnType": ["scale", "ordinal"]
+    }],
+    "pie": [{
+        "value": "avg",
+        "label": "平均值",
+        "columnType": ["scale"]
+    }, {
+        "value": "sum",
+        "label": "累计",
+        "columnType": ["scale"]
+    }, {
+        "value": "count",
+        "label": "计数",
+        "columnType": ["index", "time", "categorical", "scale", "ordinal", "string"]
+    }, {
+        "value": "distinctCount",
+        "label": "不重复计数",
+        "columnType": ["index", "time", "categorical", "scale", "ordinal", "string"]
+    }]
+}

+ 12 - 14
app/components/chartDesigner/sections/pieConfigForm.jsx

@@ -7,7 +7,6 @@ import { connect } from 'dva';
 import chartDesigner from '../../../models/chartDesigner';
 import GAUGE from './gauge.json';
 import GRANULARITY from './granularity.json';
-import './barConfigForm.less';
 
 const PieConfigForm = ({ chartDesigner, dispatch, formItemLayout }) => {
 	
@@ -30,10 +29,9 @@ const PieConfigForm = ({ chartDesigner, dispatch, formItemLayout }) => {
 		<Form hideRequiredMark={true}>
 			<FormItem label='扇区索引' {...formItemLayout}>
 				<Cascader
-					className='barconfig-yaxis'
-					value={[chartDesigner.barConfig.xAxis.column.value, chartDesigner.barConfig.xAxis.granularity.value]}
+					value={[chartDesigner.pieConfig.xAxis.column.value, chartDesigner.pieConfig.xAxis.granularity.value]}
 					allowClear={true}
-					options={columns.filter(c =>['categorical'].indexOf(c.type) != -1).map((c, i)=>{
+					options={columns.filter(c =>['ordinal', 'categorical', 'time'].indexOf(c.type) != -1).map((c, i)=>{
 						
 						return {
 							type: c.type,
@@ -51,7 +49,7 @@ const PieConfigForm = ({ chartDesigner, dispatch, formItemLayout }) => {
 						if(items.length > 1) {
 							granularity = { value: items[1].value, label: items[1].label };
 						}
-						dispatch({ type: 'chartDesigner/changeField', name: 'barConfig', value: { ...chartDesigner.barConfig, xAxis: { column, granularity } } });
+						dispatch({ type: 'chartDesigner/changeField', name: 'pieConfig', value: { ...chartDesigner.pieConfig, xAxis: { column, granularity } } });
 					}}
 					displayRender={(label, selectedOptions) => {
 						let text = '';
@@ -73,14 +71,14 @@ const PieConfigForm = ({ chartDesigner, dispatch, formItemLayout }) => {
 			</FormItem>
 			<FormItem label='值' {...formItemLayout}>
 				<Cascader
-					className='barconfig-yaxis'
-					value={[chartDesigner.barConfig.yAxis.column.value, chartDesigner.barConfig.yAxis.gauge.value]}
+					className='gauge-item'
+					value={[chartDesigner.pieConfig.yAxis.column.value, chartDesigner.pieConfig.yAxis.gauge.value]}
 					allowClear={true}
 					options={columns.map((c, i)=>{
 						return {
 							value: c.name,
 							label: c.label,
-							children: GAUGE.map(g => {
+							children: GAUGE[chartDesigner.baseConfig.viewType].map(g => {
                                 if(g.columnType.indexOf(c.type) != -1) {
                                     return g;
                                 }else {
@@ -93,24 +91,24 @@ const PieConfigForm = ({ chartDesigner, dispatch, formItemLayout }) => {
 						let column = {};
 						let gauge = {};
 						if(value.length > 0) {
-							column = { value: items[0].value, label: items[0].label };
+							column = { type: items[0].type, value: items[0].value, label: items[0].label };
 							gauge = { value: items[1].value, label: items[1].label };
 						}
-						dispatch({ type: 'chartDesigner/changeField', name: 'barConfig', value: { ...chartDesigner.barConfig, yAxis: { column, gauge } } });
+						dispatch({ type: 'chartDesigner/changeField', name: 'pieConfig', value: { ...chartDesigner.pieConfig, yAxis: { column, gauge } } });
 					}}
 					displayRender={(label, selectedOptions) => {
 						let menu = selectedOptions.length > 0 ? <Menu
-							selectedKeys={[chartDesigner.barConfig.yAxis.gauge.value]}
+							selectedKeys={[chartDesigner.pieConfig.yAxis.gauge.value]}
 							selectable={true}
 						>
 							{selectedOptions[0].children.map((c, i) => {
 								return <Menu.Item  data-value={c.value} data-label={c.label} key={c.value} onClick={(e) => {
 									let value = e.domEvent.target.getAttribute('data-value');
 									let label = e.domEvent.target.getAttribute('data-label');
-									dispatch({ type: 'chartDesigner/changeField', name: 'barConfig', value: { 
-										...chartDesigner.barConfig,
+									dispatch({ type: 'chartDesigner/changeField', name: 'pieConfig', value: { 
+										...chartDesigner.pieConfig,
 										yAxis: {
-											column: chartDesigner.barConfig.yAxis.column,
+											column: chartDesigner.pieConfig.yAxis.column,
 											gauge: { value, label }
 										}
 									}});

+ 1 - 1
app/components/chartDesigner/sections/preparingForm.jsx

@@ -23,7 +23,7 @@ class PreparingForm extends React.Component {
 						}}
 						value={props.chartDesigner.preparing.groupBy}
 					>
-						{columns.map((c, i)=>{
+						{columns.filterBy(c => {console.log(c.type); return c.type === 'categorical'}).map((c, i)=>{
 							return (<Option key={i} value={c.name}>{c.label}</Option>)
 						})}
 					</Select>

+ 1 - 1
app/components/chartDesigner/sections/toolbar.jsx

@@ -126,7 +126,7 @@ class Toolbar extends React.Component {
                         this.setState({
                             visibleColumnBox: true
                         })
-                    }}>查看列数据</Button>
+                    }}>查看列</Button>
                 </div>
                 <FilterBox key={Math.random()} columns={columns} filterData={filters} visibleFilterBox={visibleFilterBox} showFilterBox={this.showFilterBox} hideFilterBox={this.hideFilterBox} createFilters={this.createFilters} />  
             </div>

+ 40 - 78
app/components/datasource/baseConfig.jsx

@@ -1,5 +1,5 @@
 import React from 'react'
-import { Button, Form, Row, Col, Input, InputNumber, Select, Icon, Menu, Dropdown, Divider, Upload, message } from 'antd'
+import { Button, Table, Form, Row, Col, Input, InputNumber, Select, Icon, Menu, Dropdown, Divider, Upload, message } from 'antd'
 const FormItem = Form.Item
 const SelectOption = Select.Option
 const OptionGroup = Select.OptGroup
@@ -9,38 +9,52 @@ const MenuItem = Menu.Item
 const MenuItemGroup = Menu.ItemGroup
 const UploadDragger = Upload.Dragger
 import { connect } from 'dva'
+import DataConnectConfig from './dataConnectConfig'
 import '../../models/dataSource'
 import '../../models/dataConnect'
 
-const DataSourceBaseConfig = ({ dataSource, dataConnect, dispatch }) => {
+const DataSourceBaseConfig = ({ dataSource, dataConnect, dispatch, mode }) => {
 
     const formItemLayout = {
         labelCol: { span: 4 },
         wrapperCol: { span: 20 },
     };
 
-    const dataSourceLinkMenu = (
-        <Menu
-            className='menu-datasource-link'
-            onClick={(e) => {
-                const model = dataConnect.list[e.key];
-                dispatch({ type: 'dataSource/setNewModelFields', fields: [
-                    { name: 'address', value: model.address },
-                    { name: 'port', value: model.port },
-                    { name: 'dbType', value: model.dbType },
-                    { name: 'dbName', value: model.dbName },
-                    { name: 'userName', value: model.userName },
-                    { name: 'password', value: model.password }
-                ] });
-            }}
-        >
-            {
-                dataConnect.list.map((l, i) => {
-                    return <MenuItem key={i}>{l.name}</MenuItem>
-                })
-            }
-        </Menu>
-    );
+    const dataConnectColumns = [{
+        title: '名称',
+        dataIndex: 'name',
+        key: 'name',
+        width: 100,
+        render: (text, record) => {
+            return <div className='datasource-name'>
+                <div className={`datasource-type type-${record.type}`}></div>
+                <div>{text}</div>
+            </div>
+        }
+    }, {
+        title: '说明',
+        dataIndex: 'description',
+        key: 'description',
+        width: 100
+    }, {
+        title: '操作',
+        key: 'action',
+        width: 300,
+        render: (text, record) => (
+            <div className='action-col'>
+                <div className='operation' onClick={() => {
+                    let selectedModel = dataConnect.list.find((i) => { return i.code == record.code });
+                    dispatch({ type: 'dataConnect/setNewModel', model: selectedModel });
+                    this.showDataConnectBox('modify')
+                }}><Icon type="info-circle-o"/>属性</div>
+                <div className='operation'><Divider type="vertical" /></div>
+                <div className='operation' onClick={() => {
+                    dispatch({ type: 'dataConnect/remoteDelete', code: record.code });
+                }}><Icon type="delete"/>删除</div>
+            </div>
+        ),
+        width: 80
+    }];
 
     return (
         <Form className='form-base' size='small'>
@@ -54,61 +68,12 @@ const DataSourceBaseConfig = ({ dataSource, dataConnect, dispatch }) => {
             {
                 dataSource.newOne.type=='file'?(
                     <div>
-                        <Divider orientation="left">选择文件</Divider>
-                        <UploadDragger
-                            className='upload'
-                            name='file' // 上传到后台的文件名
-                            fileList={dataSource.newOne.fileList}
-                            accept='application/vnd.ms-excel,.csv' // 只支持excel和csv格式文件
-                            action='//jsonplaceholder.typicode.com/posts/'
-                            headers={{authorization: 'authorization-text'}}
-                            beforeUpload={(file, fileList) => {
-                                const trueType = file.type === 'application/vnd.ms-excel';
-                                if (!trueType) {
-                                    message.error('选择文件格式错误!');
-                                }
-                                const trueSize = file.size / 1024 / 1024 < 30;
-                                if (!trueSize) {
-                                    message.error('选择文件过大!');
-                                }
-                                return trueType && trueSize;
-                            }}
-                            onChange={(info) => {
-                                const file = info.file;
-                                let fileList = info.fileList;
-                                fileList = fileList.slice(-1); // 只保留最后一个
-                                if (info.file.status !== 'uploading') {
-                                    console.log(file, info.fileList);
-                                    const trueType = file.type === 'application/vnd.ms-excel';
-                                    const trueSize = file.size / 1024 / 1024 < 30;
-                                    if(!trueType || !trueSize) {
-                                        fileList = [];
-                                    }
-                                }
-                                if (file.status === 'done') {
-                                    message.success(`${file.name} 文件上传成功`);
-                                } else if (file.status === 'error') {
-                                    fileList = [];
-                                    message.error(`${file.name} 文件上传失败.`);
-                                }
-                                dispatch({ type: 'dataSource/setNewModelField', name: 'fileList', value: fileList })
-                            }}
-                        >
-                            <p className="ant-upload-drag-icon">
-                                <Icon type="inbox" />
-                            </p>
-                            <p className="ant-upload-text">点击选择或拖动文件到该区域</p>
-                            <p className="ant-upload-hint">仅支持上传单个30MB以内EXCEL/CSV格式文件</p>
-                        </UploadDragger>
+                        <Divider orientation="left">文件</Divider>
+                        <div>此处显示文件名</div>
                     </div>
                 ):(
                     <div>
                         <Divider orientation="left">连接配置</Divider>
-                        <div className='links'>
-                            <Dropdown trigger={['click']} overlay={dataSourceLinkMenu}>
-                                <div>使用已存在的数据连接 <Icon type="down" /></div>
-                            </Dropdown>
-                        </div>
                         <FormItem label='数据库类型' {...formItemLayout}>
                             <Select
                                 value={dataSource.newOne.dbType}
@@ -196,9 +161,6 @@ const DataSourceBaseConfig = ({ dataSource, dataConnect, dispatch }) => {
                                 </FormItem>
                             </Col>
                         </Row>
-                        <div className='buttons'>
-                            <Button size='small'>测试连接</Button>
-                        </div>
                     </div>
                 )
             }

+ 0 - 147
app/components/datasource/connectConfig.jsx

@@ -1,147 +0,0 @@
-import React from 'react'
-import { Modal, Form, Row, Col, Input, InputNumber, Select, Icon, Menu, Dropdown } from 'antd'
-const FormItem = Form.Item
-const SelectOption = Select.Option
-const OptionGroup = Select.OptGroup
-const InputGroup = Input.Group
-const SubMenu = Menu.SubMenu
-const MenuItem = Menu.Item
-const MenuItemGroup = Menu.ItemGroup;
-import { connect } from 'dva'
-import dataSource from '../../models/dataSource'
-
-const DataSourceConnectConfig = ({ dataSource, dispatch }) => {
-
-    const formItemLayout = {
-        labelCol: { span: 4 },
-        wrapperCol: { span: 20 },
-    };
-
-    const dataSourceLinkMenu = (
-        <Menu
-            className='menu-datasource-link'
-            onClick={() => {
-                dispatch({ type: 'dataSource/setNewModelField', name: 'address', value: '1111adddd' });
-                dispatch({ type: 'dataSource/setNewModelField', name: 'port', value: '1234' });
-                dispatch({ type: 'dataSource/setNewModelField', name: 'type', value: {
-                    key: 'oracle',
-                    label: 'ORACLE'
-                } });
-                dispatch({ type: 'dataSource/setNewModelField', name: 'dbName', value: 'orcl' });
-                dispatch({ type: 'dataSource/setNewModelField', name: 'userName', value: 'UAS' });
-                dispatch({ type: 'dataSource/setNewModelField', name: 'password', value: 'select!#%*(' });
-            }}
-        >
-            <MenuItem>1111</MenuItem>
-            <MenuItem>2222</MenuItem>
-            <MenuItem>33333</MenuItem>
-            <MenuItem>44</MenuItem>
-        </Menu>
-    );
-
-    return (
-        <Form size='small'>
-            <Row>
-                <Col span={19}>
-                    <FormItem label='数据库地址' {...{
-                        labelCol: { span: 5 },
-                        wrapperCol: { span: 19 }
-                    }}>
-                        <Input
-                            value={dataSource.newOne.address}
-                            onChange={(e) => {
-                                dispatch({ type: 'dataSource/setNewModelField', name: 'address', value: e.target.value });
-                            }}
-                            prefix={
-                                <Dropdown
-                                    trigger={['click']}
-                                    overlay={dataSourceLinkMenu}
-                                >
-                                    <div style={{cursor: 'pointer'}}><Icon type='down' /></div>
-                                </Dropdown>
-                            }
-                        />
-                    </FormItem>
-                </Col>
-                <Col span={5}>
-                    <FormItem className='input-port' label='端口' {...{
-                        labelCol: { span: 12 },
-                        wrapperCol: { span: 12 }
-                    }}>
-                        <InputNumber
-                            value={dataSource.newOne.port}
-                            onChange={(value) => {
-                                dispatch({ type: 'dataSource/setNewModelField', name: 'port', value: value });
-                            }}
-                        />
-                    </FormItem>
-                </Col>
-            </Row>
-            <FormItem label='数据库类型' {...formItemLayout}>
-                <Select
-                    value={dataSource.newOne.type}
-                    labelInValue={true}
-                    onChange={(value) => {
-                        dispatch({ type: 'dataSource/setNewModelField', name: 'type', value: value} );
-                    }}
-                >
-                    <SelectOption value='oracle'>
-                        ORACLE
-                    </SelectOption>
-                    <SelectOption value='mysql'>
-                        MYSQL
-                    </SelectOption>
-                    <SelectOption value='sqlserver'>
-                        SQLSERVER
-                    </SelectOption>
-                    <SelectOption value='sqlite'>
-                        SQLITE
-                    </SelectOption>
-                </Select>
-            </FormItem>
-            <FormItem label='数据库名' {...formItemLayout}>
-                <Input
-                    value={dataSource.newOne.dbName}
-                    onChange={(e) => {
-                        dispatch({ type: 'dataSource/setNewModelField', name: 'dbName', value: e.target.value });
-                    }}
-                />
-            </FormItem>
-            <Row>
-                <Col span={12}>
-                    <FormItem label='用户名' {...{
-                        labelCol: { span: 8 },
-                        wrapperCol: { span: 16 }
-                    }}>
-                        <Input
-                            value={dataSource.newOne.userName}
-                            onChange={(e) => {
-                                dispatch({ type: 'dataSource/setNewModelField', name: 'userName', value: e.target.value });
-                            }}
-                        />
-                    </FormItem>
-                </Col>
-                <Col span={12}>
-                    <FormItem label='密码' {...{
-                        labelCol: { span: 8 },
-                        wrapperCol: { span: 16 }
-                    }}>
-                        <Input
-                            type='password'
-                            value={dataSource.newOne.password}
-                            onChange={(e) => {
-                                dispatch({ type: 'dataSource/setNewModelField', name: 'password', value: e.target.value });
-                            }}
-                        />
-                    </FormItem>
-                </Col>
-            </Row>
-        </Form>
-    );
-}
-
-function mapStateToProps({ present: {dataSource} }) {
-    return { dataSource }
-}
-
-export default connect(mapStateToProps)(DataSourceConnectConfig);

+ 0 - 32
app/components/datasource/dataColumn.jsx

@@ -1,32 +0,0 @@
-import React from 'react'
-import { Tabs, Input, Button, Table, Icon, Tag, Menu, Dropdown } from 'antd'
-const { TabPane } = Tabs
-const { Search } = Input
-import { connect } from 'dva'
-import { Link } from 'react-router-dom'
-
-class DataColumn extends React.Component {
-    constructor(props) {
-        super(props);
-        this.state = {
-        }
-    };
-
-    render() {
-        console.log(this.props);
-        return (
-            <div>
-                <Button>
-                    <Link to='/dataSource'>back</Link>
-                </Button>
-                111111
-            </div>
-        )
-    }
-}
-
-function mapStateToProps({present: {dataSource}}) {
-    return { dataSource }
-}
-
-export default connect(mapStateToProps)(DataColumn)

+ 196 - 0
app/components/datasource/dataConnectConfig.jsx

@@ -0,0 +1,196 @@
+import React from 'react'
+import { Layout, Button, Table, Form, Row, Col, Input, InputNumber, Select, Icon, Menu, Dropdown, Divider, Upload, message, Card } from 'antd'
+const { Header, Content } = Layout
+const CardGrid = Card.Grid
+const FormItem = Form.Item
+const SelectOption = Select.Option
+const OptionGroup = Select.OptGroup
+const InputGroup = Input.Group
+const SubMenu = Menu.SubMenu
+const MenuItem = Menu.Item
+const MenuItemGroup = Menu.ItemGroup
+const UploadDragger = Upload.Dragger
+const Search = Input.Search
+import { connect } from 'dva'
+import DataConnectBox from './dataConnectBox'
+import '../../models/dataSource'
+import '../../models/dataConnect'
+
+class DataConnectConfig extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            visibleBox: false,
+            operation: 'modify',
+            selected: null
+        };
+    }
+    render() {
+        const { dataSource, dataConnect, dispatch } = this.props;
+        const { visibleBox, operation, selected } = this.state;
+        const formItemLayout = {
+            labelCol: { span: 4 },
+            wrapperCol: { span: 20 },
+        };
+
+        const generateCard = () => {
+            const operationMenu = (
+                <Menu className='menu-operation'>
+                    <Menu.Item onClick={() => {
+                        dispatch({ type: 'dataConnect/setNewModel', model: selected });
+                        this.setState({
+                            visibleBox: true,
+                            operation: 'modify'
+                        })
+                    }}>
+                        <Icon type='edit'/>编辑
+                    </Menu.Item>
+                    <Menu.Item onClick={(e) => {
+                        dispatch({ type: 'chart/remoteDelete', code: this.state.selectedCode });
+                    }}>
+                        <Icon type='delete'/>删除
+                    </Menu.Item>
+                </Menu>
+            )
+
+            let cards = dataConnect.list.map( (l, i) => (
+                <CardGrid className='dataconnect-card' key={i}>
+                    <Card
+                        title={
+                            <Row type='flex' justify='start'>
+                                <Col>{l.name}</Col>
+                                <div style={{ display: (selected && selected.code === l.code) ? 'block' : 'none' }} className='selected'></div>
+                            </Row>
+                        }
+                        onClick={() => {
+                            this.setState({ selected: l }, () => {
+                                let s = this.state.selected;
+                                console.log(s);
+                                dispatch({ type: 'dataSource/setNewModelFields', fields: [
+                                    { name: 'dbType', value: s.dbType },
+                                    { name: 'address', value: s.address },
+                                    { name: 'port', value: s.port },
+                                    { name: 'dbName', value: s.dbName },
+                                    { name: 'userName', value: s.userName },
+                                    { name: 'password', value: s.password },
+                                ] });
+                            });
+                        }}
+                    >
+                        <div>
+                            <Row>
+                                {`说明 :${l.description}`}
+                            </Row>
+                            <Row type='flex' justify='space-between'>
+                                <Col>{l.dbType}</Col>
+                                <Col>
+                                    <Dropdown overlay={operationMenu} trigger={['click']}>
+                                        <Icon type="ellipsis" />
+                                    </Dropdown>
+                                </Col>
+                            </Row>
+                        </div>
+                    </Card>
+                </CardGrid>
+            ))
+
+            cards.unshift(
+                <CardGrid className='dataconnect-card dataconnect-card-create' key='create' onClick={() => {
+                    dispatch({ type: 'dataConnect/setNewModel', model: {} });
+                    this.setState({
+                        operation: 'create',
+                        visibleBox: true
+                    })
+                }}>
+                    <Card>
+                        <Icon type="plus-circle-o" />
+                    </Card>
+                </CardGrid>
+            );
+            
+            return cards;
+        }
+
+        return (
+            <Form className='form-base' size='small'>
+                {
+                    dataSource.newOne.type=='file'?(
+                        <div>
+                            <Divider orientation="left">选择文件</Divider>
+                            <UploadDragger
+                                className='upload'
+                                name='file' // 上传到后台的文件名
+                                fileList={dataSource.newOne.fileList}
+                                accept='application/vnd.ms-excel,.csv' // 只支持excel和csv格式文件
+                                action='//jsonplaceholder.typicode.com/posts/'
+                                headers={{authorization: 'authorization-text'}}
+                                beforeUpload={(file, fileList) => {
+                                    const trueType = file.type === 'application/vnd.ms-excel';
+                                    if (!trueType) {
+                                        message.error('选择文件格式错误!');
+                                    }
+                                    const trueSize = file.size / 1024 / 1024 < 30;
+                                    if (!trueSize) {
+                                        message.error('选择文件过大!');
+                                    }
+                                    return trueType && trueSize;
+                                }}
+                                onChange={(info) => {
+                                    const file = info.file;
+                                    let fileList = info.fileList;
+                                    fileList = fileList.slice(-1); // 只保留最后一个
+                                    if (info.file.status !== 'uploading') {
+                                        console.log(file, info.fileList);
+                                        const trueType = file.type === 'application/vnd.ms-excel';
+                                        const trueSize = file.size / 1024 / 1024 < 30;
+                                        if(!trueType || !trueSize) {
+                                            fileList = [];
+                                        }
+                                    }
+                                    if (file.status === 'done') {
+                                        message.success(`${file.name} 文件上传成功`);
+                                    } else if (file.status === 'error') {
+                                        fileList = [];
+                                        message.error(`${file.name} 文件上传失败.`);
+                                    }
+                                    dispatch({ type: 'dataSource/setNewModelField', name: 'fileList', value: fileList })
+                                }}
+                            >
+                                <p className="ant-upload-drag-icon">
+                                    <Icon type="inbox" />
+                                </p>
+                                <p className="ant-upload-text">点击选择或拖动文件到该区域</p>
+                                <p className="ant-upload-hint">仅支持上传单个30MB以内EXCEL/CSV格式文件</p>
+                            </UploadDragger>
+                        </div>
+                    ):(
+                        <Layout className='dataconnect'>
+                            <Content>
+                                <Card title={
+                                    <Row type='flex' justify='end'>
+                                        <Search
+                                            style={{ width: '200px' }}
+                                            placeholder="请输入关键字"
+                                            onSearch={value => console.log(value)}
+                                        />
+                                    </Row>
+                                }>
+                                    <div className='dataconnect-list'>
+                                        { generateCard() }
+                                    </div>
+                                    <DataConnectBox visibleBox={visibleBox} hideBox={() => {this.setState({visibleBox: false})}} operation={operation} />
+                                </Card>
+                            </Content>
+                        </Layout>
+                    )
+                }
+            </Form>
+        );
+    }
+}
+
+function mapStateToProps({ present: {dataSource, dataConnect} }) {
+    return { dataSource, dataConnect }
+}
+
+export default connect(mapStateToProps)(DataConnectConfig);

+ 23 - 5
app/components/datasource/dataSourceDetail.jsx

@@ -5,10 +5,11 @@ const { Header, Content } = Layout
 const Step = Steps.Step
 const TabPane = Tabs.TabPane
 import { Link } from 'react-router-dom'
+import DataConnectConfig from './dataConnectConfig'
 import BaseConfig from './baseConfig'
-import ConnectConfig from './connectConfig'
 import ColumnConfig from './columnConfig'
 import AccessConfig from './accessConfig'
+import OtherConfig from './otherConfig'
 import './dataSourceDetail.less'
 
 class DataSourceDetail extends React.Component {
@@ -49,17 +50,34 @@ class DataSourceDetail extends React.Component {
         const { dispatch } = this.props;
         const { type, mode, code, tab, current } = this.state;
         const steps = [{
+            tabName: 'base',
+            title: '数据连接配置',
+            content: <DataConnectConfig mode={mode} />
+        }, {
+            tabName: 'column',
+            title: '数据列配置',
+            content: <ColumnConfig mode={mode} />
+        }, {
+            tabName: 'access',
+            title: '权限配置',
+            content: <AccessConfig mode={mode} />
+        }, {
+            tabName: 'other',
+            title: '完成',
+            content: <OtherConfig mode={mode} />
+        }];
+        const tabs = [{
             tabName: 'base',
             title: '属性配置',
-            content: <BaseConfig/>
+            content: <BaseConfig mode={mode} />
         }, {
             tabName: 'column',
             title: '数据列配置',
-            content: <ColumnConfig />
+            content: <ColumnConfig mode={mode} />
         }, {
             tabName: 'access',
             title: '权限配置',
-            content: <AccessConfig />
+            content: <AccessConfig mode={mode} />
         }];
         return (
             <Layout className='layout-datasource-detail'>
@@ -102,7 +120,7 @@ class DataSourceDetail extends React.Component {
                                 dispatch({ type: 'dataSource/remoteModify', code: this.props.match.params.code });
                             }}>保存修改</Button>}
                         >
-                            {steps.map((item, index) => {
+                            {tabs.map((item, index) => {
                                 return <TabPane className='tab-datasource' key={item.tabName} tab={item.title}>
                                     {item.content}
                                 </TabPane>

+ 39 - 12
app/components/datasource/dataSourceDetail.less

@@ -39,18 +39,45 @@
                 textarea {
                     margin-top: 4px;
                 }
-                .upload {
-                    .ant-upload-drag {
-                        margin: 0 auto;
-                        width: 80%;
-                        margin: 0 0 0 16.666666%;
-                        width: 83%;
-                    }
-                    .ant-upload-list {
-                        margin: 0 auto;
-                        width: 80%;
-                        margin: 0 0 0 16.666666%;
-                        width: 83%;
+                .dataconnect {
+                    .ant-card-body {
+                        padding: 16px;
+                        .dataconnect-list {
+                            .dataconnect-card {
+                                width: 160px;
+                                height: 160px;
+                                padding: 0;
+                                margin: 5px;
+                                cursor: pointer;
+                                .ant-card {
+                                    height: 100%;
+                                    .ant-card-head {
+                                        background: #F5F5F5;
+                                        .selected {
+                                            width: 60px;
+                                            height: 60px;
+                                            background-size: cover;
+                                            background-image: url(../../assets/selected.png);
+                                            position: absolute;
+                                            right: 0px;
+                                            top: 0px;
+                                            border-top-right-radius: 1px;
+                                        }
+                                    }
+                                    .ant-card-body {
+                                        padding: 10px;
+                                        height: 100%;
+                                        display: flex;
+                                        width: 100%;
+                                        flex-direction: column;
+                                        justify-content: center;
+                                        .anticon-plus-circle-o {
+                                            font-size: 60px;
+                                        }
+                                    }
+                                }
+                            }
+                        }
                     }
                 }
             }

+ 84 - 0
app/components/datasource/otherConfig.jsx

@@ -0,0 +1,84 @@
+import React from 'react'
+import { Button, Form, Row, Col, Input, InputNumber, Select, Icon, Menu, Dropdown, Divider, Upload, message } from 'antd'
+const FormItem = Form.Item
+const SelectOption = Select.Option
+const OptionGroup = Select.OptGroup
+const InputGroup = Input.Group
+const SubMenu = Menu.SubMenu
+const MenuItem = Menu.Item
+const MenuItemGroup = Menu.ItemGroup
+const UploadDragger = Upload.Dragger
+import { connect } from 'dva'
+import '../../models/dataSource'
+import '../../models/dataConnect'
+
+const OtherConfig = ({ dataSource, dataConnect, dispatch, mode }) => {
+
+    const formItemLayout = {
+        labelCol: { span: 4 },
+        wrapperCol: { span: 20 },
+    };
+
+    const dataSourceLinkMenu = (
+        <Menu
+            className='menu-datasource-link'
+            onClick={(e) => {
+                const model = dataConnect.list[e.key];
+                dispatch({ type: 'dataSource/setNewModelFields', fields: [
+                    { name: 'address', value: model.address },
+                    { name: 'port', value: model.port },
+                    { name: 'dbType', value: model.dbType },
+                    { name: 'dbName', value: model.dbName },
+                    { name: 'userName', value: model.userName },
+                    { name: 'password', value: model.password }
+                ] });
+            }}
+        >
+            {
+                dataConnect.list.map((l, i) => {
+                    return <MenuItem key={i}>{l.name}</MenuItem>
+                })
+            }
+        </Menu>
+    );
+
+
+    return (
+        <Form className='form-base' size='small'>
+            <FormItem label='数据源名称' {...formItemLayout}>
+                <Input
+                    value={dataSource.newOne.name}
+                    onChange={(e) => { dispatch({ type: 'dataSource/setNewModelField', name: 'name', value: e.target.value }) }}>
+                </Input>
+            </FormItem>
+            <FormItem label='标签' {...formItemLayout}>
+                <Select
+                    mode="tags"
+                    placeholder='多个标签使用逗号或空格分隔'
+                    tokenSeparators={[',', ' ']}
+                    value={dataSource.newOne.tags}
+                    dropdownStyle={{display: 'none'}}
+                    onChange={(value) => {
+                        dispatch({ type: 'dataSource/setNewModelField', name: 'tags', value: value });
+                    }}
+                >
+                </Select>
+            </FormItem>
+            <FormItem className='textarea-desc' label='说明' {...formItemLayout}>
+                <Input.TextArea
+                    autosize={{ minRows: 2, maxRows: 5 }}
+                    value={dataSource.newOne.description}
+                    onChange={(e) => {
+                        dispatch({ type: 'dataSource/setNewModelField', name: 'description', value: e.target.value });
+                    }}
+                />
+            </FormItem>
+        </Form>
+    );
+}
+
+function mapStateToProps({ present: {dataSource, dataConnect} }) {
+    return { dataSource, dataConnect }
+}
+
+export default connect(mapStateToProps)(OtherConfig);

+ 2 - 0
app/constants/url.js

@@ -42,5 +42,7 @@ const URLS = {
     CHART_DETAIL: BASE_URL + '/getChartsConfig', // 获得单个图表详细数据
 
     CHART_BAR_OPTION: BASE_URL + '/showHistogram', // 请求柱状图展示数据
+
+    CHART_PIE_OPTION: BASE_URL + '/showPie', // 请求饼展示数据
 }
 export default URLS

+ 6 - 2
app/models/chart.js

@@ -45,7 +45,6 @@ export default {
             }
         },
         *remoteDetail(action, { select, call, put }) {
-            const chartDesigner = yield select(state => state.present.chartDesigner);
             const code = action.code;
             if(!code){
                 return
@@ -112,13 +111,18 @@ export default {
                             }
                         }
                     }
-                    yield put({ type: 'chartDesigner/changeFields', fields: [
+                    yield put({ type: 'chartDesigner/defaultChangeFields', fields: [
                         { name: 'code', value: data.code },
                         { name: 'header', value: data.header },
                         { name: 'baseConfig', value: data.baseConfig },
                         { name: 'preparing', value: data.preparing },
                         { name: 'barConfig', value: data.barConfig }
                     ] });
+                    
+                    yield put({ type: 'chartDesigner/changeDataSource', value: {
+                        dataSource: data.baseConfig.dataSource,
+                        viewType: data.baseConfig.viewType
+                    } });
                 }else {
                     console.log(res);
                 }

+ 83 - 120
app/models/chartDesigner.js

@@ -11,59 +11,17 @@ export default {
             code: null,
             header: { label: '未命名' },
             baseConfig: { dataSource: '', viewType: '' },
-            preparing: { groupBy: [] },
+            preparing: { groupBy: {} },
             aggregateTableConfig: {},
             dataViewConfig: {},
             barConfig: { xAxis: { column: {}, granularity: {} }, yAxis: { column: {}, gauge: {} } },
-            pieConfig: { series: [] },
+            pieConfig: { xAxis: { column: {}, granularity: {} }, yAxis: { column: {}, gauge: {} } },
             style: {},
             filters: [],
             chartOption: {},
             autoRefresh: true
         },
-        columns: [{
-            label: '编号',
-            name: 'ID',
-            type: 'index'
-        }, {
-            label: '姓名',
-            name: 'name',
-            type: 'string'
-        }, {
-            label: '出生日期',
-            name: 'BIRTH',
-            type: 'time'
-        }, {
-            label: '性别',
-            name: 'GENDER',
-            type: 'categorical',
-            selection: ['男', '女']
-        }, {
-            label: '部门',
-            name: 'DEPARTMENT',
-            type: 'categorical',
-            selection: ['开发部', '测试部', '实施部']
-        }, {
-            label: '入职日期',
-            name: 'ENTRYDATE',
-            type: 'time'
-        }, {
-            label: '当月工作时数',
-            name: 'WORKHOURS',
-            type: 'scale'
-        }, {
-            label: '基本薪资',
-            name: 'BASICSALARY',
-            type: 'scale'
-        }, {
-            label: '绩效薪资',
-            name: 'PERFORMANCE',
-            type: 'scale'
-        }, {
-            label: '实发薪资',
-            name: 'SALARY',
-            type: 'scale'
-        }],
+        columns: [],
         allPermission: [
 			{ value: 'owner', name: '创建人' },
 			{ value: 'anyone', name: '所有人' }
@@ -95,19 +53,14 @@ export default {
             }
         },
         pieConfig: {
-            series: [{
-                name: '花花',
-                value: 30
-            }, {
-                name: '欢欢',
-                value: 21
-            }, {
-                name: '强强',
-                value: 64
-            }, {
-                name: '糖糖',
-                value: 49
-            }]
+            xAxis: {
+                column: {},
+                granularity: {}
+            },
+            yAxis: {
+                column: {},
+                gauge: {}
+            }
         },
         style: {
 
@@ -117,9 +70,22 @@ export default {
         autoRefresh: true
     },
     reducers: {
+        /**
+         * 初始化model字段值
+         * 设置撤销重做的起点,不进入撤销重做的历史
+         */
+        defaultSetFields(state, action) {
+            const { fields } = action;
+            let obj = {};
+            fields.map(f => {
+                obj[f.name] = f.value;
+            });
+            let newState = Object.assign({}, state, obj);
+            return newState;
+        },
         /**
          * 更新model字段值方法1
-         * 1. 为保持撤销重做的功能有效性,能够撤销重做的action动作才使用该通用方法,否则下方写成特殊方法
+         * 1. 为保持撤销重做的功能有效性,能够撤销重做的action动作才使用该方法
          * 2. 对数据刷新没有影响的model字段改变一般用该action
          */
         setField(state, action) {
@@ -172,6 +138,20 @@ export default {
         }
     },
     effects: {
+        /**
+         * 初始化批量更新model字段值
+         * 触发数据刷新、不进入撤销重做历史
+         */
+        *defaultChangeFields(action, { select, call, put }) {
+            const { fields } = action;
+            yield put({ type: 'defaultSetFields', fields });
+
+            const chartDesigner = yield select(state => state.present.chartDesigner);
+            const { autoRefresh } = chartDesigner;
+            if(autoRefresh) {
+                yield put({ type: 'fetchChartData' });
+            }
+        },
         /**
          * 更新model字段值方法2
          * 可能影响到数据刷新的model字段改变一般用该action
@@ -348,7 +328,7 @@ export default {
             if(viewType == 'bar') {
                 yield put({ type: 'fetchBarData' });
             }else if(viewType == 'pie') {
-                //yield put({ type: 'fetchPieData' });
+                yield put({ type: 'fetchPieData' });
             }else {
                 console.log(13)
             }
@@ -357,26 +337,9 @@ export default {
             try {
                 const chartDesigner = yield select(state => state.present.chartDesigner);
                 const { barConfig, preparing } = chartDesigner;
-                const res = yield call(service.fetch, {
-                    url: URLS.CHART_BAR_OPTION,
-                    body: {
-                        "tableName": "TEST_BI_DATA",
-                        "groups": preparing.groupBy.map(g => g.key),
-                        "xAxis": {
-                            "columnRename": barConfig.xAxis.column.value,
-                            "columnType": barConfig.xAxis.column.type,
-                            "showDataType": barConfig.xAxis.granularity.value
-                        },
-                        "yAxis": {
-                            "columnRename": barConfig.yAxis.column.value,
-                            "showDataType": barConfig.yAxis.gauge.value
-                        }
-                    }
-                });
-
-                console.log({
+                const body = {
                     "tableName": "TEST_BI_DATA",
-                    "groups": preparing.groupBy.map(g => g.key),
+                    "groups": preparing.groupBy.key ? [preparing.groupBy.key] : [],
                     "xAxis": {
                         "columnRename": barConfig.xAxis.column.value,
                         "columnType": barConfig.xAxis.column.type,
@@ -386,58 +349,58 @@ export default {
                         "columnRename": barConfig.yAxis.column.value,
                         "showDataType": barConfig.yAxis.gauge.value
                     }
-                })
+                };
                 
-                res.viewType = 'bar';
-                res.data.data.xTitle = barConfig.xAxis?`${barConfig.xAxis.column.label}${barConfig.xAxis.granularity.value?'('+barConfig.xAxis.granularity.label+')':''}`:null
-                res.data.data.yTitle = barConfig.yAxis?`${barConfig.yAxis.column.label}${barConfig.yAxis.gauge.value?'('+barConfig.yAxis.gauge.label+')':''}`:null
-                yield put({ type: 'setChartOption', option: res });
-
+                console.log(body)
+                let res = yield call(service.fetch, {
+                    url: URLS.CHART_BAR_OPTION,
+                    body: body
+                });
+                console.log('res: ', res);
+                if(!res.err && res.data.code > 0) {
+                    res.viewType = 'bar';
+                    res.data.data.xTitle = barConfig.xAxis?`${barConfig.xAxis.column.label}${barConfig.xAxis.granularity.value?'('+barConfig.xAxis.granularity.label+')':''}`:null
+                    res.data.data.yTitle = barConfig.yAxis?`${barConfig.yAxis.column.label}${barConfig.yAxis.gauge.value?'('+barConfig.yAxis.gauge.label+')':''}`:null
+                    yield put({ type: 'setChartOption', option: res });
+                }else {
+                    yield put({ type: 'setChartOption', option: {} });
+                }
             }catch(e) {
+                console.error(e);
                 yield put({ type: 'setChartOption', option: {} });
             }
         },
         *fetchPieData(action, { select, call, put }) {
             try {
                 const chartDesigner = yield select(state => state.present.chartDesigner);
-                const { barConfig, preparing } = chartDesigner;
-                const res = yield call(service.fetch, {
-                    url: URLS.CHART_BAR_OPTION,
-                    body: {
-                        "tableName": "TEST_BI_DATA",
-                        "groups": preparing.groupBy.map(g => g.key),
-                        "xAxis": {
-                            "columnRename": barConfig.xAxis.column.value,
-                            "columnType": barConfig.xAxis.column.type,
-                            "dataType": barConfig.xAxis.granularity.value
-                        },
-                        "yAxis": {
-                            "columnRename": barConfig.yAxis.column.value,
-                            "dataType": barConfig.yAxis.gauge.value
-                        }
-                    }
-                });
-
-                console.log({
-                    "tableName": "TEST_BI_DATA",
-                    "groups": preparing.groupBy.map(g => g.key),
-                    "xAxis": {
-                        "columnRename": barConfig.xAxis.column.value,
-                        "columnType": barConfig.xAxis.column.type,
-                        "dataType": barConfig.xAxis.granularity.value
+                const { pieConfig, preparing } = chartDesigner;
+                const body = {
+                    tableName: "TEST_BI_DATA",
+                    legendData: {
+                        columnRename: pieConfig.xAxis.column.value,
+                        columnType: pieConfig.xAxis.column.type,
+                        showDataType: pieConfig.xAxis.granularity.value
                     },
-                    "yAxis": {
-                        "columnRename": barConfig.yAxis.column.value,
-                        "dataType": barConfig.yAxis.gauge.value
+                    series: {
+                        columnRename: pieConfig.yAxis.column.value,
+                        columnName: pieConfig.yAxis.column.label,
+                        showDataType: pieConfig.yAxis.gauge.value
                     }
-                })
-                
-                res.viewType = 'bar';
-                res.data.data.xTitle = barConfig.xAxis?`${barConfig.xAxis.column.label}${barConfig.xAxis.granularity.value?'('+barConfig.xAxis.granularity.label+')':''}`:null
-                res.data.data.yTitle = barConfig.yAxis?barConfig.yAxis.column.label:null;
-                res.data.data.gauge = barConfig.yAxis?barConfig.yAxis.gauge.label:null;
-                yield put({ type: 'setChartOption', option: res });
+                };
+                console.log(body);
+                let res = yield call(service.fetch, {
+                    url: URLS.CHART_PIE_OPTION,
+                    body: body
+                });
 
+                if(!res.err && res.data.code > 0) {
+                    console.log('res: ', res);
+                    res.viewType = 'pie';
+                    res.data.data.columnName = pieConfig.xAxis.column.label + (pieConfig.xAxis.granularity.value ? '('+pieConfig.xAxis.granularity.label+')' : ''),
+                    yield put({ type: 'setChartOption', option: res });
+                }else {
+                    yield put({ type: 'setChartOption', option: res });
+                }
             }catch(e) {
                 yield put({ type: 'setChartOption', option: {} });
             }

+ 0 - 18
app/models/dataConnect.js

@@ -9,24 +9,6 @@ export default {
         newOne: {}
     },
     reducers: {
-        testData(state, action) {
-            let list = state.list;
-            for(let i = 0; i < 4; i++) {
-                let newOne = {
-                    name: 'tttttt'+i,
-                    type: 'oracle',
-                    address: '2',
-                    port: '3',
-                    userName: '2222',
-                    password: 'aaaww',
-                    description: ' dddddddddddddddddddddd'
-                };
-                newOne.key = new Date().getMilliseconds()+(Math.random()*100).toFixed(0)+i;
-                newOne.code = new Date().getMilliseconds()+(Math.random()*100).toFixed(0)+i;
-                list.push(newOne);
-            }
-            return Object.assign({}, state, {list});
-        },
         list(state, action) {
             const data = action.data;
             return Object.assign({}, state, {list: data});

+ 1 - 1
index.html

@@ -2,7 +2,7 @@
 <html lang="en">
 <head>
   <meta charset="UTF-8">
-  <title>Demo</title>
+  <title>BI-商业智能报表</title>
   <link rel="stylesheet" href="index.css" />
 </head>
 <body>