zhuth 7 жил өмнө
commit
49fcb7777d
37 өөрчлөгдсөн 1675 нэмэгдсэн , 0 устгасан
  1. 3 0
      .gitignore
  2. 25 0
      app/component/chartDesigner/charts/echartsView.jsx
  3. 39 0
      app/component/chartDesigner/charts/table.jsx
  4. 5 0
      app/component/chartDesigner/content.css
  5. 98 0
      app/component/chartDesigner/content.jsx
  6. 5 0
      app/component/chartDesigner/content.less
  7. 24 0
      app/component/chartDesigner/header.css
  8. 62 0
      app/component/chartDesigner/header.jsx
  9. 24 0
      app/component/chartDesigner/header.less
  10. 27 0
      app/component/chartDesigner/layout.css
  11. 47 0
      app/component/chartDesigner/layout.jsx
  12. 26 0
      app/component/chartDesigner/layout.less
  13. 88 0
      app/component/chartDesigner/sections/aggregateTableConfigForm.jsx
  14. 141 0
      app/component/chartDesigner/sections/baseConfigForm.jsx
  15. 98 0
      app/component/chartDesigner/sections/dataViewConfigForm.jsx
  16. 15 0
      app/component/chartDesigner/sections/filterBox.css
  17. 181 0
      app/component/chartDesigner/sections/filterBox.jsx
  18. 15 0
      app/component/chartDesigner/sections/filterBox.less
  19. 76 0
      app/component/chartDesigner/sections/preparingForm.jsx
  20. 83 0
      app/data/charts/option/data1.json
  21. 2 0
      app/eventManger/ev.js
  22. 4 0
      app/resources/iconfont/iconfont.css
  23. BIN
      app/resources/iconfont/iconfont.eot
  24. 182 0
      app/resources/iconfont/iconfont.svg
  25. BIN
      app/resources/iconfont/iconfont.ttf
  26. BIN
      app/resources/iconfont/iconfont.woff
  27. BIN
      app/resources/images/6165847895E8568AE73E6164F3668271B78151E6C.jpg
  28. BIN
      app/resources/images/favicon.ico
  29. 112 0
      app/utils/baseUtils.js
  30. 0 0
      custom.css
  31. 1 0
      custom.less
  32. 16 0
      index.html
  33. 22 0
      main.css
  34. 164 0
      main.js
  35. 27 0
      main.less
  36. 49 0
      package.json
  37. 14 0
      webpack.config.js

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+dist
+node_modules
+.DS_Store

+ 25 - 0
app/component/chartDesigner/charts/echartsView.jsx

@@ -0,0 +1,25 @@
+import React from 'react';
+import Echarts from 'echarts-for-react';
+
+class EchartsView extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            option: props.option
+        }
+    }
+
+    componentDidMount() {
+    }
+
+    render() {
+        return (
+            <Echarts ref={(e) => { this.echarts_react = e; }}
+                option={this.state.option}
+                className='rc-echarts'
+             />
+        )
+    }
+}
+
+export default EchartsView;

+ 39 - 0
app/component/chartDesigner/charts/table.jsx

@@ -0,0 +1,39 @@
+import React from 'react';
+import { Table } from 'antd';
+
+class TableView extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+
+        }
+    }
+
+    render() {
+        const columns = [{
+            title: '列1',
+            key: 'c1',
+            dataIndex: 'c1'
+        }, {
+            title: '列2',
+            key: 'c2',
+            dataIndex: 'c2'
+        }];
+
+        const data = [{
+            key: '1',
+            c1: 'sssss',
+            c2: 'aaaaa'
+        }, {
+            key: '2',
+            c1: '啊啊啊啊',
+            c2: 'aaaadddbb'
+        }]
+
+        return (
+            <Table columns={columns} dataSource={data}/>
+        );
+    }
+}
+
+export default TableView;

+ 5 - 0
app/component/chartDesigner/content.css

@@ -0,0 +1,5 @@
+.ant-tabs-nav .ant-tabs-tab {
+  height: 37px;
+  margin: 0;
+  padding: 7px 16px;
+}

+ 98 - 0
app/component/chartDesigner/content.jsx

@@ -0,0 +1,98 @@
+import React from 'react';
+import { Layout,  Collapse, Form, Select, Input, Tabs } from 'antd';
+const { Sider, Content } = Layout;
+const CollapsePanel = Collapse.Panel;
+const { TabPane } = Tabs;
+const { FormItem } = Form;
+const { Option } = Select;
+import emitter from '../../eventManger/ev';
+import BaseConfigForm from './sections/baseConfigForm';
+import PreparingForm from './sections/preparingForm';
+import AggregateTableConfigForm from './sections/aggregateTableConfigForm';
+import DataViewConfigForm from './sections/dataViewConfigForm';
+import TableView from './charts/table';
+import EchartsView from './charts/echartsView';
+import Filter from './sections/filterBox';
+import './content.less';
+
+class ChartDesignerContent extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            viewType: {
+                name: 'aggregateTable',
+                label: '总体统计数据表',
+            },
+            baseConfig: props.baseConfig,
+            preparingConfig: props.preparingConfig,
+            chartOption: props.chartOption,
+            history: {}
+        }
+    }
+
+    componentDidMount() {
+        // 在组件装载完成后发布事件
+        this.eventEmitter = emitter.addListener('changeViewType', (viewType)=>{
+            this.setState({
+                viewType
+            });
+        });
+    }
+
+    componentWillUnmount() {
+        emitter.removeListener(this.eventEmitter);
+    }
+
+    render() {
+        let { viewType, baseConfig, preparingConfig, chartOption } = this.state;
+
+        let configForm, chartView;
+
+        if(viewType.name == 'aggregateTable') {
+            configForm = (<AggregateTableConfigForm />)
+            chartView = (<TableView />)
+        }else if(viewType.name == 'dataView') {
+            configForm = (<DataViewConfigForm />)
+            chartView = (<TableView />)
+        }else if(['bar', 'line', 'pie', 'scatter'].indexOf(viewType.name) != -1) {
+            configForm = (<DataViewConfigForm />)
+            chartView = (<EchartsView option={chartOption}/>)
+        }
+
+        return (
+            <Layout>
+                <Sider width={270} style={{ overflow: 'auto' }}>
+                    <Collapse defaultActiveKey={['0', '1']}>
+                        <CollapsePanel header='基础设置'>
+                            <BaseConfigForm config={baseConfig}/>
+                        </CollapsePanel>
+                        <CollapsePanel header='数据预处理'>
+                            <PreparingForm config={preparingConfig}/>
+                        </CollapsePanel>
+                    </Collapse>
+                </Sider>
+                <Content>
+                    <Tabs defaultActiveKey="1">
+                        <TabPane tab="图表预览" key="1">
+                            { chartView }
+                        </TabPane>
+                        <TabPane tab="数据列" key="2">Content of Tab Pane 2</TabPane>
+                        <TabPane tab="Echarts配置档" key="3">Content of Tab Pane 3</TabPane>
+                    </Tabs>
+                </Content>
+                <Sider width={250}>
+                    <Collapse defaultActiveKey={['0', '1']}>
+                        <CollapsePanel header={`${viewType.label}选项`}>
+                            { configForm }
+                        </CollapsePanel>
+                        <CollapsePanel header='筛选'>
+                            <Filter />
+                        </CollapsePanel>
+                    </Collapse>
+                </Sider>
+            </Layout>
+        )
+    }
+}
+
+export default ChartDesignerContent;

