xiaoct 7 лет назад
Родитель
Сommit
d78b0ee252

+ 17 - 0
app/components/dashboardDesigner/element.jsx

@@ -0,0 +1,17 @@
+import React from 'react'
+
+const Element = ({ type, content, itemKey }) => {
+    if (type == "simple") {
+        return ( <span>This is a simple element</span>
+        )
+    }else if (type == "chart") {
+        return ( <span>This is a chart element! </span>
+        )
+    }else {
+        return null
+    }
+
+}
+
+
+export default Element

+ 74 - 0
app/components/dashboardDesigner/elementConfig.jsx

@@ -0,0 +1,74 @@
+import React from 'react'
+import ReactDOM from 'react-dom'
+import dashboardDesigner from '../../models/dashboardDesigner'
+import { Modal, Select } from 'antd'
+import { connect } from 'dva'
+import BraftEditor from 'braft-editor'
+import 'braft-editor/dist/braft.css'
+const Option = Select.Option
+
+const ElementConfig = ({operation, visibleBox, hideElementConfigBox, dashboardDesigner, dispatch}) => {
+   
+    const okHandler = (model) => {
+        if(operation == 'create') {
+            
+        }else if(operation == 'modify') {
+            
+        }
+    }
+
+    const handleChange = (content) => {
+    console.log(content)
+    }
+
+    const handleRawChange = (rawContent) => {
+    console.log(rawContent)
+    }
+
+    
+    const editorProps = {
+        height: 300,
+        contentFormat: 'raw',
+        initialContent: '<p>Hello World!</p>',
+        onChange: handleChange(),
+        onRawChange: handleRawChange()
+    }
+
+        
+    return (
+        <Modal
+            className='element-config'
+            title={`${operation=='create'?'新增':'修改'}元素`}
+            visible={visibleBox}
+            onOk={() => {okHandler()}}
+            onCancel={hideElementConfigBox}
+            maskClosable={false}
+            destroyOnClose={true}
+            width="925px"
+        >
+            <div>
+                <Select defaultValue={dashboardDesigner.configBoxForm.type} style={{ width: 120 }} 
+                    onChange={(value) => { dispatch( {type: 'dashboardDesigner/handleFieldChange', name: 'type', value: value})}}>
+                    <Option value="chart">图表</Option>
+                    <Option value="simple">基础元素</Option>
+                </Select>
+            </div>
+            <div style={{display: `${dashboardDesigner.configBoxForm.type == "simple" ? "inline":"none"}`}}>
+                <BraftEditor {...editorProps}/>
+            </div>
+        </Modal>
+    )
+}
+
+
+
+    
+
+
+
+
+function mapStateToProps({ present: { dashboardDesigner } }) {
+    return { dashboardDesigner: dashboardDesigner };
+}
+
+export default connect(mapStateToProps)(ElementConfig)

+ 161 - 24
app/components/dashboardDesigner/layout.jsx

@@ -1,48 +1,185 @@
 import React from 'react'
 import { connect } from 'dva'
-import { Responsive, WidthProvider } from 'react-grid-layout'
+import ReactGridLayout from 'react-grid-layout'
 import '../../models/dashboard'
 import '../../../node_modules/react-grid-layout/css/styles.css'
 import '../../../node_modules/react-resizable/css/styles.css'
