Explorar o código

数据源关联图表

zhuth %!s(int64=7) %!d(string=hai) anos
pai
achega
3cacf49313

+ 128 - 0
app/components/chart/chooseDataSourceBox.jsx

@@ -0,0 +1,128 @@
+import React from 'react'
+import { Modal, Form, Row, Col, Input, InputNumber, Select, Icon, Menu, Dropdown, Table, Radio, Tag } 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 '../../models/dataSource'
+import './chooseDataSourceBox.less'
+
+class ChooseDataSourceBox extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            selectedRecord: null
+        }
+    }
+
+    changeSelected = (record) => {
+        this.setState({
+            selectedRecord: Object.assign({}, record)
+        });
+    }
+
+    okHandler = (model) => {
+        const { selectedRecord } = this.state;
+        const { dispatch } = this.props;
+        if(selectedRecord) {
+            dispatch({ type: 'chartDesigner/changeDataSource', value: {
+                dataSource: { key: selectedRecord.code, label: selectedRecord.name}
+            } });
+            dispatch({ type: 'main/redirect', path: '/chart/create' });
+        }else {
+            this.props.hideBox()
+        }
+    }
+
+    render() {
+        const { selectedRecord } = this.state;
+        const { operation, dispatch, dataSource, visibleBox, hideBox } = this.props
+        const formItemLayout = {
+            labelCol: { span: 4 },
+            wrapperCol: { span: 20 },
+        };
+
+        const columns = [{
+            title: '选择',
+            key: 'selected',
+            width: 50,
+            render: (text, record) => {
+                return <Radio checked={selectedRecord ? selectedRecord.code == record.code : false}/>
+            }
+        }, {
+            title: '名称',
+            dataIndex: 'name',
+            key: 'name',
+            width: 100,
+            render: (text, record) => {
+                return <div className='datasource-name'>
+                    <div className={`datasource-type type-${record.type.key}`}></div>
+                    <div>{text}</div>
+                </div>
+            }
+        }, {
+            title: '标签',
+            dataIndex: 'tags',
+            key: 'tag',
+            width: 150,
+            render: (text, record) => {
+                text=text.join(',');
+                let tags = text ? text.split(',').map((t, i) => {
+                    return <Tag className='datasource-tag' key={i}>{t}</Tag>
+                }) : '';
+                return (<div>
+                    {tags}
+                </div>)
+            }
+        }, {
+            title: '说明',
+            dataIndex: 'description',
+            key: 'description',
+            width: 200
+        }, {
+            title: '图表',
+            dataIndex: 'chartSum',
+            key: 'chartSum',
+            width: 80
+        }];
+
+        return (
+            <Modal
+                className='choosedatasource-box'
+                title='选择数据源'
+                visible={visibleBox}
+                onOk={this.okHandler}
+                onCancel={hideBox}
+                maskClosable={false}
+                destroyOnClose={true}
+            >
+                <Table
+                    className='choosedatasource-table'
+                    columns={columns}
+                    dataSource={dataSource.list}
+                    size='small'
+                    scroll={{x: false, y: 471}}
+                    pagination={false}
+                    showHeader={false}
+                    onRow={(record) => {
+                        return {
+                            onClick: () => {
+                                this.changeSelected(record);
+                            }
+                        };
+                    }}
+                />
+            </Modal>
+        )
+    }
+}
+
+function mapStateToProps({ present: { dataSource } }) {
+    return { dataSource: dataSource };
+}
+
+export default connect(mapStateToProps)(ChooseDataSourceBox)

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

@@ -0,0 +1,7 @@
+.choosedatasource-box {
+    .ant-modal-body {
+        padding: 0;
+        max-height: 50vh;
+        overflow-y: auto;
+    }
+}

+ 50 - 13
app/components/chart/list.jsx

@@ -9,12 +9,15 @@ const CardGrid = Card.Grid
 import { connect } from 'dva'
 import '../../models/chart'
 import './list.less'
