Procházet zdrojové kódy

Merge pull request #8 from ant-design/design-review

[WIP] List & Dashboard design review
niko před 8 roky
rodič
revize
63b095f24a

+ 1 - 0
package.json

@@ -35,6 +35,7 @@
     "eslint-plugin-babel": "^4.0.0",
     "eslint-plugin-import": "^2.2.0",
     "eslint-plugin-jsx-a11y": "^5.0.1",
+    "eslint-plugin-markdown": "^1.0.0-beta.6",
     "eslint-plugin-react": "^7.0.1",
     "gh-pages": "^1.0.0",
     "husky": "^0.13.4",

+ 77 - 0
src/components/ActiveChart/index.js

@@ -0,0 +1,77 @@
+import React, { PureComponent } from 'react';
+
+import { NumberInfo, MiniArea } from '../Charts';
+import { fixedZero } from '../../utils/utils';
+
+import styles from './index.less';
+
+function getActiveData() {
+  const activeData = [];
+  for (let i = 0; i < 24; i += 1) {
+    activeData.push({
+      x: `${fixedZero(i)}:00`,
+      y: (i * 50) + (Math.floor(Math.random() * 200)),
+    });
+  }
+  return activeData;
+}
+
+export default class ActiveChart extends PureComponent {
+  state = {
+    activeData: getActiveData(),
+  }
+
+  componentDidMount() {
+    setInterval(() => {
+      this.setState({
+        activeData: getActiveData(),
+      });
+    }, 1000);
+  }
+
+  render() {
+    const { activeData = [] } = this.state;
+
+    return (
+      <div className={styles.activeChart}>
+        <NumberInfo
+          subTitle="目标评估"
+          total="有望达到预期"
+        />
+        <div style={{ marginTop: 32 }}>
+          <MiniArea
+            animate={false}
+            line
+            color="#5DD1DD"
+            height={84}
+            yAxis={{
+              tickCount: 3,
+              tickLine: false,
+              labels: false,
+              title: false,
+              line: false,
+            }}
+            data={activeData}
+          />
+        </div>
+        {
+          activeData && (
+            <div className={styles.activeChartGrid}>
+              <p>{[...activeData].sort()[activeData.length - 1].y + 200} 亿元</p>
+              <p>{[...activeData].sort()[Math.floor(activeData.length / 2)].y} 亿元</p>
+            </div>
+          )
+        }
+        {
+          activeData && (
+            <div className={styles.activeChartLegend}>
+              <span>00:00</span>
+              <span>{activeData[Math.floor(activeData.length / 2)].x}</span>
+              <span>{activeData[activeData.length - 1].x}</span>
+            </div>
+          )
+        }
+      </div>
+    );
+  }
+}

+ 34 - 0
src/components/ActiveChart/index.less

@@ -0,0 +1,34 @@
+@import "~antd/lib/style/themes/default.less";
+@import "../../utils/utils.less";
+
+.activeChart {
+  position: relative;
+}
+.activeChartGrid {
+  p {
+    position: absolute;
+    top: 80px;
+  }
+  p:last-child {
+    top: 115px;
+  }
+}
+.activeChartLegend {
+  position: relative;
+  font-size: 0;
+  margin-top: 8px;
+  height: 20px;
+  line-height: 20px;
+  span {
+    display: inline-block;
+    font-size: 12px;
+    text-align: center;
+    width: 33.33%;
+  }
+  span:first-child {
+    text-align: left;
+  }
+  span:last-child {
+    text-align: right;
+  }
+}

+ 2 - 1
src/components/Charts/Bar/index.js

@@ -1,5 +1,6 @@
 import React, { PureComponent } from 'react';
 import G2 from 'g2';
