Browse Source

图片管理、单图上传、多图上传功能模块

zhanghe 6 years ago
commit
2369ea5998
100 changed files with 5547 additions and 0 deletions
  1. 16 0
      .editorconfig
  2. 67 0
      .eslintrc
  3. 3 0
      .ga
  4. 23 0
      .gitignore
  5. 136 0
      .roadhogrc.mock.js
  6. 26 0
      .stylelintrc
  7. 22 0
      .webpackrc
  8. 90 0
      README.md
  9. 0 0
      mock/.gitkeep
  10. 295 0
      mock/api.js
  11. 197 0
      mock/chart.js
  12. 99 0
      mock/notices.js
  13. 158 0
      mock/profile.js
  14. 131 0
      mock/rule.js
  15. 91 0
      package.json
  16. BIN
      public/favicon.png
  17. BIN
      src/assets/default_handsome.jpg
  18. BIN
      src/assets/logo.jpg
  19. 43 0
      src/assets/logo.svg
  20. 230 0
      src/common/menu.js
  21. 216 0
      src/common/router.js
  22. 82 0
      src/components/ActiveChart/index.js
  23. 31 0
      src/components/ActiveChart/index.less
  24. 12 0
      src/components/Authorized/Authorized.js
  25. 35 0
      src/components/Authorized/AuthorizedRoute.js
  26. 69 0
      src/components/Authorized/CheckPermissions.js
  27. 45 0
      src/components/Authorized/CheckPermissions.test.js
  28. 59 0
      src/components/Authorized/PromiseRender.js
  29. 57 0
      src/components/Authorized/Secured.js
  30. 23 0
      src/components/Authorized/demo/AuthorizedArray.md
  31. 31 0
      src/components/Authorized/demo/AuthorizedFunction.md
  32. 25 0
      src/components/Authorized/demo/basic.md
  33. 28 0
      src/components/Authorized/demo/secured.md
  34. 48 0
      src/components/Authorized/index.d.ts
  35. 32 0
      src/components/Authorized/index.js
  36. 58 0
      src/components/Authorized/index.md
  37. 77 0
      src/components/AvatarIcon/index.js
  38. 47 0
      src/components/AvatarIcon/index.less
  39. 20 0
      src/components/AvatarList/demo/simple.md
  40. 23 0
      src/components/AvatarList/index.d.ts
  41. 22 0
      src/components/AvatarList/index.en-US.md
  42. 43 0
      src/components/AvatarList/index.js
  43. 45 0
      src/components/AvatarList/index.less
  44. 23 0
      src/components/AvatarList/index.zh-CN.md
  45. 39 0
      src/components/CardList/index.js
  46. 15 0
      src/components/Charts/Bar/index.d.ts
  47. 113 0
      src/components/Charts/Bar/index.js
  48. 12 0
      src/components/Charts/ChartCard/index.d.ts
  49. 60 0
      src/components/Charts/ChartCard/index.js
  50. 78 0
      src/components/Charts/ChartCard/index.less
  51. 8 0
      src/components/Charts/Field/index.d.ts
  52. 12 0
      src/components/Charts/Field/index.js
  53. 16 0
      src/components/Charts/Field/index.less
  54. 11 0
      src/components/Charts/Gauge/index.d.ts
  55. 167 0
      src/components/Charts/Gauge/index.js
  56. 29 0
      src/components/Charts/MiniArea/index.d.ts
  57. 106 0
      src/components/Charts/MiniArea/index.js
  58. 12 0
      src/components/Charts/MiniBar/index.d.ts
  59. 50 0
      src/components/Charts/MiniBar/index.js
  60. 13 0
      src/components/Charts/MiniProgress/index.d.ts
  61. 30 0
      src/components/Charts/MiniProgress/index.js
  62. 35 0
      src/components/Charts/MiniProgress/index.less
  63. 20 0
      src/components/Charts/Pie/index.d.ts
  64. 256 0
      src/components/Charts/Pie/index.js
  65. 94 0
      src/components/Charts/Pie/index.less
  66. 15 0
      src/components/Charts/Radar/index.d.ts
  67. 187 0
      src/components/Charts/Radar/index.js
  68. 46 0
      src/components/Charts/Radar/index.less
  69. 11 0
      src/components/Charts/TagCloud/index.d.ts
  70. 164 0
      src/components/Charts/TagCloud/index.js
  71. 7 0
      src/components/Charts/TagCloud/index.less
  72. 17 0
      src/components/Charts/TimelineChart/index.d.ts
  73. 123 0
      src/components/Charts/TimelineChart/index.js
  74. 3 0
      src/components/Charts/TimelineChart/index.less
  75. 10 0
      src/components/Charts/WaterWave/index.d.ts
  76. 197 0
      src/components/Charts/WaterWave/index.js
  77. 28 0
      src/components/Charts/WaterWave/index.less
  78. 63 0
      src/components/Charts/autoHeight.js
  79. 26 0
      src/components/Charts/demo/bar.md
  80. 65 0
      src/components/Charts/demo/chart-card.md
  81. 18 0
      src/components/Charts/demo/gauge.md
  82. 28 0
      src/components/Charts/demo/mini-area.md
  83. 28 0
      src/components/Charts/demo/mini-bar.md
  84. 16 0
      src/components/Charts/demo/mini-pie.md
  85. 12 0
      src/components/Charts/demo/mini-progress.md
  86. 83 0
      src/components/Charts/demo/mix.md
  87. 47 0
      src/components/Charts/demo/pie.md
  88. 64 0
      src/components/Charts/demo/radar.md
  89. 25 0
      src/components/Charts/demo/tag-cloud.md
  90. 27 0
      src/components/Charts/demo/timeline-chart.md
  91. 20 0
      src/components/Charts/demo/waterwave.md
  92. 15 0
      src/components/Charts/g2.js
  93. 17 0
      src/components/Charts/index.d.ts
  94. 32 0
      src/components/Charts/index.js
  95. 19 0
      src/components/Charts/index.less
  96. 132 0
      src/components/Charts/index.md
  97. 24 0
      src/components/CountDown/demo/simple.md
  98. 9 0
      src/components/CountDown/index.d.ts
  99. 15 0
      src/components/CountDown/index.en-US.md
  100. 0 0
      src/components/CountDown/index.js

+ 16 - 0
.editorconfig

@@ -0,0 +1,16 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[Makefile]
+indent_style = tab

+ 67 - 0
.eslintrc

@@ -0,0 +1,67 @@
+{
+  "parser": "babel-eslint",
+  "extends": "airbnb",
+  "plugins": ["compat"],
+  "env": {
+    "browser": true,
+    "node": true,
+    "es6": true,
+    "mocha": true,
+    "jest": true,
+    "jasmine": true
+  },
+  "rules": {
+    "generator-star-spacing": [0],
+    "consistent-return": [0],
+    "react/forbid-prop-types": [0],
+    "react/jsx-filename-extension": [1, { "extensions": [".js"] }],
+    "global-require": [1],
+    "import/prefer-default-export": [0],
+    "react/jsx-no-bind": [0],
+    "react/prop-types": [0],
+    "react/prefer-stateless-function": [0],
+    "react/jsx-wrap-multilines": ["error", {
+      "declaration": "parens-new-line",
+      "assignment": "parens-new-line",
+      "return": "parens-new-line",
+      "arrow": "parens-new-line",
+      "condition": "parens-new-line",
+      "logical": "parens-new-line",
+      "prop": "ignore"
+    }],
+    "no-else-return": [0],
+    "no-restricted-syntax": [0],
+    "import/no-extraneous-dependencies": [0],
+    "no-use-before-define": [0],
+    "jsx-a11y/no-static-element-interactions": [0],
+    "jsx-a11y/no-noninteractive-element-interactions": [0],
+    "jsx-a11y/click-events-have-key-events": [0],
+    "jsx-a11y/anchor-is-valid": [0],
+    "no-nested-ternary": [0],
+    "arrow-body-style": [0],
+    "import/extensions": [0],
+    "no-bitwise": [0],
+    "no-cond-assign": [0],
+    "import/no-unresolved": [0],
+    "comma-dangle": ["error", {
+      "arrays": "always-multiline",
+      "objects": "always-multiline",
+      "imports": "always-multiline",
+      "exports": "always-multiline",
+      "functions": "ignore"
+    }],
+    "object-curly-newline": [0],
+    "function-paren-newline": [0],
+    "no-restricted-globals": [0],
+    "require-yield": [1],
+    "compat/compat": "error"
+  },
+  "parserOptions": {
+    "ecmaFeatures": {
+      "experimentalObjectRestSpread": true
+    }
+  },
+  "settings": {
+    "polyfills": ["fetch", "promises"]
+  }
+}

+ 3 - 0
.ga

@@ -0,0 +1,3 @@
+{
+    "code":"UA-72788897-6"
+} 

+ 23 - 0
.gitignore

@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+# roadhog-api-doc ignore
+/src/utils/request-temp.js
+_roadhog-api-doc
+
+# production
+/dist
+
+# misc
+.DS_Store
+npm-debug.log*
+yarn-error.log
+
+/coverage
+.idea
+yarn.lock
+package-lock.json
+*bak
+jsconfig.json
+.prettierrc

+ 136 - 0
.roadhogrc.mock.js

@@ -0,0 +1,136 @@
+import mockjs from 'mockjs';
+import { getRule, postRule } from './mock/rule';
+import { getActivities, getNotice, getFakeList } from './mock/api';
+import { getFakeChartData } from './mock/chart';
+import { getProfileBasicData } from './mock/profile';
+import { getProfileAdvancedData } from './mock/profile';
+import { getNotices } from './mock/notices';
+import { format, delay } from 'roadhog-api-doc';
+
+// 是否禁用代理
+const noProxy = process.env.NO_PROXY === 'true';
+
+// 代码中会兼容本地 service mock 以及部署站点的静态数据
+const proxy = {
+  // 支持值为 Object 和 Array
+  'GET /api/currentUser': {
+    $desc: "获取当前用户接口",
+    $params: {
+      pageSize: {
+        desc: '分页',
+        exp: 2,
+      },
+    },
+    $body: {
+      name: 'Serati Ma',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
+      userid: '00000001',
+      notifyCount: 12,
+    },
+  },
+  // GET POST 可省略
+  'GET /api/users': [{
+    key: '1',
+    name: 'John Brown',
+    age: 32,
+    address: 'New York No. 1 Lake Park',
+  }, {
+    key: '2',
+    name: 'Jim Green',
+    age: 42,
+    address: 'London No. 1 Lake Park',
+  }, {
+    key: '3',
+    name: 'Joe Black',
+    age: 32,
+    address: 'Sidney No. 1 Lake Park',
+  }],
+  'GET /api/project/notice': getNotice,
+  'GET /api/activities': getActivities,
+  'GET /api/rule': getRule,
+  'POST /api/rule': {
+    $params: {
+      pageSize: {
+        desc: '分页',
+        exp: 2,
+      },
+    },
+    $body: postRule,
+  },
+  'POST /api/forms': (req, res) => {
+    res.send({ message: 'Ok' });
+  },
+  'GET /api/tags': mockjs.mock({
+    'list|100': [{ name: '@city', 'value|1-100': 150, 'type|0-2': 1 }]
+  }),
+  'GET /api/fake_list': getFakeList,
+  'GET /api/fake_chart_data': getFakeChartData,
+  'GET /api/profile/basic': getProfileBasicData,
+  'GET /api/profile/advanced': getProfileAdvancedData,
+  'POST /api/login/account': (req, res) => {
+    const { password, username, type } = req.body;
+    if(password === '888888' && username === 'admin'){
+      res.send({
+        status: 'ok',
+        type,
+        currentAuthority: 'admin'
+      });
+      return ;
+    }
+    if(password === '123456' && username === 'user'){
+      res.send({
+        status: 'ok',
+        type,
+        currentAuthority: 'user'
+      });
+      return ;
+    }
+    res.send({
+      status: 'error',
+      type,
+      currentAuthority: 'guest'
+    });
+  },
+  'POST /api/register': (req, res) => {
+    res.send({ status: 'ok', currentAuthority: 'user' });
+  },
+  'GET /api/notices': getNotices,
+  'GET /api/500': (req, res) => {
+    res.status(500).send({
+      "timestamp": 1513932555104,
+      "status": 500,
+      "error": "error",
+      "message": "error",
+      "path": "/base/category/list"
+    });
+  },
+  'GET /api/404': (req, res) => {
+    res.status(404).send({
+      "timestamp": 1513932643431,
+      "status": 404,
+      "error": "Not Found",
+      "message": "No message available",
+      "path": "/base/category/list/2121212"
+    });
+  },
+  'GET /api/403': (req, res) => {
+    res.status(403).send({
+      "timestamp": 1513932555104,
+      "status": 403,
+      "error": "Unauthorized",
+      "message": "Unauthorized",
+      "path": "/base/category/list"
+    });
+  },
+  'GET /api/401': (req, res) => {
+    res.status(401).send({
+      "timestamp": 1513932555104,
+      "status": 401,
+      "error": "Unauthorized",
+      "message": "Unauthorized",
+      "path": "/base/category/list"
+    });
+  },
+};
+
+export default noProxy ? {} : delay(proxy, 1000);

+ 26 - 0
.stylelintrc

@@ -0,0 +1,26 @@
+{
+  "extends": "stylelint-config-standard",
+  "rules": {
+    "selector-pseudo-class-no-unknown": null,
+    "shorthand-property-no-redundant-values": null,
+    "at-rule-empty-line-before": null,
+    "at-rule-name-space-after": null,
+    "comment-empty-line-before": null,
+    "declaration-bang-space-before": null,
+    "declaration-empty-line-before": null,
+    "function-comma-newline-after": null,
+    "function-name-case": null,
+    "function-parentheses-newline-inside": null,
+    "function-max-empty-lines": null,
+    "function-whitespace-after": null,
+    "number-leading-zero": null,
+    "number-no-trailing-zeros": null,
+    "rule-empty-line-before": null,
+    "selector-combinator-space-after": null,
+    "selector-list-comma-newline-after": null,
+    "selector-pseudo-element-colon-notation": null,
+    "unit-no-unknown": null,
+    "no-descending-specificity": null,
+    "value-list-max-empty-lines": null
+  }
+}

+ 22 - 0
.webpackrc

@@ -0,0 +1,22 @@
+{
+  "entry": "src/index.js",
+  "extraBabelPlugins": [
+    "transform-decorators-legacy",
+    ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": true }]
+  ],
+  "env": {
+    "development": {
+      "extraBabelPlugins": [
+        "dva-hmr"
+      ]
+    }
+  },
+  "ignoreMomentLocale": true,
+  "theme": "./src/theme.js",
+  "html": {
+    "template": "./src/index.ejs"
+  },
+  "publicPath": "/",
+  "disableDynamicImport": true,
+  "hash": true
+}

+ 90 - 0
README.md