+ 5 - 0
app/component/chartDesigner/content.less

@@ -0,0 +1,5 @@
+.ant-tabs-nav .ant-tabs-tab {
+    height: 37px;
+    margin: 0;
+    padding: 7px 16px;
+}

+ 24 - 0
app/component/chartDesigner/header.css

@@ -0,0 +1,24 @@
+.toolbar-title {
+  float: left;
+}
+.toolbar-title .ant-input-group-wrapper {
+  display: inline-block;
+  vertical-align: middle;
+  width: 100%;
+  margin-bottom: 4px;
+}
+.toolbar-title .ant-input-group-wrapper .input-title {
+  float: left;
+}
+.toolbar-buttons {
+  float: right;
+}
+.ant-layout-header {
+  background: none;
+  padding: 0 10px;
+  height: 40px !important;
+  line-height: 40px !important;
+  border-width: 1px 0;
+  border-style: solid;
+  border-color: #CCCCCC;
+}

+ 62 - 0
app/component/chartDesigner/header.jsx

@@ -0,0 +1,62 @@
+import React from 'react';
+import { Input, Select, Icon, Button } from 'antd';
+import emitter from '../../eventManger/ev';
+const Option = Select.Option;
+import './header.less';
+
+class Header extends React.Component {
+    constructor(props) {
+        super(props);
+		this.state = {
+			title: props.chartTitle || '未命名'
+		};
+    }
+
+    componentDidMount() {
+        // 在组件装载完成后发布事件
+        this.eventEmitter = emitter.addListener('headersettitle', (title)=>{
+            this.setState({
+                title
+            });
+        });
+        console.log('chartDesigner header\'s emitter has been added...');
+    }
+
+    componentWillUnmount() {
+        emitter.removeListener(this.eventEmitter);
+        console.log('chartDesigner header\'s emitter has been removed...');
+    }
+
+    emit(eventName, params) {
+        emitter.emit(eventName);
+    }
+
+    titleChange() {
+        
+    }
+
+    render() {
+        const { emptyTitle, title } = this.state;
+
+        return <div className='header'>
+            <div className='header-item toolbar-title'>
+                <Input className='input-title' 
+                    width={200} 
+                    addonAfter={<Icon type="setting" />} 
+                    value={title}
+                    onChange={this.titleChange}
+                />
+            </div>
+            <div className='header-item toolbar-buttons'>
+                <div className=''>
+                    <Button className='button-uodo' icon='undo' onClick={this.emit('undo')}>撤销</Button>
+                    <Button className='button-redo' onClick={this.emit('redo')}>重做</Button>
+                    <Button className='button-uodo' onClick={this.emit('Preview')}>预览</Button>
+                    <Button className='button-uodo' onClick={this.emit('save')}>保存</Button>
+                </div>
+            </div>
+        </div>
+    }
+}
+
+export default Header;

+ 24 - 0
app/component/chartDesigner/header.less

@@ -0,0 +1,24 @@
+.toolbar-title {
+    float: left;
+    .ant-input-group-wrapper {
+        display: inline-block;
+        vertical-align: middle;
+        width: 100%;
+        margin-bottom: 4px;
+        .input-title {
+            float: left;
+        }
+    }
+}
+.toolbar-buttons {
+    float: right;
+}
+.ant-layout-header {
+    background: none;
+    padding: 0 10px;
+    height: 40px !important;
+    line-height: 40px !important;
+    border-width: 1px 0;
+    border-style: solid;
+    border-color: #CCCCCC;
+}

+ 27 - 0
app/component/chartDesigner/layout.css

@@ -0,0 +1,27 @@
+html,
+body,
+#root {
+  width: 100%;
+  height: 100%;
+}
+.ant-layout {
+  height: 100%;
+}
+.ant-layout-header {
+  background: none !important;
+}
+.ant-layout-content {
+  flex: 1 !important;
+}
+.ant-layout-sider {
+  background: none !important;
+  border-width: 0 1px 0 1px;
+  border-style: solid;
+  border-color: #CCCCCC;
+}
+.ant-tabs {
+  height: 100%;
+}
+.ant-tabs-bar {
+  margin: 0;
+}

+ 47 - 0
app/component/chartDesigner/layout.jsx

