Browse Source

报表中图表组件统一使用图表中的组件/数据视图组件分页逻辑重新设计/总体统计图重新设计

zhuth 6 years ago
parent
commit
b70ed307d3

+ 65 - 0
src/components/chartDesigner/charts/aggregateTableView.jsx

@@ -0,0 +1,65 @@
+/**
+ * 总体统计图
+ */
+import React from 'react'
+import EmptyContent from '../../common/emptyContent/index'
+import './aggregateTableView.less'
+
+class AggregateTableView extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {}
+    }
+
+    getTableHeader = () => {
+        const { chartOption } = this.props;
+        const { statistics, direction } = chartOption;
+        return <thead>
+            <tr>
+                <th colSpan={direction === 'vertical' ? '2' : statistics.length + ''} style={{ textAlign: 'center' }}>{chartOption.targetColumn.label}</th>
+            </tr>
+        </thead>
+    }
+    getTableBody = () => {
+        const { chartOption } = this.props;
+        const { statistics, direction } = chartOption;
+        if(direction === 'vertical') {
+            return <tbody>
+                {statistics.map((s, i) => (
+                    <tr key={i}>
+                        <td>{s.label}</td>
+                        <td>{Number(Number(s.value).toFixed(4))}</td>
+                    </tr>
+                ))}
+            </tbody>
+        }else {
+            return <tbody>
+                <tr>
+                    {statistics.map((s, i) => (
+                        <td key={i}>{s.label}</td>
+                    ))}
+                </tr>
+                <tr>
+                    {statistics.map((s, i) => (
+                        <td key={i}>{Number(Number(s.value).toFixed(4))}</td>
+                    ))}
+                </tr>
+            </tbody>
+        }
+    }
+
+    render() {
+        const { chartOption } = this.props;
+        if(!chartOption || !chartOption.targetColumn || !chartOption.statistics || chartOption.statistics.length === 0) {
+            return <EmptyContent />
+        }
+        return <div ref={ node => this.formRef = node } className='aggregate-container'>
+            <table className={`aggregate-table`} border='1'>
+                { this.getTableHeader() }
+                { this.getTableBody() }
+            </table>
+        </div>
+    }
+}
+
+export default AggregateTableView

+ 14 - 0
src/components/chartDesigner/charts/aggregateTableView.less

@@ -0,0 +1,14 @@
+.aggregate-container {
+    background: #fff;
+    padding: 12px;
+    height: 100%;
+    .aggregate-table {
+        tbody {
+            tr {
+                td {
+                    padding: 8px 16px;
+                }
+            }
+        }
+    }
+}

+ 2 - 8
src/components/chartDesigner/charts/echartsView.jsx

@@ -1,11 +1,9 @@
 import React from 'react'
 import Echarts from 'echarts-for-react'
-import { connect } from 'dva'
 import { hashcode } from '../../../utils/baseUtils'
 import EmptyContent from '../../common/emptyContent'
 
