Browse Source

图表无权限样式调整/filterBox支持二级列数据/看板每次只允许添加一个图表/看板自定义公共参数过滤字段前台逻辑完善/图表显示默认过滤条件

zhuth 7 years ago
parent
commit
117b1c8652

+ 1 - 1
src/components/chart/list.jsx

@@ -167,7 +167,7 @@ class ChartList extends React.Component {
                                 dispatch({ type: 'main/redirect', path: '/chart/' + l.code });
                             } : () => {}}>
                                 {!l.access && <div className='deny-body'>
-                                    <div className='deny-tip'>你没有对应数据源的权限</div>
+                                    <div className='deny-tip'>无数据权限</div>
                                 </div>}
                                 <Thumbnail style={{ opacity: l.access ? 1 : 0.3 }} type={l.type} code={l.code} option={l.chartOption}/>
                             </Row>

+ 2 - 2
src/components/chart/list.less

@@ -69,8 +69,8 @@
                             .deny-tip {
                                 height: 40px;
                                 line-height: 40px;
-                                background: rgba(255,255,255,.7);
-                                border: 2px solid #dd6a6a;
+                                background: rgba(218, 218, 218, 0.5);
+                                border: 2px solid #aaaaaa;
                                 font-weight: bold;
                             }
                         }

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