-
-const ResponsiveReactGridLayout = WidthProvider(Responsive);
-
+import dashboardDesigner from '../../models/dashboardDesigner'
+import { Icon, Menu, Layout, Switch, Row, Col, Button, Dropdown } from 'antd';
+import ElementConfig from './elementConfig'
+import './layout.less'
+import Element from './element'
+import dataConnect from '../../models/dataConnect';
+const { Header, Content } = Layout
 
 class DashboardDesigner extends React.Component {
 
     constructor(props) {
         super(props);
+        var layout = this.loadLayout()
         this.state = {
+            editMode: true,
+            operation: 'create',
+            visibleBox: false,
+            layout: layout,
+        };
+    }
+
+
+    showElementConfigBox = (o) => {
+        this.setState({
+            operation: o,
+            visibleBox: true
+        });
+    }
+
+    hideElementConfigBox = (o) => {
+        this.setState({
+            visibleBox: false
+        });
+    }
+
+    enableEditMode = (o) => {
+        this.setState({
+            editMode:true
+        })
+    }
+
+    disableEditMode = (o) => {
+        this.setState({
+            editMode:false
+        })
+    }
+
+    generateElementConfigMenu = (key) => {
+        const {dashboardDesigner, dispatch} = this.props;
+        return (
+            <Menu>
+                <Menu.Item
+                    onClick={()=> {
+                        let selectedElement = dashboardDesigner.elementList.find((i) => {return i.key == key});
+                        dispatch({ type: 'dashboardDesigner/resetConfigBoxForm'});
+                        dispatch({ type: 'dashboardDesigner/loadConfigBoxForm', element: selectedElement});
+                        this.showElementConfigBox('modify')
+                }}>
+                    <span>属性设置</span>
+                </Menu.Item>
+            </Menu>
+        
+        
+        )
+    }
+    generateElement() {    
+        const elementList = this.props.dashboardDesigner.elementList;
+        const editMode = this.state.editMode
+        let IconStyle={
+            display: editMode? 'inline-block':'none', 
+            position: 'absolute',
+            right: '3px',
+            top: '3px'
+        };
+
+        
+        return elementList.map ((item) => {
+            if (item.type == 'chart') {
+                return (
+                    <div 
+                        key={item.key.toString()}
+                        className={`grid-item${this.state.editMode ?' grid-item-edit': ''}`} 
+                    >
+                        <Element type="chart" content={item.content} itemKey={item.key}/>  
+                        <Dropdown overlay={this.generateElementConfigMenu(item.key)} trigger={['click']}>
+                            <Icon type="setting" style={IconStyle} />
+                        </Dropdown>
+                    </div>
+                )
+            }else if (item.type == "simple") {
+                return (
+                    <div 
+                        key={item.key.toString()}
+                        className={`grid-item${this.state.editMode ?' grid-item-edit': ''}`} 
+                    >
+                        <Element type="simple" content={item.content} itemKey={item.key}/>
+                        <Dropdown overlay={this.generateElementConfigMenu(item.key)} trigger={['click']}>
+                            <Icon type="setting" style={IconStyle} />
+                        </Dropdown>
+                    </div>
+                )
+            }
+            else{
+                return (<div key="3" >I'm not typed!</div>)
             
+            }
+        })
+    }
+
+    loadLayout() {
+        const layout = this.props.dashboardDesigner.layout;
 
-        }
+        return layout.map((item) => {
+            let { key, x, y, w, h, minW, maxW, minH, maxH } = item;
+            return { i: key.toString(), x: x, y: y, w: w, h: h, minW: minW, maxW: maxW, minH: minH, maxH: maxH}
+        })
     }
-    
+
     render() {
-        let layout = [
-            { i: 'a', x: 0, y: 0, w: 1, h: 2, static: true },
-            { i: 'b', x: 1, y: 0, w: 3, h: 2, minW: 2, maxW: 4 },
-            { i: 'c', x: 4, y: 0, w: 1, h: 2 }
-        ];
+        const { operation, visibleBox, editMode } = this.state;
+        
         return (
-            <ResponsiveReactGridLayout
-                className="grid-layout"
-                layouts={layout}
-                breakpoints={{lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0}}
-                cols={{lg: 12, md: 10, sm: 6, xs: 4, xxs: 2}}
-            >
-                <div key="a">a</div>
-                <div key="b">b</div>
-                <div key="c">c</div>
-            </ResponsiveReactGridLayout>
-
+            <Layout>
+                <Row 
+                    type='flex' 
+                    justify ='space-between'
+                    className='dashboard-header'
+                >
+                        <Col className='dashboard-header-title'                        >
+                            <div>{this.props.dashboardDesigner.basicConfig.title}</div>
+                        </Col>
+                        <Col className='dashboard-toolbar'>
+                            <div>
+                                <Button onClick={() => {
+                                    this.showElementConfigBox("create")
+                                }}>
+                                    添加元素
+                                </Button>
+                                <span>编辑模式</span>
+                                <Switch 
+                                    checkedChildren="开" 
+                                    unCheckedChildren="关" 
+                                    checked= {editMode}
+                                    onChange={(checked) => {
+                                        this.setState({editMode:checked}, () => {this.forceItUpdate})
+                                    }}
+                                    
+                                />
+                            </div>
+                        </Col>
+                </Row>
+                <ReactGridLayout 
+                    className={`grid-layout${this.state.editMode ?' grid-layout-edit': ''}`}  
+                    cols={12}
+                    layout={this.state.layout} 
+                    rowHeight={30} 
+                    width={1200}  //TODO: 宽度响应式设计?
+                    isDraggable={editMode}
+                    isResizable={editMode}
+                    compactType="horizontal"
+                >
+                {this.generateElement()}
+                
+                </ReactGridLayout>
+                <ElementConfig 
+                    operation={operation} 
+                    visibleBox={visibleBox}
+                    hideElementConfigBox={this.hideElementConfigBox} />
+            </Layout>
         )
     }
 
 }
 