@@ -0,0 +1,47 @@
+import React from 'react';
+import { Layout, Menu, Breadcrumb, Icon, Tabs, Collapse } from 'antd';
+const { Header, Content, Sider } = Layout;
+const CollapsePanel = Collapse.Panel;
+const { TabPane } = Tabs;
+const SubMenu = Menu.SubMenu;
+import './layout.less';
+import ChartDesignerHeader from './header';
+import ChartDesignerContent from './content';
+import chartOption from '../../data/charts/option/data1.json';
+
+class ChartDesignerLayout extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+
+        }
+    }
+
+    render() {
+        let viewType = 'dataView';
+        let note = '测试描述...';
+        let currentDataSource = 'd1';
+        let accessPermission = 'anyone';
+        let editPermission = 'owner';
+        let showLegend = true;
+        let showTooltip = true;
+        let datazoom = true;
+        let toolbox = true;
+
+        let baseConfig = { viewType, note, currentDataSource, accessPermission, editPermission, showLegend,
+            showTooltip, datazoom, toolbox
+        };
+        let preparingConfig = { groupBy: ['c3'] };
+
+        return <Layout>
+            <Header>
+                <ChartDesignerHeader />
+            </Header>
+            <Content>
+                <ChartDesignerContent baseConfig={baseConfig} preparingConfig={preparingConfig} chartOption={chartOption}/>
+            </Content>
+        </Layout>
+    }
+}
+
+export default ChartDesignerLayout;

+ 26 - 0
app/component/chartDesigner/layout.less

@@ -0,0 +1,26 @@
+html,body,#root{
+    width: 100%;
+    height: 100%;
+}
+.ant-layout {
+    height: 100%;
+}
+.ant-layout-header {
+    background: none !important;
+}
+.ant-layout-content {
+    flex: 1 !important;
+}
+.ant-layout-sider {
+    background: none !important;
+    border-width: 0 1px 0 1px;
+    border-style: solid;
+    border-color: #CCCCCC;
+}
+.ant-tabs {
+    height: 100%;
+}
+.ant-tabs-bar {
+    margin: 0;
+}
+  

+ 88 - 0
app/component/chartDesigner/sections/aggregateTableConfigForm.jsx

@@ -0,0 +1,88 @@
+import React from 'react';
+import { Form, Icon, Input, Button, Select, Switch, Checkbox } from 'antd';
+const FormItem = Form.Item;
+const { Option } = Select;
+const CheckboxGroup = Checkbox.Group;
+
+class AggregateTableConfigForm extends React.Component {
+
+	constructor(props) {
+		super(props);
+		this.state = {
+			config: props.config
+		}
+	}
+
+	componentDidMount() {
+		// To disabled submit button at the beginning.
+		this.props.form.validateFields();
+    }
+    
+    onChange(checkedList) {
+        console.log(checkedList);
+    }
+
+	render() {
+		const { config } = this.state;
+        
+        const columns = [
+			{ value: 'c1', label: '列1' },
+			{ value: 'c2', label: '列2' },
+			{ value: 'c3', label: '列3' },
+			{ value: 'c4', label: '列4' },
+			{ value: 'c5', label: '列5' },
+        ];
+
+        let targetColumn = ['c3'];
+        
+        const statisticsOptions = [
+            { value: 'count', label: '条数', checked: true }, 
+            { value: 'max', label: '最大值', checked: true },
+            { value: 'percentage', label: '百分比', checked: true },
+            { value: '75th', label: '75th值', checked: true },
+            { value: 'sum', label: '总和', checked: true },
+            { value: 'median', label: '中位数', checked: false },
+            { value: 'avg', label: '平均数', checked: true },
+            { value: '25th', label: '25th值', checked: true },
+            { value: 'std', label: '标准差', checked: true },
+            { value: 'min', label: '最小值', checked: true }
+        ];
+
+        let filterFunc = function(option) {
+            return option.checked == true;
+        }
+		
+		const { getFieldDecorator, getFieldsError, getFieldError, isFieldTouched } = this.props.form;
+
+		const formItemLayout = {
+			labelCol: { span: 10 },
+			wrapperCol: { span: 14 },
+        };
+        
+		return (
+			<Form layout='horizontal'>
+				<FormItem label='分析目标' {...formItemLayout}>
+					{getFieldDecorator('targetColumn', {
+						rules: [{ required: true, message: '分析目标不能为空' }],
+						initialValue : targetColumn
+					})(
+						<Select mode='multiple'>
+							{columns.map((c, i)=>{
+								return (<Option key={`c-${i}`} value={c.value}>{c.label}</Option>)
+							})}
+						</Select>
+					)}
+				</FormItem>
+				<FormItem label='显示总体数据' {...formItemLayout}>
+					{getFieldDecorator('statistics', {
+						initialValue: statisticsOptions.filter(filterFunc).map((s)=>{return s.label})
+					})(
+						<CheckboxGroup options={statisticsOptions.map((s)=>{return s.label})} onChange={this.onChange} />
+					)}
+				</FormItem>
+			</Form>
+		);
+	}
+}
+
+export default Form.create()(AggregateTableConfigForm);

+ 141 - 0
app/component/chartDesigner/sections/baseConfigForm.jsx