@@ -0,0 +1,90 @@
+[English](./README.md) | 简体中文
+
+# Ant Design Pro
+
+[![](https://img.shields.io/travis/ant-design/ant-design-pro.svg?style=flat-square)](https://travis-ci.org/ant-design/ant-design-pro) [![Build status](https://ci.appveyor.com/api/projects/status/67fxu2by3ibvqtat/branch/master?svg=true)](https://ci.appveyor.com/project/afc163/ant-design-pro/branch/master)  [![Gitter](https://badges.gitter.im/ant-design/ant-design-pro.svg)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
+
+开箱即用的中台前端/设计解决方案。
+
+![](https://gw.alipayobjects.com/zos/rmsportal/xEdBqwSzvoSapmnSnYjU.png)
+
+- 预览:http://preview.pro.ant.design
+- 首页:http://pro.ant.design/index-cn
+- 使用文档:http://pro.ant.design/docs/getting-started-cn
+- 更新日志: http://pro.ant.design/docs/changelog-cn
+- 常见问题:http://pro.ant.design/docs/faq-cn
+
+## 特性
+
+- :gem: **优雅美观**:基于 Ant Design 体系精心设计
+- :triangular_ruler: **常见设计模式**:提炼自中后台应用的典型页面和场景
+- :rocket: **最新技术栈**:使用 React/dva/antd 等前端前沿技术开发
+- :iphone: **响应式**:针对不同屏幕大小设计
+- :art: **主题**:可配置的主题满足多样化的品牌诉求
+- :globe_with_meridians: **国际化**:内建业界通用的国际化方案
+- :gear: **最佳实践**:良好的工程实践助您持续产出高质量代码
+- :1234: **Mock 数据**:实用的本地数据调试方案
+- :white_check_mark: **UI 测试**:自动化测试保障前端产品质量
+
+## 模板
+
+```
+- Dashboard
+  - 分析页
+  - 监控页
+  - 工作台
+- 表单页
+  - 基础表单页
+  - 分步表单页
+  - 高级表单页
+- 列表页
+  - 查询表格
+  - 标准列表
+  - 卡片列表
+  - 搜索列表(项目/应用/文章)
+- 详情页
+  - 基础详情页
+  - 高级详情页
+- 结果
+  - 成功页
+  - 失败页
+- 异常
+  - 403 无权限
+  - 404 找不到
+  - 500 服务器出错
+- 帐户
+  - 登录
+  - 注册
+  - 注册成功
+```
+
+## 使用
+
+```bash
+$ git clone https://github.com/ant-design/ant-design-pro.git --depth=1
+$ cd ant-design-pro
+$ npm install
+$ npm start         # 访问 http://localhost:8000
+```
+
+也可以使用集成化的 [ant-design-pro-cli](https://github.com/ant-design/ant-design-pro-cli) 工具。
+
+```bash
+$ npm install ant-design-pro-cli -g
+$ mkdir pro-demo && cd pro-demo
+$ pro new
+```
+
+更多信息请参考 [使用文档](http://pro.ant.design/docs/getting-started)。
+
+## 兼容性
+
+现代浏览器及 IE11。
+
+## 参与贡献
+
+我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :smiley::
+
+- 在你的公司或个人项目中使用 Ant Design Pro。
+- 通过 [Issue](http://github.com/ant-design/ant-design-pro/issues) 报告 bug 或进行咨询。
+- 提交 [Pull Request](http://github.com/ant-design/ant-design-pro/pulls) 改进 Pro 的代码。

+ 0 - 0
mock/.gitkeep


+ 295 - 0
mock/api.js

@@ -0,0 +1,295 @@
+import { parse } from 'url';
+
+const titles = [
+  'Alipay',
+  'Angular',
+  'Ant Design',
+  'Ant Design Pro',
+  'Bootstrap',
+  'React',
+  'Vue',
+  'Webpack',
+];
+const avatars = [
+  'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', // Alipay
+  'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', // Angular
+  'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', // Ant Design
+  'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', // Ant Design Pro
+  'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', // Bootstrap
+  'https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png', // React
+  'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png', // Vue
+  'https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png', // Webpack
+];
+
+const avatars2 = [
+  'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
+  'https://gw.alipayobjects.com/zos/rmsportal/cnrhVkzwxjPwAaCfPbdc.png',
+  'https://gw.alipayobjects.com/zos/rmsportal/gaOngJwsRYRaVAuXXcmB.png',
+  'https://gw.alipayobjects.com/zos/rmsportal/ubnKSIfAJTxIgXOKlciN.png',
+  'https://gw.alipayobjects.com/zos/rmsportal/WhxKECPNujWoWEFNdnJE.png',
+  'https://gw.alipayobjects.com/zos/rmsportal/jZUIxmJycoymBprLOUbT.png',
+  'https://gw.alipayobjects.com/zos/rmsportal/psOgztMplJMGpVEqfcgF.png',
+  'https://gw.alipayobjects.com/zos/rmsportal/ZpBqSxLxVEXfcUNoPKrz.png',
+  'https://gw.alipayobjects.com/zos/rmsportal/laiEnJdGHVOhJrUShBaJ.png',
+  'https://gw.alipayobjects.com/zos/rmsportal/UrQsqscbKEpNuJcvBZBu.png',
+];
+
+const covers = [
+  'https://gw.alipayobjects.com/zos/rmsportal/uMfMFlvUuceEyPpotzlq.png',
+  'https://gw.alipayobjects.com/zos/rmsportal/iZBVOIhGJiAnhplqjvZW.png',
+  'https://gw.alipayobjects.com/zos/rmsportal/uVZonEtjWwmUZPBQfycs.png',
+  'https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png',
+];
+const desc = [
+  '那是一种内在的东西, 他们到达不了,也无法触及的',
+  '希望是一个好东西,也许是最好的,好东西是不会消亡的',
+  '生命就像一盒巧克力,结果往往出人意料',
+  '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆',
+  '那时候我只会想自己想要什么,从不想自己拥有什么',
+];
+
+const user = [
+  '付小小',
+  '曲丽丽',
+  '林东东',
+  '周星星',
+  '吴加好',
+  '朱偏右',
+  '鱼酱',
+  '乐哥',
+  '谭小仪',
+  '仲尼',
+];
+
+export function fakeList(count) {
+  const list = [];
+  for (let i = 0; i < count; i += 1) {
+    list.push({
+      id: `fake-list-${i}`,
+      owner: user[i % 10],
+      title: titles[i % 8],
+      avatar: avatars[i % 8],
+      cover: parseInt(i / 4, 10) % 2 === 0 ? covers[i % 4] : covers[3 - (i % 4)],
+      status: ['active', 'exception', 'normal'][i % 3],
+      percent: Math.ceil(Math.random() * 50) + 50,
+      logo: avatars[i % 8],
+      href: 'https://ant.design',
+      updatedAt: new Date(new Date().getTime() - (1000 * 60 * 60 * 2 * i)),
+      createdAt: new Date(new Date().getTime() - (1000 * 60 * 60 * 2 * i)),
+      subDescription: desc[i % 5],
+      description: '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。',
+      activeUser: Math.ceil(Math.random() * 100000) + 100000,
+      newUser: Math.ceil(Math.random() * 1000) + 1000,
+      star: Math.ceil(Math.random() * 100) + 100,
+      like: Math.ceil(Math.random() * 100) + 100,
+      message: Math.ceil(Math.random() * 10) + 10,
+      content: '段落示意:蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。',
+      members: [
+        {
+          avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png',
+          name: '曲丽丽',
+        },
+        {
+          avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png',
+          name: '王昭君',
+        },
+        {
+          avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png',
+          name: '董娜娜',
+        },
+      ],
+    });
+  }
+
+  return list;
+}
+
+export function getFakeList(req, res, u) {
+  let url = u;
+  if (!url || Object.prototype.toString.call(url) !== '[object String]') {
+    url = req.url; // eslint-disable-line
+  }
+
+  const params = parse(url, true).query;
+
+  const count = (params.count * 1) || 20;
+
+  const result = fakeList(count);
+
+  if (res && res.json) {
+    res.json(result);
+  } else {
+    return result;
+  }
+}
+
+export const getNotice = [
+  {
+    id: 'xxx1',
+    title: titles[0],
+    logo: avatars[0],
+    description: '那是一种内在的东西,他们到达不了,也无法触及的',
+    updatedAt: new Date(),
+    member: '科学搬砖组',
+    href: '',
+    memberLink: '',
+  },
+  {
+    id: 'xxx2',
+    title: titles[1],
+    logo: avatars[1],
+    description: '希望是一个好东西,也许是最好的,好东西是不会消亡的',
+    updatedAt: new Date('2017-07-24'),
+    member: '全组都是吴彦祖',
+    href: '',
+    memberLink: '',
+  },
+  {
+    id: 'xxx3',
+    title: titles[2],
+    logo: avatars[2],
+    description: '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆',
+    updatedAt: new Date(),
+    member: '中二少女团',
+    href: '',
+    memberLink: '',
+  },
+  {
+    id: 'xxx4',
+    title: titles[3],
+    logo: avatars[3],
+    description: '那时候我只会想自己想要什么,从不想自己拥有什么',
+    updatedAt: new Date('2017-07-23'),
+    member: '程序员日常',
+    href: '',
+    memberLink: '',
+  },
+  {
+    id: 'xxx5',
+    title: titles[4],
+    logo: avatars[4],
+    description: '凛冬将至',
+    updatedAt: new Date('2017-07-23'),
+    member: '高逼格设计天团',
+    href: '',
+    memberLink: '',
+  },
+  {
+    id: 'xxx6',
+    title: titles[5],
+    logo: avatars[5],
+    description: '生命就像一盒巧克力,结果往往出人意料',
+    updatedAt: new Date('2017-07-23'),
+    member: '骗你来学计算机',
+    href: '',
+    memberLink: '',
+  },
+];
+
+export const getActivities = [
+  {
+    id: 'trend-1',
+    updatedAt: new Date(),
+    user: {
+      name: '曲丽丽',
+      avatar: avatars2[0],
+    },
+    group: {
+      name: '高逼格设计天团',
+      link: 'http://github.com/',
+    },
+    project: {
+      name: '六月迭代',
+      link: 'http://github.com/',
+    },
+    template: '在 @{group} 新建项目 @{project}',
+  },
+  {
+    id: 'trend-2',
+    updatedAt: new Date(),
+    user: {
+      name: '付小小',
+      avatar: avatars2[1],
+    },
+    group: {
+      name: '高逼格设计天团',
+      link: 'http://github.com/',
+    },
+    project: {
+      name: '六月迭代',
+      link: 'http://github.com/',
+    },
+    template: '在 @{group} 新建项目 @{project}',
+  },
+  {
+    id: 'trend-3',
+    updatedAt: new Date(),
+    user: {
+      name: '林东东',
+      avatar: avatars2[2],
+    },
+    group: {
+      name: '中二少女团',
+      link: 'http://github.com/',
+    },
+    project: {
+      name: '六月迭代',
+      link: 'http://github.com/',
+    },
+    template: '在 @{group} 新建项目 @{project}',
+  },
+  {
+    id: 'trend-4',
+    updatedAt: new Date(),
+    user: {
+      name: '周星星',
+      avatar: avatars2[4],
+    },
+    project: {
+      name: '5 月日常迭代',
+      link: 'http://github.com/',
+    },
+    template: '将 @{project} 更新至已发布状态',
+  },
+  {
+    id: 'trend-5',
+    updatedAt: new Date(),
+    user: {
+      name: '朱偏右',
+      avatar: avatars2[3],
+    },
+    project: {
+      name: '工程效能',
+      link: 'http://github.com/',
+    },
+    comment: {
+      name: '留言',
+      link: 'http://github.com/',
+    },
+    template: '在 @{project} 发布了 @{comment}',
+  },
+  {
+    id: 'trend-6',
+    updatedAt: new Date(),
+    user: {
+      name: '乐哥',
+      avatar: avatars2[5],
+    },
+    group: {
+      name: '程序员日常',
+      link: 'http://github.com/',
+    },
+    project: {
+      name: '品牌迭代',
+      link: 'http://github.com/',
+    },
+    template: '在 @{group} 新建项目 @{project}',
+  },
+];
+
+
+export default {
+  getNotice,
+  getActivities,
+  getFakeList,
+};

+ 197 - 0
mock/chart.js

@@ -0,0 +1,197 @@
+import moment from 'moment';
+
+// mock data
+const visitData = [];
+const beginDay = new Date().getTime();
+
+const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5];
+for (let i = 0; i < fakeY.length; i += 1) {
+  visitData.push({
+    x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'),
+    y: fakeY[i],
+  });
+}
+
+const visitData2 = [];
+const fakeY2 = [1, 6, 4, 8, 3, 7, 2];
+for (let i = 0; i < fakeY2.length; i += 1) {
+  visitData2.push({
+    x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'),
+    y: fakeY2[i],
+  });
+}
+
+const salesData = [];
+for (let i = 0; i < 12; i += 1) {
+  salesData.push({
+    x: `${i + 1}月`,
+    y: Math.floor(Math.random() * 1000) + 200,
+  });
+}
+const searchData = [];
+for (let i = 0; i < 50; i += 1) {
+  searchData.push({
+    index: i + 1,
+    keyword: `搜索关键词-${i}`,
+    count: Math.floor(Math.random() * 1000),
+    range: Math.floor(Math.random() * 100),
+    status: Math.floor((Math.random() * 10) % 2),
+  });
+}
+const salesTypeData = [
+  {
+    x: '家用电器',
+    y: 4544,
+  },
+  {
+    x: '食用酒水',
+    y: 3321,
+  },
+  {
+    x: '个护健康',
+    y: 3113,
+  },
+  {
+    x: '服饰箱包',
+    y: 2341,
+  },
+  {
+    x: '母婴产品',
+    y: 1231,
+  },
+  {
+    x: '其他',
+    y: 1231,
+  },
+];
+
+const salesTypeDataOnline = [
+  {
+    x: '家用电器',
+    y: 244,
+  },
+  {
+    x: '食用酒水',
+    y: 321,
+  },
+  {
+    x: '个护健康',
+    y: 311,
+  },
+  {
+    x: '服饰箱包',
+    y: 41,
+  },
+  {
+    x: '母婴产品',
+    y: 121,
+  },
+  {
+    x: '其他',
+    y: 111,
+  },
+];
+
+const salesTypeDataOffline = [
+  {
+    x: '家用电器',
+    y: 99,
+  },
+  {
+    x: '个护健康',
+    y: 188,
+  },
+  {
+    x: '服饰箱包',
+    y: 344,
+  },
+  {
+    x: '母婴产品',
+    y: 255,
+  },
+  {
+    x: '其他',
+    y: 65,
+  },
+];
+
+const offlineData = [];
+for (let i = 0; i < 10; i += 1) {
+  offlineData.push({
+    name: `门店${i}`,
+    cvr: Math.ceil(Math.random() * 9) / 10,
+  });
+}
+const offlineChartData = [];
+for (let i = 0; i < 20; i += 1) {
+  offlineChartData.push({
+    x: (new Date().getTime()) + (1000 * 60 * 30 * i),
+    y1: Math.floor(Math.random() * 100) + 10,
+    y2: Math.floor(Math.random() * 100) + 10,
+  });
+}
+
+const radarOriginData = [
+  {
+    name: '个人',
+    ref: 10,
+    koubei: 8,
+    output: 4,
+    contribute: 5,
+    hot: 7,
+  },
+  {
+    name: '团队',
+    ref: 3,
+    koubei: 9,
+    output: 6,
+    contribute: 3,
+    hot: 1,
+  },
+  {
+    name: '部门',
+    ref: 4,
+    koubei: 1,
+    output: 6,
+    contribute: 5,
+    hot: 7,
+  },
+];
+
+//
+const radarData = [];
+const radarTitleMap = {
+  ref: '引用',
+  koubei: '口碑',
+  output: '产量',
+  contribute: '贡献',
+  hot: '热度',
+};
+radarOriginData.forEach((item) => {
+  Object.keys(item).forEach((key) => {
+    if (key !== 'name') {
+      radarData.push({
+        name: item.name,
+        label: radarTitleMap[key],
+        value: item[key],
+      });
+    }
+  });
+});
+
+export const getFakeChartData = {
+  visitData,
+  visitData2,
+  salesData,
+  searchData,
+  offlineData,
+  offlineChartData,
+  salesTypeData,
+  salesTypeDataOnline,
+  salesTypeDataOffline,
+  radarData,
+};
+
+export default {
+  getFakeChartData,
+};

+ 99 - 0
mock/notices.js

@@ -0,0 +1,99 @@
+export const getNotices = (req, res) => {
+  res.json([
+    {
+      id: '000000001',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
+      title: '你收到了 14 份新周报',
+      datetime: '2017-08-09',
+      type: '通知',
+    },
+    {
+      id: '000000002',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
+      title: '你推荐的 曲妮妮 已通过第三轮面试',
+      datetime: '2017-08-08',
+      type: '通知',
+    },
+    {
+      id: '000000003',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
+      title: '这种模板可以区分多种通知类型',
+      datetime: '2017-08-07',
+      read: true,
+      type: '通知',
+    },
+    {
+      id: '000000004',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
+      title: '左侧图标用于区分不同的类型',
+      datetime: '2017-08-07',
+      type: '通知',
+    },
+    {
+      id: '000000005',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
+      title: '内容不要超过两行字,超出时自动截断',
+      datetime: '2017-08-07',
+      type: '通知',
+    },
+    {
+      id: '000000006',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
+      title: '曲丽丽 评论了你',
+      description: '描述信息描述信息描述信息',
+      datetime: '2017-08-07',
+      type: '消息',
+    },
+    {
+      id: '000000007',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
+      title: '朱偏右 回复了你',
+      description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
+      datetime: '2017-08-07',
+      type: '消息',
+    },
+    {
+      id: '000000008',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
+      title: '标题',
+      description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
+      datetime: '2017-08-07',
+      type: '消息',
+    },
+    {
+      id: '000000009',
+      title: '任务名称',
+      description: '任务需要在 2017-01-12 20:00 前启动',
+      extra: '未开始',
+      status: 'todo',
+      type: '待办',
+    },
+    {
+      id: '000000010',
+      title: '第三方紧急代码变更',
+      description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
+      extra: '马上到期',
+      status: 'urgent',
+      type: '待办',
+    },
+    {
+      id: '000000011',
+      title: '信息安全考试',
+      description: '指派竹尔于 2017-01-09 前完成更新并发布',
+      extra: '已耗时 8 天',
+      status: 'doing',
+      type: '待办',
+    },
+    {
+      id: '000000012',
+      title: 'ABCD 版本发布',
+      description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
+      extra: '进行中',
+      status: 'processing',
+      type: '待办',
+    },
+  ]);
+};
+export default {
+  getNotices,
+};

+ 158 - 0
mock/profile.js

@@ -0,0 +1,158 @@
+const basicGoods = [
+  {
+    id: '1234561',
+    name: '矿泉水 550ml',
+    barcode: '12421432143214321',
+    price: '2.00',
+    num: '1',
+    amount: '2.00',
+  },
+  {
+    id: '1234562',
+    name: '凉茶 300ml',
+    barcode: '12421432143214322',
+    price: '3.00',
+    num: '2',
+    amount: '6.00',
+  },
+  {
+    id: '1234563',
+    name: '好吃的薯片',
+    barcode: '12421432143214323',
+    price: '7.00',
+    num: '4',
+    amount: '28.00',
+  },
+  {
+    id: '1234564',
+    name: '特别好吃的蛋卷',
+    barcode: '12421432143214324',
+    price: '8.50',
+    num: '3',
+    amount: '25.50',
+  },
+];
+
+const basicProgress = [
+  {
+    key: '1',
+    time: '2017-10-01 14:10',
+    rate: '联系客户',
+    status: 'processing',
+    operator: '取货员 ID1234',
+    cost: '5mins',
+  },
+  {
+    key: '2',
+    time: '2017-10-01 14:05',
+    rate: '取货员出发',
+    status: 'success',
+    operator: '取货员 ID1234',
+    cost: '1h',
+  },
+  {
+    key: '3',
+    time: '2017-10-01 13:05',
+    rate: '取货员接单',
+    status: 'success',
+    operator: '取货员 ID1234',
+    cost: '5mins',
+  },
+  {
+    key: '4',
+    time: '2017-10-01 13:00',
+    rate: '申请审批通过',
+    status: 'success',
+    operator: '系统',
+    cost: '1h',
+  },
+  {
+    key: '5',
+    time: '2017-10-01 12:00',
+    rate: '发起退货申请',
+    status: 'success',
+    operator: '用户',
+    cost: '5mins',
+  },
+];
+
+const advancedOperation1 = [
+  {
+    key: 'op1',
+    type: '订购关系生效',
+    name: '曲丽丽',
+    status: 'agree',
+    updatedAt: '2017-10-03  19:23:12',
+    memo: '-',
+  },
+  {
+    key: 'op2',
+    type: '财务复审',
+    name: '付小小',
+    status: 'reject',
+    updatedAt: '2017-10-03  19:23:12',
+    memo: '不通过原因',
+  },
+  {
+    key: 'op3',
+    type: '部门初审',
+    name: '周毛毛',
+    status: 'agree',
+    updatedAt: '2017-10-03  19:23:12',
+    memo: '-',
+  },
+  {
+    key: 'op4',
+    type: '提交订单',
+    name: '林东东',
+    status: 'agree',
+    updatedAt: '2017-10-03  19:23:12',
+    memo: '很棒',
+  },
+  {
+    key: 'op5',
+    type: '创建订单',
+    name: '汗牙牙',
+    status: 'agree',
+    updatedAt: '2017-10-03  19:23:12',
+    memo: '-',
+  },
+];
+
+const advancedOperation2 = [
+  {
+    key: 'op1',
+    type: '订购关系生效',
+    name: '曲丽丽',
+    status: 'agree',
+    updatedAt: '2017-10-03  19:23:12',
+    memo: '-',
+  },
+];
+
+const advancedOperation3 = [
+  {
+    key: 'op1',
+    type: '创建订单',
+    name: '汗牙牙',
+    status: 'agree',
+    updatedAt: '2017-10-03  19:23:12',
+    memo: '-',
+  },
+];
+
+export const getProfileBasicData = {
+  basicGoods,
+  basicProgress,
+};
+
+export const getProfileAdvancedData = {
+  advancedOperation1,
+  advancedOperation2,
+  advancedOperation3,
+};
+
+export default {
+  getProfileBasicData,
+  getProfileAdvancedData,
+};

+ 131 - 0
mock/rule.js

@@ -0,0 +1,131 @@
+import { parse } from 'url';
+
+// mock tableListDataSource
+let tableListDataSource = [];
+for (let i = 0; i < 46; i += 1) {
+  tableListDataSource.push({
+    key: i,
+    disabled: ((i % 6) === 0),
+    href: 'https://ant.design',
+    avatar: ['https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png'][i % 2],
+    no: `TradeCode ${i}`,
+    title: `一个任务名称 ${i}`,
+    owner: '曲丽丽',
+    description: '这是一段描述',
+    callNo: Math.floor(Math.random() * 1000),
+    status: Math.floor(Math.random() * 10) % 4,
+    updatedAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`),
+    createdAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`),
+    progress: Math.ceil(Math.random() * 100),
+  });
+}
+
+export function getRule(req, res, u) {
+  let url = u;
+  if (!url || Object.prototype.toString.call(url) !== '[object String]') {
+    url = req.url; // eslint-disable-line
+  }
+
+  const params = parse(url, true).query;
+
+  let dataSource = [...tableListDataSource];
+
+  if (params.sorter) {
+    const s = params.sorter.split('_');
+    dataSource = dataSource.sort((prev, next) => {
+      if (s[1] === 'descend') {
+        return next[s[0]] - prev[s[0]];
+      }
+      return prev[s[0]] - next[s[0]];
+    });
+  }
+
+  if (params.status) {
+    const status = params.status.split(',');
+    let filterDataSource = [];
+    status.forEach((s) => {
+      filterDataSource = filterDataSource.concat(
+        [...dataSource].filter(data => parseInt(data.status, 10) === parseInt(s[0], 10))
+      );
+    });
+    dataSource = filterDataSource;
+  }
+
+  if (params.no) {
+    dataSource = dataSource.filter(data => data.no.indexOf(params.no) > -1);
+  }
+
+  let pageSize = 10;
+  if (params.pageSize) {
+    pageSize = params.pageSize * 1;
+  }
+
+  const result = {
+    list: dataSource,
+    pagination: {
+      total: dataSource.length,
+      pageSize,
+      current: parseInt(params.currentPage, 10) || 1,
+    },
+  };
+
+  if (res && res.json) {
+    res.json(result);
+  } else {
+    return result;
+  }
+}
+
+export function postRule(req, res, u, b) {
+  let url = u;
+  if (!url || Object.prototype.toString.call(url) !== '[object String]') {
+    url = req.url; // eslint-disable-line
+  }
+
+  const body = (b && b.body) || req.body;
+  const { method, no, description } = body;
+
+  switch (method) {
+    /* eslint no-case-declarations:0 */
+    case 'delete':
+      tableListDataSource = tableListDataSource.filter(item => no.indexOf(item.no) === -1);
+      break;
+    case 'post':
+      const i = Math.ceil(Math.random() * 10000);
+      tableListDataSource.unshift({
+        key: i,
+        href: 'https://ant.design',
+        avatar: ['https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png'][i % 2],
+        no: `TradeCode ${i}`,
+        title: `一个任务名称 ${i}`,
+        owner: '曲丽丽',
+        description,
+        callNo: Math.floor(Math.random() * 1000),
+        status: Math.floor(Math.random() * 10) % 2,
+        updatedAt: new Date(),
+        createdAt: new Date(),
+        progress: Math.ceil(Math.random() * 100),
+      });
+      break;
+    default:
+      break;
+  }
+
+  const result = {
+    list: tableListDataSource,
+    pagination: {
+      total: tableListDataSource.length,
+    },
+  };
+
+  if (res && res.json) {
+    res.json(result);
+  } else {
+    return result;
+  }
+}
+
+export default {
+  getRule,
+  postRule,
+};

+ 91 - 0
package.json

@@ -0,0 +1,91 @@
+{
+  "name": "ant-design-pro",
+  "version": "1.1.0",
+  "description": "An out-of-box UI solution for enterprise applications",
+  "private": true,
+  "scripts": {
+    "precommit": "npm run lint-staged",
+    "start": "cross-env DISABLE_ESLINT=true roadhog dev",
+    "start:no-proxy": "cross-env NO_PROXY=true DISABLE_ESLINT=true roadhog dev",
+    "build": "cross-env DISABLE_ESLINT=true roadhog build",
+    "site": "roadhog-api-doc static && gh-pages -d dist",
+    "analyze": "cross-env ANALYZE=true roadhog build",
+    "lint:style": "stylelint \"src/**/*.less\" --syntax less",
+    "lint": "eslint --ext .js src mock tests && npm run lint:style",
+    "lint:fix": "eslint --fix --ext .js src mock tests && npm run lint:style",
+    "lint-staged": "lint-staged",
+    "lint-staged:js": "eslint --ext .js",
+    "test": "roadhog test",
+    "test:component": "roadhog test ./src/components",
+    "test:all": "node ./tests/run-tests.js"
+  },
+  "dependencies": {
+    "@antv/data-set": "^0.8.0",
+    "@babel/polyfill": "^7.0.0-beta.36",
+    "antd": "^3.1.0",
+    "babel-runtime": "^6.9.2",
+    "bizcharts": "^3.1.3-beta.1",
+    "bizcharts-plugin-slider": "^2.0.1",
+    "classnames": "^2.2.5",
+    "dva": "^2.1.0",
+    "dva-loading": "^1.0.4",
+    "enquire-js": "^0.1.1",
+    "fastclick": "^1.0.6",
+    "lodash": "^4.17.4",
+    "lodash-decorators": "^4.4.1",
+    "moment": "^2.19.1",
+    "numeral": "^2.0.6",
+    "omit.js": "^1.0.0",
+    "path-to-regexp": "^2.1.0",
+    "prop-types": "^15.5.10",
+    "qs": "^6.5.0",
+    "rc-drawer-menu": "^0.5.0",
+    "react": "^16.2.0",
+    "react-container-query": "^0.9.1",
+    "react-document-title": "^2.0.3",
+    "react-dom": "^16.2.0",
+    "react-fittext": "^1.0.0",
+    "rollbar": "^2.3.4",
+    "url-polyfill": "^1.0.10"
+  },
+  "devDependencies": {
+    "babel-eslint": "^8.1.2",
+    "babel-plugin-dva-hmr": "^0.4.1",
+    "babel-plugin-import": "^1.6.3",
+    "babel-plugin-transform-decorators-legacy": "^1.3.4",
+    "cross-env": "^5.1.1",
+    "cross-port-killer": "^1.0.1",
+    "enzyme": "^3.1.0",
+    "eslint": "^4.14.0",
+    "eslint-config-airbnb": "^16.0.0",
+    "eslint-plugin-babel": "^4.0.0",
+    "eslint-plugin-compat": "^2.1.0",
+    "eslint-plugin-import": "^2.8.0",
+    "eslint-plugin-jsx-a11y": "^6.0.3",
+    "eslint-plugin-markdown": "^1.0.0-beta.6",
+    "eslint-plugin-react": "^7.0.1",
+    "gh-pages": "^1.0.0",
+    "husky": "^0.14.3",
+    "lint-staged": "^6.0.0",
+    "mockjs": "^1.0.1-beta3",
+    "pro-download": "^1.0.1",
+    "redbox-react": "^1.5.0",
+    "regenerator-runtime": "^0.11.1",
+    "roadhog": "^2.1.0",
+    "roadhog-api-doc": "^0.3.4",
+    "stylelint": "^8.4.0",
+    "stylelint-config-standard": "^18.0.0"
+  },
+  "optionalDependencies": {
+    "nightmare": "^2.10.0"
+  },
+  "lint-staged": {
+    "**/*.{js,jsx}": "lint-staged:js",
+    "**/*.less": "stylelint --syntax less"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not ie <= 10"
+  ]
+}

BIN
public/favicon.png


BIN
src/assets/default_handsome.jpg


BIN
src/assets/logo.jpg


File diff suppressed because it is too large
+ 43 - 0
src/assets/logo.svg


+ 230 - 0
src/common/menu.js

@@ -0,0 +1,230 @@
+import React from 'react';
+import { isUrl } from '../utils/utils';
+import { plantform } from '../utils/config';
+import RBIcon from '../components/RBIcon';
+
+const menuData = () => {
+  if ('LJ' === plantform) {
+    return [{
+      name: '统计概览',
+      icon: 'dashboard',
+      path: 'dashboard',
+      children: [{
+        name: '运行监控',
+        path: 'analysis',
+        icon: <RBIcon type="apprun" />,
+      }, {
+        name: '营销统计',
+        path: 'monitor',
+        icon: <RBIcon type="monitor" />,
+      }, {
+        name: '行为分析',
+        path: 'workplace',
+        icon: <RBIcon type="action" />,
+      }],
+    }, {
+      name: '基础资源',
+      icon: 'folder',
+      path: 'resource',
+      children: [{
+        name: '图库管理',
+        path: 'image',
+        icon: 'file-jpg',
+      }, {
+        name: '视频管理',
+        path: 'video',
+        icon: 'video-camera',
+      }],
+    }, {
+      name: '产品加工',
+      icon: 'appstore-o',
+      path: 'product',
+      children: [{
+        name: '制作课件',
+        path: 'ware',
+      }, {
+        name: '制作课',
+        path: 'lesson',
+      }, {
+        name: '制作课程',
+        path: 'course',
+      }, {
+        name: '制作配套',
+        path: 'support',
+      }, {
+        name: '制作套装包',
+        path: 'package',
+      }],
+    }, {
+      name: '产品出售',
+      icon: 'shop',
+      path: 'goods',
+      children: [{
+        name: '虚拟课程',
+        path: 'virtual',
+      }, {
+        name: '实体物品',
+        path: 'entity',
+      }, {
+        name: '打包套装',
+        path: 'package',
+      }],
+    }, {
+      name: '前端配置',
+      icon: 'android-o',
+      path: 'app',
+      children: [{
+        name: '首页入口',
+        path: 'tagGroup',
+      }, {
+        name: '标签栏目',
+        path: 'tag',
+      }, {
+        name: '推荐位配置',
+        path: 'recommend',
+      }],
+    }, {
+      name: '订单系统',
+      icon: 'trademark',
+      path: 'trade',
+      children: [{
+        name: '购物车',
+        icon: 'shopping-cart',
+        path: 'shopcart',
+      }, {
+        name: '订单列表',
+        icon: <RBIcon type="order" />,
+      }]
+    }, {
+      name: '厂商管理',
+      icon: 'team',
+      path: 'merchant',
+    }, {
+      name: '校区管理',
+      icon: <RBIcon type="campus" />,
+      path: 'campus',
+    }, {
+      name: '终端用户',
+      path: 'terminal',
+      icon: <RBIcon type="terminal" />
+    }, {
+      name: '系统用户',
+      path: 'system',
+      icon: <RBIcon type="systemuser" />
+    }];
+  } else if ('PJ' === plantform) {
+    return [{
+      name: '统计概览',
+      icon: 'dashboard',
+      path: 'dashboard',
+      children: [{
+        name: '运行监控',
+        path: 'analysis',
+        icon: <RBIcon type="apprun" />,
+      }, {
+        name: '营销统计',
+        path: 'monitor',
+        icon: <RBIcon type="monitor" />,
+      }, {
+        name: '行为分析',
+        path: 'workplace',
+        icon: <RBIcon type="action" />,
+      }],
+    }, {
+      name: '产品库',
+      icon: 'shop',
+      path: 'goods',
+      children: [{
+        name: '虚拟课程',
+        path: 'virtual',
+      }, {
+        name: '实体物品',
+        path: 'entity',
+      }, {
+        name: '打包套装',
+        path: 'package',
+      }],
+    }, {
+      name: '订单系统',
+      icon: 'trademark',
+      path: 'trade',
+      children: [{
+        name: '购物车',
+        icon: 'shopping-cart',
+        path: 'shopcart',
+      }, {
+        name: '订单列表',
+        icon: <RBIcon type="order" />,
+      }]
+    }, {
+      name: '校区管理',
+      icon: <RBIcon type="campus" />,
+      path: 'campus',
+    }, {
+      name: '终端用户',
+      path: 'terminal',
+      icon: <RBIcon type="terminal" />
+    }, {
+      name: '账户信息',
+      icon: 'team',
+      path: 'merchant',
+    }];
+  } else if ('CP' === plantform) {
+    return [{
+      name: '统计概览',
+      icon: 'dashboard',
+      path: 'dashboard',
+      children: [{
+        name: '营销统计',
+        path: 'monitor',
+        icon: <RBIcon type="monitor" />,
+      }]
+    }, {
+      name: '产品库',
+      icon: 'shop',
+      path: 'goods',
+      children: [{
+        name: '虚拟课程',
+        path: 'virtual',
+      }, {
+        name: '实体物品',
+        path: 'entity',
+      }, {
+        name: '打包套装',
+        path: 'package',
+      }],
+    }, {
+      name: '订单系统',
+      icon: 'trademark',
+      path: 'trade',
+      children: [{
+        name: '订单列表',
+        icon: <RBIcon type="order" />,
+      }]
+    }, {
+      name: '账户信息',
+      icon: 'team',
+      path: 'merchant',
+    }];
+  }
+};
+
+function formatter(data, parentPath = '/', parentAuthority) {
+  return data.map((item) => {
+    let { path } = item;
+    if (!isUrl(path)) {
+      path = parentPath + item.path;
+    }
+    const result = {
+      ...item,
+      path,
+      authority: item.authority || parentAuthority,
+    };
+    if (item.children) {
+      result.children = formatter(item.children, `${parentPath}${item.path}/`, item.authority);
+    }
+    return result;
+  });
+}
+
+export const getMenuData = () => formatter(menuData());

+ 216 - 0
src/common/router.js

@@ -0,0 +1,216 @@
+import { createElement } from 'react';
+import dynamic from 'dva/dynamic';
+import pathToRegexp from 'path-to-regexp';
+import { getMenuData } from './menu';
+
+let routerDataCache;
+
+const modelNotExisted = (app, model) => (
+  // eslint-disable-next-line
+  !app._models.some(({ namespace }) => {
+    return namespace === model.substring(model.lastIndexOf('/') + 1);
+  })
+);
+
+// wrapper of dynamic
+const dynamicWrapper = (app, models, component) => {
+  // () => require('module')
+  // transformed by babel-plugin-dynamic-import-node-sync
+  if (component.toString().indexOf('.then(') < 0) {
+    models.forEach((model) => {
+      if (modelNotExisted(app, model)) {
+        // eslint-disable-next-line
+        app.model(require(`../models/${model}`).default);
+      }
+    });
+    return (props) => {
+      if (!routerDataCache) {
+        routerDataCache = getRouterData(app);
+      }
+      return createElement(component().default, {
+        ...props,
+        routerData: routerDataCache,
+      });
+    };
+  }
+  // () => import('module')
+  return dynamic({
+    app,
+    models: () => models.filter(
+      model => modelNotExisted(app, model)).map(m => import(`../models/${m}.js`)
+    ),
+    // add routerData prop
+    component: () => {
+      if (!routerDataCache) {
+        routerDataCache = getRouterData(app);
+      }
+      return component().then((raw) => {
+        const Component = raw.default || raw;
+        return props => createElement(Component, {
+          ...props,
+          routerData: routerDataCache,
+        });
+      });
+    },
+  });
+};
+
+function getFlatMenuData(menus) {
+  let keys = {};
+  menus.forEach((item) => {
+    if (item.children) {
+      keys[item.path] = { ...item };
+      keys = { ...keys, ...getFlatMenuData(item.children) };
+    } else {
+      keys[item.path] = { ...item };
+    }
+  });
+  return keys;
+}
+
+export const getRouterData = (app) => {
+  const routerConfig = {
+    '/': {
+      component: dynamicWrapper(app, ['user', 'login'], () => import('../layouts/BasicLayout')),
+    },
+    '/resource/image': {
+      component: dynamicWrapper(app, ['resource'], () => import('../routes/Resource/PictureList')),
+    },
+    '/resource/imageCreate': {
+      component: dynamicWrapper(app, [], () => import('../routes/Resource/PictureCreate')),
+    },
+    '/resource/imageCreate/single': {
+      component: dynamicWrapper(app, ['resource'], () => import('../routes/Resource/PictureCreateSingle')),
+      name: '单图上传'
+    },
+    '/resource/imageCreate/multiple': {
+      component: dynamicWrapper(app, [], () => import('../routes/Resource/PictureCreateMultiple')),
+      name: '多图上传'
+    },
+    '/resource/video': {
+      component: dynamicWrapper(app, ['resource'], () => import('../routes/Resource/VideoList')),
+    },
+    '/dashboard/analysis': {
+      component: dynamicWrapper(app, ['chart'], () => import('../routes/Dashboard/Analysis')),
+    },
+    '/dashboard/monitor': {
+      component: dynamicWrapper(app, ['monitor'], () => import('../routes/Dashboard/Monitor')),
+    },
+    '/dashboard/workplace': {
+      component: dynamicWrapper(app, ['project', 'activities', 'chart'], () => import('../routes/Dashboard/Workplace')),
+      // hideInBreadcrumb: true,
+      // name: '工作台',
+      // authority: 'admin',
+    },
+    '/form/basic-form': {
+      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/BasicForm')),
+    },
+    '/form/step-form': {
+      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/StepForm')),
+    },
+    '/form/step-form/info': {
+      name: '分步表单(填写转账信息)',
+      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/StepForm/Step1')),
+    },
+    '/form/step-form/confirm': {
+      name: '分步表单(确认转账信息)',
+      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/StepForm/Step2')),
+    },
+    '/form/step-form/result': {
+      name: '分步表单(完成)',
+      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/StepForm/Step3')),
+    },
+    '/form/advanced-form': {
+      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/AdvancedForm')),
+    },
+    '/list/table-list': {
+      component: dynamicWrapper(app, ['rule'], () => import('../routes/List/TableList')),
+    },
+    '/list/basic-list': {
+      component: dynamicWrapper(app, ['list'], () => import('../routes/List/BasicList')),
+    },
+    '/list/card-list': {
+      component: dynamicWrapper(app, ['list'], () => import('../routes/List/CardList')),
+    },
+    '/list/search': {
+      component: dynamicWrapper(app, ['list'], () => import('../routes/List/List')),
+    },
+    '/list/search/projects': {
+      component: dynamicWrapper(app, ['list'], () => import('../routes/List/Projects')),
+    },
+    '/list/search/applications': {
+      component: dynamicWrapper(app, ['list'], () => import('../routes/List/Applications')),
+    },
+    '/list/search/articles': {
+      component: dynamicWrapper(app, ['list'], () => import('../routes/List/Articles')),
+    },
+    '/profile/basic': {
+      component: dynamicWrapper(app, ['profile'], () => import('../routes/Profile/BasicProfile')),
+    },
+    '/profile/advanced': {
+      component: dynamicWrapper(app, ['profile'], () => import('../routes/Profile/AdvancedProfile')),
+    },
+    '/result/success': {
+      component: dynamicWrapper(app, [], () => import('../routes/Result/Success')),
+    },
+    '/result/fail': {
+      component: dynamicWrapper(app, [], () => import('../routes/Result/Error')),
+    },
+    '/exception/403': {
+      component: dynamicWrapper(app, [], () => import('../routes/Exception/403')),
+    },
+    '/exception/404': {
+      component: dynamicWrapper(app, [], () => import('../routes/Exception/404')),
+    },
+    '/exception/500': {
+      component: dynamicWrapper(app, [], () => import('../routes/Exception/500')),
+    },
+    '/exception/trigger': {
+      component: dynamicWrapper(app, ['error'], () => import('../routes/Exception/triggerException')),
+    },
+    '/user': {
+      component: dynamicWrapper(app, [], () => import('../layouts/UserLayout')),
+    },
+    '/user/login': {
+      component: dynamicWrapper(app, ['login'], () => import('../routes/User/Login')),
+    },
+    '/user/register': {
+      component: dynamicWrapper(app, ['register'], () => import('../routes/User/Register')),
+    },
+    '/user/register-result': {
+      component: dynamicWrapper(app, [], () => import('../routes/User/RegisterResult')),
+    },
+    // '/user/:id': {
+    //   component: dynamicWrapper(app, [], () => import('../routes/User/SomeComponent')),
+    // },
+  };
+  // Get name from ./menu.js or just set it in the router data.
+  const menuData = getFlatMenuData(getMenuData());
+
+  // Route configuration data
+  // eg. {name,authority ...routerConfig }
+  const routerData = {};
+  // The route matches the menu
+  Object.keys(routerConfig).forEach((path) => {
+    // Regular match item name
+    // eg.  router /user/:id === /user/chen
+    const pathRegexp = pathToRegexp(path);
+    const menuKey = Object.keys(menuData).find(key => pathRegexp.test(`${key}`));
+    let menuItem = {};
+    // If menuKey is not empty
+    if (menuKey) {
+      menuItem = menuData[menuKey];
+    }
+    let router = routerConfig[path];
+    // If you need to configure complex parameter routing,
+    // https://github.com/ant-design/ant-design-pro-site/blob/master/docs/router-and-nav.md#%E5%B8%A6%E5%8F%82%E6%95%B0%E7%9A%84%E8%B7%AF%E7%94%B1%E8%8F%9C%E5%8D%95
+    // eg . /list/:type/user/info/:id
+    router = {
+      ...router,
+      name: router.name || menuItem.name,
+      authority: router.authority || menuItem.authority,
+    };
+    routerData[path] = router;
+  });
+  return routerData;
+};

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

@@ -0,0 +1,82 @@
+import React, { Component } from 'react';
+
+import { MiniArea } from '../Charts';
+import NumberInfo from '../NumberInfo';
+
+import styles from './index.less';
+
+function fixedZero(val) {
+  return val * 1 < 10 ? `0${val}` : val;
+}
+
+function getActiveData() {
+  const activeData = [];
+  for (let i = 0; i < 24; i += 1) {
+    activeData.push({
+      x: `${fixedZero(i)}:00`,
+      y: Math.floor(Math.random() * 200) + (i * 50),
+    });
+  }
+  return activeData;
+}
+
+export default class ActiveChart extends Component {
+  state = {
+    activeData: getActiveData(),
+  };
+
+  componentDidMount() {
+    this.timer = setInterval(() => {
+      this.setState({
+        activeData: getActiveData(),
+      });
+    }, 1000);
+  }
+
+  componentWillUnmount() {
+    clearInterval(this.timer);
+  }
+
+  render() {
+    const { activeData = [] } = this.state;
+
+    return (
+      <div className={styles.activeChart}>
+        <NumberInfo subTitle="目标评估" total="有望达到预期" />
+        <div style={{ marginTop: 32 }}>
+          <MiniArea
+            animate={false}
+            line
+            borderWidth={2}
+            height={84}
+            scale={{
+              y: {
+                tickCount: 3,
+              },
+            }}
+            yAxis={{
+              tickLine: false,
+              label: 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>
+    );
+  }
+}

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

@@ -0,0 +1,31 @@
+.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;
+  }
+}

+ 12 - 0
src/components/Authorized/Authorized.js

@@ -0,0 +1,12 @@
+import React from 'react';
+import CheckPermissions from './CheckPermissions';
+
+class Authorized extends React.Component {
+  render() {
+    const { children, authority, noMatch = null } = this.props;
+    const childrenRender = typeof children === 'undefined' ? null : children;
+    return CheckPermissions(authority, childrenRender, noMatch);
+  }
+}
+
+export default Authorized;

+ 35 - 0
src/components/Authorized/AuthorizedRoute.js

@@ -0,0 +1,35 @@
+import React from 'react';
+import { Route, Redirect } from 'react-router-dom';
+import Authorized from './Authorized';
+
+class AuthorizedRoute extends React.Component {
+  render() {
+    const {
+      component: Component,
+      render,
+      authority,
+      redirectPath,
+      ...rest
+    } = this.props;
+    return (
+      <Authorized
+        authority={authority}
+        noMatch={
+          <Route
+            {...rest}
+            render={() => <Redirect to={{ pathname: redirectPath }} />}
+          />
+        }
+      >
+        <Route
+          {...rest}
+          render={props =>
+            (Component ? <Component {...props} /> : render(props))
+          }
+        />
+      </Authorized>
+    );
+  }
+}
+
+export default AuthorizedRoute;

+ 69 - 0
src/components/Authorized/CheckPermissions.js

@@ -0,0 +1,69 @@
+import React from 'react';
+import PromiseRender from './PromiseRender';
+import { CURRENT } from './index';
+
+function isPromise(obj) {
+  return (
+    !!obj &&
+    (typeof obj === 'object' || typeof obj === 'function') &&
+    typeof obj.then === 'function'
+  );
+}
+
+/**
+ * 通用权限检查方法
+ * Common check permissions method
+ * @param { 权限判定 Permission judgment type string |array | Promise | Function } authority
+ * @param { 你的权限 Your permission description  type:string} currentAuthority
+ * @param { 通过的组件 Passing components } target
+ * @param { 未通过的组件 no pass components } Exception
+ */
+const checkPermissions = (authority, currentAuthority, target, Exception) => {
+  // 没有判定权限.默认查看所有
+  // Retirement authority, return target;
+  if (!authority) {
+    return target;
+  }
+  // 数组处理
+  if (Array.isArray(authority)) {
+    if (authority.indexOf(currentAuthority) >= 0) {
+      return target;
+    }
+    return Exception;
+  }
+
+  // string 处理
+  if (typeof authority === 'string') {
+    if (authority === currentAuthority) {
+      return target;
+    }
+    return Exception;
+  }
+
+  // Promise 处理
+  if (isPromise(authority)) {
+    return <PromiseRender ok={target} error={Exception} promise={authority} />;
+  }
+
+  // Function 处理
+  if (typeof authority === 'function') {
+    try {
+      const bool = authority(currentAuthority);
+      if (bool) {
+        return target;
+      }
+      return Exception;
+    } catch (error) {
+      throw error;
+    }
+  }
+  throw new Error('unsupported parameters');
+};
+
+export { checkPermissions };
+
+const check = (authority, target, Exception) => {
+  return checkPermissions(authority, CURRENT, target, Exception);
+};
+
+export default check;

+ 45 - 0
src/components/Authorized/CheckPermissions.test.js

@@ -0,0 +1,45 @@
+import { checkPermissions } from './CheckPermissions.js';
+
+const target = 'ok';
+const error = 'error';
+
+describe('test CheckPermissions', () => {
+  it('Correct string permission authentication', () => {
+    expect(checkPermissions('user', 'user', target, error)).toEqual('ok');
+  });
+  it('Correct string permission authentication', () => {
+    expect(checkPermissions('user', 'NULL', target, error)).toEqual('error');
+  });
+  it('authority is undefined , return ok', () => {
+    expect(checkPermissions(null, 'NULL', target, error)).toEqual('ok');
+  });
+  it('currentAuthority is undefined , return error', () => {
+    expect(checkPermissions('admin', null, target, error)).toEqual('error');
+  });
+  it('Wrong string permission authentication', () => {
+    expect(checkPermissions('admin', 'user', target, error)).toEqual('error');
+  });
+  it('Correct Array permission authentication', () => {
+    expect(checkPermissions(['user', 'admin'], 'user', target, error)).toEqual(
+      'ok'
+    );
+  });
+  it('Wrong Array permission authentication,currentAuthority error', () => {
+    expect(
+      checkPermissions(['user', 'admin'], 'user,admin', target, error)
+    ).toEqual('error');
+  });
+  it('Wrong Array permission authentication', () => {
+    expect(checkPermissions(['user', 'admin'], 'guest', target, error)).toEqual(
+      'error'
+    );
+  });
+  it('Wrong Function permission authentication', () => {
+    expect(checkPermissions(() => false, 'guest', target, error)).toEqual(
+      'error'
+    );
+  });
+  it('Correct Function permission authentication', () => {
+    expect(checkPermissions(() => true, 'guest', target, error)).toEqual('ok');
+  });
+});

+ 59 - 0
src/components/Authorized/PromiseRender.js

@@ -0,0 +1,59 @@
+import React from 'react';
+import { Spin } from 'antd';
+
+export default class PromiseRender extends React.PureComponent {
+  state = {
+    component: null,
+  };
+  componentDidMount() {
+    this.setRenderComponent(this.props);
+  }
+  componentWillReceiveProps(nextProps) {
+    // new Props enter
+    this.setRenderComponent(nextProps);
+  }
+  // set render Component : ok or error
+  setRenderComponent(props) {
+    const ok = this.checkIsInstantiation(props.ok);
+    const error = this.checkIsInstantiation(props.error);
+    props.promise
+      .then(() => {
+        this.setState({
+          component: ok,
+        });
+      })
+      .catch(() => {
+        this.setState({
+          component: error,
+        });
+      });
+  }
+  // Determine whether the incoming component has been instantiated
+  // AuthorizedRoute is already instantiated
+  // Authorized  render is already instantiated, children is no instantiated
+  // Secured is not instantiated
+  checkIsInstantiation = (target) => {
+    if (!React.isValidElement(target)) {
+      return target;
+    }
+    return () => target;
+  };
+  render() {
+    const Component = this.state.component;
+    return Component ? (
+      <Component {...this.props} />
+    ) : (
+      <div
+        style={{
+          width: '100%',
+          height: '100%',
+          margin: 'auto',
+          paddingTop: 50,
+          textAlign: 'center',
+        }}
+      >
+        <Spin size="large" />
+      </div>
+    );
+  }
+}

+ 57 - 0
src/components/Authorized/Secured.js

@@ -0,0 +1,57 @@
+import React from 'react';
+import Exception from '../Exception/index';
+import CheckPermissions from './CheckPermissions';
+/**
+ * 默认不能访问任何页面
+ * default is "NULL"
+ */
+const Exception403 = () => (
+  <Exception type="403" style={{ minHeight: 500, height: '80%' }} />
+);
+
+// Determine whether the incoming component has been instantiated
+// AuthorizedRoute is already instantiated
+// Authorized  render is already instantiated, children is no instantiated
+// Secured is not instantiated
+const checkIsInstantiation = (target) => {
+  if (!React.isValidElement(target)) {
+    return target;
+  }
+  return () => target;
+};
+
+/**
+ * 用于判断是否拥有权限访问此view权限
+ * authority 支持传入  string ,funtion:()=>boolean|Promise
+ * e.g. 'user' 只有user用户能访问
+ * e.g. 'user,admin' user和 admin 都能访问
+ * e.g. ()=>boolean 返回true能访问,返回false不能访问
+ * e.g. Promise  then 能访问   catch不能访问
+ * e.g. authority support incoming string, funtion: () => boolean | Promise
+ * e.g. 'user' only user user can access
+ * e.g. 'user, admin' user and admin can access
+ * e.g. () => boolean true to be able to visit, return false can not be accessed
+ * e.g. Promise then can not access the visit to catch
+ * @param {string | function | Promise} authority
+ * @param {ReactNode} error 非必需参数
+ */
+const authorize = (authority, error) => {
+  /**
+   * conversion into a class
+   * 防止传入字符串时找不到staticContext造成报错
+   * String parameters can cause staticContext not found error
+   */
+  let classError = false;
+  if (error) {
+    classError = () => error;
+  }
+  if (!authority) {
+    throw new Error('authority is required');
+  }
+  return function decideAuthority(targer) {
+    const component = CheckPermissions(authority, targer, classError || Exception403);
+    return checkIsInstantiation(component);
+  };
+};
+
+export default authorize;

+ 23 - 0
src/components/Authorized/demo/AuthorizedArray.md

@@ -0,0 +1,23 @@
+---
+order: 1
+title: 
+  zh-CN: 使用数组作为参数
+  en-US: Use Array as a parameter
+---
+
+Use Array as a parameter
+
+```jsx
+import RenderAuthorized from 'ant-design-pro/lib/Authorized';
+import { Alert } from 'antd';
+
+const Authorized = RenderAuthorized('user');
+const noMatch = <Alert message="No permission." type="error" showIcon />;
+
+ReactDOM.render(
+  <Authorized authority={['user', 'admin']} noMatch={noMatch}>
+    <Alert message="Use Array as a parameter passed!" type="success" showIcon />
+  </Authorized>,
+  mountNode,
+);
+```

+ 31 - 0
src/components/Authorized/demo/AuthorizedFunction.md

@@ -0,0 +1,31 @@
+---
+order: 2
+title: 
+  zh-CN: 使用方法作为参数
+  en-US: Use function as a parameter
+---
+
+Use Function as a parameter
+
+```jsx
+import RenderAuthorized from 'ant-design-pro/lib/Authorized';
+import { Alert } from 'antd';
+
+const Authorized = RenderAuthorized('user');
+const noMatch = <Alert message="No permission." type="error" showIcon />;
+
+const havePermission = () => {
+  return false;
+};
+
+ReactDOM.render(
+  <Authorized authority={havePermission} noMatch={noMatch}>
+    <Alert
+      message="Use Function as a parameter passed!"
+      type="success"
+      showIcon
+    />
+  </Authorized>,
+  mountNode,
+);
+```

+ 25 - 0
src/components/Authorized/demo/basic.md

@@ -0,0 +1,25 @@
+---
+order: 0
+title: 
+  zh-CN: 基本使用
+  en-US: Basic use
+---
+
+Basic use
+
+```jsx
+import RenderAuthorized from 'ant-design-pro/lib/Authorized';
+import { Alert } from 'antd';
+
+const Authorized = RenderAuthorized('user');
+const noMatch = <Alert message="No permission." type="error" showIcon />;
+
+ReactDOM.render(
+  <div>
+    <Authorized authority="admin" noMatch={noMatch}>
+      <Alert message="user Passed!" type="success" showIcon />
+    </Authorized>
+  </div>,
+  mountNode,
+);
+```

+ 28 - 0
src/components/Authorized/demo/secured.md

@@ -0,0 +1,28 @@
+---
+order: 3
+title: 
+  zh-CN: 注解基本使用
+  en-US: Basic use secured
+---
+
+secured demo used
+
+```jsx
+import RenderAuthorized from 'ant-design-pro/lib/Authorized';
+import { Alert } from 'antd';
+
+const { Secured } = RenderAuthorized('user');
+
+@Secured('admin')
+class TestSecuredString extends React.Component {
+  render() {
+    <Alert message="user Passed!" type="success" showIcon />;
+  }
+}
+ReactDOM.render(
+  <div>
+    <TestSecuredString />
+  </div>,
+  mountNode,
+);
+```

+ 48 - 0
src/components/Authorized/index.d.ts

@@ -0,0 +1,48 @@
+import * as React from 'react';
+import { RouteProps } from 'react-router';
+
+type authorityFN = (currentAuthority?: string) => boolean;
+
+type authority = string | Array<string> | authorityFN | Promise<any>;
+
+export type IReactComponent<P = any> =
+  | React.StatelessComponent<P>
+  | React.ComponentClass<P>
+  | React.ClassicComponentClass<P>;
+
+interface Secured {
+  (authority: authority, error?: React.ReactNode): <T extends IReactComponent>(
+    target: T,
+  ) => T;
+}
+
+export interface AuthorizedRouteProps extends RouteProps {
+  authority: authority;
+}
+export class AuthorizedRoute extends React.Component<
+  AuthorizedRouteProps,
+  any
+> {}
+
+interface check {
+  <T extends IReactComponent, S extends IReactComponent>(
+    authority: authority,
+    target: T,
+    Exception: S,
+  ): T | S;
+}
+
+interface AuthorizedProps {
+  authority: authority;
+  noMatch?: React.ReactNode;
+}
+
+export class Authorized extends React.Component<AuthorizedProps, any> {
+  static Secured: Secured;
+  static AuthorizedRoute: typeof AuthorizedRoute;
+  static check: check;
+}
+
+declare function renderAuthorize(currentAuthority: string): typeof Authorized;
+
+export default renderAuthorize;

+ 32 - 0
src/components/Authorized/index.js

@@ -0,0 +1,32 @@
+import Authorized from './Authorized';
+import AuthorizedRoute from './AuthorizedRoute';
+import Secured from './Secured';
+import check from './CheckPermissions.js';
+
+/* eslint-disable import/no-mutable-exports */
+let CURRENT = 'NULL';
+
+Authorized.Secured = Secured;
+Authorized.AuthorizedRoute = AuthorizedRoute;
+Authorized.check = check;
+
+/**
+ * use  authority or getAuthority
+ * @param {string|()=>String} currentAuthority
+ */
+const renderAuthorize = (currentAuthority) => {
+  if (currentAuthority) {
+    if (currentAuthority.constructor.name === 'Function') {
+      CURRENT = currentAuthority();
+    }
+    if (currentAuthority.constructor.name === 'String') {
+      CURRENT = currentAuthority;
+    }
+  } else {
+    CURRENT = 'NULL';
+  }
+  return Authorized;
+};
+
+export { CURRENT };
+export default renderAuthorize;

+ 58 - 0
src/components/Authorized/index.md

@@ -0,0 +1,58 @@
+---
+title:
+  en-US: Authorized
+  zh-CN: Authorized
+subtitle: 权限
+cols: 1
+order: 15
+---
+
+权限组件,通过比对现有权限与准入权限,决定相关元素的展示。
+
+## API
+
+### RenderAuthorized
+
+`RenderAuthorized: (currentAuthority: string | () => string) => Authorized`
+
+权限组件默认 export RenderAuthorized 函数,它接收当前权限作为参数,返回一个权限对象,该对象提供以下几种使用方式。
+
+
+### Authorized
+
+最基础的权限控制。
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| children    | 正常渲染的元素,权限判断通过时展示           | ReactNode  | - |
+| authority   | 准入权限/权限判断         | `string | array | Promise | (currentAuthority) => boolean` | - |
+| noMatch     | 权限异常渲染元素,权限判断不通过时展示        | ReactNode  | - |
+
+### Authorized.AuthorizedRoute
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| authority     | 准入权限/权限判断         | `string | array | Promise | (currentAuthority) => boolean` | - |
+| redirectPath  | 权限异常时重定向的页面路由                | string  | - |
+
+其余参数与 `Route` 相同。
+
+### Authorized.Secured
+
+注解方式,`@Authorized.Secured(authority, error)`
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| authority     | 准入权限/权限判断         | `string | Promise | (currentAuthority) => boolean` | - |
+| error  | 权限异常时渲染元素                |  ReactNode | <Exception type="403" /> |
+
+### Authorized.check
+
+函数形式的 Authorized,用于某些不能被 HOC 包裹的组件。 `Authorized.check(authority, target, Exception)`  
+注意:传入一个 Promise 时,无论正确还是错误返回的都是一个 ReactClass。
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| authority     | 准入权限/权限判断         | `string | Promise | (currentAuthority) => boolean` | - |
+| target     | 权限判断通过时渲染的元素         | ReactNode | - |
+| Exception  | 权限异常时渲染元素                |  ReactNode | - |

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

@@ -0,0 +1,77 @@
+import React, { PureComponent } from 'react';
+import { Card, Popover, Avatar, Icon } from 'antd';
+import classnames from 'classnames';
+import defaultAvatar from '../../assets/default_handsome.jpg';
+import styles from './index.less';
+
+const userEntranceList = [{
+  name: '基本资料',
+  icon: 'profile',
+  path: '',
+}, {
+  name: '账户设置',
+  icon: 'setting',
+  path: '',
+}, {
+  name: '访问控制',
+  icon: 'idcard',
+  path: '',
+}];
+
+export default class AvatarIcon extends PureComponent {
+  constructor(props) {
+    super(props);
+    this.state = {};
+  }
+  onLogoutClick = () => {
+    const { onLogoutClick } = this.props;
+    onLogoutClick({ key: 'logout' });
+  }
+  render() {
+    const { avatar, account, popupAlign, className } = this.props;
+    const avatarClass = classnames(className, styles.avatar);
+    const trigger = (
+      <span className={avatarClass}>
+        <Avatar src={avatar || defaultAvatar} />
+      </span>
+    );
+    const userBoxTitle = (
+      <span>
+        <Avatar src={avatar || defaultAvatar} />
+        <p>{account}</p>
+      </span>
+    );
+    const userBoxContent = (
+      <div className={styles.userBoxContent}>
+        <div className={styles.entranceCard}>
+          {userEntranceList.map((entrance) => {
+            return (
+              <Card
+                key={`user-box-entrance-${entrance.name}`}
+                bordered={false}
+                hoverable
+              >
+                <Icon type={entrance.icon} />
+                <div>{entrance.name}</div>
+              </Card>
+            );
+          })}
+        </div>
+        <a onClick={this.onLogoutClick} className={styles.logoutButton}>退出管理平台</a>
+      </div>
+    );
+    return (
+      <Popover
+        placement="bottomRight"
+        popupClassName={styles.popover}
+        title={userBoxTitle}
+        content={userBoxContent}
+        trigger="hover"
+        arrowPointAtCenter
+        popupAlign={popupAlign}
+      >
+        {trigger}
+      </Popover>
+    );
+  }
+}

+ 47 - 0
src/components/AvatarIcon/index.less

@@ -0,0 +1,47 @@
+@import "~antd/lib/style/themes/default.less";
+
+.popover {
+  width: 250px;
+  :global(.ant-popover-title) {
+    text-align: center;
+  }
+  :global(.ant-popover-inner-content) {
+    padding: 0;
+  }
+  p {
+    margin-top: 5px;
+  }
+}
+
+.userBoxContent {
+  padding-top: 16px;
+  :global(.ant-card) {
+    margin-left: 10px;
+    width: 70px;
+    height: 70px;
+    float: left;
+    &:hover {
+      background: #f5f5f5;
+    }
+  }
+  :global(.ant-card .ant-card-body) {
+    padding: 20px 0;
+    text-align: center;
+    > i {
+      font-size: 20px;
+      color: #788d9b;
+    }
+  }
+  .logoutButton {
+    display: block;
+    clear: both;
+    height: 50px;
+    line-height: 50px;
+    background: #f5f5f5;
+    text-align: center;
+    font-size: 14px;
+    font-weight: 500;
+    vertical-align: middle;
+    margin-top: 96px;
+  }
+}

+ 20 - 0
src/components/AvatarList/demo/simple.md

@@ -0,0 +1,20 @@
+---
+order: 0
+title: 
+  zh-CN: 基础样例 
+  en-US: Basic Usage
+---
+
+Simplest of usage.
+
+````jsx
+import AvatarList from 'ant-design-pro/lib/AvatarList';
+
+ReactDOM.render(
+  <AvatarList size="mini">
+    <AvatarList.Item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
+    <AvatarList.Item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
+    <AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
+  </AvatarList>
+, mountNode);
+````

+ 23 - 0
src/components/AvatarList/index.d.ts

@@ -0,0 +1,23 @@
+import * as React from "react";
+export interface AvatarItemProps {
+  tips: React.ReactNode;
+  src: string;
+  style?: React.CSSProperties;
+}
+
+export interface AvatarListProps {
+  size?: "large" | "small" | "mini" | "default";
+  style?: React.CSSProperties;
+  children:
+    | React.ReactElement<AvatarItem>
+    | Array<React.ReactElement<AvatarItem>>;
+}
+
+declare class AvatarItem extends React.Component<AvatarItemProps, any> {
+  constructor(props: AvatarItemProps);
+}
+
+export default class AvatarList extends React.Component<AvatarListProps, any> {
+  constructor(props: AvatarListProps);
+  static Item: typeof AvatarItem;
+}

+ 22 - 0
src/components/AvatarList/index.en-US.md

@@ -0,0 +1,22 @@
+---
+title: AvatarList
+order: 1
+cols: 1
+---
+
+A list of user's avatar for project or group member list frequently. If a large or small AvatarList is desired, set the `size` property to either `large` or `small` and `mini` respectively. Omit the `size` property for a AvatarList with the default size.
+
+## API
+
+### AvatarList
+
+| Property | Description | Type | Default |
+|----------|------------------------------------------|-------------|-------|
+| size | size of list | `large`、`small` 、`mini`, `default` | `default` |
+
+### AvatarList.Item
+
+| Property | Description | Type | Default |
+|----------|------------------------------------------|-------------|-------|
+| tips | title tips for avatar item | ReactNode\/string | - |
+| src | the address of the image for an image avatar | string | - |

+ 43 - 0
src/components/AvatarList/index.js

@@ -0,0 +1,43 @@
+import React from 'react';
+import { Tooltip, Avatar } from 'antd';
+import classNames from 'classnames';
+
+import styles from './index.less';
+
+const AvatarList = ({ children, size, ...other }) => {
+  const childrenWithProps = React.Children.map(children, child =>
+    React.cloneElement(child, {
+      size,
+    })
+  );
+
+  return (
+    <div {...other} className={styles.avatarList}>
+      <ul> {childrenWithProps} </ul>
+    </div>
+  );
+};
+
+const Item = ({ src, size, tips, onClick = (() => {}) }) => {
+  const cls = classNames(styles.avatarItem, {
+    [styles.avatarItemLarge]: size === 'large',
+    [styles.avatarItemSmall]: size === 'small',
+    [styles.avatarItemMini]: size === 'mini',
+  });
+
+  return (
+    <li className={cls} onClick={onClick} >
+      {
+        tips ? (
+          <Tooltip title={tips}>
+            <Avatar src={src} size={size} style={{ cursor: 'pointer' }} />
+          </Tooltip>
+        ) : <Avatar src={src} size={size} />
+      }
+    </li>
+  );
+};
+
+AvatarList.Item = Item;
+
+export default AvatarList;

+ 45 - 0
src/components/AvatarList/index.less

@@ -0,0 +1,45 @@
+@import "~antd/lib/style/themes/default.less";
+
+.avatarList {
+  display: inline-block;
+  ul {
+    display: inline-block;
+    margin-left: 8px;
+    font-size: 0;
+  }
+}
+
+.avatarItem {
+  display: inline-block;
+  font-size: @font-size-base;
+  margin-left: -8px;
+  width: @avatar-size-base;
+  height: @avatar-size-base;
+  :global {
+    .ant-avatar {
+      border: 1px solid #fff;
+    }
+  }
+}
+
+.avatarItemLarge {
+  width: @avatar-size-lg;
+  height: @avatar-size-lg;
+}
+
+.avatarItemSmall {
+  width: @avatar-size-sm;
+  height: @avatar-size-sm;
+}
+
+.avatarItemMini {
+  width: 20px;
+  height: 20px;
+  :global {
+    .ant-avatar {
+      width: 20px;
+      height: 20px;
+      line-height: 20px;
+    }
+  }
+}

+ 23 - 0
src/components/AvatarList/index.zh-CN.md

@@ -0,0 +1,23 @@
+---
+title: AvatarList
+subtitle: 用户头像列表
+order: 1
+cols: 1
+---
+
+一组用户头像,常用在项目/团队成员列表。可通过设置 `size` 属性来指定头像大小。
+
+## API
+
+### AvatarList
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| size | 头像大小 | `large`、`small` 、`mini`, `default` | `default` |
+
+### AvatarList.Item
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| tips | 头像展示文案 | ReactNode\/string | - |
+| src | 头像图片连接 | string | - |

+ 39 - 0
src/components/CardList/index.js

@@ -0,0 +1,39 @@
+import React, { PureComponent } from 'react';
+import { List } from 'antd';
+
+export default class CardList extends PureComponent {
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    const { dataset, loading, grid, children, pagination, header, footer, bordered } = this.props;
+    return (
+      <div className={styles.list}>
+        <div className={styles.header}></div>
+        <div className={styles.content}>
+          <List
+            pagination={false}
+            bordered={bordered}
+            header={header}
+            footer={footer}
+            grid={grid}
+            dataSource={dataset}
+            loading={loading}
+            renderItem={item =>
+              <List.Item key={item.id}>
+                <PictureItem
+                  picUrl={item.url}
+                  picCode={item.code}
+                  picName={item.name}
+                  checked={item.checked}
+                />
+              </List.Item>
+            }
+          />
+        </div>
+        <div className={styles.footer}></div>
+      </div>
+    );
+  }
+}

+ 15 - 0
src/components/Charts/Bar/index.d.ts

@@ -0,0 +1,15 @@
+import * as React from "react";
+export interface BarProps {
+  title: React.ReactNode;
+  color?: string;
+  padding?: [number, number, number, number];
+  height: number;
+  data: Array<{
+    x: string;
+    y: number;
+  }>;
+  autoLabel?: boolean;
+  style?: React.CSSProperties;
+}
+
+export default class Bar extends React.Component<BarProps, any> {}

+ 113 - 0
src/components/Charts/Bar/index.js

@@ -0,0 +1,113 @@
+import React, { Component } from 'react';
+import { Chart, Axis, Tooltip, Geom } from 'bizcharts';
+import Debounce from 'lodash-decorators/debounce';
+import Bind from 'lodash-decorators/bind';
+import autoHeight from '../autoHeight';
+import styles from '../index.less';
+
+@autoHeight()
+class Bar extends Component {
+  state = {
+    autoHideXLabels: false,
+  };
+
+  componentDidMount() {
+    window.addEventListener('resize', this.resize);
+  }
+
+  componentWillUnmount() {
+    window.removeEventListener('resize', this.resize);
+  }
+
+  @Bind()
+  @Debounce(400)
+  resize() {
+    if (!this.node) {
+      return;
+    }
+    const canvasWidth = this.node.parentNode.clientWidth;
+    const { data = [], autoLabel = true } = this.props;
+    if (!autoLabel) {
+      return;
+    }
+    const minWidth = data.length * 30;
+    const { autoHideXLabels } = this.state;
+
+    if (canvasWidth <= minWidth) {
+      if (!autoHideXLabels) {
+        this.setState({
+          autoHideXLabels: true,
+        });
+      }
+    } else if (autoHideXLabels) {
+      this.setState({
+        autoHideXLabels: false,
+      });
+    }
+  }
+
+  handleRoot = (n) => {
+    this.root = n;
+  };
+
+  handleRef = (n) => {
+    this.node = n;
+  };
+
+  render() {
+    const {
+      height,
+      title,
+      forceFit = true,
+      data,
+      color = 'rgba(24, 144, 255, 0.85)',
+      padding,
+    } = this.props;
+
+    const { autoHideXLabels } = this.state;
+
+    const scale = {
+      x: {
+        type: 'cat',
+      },
+      y: {
+        min: 0,
+      },
+    };
+
+    const tooltip = [
+      'x*y',
+      (x, y) => ({
+        name: x,
+        value: y,
+      }),
+    ];
+
+    return (
+      <div className={styles.chart} style={{ height }} ref={this.handleRoot}>
+        <div ref={this.handleRef}>
+          {title && <h4 style={{ marginBottom: 20 }}>{title}</h4>}
+          <Chart
+            scale={scale}
+            height={title ? height - 41 : height}
+            forceFit={forceFit}
+            data={data}
+            padding={padding || 'auto'}
+          >
+            <Axis
+              name="x"
+              title={false}
+              label={autoHideXLabels ? false : {}}
+              tickLine={autoHideXLabels ? false : {}}
+            />
+            <Axis name="y" min={0} />
+            <Tooltip showTitle={false} crosshairs={false} />
+            <Geom type="interval" position="x*y" color={color} tooltip={tooltip} />
+          </Chart>
+        </div>
+      </div>
+    );
+  }
+}
+
+export default Bar;

+ 12 - 0
src/components/Charts/ChartCard/index.d.ts

@@ -0,0 +1,12 @@
+import * as React from "react";
+export interface ChartCardProps {
+  title: React.ReactNode;
+  action?: React.ReactNode;
+  total?: React.ReactNode | number;
+  footer?: React.ReactNode;
+  contentHeight?: number;
+  avatar?: React.ReactNode;
+  style?: React.CSSProperties;
+}
+
+export default class ChartCard extends React.Component<ChartCardProps, any> {}

+ 60 - 0
src/components/Charts/ChartCard/index.js

@@ -0,0 +1,60 @@
+import React from 'react';
+import { Card, Spin } from 'antd';
+import classNames from 'classnames';
+
+import styles from './index.less';
+
+const ChartCard = ({
+  loading = false, contentHeight, title, avatar, action, total, footer, children, ...rest
+}) => {
+  const content = (
+    <div className={styles.chartCard}>
+      <div
+        className={classNames(styles.chartTop, { [styles.chartTopMargin]: (!children && !footer) })}
+      >
+        <div className={styles.avatar}>
+          {
+            avatar
+          }
+        </div>
+        <div className={styles.metaWrap}>
+          <div className={styles.meta}>
+            <span className={styles.title}>{title}</span>
+            <span className={styles.action}>{action}</span>
+          </div>
+          {
+            // eslint-disable-next-line
+            (total !== undefined) && (<div className={styles.total} dangerouslySetInnerHTML={{ __html: total }} />)
+          }
+        </div>
+      </div>
+      {
+        children && (
+          <div className={styles.content} style={{ height: contentHeight || 'auto' }}>
+            <div className={contentHeight && styles.contentFixed}>
+              {children}
+            </div>
+          </div>
+        )
+      }
+      {
+        footer && (
+          <div className={classNames(styles.footer, { [styles.footerMargin]: !children })}>
+            {footer}
+          </div>
+        )
+      }
+    </div>
+  );
+
+  return (
+    <Card
+      bodyStyle={{ padding: '20px 24px 8px 24px' }}
+      {...rest}
+    >
+      {<Spin spinning={loading} wrapperClassName={styles.spin}>{content}</Spin>}
+    </Card>
+  );
+};
+
+export default ChartCard;

+ 78 - 0
src/components/Charts/ChartCard/index.less

@@ -0,0 +1,78 @@
+@import "~antd/lib/style/themes/default.less";
+
+.chartCard {
+  position: relative;
+  .chartTop {
+    position: relative;
+    overflow: hidden;
+    width: 100%;
+  }
+  .chartTopMargin {
+    margin-bottom: 12px;
+  }
+  .chartTopHasMargin {
+    margin-bottom: 20px;
+  }
+  .metaWrap {
+    float: left;
+  }
+  .avatar {
+    position: relative;
+    top: 4px;
+    float: left;
+    margin-right: 20px;
+    img {
+      border-radius: 100%;
+    }
+  }
+  .meta {
+    color: @text-color-secondary;
+    font-size: @font-size-base;
+    line-height: 22px;
+    height: 22px;
+  }
+  .action {
+    cursor: pointer;
+    position: absolute;
+    top: 0;
+    right: 0;
+  }
+  .total {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    white-space: nowrap;
+    color: @heading-color;
+    margin-top: 4px;
+    margin-bottom: 0;
+    font-size: 30px;
+    line-height: 38px;
+    height: 38px;
+  }
+  .content {
+    margin-bottom: 12px;
+    position: relative;
+    width: 100%;
+  }
+  .contentFixed {
+    position: absolute;
+    left: 0;
+    bottom: 0;
+    width: 100%;
+  }
+  .footer {
+    border-top: 1px solid @border-color-split;
+    padding-top: 9px;
+    margin-top: 8px;
+    & > * {
+      position: relative;
+    }
+  }
+  .footerMargin {
+    margin-top: 20px;
+  }
+}
+
+.spin :global(.ant-spin-container) {
+  overflow: visible;
+}

+ 8 - 0
src/components/Charts/Field/index.d.ts

@@ -0,0 +1,8 @@
+import * as React from "react";
+export interface FieldProps {
+  label: React.ReactNode;
+  value: React.ReactNode;
+  style?: React.CSSProperties;
+}
+
+export default class Field extends React.Component<FieldProps, any> {}

+ 12 - 0
src/components/Charts/Field/index.js

@@ -0,0 +1,12 @@
+import React from 'react';
+
+import styles from './index.less';
+
+const Field = ({ label, value, ...rest }) => (
+  <div className={styles.field} {...rest}>
+    <span>{label}</span>
+    <span>{value}</span>
+  </div>
+);
+
+export default Field;

+ 16 - 0
src/components/Charts/Field/index.less

@@ -0,0 +1,16 @@
+@import "~antd/lib/style/themes/default.less";
+
+.field {
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  margin: 0;
+  span {
+    font-size: @font-size-base;
+    line-height: 22px;
+  }
+  span:last-child {
+    margin-left: 8px;
+    color: @heading-color;
+  }
+}

+ 11 - 0
src/components/Charts/Gauge/index.d.ts

@@ -0,0 +1,11 @@
+import * as React from "react";
+export interface GaugeProps {
+  title: React.ReactNode;
+  color?: string;
+  height: number;
+  bgColor?: number;
+  percent: number;
+  style?: React.CSSProperties;
+}
+
+export default class Gauge extends React.Component<GaugeProps, any> {}

+ 167 - 0
src/components/Charts/Gauge/index.js

@@ -0,0 +1,167 @@
+import React from 'react';
+import { Chart, Geom, Axis, Coord, Guide, Shape } from 'bizcharts';
+import autoHeight from '../autoHeight';
+
+const { Arc, Html, Line } = Guide;
+
+const defaultFormatter = (val) => {
+  switch (val) {
+    case '2':
+      return '差';
+    case '4':
+      return '中';
+    case '6':
+      return '良';
+    case '8':
+      return '优';
+    default:
+      return '';
+  }
+};
+
+Shape.registerShape('point', 'pointer', {
+  drawShape(cfg, group) {
+    let point = cfg.points[0];
+    point = this.parsePoint(point);
+    const center = this.parsePoint({
+      x: 0,
+      y: 0,
+    });
+    group.addShape('line', {
+      attrs: {
+        x1: center.x,
+        y1: center.y,
+        x2: point.x,
+        y2: point.y,
+        stroke: cfg.color,
+        lineWidth: 2,
+        lineCap: 'round',
+      },
+    });
+    return group.addShape('circle', {
+      attrs: {
+        x: center.x,
+        y: center.y,
+        r: 6,
+        stroke: cfg.color,
+        lineWidth: 3,
+        fill: '#fff',
+      },
+    });
+  },
+});
+
+@autoHeight()
+export default class Gauge extends React.Component {
+  render() {
+    const {
+      title,
+      height,
+      percent,
+      forceFit = true,
+      formatter = defaultFormatter,
+      color = '#2F9CFF',
+      bgColor = '#F0F2F5',
+    } = this.props;
+    const cols = {
+      value: {
+        type: 'linear',
+        min: 0,
+        max: 10,
+        tickCount: 6,
+        nice: true,
+      },
+    };
+    const data = [{ value: percent / 10 }];
+    return (
+      <Chart height={height} data={data} scale={cols} padding={[-16, 0, 16, 0]} forceFit={forceFit}>
+        <Coord type="polar" startAngle={-1.25 * Math.PI} endAngle={0.25 * Math.PI} radius={0.8} />
+        <Axis name="1" line={null} />
+        <Axis
+          line={null}
+          tickLine={null}
+          subTickLine={null}
+          name="value"
+          zIndex={2}
+          gird={null}
+          label={{
+            offset: -12,
+            formatter,
+            textStyle: {
+              fontSize: 12,
+              fill: 'rgba(0, 0, 0, 0.65)',
+              textAlign: 'center',
+            },
+          }}
+        />
+        <Guide>
+          <Line
+            start={[3, 0.905]}
+            end={[3, 0.85]}
+            lineStyle={{
+              stroke: color,
+              lineDash: null,
+              lineWidth: 2,
+            }}
+          />
+          <Line
+            start={[5, 0.905]}
+            end={[5, 0.85]}
+            lineStyle={{
+              stroke: color,
+              lineDash: null,
+              lineWidth: 3,
+            }}
+          />
+          <Line
+            start={[7, 0.905]}
+            end={[7, 0.85]}
+            lineStyle={{
+              stroke: color,
+              lineDash: null,
+              lineWidth: 3,
+            }}
+          />
+          <Arc
+            zIndex={0}
+            start={[0, 0.965]}
+            end={[10, 0.965]}
+            style={{
+              stroke: bgColor,
+              lineWidth: 10,
+            }}
+          />
+          <Arc
+            zIndex={1}
+            start={[0, 0.965]}
+            end={[data[0].value, 0.965]}
+            style={{
+              stroke: color,
+              lineWidth: 10,
+            }}
+          />
+          <Html
+            position={['50%', '95%']}
+            html={() => {
+              return `
+                <div style="width: 300px;text-align: center;font-size: 12px!important;">
+                  <p style="font-size: 14px; color: rgba(0,0,0,0.43);margin: 0;">${title}</p>
+                  <p style="font-size: 24px;color: rgba(0,0,0,0.85);margin: 0;">
+                    ${data[0].value * 10}%
+                  </p>
+                </div>`;
+            }}
+          />
+        </Guide>
+        <Geom
+          line={false}
+          type="point"
+          position="value*1"
+          shape="pointer"
+          color={color}
+          active={false}
+        />
+      </Chart>
+    );
+  }
+}

+ 29 - 0
src/components/Charts/MiniArea/index.d.ts

@@ -0,0 +1,29 @@
+import * as React from "react";
+
+// g2已经更新到3.0
+// 不带的写了
+
+export interface Axis {
+  title: any;
+  line: any;
+  gridAlign: any;
+  labels: any;
+  tickLine: any;
+  grid: any;
+}
+
+export interface MiniAreaProps {
+  color?: string;
+  height: number;
+  borderColor?: string;
+  line?: boolean;
+  animate?: boolean;
+  xAxis?: Axis;
+  yAxis?: Axis;
+  data: Array<{
+    x: number;
+    y: number;
+  }>;
+}
+
+export default class MiniArea extends React.Component<MiniAreaProps, any> {}

+ 106 - 0
src/components/Charts/MiniArea/index.js

@@ -0,0 +1,106 @@
+import React from 'react';
+import { Chart, Axis, Tooltip, Geom } from 'bizcharts';
+import autoHeight from '../autoHeight';
+import styles from '../index.less';
+
+@autoHeight()
+export default class MiniArea extends React.Component {
+  render() {
+    const {
+      height,
+      data = [],
+      forceFit = true,
+      color = 'rgba(24, 144, 255, 0.2)',
+      borderColor = '#1089ff',
+      scale = {},
+      borderWidth = 2,
+      line,
+      xAxis,
+      yAxis,
+      animate = true,
+    } = this.props;
+
+    const padding = [36, 5, 30, 5];
+
+    const scaleProps = {
+      x: {
+        type: 'cat',
+        range: [0, 1],
+        ...scale.x,
+      },
+      y: {
+        min: 0,
+        ...scale.y,
+      },
+    };
+
+    const tooltip = [
+      'x*y',
+      (x, y) => ({
+        name: x,
+        value: y,
+      }),
+    ];
+
+    const chartHeight = height + 54;
+
+    return (
+      <div className={styles.miniChart} style={{ height }}>
+        <div className={styles.chartContent}>
+          {height > 0 && (
+            <Chart
+              animate={animate}
+              scale={scaleProps}
+              height={chartHeight}
+              forceFit={forceFit}
+              data={data}
+              padding={padding}
+            >
+              <Axis
+                key="axis-x"
+                name="x"
+                label={false}
+                line={false}
+                tickLine={false}
+                grid={false}
+                {...xAxis}
+              />
+              <Axis
+                key="axis-y"
+                name="y"
+                label={false}
+                line={false}
+                tickLine={false}
+                grid={false}
+                {...yAxis}
+              />
+              <Tooltip showTitle={false} crosshairs={false} />
+              <Geom
+                type="area"
+                position="x*y"
+                color={color}
+                tooltip={tooltip}
+                shape="smooth"
+                style={{
+                  fillOpacity: 1,
+                }}
+              />
+              {line ? (
+                <Geom
+                  type="line"
+                  position="x*y"
+                  shape="smooth"
+                  color={borderColor}
+                  size={borderWidth}
+                  tooltip={false}
+                />
+              ) : (
+                <span style={{ display: 'none' }} />
+              )}
+            </Chart>
+          )}
+        </div>
+      </div>
+    );
+  }
+}

+ 12 - 0
src/components/Charts/MiniBar/index.d.ts

@@ -0,0 +1,12 @@
+import * as React from 'react';
+export interface MiniBarProps {
+  color?: string;
+  height: number;
+  data: Array<{
+    x: number | string;
+    y: number;
+  }>;
+  style?: React.CSSProperties;
+}
+
+export default class MiniBar extends React.Component<MiniBarProps, any> {}

+ 50 - 0
src/components/Charts/MiniBar/index.js

@@ -0,0 +1,50 @@
+import React from 'react';
+import { Chart, Tooltip, Geom } from 'bizcharts';
+import autoHeight from '../autoHeight';
+import styles from '../index.less';
+
+@autoHeight()
+export default class MiniBar extends React.Component {
+  render() {
+    const { height, forceFit = true, color = '#1890FF', data = [] } = this.props;
+
+    const scale = {
+      x: {
+        type: 'cat',
+      },
+      y: {
+        min: 0,
+      },
+    };
+
+    const padding = [36, 5, 30, 5];
+
+    const tooltip = [
+      'x*y',
+      (x, y) => ({
+        name: x,
+        value: y,
+      }),
+    ];
+
+    // for tooltip not to be hide
+    const chartHeight = height + 54;
+
+    return (
+      <div className={styles.miniChart} style={{ height }}>
+        <div className={styles.chartContent}>
+          <Chart
+            scale={scale}
+            height={chartHeight}
+            forceFit={forceFit}
+            data={data}
+            padding={padding}
+          >
+            <Tooltip showTitle={false} crosshairs={false} />
+            <Geom type="interval" position="x*y" color={color} tooltip={tooltip} />
+          </Chart>
+        </div>
+      </div>
+    );
+  }
+}

+ 13 - 0
src/components/Charts/MiniProgress/index.d.ts

@@ -0,0 +1,13 @@
+import * as React from "react";
+export interface MiniProgressProps {
+  target: number;
+  color?: string;
+  strokeWidth?: number;
+  percent?: number;
+  style?: React.CSSProperties;
+}
+
+export default class MiniProgress extends React.Component<
+  MiniProgressProps,
+  any
+> {}

+ 30 - 0
src/components/Charts/MiniProgress/index.js

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

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

@@ -0,0 +1,35 @@
+@import "~antd/lib/style/themes/default.less";
+
+.miniProgress {
+  padding: 5px 0;
+  position: relative;
+  width: 100%;
+  .progressWrap {
+    background-color: @background-color-base;
+    position: relative;
+  }
+  .progress {
+    transition: all .4s cubic-bezier(.08, .82, .17, 1) 0s;
+    border-radius: 1px 0 0 1px;
+    background-color: @primary-color;
+    width: 0;
+    height: 100%;
+  }
+  .target {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    span {
+      border-radius: 100px;
+      position: absolute;
+      top: 0;
+      left: 0;
+      height: 4px;
+      width: 2px;
+    }
+    span:last-child {
+      top: auto;
+      bottom: 0;
+    }
+  }
+}

+ 20 - 0
src/components/Charts/Pie/index.d.ts

@@ -0,0 +1,20 @@
+import * as React from 'react';
+export interface PieProps {
+  animate?: boolean;
+  color?: string;
+  height: number;
+  hasLegend?: boolean;
+  padding?: [number, number, number, number];
+  percent?: number;
+  data?: Array<{
+    x: string | string;
+    y: number;
+  }>;
+  total?: string;
+  title?: React.ReactNode;
+  tooltip?: boolean;
+  valueFormat?: (value: string) => string;
+  subTitle?: React.ReactNode;
+}
+
+export default class Pie extends React.Component<PieProps, any> {}

+ 256 - 0
src/components/Charts/Pie/index.js

@@ -0,0 +1,256 @@
+import React, { Component } from 'react';
+import { Chart, Tooltip, Geom, Coord } from 'bizcharts';
+import { DataView } from '@antv/data-set';
+import { Divider } from 'antd';
+import classNames from 'classnames';
+import ReactFitText from 'react-fittext';
+import Debounce from 'lodash-decorators/debounce';
+import Bind from 'lodash-decorators/bind';
+import autoHeight from '../autoHeight';
+
+import styles from './index.less';
+
+/* eslint react/no-danger:0 */
+@autoHeight()
+export default class Pie extends Component {
+  state = {
+    legendData: [],
+    legendBlock: false,
+  };
+
+  componentDidMount() {
+    this.getLengendData();
+    this.resize();
+    window.addEventListener('resize', this.resize);
+  }
+
+  componentWillReceiveProps(nextProps) {
+    if (this.props.data !== nextProps.data) {
+      // because of charts data create when rendered
+      // so there is a trick for get rendered time
+      this.setState(
+        {
+          legendData: [...this.state.legendData],
+        },
+        () => {
+          this.getLengendData();
+        }
+      );
+    }
+  }
+
+  componentWillUnmount() {
+    window.removeEventListener('resize', this.resize);
+    this.resize.cancel();
+  }
+
+  getG2Instance = (chart) => {
+    this.chart = chart;
+  };
+
+  // for custom lengend view
+  getLengendData = () => {
+    if (!this.chart) return;
+    const geom = this.chart.getAllGeoms()[0]; // 获取所有的图形
+    const items = geom.get('dataArray') || []; // 获取图形对应的
+
+    const legendData = items.map((item) => {
+      /* eslint no-underscore-dangle:0 */
+      const origin = item[0]._origin;
+      origin.color = item[0].color;
+      origin.checked = true;
+      return origin;
+    });
+
+    this.setState({
+      legendData,
+    });
+  };
+
+  // for window resize auto responsive legend
+  @Bind()
+  @Debounce(300)
+  resize() {
+    const { hasLegend } = this.props;
+    if (!hasLegend || !this.root) {
+      window.removeEventListener('resize', this.resize);
+      return;
+    }
+    if (this.root.parentNode.clientWidth <= 380) {
+      if (!this.state.legendBlock) {
+        this.setState({
+          legendBlock: true,
+        });
+      }
+    } else if (this.state.legendBlock) {
+      this.setState({
+        legendBlock: false,
+      });
+    }
+  }
+
+  handleRoot = (n) => {
+    this.root = n;
+  };
+
+  handleLegendClick = (item, i) => {
+    const newItem = item;
+    newItem.checked = !newItem.checked;
+
+    const { legendData } = this.state;
+    legendData[i] = newItem;
+
+    const filteredLegendData = legendData.filter(l => l.checked).map(l => l.x);
+
+    if (this.chart) {
+      this.chart.filter('x', val => filteredLegendData.indexOf(val) > -1);
+    }
+
+    this.setState({
+      legendData,
+    });
+  };
+
+  render() {
+    const {
+      valueFormat,
+      subTitle,
+      total,
+      hasLegend = false,
+      className,
+      style,
+      height,
+      forceFit = true,
+      percent = 0,
+      color,
+      inner = 0.75,
+      animate = true,
+      colors,
+      lineWidth = 1,
+    } = this.props;
+
+    const { legendData, legendBlock } = this.state;
+    const pieClassName = classNames(styles.pie, className, {
+      [styles.hasLegend]: !!hasLegend,
+      [styles.legendBlock]: legendBlock,
+    });
+
+    const defaultColors = colors;
+    let data = this.props.data || [];
+    let selected = this.props.selected || true;
+    let tooltip = this.props.tooltip || true;
+    let formatColor;
+
+    const scale = {
+      x: {
+        type: 'cat',
+        range: [0, 1],
+      },
+      y: {
+        min: 0,
+      },
+    };
+
+    if (percent) {
+      selected = false;
+      tooltip = false;
+      formatColor = (value) => {
+        if (value === '占比') {
+          return color || 'rgba(24, 144, 255, 0.85)';
+        } else {
+          return '#F0F2F5';
+        }
+      };
+
+      data = [
+        {
+          x: '占比',
+          y: parseFloat(percent),
+        },
+        {
+          x: '反比',
+          y: 100 - parseFloat(percent),
+        },
+      ];
+    }
+
+    const tooltipFormat = [
+      'x*percent',
+      (x, p) => ({
+        name: x,
+        value: `${(p * 100).toFixed(2)}%`,
+      }),
+    ];
+
+    const padding = [12, 0, 12, 0];
+
+    const dv = new DataView();
+    dv.source(data).transform({
+      type: 'percent',
+      field: 'y',
+      dimension: 'x',
+      as: 'percent',
+    });
+
+    return (
+      <div ref={this.handleRoot} className={pieClassName} style={style}>
+        <ReactFitText maxFontSize={25}>
+          <div className={styles.chart}>
+            <Chart
+              scale={scale}
+              height={height}
+              forceFit={forceFit}
+              data={dv}
+              padding={padding}
+              animate={animate}
+              onGetG2Instance={this.getG2Instance}
+            >
+              {!!tooltip && <Tooltip showTitle={false} />}
+              <Coord type="theta" innerRadius={inner} />
+              <Geom
+                style={{ lineWidth, stroke: '#fff' }}
+                tooltip={tooltip && tooltipFormat}
+                type="intervalStack"
+                position="percent"
+                color={['x', percent ? formatColor : defaultColors]}
+                selected={selected}
+              />
+            </Chart>
+
+            {(subTitle || total) && (
+              <div className={styles.total}>
+                {subTitle && <h4 className="pie-sub-title">{subTitle}</h4>}
+                {/* eslint-disable-next-line */}
+                {total && <div className="pie-stat" dangerouslySetInnerHTML={{ __html: total }} />}
+              </div>
+            )}
+          </div>
+        </ReactFitText>
+
+        {hasLegend && (
+          <ul className={styles.legend}>
+            {legendData.map((item, i) => (
+              <li key={item.x} onClick={() => this.handleLegendClick(item, i)}>
+                <span
+                  className={styles.dot}
+                  style={{ backgroundColor: !item.checked ? '#aaa' : item.color }}
+                />
+                <span className={styles.legendTitle}>{item.x}</span>
+                <Divider type="vertical" />
+                <span className={styles.percent}>
+                  {`${(isNaN(item.percent) ? 0 : item.percent * 100).toFixed(2)}%`}
+                </span>
+                <span
+                  className={styles.value}
+                  dangerouslySetInnerHTML={{
+                    __html: valueFormat ? valueFormat(item.y) : item.y,
+                  }}
+                />
+              </li>
+            ))}
+          </ul>
+        )}
+      </div>
+    );
+  }
+}

+ 94 - 0
src/components/Charts/Pie/index.less

@@ -0,0 +1,94 @@
+@import "~antd/lib/style/themes/default.less";
+
+.pie {
+  position: relative;
+  .chart {
+    position: relative;
+  }
+  &.hasLegend .chart {
+    width: ~"calc(100% - 240px)";
+  }
+  .legend {
+    position: absolute;
+    right: 0;
+    min-width: 200px;
+    top: 50%;
+    transform: translateY(-50%);
+    margin: 0 20px;
+    list-style: none;
+    padding: 0;
+    li {
+      cursor: pointer;
+      margin-bottom: 16px;
+      height: 22px;
+      line-height: 22px;
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
+  }
+  .dot {
+    border-radius: 8px;
+    display: inline-block;
+    margin-right: 8px;
+    position: relative;
+    top: -1px;
+    height: 8px;
+    width: 8px;
+  }
+  .line {
+    background-color: @border-color-split;
+    display: inline-block;
+    margin-right: 8px;
+    width: 1px;
+    height: 16px;
+  }
+  .legendTitle {
+    color: @text-color;
+  }
+  .percent {
+    color: @text-color-secondary;
+  }
+  .value {
+    position: absolute;
+    right: 0;
+  }
+  .title {
+    margin-bottom: 8px;
+  }
+  .total {
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    text-align: center;
+    height: 62px;
+    transform: translate(-50%, -50%);
+    & > h4 {
+      color: @text-color-secondary;
+      font-size: 14px;
+      line-height: 22px;
+      height: 22px;
+      margin-bottom: 8px;
+      font-weight: normal;
+    }
+    & > p {
+      color: @heading-color;
+      display: block;
+      font-size: 1.2em;
+      height: 32px;
+      line-height: 32px;
+      white-space: nowrap;
+    }
+  }
+}
+
+.legendBlock {
+  &.hasLegend .chart {
+    width: 100%;
+    margin: 0 0 32px 0;
+  }
+  .legend {
+    position: relative;
+    transform: none;
+  }
+}

+ 15 - 0
src/components/Charts/Radar/index.d.ts

@@ -0,0 +1,15 @@
+import * as React from "react";
+export interface RadarProps {
+  title?: React.ReactNode;
+  height: number;
+  padding?: [number, number, number, number];
+  hasLegend?: boolean;
+  data: Array<{
+    name: string;
+    label: string;
+    value: string;
+  }>;
+  style?: React.CSSProperties;
+}
+
+export default class Radar extends React.Component<RadarProps, any> {}

+ 187 - 0
src/components/Charts/Radar/index.js

@@ -0,0 +1,187 @@
+import React, { Component } from 'react';
+import { Chart, Tooltip, Geom, Coord, Axis } from 'bizcharts';
+import { Row, Col } from 'antd';
+import autoHeight from '../autoHeight';
+import styles from './index.less';
+
+/* eslint react/no-danger:0 */
+@autoHeight()
+export default class Radar extends Component {
+  state = {
+    legendData: [],
+  };
+
+  componentDidMount() {
+    this.getLengendData();
+  }
+
+  componentWillReceiveProps(nextProps) {
+    if (this.props.data !== nextProps.data) {
+      this.getLengendData();
+    }
+  }
+
+  getG2Instance = (chart) => {
+    this.chart = chart;
+  };
+
+  // for custom lengend view
+  getLengendData = () => {
+    if (!this.chart) return;
+    const geom = this.chart.getAllGeoms()[0]; // 获取所有的图形
+    const items = geom.get('dataArray') || []; // 获取图形对应的
+
+    const legendData = items.map((item) => {
+      // eslint-disable-next-line
+      const origins = item.map(t => t._origin);
+      const result = {
+        name: origins[0].name,
+        color: item[0].color,
+        checked: true,
+        value: origins.reduce((p, n) => p + n.value, 0),
+      };
+
+      return result;
+    });
+
+    this.setState({
+      legendData,
+    });
+  };
+
+  handleRef = (n) => {
+    this.node = n;
+  };
+
+  handleLegendClick = (item, i) => {
+    const newItem = item;
+    newItem.checked = !newItem.checked;
+
+    const { legendData } = this.state;
+    legendData[i] = newItem;
+
+    const filteredLegendData = legendData
+      .filter(l => l.checked)
+      .map(l => l.name);
+
+    if (this.chart) {
+      this.chart.filter('name', val => filteredLegendData.indexOf(val) > -1);
+      this.chart.repaint();
+    }
+
+    this.setState({
+      legendData,
+    });
+  };
+
+  render() {
+    const defaultColors = [
+      '#1890FF',
+      '#FACC14',
+      '#2FC25B',
+      '#8543E0',
+      '#F04864',
+      '#13C2C2',
+      '#fa8c16',
+      '#a0d911',
+    ];
+
+    const {
+      data = [],
+      height = 0,
+      title,
+      hasLegend = false,
+      forceFit = true,
+      tickCount = 4,
+      padding = [35, 30, 16, 30],
+      animate = true,
+      colors = defaultColors,
+    } = this.props;
+
+    const { legendData } = this.state;
+
+    const scale = {
+      value: {
+        min: 0,
+        tickCount,
+      },
+    };
+
+    const chartHeight = height - (hasLegend ? 80 : 22);
+
+    return (
+      <div className={styles.radar} style={{ height }}>
+        {title && <h4>{title}</h4>}
+        <Chart
+          scale={scale}
+          height={chartHeight}
+          forceFit={forceFit}
+          data={data}
+          padding={padding}
+          animate={animate}
+          onGetG2Instance={this.getG2Instance}
+        >
+          <Tooltip />
+          <Coord type="polar" />
+          <Axis
+            name="label"
+            line={null}
+            tickLine={null}
+            grid={{
+              lineStyle: {
+                lineDash: null,
+              },
+              hideFirstLine: false,
+            }}
+          />
+          <Axis
+            name="value"
+            grid={{
+              type: 'polygon',
+              lineStyle: {
+                lineDash: null,
+              },
+            }}
+          />
+          <Geom
+            type="line"
+            position="label*value"
+            color={['name', colors]}
+            size={1}
+          />
+          <Geom
+            type="point"
+            position="label*value"
+            color={['name', colors]}
+            shape="circle"
+            size={3}
+          />
+        </Chart>
+        {hasLegend && (
+          <Row className={styles.legend}>
+            {legendData.map((item, i) => (
+              <Col
+                span={24 / legendData.length}
+                key={item.name}
+                onClick={() => this.handleLegendClick(item, i)}
+              >
+                <div className={styles.legendItem}>
+                  <p>
+                    <span
+                      className={styles.dot}
+                      style={{
+                        backgroundColor: !item.checked ? '#aaa' : item.color,
+                      }}
+                    />
+                    <span>{item.name}</span>
+                  </p>
+                  <h6>{item.value}</h6>
+                </div>
+              </Col>
+           ))}
+          </Row>
+        )}
+      </div>
+    );
+  }
+}

+ 46 - 0
src/components/Charts/Radar/index.less

@@ -0,0 +1,46 @@
+@import "~antd/lib/style/themes/default.less";
+
+.radar {
+  .legend {
+    margin-top: 16px;
+    .legendItem {
+      position: relative;
+      text-align: center;
+      cursor: pointer;
+      color: @text-color-secondary;
+      line-height: 22px;
+      p {
+        margin: 0;
+      }
+      h6 {
+        color: @heading-color;
+        padding-left: 16px;
+        font-size: 24px;
+        line-height: 32px;
+        margin-top: 4px;
+        margin-bottom: 0;
+      }
+      &:after {
+        background-color: @border-color-split;
+        position: absolute;
+        top: 8px;
+        right: 0;
+        height: 40px;
+        width: 1px;
+        content: '';
+      }
+    }
+    > :last-child .legendItem:after {
+      display: none;
+    }
+    .dot {
+      border-radius: 6px;
+      display: inline-block;
+      margin-right: 6px;
+      position: relative;
+      top: -1px;
+      height: 6px;
+      width: 6px;
+    }
+  }
+}

+ 11 - 0
src/components/Charts/TagCloud/index.d.ts

@@ -0,0 +1,11 @@
+import * as React from "react";
+export interface TagCloudProps {
+  data: Array<{
+    name: string;
+    value: number;
+  }>;
+  height: number;
+  style?: React.CSSProperties;
+}
+
+export default class TagCloud extends React.Component<TagCloudProps, any> {}

+ 164 - 0
src/components/Charts/TagCloud/index.js

@@ -0,0 +1,164 @@
+import React, { Component } from 'react';
+import { Chart, Geom, Coord, Shape } from 'bizcharts';
+import DataSet from '@antv/data-set';
+import Debounce from 'lodash-decorators/debounce';
+import Bind from 'lodash-decorators/bind';
+import classNames from 'classnames';
+import autoHeight from '../autoHeight';
+import styles from './index.less';
+
+/* eslint no-underscore-dangle: 0 */
+/* eslint no-param-reassign: 0 */
+
+const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png';
+
+@autoHeight()
+class TagCloud extends Component {
+  state = {
+    dv: null,
+  };
+
+  componentDidMount() {
+    this.initTagCloud();
+    this.renderChart();
+    window.addEventListener('resize', this.resize);
+  }
+
+  componentWillReceiveProps(nextProps) {
+    if (JSON.stringify(nextProps.data) !== JSON.stringify(this.props.data)) {
+      this.renderChart(nextProps);
+    }
+  }
+
+  componentWillUnmount() {
+    this.isUnmount = true;
+    window.removeEventListener('resize', this.resize);
+  }
+
+  resize = () => {
+    this.renderChart();
+  };
+
+  saveRootRef = (node) => {
+    this.root = node;
+  };
+
+  initTagCloud = () => {
+    function getTextAttrs(cfg) {
+      return Object.assign(
+        {},
+        {
+          fillOpacity: cfg.opacity,
+          fontSize: cfg.origin._origin.size,
+          rotate: cfg.origin._origin.rotate,
+          text: cfg.origin._origin.text,
+          textAlign: 'center',
+          fontFamily: cfg.origin._origin.font,
+          fill: cfg.color,
+          textBaseline: 'Alphabetic',
+        },
+        cfg.style
+      );
+    }
+
+    // 给point注册一个词云的shape
+    Shape.registerShape('point', 'cloud', {
+      drawShape(cfg, container) {
+        const attrs = getTextAttrs(cfg);
+        return container.addShape('text', {
+          attrs: Object.assign(attrs, {
+            x: cfg.x,
+            y: cfg.y,
+          }),
+        });
+      },
+    });
+  };
+
+  @Bind()
+  @Debounce(500)
+  renderChart = (nextProps) => {
+    // const colors = ['#1890FF', '#41D9C7', '#2FC25B', '#FACC14', '#9AE65C'];
+    const { data, height } = nextProps || this.props;
+
+    if (data.length < 1 || !this.root) {
+      return;
+    }
+
+    const h = height * 4;
+    const w = this.root.offsetWidth * 4;
+
+    const onload = () => {
+      const dv = new DataSet.View().source(data);
+      const range = dv.range('value');
+      const [min, max] = range;
+      dv.transform({
+        type: 'tag-cloud',
+        fields: ['name', 'value'],
+        imageMask: this.imageMask,
+        font: 'Verdana',
+        size: [w, h], // 宽高设置最好根据 imageMask 做调整
+        padding: 5,
+        timeInterval: 5000, // max execute time
+        rotate() {
+          return 0;
+        },
+        fontSize(d) {
+          // eslint-disable-next-line
+          return Math.pow((d.value - min) / (max - min), 2) * (70 - 20) + 20;
+        },
+      });
+
+      if (this.isUnmount) {
+        return;
+      }
+
+      this.setState({
+        dv,
+        w,
+        h,
+      });
+    };
+
+    if (!this.imageMask) {
+      this.imageMask = new Image();
+      this.imageMask.crossOrigin = '';
+      this.imageMask.src = imgUrl;
+
+      this.imageMask.onload = onload;
+    } else {
+      onload();
+    }
+  };
+
+  render() {
+    const { className, height } = this.props;
+    const { dv, w, h } = this.state;
+
+    return (
+      <div
+        className={classNames(styles.tagCloud, className)}
+        style={{ width: '100%', height }}
+        ref={this.saveRootRef}
+      >
+        {dv && (
+          <Chart
+            width={w}
+            height={h}
+            data={dv}
+            padding={0}
+            scale={{
+              x: { nice: false },
+              y: { nice: false },
+            }}
+          >
+            <Coord reflect="y" />
+            <Geom type="point" position="x*y" color="text" shape="cloud" />
+          </Chart>
+        )}
+      </div>
+    );
+  }
+}
+
+export default TagCloud;

+ 7 - 0
src/components/Charts/TagCloud/index.less

@@ -0,0 +1,7 @@
+.tagCloud {
+  overflow: hidden;
+  canvas {
+    transform: scale(0.25);
+    transform-origin: 0 0;
+  }
+}

+ 17 - 0
src/components/Charts/TimelineChart/index.d.ts

@@ -0,0 +1,17 @@
+import * as React from "react";
+export interface TimelineChartProps {
+  data: Array<{
+    x: string;
+    y1: string;
+    y2: string;
+  }>;
+  titleMap: { y1: string; y2: string };
+  padding?: [number, number, number, number];
+  height?: number;
+  style?: React.CSSProperties;
+}
+
+export default class TimelineChart extends React.Component<
+  TimelineChartProps,
+  any
+> {}

+ 123 - 0
src/components/Charts/TimelineChart/index.js

@@ -0,0 +1,123 @@
+import React from 'react';
+import { Chart, Tooltip, Geom, Legend, Axis } from 'bizcharts';
+import DataSet from '@antv/data-set';
+import Slider from 'bizcharts-plugin-slider';
+import autoHeight from '../autoHeight';
+import styles from './index.less';
+
+@autoHeight()
+export default class TimelineChart extends React.Component {
+  render() {
+    const {
+      title,
+      height = 400,
+      padding = [60, 20, 40, 40],
+      titleMap = {
+        y1: 'y1',
+        y2: 'y2',
+      },
+      borderWidth = 2,
+      data = [
+        {
+          x: 0,
+          y1: 0,
+          y2: 0,
+        },
+      ],
+    } = this.props;
+
+    data.sort((a, b) => a.x - b.x);
+
+    let max;
+    if (data[0] && data[0].y1 && data[0].y2) {
+      max = Math.max(
+        [...data].sort((a, b) => b.y1 - a.y1)[0].y1,
+        [...data].sort((a, b) => b.y2 - a.y2)[0].y2
+      );
+    }
+
+    const ds = new DataSet({
+      state: {
+        start: data[0].x,
+        end: data[data.length - 1].x,
+      },
+    });
+
+    const dv = ds.createView();
+    dv
+      .source(data)
+      .transform({
+        type: 'filter',
+        callback: (obj) => {
+          const date = obj.x;
+          return date <= ds.state.end && date >= ds.state.start;
+        },
+      })
+      .transform({
+        type: 'map',
+        callback(row) {
+          const newRow = { ...row };
+          newRow[titleMap.y1] = row.y1;
+          newRow[titleMap.y2] = row.y2;
+          return newRow;
+        },
+      })
+      .transform({
+        type: 'fold',
+        fields: [titleMap.y1, titleMap.y2], // 展开字段集
+        key: 'key', // key字段
+        value: 'value', // value字段
+      });
+
+    const timeScale = {
+      type: 'time',
+      tickCount: 10,
+      mask: 'HH:MM',
+      range: [0, 1],
+    };
+
+    const cols = {
+      x: timeScale,
+      value: {
+        max,
+        min: 0,
+      },
+    };
+
+    const SliderGen = () => (
+      <Slider
+        padding={[0, padding[1] + 20, 0, padding[3]]}
+        width="auto"
+        height={26}
+        xAxis="x"
+        yAxis="y1"
+        scales={{ x: timeScale }}
+        data={data}
+        start={ds.state.start}
+        end={ds.state.end}
+        backgroundChart={{ type: 'line' }}
+        onChange={({ startValue, endValue }) => {
+          ds.setState('start', startValue);
+          ds.setState('end', endValue);
+        }}
+      />
+    );
+
+    return (
+      <div className={styles.timelineChart} style={{ height: height + 30 }}>
+        <div>
+          {title && <h4>{title}</h4>}
+          <Chart height={height} padding={padding} data={dv} scale={cols} forceFit>
+            <Axis name="x" />
+            <Tooltip />
+            <Legend name="key" position="top" />
+            <Geom type="line" position="x*value" size={borderWidth} color="key" />
+          </Chart>
+          <div style={{ marginRight: -20 }}>
+            <SliderGen />
+          </div>
+        </div>
+      </div>
+    );
+  }
+}

+ 3 - 0
src/components/Charts/TimelineChart/index.less

@@ -0,0 +1,3 @@
+.timelineChart {
+  background: #fff;
+}

+ 10 - 0
src/components/Charts/WaterWave/index.d.ts

@@ -0,0 +1,10 @@
+import * as React from 'react';
+export interface WaterWaveProps {
+  title: React.ReactNode;
+  color?: string;
+  height: number;
+  percent: number;
+  style?: React.CSSProperties;
+}
+
+export default class WaterWave extends React.Component<WaterWaveProps, any> {}

+ 197 - 0
src/components/Charts/WaterWave/index.js

@@ -0,0 +1,197 @@
+import React, { PureComponent } from 'react';
+import autoHeight from '../autoHeight';
+import styles from './index.less';
+
+/* eslint no-return-assign: 0 */
+/* eslint no-mixed-operators: 0 */
+// riddle: https://riddle.alibaba-inc.com/riddles/2d9a4b90
+
+@autoHeight()
+export default class WaterWave extends PureComponent {
+  state = {
+    radio: 1,
+  };
+
+  componentDidMount() {
+    this.renderChart();
+    this.resize();
+
+    window.addEventListener('resize', this.resize);
+  }
+
+  componentWillUnmount() {
+    cancelAnimationFrame(this.timer);
+    if (this.node) {
+      this.node.innerHTML = '';
+    }
+    window.removeEventListener('resize', this.resize);
+  }
+
+  resize = () => {
+    const { height } = this.props;
+    const { offsetWidth } = this.root.parentNode;
+    this.setState({
+      radio: offsetWidth < height ? offsetWidth / height : 1,
+    });
+  };
+
+  renderChart() {
+    const { percent, color = '#1890FF' } = this.props;
+    const data = percent / 100;
+    const self = this;
+
+    if (!this.node || !data) {
+      return;
+    }
+
+    const canvas = this.node;
+    const ctx = canvas.getContext('2d');
+
+    const canvasWidth = canvas.width;
+    const canvasHeight = canvas.height;
+    const radius = canvasWidth / 2;
+    const lineWidth = 2;
+    const cR = radius - lineWidth;
+
+    ctx.beginPath();
+    ctx.lineWidth = lineWidth * 2;
+
+    const axisLength = canvasWidth - lineWidth;
+    const unit = axisLength / 8;
+    const range = 0.2; // 振幅
+    let currRange = range;
+    const xOffset = lineWidth;
+    let sp = 0; // 周期偏移量
+    let currData = 0;
+    const waveupsp = 0.005; // 水波上涨速度
+
+    let arcStack = [];
+    const bR = radius - lineWidth;
+    const circleOffset = -(Math.PI / 2);
+    let circleLock = true;
+
+    for (let i = circleOffset; i < circleOffset + 2 * Math.PI; i += 1 / (8 * Math.PI)) {
+      arcStack.push([radius + bR * Math.cos(i), radius + bR * Math.sin(i)]);
+    }
+
+    const cStartPoint = arcStack.shift();
+    ctx.strokeStyle = color;
+    ctx.moveTo(cStartPoint[0], cStartPoint[1]);
+
+    function drawSin() {
+      ctx.beginPath();
+      ctx.save();
+
+      const sinStack = [];
+      for (let i = xOffset; i <= xOffset + axisLength; i += 20 / axisLength) {
+        const x = sp + (xOffset + i) / unit;
+        const y = Math.sin(x) * currRange;
+        const dx = i;
+        const dy = 2 * cR * (1 - currData) + (radius - cR) - unit * y;
+
+        ctx.lineTo(dx, dy);
+        sinStack.push([dx, dy]);
+      }
+
+      const startPoint = sinStack.shift();
+
+      ctx.lineTo(xOffset + axisLength, canvasHeight);
+      ctx.lineTo(xOffset, canvasHeight);
+      ctx.lineTo(startPoint[0], startPoint[1]);
+
+      const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeight);
+      gradient.addColorStop(0, '#ffffff');
+      gradient.addColorStop(1, '#1890FF');
+      ctx.fillStyle = gradient;
+      ctx.fill();
+      ctx.restore();
+    }
+
+    function render() {
+      ctx.clearRect(0, 0, canvasWidth, canvasHeight);
+      if (circleLock) {
+        if (arcStack.length) {
+          const temp = arcStack.shift();
+          ctx.lineTo(temp[0], temp[1]);
+          ctx.stroke();
+        } else {
+          circleLock = false;
+          ctx.lineTo(cStartPoint[0], cStartPoint[1]);
+          ctx.stroke();
+          arcStack = null;
+
+          ctx.globalCompositeOperation = 'destination-over';
+          ctx.beginPath();
+          ctx.lineWidth = lineWidth;
+          ctx.arc(radius, radius, bR, 0, 2 * Math.PI, 1);
+
+          ctx.beginPath();
+          ctx.save();
+          ctx.arc(radius, radius, radius - 3 * lineWidth, 0, 2 * Math.PI, 1);
+
+          ctx.restore();
+          ctx.clip();
+          ctx.fillStyle = '#1890FF';
+        }
+      } else {
+        if (data >= 0.85) {
+          if (currRange > range / 4) {
+            const t = range * 0.01;
+            currRange -= t;
+          }
+        } else if (data <= 0.1) {
+          if (currRange < range * 1.5) {
+            const t = range * 0.01;
+            currRange += t;
+          }
+        } else {
+          if (currRange <= range) {
+            const t = range * 0.01;
+            currRange += t;
+          }
+          if (currRange >= range) {
+            const t = range * 0.01;
+            currRange -= t;
+          }
+        }
+        if (data - currData > 0) {
+          currData += waveupsp;
+        }
+        if (data - currData < 0) {
+          currData -= waveupsp;
+        }
+
+        sp += 0.07;
+        drawSin();
+      }
+      self.timer = requestAnimationFrame(render);
+    }
+
+    render();
+  }
+
+  render() {
+    const { radio } = this.state;
+    const { percent, title, height } = this.props;
+    return (
+      <div
+        className={styles.waterWave}
+        ref={n => (this.root = n)}
+        style={{ transform: `scale(${radio})` }}
+      >
+        <div style={{ width: height, height, overflow: 'hidden' }}>
+          <canvas
+            className={styles.waterWaveCanvasWrapper}
+            ref={n => (this.node = n)}
+            width={height * 2}
+            height={height * 2}
+          />
+        </div>
+        <div className={styles.text} style={{ width: height }}>
+          {title && <span>{title}</span>}
+          <h4>{percent}%</h4>
+        </div>
+      </div>
+    );
+  }
+}

+ 28 - 0
src/components/Charts/WaterWave/index.less

@@ -0,0 +1,28 @@
+@import "~antd/lib/style/themes/default.less";
+
+.waterWave {
+  display: inline-block;
+  position: relative;
+  transform-origin: left;
+  .text {
+    position: absolute;
+    left: 0;
+    top: 32px;
+    text-align: center;
+    width: 100%;
+    span {
+      color: @text-color-secondary;
+      font-size: 14px;
+      line-height: 22px;
+    }
+    h4 {
+      color: @heading-color;
+      line-height: 32px;
+      font-size: 24px;
+    }
+  }
+  .waterWaveCanvasWrapper {
+    transform: scale(.5);
+    transform-origin: 0 0;
+  }
+}

+ 63 - 0
src/components/Charts/autoHeight.js

@@ -0,0 +1,63 @@
+/* eslint eqeqeq: 0 */
+import React from 'react';
+
+function computeHeight(node) {
+  const totalHeight = parseInt(getComputedStyle(node).height, 10);
+  const padding =
+    parseInt(getComputedStyle(node).paddingTop, 10) +
+    parseInt(getComputedStyle(node).paddingBottom, 10);
+  return totalHeight - padding;
+}
+
+function getAutoHeight(n) {
+  if (!n) {
+    return 0;
+  }
+
+  let node = n;
+
+  let height = computeHeight(node);
+
+  while (!height) {
+    node = node.parentNode;
+    if (node) {
+      height = computeHeight(node);
+    } else {
+      break;
+    }
+  }
+
+  return height;
+}
+
+const autoHeight = () => (WrappedComponent) => {
+  return class extends React.Component {
+    state = {
+      computedHeight: 0,
+    };
+
+    componentDidMount() {
+      const { height } = this.props;
+      if (!height) {
+        const h = getAutoHeight(this.root);
+        // eslint-disable-next-line
+        this.setState({ computedHeight: h });
+      }
+    }
+
+    handleRoot = (node) => {
+      this.root = node;
+    };
+
+    render() {
+      const { height } = this.props;
+      const { computedHeight } = this.state;
+      const h = height || computedHeight;
+      return (
+        <div ref={this.handleRoot}>{h > 0 && <WrappedComponent {...this.props} height={h} />}</div>
+      );
+    }
+  };
+};
+
+export default autoHeight;

+ 26 - 0
src/components/Charts/demo/bar.md

@@ -0,0 +1,26 @@
+---
+order: 4
+title: 柱状图
+---
+
+通过设置 `x`,`y` 属性,可以快速的构建出一个漂亮的柱状图,各种纬度的关系则是通过自定义的数据展现。
+
+````jsx
+import { Bar } from 'ant-design-pro/lib/Charts';
+
+const salesData = [];
+for (let i = 0; i < 12; i += 1) {
+  salesData.push({
+    x: `${i + 1}月`,
+    y: Math.floor(Math.random() * 1000) + 200,
+  });
+}
+
+ReactDOM.render(
+  <Bar
+    height={200}
+    title="销售额趋势"
+    data={salesData}
+  />
+, mountNode);
+````

+ 65 - 0
src/components/Charts/demo/chart-card.md

@@ -0,0 +1,65 @@
+---
+order: 1
+title: 图表卡片
+---
+
+用于展示图表的卡片容器,可以方便的配合其它图表套件展示丰富信息。
+
+````jsx
+import { ChartCard, yuan, Field } from 'ant-design-pro/lib/Charts';
+import Trend from 'ant-design-pro/lib/Trend';
+import { Row, Col, Icon, Tooltip } from 'antd';
+import numeral from 'numeral';
+
+ReactDOM.render(
+  <Row>
+    <Col span={24}>
+      <ChartCard
+        title="销售额"
+        action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>}
+        total={yuan(126560)}
+        footer={<Field label="日均销售额" value={numeral(12423).format('0,0')} />}
+        contentHeight={46}
+      >
+        <span>
+          周同比
+          <Trend flag="up" style={{ marginLeft: 8, color: 'rgba(0,0,0,.85)' }}>12%</Trend>
+        </span>
+        <span style={{ marginLeft: 16 }}>
+          日环比
+          <Trend flag="down" style={{ marginLeft: 8, color: 'rgba(0,0,0,.85)' }}>11%</Trend>
+        </span>
+      </ChartCard>
+    </Col>
+    <Col span={24} style={{ marginTop: 24 }}>
+      <ChartCard
+        title="移动指标"
+        avatar={
+          <img
+            style={{ width: 56, height: 56 }}
+            src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png"
+            alt="indicator"
+          />
+        }
+        action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>}
+        total={yuan(126560)}
+        footer={<Field label="日均销售额" value={numeral(12423).format('0,0')} />}
+      />
+    </Col>
+    <Col span={24} style={{ marginTop: 24 }}>
+      <ChartCard
+        title="移动指标"
+        avatar={(
+          <img
+            alt="indicator"
+            style={{ width: 56, height: 56 }}
+            src="https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png"
+          />
+        )}
+        action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>}
+        total={yuan(126560)}
+      />
+    </Col>
+  </Row>
+, mountNode);
+````

+ 18 - 0
src/components/Charts/demo/gauge.md

@@ -0,0 +1,18 @@
+---
+order: 7
+title: 仪表盘 
+---
+
+仪表盘是一种进度展示方式,可以更直观的展示当前的进展情况,通常也可表示占比。
+
+````jsx
+import { Gauge } from 'ant-design-pro/lib/Charts';
+
+ReactDOM.render(
+  <Gauge
+    title="核销率"
+    height={164}
+    percent={87}
+  />
+, mountNode);
+````

+ 28 - 0
src/components/Charts/demo/mini-area.md

@@ -0,0 +1,28 @@
+---
+order: 2
+col: 2
+title: 迷你区域图
+---
+
+````jsx
+import { MiniArea } from 'ant-design-pro/lib/Charts';
+import moment from 'moment';
+
+const visitData = [];
+const beginDay = new Date().getTime();
+for (let i = 0; i < 20; i += 1) {
+  visitData.push({
+    x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'),
+    y: Math.floor(Math.random() * 100) + 10,
+  });
+}
+
+ReactDOM.render(
+  <MiniArea
+    line
+    color="#cceafe"
+    height={45}
+    data={visitData}
+  />
+, mountNode);
+````

+ 28 - 0
src/components/Charts/demo/mini-bar.md

@@ -0,0 +1,28 @@
+---
+order: 2
+col: 2
+title: 迷你柱状图
+---
+
+迷你柱状图更适合展示简单的区间数据,简洁的表现方式可以很好的减少大数据量的视觉展现压力。
+
+````jsx
+import { MiniBar } from 'ant-design-pro/lib/Charts';
+import moment from 'moment';
+
+const visitData = [];
+const beginDay = new Date().getTime();
+for (let i = 0; i < 20; i += 1) {
+  visitData.push({
+    x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'),
+    y: Math.floor(Math.random() * 100) + 10,
+  });
+}
+
+ReactDOM.render(
+  <MiniBar
+    height={45}
+    data={visitData}
+  />
+, mountNode);
+````

+ 16 - 0
src/components/Charts/demo/mini-pie.md

@@ -0,0 +1,16 @@
+---
+order: 6
+title: 迷你饼状图
+---
+
+通过简化 `Pie` 属性的设置,可以快速的实现极简的饼状图,可配合 `ChartCard` 组合展
+现更多业务场景。
+
+```jsx
+import { Pie } from 'ant-design-pro/lib/Charts';
+
+ReactDOM.render(
+  <Pie percent={28} subTitle="中式快餐" total="28%" height={140} />,
+  mountNode
+);
+```

+ 12 - 0
src/components/Charts/demo/mini-progress.md

@@ -0,0 +1,12 @@
+---
+order: 3
+title: 迷你进度条
+---
+
+````jsx
+import { MiniProgress } from 'ant-design-pro/lib/Charts';
+
+ReactDOM.render(
+  <MiniProgress percent={78} strokeWidth={8} target={80} />
+, mountNode);
+````

+ 83 - 0
src/components/Charts/demo/mix.md

@@ -0,0 +1,83 @@
+---
+order: 0
+title: 图表套件组合展示
+---
+
+利用 Ant Design Pro 提供的图表套件,可以灵活组合符合设计规范的图表来满足复杂的业务需求。
+
+````jsx
+import { ChartCard, Field, MiniArea, MiniBar, MiniProgress } from 'ant-design-pro/lib/Charts';
+import Trend from 'ant-design-pro/lib/Trend';
+import NumberInfo from 'ant-design-pro/lib/NumberInfo';
+import { Row, Col, Icon, Tooltip } from 'antd';
+import numeral from 'numeral';
+import moment from 'moment';
+
+const visitData = [];
+const beginDay = new Date().getTime();
+for (let i = 0; i < 20; i += 1) {
+  visitData.push({
+    x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'),
+    y: Math.floor(Math.random() * 100) + 10,
+  });
+}
+
+ReactDOM.render(
+  <Row>
+    <Col span={24}>
+      <ChartCard
+        title="搜索用户数量"
+        contentHeight={134}
+      >
+        <NumberInfo
+          subTitle={<span>本周访问</span>}
+          total={numeral(12321).format('0,0')}
+          status="up"
+          subTotal={17.1}
+        />
+        <MiniArea
+          line
+          height={45}
+          data={visitData}
+        />
+      </ChartCard>
+    </Col>
+    <Col span={24} style={{ marginTop: 24 }}>
+      <ChartCard
+        title="访问量"
+        action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>}
+        total={numeral(8846).format('0,0')}
+        footer={<Field label="日访问量" value={numeral(1234).format('0,0')} />}
+        contentHeight={46}
+      >
+        <MiniBar
+          height={46}
+          data={visitData}
+        />
+      </ChartCard>
+    </Col>
+    <Col span={24} style={{ marginTop: 24 }}>
+      <ChartCard
+        title="线上购物转化率"
+        action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>}
+        total="78%"
+        footer={
+          <div>
+            <span>
+              周同比
+              <Trend flag="up" style={{ marginLeft: 8, color: 'rgba(0,0,0,.85)' }}>12%</Trend>
+            </span>
+            <span style={{ marginLeft: 16 }}>
+              日环比
+              <Trend flag="down" style={{ marginLeft: 8, color: 'rgba(0,0,0,.85)' }}>11%</Trend>
+            </span>
+          </div>
+        }
+        contentHeight={46}
+      >
+        <MiniProgress percent={78} strokeWidth={8} target={80} />
+      </ChartCard>
+    </Col>
+  </Row>
+, mountNode);
+````