-function mapStateToProps({present: {dashboard}}) {
-    return { dashboard }
+function mapStateToProps({present: {dashboardDesigner}}) {
+    return { dashboardDesigner }
 }
 
 export default connect(mapStateToProps)(DashboardDesigner)

+ 36 - 0
app/components/dashboardDesigner/layout.less

@@ -0,0 +1,36 @@
+.dashboard-header {
+    height:64px;
+    line-height: 64px;
+}
+.dashboard-header-title {
+    position: relative;
+    left: 5%;
+    top: 5%;
+
+}
+.dashboard-toolbar {
+    position: relative;
+    right: 5%;
+    top: 5%;
+}
+
+.grid-layout {
+    border: 1px solid transparent;
+    margin: 0 5% 0 5%;
+}
+.grid-layout-edit {
+    border: 1px dashed black;
+    
+}
+.grid-item {
+    border: 1px solid transparent;
+    background-color: aqua;
+}
+
+.grid-item-edit {
+    border: 1px dashed black;
+    background-color: pink;
+}
+
+
+

+ 2 - 0
app/index.js

@@ -8,6 +8,7 @@ import dashboard from './models/dashboard'
 import chart from './models/chart'
 import './utils/baseUtils'
 import './index.less'
+import dashboardDesigner from './models/dashboardDesigner';
 
 // 1. Initialize
 const app = dva({
@@ -30,6 +31,7 @@ app.model(dataSource); // 数据源
 app.model(dataConnect); // 数据连接
 app.model(dashboard);  //报告与看板
 app.model(chart);  // 图表
+app.model(dashboardDesigner)
 
 // 4. Router
 app.router(require('./routes/router'));

+ 21 - 2
app/models/dashboard.js

@@ -40,8 +40,27 @@ export default {
             coverAlt: '',
             avatar:{},
             dashboardConfig: {}
+        },{
+            dashboardID: 6,
+            type: 'dynamic',
+            title: 'Card 6',
+            url: '',
+            description: 'Description 1',
+            coverImg:'https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png',
+            coverAlt: '',
+            avatar:{},
+            dashboardConfig: {}
+        },{
+            dashboardID: 7,
+            type: 'dynamic',
+            title: 'Card 7',
+            url: '',
+            description: 'Description 1',
+            coverImg:'https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png',
+            coverAlt: '',
+            avatar:{},
+            dashboardConfig: {}
         }
-    
         ],
         myStaticDashboardList: [{            //Static Dashboard指报告(静态)
             dashboardID: 4,
@@ -63,8 +82,8 @@ export default {
             coverAlt: '',
             avatar:{},
             dashboardConfig: {}
+        
         }
-
         ],
                     
     }

+ 78 - 0
app/models/dashboardDesigner.js

@@ -0,0 +1,78 @@
+import { combineReducers } from "redux";
+
+export default {
+    namespace: 'dashboardDesigner',
+    state: {
+        basicConfig: {
+            title:'可爱的标题!',  
+            description:'',
+            type:'',   //分为Dynamic和Static
+            refreshTimer: 0, //自动刷新时间,如果是Static时应当无效或为0
+            width: '',   //Grid宽度,数字
+            cols: '',  //Grid列数 数字
+            compactType:'',   //Grid紧凑方式
+            margin:'', //Grid内Element之间的间隔
+            containerPadding:'',  //Element的内垫
+            rowHeight:'', //Grid单行高,数字
+        },
+        parameters: {
+        },  //全局可用参数
+        layout:[{
+            key:0,
+            x:0,     //单个元素的坐标宽高
+            y:0,
+            w:1,
+            h:1,
+            minW:  1,  //最小宽度
+            maxW: Infinity, //最大宽度
+            minH:  1, //最小高度
+            maxH: Infinity,  //最大宽度
+        },{
+            key:1,
+            x:1,     //单个元素的坐标宽高
+            y:0,
+            w:1,
+            h:1,
+            minW:  1,  //最小宽度
+            maxW: Infinity, //最大宽度
+            minH:  1, //最小高度
+            maxH: Infinity,  //最大宽度
+        }],
+        elementList:[{
+            key:0,
+            type:'chart',  //基础元素simple vs 图表元素chart
+            content:'',            
+        }, {
+            key:1,
+            type:'simple',
+            content:'',
+        }],
+        configBoxForm: {
+        }
+    },
+    
+    reducers: {
+        handleFieldChange(state, action) {
+            const { name, value } = action;
+            let configBoxForm = state.configBoxForm;
+            configBoxForm[name] = value;
+            return Object.assign({}, state, {configBoxForm})
+        },
+        resetConfigBoxForm(state, action) {
+            return Object.assign({}, state, {configBoxForm: {}});
+        },
+        loadConfigBoxForm(state, action) {
+            const { element } = action;
+            let configBoxForm = Object.assign({}, element);
+            return Object.assign({}, state, {configBoxForm})
+        }
+    },
+
+    effects: {
+
+    },
+    subscription: {
+
+    },
+};
+

+ 65 - 6
package-lock.json

@@ -85,7 +85,7 @@
         "@types/history": "4.6.2",
         "@types/react": "16.4.4",
         "@types/react-router": "4.0.27",
-        "redux": "3.7.2"
+        "redux": "4.0.0"
       }
     },
     "Base64": {
@@ -1959,6 +1959,16 @@
         "repeat-element": "1.1.2"
       }
     },