@@ -0,0 +1,141 @@
+import React from 'react';
+import { Form, Icon, Input, Button, Select, Switch } from 'antd';
+const FormItem = Form.Item;
+const { Option } = Select;
+import emitter from '../../../eventManger/ev'
+
+function hasErrors(fieldsError) {
+	return Object.keys(fieldsError).some(field => fieldsError[field]);
+}
+
+class baseConfigForm extends React.Component {
+
+	constructor(props) {
+		super(props);
+		this.state = {
+			config: props.config
+		}
+	}
+
+	componentDidMount() {
+		// To disabled submit button at the beginning.
+		this.props.form.validateFields();
+	}
+
+	changeViewType(value, ele) {
+		let viewType = {
+			name: value,
+			label: ele.props.children
+		}
+        emitter.emit('changeViewType', viewType);
+    }
+
+	render() {
+		const { config } = this.state;
+		const { viewType, note, currentDataSource, accessPermission, editPermission, showLegend, showTooltip, datazoom, toolbox } = config;
+		
+		const allDataSource = [
+			{ id: 'd1', name: '数据源1' },
+			{ id: 'd2', name: '数据源2' },
+			{ id: 'd3', name: '数据源3' },
+			{ id: 'd4', name: '数据源4' },
+			{ id: 'd5', name: '数据源5' },
+			{ id: 'd6', name: '数据源6' },
+		];
+
+		const allPermission = [
+			{ value: 'owner', name: '创建人' },
+			{ value: 'anyone', name: '所有人' }
+		];
+		
+		const { getFieldDecorator, getFieldsError, getFieldError, isFieldTouched } = this.props.form;
+
+		const formItemLayout = {
+			labelCol: { span: 9 },
+			wrapperCol: { span: 15 },
+		};
+
+		return (
+			<Form hideRequiredMark={true}>
+				<FormItem label='可视化模式' {...formItemLayout}>
+					{getFieldDecorator('viewType', {
+						rules: [{ required: true, message: '可视化模式不能为空' }],
+						initialValue : viewType
+					})(
+						<Select onChange={this.changeViewType}>
+							<Option value='aggregateTable'>总体统计数据表</Option>
+							<Option value='dataView'>个体统计数据表</Option>
+							<Option value='line'>折线图</Option>
+							<Option value='bar'>柱状图</Option>
+							<Option value='pie'>饼状图</Option>
+							<Option value='scatter'>散点图</Option>
+						</Select>
+					)}
+				</FormItem>
+				<FormItem label='描述' {...formItemLayout}>
+					{getFieldDecorator('note', {
+						initialValue: note
+					})(
+						<Input placeholder="请输入" />
+					)}
+				</FormItem>
+				<FormItem label='数据源' {...formItemLayout}>
+					{getFieldDecorator('dataSource', {
+						rules: [{ required: true, message: '数据源不能为空' }],
+						initialValue: currentDataSource
+					})(
+						<Select>
+							{allDataSource.map((dataSource, i)=>{
+								return (<Option key={`dataSource-${i}`} value={dataSource.id}>{dataSource.name}</Option>)
+							})}
+						</Select>
+					)}
+				</FormItem>
+				<FormItem label='访问权限' {...formItemLayout}>
+					{getFieldDecorator('accessPermission', {
+						rules: [{ required: true, message: '访问权限不能为空' }],
+						initialValue: accessPermission
+					})(
+						<Select mode="multiple">
+							{allPermission.map((accessPermission, i)=>{
+								return (<Option key={`accessPermission-${i}`} value={accessPermission.value}>{accessPermission.name}</Option>)
+							})}
+						</Select>
+					)}
+				</FormItem>
+				<FormItem label='修改权限' {...formItemLayout}>
+					{getFieldDecorator('editPermission', {
+						rules: [{ required: true, message: '修改权限不能为空' }],
+						initialValue: editPermission
+					})(
+						<Select mode="multiple">
+							{allPermission.map((editPermission, i)=>{
+								return (<Option key={`editPermission-${i}`} value={editPermission.value}>{editPermission.name}</Option>)
+							})}
+						</Select>
+					)}
+				</FormItem>
+				<FormItem label='图例' {...formItemLayout}>
+					<Switch checkedChildren="开" unCheckedChildren="关" defaultChecked={showLegend} />
+				</FormItem>
+				<FormItem label='提示框' {...formItemLayout}>
+					{getFieldDecorator('tooltip')(
+						<Switch checkedChildren="开" unCheckedChildren="关" defaultChecked={showTooltip} />
+					)}
+				</FormItem>
+				<FormItem label='缩放' {...formItemLayout}>
+					{getFieldDecorator('datazoom')(
+						<Switch checkedChildren="开" unCheckedChildren="关" defaultChecked={datazoom} />
+					)}
+				</FormItem>
+				<FormItem label='工具箱' {...formItemLayout}>
+					{getFieldDecorator('toolbox')(
+						<Switch checkedChildren="开" unCheckedChildren="关" defaultChecked={toolbox} />
+					)}
+				</FormItem>
+			</Form>
+		);
+	}
+}
+
+export default Form.create()(baseConfigForm);

+ 98 - 0
app/component/chartDesigner/sections/dataViewConfigForm.jsx

@@ -0,0 +1,98 @@
+import React from 'react';
+import { Form, Icon, Input, Button, Select, Switch, Checkbox, InputNumber  } from 'antd';
+const FormItem = Form.Item;
+const { Option } = Select;
+const CheckboxGroup = Checkbox.Group;
+
+class DataViewConfigForm extends React.Component {
+
+	constructor(props) {
+		super(props);
+		this.state = {
+			config: props.config
+		}
+	}
+
+	componentDidMount() {
+		// To disabled submit button at the beginning.
+		this.props.form.validateFields();
+    }
+    
+    onChange(checkedList) {
+        console.log(checkedList);
+    }
+
+	render() {
+        const columns = [
+			{ value: 'c1', label: '列1' },
+			{ value: 'c2', label: '列2' },
+			{ value: 'c3', label: '列3' },
+			{ value: 'c4', label: '列4' },
+			{ value: 'c5', label: '列5' },
+        ];
+
+        let targetColumn = ['c3'];
+
+        let rows = 3;
+
+        let sort = 'asc';
+
+        const gaugeOptions = [
+            { value: 'z-score', label: '偏差值', checked: true }
+        ];
+
+        let filterFunc = function(option) {
+            return option.checked == true;
+        }
+		
+		const { getFieldDecorator, getFieldsError, getFieldError, isFieldTouched } = this.props.form;
+
+		const formItemLayout = {
+			labelCol: { span: 10 },
+			wrapperCol: { span: 14 },
+        };
+        
+		return (
+			<Form layout='horizontal'>
+				<FormItem label='分析目标' {...formItemLayout}>
+					{getFieldDecorator('targetColumn', {
+						rules: [{ required: true, message: '分析目标不能为空' }],
+						initialValue : targetColumn
+					})(
+						<Select mode='multiple'>
+							{columns.map((c, i)=>{
+								return (<Option key={`c-${i}`} value={c.value}>{c.label}</Option>)
+							})}
+						</Select>
+					)}
+				</FormItem>
+				<FormItem label='显示行数' {...formItemLayout}>
+					{getFieldDecorator('rows', {
+						initialValue: rows
+					})(
+					    <InputNumber min={1}/>
+					)}
+				</FormItem>
+                <FormItem label='显示顺序' {...formItemLayout}>
+					{getFieldDecorator('sort', {
+						initialValue : sort
+					})(
+						<Select>
+                            <Option key={`s-1`} value='asc'>正序</Option>
+                            <Option key={`s-2`} value='desc'>倒序</Option>
+						</Select>
+					)}
+				</FormItem>
+                <FormItem label='显示个体数据' {...formItemLayout}>
+					{getFieldDecorator('gauge', {
+						initialValue: gaugeOptions.filter(filterFunc).map((s)=>{return s.label})
+					})(
+						<CheckboxGroup options={gaugeOptions.map((s)=>{return s.label})} onChange={this.onChange} />
+					)}
+				</FormItem>
+			</Form>
+		);
+	}
+}
+
+export default Form.create()(DataViewConfigForm);