@@ -17,7 +17,7 @@ class baseConfigForm extends React.Component {
 				<FormItem label='数据源' {...formItemLayout}>
 					<Select
 						disabled
-						value={props.chartDesigner.baseConfig.dataSource}
+						value={props.chartDesigner.baseConfig.dataSource.code}
 						onChange={(value) => {
 							props.dispatch({ type: 'chartDesigner/changeDataSource', name: 'baseConfig', value: {
 								...props.chartDesigner.baseConfig, dataSource: value } });

+ 4 - 3
src/components/chartDesigner/sections/toolbar.jsx

@@ -2,7 +2,7 @@ import React from 'react'
 import { Tag, Icon, Button } from 'antd'
 import FilterBox from '../../common/filterBox/filterBox'
 import { connect } from 'dva'
-import { dateFormat } from '../../../utils/baseUtils'
+import moment from 'moment'
 import './toolbar.less'
 
 class Toolbar extends React.Component {
@@ -66,8 +66,9 @@ class Toolbar extends React.Component {
                 filterLabel = `${label} ${operatorLabel} ${value1}`; 
             }
         }else if(type === 'time') {
-            value1 = dateFormat(new Date(value1), 'yyyy/MM/dd');
-            value2 = dateFormat(new Date(value2), 'yyyy/MM/dd');
+            value1 = moment(value1).format('YYYY/MM/DD');
+            value2 = moment(value2).format('YYYY/MM/DD');
+
             if(operator === 'null' || operator === 'notNull') {
                 filterLabel = `${label} ${operatorLabel}`;
             }else if(operator === 'between') {

+ 32 - 6
src/components/common/filterBox/filterBox.jsx

@@ -8,6 +8,7 @@ import URLS from '../../../constants/url'
 import moment from 'moment';
 const FormItem = Form.Item
 const SelectOption = Select.Option
+const OptionGroup = Select.OptGroup 
 
 let uuid = 0;
 class FilterBox extends React.Component {
@@ -164,7 +165,7 @@ class FilterBox extends React.Component {
                     ...f,
                     filterLabel: this.createFilterLabel(f)
                 }));
-                createFilters(filters);
+                createFilters && createFilters(filters);
                 hideFilterBox();
             }
         })
@@ -288,6 +289,24 @@ class FilterBox extends React.Component {
     getFilterItems() {
         const { columns } = this.state;
         const { getFieldDecorator, getFieldValue } = this.props.form;
+        const groups = [];
+        // 处理带分组的列
+        for(let i = 0; i < columns.length; i++) {
+            let c = columns[i];
+            if(c.group) {
+                if(groups.findIndex(g => g.groupName === c.group) === -1) {
+                    groups.unshift({
+                        groupName: c.group,
+                        options: [c]
+                    });
+                }else {
+                    let g = groups.find(g => g.groupName === c.group);
+                    g.options.push(c);
+                }
+            }else {
+                groups.push(c);
+            }
+        }
         getFieldDecorator('filters', { initialValue: [] });
         const filters = getFieldValue('filters');
         const filterItems = filters.map((f, index) => {
@@ -313,11 +332,18 @@ class FilterBox extends React.Component {
                                         }
                                         onChange={(value) => {this.changeFilterName(f, value)}}
                                     >
-                                        {columns.map((c, i) => {
-                                            return (
-                                                <SelectOption key={i} value={c.name}>{c.label}</SelectOption>
-                                            )
-                                        })}
+                                    {
+                                        groups.map((g, i) => {
+                                            let options = g.options;
+                                            return !!g.groupName ?
+                                            <OptionGroup key={i} label={g.groupName}>
+                                                {
+                                                    options.map(o => <SelectOption key={o.name} value={o.name}>{o.label}</SelectOption> )
+                                                }
+                                            </OptionGroup> :
+                                            <SelectOption key={i} value={g.name}>{g.label}</SelectOption> 
+                                        })
+                                    }
                                     </Select>)}
                             </FormItem>
                         </Col>

+ 11 - 14
src/components/dashboardDesigner/chooseChartBox.jsx

@@ -1,6 +1,6 @@
 import React from 'react'
 import '../../models/dashboardDesigner'
-import { Modal, Checkbox, Row, Col, Table, Input, message } from 'antd'
+import { Modal, Radio, Row, Col, Table, Input, message } from 'antd'
 import { connect } from 'dva'
 import { dateFormat } from '../../utils/baseUtils'
 import 'braft-editor/dist/braft.css'
@@ -13,7 +13,7 @@ class ChooseChartBox extends React.Component {
         super(props);
         this.state = {
             filterLabel: '',
-            selectedRecord: [],
+            selectedRecord: -1,
             screenWidth: document.documentElement.clientWidth || document.body.clientWidth,
             screenHeight: document.documentElement.clientHeight || document.body.clientHeight
         };
@@ -32,15 +32,8 @@ class ChooseChartBox extends React.Component {
     }
 
     changeSelected = (record) => {
-        let arr = this.state.selectedRecord;
-        let findIndex = arr.findIndex(a => a.code === record.code);
-        if(findIndex !== -1) {
-            arr.splice(findIndex, 1)
-        }else {
-            arr.push(record);
-        }
         this.setState({
-            selectedRecord: arr 
+            selectedRecord: record
         });
     }
 
@@ -48,7 +41,7 @@ class ChooseChartBox extends React.Component {
         const { selectedRecord } = this.state;
         const { dispatch, hideBox } = this.props;
         if(selectedRecord) {
-            dispatch({ type: 'dashboardDesigner/addCharts', charts: selectedRecord });
+            dispatch({ type: 'dashboardDesigner/addChart', chart: selectedRecord });
             hideBox();
         }else {
             message.warning('未选中图表');
@@ -84,7 +77,11 @@ class ChooseChartBox extends React.Component {
             key: 'selected',
             width: 50,
             render: (text, record) => {
-                return <Checkbox checked={dashboardDesigner.items.findIndex(i => i.chartCode===record.code)!==-1 || selectedRecord.findIndex(s => s.code === record.code) !== -1 } disabled={dashboardDesigner.items.findIndex(i => i.chartCode===record.code)!==-1}/>
+                // return <Checkbox checked={dashboardDesigner.items.findIndex(i => i.chartCode===record.code)!==-1 || selectedRecord.findIndex(s => s.code === record.code) !== -1 } disabled={dashboardDesigner.items.findIndex(i => i.chartCode===record.code)!==-1}/>
+                return <Radio
+                    checked={dashboardDesigner.items.findIndex(i => i.chartCode===record.code)!==-1 || selectedRecord.code === record.code }
+                    disabled={dashboardDesigner.items.findIndex(i => i.chartCode===record.code)!==-1}
+                />
             }
         }, {
             title: '名称',
@@ -162,8 +159,8 @@ class ChooseChartBox extends React.Component {
                     </Row>
                 }
                 visible={visibleBox}
-                onOk={() => {this.setState({ filterLabel: '', selectedRecord: [] });this.okHandler()}}
-                onCancel={() => {this.setState({ filterLabel: '', selectedRecord: [] });hideBox()}}
+                onOk={() => {this.setState({ filterLabel: '', selectedRecord: -1 });this.okHandler()}}
+                onCancel={() => {this.setState({ filterLabel: '', selectedRecord: -1 });hideBox()}}
                 maskClosable={false}
                 destroyOnClose={true}
             >

+ 182 - 9
src/components/dashboardDesigner/configForm.jsx

@@ -1,16 +1,189 @@
+import React from 'react'
 import { connect } from 'dva'
-import { Form, Input, Divider, Select, Checkbox } from 'antd'
+import { Form, Input, Divider, Button, Icon, Collapse, Spin, Select, Checkbox } from 'antd'
 const { TextArea } = Input;
 
-const ConfigForm = ({ dashboardDesigner, dispatch }) => {
+// const ConfigForm = ({ dashboardDesigner, dispatch }) => {
+class ConfigForm extends React.Component {
 
-    return <Form className='config-form'>
-        <Divider>基础设置</Divider>
-        <Form.Item label='备注'>
-            <TextArea placeholder="Autosize height with minimum and maximum number of lines" autosize={{ minRows: 2, maxRows: 6 }} />
-        </Form.Item>
-        <Divider>过滤字段设置</Divider>
-    </Form>
+    constructor(props) {
+        super(props);
+        this.state = {
+            selectedDataSource: null,
+            selectedColumn: null,
+            activeKey: [],
+            editing: false
+        };
+    }
+
+    addRelationColumn = () => {
+        const { dispatch } = this.props;
+        dispatch({ type: 'dashboardDesigner/addRelationColumn' });
+    }
+
+    deleteRelationColumn = (e) => {
+        const { dispatch } = this.props;
+        const code = e.target.dataset.code;
+        dispatch({ type: 'dashboardDesigner/deleteRelationColumn', code });
+    }
+
+    render() {
+        const { dashboardDesigner, dispatch } = this.props;
+        const { activeKey, editing, selectedDataSource, selectedColumn } = this.state;
+        const { relationColumns, dataSources, columnFetching } = dashboardDesigner;
+
+        return <Form className='config-form'>
+            <Divider>基础设置</Divider>
+            <Form.Item label='备注'>
+                <TextArea autosize={{ minRows: 2, maxRows: 6 }} />
+            </Form.Item>
+            <Divider>自定义过滤字段</Divider>
+            <div className='filtercolumns'>
+                <Collapse key='filtercolumnscollapse' activeKey={activeKey} onChange={k => {
+                    this.setState({
+                        selectedDataSource: null,
+                        selectedColumn: null,
+                        activeKey: k[k.length - 1], // 保持只有一个展开项
+                    });
+                }}>
+                    {
+                        relationColumns.map((r, ri) => (
+                            <Collapse.Panel
+                                key={r.code}
+                                disabled={editing}
+                                header={<Form.Item className='filtercolumn-name' label='名称' labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}>
+                                    <Input size='small' value={r.name}
+                                    onChange={(e) => {
+                                        dispatch({ type: 'dashboardDesigner/setRelationColumn', code: r.code, relationColumn: { ...r, name: e.target.value } });
+                                    }} onFocus={() => {
+                                        this.setState({
+                                            editing: true,
+                                        })
+                                    }} onBlur={() => {
+                                        this.setState({
+                                            editing: false
+                                        })
+                                    }}/>
+                                </Form.Item>}
+                            >
+                                {(dataSources.length > 0 ? <div className='filtercolumn-relation'>
+                                    {columnFetching && <div className='loading'>
+                                        <Spin />
+                                    </div>}
+                                    <div className='datasources'>
+                                        { dataSources.map((d, di) => (
+                                            <Button
+                                                className='datasource'
+                                                key={d.code}
+                                                type={!!selectedDataSource && selectedDataSource.code === d.code ? 'primary' : 'default'}
+                                                onClick={() => {
+                                                    this.setState({
+                                                        selectedDataSource: {
+                                                            code: d.code,
+                                                            name: d.name
+                                                        },
+                                                        selectedColumn: null
+                                                    });
+                                                    dispatch({ type: 'dashboardDesigner/remoteGetColumns', dataSourceCode: d.code });
+                                                }}
+                                            >
+                                                <span className='label'>{d.name}</span>
+                                                {r.relations.findIndex(r => r.dataSource.code === d.code) !== -1 && <Icon type='check-circle' theme={r.relations[0].dataSource.code === d.code ? 'filled' : 'outlined'} />}
+                                            </Button>
+                                        )) }
+                                    </div>
+                                    <div className='columns'>
+                                        { !!selectedDataSource &&
+                                            !!dataSources.find(d => d.code === selectedDataSource.code) &&
+                                            !!dataSources.find(d => d.code === selectedDataSource.code).columns &&
+                                            dataSources.find(d => d.code === selectedDataSource.code).columns.filter(c => {
+                                                if(!!r.relations[0]) {
+                                                    // 选中的不是第一个
+                                                    if(r.relations[0].dataSource.code !== selectedDataSource.code) {
+                                                        // 只能选用同类型的列
+                                                        return c.type === r.relations[0].column.type;
+                                                    }else {
+                                                        return true;
+                                                    }
+                                                }else {
+                                                    return true;
+                                                }
+                                            }).map(c => (
+                                            <Button
+                                                className='column'
+                                                type={!!selectedColumn && selectedColumn.name === c.name ? 'primary' : 'default'}
+                                                key={c.name}
+                                                onClick={() => {
+                                                    this.setState({
+                                                        selectedColumn: {
+                                                            name: c.name,
+                                                            label: c.label
+                                                        }
+                                                    }, () => {
+                                                        let { selectedColumn } = this.state;
+                                                        const { relations } = r;
+                                                        let idx = relations.findIndex(r => r.dataSource.code === selectedDataSource.code);
+                                                        if(idx === -1){
+                                                            relations.push({
+                                                                dataSource: {
+                                                                    code: selectedDataSource.code,
+                                                                    name: selectedDataSource.name
+                                                                },
+                                                                column: {
+                                                                    label: c.label,
+                                                                    name: c.name,
+                                                                    type: c.type
+                                                                }
+                                                            });
+                                                        }else {
+                                                            let cr = relations[idx];
+                                                            if(cr.column.name === selectedColumn.name) {
+                                                                relations.splice(idx, 1);
+                                                            }else {
+                                                                relations[idx] = {
+                                                                    dataSource: {
+                                                                        code: selectedDataSource.code,
+                                                                        name: selectedDataSource.name
+                                                                    },
+                                                                    column: {
+                                                                        code: c.code,
+                                                                        name: c.name,
+                                                                        type: c.type
+                                                                    }
+                                                                };
+                                                            }
+                                                        }
+                                                        let index = relationColumns.findIndex(rc => rc.code === r.code);
+                                                        relationColumns[index] = { ...r, relations };
+                                                        dispatch({ type: 'dashboardDesigner/setField', name: 'relationColumns', value: relationColumns });
+                                                    });
+                                                }}
+                                            >
+                                                <span className='label'>{c.label}</span>
+                                                {r.relations.findIndex(r => r.dataSource.code === selectedDataSource.code) !== -1 && r.relations[r.relations.findIndex(r => r.dataSource.code === selectedDataSource.code)].column.name === c.name && <Icon type='check-circle' theme={r.relations[0].column.name === c.name ? 'filled' : 'outlined'} />}
+                                            </Button>
+                                        )) }
+                                    </div>
+                                </div> : <div className='filtercolumn-empty'>
+                                    无关联数据源
+                                </div>)}
+                                <div className='filtercolumn-delete'>
+                                    <Button type='danger' className='delbtn' data-code={r.code} onClick={this.deleteRelationColumn}>
+                                        <Icon type='delete' theme='outlined' />删除
+                                    </Button>
+                                </div>
+                            </Collapse.Panel>
+                        ))
+                    }
+                </Collapse>
+                <div className='bottom-btns'>
+                    <Button className='addbtn' onClick={this.addRelationColumn}>
+                        <Icon type='plus' theme='outlined' />添加
+                    </Button>
+                </div>
+            </div>
+        </Form>
+    }
 }
 
 export default connect(({ present: { dashboardDesigner } }) => ({ dashboardDesigner }))(ConfigForm);

+ 35 - 5
src/components/dashboardDesigner/content.jsx

@@ -69,7 +69,6 @@ class DashboardDesignerContent extends React.Component {
         if(!flag && this.state.contentSize.scroll === _scroll) { // 如果滚动条没有变化则直接退出
             return;
         }
-        console.log(contentEl.offsetWidth);
         this.setState({
             contentSize: {
                 scroll: _scroll,
@@ -79,6 +78,25 @@ class DashboardDesignerContent extends React.Component {
         });
     }
 
+    getRelationFilterColumns = () => {
+        const { dashboardDesigner } = this.props;
+        const { relationColumns } = dashboardDesigner;
+        return relationColumns.filter(r => r.relations.length > 0).map(r => ({
+            name: r.code,
+            label: r.name,
+            type: r.relations[0].column.type
+        }));
+    }
+
+    createFilters = (filters) => {
+        const { dispatch } = this.props;
+        dispatch({ type: 'dashboardDesigner/setField', name: 'filters', value: filters });
+        this.hideFilterBox()
+    }
+
+    /**
+     * 生成过滤规则文本
+     */
     createFilterLabel = (filter) => {
         let { label, operator, operatorLabel, type, value1, value2 } = filter;
         let filterLabel;
@@ -100,7 +118,7 @@ class DashboardDesignerContent extends React.Component {
         }else if(type === 'time') {
             value1 = moment(value1).format('YYYY/MM/DD');
             value2 = moment(value2).format('YYYY/MM/DD');
-            
+
             if(operator === 'null' || operator === 'notNull') {
                 filterLabel = `${label} ${operatorLabel}`;
             }else if(operator === 'between') {
@@ -120,9 +138,21 @@ class DashboardDesignerContent extends React.Component {
         return filterLabel;
     }
 
+    filterUsingChange = (e) => {
+        const key = e.target.dataset.key;
+        const { dashboardDesigner, dispatch } = this.props;
+        const filters = dashboardDesigner.filters;
+        dispatch({ type: 'dashboardDesigner/setField', name: 'filters', value: filters.map( f => {
+            if(+f.key === +key) {
+                f = { ...f, using: f.type ? !f.using : false}
+            }
+            return f;
+        }) });
+    }
+
     render() {
         const { dashboardDesigner, dispatch } = this.props;
-        const { code, editMode, filterColumns, filters } = dashboardDesigner;
+        const { code, editMode, filters } = dashboardDesigner;
         const { contentSize, visibleChooseChartBox, visibleFilterBox } = this.state;
 
         let tags = filters.map((f, i)=>{
@@ -155,7 +185,7 @@ class DashboardDesignerContent extends React.Component {
                             <Icon type="filter" theme="outlined" />
                         </Tag>
                     </div>
-                    {visibleFilterBox && <FilterBox type='dashboard' code={code} columns={filterColumns} filterData={filters} visibleFilterBox={visibleFilterBox} showFilterBox={this.showFilterBox} hideFilterBox={this.hideFilterBox} createFilters={this.createFilters} />}
+                    {visibleFilterBox && <FilterBox type='dashboard' code={code} columns={this.getRelationFilterColumns()} filterData={filters} visibleFilterBox={visibleFilterBox} showFilterBox={this.showFilterBox} hideFilterBox={this.hideFilterBox} createFilters={this.createFilters} />}
                 <div className='viewtype'>
                     {/* {editMode && <Dropdown overlay={(
                         <Menu onClick={(item) => {
@@ -193,7 +223,7 @@ class DashboardDesignerContent extends React.Component {
                     <Content className='viewlayout' ref='contentEl'>
                         <ViewLayout contentSize={contentSize} reset={this.refreshContentSize} editMode={editMode}/>
                     </Content>
-                    <Sider className='config-sider' width={!!editMode ? 300 : 0}>
+                    <Sider className='config-sider' width={!!editMode ? 380 : 0}>
                         <ConfigForm />
                     </Sider>
                 </Layout>

+ 1 - 1
src/components/dashboardDesigner/header.jsx

@@ -94,7 +94,7 @@ class Header extends React.Component {
                                 var e = document.createEvent("Event");
                                 e.initEvent("resize", true, true);
                                 window.dispatchEvent(e);
-                            }, 200);
+                            }, 300);
                         }}
                     />
                 </div>

+ 120 - 1
src/components/dashboardDesigner/layout.less

@@ -32,13 +32,132 @@
                         .ant-layout-sider-children {
                             margin: 0 0 0 10px;
                             padding: 5px;
+                            overflow: auto;
                             border: 1px solid #CCCCCC;
                             .config-form {
                                 .ant-divider {
                                     margin: 10px 0;
                                 }
                                 .ant-form-item-label {
-                                    line-height: 24px;
+                                    text-align: left;
+                                }
+                                .filtercolumns {
+                                    &>.ant-collapse {
+                                        &>.ant-collapse-item {
+                                            &>.ant-collapse-header {
+                                                padding: 0 10px 0 40px;
+                                                .ant-form-item {
+                                                    .ant-form-item-label {
+                                                        label {
+                                                            cursor: pointer;
+                                                        }
+                                                    }
+                                                }
+                                            }
+                                            .ant-collapse-content {
+                                                .ant-collapse-content-box {
+                                                    padding: 0;
+                                                    .filtercolumn-empty {
+                                                        margin: 5px 5px 0 5px;
+                                                        height: 40px;
+                                                        line-height: 40px;
+                                                        text-align: center;
+                                                        font-size: 16px;
+                                                        color: red;
+                                                        background-color: #d4d7dc;
+                                                    }
+                                                    .filtercolumn-relation {
+                                                        display: flex;
+                                                        max-height: 50vh;
+                                                        .loading {
+                                                            position: absolute;
+                                                            z-index: 1;
+                                                            right: 20px;
+                                                            margin-top: 10px;
+                                                        }
+                                                        .datasources {
+                                                            padding-bottom: 5px;
+                                                            flex: 0.4;
+                                                            display: flex;
+                                                            flex-direction: column;
+                                                            overflow: auto;
+                                                            border-right: 1px solid #cccccc;
+                                                            border-bottom: 1px solid #cccccc;
+                                                            .datasource {
+                                                                padding-right: 10px;
+                                                                margin: 5px 5px 0 5px;
+                                                                flex: none;
+                                                                display: flex;
+                                                                justify-content: space-between;
+                                                                .label {
+                                                                    overflow: hidden;
+                                                                    text-overflow: ellipsis;
+                                                                }
+                                                                .anticon {
+                                                                    margin-top: 3px;
+                                                                }
+                                                                &[class="ant-btn-primary"] {
+                                                                    background-color: #40A9FF;
+                                                                }
+                                                            }
+                                                        }
+                                                        .columns {
+                                                            padding-bottom: 5px;
+                                                            flex: 0.6;
+                                                            display: flex;
+                                                            flex-direction: column;
+                                                            overflow: auto;
+                                                            border-bottom: 1px solid #cccccc;
+                                                            .column {
+                                                                margin: 5px 5px 0 5px;
+                                                                padding-right: 10px;
+                                                                flex: none;
+                                                                display: flex;
+                                                                justify-content: space-between;
+                                                                .label {
+                                                                    text-overflow: ellipsis;
+                                                                    overflow: hidden;
+                                                                }
+                                                                .anticon {
+                                                                    margin-top: 3px;
+                                                                }
+                                                                &[class="ant-btn-primary"] {
+                                                                    background-color: #40A9FF;
+                                                                }
+                                                            }
+                                                        }
+                                                    }
+                                                    .filtercolumn-delete {
+                                                        padding: 5px;
+                                                        .delbtn {
+                                                            width: 100%;
+                                                        }
+                                                    }
+                                                }
+                                            }
+                                        }
+                                        .ant-collapse-item-disabled {
+                                            &>.ant-collapse-header {
+                                                cursor: pointer;
+                                                color: rgba(0, 0, 0, 0.85);
+                                                &>.arrow {
+                                                    cursor: pointer;
+                                                    color: rgba(0, 0, 0, 0.85);
+                                                }
+                                            }
+                                        }
+
+                                    }
+                                    .bottom-btns {
+                                        margin-top: 10px;
+                                        height: 40px;
+                                        .addbtn {
+                                            height: 100%;
+                                            width: 100%;
+                                            border-style: dashed;
+                                            border-width: 1px;
+                                        }
+                                    }
                                 }
                             }
                         }

+ 6 - 3
src/components/dashboardDesigner/viewLayout.jsx

@@ -1,7 +1,7 @@
 import React from "react"
 import "./viewLayout.less"
 import ReactGridLayout from 'react-grid-layout'
-import { Icon, Modal } from 'antd'
+import { Icon, Modal, Tooltip } from 'antd'
 import { connect } from 'dva'
 import ChartView from './chartView'
 
@@ -33,12 +33,15 @@ class ViewLayout extends React.PureComponent {
     createElement = (item, isPreview) => {
         const { dispatch, dashboardDesigner } = this.props;
         const { editMode } = dashboardDesigner;
-        const { code, name, viewType, layout, chartCode } = item;
+        const { code, name, viewType, layout, chartCode, filters } = item;
         return (
             <div className={`chartview${editMode ? ' chartview-edit' : ''}`} key={code} data-grid={layout}>
                 <div className='chartview-toolbar mover'>
                     <div className='chart-title'><span>{name}</span></div>
                     <div className='chart-tools'>
+                        {viewType !== 'richText' && !!filters && filters.length > 0 && <Tooltip title={'默认过滤条件: 【' + filters.map((f, i) => f.filterLabel).join(',') + '】'}>
+                            <Icon className='visible-icon' type="info-circle" theme="outlined" />
+                        </Tooltip>}
                         {!isPreview && viewType !== 'richText' && <Icon type="arrows-alt" onClick={() => this.showPreviewBox(item)}/>}
                         {editMode && viewType !== 'richText' &&  <Icon type='edit' onClick={() => {
                             dispatch({ type: 'dashboard/remoteModify' });
@@ -65,7 +68,7 @@ class ViewLayout extends React.PureComponent {
     }
 
     onResize = () => {
-        const { reset, } = this.props;
+        const { reset } = this.props;
         reset();
     }
 

+ 5 - 2
src/components/dashboardDesigner/viewLayout.less

@@ -13,12 +13,15 @@
         line-height: 2;
       }
       .chart-tools {
-        display: none;
         font-size: 20px;
         .anticon {
+          display: none;
           margin-left: 10px;
           cursor: pointer;
         }
+        .visible-icon {
+          display: inline-block;
+        }
       }
     }
     .chartview-content {
@@ -88,8 +91,8 @@
     }
     &:hover {
       .chart-tools {
-        display: block;
         .anticon {
+          display: inline-block;
           &:hover {
             color: red;
           }

+ 5 - 5
src/components/datasource/columnConfig.jsx

@@ -127,12 +127,12 @@ class DataSourceColumnConfig extends React.Component {
                     >
                     {
                         COLUMN_TYPE.map( c => {
-                            // let dataType = record.dataType;
-                            // if(c.dataType.indexOf(dataType) !== -1) {
+                            let dataType = record.dataType;
+                            if(c.dataType.indexOf(dataType) !== -1) {
                                 return <SelectOption value={c.columnType} key={c.columnType}>{c.label}</SelectOption>
-                            // }else {
-                            //     return null
-                            // }
+                            }else {
+                                return null
+                            }
                             
                         }).filter((s)=>s!==null)
                     }

+ 6 - 6
src/components/datasource/columnType.json

@@ -1,25 +1,25 @@
 [{
-    "dataType": ["VARCHAR", "VARCHAR2", "NUMBER"],
+    "dataType": ["String", "BigDecimal"],
     "columnType": "index",
     "label": "索引"
 }, {
-    "dataType": ["DATE"],
+    "dataType": ["Date"],
     "columnType": "time",
     "label": "时间"
 }, {
-    "dataType": ["VARCHAR", "VARCHAR2", "NUMBER"],
+    "dataType": ["String", "BigDecimal"],
     "columnType": "categorical",
     "label": "类别"
 }, {
-    "dataType": ["NUMBER", "FLOAT"],
+    "dataType": ["BigDecimal"],
     "columnType": "scale",
     "label": "标量"
 }, {
-    "dataType": ["VARCHAR", "VARCHAR2", "NUMBER"],
+    "dataType": ["BigDecimal", "String"],
     "columnType": "ordinal",
     "label": "序值"
 }, {
-    "dataType": ["VARCHAR", "VARCHAR2", "NUMBER", "CLOB"],
+    "dataType": ["String", "BigDecimal"],
     "columnType": "string",
     "label": "文本"
 }]

+ 1 - 1
src/components/datasource/dataConnectConfig.jsx

@@ -48,7 +48,7 @@ class DataConnectConfig extends React.Component {
                     <Card
                         title={
                             <Row type='flex' justify='start'>
-                                <Col>{l.name}</Col>
+                                <Col className='label'>{l.name}</Col>
                                 <div style={{ display: (dataConnect.selected && dataConnect.selected.code === l.code) ? 'block' : 'none' }} className='selected'></div>
                             </Row>
                         }

+ 11 - 0
src/components/datasource/dataSourceDetail.less

@@ -59,6 +59,16 @@
                                     flex-direction: column;
                                     .ant-card-head {
                                         background: #F5F5F5;
+                                        .ant-card-head-wrapper {
+                                            .ant-card-head-title {
+                                                .ant-row-flex {
+                                                    .label {
+                                                        overflow: hidden;
+                                                        text-overflow: ellipsis
+                                                    }
+                                                }
+                                            }
+                                        }
                                         .selected {
                                             width: 60px;
                                             height: 60px;
@@ -78,6 +88,7 @@
                                         flex-direction: column;
                                         justify-content: center;
                                         .anticon-plus-circle-o {
+                                            margin: 0 auto;
                                             font-size: 60px;
                                         }
                                     }

+ 9 - 7
src/models/chart.js

@@ -143,6 +143,8 @@ export default {
                         return {
                             code:  d.chartId+'',
                             name: d.chartName,
+                            dataSourceCode: d.dataId,
+                            dataSourceName: d.dataName,
                             access: d.authority === '1',
                             type: getViewType(d.chartType),
                             creatorCode: d.createId+'',
@@ -192,7 +194,10 @@ export default {
                             label: resData.chartName
                         },
                         baseConfig: {
-                            dataSource: resData.dataId,
+                            dataSource: {
+                                code: resData.dataId,
+                                name: resData.dataName,
+                            },
                             viewType: viewType
                         },
                         styleConfig: styleConfig,
@@ -247,10 +252,7 @@ export default {
                     console.log(fields);
                     yield put({ type: 'chartDesigner/defaultChangeFields', fields: fields });
                     
-                    yield put({ type: 'chartDesigner/changeDataSource', value: {
-                        dataSource: data.baseConfig.dataSource,
-                        viewType: data.baseConfig.viewType
-                    } });
+                    yield put({ type: 'chartDesigner/changeDataSource', code: data.baseConfig.dataSource.code });
                 }else {
                     message.error('解析图表错误: ' + (res.err || res.data.msg));
                 }
@@ -266,7 +268,7 @@ export default {
                     barConfig, scatterConfig, otherConfig, description, group, filters, chartOption } = chartDesigner;
                 let body = {
                     chartName: header.label,
-                    dataId: baseConfig.dataSource,
+                    dataId: baseConfig.dataSource.code,
                     createBy: 'zhuth',
                     describes: description,
                     style: '',
@@ -340,7 +342,7 @@ export default {
                     chartId: code,
                     filters: JSON.stringify(filters),
                     chartName: header.label,
-                    dataId: baseConfig.dataSource,
+                    dataId: baseConfig.dataSource.code,
                     createBy: 'zhuth',
                     describes: description || '',
                     style: '',

+ 7 - 8
src/models/chartDesigner.js

@@ -2,7 +2,7 @@ import { message } from 'antd'
 import * as service from '../services/index'
 import URLS from '../constants/url'
 import STATISTICS_OPTION from '../components/chartDesigner/sections/statisticsOption.json'
-import { dateFormat } from '../utils/baseUtils'
+import moment from 'moment'
 
 function getBodyFilters(filters) {
     return filters.filter(f => f.using).map(f => {
@@ -16,8 +16,8 @@ function getBodyFilters(filters) {
         if(type === 'scale' && operator === 'between') {
             bodyFilter['value'] = value1 + ',' + value2;
         }else if(type === 'time') {
-            let v1 = dateFormat(new Date(value1),'yyyy-MM-dd hh:mm:ss');
-            let v2 = dateFormat(new Date(value2),'yyyy-MM-dd hh:mm:ss');
+            let v1 = moment(value1).format('YYYY-MM-DD');
+            let v2 = moment(value2).format('YYYY-MM-DD');
 
             if(operator === 'between') {
                 bodyFilter['value'] = v1 + ',' + v2;
@@ -37,7 +37,7 @@ export default {
             creatorCode: null,
             creatorName: null,
             header: { label: '标题' },
-            baseConfig: { dataSource: '', viewType: '' },
+            baseConfig: { dataSource: { }, viewType: '' },
             aggregateTableConfig: { targetColumn: {}, statistics: [], groupBy: [] },
             dataViewConfig: { viewColumns: [], sortColumn: {key: ''}, sortType: 'asc', count: 25 },
             barConfig: { xAxis: { column: {}, granularity: {} }, yAxis: { column: {}, gauge: {} }, groupBy: {key:''} },
@@ -56,7 +56,7 @@ export default {
         ],
         header: { label: '标题' },
         baseConfig: {
-            dataSource: '',
+            dataSource: {},
             viewType: ''
         },
         aggregateTableConfig: { targetColumn: {}, statistics: [], groupBy: [] },
@@ -168,9 +168,8 @@ export default {
             }
         },
         *changeDataSource(action, { select, call, put }) {
-            const { value } = action;
-            yield put({ type: 'silentSetField', name: 'baseConfig', value });
-            yield put({ type: 'remoteDataColumn', code: value.dataSource });
+            const { code } = action;
+            yield put({ type: 'remoteDataColumn', code });
         },
         *remoteQucikAdd(action, { select, call, put }) {
             try{

+ 4 - 2
src/models/dashboard.js

@@ -82,6 +82,8 @@ export default {
                         thumbnail: resData.thumbnail,
                         creator: resData.createBy,
                         createTime: resData.createDate,
+                        dataSources: [],
+                        relationColumns: [],
                     }
 
                     let fields = [];
@@ -131,11 +133,11 @@ export default {
         *remoteQucikAdd(action, { select, call, put }) {
             try {
                 const dashboardDesigner = yield select(state => state.present.dashboardDesigner);
-                const { name, items } = dashboardDesigner;
+                const { name } = dashboardDesigner;
                 let body = {
                     bdName: name,
                     bdNote: '',
-                    bdConfiguration: JSON.stringify(items),
+                    bdConfiguration: JSON.stringify([]),
                     thumbnail: '',
                     createBy: 'zhuth'
                 }

+ 163 - 12
src/models/dashboardDesigner.js

@@ -1,5 +1,25 @@
 import html2canvas from 'html2canvas'
 import { message } from 'antd'
+import * as service from '../services/index'
+import URLS from '../constants/url'
+
+const getViewType = function(type) {
+    if(type === 'Histogram') {
+        return 'bar';
+    }else if(type === 'Line') {
+        return 'line';
+    }else if(type === 'Pie') {
+        return 'pie';
+    }else if(type === 'scatter') {
+        return 'scatter';
+    }else if(type === 'population') {
+        return 'aggregateTable';
+    }else if(type === 'individual') {
+        return 'dataView';
+    }else {
+        return '';
+    }
+}
 
 export default {
     namespace: 'dashboardDesigner',
@@ -10,24 +30,26 @@ export default {
             items: [],
             description: '',
             thumbnail: '',
-            groupCode: '',
             dirty: false,
             editMode: true,
             filterColumns: [],
             filters: [],
+            dataSources: [],
+            relationColumns: [],
+            columnFetching: false,
         },
         name: '标题',
         defaultLayout: { x: 0, y: 50, w: 12, h: 6, minW: 2, maxW: 12, minH: 1 },
-        parameters: {
-        },  //全局可用参数
         items: [],
         description: '',
         thumbnail: '',
-        groupCode: '',
         dirty: false,
         editMode: true,
         filterColumns: [],
-        filters: []
+        filters: [],
+        dataSources: [], // 图表关联的所有数据源
+        relationColumns: [], // 自定义的列
+        columnFetching: false,
     },
     
     reducers: {
@@ -59,25 +81,68 @@ export default {
             let newState = Object.assign({}, state, obj);
             return Object.assign({}, newState, {dirty: true});
         },
+        addChart(state, action) {
+            let { items, defaultLayout } = state;
+            const { chart } = action;
+
+            items = items.concat([{
+                code: chart.code,
+                chartCode: chart.code,
+                name: chart.name,
+                dataSourceCode: chart.dataSourceCode+'',
+                dataSourceName: chart.dataSourceName,
+                viewType: 'chart',
+                layout: defaultLayout,
+            }]);
+            return Object.assign({}, state, {items, dirty: true});
+        },
         addCharts(state, action) {
             let { items, defaultLayout } = state;
             const { charts } = action;
 
             items = items.concat(charts.map(c => ({
                 code: c.code,
-                viewType: 'chart',
+                chartCode: c.code,
                 name: c.name,
+                viewType: 'chart',
+                dataSourceCode: c.dataSourceCode+'',
+                dataSourceName: c.dataSourceName,
                 layout: defaultLayout,
-                chartCode: c.code
             })));
             return Object.assign({}, state, {items, dirty: true});
         },
         deleteItem(state, action) {
-            let { items, dirty } = state;
+            let { items, dataSources, relationColumns, dirty } = state;
             const { item } = action;
-            let newItems = items.filter((i) => i.code !== item.code);
-            dirty = items.length !== newItems.length;
-            return { ...state, dirty: dirty, items: newItems };
+            let count = 0;
+            let targetIndex = -1;
+            for(let i = 0; i < items.length; i++) {
+                let tempItem = items[i];
+                if(tempItem.dataSourceCode === item.dataSourceCode) {
+                    count++;
+                }
+                if(tempItem.code === item.code) {
+                    targetIndex = i; 
+                }
+            }
+
+            if(count === 1) {
+                // 找到只有被删除的item使用的数据源并删除
+                let idx = dataSources.findIndex(d => d.code === item.dataSourceCode);
+                dataSources.splice(idx, 1);
+                // 同时删除已定义的关联字段
+                relationColumns.forEach(rc => {
+                    rc.relations.forEach((r, x) => {
+                        if(r.dataSourceCode === item.dataSourceCode) {
+                            rc.relations.splice(x, 1);
+                        }
+                    })
+                });
+            }
+            if(targetIndex !== -1) {
+                items.splice(targetIndex, 1);
+            }
+            return { ...state, dirty: dirty, items, dataSources };
         },
         addRichText(state, action) {
             let { items, defaultLayout } = state;
@@ -127,15 +192,101 @@ export default {
         setEditMode(state, action) {
             const { checked } = action;
             return { ...state, editMode: checked };
+        },
+        addRelationColumn(state, action) {
+            const { relationColumns } = state;
+            relationColumns.push({
+                code: Math.random()+'',
+                name: '新字段',
+                relations: []
+            });
+            return { ...state, relationColumns, dirty: true };
+        },
+        deleteRelationColumn(state, action) {
+            const { code } = action;
+            const { relationColumns } = state;
+            let index = relationColumns.findIndex(r => r.code === code);
+            relationColumns.splice(index, 1);
+            console.log(relationColumns);
+            return { ...state, relationColumns, dirty: true };
+        },
+        setRelationColumn(state, action) {
+            const { code, relationColumn } = action
+            const { relationColumns } = state;
+            let index = relationColumns.findIndex(r => r.code === code);
+            relationColumns[index] = relationColumn;
+            return { ...state, relationColumns, dirty: true };
+        },
+        setChartFilters(state, action) {
+            let { items } = state;
+            const { chartCode, filters } = action;
+            let idx = items.findIndex(i => i.chartCode === chartCode);
+            if(idx !== -1) {
+                items[idx] = { ...items[idx], filters }
+            }
+            return { ...state, items };
+        },
+        addDataSource(state, action) {
+            let { dataSources } = state;
+            const { dataSource } = action;
+            dataSources.findIndex(d => d.code === dataSource.code) === -1 && dataSources.push(dataSource);
+            console.log(dataSources);
+            return { ...state, dataSources };
         }
     },
 
     effects: {
+        *remoteGetColumns(action, { call, put, select }) {
+            const { dataSourceCode, mandatory } = action;
+            const dashboardDesigner = yield select(state => state.present.dashboardDesigner);
+            const { dataSources } = dashboardDesigner;
+            const body = dataSourceCode;
+            let idx = dataSources.findIndex(d => d.code === dataSourceCode);
+            if(!mandatory && dataSources[idx].columns && dataSources[idx].columns.length > 0) {
+                return;
+            }
+            
+
+            try {
+                yield put({ type: 'silentSetField', field: 'columnFetching', value: true });
+                const res = yield call(service.fetch, {
+                    url: URLS.DATASOURCE_QUERY_DATACOLUMNS,
+                    body: body
+                });
+                yield put({ type: 'silentSetField', field: 'columnFetching', value: false });
+                console.log('获得列数据', body, res);
+                if(!res.err && res.data.code > 0) {
+                    let resData = res.data.data;
+                    
+                    let columns = resData.map((c, i) => {
+                        return {
+                            key: i,
+                            name: c.columnName,
+                            label: c.columnRaname,
+                            type: c.columnType,
+                            groupable: c.isGroup==='1'?true:false,
+                            filterable: c.isFilter==='1'?true:false,
+                            bucketizable: c.isSubsection==='1'?true:false,
+                            selection: []
+                        }
+                    })
+                    
+                    dataSources[idx] = { ...dataSources[idx], columns }
+                    yield put({ type: 'silentSetField', name: 'dataSources', value: dataSources });
+                }else {
+                    message.error('请求列数据失败:' + (res.err || res.data.msg));
+                    yield put({ type: 'silentSetField', name: 'dataSources', value: [] });
+                }
+            }catch(e) {
+                console.log(e, body);
+                message.error('请求列数据失败');
+            }
+        },
         *saveWithThumbnail(action, {put, call}) {
             let thumbnail;
             yield call(async ()=> {
                 try {
-                    await html2canvas(document.getElementsByClassName('dashboard-content')[0]).then(
+                    await html2canvas(document.getElementsByClassName('viewlayout')[0]).then(
                         function(canvas){
                             thumbnail = canvas.toDataURL('image/png', 1.0);
                         }

+ 1 - 1
src/models/dataSource.js

@@ -365,7 +365,7 @@ export default {
                         yield put({ type: 'setNewModelField', name: 'columns', value: columns });
                     }else {
                         let mergeColumns = [];
-                        columns.map(c => {
+                        columns.forEach(c => {
                             let tc = oldColumns.find(o => o.name === c.name );
                             if(tc) {
                                 mergeColumns.push(tc);