Browse Source

图表报表标题样式/图表筛选框关键字搜索/报表复制/报表自定义过滤字段自动命名/报表自动生成分享码

zhuth 6 years ago
parent
commit
69504893ce

+ 8 - 6
src/components/chartDesigner/header.less

@@ -17,12 +17,14 @@
             width: 300px;
         }
         .input-title {
-            text-align: center;
-            font-size: 18px;
-            border: none;
-            border-bottom-right-radius: 4px;
-            border-top-right-radius: 4px;
-            background-color: transparent;
+            input {
+                text-align: center;
+                font-size: 18px;
+                border: none;
+                border-bottom-right-radius: 4px;
+                border-top-right-radius: 4px;
+                background-color: transparent;
+            }
         }
         .ant-input-group-addon {
             cursor: pointer;

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

@@ -169,14 +169,15 @@ class FilterBox extends React.Component {
         })
     }
 
-    fetchColumnData = (column) => {
+    fetchColumnData = (column, keyword, mandatory) => {
         const { type, code } = this.props;
         const { columnData } = this.state;
-        if(!columnData || columnData.length === 0) {
+        if(!columnData || columnData.length === 0 || mandatory) {
             this.setState({ columnData: [], fetching: true }, () => {
                 const body = {
                     id: code,
-                    columnName: column.name
+                    columnName: column.name,
+                    keyword, 
                 };
                 service.fetch({
                     url: type === 'chart' ? URLS.CHART_QUERY_COLUMNDATA : URLS.DATASOURCE_QUERY_COLUMNDATA,
@@ -213,7 +214,7 @@ class FilterBox extends React.Component {
         const filters = form.getFieldValue('filters');
         let filter = filters.filter((f) => {return f.key === key})[0];
         let column = columns.filter((c) => {return c.name === filter.name});
-        column = column.length>0?column[0]:{selection:[]};
+        column = column.length > 0 ? column[0] : { selection: [] };
         column.selection = column.selection || [];
         if(['index', 'string'].indexOf(type) !== -1) {
             field = <Input onBlur={(e) => {this.changeFilterValue(filter, e.target.value, index)}}/>
@@ -227,6 +228,7 @@ class FilterBox extends React.Component {
                 showSearch
                 dropdownMatchSelectWidth={false}
                 notFoundContent={fetching ? <Spin size="small" /> : '无'}
+                onSearch={(value) => {this.fetchColumnData(column, value, true)}}
                 onFocus={() => {this.fetchColumnData(column)}}
                 onChange={(value) => {this.changeFilterValue(filter, value, index)}}
             >

+ 70 - 0
src/components/dashboard/copyBox.jsx

@@ -0,0 +1,70 @@
+import React from 'react'
+import { connect } from 'dva'
+import { Modal, Form, Select } from 'antd'
+const { Item: FormItem } = Form
+const { Option: SelectOption } = Select
+
+class CopyBox extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            currentDataSource: props.currentDataSource ? props.currentDataSource.code : null
+        }
+    }
+
+    componentDidMount() {
+        const { dispatch } = this.props;
+        dispatch({ type: 'dataSource/fetchList' });
+    }
+
+    generateOption() {
+        const { dataSource } = this.props;
+        const { currentDataSource } = this.state;
+        const { list } = dataSource;
+
+        return list.filter((l) => l.code !== currentDataSource).map((l) => <SelectOption key={l.code} value={l.code}>{l.name}</SelectOption>);
+    }
+
+    render() {
+        const { visibleBox, hideBox, dispatch, currentDashboardCode } = this.props;
+        const { currentDataSource } = this.state;
+        const formItemLayout = {
+            labelCol: { span: 4 },
+            wrapperCol: { span: 8 },
+        }
+
+        return <Modal
+            className='copy-box'
+            title={'复制报表'}
+            visible={visibleBox}
+            onCancel={hideBox}
+            onOk={() => {
+                dispatch({ type: 'dashboard/copy', dashboardCode: currentDashboardCode, dataSourceCode: currentDataSource });
+            }}
+            maskClosable={true}
+            destroyOnClose={true}
+        >
+            <div>复制对象包括报表和报表包含的图表</div>
+            <Form>
+                <FormItem
+                    label='切换数据源'
+                    {...formItemLayout}
+                >
+                    <Select
+                        disabled={!currentDataSource}
+                        placeholder={currentDataSource ? '请选择...' : '无需选择数据源'}
+                        onChange={(value) => {
+                            this.setState({
+                                currentDataSource: value
+                            })
+                        }}
+                    >
+                        { this.generateOption() }
+                    </Select>
+                </FormItem>
+            </Form>
+        </Modal>
+    }
+}
+
+export default connect(({ present: { dataSource } }) => ({ dataSource }))(CopyBox)

+ 17 - 1
src/components/dashboard/list.jsx

@@ -6,6 +6,7 @@ import AccessObjectBox from '../common/accessObjectBox/accessObjectBox'
 import { dateFormat } from '../../utils/baseUtils'
 import DeleteBox from '../common/deleteBox/deleteBox'
 import ShareBox from './shareBox'
+import CopyBox from './copyBox'
 import './list.less'
 const { Content } = Layout
 const { Search } = Input
@@ -23,6 +24,7 @@ class DashboardList extends React.Component {
             visibleTransferBox: false,
             visibleGroupMenu: false, // 显示分组菜单
             visibleDeleteBox: false,
+            visibleCopyBox: false,
             defaultSelectedGroups: [],
             defaultSelectedUsers: [],
         }
@@ -109,7 +111,7 @@ class DashboardList extends React.Component {
 
     render() {
         const { dispatch, dashboard, main } = this.props;
-        const { visibleShareBox, shareUrl, visibleDistributeBox, visibleTransferBox, visibleDeleteBox, selectedRecord, defaultSelectedGroups, defaultSelectedUsers } = this.state
+        const { visibleShareBox, shareUrl, visibleDistributeBox, visibleTransferBox, visibleDeleteBox, visibleCopyBox, selectedRecord, defaultSelectedGroups, defaultSelectedUsers } = this.state
         const { currentUser } = main;
 
         const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
@@ -135,6 +137,14 @@ class DashboardList extends React.Component {
                 >
                     <Icon type="swap" />移交
                 </Menu.Item>}
+                { selectedRecord && currentUser.code === selectedRecord.creatorCode && <Menu.Divider />}
+                { selectedRecord && (selectedRecord.dataSources.length <= 1) && <Menu.Item
+                    onClick={()=>{
+                        this.setState({ visibleCopyBox: true})
+                    }}
+                >
+                    <Icon type="copy" />复制
+                </Menu.Item> }
                 { selectedRecord && currentUser.code === selectedRecord.creatorCode && <Menu.Item
                     onClick={(e) => {
                         this.setState({ visibleDeleteBox: true})
@@ -317,6 +327,12 @@ class DashboardList extends React.Component {
                         })
                     }}
                 />}
+                {visibleCopyBox && <CopyBox
+                    visibleBox={visibleCopyBox}
+                    hideBox={()=>{this.setState({visibleCopyBox: false})}}
+                    currentDashboardCode={selectedRecord.code}
+                    currentDataSource={selectedRecord.dataSources[0]}
+                />}
             </Layout>
         )
     }

+ 3 - 2
src/components/dashboardDesigner/content.jsx

@@ -73,11 +73,12 @@ 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,
+        let arr = relationColumns.filter(r => r.relations.length > 0).map(r => ({
+            name: r.relations[0].column.name,
             label: r.name,
             type: r.relations[0].column.type
         }));
+        return arr;
     }
 
     createFilters = (filters) => {

+ 3 - 3
src/components/dashboardDesigner/cusFilterBox.jsx

@@ -146,8 +146,8 @@ class CusFilterBox extends React.Component {
                         name: selectedDataSource.name
                     },
                     column: {
-                        label: c.label,
                         name: c.name,
+                        label: c.label,
                         type: c.type
                     }
                 });
@@ -162,15 +162,15 @@ class CusFilterBox extends React.Component {
                             name: selectedDataSource.name
                         },
                         column: {
-                            code: c.code,
                             name: c.name,
+                            label: c.label,
                             type: c.type
                         }
                     };
                 }
             }
             let index = relationColumns.findIndex(rc => rc.code === r.code);
-            relationColumns[index] = { ...r, relations };
+            relationColumns[index] = { ...r, name: relationColumns[index].name === '新字段' ? c.label : relationColumns[index].name, relations };
             dispatch({ type: 'dashboardDesigner/setField', name: 'relationColumns', value: relationColumns });
         });
     }