+ 15 - 0
app/component/chartDesigner/sections/filterBox.css

@@ -0,0 +1,15 @@
+.dynamic-delete-button {
+  cursor: pointer;
+  position: relative;
+  top: 10px;
+  font-size: 18px;
+  color: #999;
+  transition: all .3s;
+}
+.dynamic-delete-button:hover {
+  color: #777;
+}
+.dynamic-delete-button[disabled] {
+  cursor: not-allowed;
+  opacity: 0.5;
+}

+ 181 - 0
app/component/chartDesigner/sections/filterBox.jsx

@@ -0,0 +1,181 @@
+import React from 'react';
+import { Form, Row, Col, Input, Icon, Button, Select } from 'antd';
+const FormItem = Form.Item;
+const SelectOption = Select.Option;
+import emitter from '../../../eventManger/ev';
+import { isEqual } from '../../../utils/baseUtils.js';
+import './filterBox.less';
+
+let uuid = 0;
+class FilterBox extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            columns: [{
+                label: '列1',
+                name: 'c1',
+                type: 'string'
+            }, {
+                label: '列2',
+                name: 'c2',
+                type: 'number'
+            }, {
+                label: '列3',
+                name: 'c3',
+                type: 'time'
+            }, {
+                label: '列4',
+                name: 'c4',
+                type: 'categorical',
+                selection: ['s1', 's2', 's3']
+            }, {
+                label: '张三丰',
+                name: 'zsf',
+                type: 'categorical'
+            }],
+            filters: [{
+                key: 'c1-1',
+                name: 'c1',
+                operator: 'like',
+                value: '字符串'
+            }]
+        }
+    }
+    removeFilter(k) {
+        const { form } = this.props;
+        // can use data-binding to get
+        const keys = form.getFieldValue('keys');
+    
+        // can use data-binding to set
+        form.setFieldsValue({
+          keys: keys.filter(key => key !== k),
+        });
+    }
+
+    addFilter() {
+        const { form } = this.props;
+        // can use data-binding to get
+        const keys = form.getFieldValue('keys');
+        const nextKeys = keys.concat(uuid);
+        uuid++;
+        // can use data-binding to set
+        // important! notify form to detect changes
+        form.setFieldsValue({
+          keys: nextKeys,
+        });
+    }
+
+    changeFilterName(value) {
+        if(value == 'c1') {
+
+        }
+    }
+
+    componentDidMount() {
+    }
+
+    getFilterItems() {
+        const { columns } = this.state;
+        const { getFieldDecorator, getFieldValue } = this.props.form;
+        getFieldDecorator('keys', { initialValue: []});
+        const keys = getFieldValue('keys');
+        const filterItems = keys.map((k, index) => {
+            return (
+                <div key={`filterDiv[${k}]`}>
+                    <Row key={`filterRow[${k}]`}>
+                        <Col span={24}>
+                            <FormItem required={true} key={k}>
+                                {getFieldDecorator(`filterName[${k}]`, {
+                                    validateTrigger: ['onChange', 'onBlur'],
+                                    rules: [{
+                                        required: true,
+                                        whitespace: true,
+                                        message: "请选择过滤字段",
+                                    }],
+                                    initialValue: ''
+                                })(
+                                    <Select
+                                        showSearch={true}
+                                        filterOption={function(inputValue, option){
+                                            let { value, children } = option.props;
+                                            return value.toLowerCase().indexOf(inputValue.toLowerCase()) != -1 || 
+                                                children.toLowerCase().indexOf(inputValue.toLowerCase()) != -1;
+                                        }}
+                                        onChange={(value) => {this.changeFilterName(value)}}
+                                    >
+                                        {columns.map((c, i) => { return (<SelectOption key={i} value={c.name}>{c.label}</SelectOption>) })}
+                                    </Select>
+                                )}
+                            </FormItem>
+                        </Col>
+                    </Row>
+                    <Row>
+                        <Col span={10}>
+                            <FormItem required={true}>
+                                {getFieldDecorator(`filterOperator[${k}]`, {
+                                    validateTrigger: ['onChange', 'onBlur'],
+                                    rules: [{
+                                        required: true,
+                                        whitespace: true,
+                                        message: "请选择过滤条件",
+                                    }],
+                                    initialValue: ''
+                                })(
+                                    <Select
+                                        onFocus={function(){debugger;}}
+                                        onChange={(value) => { }}
+                                    >
+                                        { this.getFilterOperator }
+                                    </Select>
+                                )}
+                            </FormItem>
+                        </Col>
+                        <Col span={12}>
+                            <FormItem required={true}>
+                                {getFieldDecorator(`filterValue[${k}]`, {
+                                    validateTrigger: ['onChange', 'onBlur'],
+                                    rules: [{
+                                        required: true,
+                                        whitespace: true,
+                                        message: "请输入条件值",
+                                    }],
+                                    initialValue: ''
+                                })(
+                                    <Input />
+                                )}
+                            </FormItem>
+                        </Col>
+                        <Col span={2} className=''>
+                            <Icon
+                                className="dynamic-delete-button"
+                                type="close"
+                                onClick={() => { this.removeFilter(k) }}
+                            />
+                        </Col>
+                    </Row>
+                </div>
+            );
+        });
+
+        return filterItems;
+    }
+
+    render() {
+        return (
+            <Form>
+                { this.getFilterItems()}
+                <Row>
+                    <Col>
+                        <FormItem>
+                            <Button type="dashed" onClick={this.addFilter.bind(this)} style={{ width: '100%' }}>
+                                <Icon type="plus" /> 添加
+                            </Button>
+                        </FormItem>
+                    </Col>
+                </Row>
+            </Form>
+        );
+    }
+}
+
+export default Form.create()(FilterBox);