+import ChooseDataSourceBox from './chooseDataSourceBox';
 
 
 class ChartList extends React.Component {
     constructor(props) {
         super(props);
         this.state = {
+            selectedCode: -1,
+            visibleBox: false
         }
     }
 
@@ -23,7 +26,7 @@ class ChartList extends React.Component {
     }
 
     /**
-     * 设置卡片容器宽度 = 每行卡片数量 * 卡片宽度
+     * 设置卡片容器宽度 = 每行最大卡片数量 * 卡片宽度
      */
     setBodyWidth() {
         const chartBody = document.getElementsByClassName('chart-body')[0]; // 卡片容器
@@ -32,16 +35,31 @@ class ChartList extends React.Component {
         const pPadding = 10 + 10; // 父级容器左右padding
         const cWidth = 207; // 每个卡片宽度
         const cMargin = 5 + 5; // 每个卡片左右margin
+        const pTrueWidth = pWidth - pPadding; // 父容器实际可用宽度
+        const cTrueWidth = cWidth + cMargin; // 卡片实际占用宽度
+        const count = Math.floor(pTrueWidth/cTrueWidth); // 每行最大卡片数量
 
-
-        let col = Math.floor((pWidth - pPadding)/(cWidth + cMargin));
-        chartBody.style.width = col * (cWidth + cMargin)  + 'px';
+        chartBody.style.width = count * cTrueWidth  + 'px';
     }
 
-    generateCard() { 
+    generateCard() {
+        const dispatch = this.props.dispatch;
         const list = this.props.chart.list;
+
+        const operationMenu = (
+            <Menu className='menu-operation' onClick={() => {
+                dispatch({ type: 'chart/remoteDelete', code: this.state.selectedCode });
+            }}>
+                <Menu.Item>
+                    <Icon type='delete'/>删除
+                </Menu.Item>
+            </Menu>
+        )
+
         return list.map( (l, i) => (
-            <CardGrid className='chart-card' key={i}>
+            <CardGrid className='chart-card' key={i} onClick={() => {
+                this.setState({ selectedCode: l.code })
+            }}>
                 <Card
                     title={
                         <Row type='flex' justify='space-between'>
@@ -53,7 +71,10 @@ class ChartList extends React.Component {
                     }
                     cover={
                         <Col style={{ padding: '10px' }}>
-                            <Row style={{ height: '164px' }}>
+                            <Row style={{ height: '164px' }} onClick={() => {
+                                dispatch({ type: 'chartDesigner/reset' });
+                                dispatch({ type: 'main/redirect', path: '/chart/' + l.code });
+                            }}>
                                 <img style={{ height: '100px', marginTop: '10%' }} src='https://test-feapp.oss-cn-beijing.aliyuncs.com/feapp/master_a_20180711/images/chart/empty_column_chart.svg'></img>
                             </Row>
                             <Row type='flex' justify='end' align='bottom'>
@@ -62,7 +83,9 @@ class ChartList extends React.Component {
                                     <Row>{new Date(l.createTime).format('yyyy-MM-dd')}</Row>
                                 </Col>
                                 <Col span={2} style={{ textAlign: 'right' }}>
-                                    <Icon type="ellipsis" />
+                                    <Dropdown overlay={operationMenu} trigger={['click']}>
+                                        <Icon type="ellipsis" />
+                                    </Dropdown>
                                 </Col>
                             </Row>
                         </Col>
@@ -74,6 +97,8 @@ class ChartList extends React.Component {
     }
 
     render() {
+        const { visibleBox } = this.state;
+        const dispatch = this.props.dispatch;
         return (
             <Layout className='chart-list'>
                 <Content>
@@ -86,11 +111,23 @@ class ChartList extends React.Component {
                                 />
                             </Col>
                             <Col >
-                                <Link to='/chart/chartdesigner/create'>
-                                    <Button>
-                                        <Icon type="area-chart" />创建图表
-                                    </Button>
-                                </Link>
+                                <Button onClick={() => {
+                                    this.setState({
+                                        visibleBox: true
+                                    });
+                                }}>
+                                    <Icon type="area-chart" />创建图表
+                                </Button>
+                                <ChooseDataSourceBox visibleBox={visibleBox} hideBox={() => {
+                                    this.setState({
+                                        visibleBox: false
+                                    });
+                                }}/>
+                                {/* <Button onClick={() => {
+                                    dispatch({ type: 'main/redirect', path: '/chart/create' });
+                                }}>
+                                    <Icon type="area-chart" />创建图表
+                                </Button> */}
                             </Col>
                         </Row>
                     }>

+ 6 - 0
app/components/chart/list.less

@@ -38,4 +38,10 @@
             }
         }
     }
+}
+.menu-operation {
+    padding: 0;
+    .ant-dropdown-menu-item .anticon {
+        margin-right: 6px;
+    }
 }

+ 2 - 2
app/components/chartDesigner/charts/resolveChartOption.js

@@ -16,7 +16,7 @@ export default (config) => {
 }
 
 function barConfig(data) {
-    const { xAxis, serieses, xTitle, yTitle, gauge } = data;
+    const { xAxis, serieses, xTitle, yTitle } = data;
 
     let o = {
         tooltip : {
@@ -37,7 +37,7 @@ function barConfig(data) {
             name: xTitle || '横轴',
         }],
         yAxis: [{
-            name: yTitle?(`${yTitle}(${gauge})`) : '纵轴',
+            name: yTitle || '纵轴',
             type: 'value'
         }],
         series: serieses.map(s => {

+ 8 - 25
app/components/chartDesigner/content.jsx

@@ -22,24 +22,9 @@ import '../../data/charts/option/bar.json'
 import './content.less'
 
 class ChartDesignerContent extends React.Component {
-
-    constructor(props) {
-        super(props);
-        this.state = {
-            autoRefresh: true // 自动刷新
-        }
-    }
-
-    changeRefreshMode = () => {
-        this.setState({
-            autoRefresh: !this.state.autoRefresh
-        })
-    }
-
     render() {
-        const { autoRefresh } = this.state;
         const { chartDesigner, dispatch } = this.props;
-        const { baseConfig } = chartDesigner;
+        const { baseConfig, autoRefresh } = chartDesigner;
         const { viewType } = baseConfig;
         const formItemLayout = {
             labelCol: { span: 8 },
@@ -79,16 +64,14 @@ class ChartDesignerContent extends React.Component {
                     </Tabs>
                     <Footer className='sider-footer'>
                         <div className='fresh-bar'>
-                            <Switch defaultChecked checkedChildren='自动刷新' unCheckedChildren='手动刷新' onChange={this.changeRefreshMode}/>
-                            <Button hidden={autoRefresh} size='small' onClick={() => {
-                                const viewType = chartDesigner.baseConfig.viewType.key;
-                                if(viewType == 'bar') {
-                                    dispatch({ type: 'chartDesigner/fetchBarData'});
-                                }else if(viewType == 'pie') {
-                                    console.log(111);
-                                    //dispatch({ type: 'chartDesigner/fetchPieData' });
+                            <Switch defaultChecked checkedChildren='自动刷新' unCheckedChildren='手动刷新' onChange={(checked) => {
+                                // 设为自动刷新后立即请求一次数据
+                                if(checked) {
+                                    dispatch({ type: 'chartDesigner/fetchChartData' });
                                 }
-                            }}
+                                dispatch({ type: 'chartDesigner/setAutoRefresh', value: checked })
+                            }}/>
+                            <Button hidden={autoRefresh} size='small' onClick={() => { dispatch({ type: 'chartDesigner/fetchChartData'}) }}
                             >立即刷新</Button>
                         </div>
                     </Footer> 

+ 9 - 11
app/components/chartDesigner/header.jsx

@@ -13,7 +13,7 @@ const Header = ({ chartDesigner, dispatch }) => {
         <div className='header'>
             <div className='header-item toolbar-back'>
                 <Button onClick={() => {
-                    window.history.back();
+                    dispatch({ type: 'main/redirect', path: '/chart' });
                 }}>
                     返回
                 </Button>
@@ -28,21 +28,13 @@ const Header = ({ chartDesigner, dispatch }) => {
                         }}
                     />}
                     onChange={(e) => {
-                        dispatch({ type: 'chartDesigner/setModel', name: 'header', value: { label: e.target.value } });
+                        dispatch({ type: 'chartDesigner/setField', name: 'header', value: { label: e.target.value } });
                     }}
                     value={chartDesigner.header.label}
                 />
             </div>
             <div className='header-item toolbar-buttons'>
                 <div className=''>
-                    {/* <Button onClick={() => {
-                        const viewType = chartDesigner.baseConfig.viewType.key;
-                        if(viewType == 'bar') {
-                            dispatch({ type: 'chartDesigner/fetchBarData'});
-                        }else if(viewType == 'pie') {
-                            dispatch({ type: 'chartDesigner/fetchPieData' });
-                        }
-                    }}>请求测试</Button> */}
                     <Button className='button-uodo' icon='undo' onClick={() => {
                         dispatch(ActionCreators.undo());
                     }}>撤销</Button>
@@ -50,7 +42,13 @@ const Header = ({ chartDesigner, dispatch }) => {
                         dispatch(ActionCreators.redo());
                     }}>重做</Button>
                     <Button className='button-uodo' >预览</Button>
-                    <Button className='button-uodo' >保存</Button>
+                    <Button className='button-uodo' onClick={() => {
+                        if(chartDesigner.code && chartDesigner.code != -1) {
+                            dispatch({ type: 'chartDesigner/remoteModify' });
+                        }else {
+                            dispatch({ type: 'chartDesigner/remoteAdd' });
+                        }
+                    }}>保存</Button>
                 </div>
             </div>
         </div>

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

@@ -38,7 +38,7 @@ class AggregateTableConfigForm extends React.Component {
 						mode='multiple'
 						labelInValue={true}
 						onChange={(value) => {
-							props.dispatch({ type: 'chartDesigner/setModel', name: 'aggregateTable', value: { ...props.chartDesigner.aggregateTable, targetColumn: value } });
+							props.dispatch({ type: 'chartDesigner/changeField', name: 'aggregateTable', value: { ...props.chartDesigner.aggregateTable, targetColumn: value } });
 							
 						}}
 					>
@@ -52,7 +52,7 @@ class AggregateTableConfigForm extends React.Component {
 						value={props.chartDesigner.aggregateTable.statistics}
 						options={statisticsOptions.map((s)=>{return s.label})}
 						onChange={(value) => {
-							props.dispatch({ type: 'chartDesigner/setModel', name: 'aggregateTable', value: { ...props.chartDesigner.aggregateTable, statistics: value } });
+							props.dispatch({ type: 'chartDesigner/changeField', name: 'aggregateTable', value: { ...props.chartDesigner.aggregateTable, statistics: value } });
 						}}
 					/>
 				</FormItem>

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

@@ -51,7 +51,7 @@ const BarConfigForm = ({ chartDesigner, dispatch, formItemLayout }) => {
 						if(items.length > 1) {
 							granularity = { value: items[1].value, label: items[1].label };
 						}
-						dispatch({ type: 'chartDesigner/setModel', name: 'barConfig', value: { ...chartDesigner.barConfig, xAxis: { column, granularity } } });
+						dispatch({ type: 'chartDesigner/changeField', name: 'barConfig', value: { ...chartDesigner.barConfig, xAxis: { column, granularity } } });
 					}}
 					displayRender={(label, selectedOptions) => {
 						let text = '';
@@ -93,10 +93,10 @@ const BarConfigForm = ({ 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/changeModel', name: 'barConfig', value: { ...chartDesigner.barConfig, yAxis: { column, gauge } } });
+						dispatch({ type: 'chartDesigner/changeField', name: 'barConfig', value: { ...chartDesigner.barConfig, yAxis: { column, gauge } } });
 					}}
 					displayRender={(label, selectedOptions) => {
 						let menu = selectedOptions.length > 0 ? <Menu
@@ -107,7 +107,7 @@ const BarConfigForm = ({ chartDesigner, dispatch, formItemLayout }) => {
 								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/setModel', name: 'barConfig', value: { 
+									dispatch({ type: 'chartDesigner/changeField', name: 'barConfig', value: { 
 										...chartDesigner.barConfig,
 										yAxis: {
 											column: chartDesigner.barConfig.yAxis.column,
@@ -140,7 +140,7 @@ const BarConfigForm = ({ chartDesigner, dispatch, formItemLayout }) => {
 					labelInValue={true}
 					placeholder='请选择...'
 					onChange={(value) => {
-						props.dispatch({ type: 'chartDesigner/setModel', name: 'preparing', value: { ...props.chartDesigner.preparing, groupBy: value } });
+						dispatch({ type: 'chartDesigner/changeField', name: 'preparing', value: { ...chartDesigner.preparing, groupBy: value } });
 					}}
 					value={chartDesigner.preparing.groupBy}
 				>

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

@@ -17,12 +17,12 @@ class baseConfigForm extends React.Component {
 
 		return (
 			<Form hideRequiredMark={true}>
-				<FormItem label='数据源' {...formItemLayout}>
+				{/* <FormItem label='数据源' {...formItemLayout}>
 					<Select
 						value={props.chartDesigner.baseConfig.dataSource}
 						labelInValue={true}
 						onChange={(value) => {
-							props.dispatch({ type: 'chartDesigner/setModel', name: 'baseConfig', value: {
+							props.dispatch({ type: 'chartDesigner/changeDataSource', name: 'baseConfig', value: {
 								...props.chartDesigner.baseConfig, dataSource: value } });
 						}}
 					>
@@ -30,14 +30,14 @@ class baseConfigForm extends React.Component {
 							return (<Option key={`dataSource-${i}`} value={dataSource.code}>{dataSource.name}</Option>)
 						})}
 					</Select>
-				</FormItem>
+				</FormItem> */}
 				<FormItem label='可视化模式' {...formItemLayout}>
 					<Select
 						dropdownClassName='baseconfig-viewtype'
 						value={props.chartDesigner.baseConfig.viewType.key}
 						dropdownMatchSelectWidth={false}
 						onChange={(value, item) => {
-							props.dispatch({ type: 'chartDesigner/setModel', name: 'baseConfig', value: { ...props.chartDesigner.baseConfig, viewType: { key: value, label: item.title } } });
+							props.dispatch({ type: 'chartDesigner/changeField', name: 'baseConfig', value: { ...props.chartDesigner.baseConfig, viewType: { key: value, label: item.title } } });
 						}}
 					>
 					{

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

@@ -20,7 +20,7 @@ class DataViewConfigForm extends React.Component {
 						value={props.chartDesigner.dataView.targetColumn}
 						labelInValue={true}
 						onChange={(value) => {
-							props.dispatch({ type: 'chartDesigner/setModel', name: 'dataView', value: { ...props.chartDesigner.dataView, targetColumn: value }});
+							props.dispatch({ type: 'chartDesigner/changeField', name: 'dataView', value: { ...props.chartDesigner.dataView, targetColumn: value }});
 						}}
 					>
 						{columns.map((c, i)=>{

+ 3 - 3
app/components/chartDesigner/sections/pieConfigForm.jsx

@@ -51,7 +51,7 @@ const PieConfigForm = ({ chartDesigner, dispatch, formItemLayout }) => {
 						if(items.length > 1) {
 							granularity = { value: items[1].value, label: items[1].label };
 						}
-						dispatch({ type: 'chartDesigner/setModel', name: 'barConfig', value: { ...chartDesigner.barConfig, xAxis: { column, granularity } } });
+						dispatch({ type: 'chartDesigner/changeField', name: 'barConfig', value: { ...chartDesigner.barConfig, xAxis: { column, granularity } } });
 					}}
 					displayRender={(label, selectedOptions) => {
 						let text = '';
@@ -96,7 +96,7 @@ const PieConfigForm = ({ chartDesigner, dispatch, formItemLayout }) => {
 							column = { value: items[0].value, label: items[0].label };
 							gauge = { value: items[1].value, label: items[1].label };
 						}
-						dispatch({ type: 'chartDesigner/setModel', name: 'barConfig', value: { ...chartDesigner.barConfig, yAxis: { column, gauge } } });
+						dispatch({ type: 'chartDesigner/changeField', name: 'barConfig', value: { ...chartDesigner.barConfig, yAxis: { column, gauge } } });
 					}}
 					displayRender={(label, selectedOptions) => {
 						let menu = selectedOptions.length > 0 ? <Menu
@@ -107,7 +107,7 @@ const PieConfigForm = ({ chartDesigner, dispatch, formItemLayout }) => {
 								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/setModel', name: 'barConfig', value: { 
+									dispatch({ type: 'chartDesigner/changeField', name: 'barConfig', value: { 
 										...chartDesigner.barConfig,
 										yAxis: {
 											column: chartDesigner.barConfig.yAxis.column,

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

@@ -19,7 +19,7 @@ class PreparingForm extends React.Component {
 						labelInValue={true}
 						placeholder='请选择...'
 						onChange={(value) => {
-							props.dispatch({ type: 'chartDesigner/setModel', name: 'preparing', value: { ...props.chartDesigner.preparing, groupBy: value } });
+							props.dispatch({ type: 'chartDesigner/changeField', name: 'preparing', value: { ...props.chartDesigner.preparing, groupBy: value } });
 						}}
 						value={props.chartDesigner.preparing.groupBy}
 					>

+ 7 - 3
app/components/chartDesigner/sections/toolbar.jsx

@@ -19,7 +19,7 @@ class Toolbar extends React.Component {
         const props = this.props;
         const chartDesigner = props.chartDesigner;
         const filters = chartDesigner.filters;
-        props.dispatch({ type: 'chartDesigner/setModel', name: 'filters', value: filters.map( f => {
+        props.dispatch({ type: 'chartDesigner/changeField', name: 'filters', value: filters.map( f => {
             if(f.key == key) {
                 f = { ...f, using: !f.using }
             }
@@ -40,7 +40,7 @@ class Toolbar extends React.Component {
     }
 
     createFilters = (filters) => {
-        this.props.dispatch({ type: 'chartDesigner/setModel', name: 'filters', value: filters });
+        this.props.dispatch({ type: 'chartDesigner/changeField', name: 'filters', value: filters });
         this.hideFilterBox()
     }
 
@@ -122,7 +122,11 @@ class Toolbar extends React.Component {
                     </Tag>
                 </div>
                 <div className='tools'>
-                    <Button className='tools-button' size='small'>按钮</Button>
+                    <Button className='tools-button' size='small' onClick={() => {
+                        this.setState({
+                            visibleColumnBox: true
+                        })
+                    }}>查看列数据</Button>
                 </div>
                 <FilterBox key={Math.random()} columns={columns} filterData={filters} visibleFilterBox={visibleFilterBox} showFilterBox={this.showFilterBox} hideFilterBox={this.hideFilterBox} createFilters={this.createFilters} />  
             </div>

+ 1 - 1
app/components/datasource/datasource.jsx

@@ -296,7 +296,7 @@ class DataSource extends React.Component {
                 </TabPane>
                 <TabPane className='dataconnect-tab dataconnect' tab="数据库连接" key="2" >
                     <Table
-                        className='dataconnect-table dataconnect-table'
+                        className='dataconnect-table'
                         columns={dataConnectColumns}
                         dataSource={dataConnect.list}
                         loading={loading}

+ 16 - 0
app/constants/url.js

@@ -3,6 +3,8 @@ const BASE_URL = 'http://192.168.253.129:8080';
 /**后台接口地址 */
 const URLS = {
     
+    /***************************************数据源***************************************/
+
     DATASOURCE_ADD: BASE_URL + '/inputDataConnector', // 新增数据源
 
     DATASOURCE_UPDATE: BASE_URL + '/updateData', // 修改数据源
@@ -15,6 +17,8 @@ const URLS = {
 
     DATASOURCE_QUERY_COLUMNS: BASE_URL + '/implementSql', // 根据sql请求列数据信息
 
+    /***************************************数据列***************************************/
+
     DATACONNECT_ADD: BASE_URL + '/inputDatabases', // 新增数据连接配置
 
     DATACONNECT_UPDATE: BASE_URL + '/updatabases', // 修改数据连接配置
@@ -23,6 +27,18 @@ const URLS = {
 
     DATACONNECT_LIST: BASE_URL + '/getDatabases', // 获得数据连接列表
 
+    /***************************************图表编辑***************************************/
+
+    CHART_ADD: BASE_URL + '/inputHistogram', // 新增图表
+
+    CHART_UPDATE: BASE_URL + '/updateHistogram', // 修改图表
+
+    CHART_DELETE: BASE_URL + '/delChartsConfig', // 删除图表
+
+    CHART_LIST: BASE_URL + '/getListCharts', // 获得图表列表
+
+    CHART_DETAIL: BASE_URL + '/getChartsConfig', // 获得单个图表详细数据
+
     CHART_BAR_OPTION: BASE_URL + '/showHistogram', // 请求柱状图展示数据
 }
 export default URLS

+ 1 - 1
app/index.js

@@ -14,7 +14,7 @@ const app = dva({
     onReducer: reducer => (state, action) => {
         const newState = undoable(reducer, {
             limit: 10,
-            filter: includeAction('chartDesigner/setModel'),
+            filter: includeAction(['chartDesigner/setField', 'chartDesigner/setFields']), // 只对图表编辑界面的部分action生效
         })(state, action);
         return { ...newState };
     },

+ 164 - 24
app/models/chart.js

@@ -1,38 +1,178 @@
+import { message } from 'antd'
+import * as service from '../services/index'
+import URLS from '../constants/url'
+
 export default {
     namespace: 'chart',
     state: {
-        list: [{
-            name: '图表1',
-            creator: 'zhuth',
-            createTime: '2018-07-15'
-        }, {
-            name: '图表1',
-            creator: 'zhuth',
-            createTime: '2018-07-15'
-        }, {
-            name: '图表1',
-            creator: 'zhuth',
-            createTime: '2018-07-15'
-        }, {
-            name: '图表1',
-            creator: 'zhuth',
-            createTime: '2018-07-15'
-        }]
+        list: []
     },
-    reducer: {
-
+    reducers: {
+        add(state, action) {
+            const { data } = action;
+            let list = state.list;
+            list.push(data);
+            return Object.assign({}, state, {list});
+        },
+        list(state, action) {
+            let list = action.list;
+            return Object.assign({}, state, {list: list});
+        },
     },
     effects: {
         *fetchList(action, { call, put }) {
-            const res = yield call({
-                url: '',
-                body: ''
-            });
+            try{
+                const res = yield call(service.fetch, {
+                    url: URLS.CHART_LIST
+                });
+                console.log(res);
+                if(!res.err && res.data.code > 0) {
+                    let list = res.data.data.map(d => {
+                        return {
+                            code:  d.chartId,
+                            name: d.chartName,
+                            type: d.chartType,
+                            creator: d.createBy,
+                            createTime: d.createDate
+                        }
+                    })
+                    yield put({ type: 'list', list: list });
+                }else {
+                    message.error('请求图表列表失败');
+                }
+            }catch(e) {
+                console.log(e);
+            }
+        },
+        *remoteDetail(action, { select, call, put }) {
+            const chartDesigner = yield select(state => state.present.chartDesigner);
+            const code = action.code;
+            if(!code){
+                return
+            }
+            try {
+                const res = yield call(service.fetch, {
+                    url: URLS.CHART_DETAIL,
+                    body: code
+                });
+                if(!res.err && res.data.code > 0) {
+                    console.log(res);
+
+                    const getViewType = function(type) {
+                        if(type == 'Histogram') {
+                            return 'bar';
+                        }else {
+                            return '';
+                        }
+                    }
+
+                    let resData = res.data.data;
+                    let groupBy = JSON.parse(resData.groupBy);
+                    let barConfig = JSON.parse(resData.chartConfig);
+
+                    let data = {
+                        code: resData.chartId,
+                        header: {
+                            label: resData.chartName
+                        },
+                        baseConfig: {
+                            dataSource: {
+                                key: resData.dataId,
+                            },
+                            viewType: {
+                                key: getViewType(resData.chartType)
+                            }
+                        },
+                        preparing: {
+                            groupBy: groupBy.map(g => {
+                                return {
+                                    key: g.columnName,
+                                    label: g.columnRename
+                                }
+                            })
+                        },
+                        barConfig: {
+                            xAxis: {
+                                column: {
+                                    value: barConfig.xAxis.columnName,
+                                    label: barConfig.xAxis.columnRename,
+                                    type: barConfig.xAxis.columnType
+                                },
+                                granularity: {
+                                    value: barConfig.xAxis.showDataType,
+                                    label: barConfig.xAxis.showDataLable
+                                }
+                            },
+                            yAxis: {
+                                column: {
+                                    value: barConfig.yAxis.columnName,
+                                    label: barConfig.yAxis.columnRename,
+                                    type: barConfig.yAxis.columnType
+                                },
+                                gauge: {
+                                    value: barConfig.yAxis.showDataType,
+                                    label: barConfig.yAxis.showDataLable
+                                }
+                            }
+                        }
+                    }
+                    yield put({ type: 'chartDesigner/changeFields', 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 }
+                    ] });
+                }else {
+                    console.log(res);
+                }
+            }catch(e) {
+                message.error('失败');
+                console.log(e);
+                yield put({ type: 'list', data: [] });
+            }
+        },
+        *remoteDelete(action, { select, call, put, takeEvery, takeLatest }) {
+            const chart = yield select(state => state.present.chart);
+            const code = action.code;
+            let list = chart.list;
+            try {
+                const res = yield call(service.fetch, {
+                    url: URLS.CHART_DELETE,
+                    body: [code]
+                });
+                if(!res.err && res.data.code > 0) {
+                    for(let i = 0; i < list.length; i++) {
+                        if(list[i].code == code) {
+                            list.splice(i, 1);
+                            break;
+                        }
+                    }
+                    yield put({ type: 'list', list: list });
+                    message.success('删除成功');
+                }else {
+                    message.error('删除失败');
+                }
+            }catch(e) {
+                message.error('删除失败');
+                console.log(e);
+            }
         }
     },
     subscriptions: {
         setup({ dispatch, history }) {
-            //dispatch({ type: 'fetchList' })
+            dispatch({ type: 'fetchList' });
+            return history.listen(({ pathname, query }) => {
+                let detail = pathname.match(/chart\/(\w+)/);
+                if(detail) {
+                    let code = detail[1];
+                    if(code != 'create') {
+                        dispatch({ type: 'remoteDetail', code: code });
+                    }else {
+                        dispatch({ type: 'chartDesigner/reset' });
+                    }
+                }
+            })
         }
     }
 }

+ 241 - 13
app/models/chartDesigner.js

@@ -1,4 +1,5 @@
 import { routerRedux } from 'dva/router'
+import { message } from 'antd'
 import * as service from '../services/index'
 import { delay } from '../utils/baseUtils'
 import URLS from '../constants/url'
@@ -6,6 +7,20 @@ import URLS from '../constants/url'
 export default {
     namespace: 'chartDesigner',
     state: {
+        originData: {
+            code: null,
+            header: { label: '未命名' },
+            baseConfig: { dataSource: { key: '', label: '' }, viewType: { key: '', label: '' } },
+            preparing: { groupBy: [] },
+            aggregateTableConfig: {},
+            dataViewConfig: {},
+            barConfig: { xAxis: { column: {}, granularity: {} }, yAxis: { column: {}, gauge: {} } },
+            pieConfig: { series: [] },
+            style: {},
+            filters: [],
+            chartOption: {},
+            autoRefresh: true
+        },
         columns: [{
             label: '编号',
             name: 'ID',
@@ -104,32 +119,246 @@ export default {
 
         },
         filters: [],
-        chartOption: {}
+        chartOption: {},
+        autoRefresh: true
     },
     reducers: {
-        setModel(state, action) {
+        /**
+         * 更新model字段值方法1
+         * 1. 为保持撤销重做的功能有效性,能够撤销重做的action动作才使用该通用方法,否则下方写成特殊方法
+         * 2. 对数据刷新没有影响的model字段改变一般用该action
+         */
+        setField(state, action) {
             const { name, value } = action;
             let obj = {};
             obj[name] = value;
             let newState = Object.assign({}, state, obj);
             return newState;
         },
+        /**
+         * 批量更新model字段值方法1
+         */
+        setFields(state, action) {
+            const { fields } = action;
+            let obj = {};
+            fields.map(f => {
+                obj[f.name] = f.value;
+            });
+            let newState = Object.assign({}, state, obj);
+            return newState;
+        },
+        setDataSource(state, action) {
+            const { value } = action;
+            let obj = {};
+            obj['baseConfig'] = value;
+            return Object.assign({}, state, obj);
+        },
+        setColumns(state, action) {
+            const { value } = action;
+            let obj = {};
+            obj['columns'] = value;
+            return Object.assign({}, state, obj);
+        },
+        setAutoRefresh(state, action) {
+            const { value } = action;
+            let obj = {};
+            obj['autoRefresh'] = value;
+            return Object.assign({}, state, obj);
+        },
         setChartOption(state, action) {
             const { option } = action;
             let obj = {};
             obj['chartOption'] = option;
-            let newState = Object.assign({}, state, obj);
-            return newState;
+            return Object.assign({}, state, obj);
+        },
+        reset(state, action) {
+            let obj = Object.assign({}, state, state.originData);
+            console.log(obj);
+            return obj;
         }
     },
     effects: {
-        *changeModel(action, { select, call, put }) {
+        /**
+         * 更新model字段值方法2
+         * 可能影响到数据刷新的model字段改变一般用该action
+         */
+        *changeField(action, { select, call, put }) {
+            const { name, value } = action;
+            yield put({ type: 'setField', name, value });
+
+            const chartDesigner = yield select(state => state.present.chartDesigner);
+            const { autoRefresh } = chartDesigner;
+            
+            if(autoRefresh) {
+                yield put({ type: 'fetchChartData' });
+            }
+        },
+        /**
+         * 批量更新model字段值方法2
+         */
+        *changeFields(action, { select, call, put }) {
+            const { fields } = action;
+            yield put({ type: 'setFields', fields });
+
+            const chartDesigner = yield select(state => state.present.chartDesigner);
+            const { autoRefresh } = chartDesigner;
+            if(autoRefresh) {
+                yield put({ type: 'fetchChartData' });
+            }
+        },
+        *changeDataSource(action, { select, call, put }) {
+            const { value } = action;
+            yield put({ type: 'setDataSource', value });
+            yield put({ type: 'remoteDataColumn', code: value.dataSource.key });
+        },
+        *remoteAdd(action, { select, call, put }) {
+            try{
+                const chartDesigner = yield select(state => state.present.chartDesigner);
+                const { header, baseConfig, preparing, barConfig } = chartDesigner;
+                let body = {}; // 基本属性
+                if(baseConfig.viewType.key == 'bar') {
+                    body = {
+                        chartName: header.label,
+                        chartType: 'Histogram',
+                        dataId: baseConfig.dataSource.key,
+                        groupBy: preparing.groupBy.map(g => {
+                            return {
+                                columnName: g.key,
+                                columnRamane: g.label
+                            }
+                        }),
+                        chartConfig: {
+                            xAxis: {
+                                columnName: barConfig.xAxis.column.value,
+                                columnRename: barConfig.xAxis.column.label,
+                                columnType: barConfig.xAxis.column.type,
+                                showDataType: barConfig.xAxis.granularity.value,
+                                showDataLable: barConfig.xAxis.granularity.label
+                            },
+                            yAxis: {
+                                columnName: barConfig.yAxis.column.value,
+                                columnRename: barConfig.yAxis.column.label,
+                                columnType: barConfig.yAxis.column.type,
+                                showDataType: barConfig.yAxis.gauge.value,
+                                showDataLable: barConfig.yAxis.gauge.label
+                            }
+                        },
+                        isLegend: 1,
+                        isTooltip: 1,
+                        isTooltip: 1,
+                        isToolbox: 1,
+                        createBy: "zhuth",
+                        describes: ""
+                    }
+                }
+                const res = yield call(service.fetch, {
+                    url: URLS.CHART_ADD,
+                    body: body
+                })
+                if(!res.err && res.data.code > 0) {
+                    message.success('新增成功!');
+                    yield put({ type: 'chart/fetchList' });
+                }
+            }catch(e) {
+                console.log(e);
+            }
+        },
+        *remoteModify(action, { select, call, put }) {
+            try{
+                const chartDesigner = yield select(state => state.present.chartDesigner);
+                const { code, header, baseConfig, preparing, barConfig } = chartDesigner;
+                let body = {}; // 基本属性
+                if(baseConfig.viewType.key == 'bar') {
+                    body = {
+                        chartId: code,
+                        chartName: header.label,
+                        chartType: 'Histogram',
+                        dataId: baseConfig.dataSource.key,
+                        groupBy: preparing.groupBy.map(g => {
+                            return {
+                                columnName: g.key,
+                                columnRamane: g.label
+                            }
+                        }),
+                        chartConfig: {
+                            xAxis: {
+                                columnName: barConfig.xAxis.column.value,
+                                columnRename: barConfig.xAxis.column.label,
+                                columnType: barConfig.xAxis.column.type,
+                                showDataType: barConfig.xAxis.granularity.value,
+                                showDataLable: barConfig.xAxis.granularity.label
+                            },
+                            yAxis: {
+                                columnName: barConfig.yAxis.column.value,
+                                columnRename: barConfig.yAxis.column.label,
+                                columnType: barConfig.yAxis.column.type,
+                                showDataType: barConfig.yAxis.gauge.value,
+                                showDataLable: barConfig.yAxis.gauge.label
+                            }
+                        },
+                        isLegend: 1,
+                        isTooltip: 1,
+                        isTooltip: 1,
+                        isToolbox: 1,
+                        createBy: "zhuth",
+                        describes: ""
+                    }
+                }
+                console.log(body);
+                const res = yield call(service.fetch, {
+                    url: URLS.CHART_UPDATE,
+                    body: body
+                })
+                if(!res.err && res.data.code > 0) {
+                    message.success('修改成功!');
+                    yield put({ type: 'chart/fetchList' });
+                }
+            }catch(e) {
+                console.log(e);
+            }
+        },
+        *remoteDataColumn(action, { select, call, put }) {
+            const code = action.code;
+            console.log(code);
+            try {
+                const res = yield call(service.fetch, {
+                    url: URLS.DATASOURCE_DETAIL,
+                    body: code
+                });
+                if(!res.err && res.data.code > 0) {
+                    let resData = res.data.data;
+                    let columnConfig = JSON.parse(resData.columnConfig);
+
+                    let columns = columnConfig.map((c, i) => {
+                        return {
+                            key: i,
+                            using: c.isOpen=='1'?true:false,
+                            name: c.columnName,
+                            label: c.columnLable,
+                            dataType: c.dataType,
+                            type: c.columnType,
+                            selection: [],
+                            groupable: c.isGroup=='1'?true:false,
+                            bucketizable: c.isSubsection=='1'?true:false,
+                            description: c.remarks
+                        }
+                    })
+                    console.log(columns);
+                    yield put({ type: 'setColumns', value: columns });
+                }else {
+                    console.log(res);
+                }
+            }catch(e) {
+                console.log(e);
+                yield put({ type: 'list', data: [] });
+            }
+        },
+        *fetchChartData(action, { select, call, put }) {
             const chartDesigner = yield select(state => state.present.chartDesigner);
             const { baseConfig } = chartDesigner;
             const { viewType } = baseConfig;
             const viewTypeKey = viewType.key;
-            const { name, value } = action;
-            yield put({ type: 'setModel', name, value });
+
             if(viewTypeKey == 'bar') {
                 yield put({ type: 'fetchBarData' });
             }else if(viewTypeKey == 'pie') {
@@ -150,11 +379,11 @@ export default {
                         "xAxis": {
                             "columnRename": barConfig.xAxis.column.value,
                             "columnType": barConfig.xAxis.column.type,
-                            "dataType": barConfig.xAxis.granularity.value
+                            "showDataType": barConfig.xAxis.granularity.value
                         },
                         "yAxis": {
                             "columnRename": barConfig.yAxis.column.value,
-                            "dataType": barConfig.yAxis.gauge.value
+                            "showDataType": barConfig.yAxis.gauge.value
                         }
                     }
                 });
@@ -165,18 +394,17 @@ export default {
                     "xAxis": {
                         "columnRename": barConfig.xAxis.column.value,
                         "columnType": barConfig.xAxis.column.type,
-                        "dataType": barConfig.xAxis.granularity.value
+                        "showDataType": barConfig.xAxis.granularity.value
                     },
                     "yAxis": {
                         "columnRename": barConfig.yAxis.column.value,
-                        "dataType": barConfig.yAxis.gauge.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:null;
-                res.data.data.gauge = barConfig.yAxis?barConfig.yAxis.gauge.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 });
 
             }catch(e) {

+ 37 - 40
app/models/dataSource.js

@@ -182,54 +182,51 @@ export default {
             }
         },
         *remoteDetail(action, { select, call, put }) {
-            const dataSource = yield select(state => state.present.dataSource);
-            const model = dataSource.newOne;
             const code = action.code;
-            if(model.code == code){
-                return
-            }
             try {
                 const res = yield call(service.fetch, {
                     url: URLS.DATASOURCE_DETAIL,
                     body: code
                 });
-                let resData = res.data.data;
-                let columnConfig = JSON.parse(resData.columnConfig);
-                let dbConfig = JSON.parse(resData.dbConfig);
-                let tags = JSON.parse(resData.dataTag);
-                let data = {
-                    code: resData.dataId,
-                    name: resData.dataName,
-                    type: resData.type,
-                    dbType: dbConfig.databaseType,
-                    dbName: dbConfig.dataName,
-                    address: dbConfig.addrass,
-                    port: dbConfig.port,
-                    target: resData.loadObject,
-                    creator: resData.createBy,
-                    createTime: new Date(resData.createDate),
-                    userName: dbConfig.userName,
-                    password: dbConfig.passWord,
-                    tags: tags,
-                    description: resData.note,
-                    columns: columnConfig.map((c, i) => {
-                        return {
-                            key: i,
-                            using: c.isOpen=='1'?true:false,
-                            name: c.columnName,
-                            alias: c.columnLable,
-                            dataType: c.dataType,
-                            columnType: c.columnType,
-                            groupable: c.isGroup=='1'?true:false,
-                            bucketizable: c.isSubsection=='1'?true:false,
-                            description: c.remarks
-                        }
-                    })
+                if(!res.err && res.data.code > 0) {
+                    let resData = res.data.data;
+                    let columnConfig = JSON.parse(resData.columnConfig);
+                    let dbConfig = JSON.parse(resData.dbConfig);
+                    let tags = JSON.parse(resData.dataTag);
+                    let data = {
+                        code: resData.dataId,
+                        name: resData.dataName,
+                        type: resData.type,
+                        dbType: dbConfig.databaseType,
+                        dbName: dbConfig.dataName,
+                        address: dbConfig.addrass,
+                        port: dbConfig.port,
+                        target: resData.loadObject,
+                        creator: resData.createBy,
+                        createTime: new Date(resData.createDate),
+                        userName: dbConfig.userName,
+                        password: dbConfig.passWord,
+                        tags: tags,
+                        description: resData.note,
+                        columns: columnConfig.map((c, i) => {
+                            return {
+                                key: i,
+                                using: c.isOpen=='1'?true:false,
+                                name: c.columnName,
+                                alias: c.columnLable,
+                                dataType: c.dataType,
+                                columnType: c.columnType,
+                                groupable: c.isGroup=='1'?true:false,
+                                bucketizable: c.isSubsection=='1'?true:false,
+                                description: c.remarks
+                            }
+                        })
+                    }
+                    yield put({ type: 'setNewModel', model: data });
+                }else {
+                    console.log(res);
                 }
-                console.log(data);
-                yield put({ type: 'setNewModel', model: data });
             }catch(e) {
-                message.error('失败');
                 console.log(e);
                 yield put({ type: 'list', data: [] });
             }

+ 0 - 2
app/models/main.js

@@ -19,8 +19,6 @@ export default {
     subscriptions: {
         setup({ dispatch, history }) {
             return history.listen(({ pathname, query }) => {
-                console.log(pathname);
-
                 let page = pathname.match(/\/(\w*)/)[1];
                 dispatch({ type: 'setPage', page });
 

+ 3 - 0
app/models/mapRule.js

@@ -0,0 +1,3 @@
+function dataSource_detail() {
+    
+}