+import equal from '../equal';
 import styles from '../index.less';
 
 class Bar extends PureComponent {
@@ -8,7 +9,7 @@ class Bar extends PureComponent {
   }
 
   componentWillReceiveProps(nextProps) {
-    if (nextProps.data !== this.props.data) {
+    if (!equal(this.props, nextProps)) {
       this.renderChart(nextProps.data);
     }
   }

+ 4 - 1
src/components/Charts/Gauge/index.js

@@ -1,5 +1,6 @@
 import React, { PureComponent } from 'react';
 import G2 from 'g2';
+import equal from '../equal';
 
 const Shape = G2.Shape;
 
@@ -10,7 +11,9 @@ class Gauge extends PureComponent {
   }
 
   componentWillReceiveProps(nextProps) {
-    this.renderChart(nextProps);
+    if (!equal(this.props, nextProps)) {
+      this.renderChart(nextProps);
+    }
   }
 
   handleRef = (n) => {

+ 4 - 2
src/components/Charts/MiniArea/index.js

@@ -1,5 +1,6 @@
 import React, { PureComponent } from 'react';
 import G2 from 'g2';
+import equal from '../equal';
 import styles from '../index.less';
 
 class MiniArea extends PureComponent {
@@ -8,7 +9,7 @@ class MiniArea extends PureComponent {
   }
 
   componentWillReceiveProps(nextProps) {
-    if (nextProps.data !== this.props.data) {
+    if (!equal(this.props, nextProps)) {
       this.renderChart(nextProps.data);
     }
   }
@@ -18,7 +19,7 @@ class MiniArea extends PureComponent {
   }
 
   renderChart(data) {
-    const { height = 0, fit = true, color = '#33abfb', line, xAxis, yAxis } = this.props;
+    const { height = 0, fit = true, color = '#33abfb', line, xAxis, yAxis, animate = true } = this.props;
 
     if (!data || (data && data.length < 1)) {
       return;
@@ -31,6 +32,7 @@ class MiniArea extends PureComponent {
       container: this.node,
       forceFit: fit,
       height: height + 54,
+      animate,
       plotCfg: {
         margin: [36, 0, 30, 0],
       },

+ 2 - 1
src/components/Charts/MiniBar/index.js

@@ -1,5 +1,6 @@
 import React, { PureComponent } from 'react';
 import G2 from 'g2';
+import equal from '../equal';
 import styles from '../index.less';
 
 class MiniBar extends PureComponent {
@@ -8,7 +9,7 @@ class MiniBar extends PureComponent {
   }
 
   componentWillReceiveProps(nextProps) {
-    if (nextProps.data !== this.props.data) {
+    if (!equal(this.props, nextProps)) {
       this.renderChart(nextProps.data);
     }
   }

+ 10 - 7
src/components/Charts/MiniProgress/index.js

@@ -1,16 +1,19 @@
 import React from 'react';
+import { Popover } from 'antd';
 
 import styles from './index.less';
 
 const MiniProgress = ({ target, color, strokeWidth, percent }) => (
   <div className={styles.miniProgress}>
-    <div
-      className={styles.target}
-      style={{ left: (target ? `${target}%` : null) }}
-    >
-      <span style={{ backgroundColor: (color || null) }} />
-      <span style={{ backgroundColor: (color || null) }} />
-    </div>
+    <Popover title={null} content={`目标值: ${target}%`}>
+      <div
+        className={styles.target}
+        style={{ left: (target ? `${target}%` : null) }}
+      >
+        <span style={{ backgroundColor: (color || null) }} />
+        <span style={{ backgroundColor: (color || null) }} />
+      </div>
+    </Popover>
     <div className={styles.progressWrap}>
       <div
         className={styles.progress}

+ 1 - 0
src/components/Charts/MiniProgress/index.less

@@ -17,6 +17,7 @@
     height: 100%;
   }
   .target {
+    cursor: pointer;
     position: absolute;
     top: 0;
     bottom: 0;

+ 4 - 1
src/components/Charts/Pie/index.js

@@ -1,5 +1,6 @@
 import React, { Component } from 'react';
 import G2 from 'g2';
+import equal from '../equal';
 import styles from './index.less';
 
 /* eslint react/no-danger:0 */
@@ -14,7 +15,9 @@ class Pie extends Component {
   }
 
   componentWillReceiveProps(nextProps) {
-    this.renderChart(nextProps.data);
+    if (!equal(this.props, nextProps)) {
+      this.renderChart(nextProps.data);
+    }
   }
 
   handleRef = (n) => {

+ 2 - 1
src/components/Charts/Radar/index.js

@@ -1,6 +1,7 @@
 import React, { PureComponent } from 'react';
 import G2 from 'g2';
 import { Row, Col } from 'antd';
+import equal from '../equal';
 import styles from './index.less';
 
 /* eslint react/no-danger:0 */
@@ -14,7 +15,7 @@ class Radar extends PureComponent {
   }
 
   componentWillReceiveProps(nextProps) {
-    if (nextProps.data !== this.props.data) {
+    if (!equal(this.props, nextProps)) {
       this.renderChart(nextProps.data);
     }
   }

+ 17 - 0
src/components/Charts/equal.js

@@ -0,0 +1,17 @@
+/* eslint eqeqeq: 0 */
+
+function equal(old, target) {
+  let r = true;
+  for (const prop in old) {
+    if (typeof old[prop] === 'function' && typeof target[prop] === 'function') {
+      if (old[prop].toString() != target[prop].toString()) {
+        r = false;
+      }
+    } else if (old[prop] != target[prop]) {
+      r = false;
+    }
+  }
+  return r;
+}
+
+export default equal;

+ 40 - 10
src/components/SearchInput/index.js

@@ -3,13 +3,43 @@ import { Button, Input } from 'antd';
 
 import styles from './index.less';
 
-export default ({ onSearch = () => ({}), text = '搜索', ...reset }) => (
-  <div className={styles.search}>
-    <Input
-      placeholder="请输入"
-      size="large"
-      {...reset}
-      addonAfter={<Button onClick={onSearch} type="primary">{text}</Button>}
-    />
-  </div>
-);
+export default class SearchInput extends React.Component {
+  state = {
+    value: this.props.defaultValue,
+  }
+
+  handleOnChange = (e) => {
+    this.setState({
+      value: e.target.value,
+    });
+  }
+
+  handleOnSearch = () => {
+    if (this.props.onSearch) {
+      this.props.onSearch(this.state.value);
+    }
+  }
+
+  handleOnKey = (e) => {
+    if (e.keyCode === 13) {
+      this.handleOnSearch();
+    }
+  }
+
+  render() {
+    const { text = '搜索', reset } = this.props;
+    return (
+      <div className={styles.search}>
+        <Input
+          onKeyDown={this.handleOnKey}
+          placeholder="请输入"
+          size="large"
+          {...reset}
+          value={this.state.value}
+          onChange={this.handleOnChange}
+          addonAfter={<Button onClick={this.handleOnSearch} type="primary">{text}</Button>}
+        />
+      </div>
+    );
+  }
+}

+ 0 - 21
src/components/SearchInput/index.less

@@ -1,5 +1,4 @@
 @import "~antd/lib/style/themes/default.less";
-@import "../../utils/utils.less";
 
 .search {
   display: inline-block;
@@ -23,23 +22,3 @@
     height: 40px;
   }
 }
-
-@media screen and (max-width: @screen-sm) {
-  .search {
-    :global {
-      .ant-input-group .ant-input {
-        width: 300px;
-      }
-    }
-  }
-}
-
-@media screen and (max-width: @screen-xs) {
-  .search {
-    :global {
-      .ant-input-group .ant-input {
-        width: 200px;
-      }
-    }
-  }
-}

+ 1 - 1
src/components/TagCloud/index.js

@@ -79,7 +79,7 @@ class TagCloud extends PureComponent {
       height,
 
       // 设定文字大小配置函数(默认为12-40px的随机大小)
-      size: words => (((words.value - min) / (max - min)) * 10) + 12,
+      size: words => (((words.value - min) / (max - min)) * 12) + 6,
 
       // 设定文字内容
       text: words => words.name,

+ 2 - 1
src/layouts/PageHeaderLayout.js

@@ -1,8 +1,9 @@
 import React from 'react';
 import PageHeader from '../components/PageHeader';
 
-export default ({ children, wrapperClassName, ...restProps }) => (
+export default ({ children, wrapperClassName, top, ...restProps }) => (
   <div style={{ margin: '-24px -24px 0' }} className={wrapperClassName}>
+    {top}
     <PageHeader {...restProps} />
     {children ? <div style={{ margin: '24px 24px 0' }}>{children}</div> : null}
   </div>

+ 10 - 4
src/routes/Dashboard/Analysis.js

@@ -60,6 +60,10 @@ export default class Analysis extends Component {
     this.setState({
       rangePickerValue,
     });
+
+    this.props.dispatch({
+      type: 'chart/fetchSalesData',
+    });
   }
 
   selectDate = (type) => {
@@ -84,7 +88,7 @@ export default class Analysis extends Component {
       salesTypeData,
       salesTypeDataOnline,
       salesTypeDataOffline,
-      } = chart;
+    } = chart;
 
     const salesPieData = salesType === 'all' ?
       salesTypeData
@@ -140,6 +144,8 @@ export default class Analysis extends Component {
       },
     ];
 
+    const activeKey = currentTabKey || (offlineData[0] && offlineData[0].name);
+
     const CustomTab = ({ data, currentTabKey: currentKey }) => (
       <Row gutter={8} style={{ width: 138, margin: '8px 28px' }}>
         <Col span={12}>
@@ -152,7 +158,7 @@ export default class Analysis extends Component {
         </Col>
         <Col span={12} style={{ paddingTop: 36 }}>
           <Pie
-            animate={(currentKey === data.name)}
+            animate={false}
             color={(currentKey !== data.name) && '#99d5fd'}
             inner={0.55}
             tooltip={false}
@@ -361,13 +367,13 @@ export default class Analysis extends Component {
           style={{ marginTop: 24 }}
         >
           <Tabs
-            activeKey={currentTabKey || (offlineData[0] && offlineData[0].name)}
+            activeKey={activeKey}
             onChange={this.handleTabChange}
           >
             {
               offlineData.map(shop => (
                 <TabPane
-                  tab={<CustomTab data={shop} currentTabKey={currentTabKey} />}
+                  tab={<CustomTab data={shop} currentTabKey={activeKey} />}
                   key={shop.name}
                 >
                   <div style={{ padding: '0 24px' }}>

+ 5 - 0
src/routes/Dashboard/Analysis.less

@@ -3,8 +3,13 @@
 
 .iconGroup {
   i {
+    transition: color 0.32s;
+    color: @text-color-secondary;
     cursor: pointer;
     margin-left: 16px;
+    &:hover {
+      color: @text-color;
+    }
   }
 }
 .rankingList {

+ 6 - 51
src/routes/Dashboard/Monitor.js

@@ -3,22 +3,14 @@ import { connect } from 'dva';
 import { Row, Col, Card } from 'antd';
 import numeral from 'numeral';
 
-import { NumberInfo, MiniArea, Pie, WaterWave, Gauge } from '../../components/Charts';
+import { NumberInfo, Pie, WaterWave, Gauge } from '../../components/Charts';
 import MapChart from '../../components/MapChart';
 import TagCloud from '../../components/TagCloud';
 import Countdown from '../../components/Countdown';
-import { fixedZero } from '../../utils/utils';
+import ActiveChart from '../../components/ActiveChart';
 
 import styles from './Monitor.less';
 
-const activeData = [];
-for (let i = 0; i < 24; i += 1) {
-  activeData.push({
-    x: `${fixedZero(i)}:00`,
-    y: (i * 50) + (Math.floor(Math.random() * 200)),
-  });
-}
-
 const MapData = [];
 for (let i = 0; i < 50; i += 1) {
   MapData.push({
@@ -83,44 +75,7 @@ export default class Monitor extends PureComponent {
           </Col>
           <Col lg={8} md={24} sm={24} xs={24}>
             <Card title="活动情况预测" style={{ marginBottom: 24 }} bordered={false}>
-              <div className={styles.activeChart}>
-                <NumberInfo
-                  subTitle="目标评估"
-                  total="有望达到预期"
-                />
-                <div style={{ marginTop: 32 }}>
-                  <MiniArea
-                    line
-                    color="#5DD1DD"
-                    height={84}
-                    yAxis={{
-                      tickCount: 3,
-                      tickLine: false,
-                      labels: false,
-                      title: false,
-                      line: false,
-                    }}
-                    data={activeData}
-                  />
-                </div>
-                {
-                  activeData && (
-                    <div className={styles.activeChartGrid}>
-                      <p>{[...activeData].sort()[activeData.length - 1].y + 200} 亿元</p>
-                      <p>{[...activeData].sort()[Math.floor(activeData.length / 2)].y} 亿元</p>
-                    </div>
-                  )
-                }
-                {
-                  activeData && (
-                    <div className={styles.activeChartLegend}>
-                      <span>00:00</span>
-                      <span>{activeData[Math.floor(activeData.length / 2)].x}</span>
-                      <span>{activeData[activeData.length - 1].x}</span>
-                    </div>
-                  )
-                }
-              </div>
+              <ActiveChart />
             </Card>
             <Card
               title="券核效率"
@@ -129,7 +84,7 @@ export default class Monitor extends PureComponent {
               bordered={false}
             >
               <Gauge
-                title="跳出率"
+                title="核销率"
                 height={164}
                 percent={87}
               />
@@ -143,7 +98,7 @@ export default class Monitor extends PureComponent {
               style={{ marginBottom: 24 }}
               bordered={false}
             >
-              <Row style={{ padding: '18px 0 19px 0' }}>
+              <Row gutter={4} style={{ padding: '18px 0 19px 0' }}>
                 <Col span={8}>
                   <Pie
                     percent={28}
@@ -174,7 +129,7 @@ export default class Monitor extends PureComponent {
             </Card>
           </Col>
           <Col sm={8} xs={24} style={{ marginBottom: 24 }}>
-            <Card title="热门搜索" bordered={false}>
+            <Card title="热门搜索" bordered={false} bodyStyle={{ height: 214 }}>
               <TagCloud
                 data={tags}
                 height={161}

+ 0 - 32
src/routes/Dashboard/Monitor.less

@@ -1,38 +1,6 @@
 @import "~antd/lib/style/themes/default.less";
 @import "../../utils/utils.less";
 
-.activeChart {
-  position: relative;
-}
-.activeChartGrid {
-  p {
-    position: absolute;
-    top: 80px;
-  }
-  p:last-child {
-    top: 115px;
-  }
-}
-.activeChartLegend {
-  position: relative;
-  font-size: 0;
-  margin-top: 8px;
-  height: 20px;
-  line-height: 20px;
-  span {
-    display: inline-block;
-    font-size: 12px;
-    text-align: center;
-    width: 33.33%;
-  }
-  span:first-child {
-    text-align: left;
-  }
-  span:last-child {
-    text-align: right;
-  }
-}
-
 .mapChart {
   padding-top: 46px;
   height: 436px;

+ 1 - 1
src/routes/Dashboard/Workplace.js

@@ -144,9 +144,9 @@ export default class Workplace extends PureComponent {
 
     return (
       <PageHeaderLayout
+        top={pageHeaderContent}
         action={pageHeaderAction}
         title={pageHeaderTitle}
-        content={pageHeaderContent}
       >
         <Row gutter={24}>
           <Col lg={16} md={24} sm={24} xs={24}>

+ 17 - 13
src/routes/List/CardList.js

@@ -1,5 +1,6 @@
 import React, { PureComponent } from 'react';
 import { connect } from 'dva';
+import { Link } from 'dva/router';
 import { Card, Avatar, Button, Icon, List } from 'antd';
 
 import PageHeaderLayout from '../../layouts/PageHeaderLayout';
@@ -65,19 +66,22 @@ export default class CardList extends PureComponent {
             {
               list && list.map(item => (
                 <List.Item key={item.id}>
-                  <Card
-                    actions={[<a>操作一</a>, <a>操作二</a>]}
-                  >
-                    <Card.Meta
-                      avatar={<Avatar size="large" src={item.avatar} />}
-                      title={item.title}
-                      description={(
-                        <p className={styles.cardDescription}>
-                          <span>{item.description}</span>
-                        </p>
-                      )}
-                    />
-                  </Card>
+                  <Link to="/list/card-list">
+                    <Card
+                      noHovering={false}
+                      actions={[<a>操作一</a>, <a>操作二</a>]}
+                    >
+                      <Card.Meta
+                        avatar={<Avatar size="large" src={item.avatar} />}
+                        title={item.title}
+                        description={(
+                          <p className={styles.cardDescription}>
+                            <span>{item.description}</span>
+                          </p>
+                        )}
+                      />
+                    </Card>
+                  </Link>
                 </List.Item>
               ))
             }

+ 1 - 0
src/routes/List/CardList.less

@@ -31,6 +31,7 @@
 
 .cardDescription {
   .textOverflowMulti();
+  color: @text-color;
 }
 
 .pageHeaderContent {

+ 8 - 2
src/routes/List/CoverCardList.js

@@ -161,9 +161,15 @@ export default class CoverCardList extends PureComponent {
                       <TagOption value="cat2">类目二</TagOption>
                       <TagOption value="cat3">类目三</TagOption>
                       <TagOption value="cat4">类目四</TagOption>
+                      <TagOption value="cat5">类目五</TagOption>
+                      <TagOption value="cat6">类目六</TagOption>
+                      <TagOption value="cat7">类目七</TagOption>
+                      <TagOption value="cat8">类目八</TagOption>
+                      <TagOption value="cat9">类目九</TagOption>
+                      <TagOption value="cat10">类目十</TagOption>
                       <TagExpand>
-                        <TagOption value="cat5">类目五</TagOption>
-                        <TagOption value="cat6">类目六</TagOption>
+                        <TagOption value="cat11">类目十一</TagOption>
+                        <TagOption value="cat12">类目十二</TagOption>
                       </TagExpand>
                     </TagSelect>
                   )}

+ 16 - 10
src/routes/List/FilterCardList.js

@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
 import numeral from 'numeral';
 import { connect } from 'dva';
 import { routerRedux } from 'dva/router';
-import { Row, Col, Form, Card, Select, Spin, Icon, Avatar } from 'antd';
+import { Row, Col, Form, Card, Select, Icon, Avatar, List, Tooltip } from 'antd';
 
 import PageHeaderLayout from '../../layouts/PageHeaderLayout';
 import StandardFormRow from '../../components/StandardFormRow';
@@ -199,15 +199,21 @@ export default class FilterCardList extends PureComponent {
               </StandardFormRow>
             </Form>
           </Card>
-          <Row gutter={16} style={{ marginTop: 16 }}>
-            {
-              loading && <Spin />
-            }
+          <List
+            style={{ marginTop: 16 }}
+            grid={{ gutter: 16, xl: 4, lg: 3, md: 3, sm: 2, xs: 1 }}
+            loading={loading}
+          >
             {
-              !loading && list && list.map(item => (
-                <Col lg={6} md={8} sm={12} xs={24} style={{ marginBottom: 16 }} key={item.id}>
+              list && list.map(item => (
+                <List.Item key={item.id}>
                   <Card
-                    actions={[<Icon type="copy" />, <Icon type="solution" />, <Icon type="setting" />, <Icon type="delete" />]}
+                    actions={[
+                      <Tooltip title="复制"><Icon type="copy" /></Tooltip>,
+                      <Tooltip title="用户"><Icon type="solution" /></Tooltip>,
+                      <Tooltip title="设置"><Icon type="setting" /></Tooltip>,
+                      <Tooltip title="删除"><Icon type="delete" /></Tooltip>,
+                    ]}
                   >
                     <Card.Meta
                       avatar={<Avatar size="large" src={item.avatar} />}
@@ -220,10 +226,10 @@ export default class FilterCardList extends PureComponent {
                       />
                     </div>
                   </Card>
-                </Col>
+                </List.Item>
               ))
             }
-          </Row>
+          </List>
         </div>
       </PageHeaderLayout>
     );