+ 15 - 0
app/component/chartDesigner/sections/filterBox.less

@@ -0,0 +1,15 @@
+.dynamic-delete-button {
+    cursor: pointer;
+    position: relative;
+    top: 10px;
+    font-size: 18px;
+    color: #999;
+    transition: all .3s;
+  }
+  .dynamic-delete-button:hover {
+    color: #777;
+  }
+  .dynamic-delete-button[disabled] {
+    cursor: not-allowed;
+    opacity: 0.5;
+  }

+ 76 - 0
app/component/chartDesigner/sections/preparingForm.jsx

@@ -0,0 +1,76 @@
+import React from 'react';
+import { Form, Icon, Input, Button, Select, Switch } from 'antd';
+const FormItem = Form.Item;
+const { Option } = Select;
+
+class PreparingForm extends React.Component {
+
+	constructor(props) {
+		super(props);
+		this.state = {
+			config: props.config
+		}
+	}
+
+	componentDidMount() {
+		// To disabled submit button at the beginning.
+		this.props.form.validateFields();
+	}
+
+	render() {
+		const { config } = this.state;
+		const { groupBy } = config;
+		
+		const { getFieldDecorator, getFieldsError, getFieldError, isFieldTouched } = this.props.form;
+
+		const formItemLayout = {
+			labelCol: { span: 10 },
+			wrapperCol: { span: 14 },
+		};
+
+		const columns = [
+			{ value: 'c1', label: '列1' },
+			{ value: 'c2', label: '列2' },
+			{ value: 'c3', label: '列3' },
+			{ value: 'c4', label: '列4' },
+			{ value: 'c5', label: '列5' },
+			{ value: 'c6', label: '列6' },
+			{ value: 'c7', label: '列7' },
+			{ value: 'c8', label: '列8' },
+			{ value: 'c9', label: '列9' },
+			{ value: 'c10', label: '列10' },
+			{ value: 'c11', label: '列11' },
+			{ value: 'c12', label: '列12' },
+			{ value: 'c13', label: '列13' }
+		];
+
+		return (
+			<Form layout='horizontal'>
+				<FormItem label='分组' {...formItemLayout}>
+					{getFieldDecorator('groupBy', {
+						initialValue : groupBy
+					})(
+						<Select mode="multiple">
+							{columns.map((c, i)=>{
+								return (<Option key={`column-${i}`} value={c.value}>{c.label}</Option>)
+							})}
+						</Select>
+					)}
+				</FormItem>
+				{/* <FormItem label='分段' {...formItemLayout}>
+					{getFieldDecorator('bucketization', {
+						initialValue : bucketization
+					})(
+						<Select mode="multiple">
+							{bucketizations.map((b, i)=>{
+								return (<Option key={`bucketization-${i}`} value={b.value}>{b.label}</Option>)
+							})}
+						</Select>
+					)}
+				</FormItem> */}
+			</Form>
+		);
+	}
+}
+
+export default Form.create()(PreparingForm);

+ 83 - 0
app/data/charts/option/data1.json

@@ -0,0 +1,83 @@
+{
+    "title": {
+        "text": "堆叠区域图"
+    },
+    "tooltip" : {
+        "trigger": "axis",
+        "axisPointer": {
+            "type": "cross",
+            "label": {
+                "backgroundColor": "#6a7985"
+            }
+        }
+    },
+    "legend": {
+        "data":["邮件营销","联盟广告","视频广告","直接访问","搜索引擎"]
+    },
+    "toolbox": {
+        "feature": {
+            "saveAsImage": {}
+        }
+    },
+    "grid": {
+        "left": "3%",
+        "right": "4%",
+        "bottom": "3%",
+        "containLabel": true
+    },
+    "xAxis" : [
+        {
+            "type" : "category",
+            "boundaryGap" : false,
+            "data" : ["周一","周二","周三","周四","周五","周六","周日"]
+        }
+    ],
+    "yAxis" : [
+        {
+            "type" : "value"
+        }
+    ],
+    "series" : [
+        {
+            "name":"邮件营销",
+            "type":"line",
+            "stack": "总量",
+            "areaStyle": {"normal": {}},
+            "data":[120, 132, 101, 134, 90, 230, 210]
+        },
+        {
+            "name":"联盟广告",
+            "type":"line",
+            "stack": "总量",
+            "areaStyle": {"normal": {}},
+            "data":[220, 182, 191, 234, 290, 330, 310]
+        },
+        {
+            "name":"视频广告",
+            "type":"line",
+            "stack": "总量",
+            "areaStyle": {"normal": {}},
+            "data":[150, 232, 201, 154, 190, 330, 410]
+        },
+        {
+            "name":"直接访问",
+            "type":"line",
+            "stack": "总量",
+            "areaStyle": {"normal": {}},
+            "data":[320, 332, 301, 334, 390, 330, 320]
+        },
+        {
+            "name":"搜索引擎",
+            "type":"line",
+            "stack": "总量",
+            "label": {
+                "normal": {
+                    "show": true,
+                    "position": "top"
+                }
+            },
+            "areaStyle": {"normal": {}},
+            "data":[820, 932, 901, 934, 1290, 1330, 1111]
+        }
+    ]
+}

+ 2 - 0
app/eventManger/ev.js

@@ -0,0 +1,2 @@
+import { EventEmitter } from 'events';
+export default new EventEmitter();

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 4 - 0
app/resources/iconfont/iconfont.css


BIN
app/resources/iconfont/iconfont.eot


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 182 - 0
app/resources/iconfont/iconfont.svg


BIN
app/resources/iconfont/iconfont.ttf


BIN
app/resources/iconfont/iconfont.woff


BIN
app/resources/images/6165847895E8568AE73E6164F3668271B78151E6C.jpg


BIN
app/resources/images/favicon.ico


+ 112 - 0
app/utils/baseUtils.js