+ 47 - 0
src/components/Charts/demo/pie.md

@@ -0,0 +1,47 @@
+---
+order: 5
+title: 饼状图
+---
+
+````jsx
+import { Pie, yuan } from 'ant-design-pro/lib/Charts';
+
+const salesPieData = [
+  {
+    x: '家用电器',
+    y: 4544,
+  },
+  {
+    x: '食用酒水',
+    y: 3321,
+  },
+  {
+    x: '个护健康',
+    y: 3113,
+  },
+  {
+    x: '服饰箱包',
+    y: 2341,
+  },
+  {
+    x: '母婴产品',
+    y: 1231,
+  },
+  {
+    x: '其他',
+    y: 1231,
+  },
+];
+
+ReactDOM.render(
+  <Pie
+    hasLegend
+    title="销售额"
+    subTitle="销售额"
+    total={yuan(salesPieData.reduce((pre, now) => now.y + pre, 0))}
+    data={salesPieData}
+    valueFormat={val => yuan(val)}
+    height={294}
+  />
+, mountNode);
+````

+ 64 - 0
src/components/Charts/demo/radar.md

@@ -0,0 +1,64 @@
+---
+order: 7
+title: 雷达图
+---
+
+````jsx
+import { Radar, ChartCard } from 'ant-design-pro/lib/Charts';
+
+const radarOriginData = [
+  {
+    name: '个人',
+    ref: 10,
+    koubei: 8,
+    output: 4,
+    contribute: 5,
+    hot: 7,
+  },
+  {
+    name: '团队',
+    ref: 3,
+    koubei: 9,
+    output: 6,
+    contribute: 3,
+    hot: 1,
+  },
+  {
+    name: '部门',
+    ref: 4,
+    koubei: 1,
+    output: 6,
+    contribute: 5,
+    hot: 7,
+  },
+];
+const radarData = [];
+const radarTitleMap = {
+  ref: '引用',
+  koubei: '口碑',
+  output: '产量',
+  contribute: '贡献',
+  hot: '热度',
+};
+radarOriginData.forEach((item) => {
+  Object.keys(item).forEach((key) => {
+    if (key !== 'name') {
+      radarData.push({
+        name: item.name,
+        label: radarTitleMap[key],
+        value: item[key],
+      });
+    }
+  });
+});
+
+ReactDOM.render(
+  <ChartCard title="数据比例">
+    <Radar
+      hasLegend
+      height={286}
+      data={radarData}
+    />
+  </ChartCard>
+, mountNode);
+````

