Browse Source

报表展示组件优化/报表收藏

zhuth 6 years ago
parent
commit
107c0bf694

+ 9 - 8
src/components/chart/list.jsx

@@ -6,6 +6,7 @@ import { Layout, Button, Icon, Input, Menu, Dropdown, Card, Col, Row, Breadcrumb
 import { connect } from 'dva'
 import ChooseDataSourceBox from './chooseDataSourceBox'
 import GroupManagementBox from '../common/groupManageMentBox/box'
+import EmptyContent from '../common/emptyContent/index'
 import moment from 'moment'
 import Ellipsis from 'ant-design-pro/lib/Ellipsis'
 import 'ant-design-pro/dist/ant-design-pro.css'
@@ -188,25 +189,25 @@ class ChartList extends React.Component {
         let cards = this.onGroup().map(l => {
             if(filterItem.type === 'date') {
                 if(filterLabel===""){
-                    return { ...l, bf: false };
+                    return { ...l };
                 }else if(filterLabel.indexOf('#')>-1){
                     let start = filterLabel.split('#')[0]
                     let end = filterLabel.split('#')[1]
                     let nowTime = new Date(l[filterItem.name]).getTime();
                     if(nowTime>=start && nowTime<=end){
-                        return { ...l, bf: false };
+                        return { ...l };
                     }
-                    return { ...l, bf: true };
+                    return null;
                 }else{
-                    return { ...l, bf: true };
+                    return null;
                 }
             }else {
-                return ((l[filterItem.name] + '').search(filterReg) > -1) ? { ...l, bf: false } : { ...l, bf: true }
+                return ((l[filterItem.name] + '').search(filterReg) > -1) ? { ...l } : null
             }
-        }).sort((a, b) => {
+        }).filter(l => l !== null).sort((a, b) => {
             return new Date(b.createTime) - new Date(a.createTime)
         }).map( (l, i) => (
-            <CardGrid className={`chart-card${l.bf?' chart-card-hide':''}`} key={i} onClick={() => {
+            <CardGrid className={`chart-card`} key={i} onClick={() => {
                 this.setState({ selectedRecord: l })
             }}>
                 <Card
@@ -286,7 +287,7 @@ class ChartList extends React.Component {
             </CardGrid>
         ));
         if(cards.length === 0) {
-            return (<div className="ant-empty ant-empty-normal"><div className="ant-empty-image"><img alt="暂无数据" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNDEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAxKSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgIDxlbGxpcHNlIGZpbGw9IiNGNUY1RjUiIGN4PSIzMiIgY3k9IjMzIiByeD0iMzIiIHJ5PSI3Ii8+CiAgICA8ZyBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0iI0Q5RDlEOSI+CiAgICAgIDxwYXRoIGQ9Ik01NSAxMi43Nkw0NC44NTQgMS4yNThDNDQuMzY3LjQ3NCA0My42NTYgMCA0Mi45MDcgMEgyMS4wOTNjLS43NDkgMC0xLjQ2LjQ3NC0xLjk0NyAxLjI1N0w5IDEyLjc2MVYyMmg0NnYtOS4yNHoiLz4KICAgICAgPHBhdGggZD0iTTQxLjYxMyAxNS45MzFjMC0xLjYwNS45OTQtMi45MyAyLjIyNy0yLjkzMUg1NXYxOC4xMzdDNTUgMzMuMjYgNTMuNjggMzUgNTIuMDUgMzVoLTQwLjFDMTAuMzIgMzUgOSAzMy4yNTkgOSAzMS4xMzdWMTNoMTEuMTZjMS4yMzMgMCAyLjIyNyAxLjMyMyAyLjIyNyAyLjkyOHYuMDIyYzAgMS42MDUgMS4wMDUgMi45MDEgMi4yMzcgMi45MDFoMTQuNzUyYzEuMjMyIDAgMi4yMzctMS4zMDggMi4yMzctMi45MTN2LS4wMDd6IiBmaWxsPSIjRkFGQUZBIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K"/></div><p className="ant-empty-description">暂无数据</p></div>)
+            return <EmptyContent />
         }
         return cards;
     }

+ 3 - 29
src/components/chartDesigner/content.jsx

@@ -13,6 +13,7 @@ import OtherConfigForm from './sections/otherConfigForm'
 import EchartsView from './charts/echartsView'
 import ToolBar from './sections/toolbar'
 import { connect } from 'dva'
+import EmptyContent from '../common/emptyContent/index'
 import './content.less'
 const { Header, Sider, Content, Footer } = Layout
 const { TabPane } = Tabs
@@ -33,20 +34,6 @@ class ChartDesignerContent extends React.Component {
         }
     }
 
-    // componentDidMount() {
-    //     window.setTimeout(() => {
-    //         this.setState({
-    //             animation: true
-    //         });
-    //     }, 2000);
-    //     this.refreshContentSize();
-    //     window.addEventListener('resize', this.refreshContentSize);
-    // }
-
-    // componentWillUnmount() {
-    //     window.removeEventListener('resize', this.refreshContentSize);
-    // }
-
     static getDerivedStateFromProps(nextProps, nextState) {
         const { chartDesigner, main } = nextProps;
         const isOwner = chartDesigner.creatorCode === main.currentUser.code;
@@ -61,21 +48,6 @@ class ChartDesignerContent extends React.Component {
         }
     }
 
-    // /**
-    //  * 设置图表区域大小
-    //  */
-    // refreshContentSize = () => {
-    //     let contentEl = findDOMNode(this.refs.contentEl);
-    //     if(!contentEl) { return; }
-    //     let contentLayout = contentEl.getBoundingClientRect();
-    //     this.setState({
-    //         contentSize: {
-    //             width: contentLayout.width,
-    //             height: contentLayout.height
-    //         }
-    //     });
-    // }
-
     onCollapse = () => {
         this.setState({
             collapsed: !this.state.collapsed,
@@ -112,6 +84,8 @@ class ChartDesignerContent extends React.Component {
         }else if(viewType === 'scatter') {
             configForm = (<ScatterConfigForm autoRefresh={autoRefresh} formItemLayout={formItemLayout}/>);
             chartView = (<EchartsView optionConfig={{ animation }}/>);
+        }else {
+            chartView = <EmptyContent />
         }
 
         return (

+ 0 - 120
src/components/common/CardList.jsx

@@ -1,120 +0,0 @@
-import React from 'react'
-import { Card, Row, Col } from 'antd'
-import { connect } from 'dva'
-import Ellipsis from 'ant-design-pro/lib/Ellipsis'
-import moment from 'moment'
-import Thumbnail from './thumbnail'
-import './CardList.less'
-const CardGrid = Card.Grid
-
-/**
- * 尝试抽出generateCard()作为一个公共模块
- * 需要指定以下props:
- * mode 模式,分为主页、报表、图表模式 (homepage, dashboard, chart)
- * list 数据所在的列表,存于models中
- * filterLabel 生成列表时使用的过滤器字段
- * onDistribute
- * onTransfer
- * onDelete
- * 
- * 备注: chart和dashboard的thumbnail部分需要抽取出来形成公用的
- * 
- * @class CardList
- * @extends {React.Component}
- */
-
-class CardList extends React.Component {
-    
-    generateCard() {
-        const { mode, dispatch, list, type } = this.props;
-        const reg = new RegExp('([+ \\- & | ! ( ) { } \\[ \\] ^ \" ~ * ? : ( ) \/])', 'g'); // 需要转义的字符
-        let filterLabel = (mode !== 'homepage') ? list.filterLabel.replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') : ""; // 添加转义符号
-
-        let cards = list.filter(l => {
-            let reg = new RegExp('(' + filterLabel + '){1}', 'ig');
-            return (l.name || '').search(reg) !== -1 || (l.description || '').search(reg) !== -1;
-        }).sort((a, b) => {
-            return new Date(b.recentTime) - new Date(a.recentTime)
-        }).map( (l, i) => (
-            <CardGrid className={(type === 'dashboard')? 'recent-dashboard-card' : 'recent-chart-card'} key={i} onClick={() => {
-                this.setState({ selectedRecord: l })
-            }}>
-                <Card
-                    title={
-                        <Row type='flex' justify='space-between'>
-                            <Col span={21} style={{ overflow: 'hidden', textOverflow: 'ellipsis' }} >
-                                { filterLabel ?
-                                    ((l.name || '').split(new RegExp(`(${filterLabel})`, 'i')).map((fragment, i) => {
-                                        return (
-                                            fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === filterLabel.toLowerCase() ?
-                                            <span key={i} style={{fontWeight: 'bold', color: 'red'}} className="highlight">{fragment}</span> :
-                                            fragment
-                                        )
-                                    }
-                                    )) : l.name
-                                }
-                            </Col>
-                            <Col style={{ textAlign: 'right' }} span={3} >
-                                {/* {<Icon type='star-o'/>} */}
-                            </Col>
-                        </Row>
-                    }
-                    cover={
-                        <Col className='cover-body'>
-                            <Row className='thumb' onClick={() => {
-                                if(type === 'dashboard') {
-                                    dispatch({ type: 'dashboardDesigner/reset' });
-                                    dispatch({ type: 'main/redirect', path: '/dashboard/' + l.code });
-                                }else {
-                                    dispatch({ type: 'chartDesigner/reset' });
-                                    dispatch({ type: 'main/redirect', path: '/chart/' + l.code });
-                                }
-                            }}>
-                                <Thumbnail mode={type} type={l.type} code={l.code} option={l.chartOption} thumbnail={l.thumbnail}/> 
-                            </Row>
-                            {type === 'chart' && <Row className='desc'>
-                                <Ellipsis tooltip={l.description.length > 16} lines={2}>{
-                                    <span>
-                                    { filterLabel ?
-                                        ((l.description || '').split(new RegExp(`(${filterLabel})`, 'i')).map((fragment, i) => {
-                                            return (
-                                                fragment.toLowerCase().replace(new RegExp('(\\\\)', 'g'), '\\$1').replace(reg, '\\$1') === filterLabel.toLowerCase() ?
-                                                <span key={i} style={{fontWeight: 'bold', color: 'red'}} className="highlight">{fragment}</span> :
-                                                fragment
-                                            )
-                                        }
-                                        )) : l.description
-                                    }
-                                </span>
-                                }</Ellipsis>
-                            </Row>}
-                            <Row className='footer' type='flex' justify='end' align='bottom'>
-                                <Col style={{ textAlign: 'left', lineHeight: '30px' }} span={22}>
-                                    <Row>{l.createBy} {moment(l.createTime).format('YYYY-MM-DD')}</Row>
-                                </Col>
-                                <Col span={2} style={{ textAlign: 'right' }}>
-                                    {/* <Dropdown overlay={operationMenu} trigger={['click']}>
-                                        <Icon type="ellipsis" />
-                                    </Dropdown> */}
-                                </Col>
-                            </Row>
-                        </Col>
-                    }
-                >
-                </Card>
-            </CardGrid>
-        ));
-        if(cards.length === 0) {
-            cards = <div style={{ padding: '7px', textAlign: 'center', fontSize: '14px', color: 'rgba(0, 0, 0, 0.45)' }}>暂无数据</div>
-            return cards
-        }
-        
-        return cards.slice(0,3); //此处为了开发方便,限制了只显示前3个,之后可能要做成分页
-    }
-
-    render() {
-        return this.generateCard()
-    }
-}
-
-export default connect(({ present: { dashboard, homepage, chart } }) =>  { return { homepage, dashboard, chart }})(CardList)

+ 0 - 146
src/components/common/CardList.less

@@ -1,146 +0,0 @@
-.recent-chart-card {
-    width: 207px;
-    text-align: center;
-    margin: 5px;
-    padding: 0;
-
-    .ant-card-head {
-        min-height: 20px;
-        cursor: default;
-        .ant-card-head-title {
-            font-size: 14px;
-            padding: 6px 0;
-            .ant-row-flex {
-                flex: 1;
-            }
-            .anticon {
-                cursor: pointer;
-            }
-        }
-    }
-
-    .ant-card-cover {
-        height: 200px;
-        .cover-body {
-            padding: 10px;
-            .thumb {
-                height: 132px;
-                .deny-body {
-                    height: 100%;
-                    width: 100%;
-                    display: flex;
-                    position: absolute;
-                    text-align: center;
-                    justify-content: center;
-                    flex-direction: column;
-                    z-index: 1;
-                    .deny-tip {
-                        height: 40px;
-                        line-height: 40px;
-                        background: rgba(218, 218, 218, 0.5);
-                        border: 2px solid #aaaaaa;
-                        font-weight: bold;
-                    }
-                }
-                cursor: pointer;
-                canvas {
-                    cursor: pointer;
-                }
-                .table-default {
-                    background-image: url(../../../static/images/table-default.png);
-                    width: 100%;
-                    height: 100%;
-                    background-position: center;
-                    background-size: contain;
-                    background-repeat: no-repeat;
-                    cursor: pointer;
-                }
-                .chart-default {
-                    background-image: url(../../../static/images/chart-default.png);
-                    width: 100%;
-                    height: 100%;
-                    background-position: center;
-                    background-size: contain;
-                    background-repeat: no-repeat;
-                    cursor: pointer;
-                    opacity: .3;
-                }
-            }
-            .desc {
-                text-align: left;
-                height: 41px;
-            }
-            .footer {
-                margin-top: 6px;
-                .anticon {
-                    font-size: 24px;
-                }
-            }
-        }
-    }
-}
-
-.recent-dashboard-card {
-    width: 507px;
-    text-align: center;
-    margin: 5px;
-    padding: 0;
-
-    .ant-card-head {
-        min-height: 20px;
-        cursor: default;
-        .ant-card-head-title {
-            font-size: 14px;
-            padding: 6px 0;
-            .ant-row-flex {
-                flex: 1;
-            }
-            .anticon {
-                cursor: pointer;
-            }
-        }
-    }
-
-    .ant-card-cover {
-        height: 247px;
-        .cover-body {
-            padding: 10px;
-            .thumb {
-                height: 222px;
-                cursor: pointer;
-                canvas {
-                    cursor: pointer;
-                }
-                .table-default {
-                    background-image: url(../../../static/images/table-default.png);
-                    width: 100%;
-                    height: 100%;
-                    background-position: center;
-                    background-size: contain;
-                    background-repeat: no-repeat;
-                    cursor: pointer;
-                }
-                .dashboard-default {
-                    background-image: url(../../../static/images/dashboard-default.png);
-                    width: 100%;
-                    height: 100%;
-                    background-position: center;
-                    background-size: contain;
-                    background-repeat: no-repeat;
-                    cursor: pointer;
-                    opacity: .3;
-                }
-            }
-            .desc {
-                text-align: left;
-                height: 41px;
-            }
-            .footer {
-                margin-top: 6px;
-                .anticon {
-                    font-size: 24px;
-                }
-            }
-        }
-    }
-}

+ 7 - 0
src/components/common/emptyContent/index.jsx

@@ -0,0 +1,7 @@
+import React from 'react'
+
+const Content = () => {
+    return (<div className="ant-empty ant-empty-normal"><div className="ant-empty-image"><img alt="暂无数据" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNDEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAxKSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgIDxlbGxpcHNlIGZpbGw9IiNGNUY1RjUiIGN4PSIzMiIgY3k9IjMzIiByeD0iMzIiIHJ5PSI3Ii8+CiAgICA8ZyBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0iI0Q5RDlEOSI+CiAgICAgIDxwYXRoIGQ9Ik01NSAxMi43Nkw0NC44NTQgMS4yNThDNDQuMzY3LjQ3NCA0My42NTYgMCA0Mi45MDcgMEgyMS4wOTNjLS43NDkgMC0xLjQ2LjQ3NC0xLjk0NyAxLjI1N0w5IDEyLjc2MVYyMmg0NnYtOS4yNHoiLz4KICAgICAgPHBhdGggZD0iTTQxLjYxMyAxNS45MzFjMC0xLjYwNS45OTQtMi45MyAyLjIyNy0yLjkzMUg1NXYxOC4xMzdDNTUgMzMuMjYgNTMuNjggMzUgNTIuMDUgMzVoLTQwLjFDMTAuMzIgMzUgOSAzMy4yNTkgOSAzMS4xMzdWMTNoMTEuMTZjMS4yMzMgMCAyLjIyNyAxLjMyMyAyLjIyNyAyLjkyOHYuMDIyYzAgMS42MDUgMS4wMDUgMi45MDEgMi4yMzcgMi45MDFoMTQuNzUyYzEuMjMyIDAgMi4yMzctMS4zMDggMi4yMzctMi45MTN2LS4wMDd6IiBmaWxsPSIjRkFGQUZBIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K"/></div><p className="ant-empty-description">暂无数据</p></div>) 
+}
+
+export default Content

+ 1 - 1
src/components/dashboard/menu.jsx

@@ -113,7 +113,7 @@ class DashboardMenu extends React.Component {
             if(selectedMenu && selectedMenu.type === 'dashboard') {
                 dispatch({ type: 'home/openTab', tab: {
                     code: selectedMenu.code,
-                    title: selectedMenu.name
+                    name: selectedMenu.name
                 } })
             }
         }else {

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

@@ -167,6 +167,7 @@ class DashboardDesignerContent extends React.Component {
                             closable={false}
                             onClick={this.filterUsingChange}
                             data-key={tag.key}
+                            title={tag.label}
                         >
                             {tag.label}
                         </Tag>

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

@@ -52,7 +52,8 @@
             .ant-layout-header {
                 background: none;
                 padding: 0 10px;
-                height: 40px;
+                height: auto;
+                min-height: 40px;
                 line-height: 37px;
                 border-width: 1px 0;
                 border-style: solid;
@@ -61,9 +62,12 @@
                 justify-content: space-between;
                 .filters {
                     display: flex;
+                    width: 100%;
+                    flex-wrap: wrap;
                     .filter-tag {
-                        max-width: 400px;
+                        max-width: 200px;
                         overflow: hidden;
+                        white-space: nowrap;
                         text-overflow: ellipsis;
                         border-style: dashed;
                         margin: 8px 2px 8px 2px;

+ 3 - 5
src/components/dashboardDesigner/viewLayout.jsx

@@ -4,6 +4,7 @@ import ReactGridLayout from 'react-grid-layout'
 import { Icon, Modal } from 'antd'
 import { connect } from 'dva'
 import ChartView from './chartView'
+import EmptyContent from '../common/emptyContent/index'
 
 class ViewLayout extends React.PureComponent {
     constructor(props) {
@@ -83,7 +84,7 @@ class ViewLayout extends React.PureComponent {
     }
 
     render() {
-        const { isOwner, isShareView, isShareKeyView, isViewMode, dashboardDesigner, contentSize, lastContentSize } = this.props;
+        const { dashboardDesigner, contentSize, lastContentSize } = this.props;
         const { editMode } = dashboardDesigner;
         const { visiblePreviewBox, previewItem } = this.state;
         const children = dashboardDesigner.items.map((item) => this.createElement(item, false, !item.chartOption));
@@ -103,10 +104,7 @@ class ViewLayout extends React.PureComponent {
                 compactType='vertical'
             >
                 {(children.length === 0) ? <div key='default-chartview' className='default-chartview' data-grid={{ x: 0, y: 0, w: 12, h: 2, minW: 12, maxW: 12, minH: 2, maxH: 2, static: true }}>
-                    <div className='tip'>
-                        <Icon type="message" theme="outlined" />
-                        {(isOwner && !isShareView && !isShareKeyView && !isViewMode) ? <span>请从左侧选择图表/富文本添加到报表</span> : <span>无图表元素</span>}
-                    </div>
+                    <EmptyContent />
                 </div> : children}
             </ReactGridLayout>
             <Modal

+ 1 - 1
src/components/dashboardDesigner/viewLayout.less

@@ -129,7 +129,7 @@
     }
   }
   .react-grid-layout {
-    background: #eee;
+    // background: #eee;
   }
   .layoutJSON {
     background: #ddd;

+ 2 - 1
src/components/dataConnect/list.jsx

@@ -3,6 +3,7 @@ import { Layout, Row, Col, Input, Button, Icon, Card, Tooltip, Select, DatePicke
 import { connect } from 'dva'
 import moment from 'moment'
 import DeleteBox from '../common/deleteBox/deleteBox'
+import EmptyContent from '../common/emptyContent/index'
 import DataConnectBox from '../dataSourceDetail/dataConnectBox'
 import './list.less'
 const CardGrid = Card.Grid
@@ -190,7 +191,7 @@ class DataConnect extends React.Component {
         ))
 
         if(cards.length === 0) {
-            return (<div className="ant-empty ant-empty-normal"><div className="ant-empty-image"><img alt="暂无数据" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNDEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAxKSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgIDxlbGxpcHNlIGZpbGw9IiNGNUY1RjUiIGN4PSIzMiIgY3k9IjMzIiByeD0iMzIiIHJ5PSI3Ii8+CiAgICA8ZyBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0iI0Q5RDlEOSI+CiAgICAgIDxwYXRoIGQ9Ik01NSAxMi43Nkw0NC44NTQgMS4yNThDNDQuMzY3LjQ3NCA0My42NTYgMCA0Mi45MDcgMEgyMS4wOTNjLS43NDkgMC0xLjQ2LjQ3NC0xLjk0NyAxLjI1N0w5IDEyLjc2MVYyMmg0NnYtOS4yNHoiLz4KICAgICAgPHBhdGggZD0iTTQxLjYxMyAxNS45MzFjMC0xLjYwNS45OTQtMi45MyAyLjIyNy0yLjkzMUg1NXYxOC4xMzdDNTUgMzMuMjYgNTMuNjggMzUgNTIuMDUgMzVoLTQwLjFDMTAuMzIgMzUgOSAzMy4yNTkgOSAzMS4xMzdWMTNoMTEuMTZjMS4yMzMgMCAyLjIyNyAxLjMyMyAyLjIyNyAyLjkyOHYuMDIyYzAgMS42MDUgMS4wMDUgMi45MDEgMi4yMzcgMi45MDFoMTQuNzUyYzEuMjMyIDAgMi4yMzctMS4zMDggMi4yMzctMi45MTN2LS4wMDd6IiBmaWxsPSIjRkFGQUZBIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K"/></div><p className="ant-empty-description">暂无数据</p></div>)
+            return <EmptyContent />
         }
         
         return cards;

+ 2 - 1
src/components/dataSourceDetail/dataConnectConfig.jsx

@@ -3,6 +3,7 @@ import { Layout, Form, Row, Col, Input, Icon, Menu, Dropdown, Divider, Upload, m
 import { connect } from 'dva'
 import DataConnectBox from './dataConnectBox'
 import Ellipsis from 'ant-design-pro/lib/Ellipsis'
+import EmptyContent from '../common/emptyContent/index'
 const { Content } = Layout
 const CardGrid = Card.Grid
 const UploadDragger = Upload.Dragger
@@ -118,7 +119,7 @@ class DataConnectConfig extends React.Component {
             // );
 
             if(cards.length === 0) {
-                return (<div className="ant-empty ant-empty-normal"><div className="ant-empty-image"><img alt="暂无数据" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNDEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAxKSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgIDxlbGxpcHNlIGZpbGw9IiNGNUY1RjUiIGN4PSIzMiIgY3k9IjMzIiByeD0iMzIiIHJ5PSI3Ii8+CiAgICA8ZyBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0iI0Q5RDlEOSI+CiAgICAgIDxwYXRoIGQ9Ik01NSAxMi43Nkw0NC44NTQgMS4yNThDNDQuMzY3LjQ3NCA0My42NTYgMCA0Mi45MDcgMEgyMS4wOTNjLS43NDkgMC0xLjQ2LjQ3NC0xLjk0NyAxLjI1N0w5IDEyLjc2MVYyMmg0NnYtOS4yNHoiLz4KICAgICAgPHBhdGggZD0iTTQxLjYxMyAxNS45MzFjMC0xLjYwNS45OTQtMi45MyAyLjIyNy0yLjkzMUg1NXYxOC4xMzdDNTUgMzMuMjYgNTMuNjggMzUgNTIuMDUgMzVoLTQwLjFDMTAuMzIgMzUgOSAzMy4yNTkgOSAzMS4xMzdWMTNoMTEuMTZjMS4yMzMgMCAyLjIyNyAxLjMyMyAyLjIyNyAyLjkyOHYuMDIyYzAgMS42MDUgMS4wMDUgMi45MDEgMi4yMzcgMi45MDFoMTQuNzUyYzEuMjMyIDAgMi4yMzctMS4zMDggMi4yMzctMi45MTN2LS4wMDd6IiBmaWxsPSIjRkFGQUZBIi8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K"/></div><p className="ant-empty-description">暂无数据</p></div>)
+                return <EmptyContent />
             }
             
             return cards;

+ 29 - 16
src/components/homePage/collection.jsx

@@ -1,30 +1,43 @@
 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 {
-    constructor(props) {
-        super(props);
-        this.state = {
-
-        }
+    openTab = (c) => {
+        const { dispatch } = this.props;
+        dispatch({ type: 'home/openTab', tab: {
+            code: c.code,
+            name: c.name
+        } })
     }
-
     generateCollectionMenus() {
         const { home } = this.props;
-        const { menuCollected } = home;
-        return menuCollected.map((c, i) => (
-            <li key={i}>
-                <span style={{ fontWeight: 'bold' }}>
-                    <Icon style={{ marginRight: '8px' }} type="pushpin" />
-                    销售采购分析
-                </span>
-            </li>
-        ));
+        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 bordered={false} defaultActiveKey={['1']}>
+        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>

+ 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;
+                }
+            }
+        }
+    }
+}

+ 14 - 10
src/components/homePage/index.jsx

@@ -4,6 +4,7 @@ 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
@@ -15,7 +16,7 @@ class Home extends React.Component {
         const { tabs } = home;
 
         return tabs.map(t => (
-            <TabPane tab={t.title} key={t.code}>
+            <TabPane tab={t.name} key={t.code}>
                 <DashboardViewToolbar />
                 <DashboardView code={t.code} config={t.config}/>
             </TabPane>
@@ -24,9 +25,10 @@ class Home extends React.Component {
 
     onChange = (activeKey) => {
         const { dispatch, home } = this.props;
-        const { selectedTabKey } = home;
-        dispatch({ type: 'home/saveCurrentTabDashboardConfig', code: selectedTabKey });
-        dispatch({ type: 'home/changeTab', tabKey: activeKey });
+        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) => {
@@ -34,13 +36,15 @@ class Home extends React.Component {
     }
 
     remove = (targetKey) => {
-        const { dispatch } =  this.props;
-        dispatch({ type: 'home/closeTab', tabKey: 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 { selectedTabKey } = home;
+        const { tabs, selectedTab } = home;
         return <Layout className='layout-home'>
             <Sider
                 width={300}
@@ -53,15 +57,15 @@ class Home extends React.Component {
                 <MenuLayout />
             </Sider>
             <Content className='content-home'>
-                <Tabs
+                {tabs.length > 0 ? <Tabs
                     type="editable-card"
                     hideAdd
                     onEdit={this.onEdit}
                     onChange={this.onChange}
-                    activeKey={selectedTabKey}
+                    activeKey={selectedTab ? selectedTab.code : null}
                 >
                     { this.generateTabs() }
-                </Tabs>
+                </Tabs> : <EmptyContent />}
             </Content>
         </Layout>
     }

+ 23 - 7
src/components/homePage/toolbar.jsx

@@ -11,32 +11,48 @@ class Toolbar extends React.Component {
             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.selectedTabKey });
+        dispatch({ type: 'home/saveCurrentTabDashboardConfig', code: home.selectedTab.code });
     }
     render() {
         const { home } = this.props;
-        const { tabs, selectedTabKey } = home;
-        let { config } = tabs.find(t => t.code === selectedTabKey);
+        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'><span onClick={this.collect}><Icon type="star" />收藏</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%'
@@ -51,7 +67,7 @@ class Toolbar extends React.Component {
             >
                 <DashboardDesigner
                     dashboardDesigner={this.props.dashboardDesigner}
-                    code={selectedTabKey}
+                    code={selectedTab.code}
                     isViewMode
                     afterLoad={null}
                     afterRefresh={this.afterRefresh}

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

@@ -21,6 +21,19 @@
     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%;

+ 6 - 0
src/constants/url.js

@@ -187,6 +187,12 @@ 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', // 获取报表目录树

+ 76 - 20
src/models/home.js

@@ -1,11 +1,9 @@
 /**
  * 首页model
  */
-import { routerRedux } from 'dva/router'
 import { message } from 'antd'
 import * as service from '../services/index'
 import URLS from '../constants/url'
-import moment from 'moment'
 
 export default {
     namespace: 'home',
@@ -17,7 +15,8 @@ export default {
             menuAutoExpandParent: true,
             menuCollected: [{},{},{},{},{}],
             tabs: [],
-            selectedTabKey: null,
+            selectedTab: null,
+            collectionDashboards: [], // 已收藏报表
         }
     },
     reducers: {
@@ -43,14 +42,13 @@ export default {
         },
         removeTab(state, action) {
             const { tabs } = state;
-            const { tabKey } = action;
-            let idx = tabs.findIndex(t => t.code === tabKey);
+            const { tab } = action;
+            let idx = tabs.findIndex(t => t.code === tab.code);
             tabs.splice(idx, 1);
             return Object.assign({}, state, { tabs });
         },
         setSelectedTab(state, action) {
-            const { tabs } = state;
-            return Object.assign({}, state, { selectedTabKey: action.tabKey });
+            return Object.assign({}, state, { selectedTab: action.tab });
         }
     },
     effects: {
@@ -59,21 +57,21 @@ export default {
             const { tabs } = home;
             const { tab } = action;
             if(tabs.findIndex(t => t.code === tab.code) > -1) {
-                yield put({ type: 'changeTab', tabKey: tab.code });
+                yield put({ type: 'changeTab', tab: tab });
             }else {
                 yield put({ type: 'addTab', tab });
-                yield put({ type: 'changeTab', tabKey: tab.code });
+                yield put({ type: 'changeTab', tab: tab });
             }
         },
         *changeTab(action, { select, call, put }) {
             const home = yield select(state => state.present.home);
-            const { tabs, selectedTabKey } = home;
-            const { tabKey } = action;
+            const { tabs } = home;
+            const { tab } = action;
             let fields = [];
 
-            yield put({ type: 'setSelectedTab', tabKey });
-            let tab = tabs.find(t => t.code === tabKey);
-            let data = { ...tab.config };
+            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,
@@ -85,24 +83,82 @@ export default {
         },
         *closeTab(action, { select, call, put }) {
             const home = yield select(state => state.present.home);
-            const { tabs, selectedTabKey } = home;
-            const { tabKey } = action;
+            const { tabs } = home;
+            const { tab } = action;
 
-            yield put({ type: 'removeTab', tabKey });
+            yield put({ type: 'removeTab', tab });
             if(tabs.length > 0) {
-                yield put({ type: 'changeTab', tabKey: tabs[tabs.length - 1].code });
+                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 { config, code } = action;
+            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 }) {