@@ -0,0 +1,112 @@
+/**
+ * 删除数组某个值
+ * @param {*} arr 
+ * @param {*} val 
+ */
+function remove(arr, val) {
+    var index = arr.indexOf(val);
+    if (index > -1) {
+        arr.splice(index, 1);
+    }
+    return arr;
+}
+/**
+ * 判断两个对象是否相等
+ * @param {*} a 
+ * @param {*} b 
+ */
+function isEqual(a,b){
+    //如果a和b本来就全等
+    if(a===b){
+        //判断是否为0和-0
+        return a !== 0 || 1/a ===1/b;
+    }
+    //判断是否为null和undefined
+    if(a==null||b==null){
+        return a===b;
+    }
+    //接下来判断a和b的数据类型
+    var classNameA=Object.prototype.toString.call(a),
+    classNameB=Object.prototype.toString.call(b);
+    //如果数据类型不相等,则返回false
+    if(classNameA !== classNameB){
+        return false;
+    }
+    //如果数据类型相等,再根据不同数据类型分别判断
+    switch(classNameA){
+        case '[object RegExp]':
+        case '[object String]':
+        //进行字符串转换比较
+        return '' + a ==='' + b;
+        case '[object Number]':
+        //进行数字转换比较,判断是否为NaN
+        if(+a !== +a){
+            return +b !== +b;
+        }
+        //判断是否为0或-0
+        return +a === 0?1/ +a === 1/b : +a === +b;
+        case '[object Date]':
+        case '[object Boolean]':
+        return +a === +b;
+    }
+    //如果是对象类型
+    if(classNameA == '[object Object]'){
+        //获取a和b的属性长度
+        var propsA = Object.getOwnPropertyNames(a),
+        propsB = Object.getOwnPropertyNames(b);
+        if(propsA.length != propsB.length){
+            return false;
+        }
+        for(var i=0;i<propsA.length;i++){
+            var propName=propsA[i];
+            //如果对应属性对应值不相等,则返回false
+            //if(a[propName] !== b[propName]){
+            if(!isEqual(a[propName], b[propName])){
+                return false;
+            }
+        }
+        return true;
+    }
+    //如果是数组类型
+    if(classNameA == '[object Array]'){
+        let i = 0;
+        for(i; i < a.length;i++) {
+            if(!isEqual(a[i], b[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
+
+function isEmptyObject(model) {
+    var isEmpty = true;
+    for (var prop in model) {
+        isEmpty = false;
+        break;
+    }
+    return isEmpty;
+}
+
+function getUrlParam(name) {   
+    var reg = new RegExp("(^|&)"+name+"=([^&]*)(&|$)");   
+    var r = window.location.search.substr(1).match(reg);   
+    if (r != null) return decodeURI(r[2]); 
+    return null;   
+}
+
+function hashcode(obj) {
+    let str = JSON.stringify(obj);
+    let hash = 0,
+        i, chr, len;
+    if (str.length === 0) return hash;
+    for (i = 0, len = str.length; i < len; i++) {
+        chr = str.charCodeAt(i);
+        hash = ((hash << 5) - hash) + chr;
+        hash |= 0;
+    }
+    return hash;
+}
+
+
+export { remove, isEqual, isEmptyObject, getUrlParam, hashcode };

+ 0 - 0
custom.css


+ 1 - 0
custom.less

@@ -0,0 +1 @@
+@icon-url: "/app/resources/iconfont/iconfont"; // 把 iconfont 地址改到本地

+ 16 - 0
index.html

@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <title>Demo</title>
+  <link rel="stylesheet" href="index.css" />
+</head>
+<body>
+
+<div id="root"></div>
+
+<script src="common.js"></script>
+<script src="index.js"></script>
+
+</body>
+</html>

+ 22 - 0
main.css

@@ -0,0 +1,22 @@
+.ant-collapse {
+  border: none;
+  border-radius: 0;
+}
+.ant-collapse .ant-collapse-item .ant-collapse-header {
+  line-height: 14px;
+  padding: 10px 0 10px 40px;
+  color: rgba(0, 0, 0, 0.85);
+  cursor: pointer;
+  position: relative;
+  -webkit-transition: all .3s;
+  transition: all .3s;
+}
+.ant-collapse .ant-collapse-item .ant-collapse-header .arrow {
+  line-height: 35px;
+}
+.ant-collapse-content > .ant-collapse-content-box {
+  padding: 4px;
+}
+.ant-form-item {
+  margin-bottom: 0;
+}

+ 164 - 0
main.js

@@ -0,0 +1,164 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { LocaleProvider, DatePicker, message } from 'antd';
+// 由于 antd 组件的默认文案是英文,所以需要修改为中文
+import zhCN from 'antd/lib/locale-provider/zh_CN';
+import moment from 'moment';
+import 'moment/locale/zh-cn';
+import {
+    Router as HashRouter , // 或者是HashRouter、MemoryRouter
+    Route,   // 这是基本的路由块
+    Link,    // 这是a标签
+    Switch ,  // 这是监听空路由的
+    Redirect, // 这是重定向
+    Prompt   // 防止转换
+} from 'react-router-dom';
+
+import createHistory from 'history/createHashHistory';
+import App from './app/component/chartDesigner/layout';
+const history = createHistory();
+import './main.less';
+
+moment.locale('zh-cn');
+
+
+class DateT extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      date: '',
+    };
+  }
+  handleChange(date) {
+    message.info('您选择的日期是: ' + (date ? date.toString() : ''));
+    this.setState({ date });
+  }
+  render() {
+    let arr = [1,2,3,4,4,5,6,7];
+    console.log(arr.map(function(a,i){return a+1;}));
+    return (
+      <LocaleProvider locale={zhCN}>
+        <div style={{ width: 400, margin: '100px auto' }}>
+          <DatePicker onChange={value => this.handleChange(value)} />
+          <div style={{ marginTop: 20 }}>当前日期:{this.state.date && this.state.date.toString()}</div>
+        </div>
+      </LocaleProvider>
+    );
+  }
+}
+
+// A simple data API that will be used to get the data for our
+// components. On a real website, a more robust data fetching
+// solution would be more appropriate.
+const PlayerAPI = {
+	players: [
+		{ number: 1, name: "Ben Blocker", position: "G" },
+		{ number: 2, name: "Dave Defender", position: "D" },
+		{ number: 3, name: "Sam Sweeper", position: "D" },
+		{ number: 4, name: "Matt Midfielder", position: "M" },
+		{ number: 5, name: "William Winger", position: "M" },
+		{ number: 6, name: "Fillipe Forward", position: "F" }
+	],
+	all: function() { return this.players},
+	get: function(id) {
+		const isPlayer = p => p.number === id
+		return this.players.find(isPlayer)
+	}
+}
+  
+  // The FullRoster iterates over all of the players and creates
+  // a link to their profile page.
+  const FullRoster = () => (
+	<div>
+	  <ul>
+		{
+		  PlayerAPI.all().map(p => (
+			<li key={p.number}>
+			  <Link to={`/roster/${p.number}`}>{p.name}</Link>
+			</li>
+		  ))
+		}
+	  </ul>
+	</div>
+  )
+  
+  // The Player looks up the player using the number parsed from
+  // the URL's pathname. If no player is found with the given
+  // number, then a "player not found" message is displayed.
+  const Player = (props) => {
+	const player = PlayerAPI.get(
+	  parseInt(props.match.params.number, 10)
+	)
+	if (!player) {
+	  return <div>Sorry, but the player was not found</div>
+	}
+	return (
+	  <div>
+		<h1>{player.name} (#{player.number})</h1>
+		<h2>Position: {player.position}</h2>
+		<Link to='/roster'>Back</Link>
+	  </div>
+	)
+  }
+  
+  // The Roster component matches one of two different routes
+  // depending on the full pathname
+  const Roster = () => (
+	<Switch>
+	  <Route exact path='/roster' component={FullRoster}/>
+	  <Route path='/roster/:number' component={Player}/>
+	</Switch>
+  )
+  
+  const Schedule = () => (
+	<div>
+	  <ul>
+		<li>6/5 @ Evergreens</li>
+		<li>6/8 vs Kickers</li>
+		<li>6/14 @ United</li>
+	  </ul>
+	</div>
+  )
+  
+  const Home = () => (
+	<div>
+	  <h1>Welcome to the Tornadoes Website!</h1>
+	</div>
+  )
+  
+  const Main = () => (
+	<main>
+	  <Switch>
+		<Route exact path='/' component={Home}/>
+		<Route path='/roster' component={Roster}/>
+		<Route path='/schedule' component={Schedule}/>
+		<Route path='/datet' component={DateT}/>
+	  </Switch>
+	</main>
+  )
+  
+  const Header = () => (
+	<header>
+	  <nav>
+		<ul>
+		  <li><Link to='/'>Home</Link></li>
+		  <li><Link to='/roster'>Roster</Link></li>
+		  <li><Link to='/schedule1'>Schedule</Link></li>
+		  <li><Link to='/datet'>DateT</Link></li>
+		</ul>
+	  </nav>
+	</header>
+  )
+  
+//   const App = () => (
+// 	<div>
+// 	  <Header />
+// 	  <Main />
+// 	</div>
+//   )
+  
+ReactDOM.render((
+	<HashRouter history={history}>
+		<App />
+	</HashRouter>
+), document.getElementById('root'))

+ 27 - 0
main.less

@@ -0,0 +1,27 @@
+@import "~antd/dist/antd.less";
+@import "custom.less";
+
+.ant-collapse {
+    border: none;
+    border-radius: 0;
+    .ant-collapse-item {
+        .ant-collapse-header {
+            line-height: 14px;
+            padding: 10px 0 10px 40px;
+            color: rgba(0, 0, 0, 0.85);
+            cursor: pointer;
+            position: relative;
+            -webkit-transition: all .3s;
+            transition: all .3s;
+            .arrow {
+                line-height: 35px;
+            }
+        }
+    }
+}
+.ant-collapse-content > .ant-collapse-content-box {
+    padding: 4px;
+}
+.ant-form-item {
+    margin-bottom: 0;
+}

+ 49 - 0
package.json

@@ -0,0 +1,49 @@
+{
+  "private": true,
+  "entry": {
+    "index": "./main.js"
+  },
+  "dependencies": {
+    "antd": "^3.0.0",
+    "app": "^0.1.0",
+    "echarts": "^4.1.0",
+    "echarts-for-react": "^2.0.12-beta.0",
+    "events": "^3.0.0",
+    "moment": "^2.19.3",
+    "react": "^16.2.0",
+    "react-dom": "^16.2.0",
+    "react-router": "^4.3.1",
+    "react-router-dom": "^4.3.1"
+  },
+  "devDependencies": {
+    "atool-build": "^0.9.0",
+    "atool-test-mocha": "^0.1.4",
+    "babel-eslint": "^7.0.0",
+    "babel-plugin-import": "^1.0.1",
+    "babel-plugin-transform-runtime": "^6.8.0",
+    "babel-runtime": "^6.9.2",
+    "dora": "0.4.x",
+    "dora-plugin-webpack": "^0.8.1",
+    "eslint": "^3.8.1",
+    "eslint-config-airbnb": "^12.0.0",
+    "eslint-plugin-import": "^2.0.1",
+    "eslint-plugin-jsx-a11y": "^2.2.3",
+    "eslint-plugin-react": "^6.4.1",
+    "expect": "^1.20.1",
+    "extract-text-webpack-plugin": "^2.1.2",
+    "html-webpack-plugin": "^3.2.0",
+    "pre-commit": "1.x",
+    "redbox-react": "^1.2.6",
+    "webpack": "^3.0.0",
+    "webpack-dev-server": "^2.5.1"
+  },
+  "pre-commit": [
+    "lint"
+  ],
+  "scripts": {
+    "build": "atool-build",
+    "lint": "eslint --ext .js,.jsx src/",
+    "start": "dora --plugins webpack",
+    "test": "atool-test-mocha ./**/__tests__/*-test.js"
+  }
+}

+ 14 - 0
webpack.config.js

@@ -0,0 +1,14 @@
+// Learn more on how to config.
+// - https://github.com/ant-tool/atool-build#配置扩展
+
+var HtmlWebpackPlugin = require('html-webpack-plugin');
+
+module.exports = function(webpackConfig) {
+  webpackConfig.babel.plugins.push('transform-runtime');
+  webpackConfig.babel.plugins.push(['import', {
+    libraryName: 'antd',
+    style: 'css',
+  }]);
+
+  return webpackConfig;
+};

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно