Ver Fonte

Merge branch 'master' of ssh://10.10.100.21/source/platform-bi-web

hy há 6 anos atrás
pai
commit
5b35985a1e

+ 47 - 0
src/components/homePage/collection.jsx

@@ -0,0 +1,47 @@
+import React from 'react'
+import { connect } from 'dva'
+import { Collapse, Icon } from 'antd'
+import EmptyContent from '../common/emptyContent/index'
+import './collection.less'
+
+class Collection extends React.Component {
+    openTab = (c) => {
+        const { dispatch } = this.props;
+        dispatch({ type: 'home/openTab', tab: {
+            code: c.code,
+            name: c.name
+        } })
+    }
+    generateCollectionMenus() {
+        const { home } = this.props;
+        const { collectionDashboards } = home;
+        if(collectionDashboards.length > 0) {
+            return collectionDashboards.map((c, i) => (
+                <li className='item' key={i}>
+                    <span style={{ fontWeight: 'bold', cursor: 'pointer' }} onClick={() => {
+                        this.openTab(c);
+                    }}>
+                        <Icon style={{ marginRight: '8px' }} type="pushpin" />
+                        {c.name}
+                    </span>
+                </li>
+            ));
+        }else {
+            return <EmptyContent />
+        }
+    }
+
+    render() {
+        return <Collapse
+            className='collapse-collection'
+            bordered={false}
+            defaultActiveKey={['1']}
+            expandIcon={({ isActive }) => <Icon type="caret-right" rotate={isActive ? 90 : 0} />}
+        >
+            <Collapse.Panel header='我的收藏' key="1">
+                { this.generateCollectionMenus() }
+            </Collapse.Panel>
+        </Collapse>
+    }
+}
+export default connect(({ present: { home } }) => ({ home }))(Collection)

+ 25 - 0
src/components/homePage/collection.less

@@ -0,0 +1,25 @@
+.collapse-collection {
+    background-color: transparent;
+    &>.ant-collapse-item {
+        border: none;
+        &>.ant-collapse-header {
+            color: rgba(0, 0, 0, 0.65);
+            padding: 0 16px 0 30px;
+            &>.ant-collapse-arrow {
+                left: 6px;
+                &>svg {
+                    width: 10px;
+                    height: 10px;
+                }
+            }
+        }
+        &>.ant-collapse-content {
+            &>.ant-collapse-content-box {
+                padding: 8px 16px;
+                &>.item {
+                    padding: 6px 0 5px 0;
+                }
+            }
+        }
+    }
+}

+ 74 - 0
src/components/homePage/index.jsx

@@ -0,0 +1,74 @@
+import React from 'react'
+import { Layout, Tabs } from 'antd'
+import { connect } from 'dva'
+import MenuLayout from './sider'
+import DashboardViewToolbar from './toolbar'
+import DashboardView from './view'
+import EmptyContent from '../common/emptyContent/index'
+import './index.less'
+const { Sider, Content } = Layout
+const TabPane = Tabs.TabPane
+
+class Home extends React.Component {
+
+    generateTabs() {
+        const { home } = this.props;
+        const { tabs } = home;
+
+        return tabs.map(t => (
+            <TabPane tab={t.name} key={t.code}>
+                <DashboardViewToolbar />
+                <DashboardView code={t.code} config={t.config}/>
+            </TabPane>
+        ));
+    }
+
+    onChange = (activeKey) => {
+        const { dispatch, home } = this.props;
+        const { selectedTab, tabs } = home;
+        let tab = tabs.find(t => t.code === activeKey);
+        dispatch({ type: 'home/saveCurrentTabDashboardConfig', code: selectedTab.code });
+        dispatch({ type: 'home/changeTab', tab });
+    }
+
+    onEdit = (targetKey, action) => {
+        this[action](targetKey);
+    }
+
+    remove = (targetKey) => {
+        const { dispatch, home } =  this.props;
+        const { tabs } = home;
+        let tab = tabs.find(t => t.code === targetKey);
+        dispatch({ type: 'home/closeTab', tab });
+    }
+
+    render() {
+        const { home } = this.props;
+        const { tabs, selectedTab } = home;
+        return <Layout className='layout-home'>
+            <Sider
+                width={300}
+                collapsible
+                collapsedWidth={0}
+                trigger={null}
+                theme='light'
+                className='sider-home'
+            >
+                <MenuLayout />
+            </Sider>
+            <Content className='content-home'>
+                {tabs.length > 0 ? <Tabs
+                    type="editable-card"
+                    hideAdd
+                    onEdit={this.onEdit}
+                    onChange={this.onChange}
+                    activeKey={selectedTab ? selectedTab.code : null}
+                >
+                    { this.generateTabs() }
+                </Tabs> : <EmptyContent />}
+            </Content>
+        </Layout>
+    }
+}
+
+export default connect(({ present: { home } }) => ({ home }))(Home)