+    "braft-editor": {
+      "version": "1.9.8",
+      "resolved": "https://registry.npmjs.org/braft-editor/-/braft-editor-1.9.8.tgz",
+      "integrity": "sha512-GAmxLRwGQmZTIME0r5x1CCLINXv6Xpz+bQ49yz44Stxa8+vN+bfOuXbwjf1fVcSgZVkDh+5HEFlN0SPlwpuhVg==",
+      "requires": {
+        "draft-convert": "2.1.2",
+        "draft-js": "0.10.5",
+        "draftjs-utils": "0.8.8"
+      }
+    },
     "brorand": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
@@ -3366,6 +3376,15 @@
         "npm-install-webpack-plugin-cn": "2.0.4"
       }
     },
+    "draft-convert": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/draft-convert/-/draft-convert-2.1.2.tgz",
+      "integrity": "sha1-AGJLQPyA3KMgxOaTpf0h3rt4IG0=",
+      "requires": {
+        "immutable": "3.7.6",
+        "invariant": "2.2.4"
+      }
+    },
     "draft-js": {
       "version": "0.10.5",
       "resolved": "https://registry.npmjs.org/draft-js/-/draft-js-0.10.5.tgz",
@@ -3376,6 +3395,11 @@
         "object-assign": "4.1.1"
       }
     },
+    "draftjs-utils": {
+      "version": "0.8.8",
+      "resolved": "https://registry.npmjs.org/draftjs-utils/-/draftjs-utils-0.8.8.tgz",
+      "integrity": "sha1-WDXa7ZX1nT7h+W/cu7mAl3LIZJA="
+    },
     "dva": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/dva/-/dva-2.3.1.tgz",
@@ -3394,6 +3418,19 @@
         "react-router-dom": "4.3.1",
         "react-router-redux": "5.0.0-alpha.9",
         "redux": "3.7.2"
+      },
+      "dependencies": {
+        "redux": {
+          "version": "3.7.2",
+          "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
+          "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
+          "requires": {
+            "lodash": "4.17.10",
+            "lodash-es": "4.17.10",
+            "loose-envify": "1.3.1",
+            "symbol-observable": "1.2.0"
+          }
+        }
       }
     },
     "dva-core": {
@@ -3411,6 +3448,17 @@
         "warning": "3.0.0"
       },
       "dependencies": {
+        "redux": {
+          "version": "3.7.2",
+          "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
+          "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
+          "requires": {
+            "lodash": "4.17.10",
+            "lodash-es": "4.17.10",
+            "loose-envify": "1.3.1",
+            "symbol-observable": "1.2.0"
+          }
+        },
         "warning": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
@@ -10062,12 +10110,10 @@
       }
     },
     "redux": {
-      "version": "3.7.2",
-      "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
-      "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.0.tgz",
+      "integrity": "sha512-NnnHF0h0WVE/hXyrB6OlX67LYRuaf/rJcbWvnHHEPCF/Xa/AZpwhs/20WyqzQae5x4SD2F9nPObgBh2rxAgLiA==",
       "requires": {
-        "lodash": "4.17.10",
-        "lodash-es": "4.17.10",
         "loose-envify": "1.3.1",
         "symbol-observable": "1.2.0"
       }
@@ -10083,6 +10129,19 @@
       "integrity": "sha1-/0B3Sbj0aL6tY25BOBnpLAmC7so=",
       "requires": {
         "redux": "3.7.2"
+      },
+      "dependencies": {
+        "redux": {
+          "version": "3.7.2",
+          "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
+          "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
+          "requires": {
+            "lodash": "4.17.10",
+            "lodash-es": "4.17.10",
+            "loose-envify": "1.3.1",
+            "symbol-observable": "1.2.0"
+          }
+        }
       }
     },
     "regenerate": {

+ 2 - 0
package.json

@@ -6,6 +6,7 @@
   "dependencies": {
     "antd": "^3.0.0",
     "app": "^0.1.0",
+    "braft-editor": "^1.9.8",
     "dva": "^2.3.1",
     "echarts": "^4.1.0",
     "echarts-for-react": "^2.0.12-beta.0",
@@ -16,6 +17,7 @@
     "react-grid-layout": "^0.16.6",
     "react-router": "^4.3.1",
     "react-router-dom": "^4.3.1",
+    "redux": "^4.0.0",
     "redux-undo": "^0.6.1"
   },
   "devDependencies": {