+ 25 - 0
src/components/Charts/demo/tag-cloud.md

@@ -0,0 +1,25 @@
+---
+order: 9
+title: 标签云
+---
+
+标签云是一套相关的标签以及与此相应的权重展示方式,一般典型的标签云有 30 至 150 个标签,而权重影响使用的字体大小或其他视觉效果。
+
+````jsx
+import { TagCloud } from 'ant-design-pro/lib/Charts';
+
+const tags = [];
+for (let i = 0; i < 50; i += 1) {
+  tags.push({
+    name: `TagClout-Title-${i}`,
+    value: Math.floor((Math.random() * 50)) + 20,
+  });
+}
+
+ReactDOM.render(
+  <TagCloud
+    data={tags}
+    height={200}
+  />
+, mountNode);
+````

+ 27 - 0
src/components/Charts/demo/timeline-chart.md

@@ -0,0 +1,27 @@
+---
+order: 9
+title: 带有时间轴的图表
+---
+
+使用 `TimelineChart` 组件可以实现带有时间轴的柱状图展现,而其中的 `x` 属性,则是时间值的指向,默认最多支持同时展现两个指标,分别是 `y1` 和 `y2`。
+
+````jsx
+import { TimelineChart } from 'ant-design-pro/lib/Charts';
+
+const chartData = [];
+for (let i = 0; i < 20; i += 1) {
+  chartData.push({
+    x: (new Date().getTime()) + (1000 * 60 * 30 * i),
+    y1: Math.floor(Math.random() * 100) + 1000,
+    y2: Math.floor(Math.random() * 100) + 10,
+  });
+}
+
+ReactDOM.render(
+  <TimelineChart
+    height={200}
+    data={chartData}
+    titleMap={{ y1: '客流量', y2: '支付笔数' }}
+  />
+, mountNode);
+````