+ 26 - 0
src/components/homePage/index.less

@@ -0,0 +1,26 @@
+.layout-home {
+    .sider-home {
+        padding: 0;
+        border-right: 1px solid #ccc;
+        button {
+            padding: 0 8px;
+            margin: 0 0 12px 0;
+        }
+    }
+    .content-home {
+        &>.ant-tabs {
+            height: 100%;
+            padding-top: 56px;
+            &>.ant-tabs-bar {
+                margin: -40px 16px 0;
+                height: 40px;
+            }
+            &>.ant-tabs-content {
+                height: 100%;
+                &>.ant-tabs-tabpane {
+                    height: 100%;
+                }
+            }
+        }
+    }
+}

+ 55 - 0
src/components/homePage/sider.jsx

@@ -0,0 +1,55 @@
+import React from 'react'
+import { Layout, Input } from 'antd'
+import { connect } from 'dva'
+import DashboardCollection from './collection'
+import DashboardMenu from '../dashboard/menu'
+import './sider.less'
+
+const { Header, Content } = Layout
+
+class MenuLayout extends React.Component {
+
+    onSearch = (value) => {
+        const { dashboard, dispatch } = this.props;
+        const expandedKeys = this.findExpandedKeys(dashboard.menuTree, {code: '-1'}, value);
+        dispatch({ type: 'home/setFields', fields: [
+            { name: 'menuExpandedKeys', value: expandedKeys },
+            { name: 'menuFilterLabel', value },
+            { name: 'menuAutoExpandParent', value: true },
+        ] });
+    }
+
+    findExpandedKeys = (tree, parent, filterLabel) => {
+        let result = [];
+        if(tree && tree.length > 0) {
+            tree.forEach(t => {
+                if(t.name.toLowerCase().indexOf(filterLabel.toLowerCase()) > -1 && result.indexOf(parent.code) === -1) {
+                    result.push(parent.code);
+                }
+                result = result.concat(this.findExpandedKeys(t.children, t, filterLabel));
+            });
+        }
+        return result;
+    }
+
+    render() {
+        const { home } = this.props;
+        const { menuFilterLabel } = home;
+
+        return <Layout className='home-menu'>
+            <Header>
+                <Input.Search
+                    placeholder='搜索'
+                    defaultValue={menuFilterLabel}
+                    onSearch={this.onSearch}
+                />
+            </Header>
+            <Content>
+                <DashboardCollection />
+                <DashboardMenu mode='view' />
+            </Content>
+        </Layout>;
+    }
+}
+
+export default connect(({ present: { home, dashboard } }) => ({ home, dashboard }))(MenuLayout)

+ 14 - 0
src/components/homePage/sider.less

@@ -0,0 +1,14 @@
+.home-menu {
+    .ant-layout-header {
+        padding: 0 16px;
+        background: transparent;
+    }
+    .ant-layout-content {
+        display: flex;
+        flex-direction: column;
+    }
+    .menu-container {
+        overflow: auto;
+        flex: auto;
+    }
+}

+ 81 - 0
src/components/homePage/toolbar.jsx