+ 1 - 0
src/components/dashboardDesigner/header.less

@@ -6,6 +6,7 @@
         flex: 1;
         text-align: center;
         .input-title {
+            width: 250px;
             .ant-input-wrapper {
                 input {
                     text-align: center;

+ 8 - 2
src/components/dataSourceDetail/columnConfig.jsx

@@ -292,13 +292,19 @@ class DataSourceColumnConfig extends React.Component {
                                         disabled={!dataSourceDetail.address}
                                         placeholder={dataSourceDetail.address ? '输入表名或查询SQL,注意不能以分号结尾' : '请点击底部“上一步”按钮返回上一步选择数据库连接'}
                                         autosize={{ minRows: 3 }}
-                                        value={dataSourceDetail.target}
-                                        onChange={(e) => {
+                                        // value={dataSourceDetail.target}
+                                        onBlur={(e) => {
                                             dispatch({ type: 'dataSourceDetail/setFields', fields: [
                                                 { name: 'target', value: e.target.value },
                                                 { name: 'notice', value: '' }
                                             ] });
                                         }}
+                                        // onChange={(e) => {
+                                        //     dispatch({ type: 'dataSourceDetail/setFields', fields: [
+                                        //         { name: 'target', value: e.target.value },
+                                        //         { name: 'notice', value: '' }
+                                        //     ] });
+                                        // }}
                                     />
                                 </FormItem>
                                 <div className='buttons'>

+ 0 - 2
src/models/chart.js

@@ -175,7 +175,6 @@ export default {
                         id: code
                     }
                 });
-                console.log('解析图表数据', code, res);
                 if(!res.err && res.data.code > 0) {
                     let resData = res.data.data;
                     let chartConfig = JSON.parse(resData.chartConfig || '{ "xAxis": { "column": {}, "granularity": {} }, "yAxis": { "column": {}, "gauge": {} } }');
@@ -236,7 +235,6 @@ export default {
                     message.error('解析图表错误: ' + (res.err || res.data.msg));
                 }
             }catch(e) {
-                console.log(e);
                 message.error('解析图表错误: ' + e);
             }
         },

+ 0 - 6
src/models/chartDesigner.js

@@ -352,13 +352,11 @@ export default {
                     filters: getBodyFilters(filters)
                 };
                 
-                console.log('请求柱状图数据', body);
                 let res = yield call(service.fetch, {
                     url: URLS.CHART_BAR_OPTION,
                     body: body,
                     timeout: 30000
                 });
-                console.log('请求柱状图数据', body, res);
                 if(!res.err && res.data.code > 0) {
                     let option = parseChartOption('bar', res.data.data, barConfig);
                     yield put({ type: 'silentSetField', name: 'chartOption', value: option });
@@ -368,7 +366,6 @@ export default {
                 }
                 yield put({ type: 'silentSetField', name: 'fetchConfig', value: body });
             }catch(e) {
-                console.error(e);
                 yield put({ type: 'silentSetField', name: 'chartOption', value: {} });
                 message.error('请求柱状图数据失败: ' + e);
             }
@@ -431,13 +428,11 @@ export default {
                     filters: getBodyFilters(filters)
                 };
 
-                console.log('请求折线图数据', body);
                 let res = yield call(service.fetch, {
                     url: URLS.CHART_LINE_OPTION,
                     body: body,
                     timeout: 30000
                 });
-                console.log('请求折线图数据', body, res);
                 if(!res.err && res.data.code > 0) {
                     let option = parseChartOption('line', res.data.data, lineConfig);
                     yield put({ type: 'silentSetField', name: 'chartOption', value: option });
@@ -447,7 +442,6 @@ export default {
                 }
                 yield put({ type: 'silentSetField', name: 'fetchConfig', value: body });
             }catch(e) {
-                console.error(e);
                 yield put({ type: 'silentSetField', name: 'chartOption', value: {} });
                 message.error('请求折线图数据失败: ' + e);
             }

+ 55 - 20
src/models/dashboard.js

@@ -2,6 +2,10 @@ import { message } from 'antd'
 import * as service from '../services/index'
 import URLS from '../constants/url'
 
+const generateShareCode = () => {
+    return Math.random().toString(36).substr(2).toUpperCase();
+}
+
 export default {
     namespace: 'dashboard',
     state: {
@@ -51,12 +55,31 @@ export default {
                     method: 'GET',
                     body
                 });
-                console.log('请求看板列表', body, res);
                 if(!res.err && res.data.code > 0) {
                     const resData = res.data.data.list;
                     let list = resData.map(d => {
                         let items = d.bdConfiguration ? JSON.parse(d.bdConfiguration) : [];
+
+                        const allDataSources = items.map(item => {
+                            if(item.viewType === 'chart') {
+                                return {
+                                    code: item.dataSourceCode,
+                                    name: item.dataSourceName
+                                }
+                            }else {
+                                return null
+                            }
+                        }).filter(item => !!item);
+    
+                        const dataSources = [];
+                        allDataSources.forEach(ad => {
+                            if(!dataSources.find(d => d.code === ad.code)) {
+                                dataSources.push(ad);
+                            }
+                        });
+
                         return {
+                            key: d.id + '',
                             code:  d.id+'',
                             name: d.bdName,
                             items: items,
@@ -65,6 +88,8 @@ export default {
                             creatorName: d.createBy,
                             createTime: d.createDate,
                             shareCode: d.bdCode,
+                            dataSources: dataSources,
+                            chartCodes: d.chartIds ? d.chartIds.split(',') : [],
                             demo: d.demo
                         }
                     })
@@ -91,7 +116,6 @@ export default {
                         id: code
                     }
                 });
-                console.log('解析看板数据', code, res);
                 if(!res.err && res.data.code > 0) {
                     const resData = res.data.data;
                     let items = resData.bdConfiguration ? JSON.parse(resData.bdConfiguration) : [];
@@ -147,7 +171,6 @@ export default {
                     message.error('解析看板错误: ' + (res.err || res.data.msg));
                 }
             }catch(e) {
-                console.log(e);
                 message.error('解析看板错误: ' + e);
             }finally {
                 yield put({ type: 'dashboardDesigner/silentSetField', name: 'loading', value: false });
@@ -156,22 +179,21 @@ export default {
         *remoteAdd(action, { select, call, put }) {
             try {
                 const dashboardDesigner = yield select(state => state.present.dashboardDesigner);
-                const { name, items, relationColumns, filters, shareCode } = dashboardDesigner;
+                const { name, items, description, relationColumns, filters, shareCode, chartCodes } = dashboardDesigner;
                 let body = {
                     bdName: name,
-                    bdNote: '',
+                    bdNote: description,
                     bdConfiguration: JSON.stringify(items),
                     relationColumns: JSON.stringify(relationColumns),
                     filters: JSON.stringify(filters) || '',
-                    bdCode: shareCode,
-                    chartCodes: []
+                    bdCode: shareCode || generateShareCode(),
+                    thumbnail: '',
+                    chartIds: chartCodes.join(',')
                 }
-                console.log('新增看板', body);
                 const res = yield call(service.fetch, {
                     url: URLS.DASHBOARD_ADD,
                     body: body
                 });
-                console.log('新增看板', body, res);
                 if(!res.err && res.data.code > 0) {
                     yield put({ type: 'fetchList', mandatory: true });
                     message.success('保存成功');
@@ -179,29 +201,27 @@ export default {
                     message.error('保存失败: ' + (res.err || res.data.msg));
                 } 
             }catch(e) {
-                console.log(e);
                 message.error('保存失败: ' + e);
             }
         },
         *remoteQucikAdd(action, { select, call, put }) {
             try {
                 const dashboardDesigner = yield select(state => state.present.dashboardDesigner);
-                const { name, items, description, relationColumns, filters, shareCode } = dashboardDesigner;
+                const { name, items, description, relationColumns, filters } = dashboardDesigner;
                 let body = {
                     bdName: name,
                     bdNote: description,
                     bdConfiguration: JSON.stringify(items),
                     relationColumns: JSON.stringify(relationColumns),
                     filters: JSON.stringify(filters) || "",
-                    bdCode: shareCode,
-                    chartCodes: []
+                    bdCode: generateShareCode(),
+                    thumbnail: '',
+                    chartIds: ''
                 }
-                console.log('快速新增看板', body);
                 const res = yield call(service.fetch, {
                     url: URLS.DASHBOARD_ADD,
                     body: body
                 });
-                console.log('快速新增看板', body, res);
                 if(!res.err && res.data.code > 0) {
                     yield put({ type: 'fetchList', mandatory: true });
                     yield put({ type: 'main/redirect', path: '/dashboard/' + res.data.data });
@@ -209,7 +229,6 @@ export default {
                     message.error('保存失败: ' + (res.err || res.data.msg));
                 } 
             }catch(e) {
-                console.log(e);
                 message.error('保存失败: ' + e);
             }
         },
@@ -231,7 +250,6 @@ export default {
                     url: URLS.DASHBOARD_UPDATE,
                     body: body
                 });
-                console.log('修改看板', body, res);
                 if(!res.err && res.data.code > 0) {
                     yield put({ type: 'fetchList', mandatory: true });
                     yield put({ type: 'dashboardDesigner/silentSetField', name: 'dirty', value: false });
@@ -240,7 +258,6 @@ export default {
                     message.error('保存失败: ' + (res.err || res.data.msg));
                 } 
             }catch(e) {
-                console.error(e);
                 message.error('保存失败: ' + e);
             }
         },
@@ -358,7 +375,7 @@ export default {
                     const resData = res.data.data;
                     let items = resData.bdConfiguration ? JSON.parse(resData.bdConfiguration) : [];
                     let relationColumns = resData.relationColumns ? JSON.parse(resData.relationColumns) : [];
-                    let chartCodes = resData.chartCodes ? resData.chartCodes.split(',') : [];
+                    let chartCodes = resData.chartIds ? resData.chartIds.split(',') : [];
                     const main = yield select(state => state.present.main);
                     const { currentUser } = main;
 
@@ -458,7 +475,7 @@ export default {
                     const resData = res.data.data;
                     let items = resData.bdConfiguration ? JSON.parse(resData.bdConfiguration) : [];
                     let relationColumns = resData.relationColumns ? JSON.parse(resData.relationColumns) : [];
-                    let chartCodes = resData.chartCodes ? resData.chartCodes.split(',') : [];
+                    let chartCodes = resData.chartIds ? resData.chartIds.split(',') : [];
                     const main = yield select(state => state.present.main);
                     const { currentUser } = main;
 
@@ -514,6 +531,24 @@ export default {
                 yield put({ type: 'dashboardDesigner/silentSetField', name: 'loading', value: false });
             }
         },
+        *copy(action, { select, call, put }) {
+            const { dashboardCode, dataSourceCode } = action;
+
+            const res = yield call(service.fetch, {
+                url: URLS.DASHBOARD_COPY,
+                body: {
+                    dashboardId: dashboardCode,
+                    dataSourceId: dataSourceCode
+                }
+            });
+            if(!res.err && res.data.code > 0) {
+                message.success('复制成功');
+                return true;
+            }else {
+                message.error('复制失败: ' + (res.err || res.data.msg));
+                return false;
+            }
+        }
     },
     subscriptions: {
         setup({ dispatch, history}) {

+ 0 - 2
src/models/dataSource.js

@@ -222,7 +222,6 @@ export default {
                         id: code
                     }
                 });
-                console.log('解析数据源', code, res);
                 if(!res.err && res.data.code > 0) {
                     let resData = res.data.data;
                     let columnConfig = JSON.parse(resData.columnConfig) || [];
@@ -274,7 +273,6 @@ export default {
                     message.error('数据源解析错误: ' + (res.err || res.data.msg));
                 }
             }catch(e) {
-                console.log(e);
                 message.error('数据源解析错误: ' + e);
                 yield put({ type: 'list', list: [] });
             }

+ 5 - 3
src/models/parseChartOption.js

@@ -45,6 +45,7 @@ export default function(viewType, data, chartConfig) {
 function barOption(data, barConfig) {
     let xTitle = barConfig.xAxis?`${barConfig.xAxis.column.label}${barConfig.xAxis.granularity.value?'('+barConfig.xAxis.granularity.label+')':''}`:null
     let yTitle = barConfig.yAxis?`${barConfig.yAxis.column.label}${barConfig.yAxis.gauge.value?'('+barConfig.yAxis.gauge.label+')':''}`:null
+    data.serieses = data.serieses || [];
 
     let option = {
         tooltip : {
@@ -57,7 +58,7 @@ function barOption(data, barConfig) {
             }
         },
         legend: {
-            show: true
+            show: data.serieses.length > 1
         },
         grid: {
             left: '10%',
@@ -130,7 +131,8 @@ function pieOption(data, pieConfig) {
 function lineOption(data, lineConfig) {
     let xTitle = lineConfig.xAxis?`${lineConfig.xAxis.column.label}${lineConfig.xAxis.granularity.value?'('+lineConfig.xAxis.granularity.label+')':''}`:null
     let yTitle = lineConfig.yAxis?`${lineConfig.yAxis.column.label}${lineConfig.yAxis.gauge.value?'('+lineConfig.yAxis.gauge.label+')':''}`:null
-    
+    data.serieses = data.serieses || [];
+
     let option = {
         tooltip: {
             trigger: 'axis',
@@ -139,7 +141,7 @@ function lineOption(data, lineConfig) {
             }
         },
         legend: {
-            show: true
+            show: data.serieses.length > 1
         },
         xAxis:  {
             name: xTitle || '横轴',