+ 20 - 0
src/components/Charts/demo/waterwave.md

@@ -0,0 +1,20 @@
+---
+order: 8
+title: 水波图 
+---
+
+水波图是一种比例的展示方式,可以更直观的展示关键值的占比。
+
+````jsx
+import { WaterWave } from 'ant-design-pro/lib/Charts';
+
+ReactDOM.render(
+  <div style={{ textAlign: 'center' }}>
+    <WaterWave
+      height={161}
+      title="补贴资金剩余"
+      percent={34}
+    />
+  </div>
+, mountNode);
+````

+ 15 - 0
src/components/Charts/g2.js

@@ -0,0 +1,15 @@
+// 全局 G2 设置
+import { track, setTheme } from 'bizcharts';
+
+track(false);
+
+const config = {
+  defaultColor: '#1089ff',
+  shape: {
+    interval: {
+      fillOpacity: 1,
+    },
+  },
+};
+
+setTheme(config);

+ 17 - 0
src/components/Charts/index.d.ts

@@ -0,0 +1,17 @@
+import * as numeral from 'numeral';
+export { default as ChartCard } from './ChartCard';
+export { default as Bar } from './Bar';
+export { default as Pie } from './Pie';
+export { default as Radar } from './Radar';
+export { default as Gauge } from './Gauge';
+export { default as MiniArea } from './MiniArea';
+export { default as MiniBar } from './MiniBar';
+export { default as MiniProgress } from './MiniProgress';
+export { default as Field } from './Field';
+export { default as WaterWave } from './WaterWave';
+export { default as TagCloud } from './TagCloud';
+export { default as TimelineChart } from './TimelineChart';
+
+declare const yuan: (value: number | string) => string;
+
+export { yuan };

+ 32 - 0
src/components/Charts/index.js

@@ -0,0 +1,32 @@
+import numeral from 'numeral';
+import './g2';
+import ChartCard from './ChartCard';
+import Bar from './Bar';
+import Pie from './Pie';
+import Radar from './Radar';
+import Gauge from './Gauge';
+import MiniArea from './MiniArea';
+import MiniBar from './MiniBar';
+import MiniProgress from './MiniProgress';
+import Field from './Field';
+import WaterWave from './WaterWave';
+import TagCloud from './TagCloud';
+import TimelineChart from './TimelineChart';
+
+const yuan = val => `&yen; ${numeral(val).format('0,0')}`;
+
+export {
+  yuan,
+  Bar,
+  Pie,
+  Gauge,
+  Radar,
+  MiniBar,
+  MiniArea,
+  MiniProgress,
+  ChartCard,
+  Field,
+  WaterWave,
+  TagCloud,
+  TimelineChart,
+};

+ 19 - 0
src/components/Charts/index.less

@@ -0,0 +1,19 @@
+.miniChart {
+  position: relative;
+  width: 100%;
+  .chartContent {
+    position: absolute;
+    bottom: -28px;
+    width: 100%;
+    > div {
+      margin: 0 -5px;
+      overflow: hidden;
+    }
+  }
+  .chartLoading {
+    position: absolute;
+    top: 16px;
+    left: 50%;
+    margin-left: -7px;
+  }
+}