@@ -0,0 +1,81 @@
+import React from 'react'
+import { Icon, Modal } from 'antd'
+import { connect } from 'dva'
+import DashboardDesigner from '../dashboardDesigner/layout'
+import './toolbar.less'
+
+class Toolbar extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            visibleFullscreenBox: false,
+        }
+    }
+    // 全屏展示
+    fullscreen = () => {
+        this.setState({
+            visibleFullscreenBox: true,
+        });
+    }
+    // 刷新
+    refresh = () => {
+        this.props.dispatch({ type: 'dashboardDesigner/refresh' });
+    }
+    // 收藏
+    collect = () => {
+        const { dispatch, home } = this.props;
+        const { selectedTab } = home;
+        dispatch({ type: 'home/collect', data: selectedTab });
+    }
+    // 取消收藏
+    uncollect = () => {
+        const { dispatch, home } = this.props;
+        const { selectedTab } = home;
+        dispatch({ type: 'home/uncollect', data: selectedTab });
+    }
+    
+    afterRefresh = () => {
+        const { dispatch, home } = this.props;
+        dispatch({ type: 'home/saveCurrentTabDashboardConfig', code: home.selectedTab.code });
+    }
+    render() {
+        const { home } = this.props;
+        const { tabs, selectedTab, collectionDashboards } = home;
+        let { config } = tabs.find(t => t.code === selectedTab.code);
+        return <div className='dashboardview-toolbar'>
+            <div className='toos'>
+                <div className='tool'><span onClick={this.fullscreen}><Icon type="fullscreen" />全屏</span></div>
+                <div className='tool'><span onClick={this.refresh}><Icon type="sync" />刷新</span></div>
+                <div className='tool'>{
+                    collectionDashboards.findIndex(c => c.code === selectedTab.code) > -1 ? 
+                        <span onClick={this.uncollect}><Icon type="star" theme="filled" />取消收藏</span> :
+                        <span onClick={this.collect}><Icon type="star" />收藏</span>
+                }</div>
+            </div>
+            {/** 全屏展示modal */}
+            {this.state.visibleFullscreenBox && <Modal
+                className='modal-full'
+                width='100%'
+                height='100%'
+                visible={this.state.visibleFullscreenBox}
+                footer={null}
+                onCancel={() => {
+                    this.setState({
+                        visibleFullscreenBox: false
+                    })
+                }}
+            >
+                <DashboardDesigner
+                    dashboardDesigner={this.props.dashboardDesigner}
+                    code={selectedTab.code}
+                    isViewMode
+                    afterLoad={null}
+                    afterRefresh={this.afterRefresh}
+                    config={config}
+                />
+            </Modal>}
+        </div>
+    }
+}
+
+export default connect(({ present: { dashboardDesigner, home } }) => ({ dashboardDesigner, home }))(Toolbar)

+ 42 - 0
src/components/homePage/toolbar.less

@@ -0,0 +1,42 @@
+.dashboardview-toolbar {
+    display: flex;
+    justify-content: flex-end;
+    width: 100%;
+    border-top: 1px solid #ccc;
+    &>.toos {
+        display: flex;
+        margin-right: 16px;
+        &>.tool {
+            cursor: pointer;
+            margin-right: 8px;
+            i {
+                margin: 0 4px;
+            }
+        }
+    }
+}
+
+.modal-full {
+    top: 0;
+    padding: 0;
+    .ant-modal-content {
+        height: 100%;
+        .ant-modal-close {
+            width: 0;
+            height: 0;
+            border-width: 16px;
+            border-style: solid;
+            border-color: #DEDEDE #DEDEDE transparent transparent;
+            .ant-modal-close-x {
+                width: 16px;
+                height: 16px;
+                line-height: 0;
+                margin-top: -14px;
+            }
+        }
+        .ant-modal-body {
+            padding: 0;
+            height: 100%;
+        }
+    }
+}

+ 29 - 0
src/components/homePage/view.jsx

@@ -0,0 +1,29 @@
+import React from 'react'
+import { connect } from 'dva'
+import DashboardDesigner from '../dashboardDesigner/layout'
+
+class DashboardView extends React.Component {
+    afterLoad = (data) => {
+        const { dispatch, code } = this.props;
+        if(data) {
+            dispatch({ type: 'dashboardDesigner/setEditMode', checked: false });
+            dispatch({ type: 'home/saveCurrentTabDashboardConfig', code });
+        }
+    }
+
+    afterRefresh = () => {
+        const { dispatch, code } = this.props;
+        dispatch({ type: 'home/saveCurrentTabDashboardConfig', code });
+    }
+
+    render() {
+        return <DashboardDesigner
+            dashboardDesigner={this.props.dashboardDesigner}
+            code={this.props.code}
+            isViewMode
+            afterLoad={this.afterLoad}
+            afterRefresh={this.afterRefresh}
+            config={this.props.config}/>
+    }
+}
+export default connect(({ present: { dashboardDesigner } }) => ({ dashboardDesigner }))(DashboardView)

+ 3 - 0
src/components/homePage/view.less

@@ -0,0 +1,3 @@
+.layout-dashboard-view {
+    
+}

+ 1 - 3
src/components/setting/index.jsx

@@ -3,7 +3,6 @@ import { Link } from 'react-router-dom'
 import { Route, Switch, Redirect } from 'dva/router'
 import { Layout, Button } from 'antd'
 import { connect } from 'dva'