-const EchartsView = ({ chartDesigner }) => {
-    const { chartOption } = chartDesigner;
+const EchartsView = ({ chartOption }) => {
     if(!chartOption.series) {
         return <EmptyContent />
     }else {
@@ -18,8 +16,4 @@ const EchartsView = ({ chartDesigner }) => {
     }
 }
 
-function mapStateToProps({ present: { chartDesigner } }) {
-    return { chartDesigner: chartDesigner }
-}
-
-export default connect(mapStateToProps)(EchartsView);
+export default EchartsView;

+ 39 - 55
src/components/chartDesigner/charts/tableView.jsx

@@ -1,6 +1,5 @@
 import React from 'react'
 import { Table } from 'antd'
-import { connect } from 'dva'
 import './tableView.less'
 
 class TableView extends React.Component {
@@ -9,54 +8,52 @@ class TableView extends React.Component {
         super(props);
         this.state = {
             columnWidth: 200,
-            tableHeaderHeight: 60,
+            tableScrollHeight: 0,
         };
     }
 
-    // componentDidMount() {
-    //     window.addEventListener('resize', this.onWindowResize);
-    // }
+    componentDidMount() {
+        const { viewRef } = this.props;
+        this.onTableLayoutChange();
+        this[viewRef].addEventListener('resize', this.onTableLayoutChange);
+    }
+
+    componentWillUnmount() {
+        const { viewRef } = this.props;
+        this[viewRef].removeEventListener('resize', this.onTableLayoutChange);
+    }
 
-    // componentWillUnmount() {
-    //     window.removeEventListener('resize', this.onWindowResize);
-    // }
+    onTableLayoutChange = () => {
+        this.setState(this.getTableLayout());
+    }
 
-    getContentLayout = () => {
-        const tableEl = document.getElementsByClassName('table-view')[0];
-        if(!tableEl) {
-            return {
-                contentSize: {
-                    width: 0,
-                    height: 0,
-                },
-                tableHeaderHeight: 0,
+    getTableLayout = () => {
+        const { chartOption, viewRef, onPageSizeChange } = this.props;
+        let bodyRef = this[viewRef];
+        const containerHeight = bodyRef.offsetHeight;
+        const tableEl = bodyRef.getElementsByClassName('table-view')[0];
+        let obj = {};
+        if(tableEl) {
+            let tableScrollHeight = containerHeight - 40 - 24 - 8 * 2;
+            let pageSize = ~~(tableScrollHeight/38) + 1;
+            obj = {
+                tableScrollHeight
+            }
+            if(typeof onPageSizeChange === 'function' && pageSize !== chartOption.pageSize) {
+                // 在报表中的dataView第一次数据请求在此发起
+                onPageSizeChange(1, pageSize)
             }
         }
-        const tableHeaderEl = tableEl.getElementsByTagName('thead')[0];
-        const contentEl = document.getElementsByClassName('content-body')[0];
-
-        return {
-            contentSize: {
-                width: contentEl.offsetWidth,
-                height: contentEl.offsetHeight,
-            },
-            tableHeaderHeight: tableHeaderEl.offsetHeight + 2, // 表头高度(含边框)
-        };
+        return obj;
     }
 
     render() {
-        const { chartDesigner, dispatch, inPage } = this.props;
-        const { columnWidth } = this.state;
-        const { chartOption, styleConfig } = chartDesigner;
-        const { columns, dataSource, pageSize, total } = chartOption;
+        const { viewRef, columns, dataSource, styleConfig, pagination } = this.props;
+        const { columnWidth, tableScrollHeight } = this.state;
+
+        const { bordered } = (styleConfig || {});
 
-        const layout = this.getContentLayout();
-    
-        const { table } = styleConfig || {};
-        const { bordered } = table || {};
-        const tableContentHeight = layout.contentSize.height - layout.tableHeaderHeight - 42 - 10;
-    
-        return (
+        return (<div style={{ height: '100%' }} ref={node => this[viewRef] = node }>
             <Table
                 className='table-view'
                 columns={columns ? columns.map((c, i) => {
@@ -79,25 +76,12 @@ class TableView extends React.Component {
                 bordered={bordered}
                 scroll={{
                     x: columns ? columns.length * columnWidth : 0,
-                    y: tableContentHeight
-                }}
-                pagination={{
-                    pageSize: pageSize || 25,
-                    total,
-                    showTotal: (total, range) => {
-                        return `第${range[0]}到第${range[1]}条数据,共${total}条数据`;
-                    },
-                    onChange: (page, pageSize) => {
-                        inPage && dispatch({ type: 'chartDesigner/fetchDataViewData', page, pageSize });
-                    }
+                    y: tableScrollHeight
                 }}
+                pagination={pagination}
             />
-        );
+        </div>);
     }
 }
 
-function mapStateToProps({ present: { chartDesigner } }) {
-    return { chartDesigner: chartDesigner }
-}
-
-export default connect(mapStateToProps)(TableView);
+export default TableView;

+ 34 - 11
src/components/chartDesigner/content.jsx

@@ -6,11 +6,12 @@ import DataViewConfigForm from './sections/dataViewConfigForm'
 import BarConfigForm from './sections/barConfigForm'
 import PieConfigForm from './sections/pieConfigForm'
 import LineConfigForm from './sections/lineConfigForm'
-import TableView from './charts/tableView'
 import ScatterConfigForm from './sections/scatterConfigForm'
 import StyleConfigForm from './sections/style/index'
 import OtherConfigForm from './sections/otherConfigForm'
+import TableView from './charts/tableView'
 import EchartsView from './charts/echartsView'
+import AggregateTableView from './charts/aggregateTableView'
 import ToolBar from './sections/toolbar'
 import { connect } from 'dva'
 import EmptyContent from '../common/emptyContent/index'
@@ -56,30 +57,46 @@ class ChartDesignerContent extends React.Component {
     render() {
         const { chartDesigner, dispatch } = this.props;
         const { isOwner, autoRefresh } = this.state;
-        const { baseConfig } = chartDesigner;
+        const { code, baseConfig, chartOption, styleConfig } = chartDesigner;
         const { viewType } = baseConfig;
 
         let configForm, chartView;
 
         if(viewType === 'aggregateTable') {
             configForm = (<AggregateTableConfigForm autoRefresh={autoRefresh}/>);
-            chartView = (<TableView />);
+            chartView = (<AggregateTableView chartOption={chartOption}/>);
         }else if(viewType === 'dataView') {
             configForm = (<DataViewConfigForm autoRefresh={autoRefresh}/>);
-            chartView = (<TableView inPage={true}/>);
+            chartView = (<TableView
+                chartOption={chartOption}
+                viewRef={`tableview-${code}`}
+                columns={chartOption.columns}
+                dataSource={chartOption.dataSource}
+                styleConfig={styleConfig.dataView}
+                pagination={{
+                    pageSize: chartOption.pageSize || 25,
+                    total: chartOption.total,
+                    showTotal: (total, range) => {
+                        return `第${range[0]}到第${range[1]}条数据,共${total}条数据`;
+                    },
+                    onChange: (page, pageSize) => {
+                        dispatch({ type: 'chartDesigner/fetchDataViewData', page, pageSize });
+                    }
+                }}
+                />);
         }else if(viewType === 'line') {
             configForm = (<LineConfigForm autoRefresh={autoRefresh}/>);
             // optionConfig 可以用来放替换属性(已做深拷贝替换)
-            chartView = (<EchartsView />);
+            chartView = (<EchartsView chartOption={chartOption} />);
         }else if(viewType === 'bar') {
             configForm = (<BarConfigForm autoRefresh={autoRefresh}/>);
-            chartView = (<EchartsView />);
+            chartView = (<EchartsView chartOption={chartOption} />);
         }else if(viewType === 'pie') {
             configForm = (<PieConfigForm autoRefresh={autoRefresh}/>);
-            chartView = (<EchartsView />);
+            chartView = (<EchartsView chartOption={chartOption} />);
         }else if(viewType === 'scatter') {
             configForm = (<ScatterConfigForm autoRefresh={autoRefresh}/>);
-            chartView = (<EchartsView />);
+            chartView = (<EchartsView chartOption={chartOption} />);
         }else {
             chartView = <EmptyContent />
         }
@@ -105,14 +122,20 @@ class ChartDesignerContent extends React.Component {
                             <Switch defaultChecked={autoRefresh} checkedChildren='自动刷新' unCheckedChildren='手动刷新' onChange={(checked) => {
                                 // 自动刷新后立即请求一次数据
                                 if(checked) {
-                                    dispatch({ type: 'chartDesigner/fetchChartData' });
+                                    let page = chartOption.page || 1;
+                                    let pageSize = chartOption.pageSize || ~~((document.getElementsByClassName('content-body')[0].offsetHeight - 12 * 2 - 40 - 24 - 8 * 2)/38) + 1;
+                                    dispatch({ type: 'chartDesigner/fetchChartData', page, pageSize });
                                 }
                                 this.setState({
                                     autoRefresh: checked
                                 });
                                 // dispatch({ type: 'chartDesigner/silentSetField', name: 'autoRefresh', value: checked })
                             }}/>
-                            <Button style={{ display: autoRefresh ? 'none' : 'block' }} size='small' onClick={() => { dispatch({ type: 'chartDesigner/fetchChartData'}) }}
+                            <Button style={{ display: autoRefresh ? 'none' : 'block' }} size='small' onClick={() => {
+                                let page = chartOption.page || 1;
+                                let pageSize = chartOption.pageSize || ~~((document.getElementsByClassName('content-body')[0].offsetHeight - 12 * 2 - 40 - 24 - 8 * 2)/38) + 1;
+                                dispatch({ type: 'chartDesigner/fetchChartData', page, pageSize})
+                            }}
                             >立即刷新</Button>
                         </div>
                     </Footer> }
@@ -122,7 +145,7 @@ class ChartDesignerContent extends React.Component {
                         <Header className='content-header'>
                             <ToolBar className='header-toolbar' autoRefresh={autoRefresh} isOwner={isOwner}/>
                         </Header>
-                        <Content className='content-body' ref='contentEl' >
+                        <Content className='content-body' >
                             { chartView }
                         </Content>
                     </Layout>

+ 16 - 4
src/components/chartDesigner/sections/aggregateTableConfigForm.jsx

@@ -2,7 +2,7 @@
  * 总体统计数据表
  */
 import React from 'react';
-import { Form, Select, Checkbox, Row, Col } from 'antd';
+import { Form, Select, Checkbox, Row, Col, Radio } from 'antd';
 import { connect } from 'dva';
 import STATISTICS_OPTION from './statisticsOption.json'
 const FormItem = Form.Item;
@@ -32,7 +32,7 @@ class AggregateTableConfigForm extends React.Component {
 		const props = this.props;
 		const { autoRefresh, chartDesigner, dispatch } = props;
 		const{ searchKey, formItemLayout } = this.state;
-		const { columns, aggregateTableConfig } = chartDesigner;
+		const { columns, aggregateTableConfig, chartOption } = chartDesigner;
 		
 		let options = columns.filter(c => c.label.toLowerCase().indexOf(searchKey.toLowerCase()) !== -1);
 		return (
@@ -67,7 +67,19 @@ class AggregateTableConfigForm extends React.Component {
 						
 					</CheckboxGroup>
 				</FormItem>
-				<FormItem label='分组' {...formItemLayout}>
+				<FormItem label='排布方向' {...formItemLayout}>
+					<Radio.Group onChange={e => {
+						let value = e.target.value;
+						dispatch({ type: 'chartDesigner/changeField', name: 'aggregateTableConfig', value: { ...props.chartDesigner.aggregateTableConfig, direction: value }, autoRefresh: false });
+						dispatch({ type: 'chartDesigner/setField', name: 'chartOption', value: { ...chartOption, direction: value } });
+					}}
+						value={aggregateTableConfig.direction}
+					>
+						<Radio value='vertical'>垂直</Radio>
+						<Radio value='horizontal'>水平</Radio>
+					</Radio.Group>
+				</FormItem>
+				{/* <FormItem label='分组' {...formItemLayout}>
 					<Select
 						mode='multiple'
 						labelInValue={true}
@@ -92,7 +104,7 @@ class AggregateTableConfigForm extends React.Component {
 							return (<Option key={i} value={c.name}>{c.label}</Option>)
 						})}
 					</Select>
-				</FormItem>
+				</FormItem> */}
 			</Form>
 		);
 	}

+ 1 - 1
src/components/chartDesigner/sections/chartType.json

@@ -1,6 +1,6 @@
 [{
     "type": "aggregateTable",
-    "label": "总体统计数据表",
+    "label": "总体统计",
     "icon": "table"
 }, {
     "type": "dataView",

+ 5 - 5
src/components/chartDesigner/sections/statisticsOption.json

@@ -1,12 +1,12 @@
 [
     {"value": "count", "label": "条数", "columnType": ["time", "categorical", "scale", "string"] }, 
+    {"value": "dictinctCount", "label": "去重条数", "columnType": ["time", "categorical", "scale", "string"] }, 
     {"value": "max", "label": "最大值", "columnType": ["scale"] },
-    {"value": "percent", "label": "百分比", "columnType": ["time", "categorical", "scale", "string"] },
-    {"value": "75th", "label": "75th值", "columnType": ["scale"] },
+    {"value": "min","label": "最小值", "columnType": ["scale"] },
+    {"value": "avg","label": "平均数", "columnType": ["scale"] },
     {"value": "sum","label": "总和", "columnType": ["scale"] },
     {"value": "median","label": "中位数", "columnType": ["scale"] },
-    {"value": "avg","label": "平均数", "columnType": ["scale"] },
-    {"value": "25th","label": "25th值", "columnType": ["scale"] },
     {"value": "stddev","label": "标准差", "columnType": ["scale"] },
-    {"value": "min","label": "最小值", "columnType": ["scale"] }
+    {"value": "75th", "label": "75th值", "columnType": ["scale"] },
+    {"value": "25th","label": "25th值", "columnType": ["scale"] }
 ]

+ 1 - 0
src/components/chartDesigner/sections/toolbar.jsx

@@ -133,6 +133,7 @@ class Toolbar extends React.Component {
                 </div>
                 {visibleFilterBox && <FilterBox code={code} columns={columns} filterData={filters} visibleFilterBox={visibleFilterBox} showFilterBox={this.showFilterBox} hideFilterBox={this.hideFilterBox} createFilters={this.createFilters} />}
                 {visibleDataPreviewBox && <DataPreview
+                    title='查看列'
                     visibleBox={visibleDataPreviewBox}
                     hideBox={() => {
                         this.setState({

+ 2 - 1
src/components/common/dataPreview/dataPreview.jsx

@@ -43,7 +43,7 @@ class DataPreview extends React.Component {
     }
 
     render() {
-        const { fetchFunction, dataList, visibleBox, hideBox } = this.props;
+        const { title, fetchFunction, dataList, visibleBox, hideBox } = this.props;
         const { loading, columns, dataSource, pageSize, total } = dataList;
         const { screenWidth, screenHeight, boxW, boxH, columnWidth, tableHeaderHeight } = this.state;
         const tableBodyWidth = screenWidth * boxW - 10 - 10 - 18;
@@ -52,6 +52,7 @@ class DataPreview extends React.Component {
         return <Modal
             className='datapreview'
             style={{ top: `${(1 - boxH) * 0.5 * 100}%` }}
+            title={title}
             width={`${100 * boxW}%`}
             height={`${100 * boxH}%`}
             visible={visibleBox}

+ 6 - 1
src/components/common/dataPreview/dataPreview.less

@@ -8,7 +8,12 @@
         height: 100%;
         padding-top: 40px;
         overflow: hidden;
-        
+        .ant-modal-header {
+            display: block;
+            padding-top: 0;
+            margin-top: -30px;
+            padding-bottom: 6px;
+        }
         .ant-modal-body {
             height: 100%;
             padding: 0px 10px;

+ 14 - 7
src/components/common/loading/index.jsx

@@ -11,19 +11,26 @@ class Loading extends React.Component {
     componentDidMount() {
         let loadingEl = this.loadingRef;
         let containerEl = loadingEl.parentElement;
-        let width = containerEl.offsetWidth;
-        let height = containerEl.offsetHeight;
+        let box = containerEl.getBoundingClientRect();
+        let width = box.width;
+        let height = box.height;
+        let top = box.top;
+        let left = box.left;
         this.setState({
-            width: `${width}px`,
-            height: `${height}px`,
+            style: {
+                width: `${width}px`,
+                height: `${height}px`,
+                // top: `${top}px`,
+                // left: `${left}px`,
+            }
         });
     }
 
     render() {
-        const { visible } = this.props;
-        const { width, height } = this.state;
+        const { visible, style: propsStyle } = this.props;
+        const { style } = this.state;
 
-        return <div ref={node => this.loadingRef = node} style={{ display: visible ? 'block' : 'none', position: 'absolute', height, width, zIndex: '4', background: 'rgba(51,51,51,.1)' }}>
+        return <div ref={node => this.loadingRef = node} style={{ display: visible ? 'block' : 'none', position: 'absolute', zIndex: '4', background: 'rgba(51,51,51,.1)', ...style, ...propsStyle }}>
             <Spin style={{ display: 'inline-block', position: 'absolute', top: '50%', left: '50%', margin: '-10px' }} indicator={<Icon type="loading" style={{ fontSize: 24 }} spin />} />
         </div>
     }

+ 35 - 82
src/components/dashboardDesigner/chartView.jsx

@@ -1,10 +1,12 @@
 import React from 'react'
-import Echarts from 'echarts-for-react'
-import { Table, Spin, Icon } from 'antd'
+import AggregateTableView from '../chartDesigner/charts/aggregateTableView'
+import TableView from '../chartDesigner/charts/tableView'
+import EchartsView from '../chartDesigner/charts/echartsView'
 import RichTextEditor from './richTextEditor'
 import { connect } from 'dva'
-import { isEqual, hashcode } from '../../utils/baseUtils'
+import { hashcode } from '../../utils/baseUtils'
 import EmptyContent from '../common/emptyContent/index'
+import Loading from '../common/loading/index'
 
 class ChartView extends React.Component {
     
@@ -20,97 +22,50 @@ class ChartView extends React.Component {
     }
 
     componentDidMount() {
-        const { item, dispatch, reload } = this.props;
-        const { viewType } = item;
-        if(viewType === 'chart') {
-            dispatch({ type: 'dashboardDesigner/fetchChartData', item, mandatory: reload });
+        const { item, dispatch, reload, minLayoutHeight } = this.props;
+        const { viewType, chartType, layout } = item;
+        let page = 1;
+        let pageSize = ~~((layout.h * minLayoutHeight + (layout.h - 1) * 12 - 20 - 40 - 24 - 8 * 2)/38) + 1;
+        if(viewType === 'chart' && chartType !== 'dataView') {
+            dispatch({ type: 'dashboardDesigner/fetchChartData', item, mandatory: reload, page, pageSize });
         }
-        this.setTableSize();
-        window.addEventListener('resize', this.setTableSize);
-    }
-
-    componentWillUnmount() {
-        window.removeEventListener('resize', this.setTableSize);
-    }
-
-    componentDidUpdate(preProps) {
-        if(!isEqual(preProps.item.layout, this.state.layout)) {
-            this.setState({
-                layout: { ...preProps.item.layout },
-                tableScrollHeight: this.getTableScrollSize()
-            });
-        }
-    }
-
-    setTableSize = () => {
-        this.setState({
-            tableScrollHeight: this.getTableScrollSize()
-        });
-    }
-
-    getTableScrollSize = () => {
-        const { chartRef } = this.props;
-        let ref = this[chartRef];
-        return ref.offsetHeight - 40 - 10 - 3 - 40;
     }
 
     render() {
         const { item, editMode, dispatch, readOnly, chartRef } = this.props;
-        const { tableScrollHeight } = this.state;
-        const { viewType, chartType, content, chartOption, fetching } = item;
+        // const { tableScrollHeight } = this.state;
+        const { chartCode, viewType, chartType, content, chartOption, styleConfig, fetching, layout } = item;
         let children = <EmptyContent/>;
         
         if(viewType === 'chart') { // 图表类型
             if(chartOption) {
-                let newOption = chartOption;
-                let type = ['bar', 'line', 'pie', 'scatter'].indexOf(chartType) !== -1 ? 'echarts' :
-                    (['aggregateTable', 'dataView'].indexOf(chartType) !== -1 ? 'table' : 'default');
-                if(type === 'echarts') {
-                    children = <Echarts
-                        key={hashcode(chartOption)}
-                        option={newOption}
-                        className='rc-echarts mover'
-                        style={{height: '100%'}}
-                    />
-                }else if(type === 'table') {
-                    const { columns, dataSource } = chartOption;
-                    let pagination
-                    if (chartType === 'dataView') {
-                        pagination = {
-                            simple:true,
-                            hideOnSinglePage:true,
-                            pageSize: chartOption.pageSize || 25,
+                if(chartType === 'aggregateTable') {
+                    children = (<AggregateTableView chartOption={chartOption}/>);
+                }else if(chartType === 'dataView') {
+                    children = (<TableView
+                        key={`${chartCode}-${layout.h}-${hashcode(chartOption.page)}`} // 这个KEY的变化才会正确引起页面的刷新
+                        chartOption={chartOption}
+                        viewRef={`${chartRef}-tableview`}
+                        columns={chartOption.columns}
+                        dataSource={chartOption.dataSource}
+                        styleConfig={(styleConfig || {}).dataView}
+                        pagination={{
+                            pageSize: chartOption.pageSize,
                             total: chartOption.total,
-                            current: chartOption.pageNum,
+                            current: chartOption.page,
                             showTotal: (total, range) => {
                                 return `第${range[0]}到第${range[1]}条数据,共${total}条数据`;
                             },
                             onChange: (page, pageSize) => {
-                                dispatch({ type: 'dashboardDesigner/fetchChartData', item, page, pageSize, mandatory: chartType === 'dataView' });
+                                dispatch({ type: 'dashboardDesigner/fetchChartData', item, mandatory: true, page, pageSize });
                             }
-                        }
-                    }else {pagination = {
-                        pageSize: chartOption.pageSize || 25,
-                        total: chartOption.total
-                    }}
-                    children = <Table
-                        key={hashcode({
-                            columns,
-                            dataSource
-                        })}
-                        className='dashboard-table'
-                        size='small'
-                        scroll={{
-                            x: columns ? columns.length * 200 : 0,
-                            y: tableScrollHeight,
                         }}
-                        pagination={pagination}
-                        columns={columns ? columns.map(c => ({
-                            ...c,
-                            width: 200
-                        })) : []}
-                        dataSource={dataSource}
-                    />
+                        onPageSizeChange={(page, pageSize) => {
+                            dispatch({ type: 'dashboardDesigner/fetchChartData', item, mandatory: true, page, pageSize });
+                        }}
+                    />);
+                }else if(['line', 'bar', 'pie', 'scatter'].indexOf(chartType) > -1) {
+                    children = (<EchartsView chartOption={chartOption}/>);
                 }
             }
         }else if(viewType === 'richText') { // 富文本类型
@@ -122,11 +77,9 @@ class ChartView extends React.Component {
         }
     
         return (
-            <div ref={node => this[chartRef] = node } className='chartview-content' style={{ width: '100%', height: '100%' }}>
+            <div ref={node => this[chartRef] = node } className='chartview-content'>
                 { children }
-                { fetching && <div style={{ display: fetching ? 'block' : 'none', position: 'absolute', height: '100%', width: '100%', zIndex: '4', background: 'rgba(51,51,51,.1)', top: 0, left: 0 }}>
-                    <Spin style={{ display: 'inline-block', position: 'absolute', top: '50%', left: '50%', margin: '-10px' }} indicator={<Icon type="loading" style={{ fontSize: 24 }} spin />} />
-                </div> }
+                <Loading visible={fetching} style={{ top: 0, left: 0, width: '100%', height: '100%' }}/>
             </div>
         );
     }

+ 0 - 45
src/components/dashboardDesigner/content.jsx

@@ -5,7 +5,6 @@ import ViewLayout from './viewLayout'
 import FilterBox from '../common/filterBox/filterBox2'
 import Filter from '../common/filterBox/filter2'
 import ConfigSider from './configSider'
-import moment from 'moment'
 
 const { Header, Content, Sider } = Layout
 
@@ -90,50 +89,6 @@ class DashboardDesignerContent extends React.Component {
         });
     }
 
-    /**
-     * 生成过滤规则文本
-     */
-    createFilterLabel = (filter) => {
-        let { label, operator, operatorLabel, type, value1, value2 } = filter;
-        let filterLabel;
-
-        if(type === 'string' || type === 'index') {
-            if(operator === 'null' || operator === 'notNull') {
-                filterLabel = `${label} ${operatorLabel}`;
-            }else {
-                filterLabel = `${label} ${operatorLabel} ${value1}`;
-            }
-        }else if(type === 'scale') {
-            if(operator === 'null' || operator === 'notNull') {
-                filterLabel = `${label} ${operatorLabel}`;
-            }else if(operator === 'between') {
-                filterLabel = `${label} ${operatorLabel} ${value1} ~ ${value2}`; 
-            }else {
-                filterLabel = `${label} ${operatorLabel} ${value1}`; 
-            }
-        }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') {
-                filterLabel = `${label} ${operatorLabel} ${value1} ~ ${value2}`;
-            }else {
-                filterLabel = `${label} ${operatorLabel} ${value1}`;
-            }
-        }else if(type === 'categorical') {
-            if(operator === 'null' || operator === 'notNull') {
-                filterLabel = `${label} ${operatorLabel}`;
-            }else {
-                filterLabel = `${label} ${operatorLabel} ${value1}`;
-            }
-        }else {
-            filterLabel = '错误条件';
-        }
-        return filterLabel;
-    }
-
     filterUsingChange = (e) => {
         const key = e.target.dataset.key;
         const { dashboardDesigner, dispatch, afterRefresh } = this.props;

+ 0 - 1
src/components/dashboardDesigner/layout.jsx

@@ -39,7 +39,6 @@ class DashboardDesigner extends React.Component {
         }
     }
 
-
     isOwner = () => {
         const { dashboardDesigner, main } = this.props;
         const { creatorCode } = dashboardDesigner;

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

@@ -29,9 +29,10 @@
                         display: flex;
                         flex-direction: column;
                         padding: 0;
-                        overflow-x: hidden;
+                        overflow: hidden;
                         >.dashboard-viewcontent {
                             flex: 1;
+                            overflow: auto;
                             // margin: 8px;
                         }
                     }

+ 31 - 21
src/components/dashboardDesigner/viewLayout.jsx

@@ -1,10 +1,11 @@
 import React from "react"
 import "./viewLayout.less"
 import ReactGridLayout from 'react-grid-layout'
-import { Icon, Modal, Input } from 'antd'
+import { Icon, Input } from 'antd'
 import { connect } from 'dva'
 import ChartView from './chartView'
 import EmptyContent from '../common/emptyContent/index'
+import DataPreview from '../common/dataPreview/dataPreview'
 
 class ViewLayout extends React.PureComponent {
     constructor(props) {
@@ -43,8 +44,8 @@ class ViewLayout extends React.PureComponent {
         const { dispatch, main, dashboardDesigner } = this.props;
         const { editingKey, richTextReadOnly } = this.state;
         const { currentUser } = main;
-        const { editMode } = dashboardDesigner;
-        const { code, name, viewType, layout, chartCode } = item;
+        const { editMode, minLayoutHeight } = dashboardDesigner;
+        const { code, name, viewType, layout, chartCode, chartOption } = item;
         const iconCls = editMode ? 'visible-icon' : '';
 
         return (
@@ -82,7 +83,9 @@ class ViewLayout extends React.PureComponent {
                     </div>
                     <div className='chart-tools'>
                         {viewType !== 'richText' && <Icon className={iconCls} type="reload" theme="outlined" onClick={() => {
-                            dispatch({ type: 'dashboardDesigner/fetchChartData', item, mandatory: true });
+                            let page = chartOption ? chartOption.page : 1;
+                            let pageSize = chartOption ? chartOption.pageSize : (~~((layout.h * minLayoutHeight + (layout.h - 1) * 12 - 20 - 40 - 24 - 8 * 2)/38) + 1)
+                            dispatch({ type: 'dashboardDesigner/fetchChartData', item, mandatory: true, page, pageSize });
                         }}/>}
                         {!isPreview && viewType !== 'richText' && <Icon className={iconCls} type="arrows-alt" onClick={() => this.showPreviewBox(item)}/>}
                         {editMode && !isPreview && (item.creatorCode === currentUser.code || currentUser.role === 'superAdmin') && viewType !== 'richText' &&  <Icon className={iconCls} type='edit' onClick={() => {
@@ -96,7 +99,7 @@ class ViewLayout extends React.PureComponent {
                         {isPreview && <Icon className={iconCls} type="close" onClick={this.hidePreviewBox}/>}
                     </div>
                 </div>
-                <ChartView chartRef={'chartRef-' + code} readOnly={richTextReadOnly} editMode={isPreview ? false : editMode} item={{...item}} reload={reload}/>
+                <ChartView chartRef={'chartRef-' + code} minLayoutHeight={minLayoutHeight} readOnly={richTextReadOnly} editMode={isPreview ? false : editMode} item={{...item}} reload={reload}/>
             </div>
         )
     }
@@ -111,21 +114,32 @@ class ViewLayout extends React.PureComponent {
     showPreviewBox = (item) => {
         this.setState({
             previewItem: item,
-            visiblePreviewBox: true
+            visiblePreviewBox: true,
+            lastViewCode: item.code,
+            lastPage: item.chartOption ? item.chartOption.page : 1,
+            lastPageSize: item.chartOption ? item.chartOption.pageSize : 0,
         });
     }
 
     hidePreviewBox = () => {
+        const { dashboardDesigner, dispatch } = this.props;
+        const { lastViewCode, lastPage, lastPageSize } = this.state;
+        let item = dashboardDesigner.items.find(x => x.code === lastViewCode)
         this.setState({
             previewItem: null,
             visiblePreviewBox: false
         });
+        if(item.chartOption) {
+            dispatch({ type: 'setItemFields', code: lastViewCode, fields: [
+                { name: 'chartOption', value: { ...item.chartOption, page: lastPage, pageSize: lastPageSize } }
+            ] });
+        }
     }
 
     render() {
-        const { dashboardDesigner, contentSize, lastContentSize } = this.props;
+        const { dashboardDesigner, contentSize, lastContentSize, dispatch } = this.props;
         const { editingKey } = this.state;
-        const { editMode } = dashboardDesigner;
+        const { editMode, minLayoutHeight } = dashboardDesigner;
         const { visiblePreviewBox, previewItem } = this.state;
         const children = dashboardDesigner.items.map((item) => this.createElement(item, false, !item.chartOption));
         return (<div className='dashboard-viewcontent'>
@@ -134,7 +148,7 @@ class ViewLayout extends React.PureComponent {
                 autoSize={true}
                 cols={12}
                 margin = {editMode ? [12, 12] : [12, 12]}
-                rowHeight = {40}
+                rowHeight = {minLayoutHeight}
                 isDraggable={editMode && !editingKey}
                 isResizable={editMode && !editingKey}
                 draggableHandle='.mover'
@@ -146,18 +160,14 @@ class ViewLayout extends React.PureComponent {
                     <EmptyContent />
                 </div> : children}
             </ReactGridLayout>
-            <Modal
-                className='previewbox'
-                width='80%'
-                height='80%'
-                visible={visiblePreviewBox}
-                onCancel={this.hidePreviewBox}
-                footer={null}
-                keyboard={true}
-                maskClosable={true}
-                >
-                {!!previewItem && this.createElement(dashboardDesigner.items.find(item => item.code === previewItem.code), true, false)}
-            </Modal>
+            {visiblePreviewBox && <DataPreview
+                title={previewItem.name}
+                visibleBox={visiblePreviewBox}
+                hideBox={this.hidePreviewBox}
+                fetchFunction={(page, pageSize) => {
+                    dispatch({ type: 'dashboardDesigner/fetchDataList', item: previewItem, mandatory: true, page, pageSize });
+                }}
+            />}
         </div>);
     }
 }

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

@@ -14,6 +14,8 @@
     }
   }
   .chartview {
+    display: flex;
+    flex-direction: column;
     padding-top: 40px;
     z-index: 1;
     border: 1px solid #CCCCCC;
@@ -41,6 +43,7 @@
         display: none;
         font-size: 20px;
         line-height: 40px;
+        white-space: nowrap;
         .anticon {
           display: none;
           margin-left: 10px;
@@ -52,6 +55,7 @@
       }
     }
     .chartview-content {
+      flex: 1;
       overflow: hidden;
       .dashboard-table { // 表格
         height: 100%;
@@ -135,6 +139,8 @@
       cursor: move;
     }
     .chartview-content {
+      height: calc(~'100% - 20px');
+      width: 100%;
       .richtexteditor {
         .w-e-text-container {
           pointer-events: all;
@@ -289,6 +295,8 @@
       height: 100%;
       padding-top: 0;
       .chartview  {
+        display: flex;
+        flex-direction: column;
         height: 100%;
         padding-top: 40px;
         .chartview-toolbar {
@@ -304,14 +312,18 @@
           .chart-tools {
             font-size: 20px;
             line-height: 2;
+            white-space: nowrap;
             .anticon {
               margin-left: 10px;
               cursor: pointer;
             }
           }
         }
-        .ant-table-body {
-          margin-top: 0;
+        .chartview-content {
+          flex:1;
+          .ant-table-body {
+            margin-top: 0;
+          }
         }
       }
     }

+ 1 - 0
src/components/dataSource/list.jsx

@@ -498,6 +498,7 @@ class DataSource extends React.Component {
                             }} 
                         />}
                         {visibleDataPreviewBox && <DataPreview
+                            title={selectedRecord.name}
                             visibleBox={visibleDataPreviewBox}
                             hideBox={() => {
                                 this.setState({

+ 2 - 2
src/models/chart.js

@@ -308,8 +308,8 @@ export default {
                     }
                     yield put({ type: 'chartDesigner/silentChangeFields', fields: fields });
                     yield put({ type: 'chartDesigner/silentChangeDataSource', dataSource: data.baseConfig.dataSource });
-                    console.log('chart-model-componentDidMount');
-                    yield put({ type: 'chartDesigner/fetchChartData' });
+                    let pageSize = ~~((document.getElementsByClassName('content-body')[0].offsetHeight - 12 * 2 - 40 - 24 - 8 * 2)/38) + 1;
+                    yield put({ type: 'chartDesigner/fetchChartData', page: 1, pageSize });
                 }else {
                     message.error('解析图表错误: ' + (res.err || res.data.msg));
                 }

+ 4 - 2
src/models/chartDesigner.js

@@ -51,7 +51,7 @@ export default {
             header: { label: '无标题' },
             columns: [],
             baseConfig: { dataSource: { code: '' }, viewType: '' },
-            aggregateTableConfig: { targetColumn: {}, statistics: [], groupBy: [] },
+            aggregateTableConfig: { targetColumn: {}, statistics: [], groupBy: [], direction: ['vertical', 'horizontal'][0] },
             dataViewConfig: { viewColumns: [], sortColumn: {key: ''}, sortType: 'asc' },
             barConfig: { xAxis: { column: {}, granularity: {} }, yAxis: { column: {}, gauge: {} }, groupBy: {key:''}, threshold: 20 },
             lineConfig: { xAxis: { column: {}, granularity: {} }, yAxis: { column: {}, gauge: {} }, groupBy: {key:''}, threshold: 1000 },
@@ -283,6 +283,7 @@ export default {
             const chartDesigner = yield select(state => state.present.chartDesigner);
             const { baseConfig } = chartDesigner;
             const { viewType } = baseConfig;
+            const { page, pageSize } = action;
 
             try{
                 yield put({ type: 'silentSetField', name: 'fetchConfig', value: {} });
@@ -317,7 +318,8 @@ export default {
                 }else if(viewType === 'dataView') {
                     const { dataViewConfig } = chartDesigner;
                     if(dataViewConfig.viewColumns.length > 0) {
-                        yield put({ type: 'fetchDataViewData' });
+                        yield put({ type: 'fetchDataViewData', page, pageSize });
+                        // yield put({ type: 'fetchDataViewData' }); // dataView不需要在这里触发数据请求,组件在计算完pageSize后会自动发起数据请求
                     }else {
                         yield put({ type: 'silentSetField', name: 'chartOption', value: {} });
                     }

+ 16 - 1
src/models/dashboard.js

@@ -258,7 +258,22 @@ export default {
                     id: code,
                     bdName: name,
                     bdNote: description,
-                    bdConfiguration: JSON.stringify(items.map(item => ({ ...item, chartOption: null }))),
+                    bdConfiguration: JSON.stringify(items.map(item => {
+                        if(item.chartType === 'dataView') {
+                            return {
+                                ...item,
+                                chartOption: {
+                                    total: 0,
+                                    page: 1,
+                                    pageSize: 0, // 保存为0以保证在tableView组件渲染时getTableLayout方法中总能触发取数
+                                    columns: [],
+                                    dataSource: []
+                                }
+                            }
+                        }else {
+                            return { ...item, chartOption: null }
+                        }
+                    })),
                     relationColumns: JSON.stringify(relationColumns),
                     filters: JSON.stringify(filters) || "",
                     chartIds: chartCodes.join(','),

+ 83 - 11
src/models/dashboardDesigner.js

@@ -89,6 +89,7 @@ export default {
         originData: {
             code: null,
             name: '无标题',
+            minLayoutHeight: 40,
             defaultLayout: { x: 0, y: 50, w: 12, h: 6, minW: 2, maxW: 12, minH: 1 },
             items: [],
             chartCodes: [], // 报表包含的所有图表
@@ -151,7 +152,7 @@ export default {
             return Object.assign({}, state, {filterLabel: label});
         },
         addChart(state, action) {
-            let { items, dataSources, chartCodes, defaultLayout } = state;
+            let { items, dataSources, chartCodes, defaultLayout, minLayoutHeight } = state;
             const { chart } = action;
 
             items = items.concat([{
@@ -168,6 +169,14 @@ export default {
                 chartType: chart.type,
                 filters: chart.filters,
                 layout: defaultLayout,
+                styleConfig: chart.styleConfig,
+                chartOption: chart.type === 'dataView' ? {
+                    total: 0,
+                    page: 1,
+                    pageSize: 0, // 设为0以保证在tableView组件渲染时getTableLayout方法中总能触发取数
+                    columns: [],
+                    dataSource: []
+                } : null
             }]);
             chartCodes.push(chart.code);
             dataSources.findIndex(d => d.code === chart.dataSourceCode+'') === -1 && dataSources.push({
@@ -223,7 +232,7 @@ export default {
         },
         changeLayout(state, action) {
             const { layout } = action;
-            let { items, dirty } = state;
+            let { items, dirty, minLayoutHeight } = state;
             const ly = ['x', 'y', 'w', 'h'];
             for(let i = 0; i < items.length; i++) {
                 if(layout[i]) { // 非删除引起
@@ -233,6 +242,12 @@ export default {
                             items[i].layout[ly[j]] = layout[i][ly[j]];
                         }
                     }
+                    if(items[i].chartOption) {
+                        items[i].chartOption = { ...items[i].chartOption,
+                            page: 1,
+                            pageSize: (~~((layout[i][ly[3]] * minLayoutHeight + (layout[i][ly[3]] - 1) * 12 - 20 - 40 - 24 - 8 * 2)/38) + 1)
+                        }
+                    }
                 }else { // 删除引起
                     dirty = true;
                 }
@@ -337,12 +352,14 @@ export default {
          */
         *refresh(action, { call, put, select }) {
             const dashboardDesigner = yield select(state => state.present.dashboardDesigner);
-            const { items } = dashboardDesigner;
+            const { items, minLayoutHeight } = dashboardDesigner;
 
             for(let i = 0; i < items.length; i++) {
                 let item = items[i];
                 if(item.viewType === 'chart') {
-                    yield put({ type:'fetchChartData', item: items[i], mandatory: true });
+                    let page = item.chartOption ? item.chartOption.page : 1;
+                    let pageSize = item.chartOption ? item.chartOption.pageSize : (~~((item.layout.h * minLayoutHeight + (item.layout.h - 1) * 12 - 20 - 40 - 24 - 8 * 2)/38) + 1)
+                    yield put({ type:'fetchChartData', item: items[i], mandatory: true, page, pageSize });
                 }
             }
         },
@@ -395,12 +412,14 @@ export default {
             try {
                 const { filters } = action;
                 const dashboardDesigner = yield select(state => state.present.dashboardDesigner);
-                const { items } = dashboardDesigner;
+                const { items, minLayoutHeight } = dashboardDesigner;
     
                 yield put({ type: 'silentSetField', name: 'filters', value: filters });
                 
                 for(let i = 0; i < items.length; i++) {
-                    yield put({ type:'fetchChartData', item: items[i], mandatory: true });
+                    let page = items[i].chartOption ? items[i].chartOption.page : 1;
+                    let pageSize = items[i].chartOption ? items[i].chartOption.pageSize : (~~((items[i].layout.h * minLayoutHeight + (items[i].layout.h - 1) * 12 - 20 - 40 - 24 - 8 * 2)/38) + 1)
+                    yield put({ type:'fetchChartData', item: items[i], mandatory: true, page, pageSize });
                 }
             }catch(e) {
                 message.error('更改过滤条件失败: ' + e.message);
@@ -412,7 +431,7 @@ export default {
         *changeFilter(action, { put, call, select }) {
             const { filter } = action;
             const dashboardDesigner = yield select(state => state.present.dashboardDesigner);
-            let { filters, items } = dashboardDesigner;
+            let { filters, items, minLayoutHeight } = dashboardDesigner;
             let targetDataSourceCodes = [];
 
             filters = filters.map(f => {
@@ -435,7 +454,9 @@ export default {
             yield put({ type: 'silentSetField', name: 'filters', value: filters });
 
             for(let i = 0; i < targetItems.length; i++) {
-                yield put({ type:'fetchChartData', item: targetItems[i], mandatory: true });
+                let page = targetItems[i].chartOption ? targetItems[i].chartOption.page : 1;
+                let pageSize = targetItems[i].chartOption ? targetItems[i].chartOption.pageSize : (~~((targetItems[i].layout.h * minLayoutHeight + (targetItems[i].layout.h - 1) * 12 - 20 - 40 - 24 - 8 * 2)/38) + 1)
+                yield put({ type:'fetchChartData', item: targetItems[i], mandatory: true, page, pageSize });
             }
         },
         *fetchChartData(action, { put, call, select }) {
@@ -456,7 +477,7 @@ export default {
                     filters: getBodyFilters(getTrueFilters(item, filters)),
                     testPage: {
                         pageNum: page|| 1,
-                        pageSize: pageSize || 25, 
+                        pageSize: pageSize || 99, 
                     }
                 };
                 const res = yield call(service.fetch, {
@@ -536,7 +557,7 @@ export default {
             const { relationColumns } = action;
             let targetDataSourceCodes = []; // 记录有改动的数据源code
             const dashboardDesigner = yield select(state => state.present.dashboardDesigner);
-            let { filters, items } = dashboardDesigner;
+            let { filters, items, minLayoutHeight } = dashboardDesigner;
             for(let i = filters.length - 1; i >= 0; i--) {
                 let f = filters[i];
                 let idx = relationColumns.findIndex(rc => rc.code === f.name);
@@ -583,7 +604,9 @@ export default {
             yield put({ type: 'silentSetField', name: 'filters', value: filters });
 
             for(let i = 0; i < targetItems.length; i++) {
-                yield put({ type:'fetchChartData', item: targetItems[i], mandatory: true });
+                let page = 1;
+                let pageSize = targetItems[i].chartOption ? targetItems[i].chartOption.pageSize : (~~((targetItems[i].layout.h * minLayoutHeight + (targetItems[i].layout.h - 1) * 12 - 20 - 40 - 24 - 8 * 2)/38) + 1)
+                yield put({ type:'fetchChartData', item: targetItems[i], mandatory: true, page, pageSize });
             }
         },
         *modifyItem(action, { select, call, put }) {
@@ -599,6 +622,55 @@ export default {
             }catch(e) {
                 message.error('修改失败: ' + e.message);
             }
+        },
+        /**
+         * dataView预览窗口取数
+         */
+        *fetchDataList(action, { select, call, put }) {
+            const { item, page, pageSize } = action;
+            const dashboardDesigner = yield select(state => state.present.dashboardDesigner);
+            const { creatorCode, filters } = dashboardDesigner;
+            const { chartCode } = item;
+            
+            try {
+                yield put({ type: 'dataList/setField', name: 'loading', value: true });
+                const body = {
+                    dashboardCreatorId: creatorCode,
+                    chartId: chartCode,
+                    filters: getBodyFilters(getTrueFilters(item, filters)),
+                    testPage: {
+                        pageNum: page || 1,
+                        pageSize: pageSize || 25, 
+                    }
+                };
+                const res = yield call(service.fetch, {
+                    url: URLS.CHART_OPTION,
+                    allow: true,
+                    body,
+                    timeout: 30000
+                });
+                if(!res.err && res.data.code > 0) {
+                    const { chartsColumnConfig, valueList } = res.data.data;
+                    const { list, pageSize, total } = valueList;
+                    const chartConfig = JSON.parse(chartsColumnConfig.chartConfig);
+                    const columns = chartConfig.viewColumns;
+
+                    yield put({ type: 'dataList/setFields', fields: [
+                        { name: 'columns', value: columns },
+                        { name: 'dataSource', value: list },
+                        { name: 'pageSize', value: pageSize },
+                        { name: 'total', value: total }
+                    ] });
+                }else {
+                    message.error('请求图表展示数据失败: ' + (res.err || res.data.msg));
+                    return false;
+                }
+            }catch(e) {
+                message.error('请求图表展示数据错误: ' + e.message);
+                return false;
+            }finally {
+                yield put({ type: 'dataList/setField', name: 'loading', value: false });
+            }
         }
     },
     subscriptions: {

+ 11 - 58
src/models/parseChartOption.js

@@ -298,65 +298,18 @@ function scatterOption(data, scatterConfig, themeConfig, styleConfig) {
 
 function aggregateTableOption( data, aggregateTableConfig, themeConfig, styleConfig) {
     const resData = data.valueList;
-    const { targetColumn, statistics } = aggregateTableConfig;
+    const { targetColumn, statistics: statisticsNames, direction } = aggregateTableConfig;
     
-    let stypes = STATISTICS_OPTION.filter(o => statistics.indexOf(o.value) !== -1);
-
-    let column = {
-        title: '分析目标',
-        dataIndex: 'targetColumn'
-    };
-    let targetColumnData = { targetColumn: targetColumn.label };
-    let columns = [column];
-    let dataSource = [targetColumnData]
-
-    if(aggregateTableConfig.groupBy && aggregateTableConfig.groupBy.length > 0) {
-        columns = columns.concat(aggregateTableConfig.groupBy.map(g => {
-            return {
-                title: g.label,
-                dataIndex: g.key
-            }
-        })).concat(stypes.map(st => {
-            return {
-                title: st.label,
-                dataIndex: st.value
-            }
-        }));
-        dataSource = resData.map(d => {
-            let obj = {};
-            stypes.map(st => obj[st.value] = d[st.value.toUpperCase()]);
-            aggregateTableConfig.groupBy.map(g => obj[g.key] = d[g.key])
-            return { ...targetColumnData, ...obj };
-        });
-    }else {
-        columns = columns.concat(stypes.map(st => {
-            dataSource = dataSource.map(d => {
-                d[st.value] = resData[st.value.toUpperCase()]
-                return d
-            });
-            return {
-                title: st.label,
-                dataIndex: st.value
-            }
-        }));
-    }
+    let statistics = STATISTICS_OPTION.filter(o => statisticsNames.indexOf(o.value) !== -1);
 
     let option = {
-        columns: columns.map(c => {
-            let _c = {
-                ...c, width: 200
-            };
-            if(_c.dataIndex === 'percent') {
-                return { ..._c, render: (value, record, index) => {
-                    return ((+value*100).toFixed(2)) + '%'}
-                };
-            }else {
-                return _c;
-            }
-        }),
-        dataSource: dataSource.map((d, i) => {
-            return { ...d, key: i}
-        })
+        targetColumn: targetColumn,
+        statistics: statistics.map(s => ({
+            name: s.value,
+            label: s.label,
+            value: resData[s.value]
+        })),
+        direction: direction || 'vertical'
     };
 
     return option;
@@ -376,7 +329,7 @@ function dataViewOption(data, dataViewConfig, themeConfig, styleConfig) {
             }
             if(c.type === 'time') {
                 obj.render = (v, r, i) => {
-                    let text = moment(v).isValid() ? moment(v).format('YYYY-MM-DD') : v
+                    let text = v === null ? '空' : moment(v).isValid() ? moment(v).format('YYYY-MM-DD') : v
                     return <EllipsisTooltip title={text}>{text}</EllipsisTooltip>
                 }
             }else {
@@ -390,7 +343,7 @@ function dataViewOption(data, dataViewConfig, themeConfig, styleConfig) {
         dataSource: dataSource.map((d, i) => {
             return { ...d, key: i}
         }),
-        pageNum,
+        page: pageNum,
         pageSize,
         pages,
         total,