+ 132 - 0
src/components/Charts/index.md

@@ -0,0 +1,132 @@
+---
+title: 
+  en-US: Charts
+  zh-CN: Charts
+subtitle: 图表
+order: 2
+cols: 2
+---
+
+Ant Design Pro 提供的业务中常用的图表类型,都是基于 [G2](https://antv.alipay.com/g2/doc/index.html) 按照 Ant Design 图表规范封装,需要注意的是 Ant Design Pro 的图表组件以套件形式提供,可以任意组合实现复杂的业务需求。
+
+因为结合了 Ant Design 的标准设计,本着极简的设计思想以及开箱即用的理念,简化了大量 API 配置,所以如果需要灵活定制图表,可以参考 Ant Design Pro 图表实现,自行基于 [G2](https://antv.alipay.com/g2/doc/index.html) 封装图表组件使用。
+
+## API
+
+### ChartCard
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| title | 卡片标题 | ReactNode\|string | - |
+| action | 卡片操作 | ReactNode | - |
+| total | 数据总量 | ReactNode \| number | - |
+| footer | 卡片底部 | ReactNode | - |
+| contentHeight | 内容区域高度 | number | - |
+| avatar | 右侧图标 | React.ReactNode | - |
+### MiniBar
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| color | 图表颜色 | string | `#1890FF` |
+| height | 图表高度 | number | - |
+| data | 数据 | array<{x, y}> | - |
+
+### MiniArea
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| color | 图表颜色 | string | `rgba(24, 144, 255, 0.2)` |
+| borderColor | 图表边颜色 | string | `#1890FF` |
+| height | 图表高度 | number | - |
+| line | 是否显示描边 | boolean | false |
+| animate | 是否显示动画 | boolean | true |
+| xAxis | [x 轴配置](http://antvis.github.io/g2/doc/tutorial/start/axis.html) | object | - |
+| yAxis | [y 轴配置](http://antvis.github.io/g2/doc/tutorial/start/axis.html) | object | - |
+| data | 数据 | array<{x, y}> | - |
+
+### MiniProgress
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| target | 目标比例 | number | - |
+| color | 进度条颜色 | string | - |
+| strokeWidth | 进度条高度 | number | - |
+| percent | 进度比例 | number | - |
+
+### Bar
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| title | 图表标题 | ReactNode\|string | - |
+| color | 图表颜色 | string | `rgba(24, 144, 255, 0.85)` |
+| margin | 图表内部间距 | array | \[32, 0, 32, 40\] |
+| height | 图表高度 | number | - |
+| data | 数据 | array<{x, y}> | - |
+| autoLabel | 在宽度不足时,自动隐藏 x 轴的 label | boolean | `true` |
+
+### Pie
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| animate | 是否显示动画 | boolean | true |
+| color | 图表颜色 | string | `rgba(24, 144, 255, 0.85)` |
+| height | 图表高度 | number | - |
+| hasLegend | 是否显示 legend | boolean | `false` |
+| margin | 图表内部间距 | array | \[24, 0, 24, 0\] |
+| percent | 占比 | number | - |
+| tooltip | 是否显示 tooltip | boolean | true |
+| valueFormat | 显示值的格式化函数 | function | - |
+| title | 图表标题 | ReactNode\|string | - |
+| subTitle | 图表子标题 | ReactNode\|string | - |
+| total | 图标中央的总数 | string | - |
+
+### Radar
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| title | 图表标题 | ReactNode\|string | - |
+| height | 图表高度 | number | - |
+| hasLegend | 是否显示 legend | boolean | `false` |
+| margin | 图表内部间距 | array | \[24, 30, 16, 30\] |
+| data | 图标数据 | array<{name,label,value}> | - |
+
+### Gauge
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| title | 图表标题 | ReactNode\|string | - |
+| height | 图表高度 | number | - |
+| color | 图表颜色 | string | `#2F9CFF` |
+| bgColor | 图表背景颜色 | string | `#F0F2F5` |
+| percent | 进度比例 | number | - |
+
+### WaterWave
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| title | 图表标题 | ReactNode\|string | - |
+| height | 图表高度 | number | - |
+| color | 图表颜色 | string | `#1890FF` |
+| percent | 进度比例 | number | - |
+
+### TagCloud
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| data | 标题 | Array<name, value\> | - |
+| height | 高度值 | number | - |
+
+### TimelineChart
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| data | 标题 | Array<x, y1, y2\> | - |
+| titleMap | 指标别名 | Object{y1: '客流量', y2: '支付笔数'} | - |
+| height | 高度值 | number | 400 |
+
+### Field
+
+| 参数      | 说明                                      | 类型         | 默认值 |
+|----------|------------------------------------------|-------------|-------|
+| label | 标题 | ReactNode\|string | - |
+| value | 值 | ReactNode\|string | - |

+ 24 - 0
src/components/CountDown/demo/simple.md

@@ -0,0 +1,24 @@
+---
+order: 0
+title:
+  zh-CN: 基本
+  en-US: Basic
+---
+
+## zh-CN
+
+简单的倒计时组件使用。
+
+## en-US
+
+The simplest usage.
+
+````jsx
+import CountDown from 'ant-design-pro/lib/CountDown';
+
+const targetTime = new Date().getTime() + 3900000;
+
+ReactDOM.render(
+  <CountDown style={{ fontSize: 20 }} target={targetTime} />
+, mountNode);
+````

+ 9 - 0
src/components/CountDown/index.d.ts

@@ -0,0 +1,9 @@
+import * as React from "react";
+export interface CountDownProps {
+  format?: (time: number) => void;
+  target: Date | number;
+  onEnd?: () => void;
+  style?: React.CSSProperties;
+}
+
+export default class CountDown extends React.Component<CountDownProps, any> {}

+ 15 - 0
src/components/CountDown/index.en-US.md

@@ -0,0 +1,15 @@
+---
+title: CountDown
+cols: 1
+order: 3
+---
+
+Simple CountDown Component.
+
+## API
+
+| Property | Description | Type | Default |
+|----------|------------------------------------------|-------------|-------|
+| format | Formatter of time | Function(time) |  |
+| target | Target time | Date | - |
+| onEnd |  Countdown to the end callback | funtion | -|

+ 0 - 0
src/components/CountDown/index.js


Some files were not shown because too many files changed in this diff