-import Loading from '../../components/common/loading/loading'
 import Admin from '../../components/admin/admin'
 import Logs from '../../components/logs/logs'
 import './index.less'
@@ -24,7 +23,6 @@ class Setting extends React.Component {
         let paths = currentPage.match(/\/(\w+)/g).map(p => p.replace('/', ''));
 
         return <Layout className='layout-setting'>
-            <Loading />
             <Sider
                 width={100}
                 collapsible
@@ -37,7 +35,7 @@ class Setting extends React.Component {
                 <Link to=''><Button disabled className='ant-btn-block' type={paths[1] === 'datasource' ? 'primary' : 'default'} >权限管理</Button></Link>
                 <Link to='/setting/logs'><Button className='ant-btn-block' type={paths[1] === 'logs' ? 'primary' : 'default'} >操作日志</Button></Link>
             </Sider>
-            <Content className='main-content'>
+            <Content className='content-setting'>
                 <Switch>
                     <Route sensitive path='/setting/admin' component={Admin}/>
                     <Route sensitive path='/setting/logs' component={Logs}/>

+ 4 - 0
src/components/setting/index.less

@@ -1,9 +1,13 @@
 .layout-setting {
     .sider-setting {
         padding: 12px;
+        border: 1px solid #ccc;
         button {
             padding: 0 8px;
             margin: 0 0 12px 0;
         }
     }
+    .content-setting {
+        // padding: 12px;
+    }
 }

+ 1 - 3
src/components/workshop/index.jsx

@@ -3,7 +3,6 @@ import { Link } from 'react-router-dom'
 import { Route, Switch, Redirect } from 'dva/router'
 import { Layout, Button } from 'antd'
 import { connect } from 'dva'
-import Loading from '../../components/common/loading/loading'
 import DataConnect from '../../components/dataConnect/list'
 import DataSource from '../../components/dataSource/list'
 import Dashboard from '../../components/dashboard/layout'
@@ -27,7 +26,6 @@ class Workshop extends React.Component {
         let paths = currentPage.match(/\/(\w+)/g).map(p => p.replace('/', ''));
 
         return <Layout className='layout-workshop'>
-            <Loading />
             <Sider
                 width={100}
                 collapsible
@@ -41,7 +39,7 @@ class Workshop extends React.Component {
                 <Link to='/workshop/chart'><Button className='ant-btn-block' type={paths[1] === 'chart' ? 'primary' : 'default'} >图表</Button></Link>
                 <Link to='/workshop/dashboard'><Button className='ant-btn-block' type={paths[1] === 'dashboard' ? 'primary' : 'default'} >报表</Button></Link>
             </Sider>
-            <Content className='main-content'>
+            <Content className='content-workshop'>
                 <Switch>
                     <Route sensitive path='/workshop/dataconnect' component={DataConnect}/>
                     <Route sensitive path='/workshop/datasource/:type/:code/:tab' component={DataSourceDetail}/>

+ 15 - 9
src/constants/url.js

@@ -1,6 +1,6 @@
-const BASE_URL = 'http://10.1.1.168:8094/BI';
-// const BASE_URL = 'http://10.1.80.36:8011';
-// const BASE_URL = 'http://218.18.115.198:8888/BI'
+// const BASE_URL = 'http://10.1.1.168:8094/BI';
+// const BASE_URL = 'http://10.1.80.74:8011';
+const BASE_URL = 'http://218.18.115.198:8888/BI'
 
 /**后台接口地址 */
 const URLS = {
@@ -187,19 +187,25 @@ const URLS = {
 
     DASHBOARD_SHARE_DETAIL_BY_CODE: BASE_URL + '/getDashboardByCode', // 通过报表编号获得报表数据
 
+    DASHBOARD_COLLECT_LIST: BASE_URL + '/dashboard/collect/list', // 报表收藏列表
+
+    DASHBOARD_COLLECT_ADD: BASE_URL + '/dashboard/collect/add', // 添加报表到收藏
+
+    DASHBOARD_COLLECT_REMOVE: BASE_URL + '/dashboard/collect/remove', // 报表取消收藏
+
     /***************************************报表目录***************************************/
 
-    DASHBOARD_MENU_TREE: BASE_URL + '/dashBoard/menu/list', // 获取报表目录树
+    DASHBOARD_MENU_TREE: BASE_URL + '/dashboard/menu/list', // 获取报表目录树
 
-    DASHBOARD_MENU_ADD: BASE_URL + '/dashBoard/menu/save', // 添加报表目录
+    DASHBOARD_MENU_ADD: BASE_URL + '/dashboard/menu/save', // 添加报表目录
 
-    DASHBOARD_MENU_UPDATE: BASE_URL + '/dashBoard/menu/update', // 添加报表目录
+    DASHBOARD_MENU_UPDATE: BASE_URL + '/dashboard/menu/update', // 添加报表目录
 
-    DASHBOARD_MENU_DELETE: BASE_URL + '/dashBoard/menu/delete', // 删除报表目录
+    DASHBOARD_MENU_DELETE: BASE_URL + '/dashboard/menu/delete', // 删除报表目录
 
-    DASHBOARD_MENU_DASHBOARD_LIST: BASE_URL + '/dashBoard/menu/list/dashBoard', // 获得目录下的所有报表
+    DASHBOARD_MENU_DASHBOARD_LIST: BASE_URL + '/dashboard/menu/list', // 获得目录下的所有报表
 
-    DASHBOARD_SET_MENU: BASE_URL + '/dashBoard/menu/update/dashboard', // 设置报表所属目录
+    DASHBOARD_SET_MENU: BASE_URL + '/dashboard/menu/update/dashboard', // 设置报表所属目录
 
     /***************************************浏览记录***************************************/
     

+ 2 - 0
src/index.js

@@ -2,6 +2,7 @@ import dva from 'dva'
 import undoable, { includeAction } from 'redux-undo'
 import indexRouter from './routes/router'
 import mainModel from './models/main'
+import homeModel from './models/home'
 import dataSource from './models/dataSource'
 import dataSourceDetail from './models/dataSourceDetail'
 import dataConnect from './models/dataConnect'
@@ -35,6 +36,7 @@ const app = dva({
 app.use(createLoading());
 
 // 3. Model
+app.model(homeModel); // 通用action
 app.model(mainModel); // 通用action
 app.model(dataConnect); // 数据链接
 app.model(dataSource); // 数据源

+ 13 - 12
src/models/chart.js

@@ -419,15 +419,19 @@ export default {
                 operate: g.operate,
                 groupName: g.label
             }))
-            const res = yield call(service.fetch, {
-                url: URLS.GROUP_CHART_BATCH_SET,
-                body
-            });
-            if(!res.err && res.data.code > 0) {
-                message.success('修改分组信息成功');
-                yield put({ type: 'remoteGroupList', mandatory: true });
-            }else {
-                message.error('修改分组信息失败: ' + (res.data.msg)); 
+            try{
+                const res = yield call(service.fetch, {
+                    url: URLS.GROUP_CHART_BATCH_SET,
+                    body
+                });
+                if(!res.err && res.data.code > 0) {
+                    message.success('修改分组信息成功');
+                    yield put({ type: 'remoteGroupList', mandatory: true });
+                }else {
+                    message.error('修改分组信息失败: ' + (res.data.msg));
+                }
+            }catch(e) {
+                message.error('修改分组信息失败: ' + e.message);
             }
         },
         /**
@@ -455,12 +459,10 @@ export default {
                         groupIndex: pgroups.length,
                     }
                 }
-                console.log('新增图表分组', body);
                 const res = yield call(service.fetch, {
                     url: URLS.GROUP_CHART_ADD,
                     body: body
                 });
-                console.log('新增图表分组', body, res);
                 if(!res.err && res.data.code > 0) {
                     let group = {
                         code: res.data.data + '',
@@ -473,7 +475,6 @@ export default {
                     message.error('添加分组失败: ' + (res.err || res.data.msg));
                 }
             }catch(e) {
-                console.log(e);
                 message.error('添加分组失败: ' + e.message);
             }
         },

+ 7 - 29
src/models/dashboard.js

@@ -193,42 +193,20 @@ export default {
                         })
                     }
                     yield put({ type: 'dashboardDesigner/silentSetFields', fields: fields });
+                    return data;
                 }else {
                     message.error('解析报表错误: ' + (res.err || res.data.msg));
+                    return false;
                 }
             }catch(e) {
                 message.error('解析报表错误: ' + e.message);
+                return false;
             }finally {
                 yield put({ type: 'dashboardDesigner/silentSetField', name: 'loading', value: false });
             }
         },
         *remoteAdd(action, { select, call, put }) {
-            try {
-                const dashboardDesigner = yield select(state => state.present.dashboardDesigner);
-                const { name, items, description, relationColumns, filters, shareCode, chartCodes } = dashboardDesigner;
-                let body = {
-                    bdName: name,
-                    bdNote: description,
-                    bdConfiguration: JSON.stringify(items),
-                    relationColumns: JSON.stringify(relationColumns),
-                    filters: JSON.stringify(filters) || '',
-                    bdCode: shareCode || generateShareCode(),
-                    thumbnail: '',
-                    chartIds: chartCodes.join(',')
-                }
-                const res = yield call(service.fetch, {
-                    url: URLS.DASHBOARD_ADD,
-                    body: body
-                });
-                if(!res.err && res.data.code > 0) {
-                    yield put({ type: 'fetchList', mandatory: true });
-                    message.success('保存成功');
-                }else {
-                    message.error('保存失败: ' + (res.err || res.data.msg));
-                } 
-            }catch(e) {
-                message.error('保存失败: ' + e.message);
-            }
+            yield message.error('未指定目录');
         },
         *remoteQucikAdd(action, { select, call, put }) {
             const { menuCode } = action;
@@ -251,7 +229,7 @@ export default {
                     body: body
                 });
                 if(!res.err && res.data.code > 0) {
-                    yield put({ type: 'fetchList', mandatory: true });
+                    yield put({ type: 'remoteMenuDashboardList', menuCode });
                     yield put({ type: 'main/redirect', path: '/dashboard/' + res.data.data });
                 }else {
                     message.error('保存失败: ' + (res.err || res.data.msg));
@@ -370,7 +348,7 @@ export default {
                         id: menu.code,
                         index: menu.index,
                         name: menu.name,
-                        parentId: menu.pcode
+                        parentId: menu.pcode === '-1' ? 0 : menu.pcode
                     }
                 })
                 if(!res.err && res.data.code > 0) {
@@ -395,7 +373,7 @@ export default {
                     id: menu.code,
                     index: menu.index,
                     name: menu.name,
-                    parentId: menu.pcode
+                    parentId: menu.pcode === '-1' ? '0' : menu.pcode
                 }
             });
             if(!res.err && res.data.code > 0) {

+ 27 - 1
src/models/dashboardDesigner.js

@@ -92,7 +92,14 @@ export default {
             columnFetching: false,
             loading: false,
             shareCode: '', // 分享码
-            demo: false
+            demo: false,
+            filterItems: [ // 可选过滤字段(选择图表时用)
+                { name: 'name', label: '图表名称', type: 'string' },
+                { name: 'description', label: '说明', type: 'string' },
+                { name: 'creatorName', label: '创建人', type: 'string' },
+                { name: 'createTime', label: '创建时间', type: 'date' },
+            ],
+            filterItem: { name: 'name', label: '图表名称', type: 'string' }, // 已选过滤字段(选择图表时用)
         },
     },
     
@@ -125,6 +132,14 @@ export default {
             let newState = Object.assign({}, state, obj);
             return Object.assign({}, newState, {dirty: true});
         },
+        setFilterItem(state, action) {
+            const { item } = action;
+            return Object.assign({}, state, {filterItem: item, filterLabel: ''});
+        },
+        setFilterLabel(state, action) {
+            const { label } = action;
+            return Object.assign({}, state, {filterLabel: label});
+        },
         addChart(state, action) {
             let { items, dataSources, chartCodes, defaultLayout } = state;
             const { chart } = action;
@@ -299,6 +314,17 @@ export default {
     },
 
     effects: {
+        /**
+         * 刷新报表
+         */
+        *refresh(action, { call, put, select }) {
+            const dashboardDesigner = yield select(state => state.present.dashboardDesigner);
+            const { items } = dashboardDesigner;
+
+            for(let i = 0; i < items.length; i++) {
+                yield put({ type:'fetchChartData', item: items[i], mandatory: true });
+            }
+        },
         *remoteGetColumns(action, { call, put, select }) {
             const { dataSourceCode, mandatory } = action;
             const dashboardDesigner = yield select(state => state.present.dashboardDesigner);

+ 13 - 9
src/models/dataSource.js

@@ -403,15 +403,19 @@ export default {
                 operate: g.operate,
                 groupName: g.label
             }))
-            const res = yield call(service.fetch, {
-                url: URLS.GROUP_DATASOURCE_BATCH_SET,
-                body
-            });
-            if(!res.err && res.data.code > 0) {
-                message.success('修改分组信息成功');
-                yield put({ type: 'remoteGroupList', mandatory: true });
-            }else {
-                message.error('修改分组信息失败: ' + (res.data.msg)); 
+            try{
+                const res = yield call(service.fetch, {
+                    url: URLS.GROUP_DATASOURCE_BATCH_SET,
+                    body
+                });
+                if(!res.err && res.data.code > 0) {
+                    message.success('修改分组信息成功');
+                    yield put({ type: 'remoteGroupList', mandatory: true });
+                }else {
+                    message.error('修改分组信息失败: ' + (res.data.msg));
+                }
+            }catch(e) {
+                message.error('修改分组信息失败: ' + e.message);
             }
         },
         /**

+ 168 - 0
src/models/home.js

@@ -0,0 +1,168 @@
+/**
+ * 首页model
+ */
+import { message } from 'antd'
+import * as service from '../services/index'
+import URLS from '../constants/url'
+
+export default {
+    namespace: 'home',
+    state: {
+        originData: {
+            menuExpandedKeys: ['-1'],
+            menuSelectedKeys: [],
+            menuFilterLabel: '',
+            menuAutoExpandParent: true,
+            menuCollected: [{},{},{},{},{}],
+            tabs: [],
+            selectedTab: null,
+            collectionDashboards: [], // 已收藏报表
+        }
+    },
+    reducers: {
+        setField(state, action) {
+            const { name, value } = action;
+            let obj = {};
+            obj[name] = value;
+            return Object.assign({}, state, obj);
+        },
+        setFields(state, action) {
+            const { fields } = action;
+            let obj = {};
+            fields.map(f => (obj[f.name] = f.value));
+            return Object.assign({}, state, obj);
+        },
+        reset(state, action) {
+            return Object.assign({}, state, state.originData);
+        },
+        addTab(state, action) {
+            const { tabs } = state;
+            const { tab } = action;
+            return Object.assign({}, state, { tabs: tabs.concat([tab]) });
+        },
+        removeTab(state, action) {
+            const { tabs } = state;
+            const { tab } = action;
+            let idx = tabs.findIndex(t => t.code === tab.code);
+            tabs.splice(idx, 1);
+            return Object.assign({}, state, { tabs });
+        },
+        setSelectedTab(state, action) {
+            return Object.assign({}, state, { selectedTab: action.tab });
+        }
+    },
+    effects: {
+        *openTab(action, { select, call, put }) {
+            const home = yield select(state => state.present.home);
+            const { tabs } = home;
+            const { tab } = action;
+            if(tabs.findIndex(t => t.code === tab.code) > -1) {
+                yield put({ type: 'changeTab', tab: tab });
+            }else {
+                yield put({ type: 'addTab', tab });
+                yield put({ type: 'changeTab', tab: tab });
+            }
+        },
+        *changeTab(action, { select, call, put }) {
+            const home = yield select(state => state.present.home);
+            const { tabs } = home;
+            const { tab } = action;
+            let fields = [];
+
+            yield put({ type: 'setSelectedTab', tab });
+            let fTab = tabs.find(t => t.code === tab.code);
+            let data = { ...fTab.config };
+            for(let key in data) {
+                fields.push({
+                    name: key,
+                    value: data[key]
+                })
+            }
+            yield put({ type: 'dashboardDesigner/reset' });
+            yield put({ type: 'dashboardDesigner/silentSetFields', fields: fields });
+        },
+        *closeTab(action, { select, call, put }) {
+            const home = yield select(state => state.present.home);
+            const { tabs } = home;
+            const { tab } = action;
+
+            yield put({ type: 'removeTab', tab });
+            if(tabs.length > 0) {
+                yield put({ type: 'changeTab', tab: tabs[tabs.length - 1] });
+            }
+        },
+        *saveCurrentTabDashboardConfig(action, { select, call, put }) {
+            const { home, dashboardDesigner } = yield select(state => ({ home: state.present.home, dashboardDesigner: state.present.dashboardDesigner }));
+            const { tabs } = home;
+            const { code } = action;
+            let idx = tabs.findIndex(t => t.code === code);
+            if(idx > -1) {
+                tabs[idx].config = { ...dashboardDesigner }
+            }
+            yield put({ type: 'setField', name: 'tabs', value: tabs });
+        },
+        *fetchCollectionDashboardList(action, { select, call, put }) {
+            try {
+                const res = yield call(service.fetch, {
+                    url: URLS.DASHBOARD_COLLECTION_LIST,
+                    method: 'GET'
+                });
+                if(!res.err && res.data.code > 0) {
+                    const list = res.data.data.map(d => ({
+                        code: d.id + '',
+                        name: d.name
+                    }));
+                    yield put({ type: 'setField', name: 'collectionDashboards', value: list });
+                }else {
+                    message.error('获取收藏列表失败: ' + (res.err || res.data.msg));
+                }
+            }catch(e) {
+                message.error('获取收藏列表失败: ' + e.message);
+            }
+        },
+        *collect(action, { select, put, call }) {
+            try{
+                const home = yield select(state => state.present.home);
+                const { data } = action;
+                const { collectionDashboards } = home;
+                const res = yield call(service.fetch, {
+                    url: URLS.DASHBOARD_COLLECT_ADD,
+                    body: {}
+                });
+                if(!res.err && res.data.code > 0) {
+                    collectionDashboards.unshift({ code: data.code, name: data.name });
+                    yield put({ type: 'setField', name: 'collectionDashboards', value: collectionDashboards });
+                }else {
+                    message.error('添加收藏失败: ' + (res.err || res.data.msg));
+                }
+            }catch(e) {
+                message.error('添加收藏失败: ' + e.message);
+            }
+        },
+        *uncollect(action, { select, put, call }) {
+            try{
+                const home = yield select(state => state.present.home);
+                const { data } = action;
+                const { collectionDashboards } = home;
+                const res = yield call(service.fetch, {
+                    url: URLS.DASHBOARD_COLLECT_REMOVE,
+                    body: {}
+                });
+                if(!res.err && res.data.code > 0) {
+                    let idx = collectionDashboards.findIndex(d => d.code === data.code);
+                    collectionDashboards.splice(idx, 1);
+                    yield put({ type: 'setField', name: 'collectionDashboards', value: collectionDashboards });
+                }else {
+                    message.error('添加收藏失败: ' + (res.err || res.data.msg));
+                }
+            }catch(e) {
+                message.error('添加收藏失败: ' + e.message);
+            }
+        },
+    },
+    subscriptions: {
+        setup({ dispatch, history }) {
+            dispatch({ type: 'reset' });
+        }
+    }
+};

+ 0 - 1
src/models/main.js

@@ -142,7 +142,6 @@ export default {
                 }
             }catch(e) {
                 message.error('登录失败: ' + e.message);
-                console.error(body, e);
                 return false;
             }
         },

+ 2 - 2
src/routes/mainLayout.jsx

@@ -2,8 +2,8 @@ import React from 'react'
 import { Layout } from 'antd'
 import { Route, Switch, Redirect } from 'dva/router'
 import Navigator from '../components/common/navigator'
-import HomePage from '../components/homePage/homePage'
 import Loading from '../components/common/loading/loading'
+import HomePage from '../components/homePage/index'
 import Workshop from '../components/workshop/index'
 import Setting from '../components/setting/index'
 import './mainLayout.less'
@@ -19,7 +19,7 @@ const MainLayout = () => {
             </Header>
             <Content className='main-content'>
                 <Switch>
-                    <Route sensitive path='/home' component={HomePage}/>
+                    <Route sensitive exact={false} path='/home' component={HomePage}/>
                     <Route sensitive exact={false} path='/workshop' component={Workshop}/>
                     <Route sensitive exact={false} path='/setting' component={Setting} />
                     <Route sensitive path='/userinfo' component={UserInfo} />

+ 2 - 2
src/routes/router.js

@@ -6,7 +6,7 @@ import Login from '../components/common/login/login'
 import Register from '../components/common/login/register'
 import MainLayout from './mainLayout'
 import ChartDesigner from '../components/chartDesigner/layout'
-import DashboardDesigner from '../components/dashboardDesigner/layout'
+import DashboardView from '../components/dashboard/view'
 import DashboardShareView from '../components/dashboard/shareView'
 import DashboardShareKeyView from '../components/dashboard/shareKeyView'
 // 由于 antd 组件的默认文案是英文,所以需要修改为中文
@@ -22,7 +22,7 @@ function RouterConfig({ history }) {
                     <Route sensitive path='/dashboard/share/:code' component={DashboardShareView} />
                     <Route sensitive path='/dashboard/share_key/:code' component={DashboardShareKeyView} />
                     <PrivateRoute sensitive path='/chart/:code' component={ChartDesigner} />
-                    <PrivateRoute sensitive path='/dashboard/:code/' component={DashboardDesigner} />
+                    <PrivateRoute sensitive path='/dashboard/:code/' component={DashboardView}/>
                     <PrivateRoute path='/' component={MainLayout} />
                 </Switch>
             </Router>