Browse Source

feat: 动态路由 + 动态菜单 antd pro

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

+ 12
- 0
.babelrc.js View File

@@ -0,0 +1,12 @@
1
+module.exports = {
2
+  plugins: [
3
+    [
4
+      'babel-plugin-module-resolver',
5
+      {
6
+        alias: {
7
+          components: './src/components',
8
+        },
9
+      },
10
+    ],
11
+  ],
12
+};

+ 16
- 0
.editorconfig View File

@@ -0,0 +1,16 @@
1
+# http://editorconfig.org
2
+root = true
3
+
4
+[*]
5
+indent_style = space
6
+indent_size = 2
7
+end_of_line = lf
8
+charset = utf-8
9
+trim_trailing_whitespace = true
10
+insert_final_newline = true
11
+
12
+[*.md]
13
+trim_trailing_whitespace = false
14
+
15
+[Makefile]
16
+indent_style = tab

+ 71
- 0
.eslintrc.js View File

@@ -0,0 +1,71 @@
1
+module.exports = {
2
+  parser: 'babel-eslint',
3
+  extends: ['airbnb', 'prettier'],
4
+  env: {
5
+    browser: true,
6
+    node: true,
7
+    es6: true,
8
+    mocha: true,
9
+    jest: true,
10
+    jasmine: true,
11
+  },
12
+  rules: {
13
+    'generator-star-spacing': [0],
14
+    'consistent-return': [0],
15
+    'react/forbid-prop-types': [0],
16
+    'react/jsx-filename-extension': [1, { extensions: ['.js'] }],
17
+    'global-require': [1],
18
+    'import/prefer-default-export': [0],
19
+    'react/jsx-no-bind': [0],
20
+    'react/prop-types': [0],
21
+    'react/prefer-stateless-function': [0],
22
+    'react/jsx-wrap-multilines': [
23
+      'error',
24
+      {
25
+        declaration: 'parens-new-line',
26
+        assignment: 'parens-new-line',
27
+        return: 'parens-new-line',
28
+        arrow: 'parens-new-line',
29
+        condition: 'parens-new-line',
30
+        logical: 'parens-new-line',
31
+        prop: 'ignore',
32
+      },
33
+    ],
34
+    'no-else-return': [0],
35
+    'no-restricted-syntax': [0],
36
+    'import/no-extraneous-dependencies': [0],
37
+    'no-use-before-define': [0],
38
+    'jsx-a11y/no-static-element-interactions': [0],
39
+    'jsx-a11y/no-noninteractive-element-interactions': [0],
40
+    'jsx-a11y/click-events-have-key-events': [0],
41
+    'jsx-a11y/anchor-is-valid': [0],
42
+    'no-nested-ternary': [0],
43
+    'arrow-body-style': [0],
44
+    'import/extensions': [0],
45
+    'no-bitwise': [0],
46
+    'no-cond-assign': [0],
47
+    'import/no-unresolved': [0],
48
+    'comma-dangle': [
49
+      'error',
50
+      {
51
+        arrays: 'always-multiline',
52
+        objects: 'always-multiline',
53
+        imports: 'always-multiline',
54
+        exports: 'always-multiline',
55
+        functions: 'ignore',
56
+      },
57
+    ],
58
+    'object-curly-newline': [0],
59
+    'function-paren-newline': [0],
60
+    'no-restricted-globals': [0],
61
+    'require-yield': [1],
62
+  },
63
+  parserOptions: {
64
+    ecmaFeatures: {
65
+      experimentalObjectRestSpread: true,
66
+    },
67
+  },
68
+  settings: {
69
+    polyfills: ['fetch', 'promises'],
70
+  },
71
+};

+ 3
- 0
.ga View File

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

+ 23
- 0
.gitignore View File

@@ -0,0 +1,23 @@
1
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+# dependencies
4
+/node_modules
5
+# roadhog-api-doc ignore
6
+/src/utils/request-temp.js
7
+_roadhog-api-doc
8
+
9
+# production
10
+/dist
11
+/vscode
12
+
13
+# misc
14
+.DS_Store
15
+npm-debug.log*
16
+yarn-error.log
17
+
18
+/coverage
19
+.idea
20
+yarn.lock
21
+package-lock.json
22
+*bak
23
+jsconfig.json

+ 3
- 0
.prettierignore View File

@@ -0,0 +1,3 @@
1
+**/*.md
2
+**/*.svg
3
+package.json

+ 11
- 0
.prettierrc View File

@@ -0,0 +1,11 @@
1
+{
2
+  "singleQuote": true,
3
+  "trailingComma": "es5",
4
+  "printWidth": 100,
5
+  "overrides": [
6
+    {
7
+      "files": ".prettierrc",
8
+      "options": { "parser": "json" }
9
+    }
10
+  ]
11
+}

+ 295
- 0
.roadhogrc.mock.js View File

@@ -0,0 +1,295 @@
1
+import mockjs from 'mockjs';
2
+import { getRule, postRule } from './mock/rule';
3
+import { getActivities, getNotice, getFakeList } from './mock/api';
4
+import { getFakeChartData } from './mock/chart';
5
+import { getProfileBasicData } from './mock/profile';
6
+import { getProfileAdvancedData } from './mock/profile';
7
+import { getNotices } from './mock/notices';
8
+import { format, delay } from 'roadhog-api-doc';
9
+
10
+// 是否禁用代理
11
+const noProxy = process.env.NO_PROXY === 'true';
12
+
13
+// 代码中会兼容本地 service mock 以及部署站点的静态数据
14
+const proxy = {
15
+  // 支持值为 Object 和 Array
16
+  'GET /api/currentUser': {
17
+    $desc: '获取当前用户接口',
18
+    $params: {
19
+      pageSize: {
20
+        desc: '分页',
21
+        exp: 2,
22
+      },
23
+    },
24
+    $body: {
25
+      name: 'Serati Ma',
26
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
27
+      userid: '00000001',
28
+      notifyCount: 12,
29
+    },
30
+  },
31
+  // GET POST 可省略
32
+  'GET /api/users': [
33
+    {
34
+      key: '1',
35
+      name: 'John Brown',
36
+      age: 32,
37
+      address: 'New York No. 1 Lake Park',
38
+    },
39
+    {
40
+      key: '2',
41
+      name: 'Jim Green',
42
+      age: 42,
43
+      address: 'London No. 1 Lake Park',
44
+    },
45
+    {
46
+      key: '3',
47
+      name: 'Joe Black',
48
+      age: 32,
49
+      address: 'Sidney No. 1 Lake Park',
50
+    },
51
+  ],
52
+  'GET /api/project/notice': getNotice,
53
+  'GET /api/activities': getActivities,
54
+  'GET /api/rule': getRule,
55
+  'POST /api/rule': {
56
+    $params: {
57
+      pageSize: {
58
+        desc: '分页',
59
+        exp: 2,
60
+      },
61
+    },
62
+    $body: postRule,
63
+  },
64
+  'POST /api/forms': (req, res) => {
65
+    res.send({ message: 'Ok' });
66
+  },
67
+  'GET /api/tags': mockjs.mock({
68
+    'list|100': [{ name: '@city', 'value|1-100': 150, 'type|0-2': 1 }],
69
+  }),
70
+  'GET /api/fake_list': getFakeList,
71
+  'GET /api/fake_chart_data': getFakeChartData,
72
+  'GET /api/profile/basic': getProfileBasicData,
73
+  'GET /api/profile/advanced': getProfileAdvancedData,
74
+  'POST /api/login/account': (req, res) => {
75
+    const { password, userName, type } = req.body;
76
+    if (password === '888888' && userName === 'admin') {
77
+      res.send({
78
+        status: 'ok',
79
+        type,
80
+        currentAuthority: 'admin',
81
+      });
82
+      return;
83
+    }
84
+    if (password === '123456' && userName === 'user') {
85
+      res.send({
86
+        status: 'ok',
87
+        type,
88
+        currentAuthority: 'user',
89
+      });
90
+      return;
91
+    }
92
+    res.send({
93
+      status: 'error',
94
+      type,
95
+      currentAuthority: 'guest',
96
+    });
97
+  },
98
+  'POST /api/register': (req, res) => {
99
+    res.send({ status: 'ok', currentAuthority: 'user' });
100
+  },
101
+  'GET /api/notices': getNotices,
102
+  'GET /api/500': (req, res) => {
103
+    res.status(500).send({
104
+      timestamp: 1513932555104,
105
+      status: 500,
106
+      error: 'error',
107
+      message: 'error',
108
+      path: '/base/category/list',
109
+    });
110
+  },
111
+  'GET /api/404': (req, res) => {
112
+    res.status(404).send({
113
+      timestamp: 1513932643431,
114
+      status: 404,
115
+      error: 'Not Found',
116
+      message: 'No message available',
117
+      path: '/base/category/list/2121212',
118
+    });
119
+  },
120
+  'GET /api/403': (req, res) => {
121
+    res.status(403).send({
122
+      timestamp: 1513932555104,
123
+      status: 403,
124
+      error: 'Unauthorized',
125
+      message: 'Unauthorized',
126
+      path: '/base/category/list',
127
+    });
128
+  },
129
+  'GET /api/401': (req, res) => {
130
+    res.status(401).send({
131
+      timestamp: 1513932555104,
132
+      status: 401,
133
+      error: 'Unauthorized',
134
+      message: 'Unauthorized',
135
+      path: '/base/category/list',
136
+    });
137
+  },
138
+   'GET /api/menus':[
139
+       {
140
+         name: 'dashboard',
141
+         icon: 'dashboard',
142
+         path: 'dashboard',
143
+         children: [
144
+           {
145
+             name: '分析页',
146
+             path: 'analysis',
147
+           },
148
+           {
149
+             name: '监控页',
150
+             path: 'monitor',
151
+           },
152
+           {
153
+             name: '工作台',
154
+             path: 'workplace',
155
+             // hideInBreadcrumb: true,
156
+             // hideInMenu: true,
157
+           },
158
+         ],
159
+       },
160
+       {
161
+         name: '表单页',
162
+         icon: 'form',
163
+         path: 'form',
164
+         children: [
165
+           {
166
+             name: '基础表单',
167
+             path: 'basic-form',
168
+           },
169
+           {
170
+             name: '分步表单',
171
+             path: 'step-form',
172
+           },
173
+           {
174
+             name: '高级表单',
175
+             authority: 'admin',
176
+             path: 'advanced-form',
177
+           },
178
+         ],
179
+       },
180
+       {
181
+         name: '列表页',
182
+         icon: 'table',
183
+         path: 'list',
184
+         children: [
185
+           {
186
+             name: '查询表格',
187
+             path: 'table-list',
188
+           },
189
+           {
190
+             name: '标准列表',
191
+             path: 'basic-list',
192
+           },
193
+           {
194
+             name: '卡片列表',
195
+             path: 'card-list',
196
+           },
197
+           {
198
+             name: '搜索列表',
199
+             path: 'search',
200
+             children: [
201
+               {
202
+                 name: '搜索列表(文章)',
203
+                 path: 'articles',
204
+               },
205
+               {
206
+                 name: '搜索列表(项目)',
207
+                 path: 'projects',
208
+               },
209
+               {
210
+                 name: '搜索列表(应用)',
211
+                 path: 'applications',
212
+               },
213
+             ],
214
+           },
215
+         ],
216
+       },
217
+       {
218
+         name: '详情页',
219
+         icon: 'profile',
220
+         path: 'profile',
221
+         children: [
222
+           {
223
+             name: '基础详情页',
224
+             path: 'basic',
225
+           },
226
+           {
227
+             name: '高级详情页',
228
+             path: 'advanced',
229
+             authority: 'admin',
230
+           },
231
+         ],
232
+       },
233
+       {
234
+         name: '结果页',
235
+         icon: 'check-circle-o',
236
+         path: 'result',
237
+         children: [
238
+           {
239
+             name: '成功',
240
+             path: 'success',
241
+           },
242
+           {
243
+             name: '失败',
244
+             path: 'fail',
245
+           },
246
+         ],
247
+       },
248
+       {
249
+         name: '异常页',
250
+         icon: 'warning',
251
+         path: 'exception',
252
+         children: [
253
+           {
254
+             name: '403',
255
+             path: '403',
256
+           },
257
+           {
258
+             name: '404',
259
+             path: '404',
260
+           },
261
+           {
262
+             name: '500',
263
+             path: '500',
264
+           },
265
+           {
266
+             name: '触发异常',
267
+             path: 'trigger',
268
+             hideInMenu: true,
269
+           },
270
+         ],
271
+       },
272
+       {
273
+         name: '账户',
274
+         icon: 'user',
275
+         path: 'user',
276
+         authority: 'guest',
277
+         children: [
278
+           {
279
+             name: '登录',
280
+             path: 'login',
281
+           },
282
+           {
283
+             name: '注册',
284
+             path: 'register',
285
+           },
286
+           {
287
+             name: '注册结果',
288
+             path: 'register-result',
289
+           },
290
+         ],
291
+       },
292
+     ]
293
+};
294
+
295
+export default (noProxy ? {} : delay(proxy, 1000));

+ 27
- 0
.stylelintrc View File

@@ -0,0 +1,27 @@
1
+{
2
+  "extends": ["stylelint-config-standard", "stylelint-config-prettier"],
3
+  "rules": {
4
+    "selector-pseudo-class-no-unknown": null,
5
+    "shorthand-property-no-redundant-values": null,
6
+    "at-rule-empty-line-before": null,
7
+    "at-rule-name-space-after": null,
8
+    "comment-empty-line-before": null,
9
+    "declaration-bang-space-before": null,
10
+    "declaration-empty-line-before": null,
11
+    "function-comma-newline-after": null,
12
+    "function-name-case": null,
13
+    "function-parentheses-newline-inside": null,
14
+    "function-max-empty-lines": null,
15
+    "function-whitespace-after": null,
16
+    "number-leading-zero": null,
17
+    "number-no-trailing-zeros": null,
18
+    "rule-empty-line-before": null,
19
+    "selector-combinator-space-after": null,
20
+    "selector-descendant-combinator-no-non-space": null,
21
+    "selector-list-comma-newline-after": null,
22
+    "selector-pseudo-element-colon-notation": null,
23
+    "unit-no-unknown": null,
24
+    "no-descending-specificity": null,
25
+    "value-list-max-empty-lines": null
26
+  }
27
+}

+ 36
- 0
.travis.yml View File

@@ -0,0 +1,36 @@
1
+language: node_js
2
+
3
+node_js:
4
+  - "8"
5
+
6
+env:
7
+  matrix:
8
+    - TEST_TYPE=lint
9
+    - TEST_TYPE=build
10
+    - TEST_TYPE=test-all
11
+    - TEST_TYPE=test-dist
12
+
13
+addons:
14
+  apt:
15
+    packages:
16
+      - xvfb
17
+
18
+install:
19
+  - export DISPLAY=':99.0'
20
+  - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
21
+  - npm install
22
+
23
+script:
24
+  - |
25
+    if [ "$TEST_TYPE" = lint ]; then
26
+      npm run lint
27
+    elif [ "$TEST_TYPE" = build ]; then
28
+      npm run build
29
+    elif [ "$TEST_TYPE" = test-all ]; then
30
+      npm run test:all
31
+    elif [ "$TEST_TYPE" = test-dist ]; then
32
+      npm run site
33
+      mv dist/* ./
34
+      php -S localhost:8000 &
35
+      DEBUG=* npm test .e2e.js
36
+    fi

+ 24
- 0
.webpackrc.js View File

@@ -0,0 +1,24 @@
1
+const path = require('path');
2
+
3
+export default {
4
+  entry: 'src/index.js',
5
+  extraBabelPlugins: [
6
+    ['import', { libraryName: 'antd', libraryDirectory: 'es', style: true }],
7
+  ],
8
+  env: {
9
+    development: {
10
+      extraBabelPlugins: ['dva-hmr'],
11
+    },
12
+  },
13
+  alias: {
14
+    components: path.resolve(__dirname, 'src/components/'),
15
+  },
16
+  ignoreMomentLocale: true,
17
+  theme: './src/theme.js',
18
+  html: {
19
+    template: './src/index.ejs',
20
+  },
21
+  disableDynamicImport: false,
22
+  publicPath: '/',
23
+  hash: true,
24
+};

+ 46
- 0
CODE_OF_CONDUCT.md View File

@@ -0,0 +1,46 @@
1
+# Contributor Covenant Code of Conduct
2
+
3
+## Our Pledge
4
+
5
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+## Our Standards
8
+
9
+Examples of behavior that contributes to creating a positive environment include:
10
+
11
+* Using welcoming and inclusive language
12
+* Being respectful of differing viewpoints and experiences
13
+* Gracefully accepting constructive criticism
14
+* Focusing on what is best for the community
15
+* Showing empathy towards other community members
16
+
17
+Examples of unacceptable behavior by participants include:
18
+
19
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
20
+* Trolling, insulting/derogatory comments, and personal or political attacks
21
+* Public or private harassment
22
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
23
+* Other conduct which could reasonably be considered inappropriate in a professional setting
24
+
25
+## Our Responsibilities
26
+
27
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28
+
29
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30
+
31
+## Scope
32
+
33
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34
+
35
+## Enforcement
36
+
37
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at afc163@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38
+
39
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40
+
41
+## Attribution
42
+
43
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44
+
45
+[homepage]: http://contributor-covenant.org
46
+[version]: http://contributor-covenant.org/version/1/4/

+ 21
- 0
LICENSE View File

@@ -0,0 +1,21 @@
1
+MIT License
2
+
3
+Copyright (c) 2018 Alipay.inc
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in all
13
+copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+SOFTWARE.

+ 95
- 0
README.md View File

@@ -0,0 +1,95 @@
1
+English | [简体中文](./README.zh-CN.md)
2
+
3
+# Ant Design Pro
4
+
5
+[![](https://img.shields.io/travis/ant-design/ant-design-pro/master.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)
6
+
7
+An out-of-box UI solution for enterprise applications as a React boilerplate.
8
+
9
+![](https://gw.alipayobjects.com/zos/rmsportal/xEdBqwSzvoSapmnSnYjU.png)
10
+
11
+- Preview: http://preview.pro.ant.design
12
+- Home Page: http://pro.ant.design
13
+- Documentation: http://pro.ant.design/docs/getting-started
14
+- ChangeLog: http://pro.ant.design/docs/changelog
15
+- FAQ: http://pro.ant.design/docs/faq
16
+- Mirror Site in China: http://ant-design-pro.gitee.io
17
+
18
+## Translation Recruitment :loudspeaker:
19
+
20
+We need your help: https://github.com/ant-design/ant-design-pro/issues/120
21
+
22
+## Features
23
+
24
+- :gem: **Neat Design**: Follow [Ant Design specification](http://ant.design/)
25
+- :triangular_ruler: **Common Templates**: Typical templates for enterprise applications
26
+- :rocket: **State of The Art Development**: Newest development stack of React/dva/antd
27
+- :iphone: **Responsive**: Designed for varies of screen size
28
+- :art: **Theming**: Customizable theme with simple config
29
+- :globe_with_meridians: **International**: Built-in i18n solution
30
+- :gear: **Best Practice**: Solid workflow make your code health
31
+- :1234: **Mock development**: Easy to use mock development solution
32
+- :white_check_mark: **UI Test**: Fly safely with unit test and e2e test
33
+
34
+## Templates
35
+
36
+```
37
+- Dashboard
38
+  - Analytic
39
+  - Monitor
40
+  - Workspace
41
+- Form
42
+  - Basic Form
43
+  - Step Form
44
+  - Advanced From
45
+- List
46
+  - Standard Table
47
+  - Standard List
48
+  - Card List
49
+  - Search List (Project/Applications/Article)
50
+- Profile
51
+  - Simple Profile
52
+  - Advanced Profile
53
+- Result
54
+  - Success
55
+  - Failed
56
+- Exception
57
+  - 403
58
+  - 404
59
+  - 500
60
+- User
61
+  - Login
62
+  - Register
63
+  - Register Result
64
+```
65
+
66
+## Usage
67
+
68
+```bash
69
+$ git clone https://github.com/ant-design/ant-design-pro.git --depth=1
70
+$ cd ant-design-pro
71
+$ npm install
72
+$ npm start         # visit http://localhost:8000
73
+```
74
+
75
+Or you can use the command tool: [ant-design-pro-cli](https://github.com/ant-design/ant-design-pro-cli)
76
+
77
+```bash
78
+$ npm install ant-design-pro-cli -g
79
+$ mkdir pro-demo && cd pro-demo
80
+$ pro new
81
+```
82
+
83
+More instruction at [documentation](http://pro.ant.design/docs/getting-started).
84
+
85
+## Compatibility
86
+
87
+Modern browsers and IE11.
88
+
89
+## Contributing
90
+
91
+Any Contribution of following ways will be welcome:
92
+
93
+- Use Ant Design Pro in your daily work.
94
+- Submit [issue](http://github.com/ant-design/ant-design-pro/issues) to report bug or ask questions.
95
+- Propose [pull request](http://github.com/ant-design/ant-design-pro/pulls) to improve our code.

+ 91
- 0
README.zh-CN.md View File

@@ -0,0 +1,91 @@
1
+[English](./README.md) | 简体中文
2
+
3
+# Ant Design Pro
4
+
5
+[![](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)
6
+
7
+开箱即用的中台前端/设计解决方案。
8
+
9
+![](https://gw.alipayobjects.com/zos/rmsportal/xEdBqwSzvoSapmnSnYjU.png)
10
+
11
+- 预览:http://preview.pro.ant.design
12
+- 首页:http://pro.ant.design/index-cn
13
+- 使用文档:http://pro.ant.design/docs/getting-started-cn
14
+- 更新日志: http://pro.ant.design/docs/changelog-cn
15
+- 常见问题:http://pro.ant.design/docs/faq-cn
16
+- 国内镜像:http://ant-design-pro.gitee.io
17
+
18
+## 特性
19
+
20
+- :gem: **优雅美观**:基于 Ant Design 体系精心设计
21
+- :triangular_ruler: **常见设计模式**:提炼自中后台应用的典型页面和场景
22
+- :rocket: **最新技术栈**:使用 React/dva/antd 等前端前沿技术开发
23
+- :iphone: **响应式**:针对不同屏幕大小设计
24
+- :art: **主题**:可配置的主题满足多样化的品牌诉求
25
+- :globe_with_meridians: **国际化**:内建业界通用的国际化方案
26
+- :gear: **最佳实践**:良好的工程实践助您持续产出高质量代码
27
+- :1234: **Mock 数据**:实用的本地数据调试方案
28
+- :white_check_mark: **UI 测试**:自动化测试保障前端产品质量
29
+
30
+## 模板
31
+
32
+```
33
+- Dashboard
34
+  - 分析页
35
+  - 监控页
36
+  - 工作台
37
+- 表单页
38
+  - 基础表单页
39
+  - 分步表单页
40
+  - 高级表单页
41
+- 列表页
42
+  - 查询表格
43
+  - 标准列表
44
+  - 卡片列表
45
+  - 搜索列表(项目/应用/文章)
46
+- 详情页
47
+  - 基础详情页
48
+  - 高级详情页
49
+- 结果
50
+  - 成功页
51
+  - 失败页
52
+- 异常
53
+  - 403 无权限
54
+  - 404 找不到
55
+  - 500 服务器出错
56
+- 帐户
57
+  - 登录
58
+  - 注册
59
+  - 注册成功
60
+```
61
+
62
+## 使用
63
+
64
+```bash
65
+$ git clone https://github.com/ant-design/ant-design-pro.git --depth=1
66
+$ cd ant-design-pro
67
+$ npm install
68
+$ npm start         # 访问 http://localhost:8000
69
+```
70
+
71
+也可以使用集成化的 [ant-design-pro-cli](https://github.com/ant-design/ant-design-pro-cli) 工具。
72
+
73
+```bash
74
+$ npm install ant-design-pro-cli -g
75
+$ mkdir pro-demo && cd pro-demo
76
+$ pro new
77
+```
78
+
79
+更多信息请参考 [使用文档](http://pro.ant.design/docs/getting-started)。
80
+
81
+## 兼容性
82
+
83
+现代浏览器及 IE11。
84
+
85
+## 参与贡献
86
+
87
+我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :smiley::
88
+
89
+- 在你的公司或个人项目中使用 Ant Design Pro。
90
+- 通过 [Issue](http://github.com/ant-design/ant-design-pro/issues) 报告 bug 或进行咨询。
91
+- 提交 [Pull Request](http://github.com/ant-design/ant-design-pro/pulls) 改进 Pro 的代码。

+ 26
- 0
appveyor.yml View File

@@ -0,0 +1,26 @@
1
+# Test against the latest version of this Node.js version
2
+environment:
3
+  nodejs_version: "8"
4
+
5
+# this is how to allow failing jobs in the matrix
6
+matrix:
7
+  fast_finish: true     # set this flag to immediately finish build once one of the jobs fails.
8
+
9
+# Install scripts. (runs after repo cloning)
10
+install:
11
+  # Get the latest stable version of Node.js or io.js
12
+  - ps: Install-Product node $env:nodejs_version
13
+  # install modules
14
+  - npm install
15
+  # Output useful info for debugging.
16
+  - node --version
17
+  - npm --version
18
+
19
+# Post-install test scripts.
20
+test_script:
21
+  - npm run lint
22
+  - npm run test:all
23
+  - npm run build
24
+
25
+# Don't actually build.
26
+build: off

+ 0
- 0
mock/.gitkeep View File


+ 296
- 0
mock/api.js View File

@@ -0,0 +1,296 @@
1
+import { parse } from 'url';
2
+
3
+const titles = [
4
+  'Alipay',
5
+  'Angular',
6
+  'Ant Design',
7
+  'Ant Design Pro',
8
+  'Bootstrap',
9
+  'React',
10
+  'Vue',
11
+  'Webpack',
12
+];
13
+const avatars = [
14
+  'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', // Alipay
15
+  'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', // Angular
16
+  'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', // Ant Design
17
+  'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', // Ant Design Pro
18
+  'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', // Bootstrap
19
+  'https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png', // React
20
+  'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png', // Vue
21
+  'https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png', // Webpack
22
+];
23
+
24
+const avatars2 = [
25
+  'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
26
+  'https://gw.alipayobjects.com/zos/rmsportal/cnrhVkzwxjPwAaCfPbdc.png',
27
+  'https://gw.alipayobjects.com/zos/rmsportal/gaOngJwsRYRaVAuXXcmB.png',
28
+  'https://gw.alipayobjects.com/zos/rmsportal/ubnKSIfAJTxIgXOKlciN.png',
29
+  'https://gw.alipayobjects.com/zos/rmsportal/WhxKECPNujWoWEFNdnJE.png',
30
+  'https://gw.alipayobjects.com/zos/rmsportal/jZUIxmJycoymBprLOUbT.png',
31
+  'https://gw.alipayobjects.com/zos/rmsportal/psOgztMplJMGpVEqfcgF.png',
32
+  'https://gw.alipayobjects.com/zos/rmsportal/ZpBqSxLxVEXfcUNoPKrz.png',
33
+  'https://gw.alipayobjects.com/zos/rmsportal/laiEnJdGHVOhJrUShBaJ.png',
34
+  'https://gw.alipayobjects.com/zos/rmsportal/UrQsqscbKEpNuJcvBZBu.png',
35
+];
36
+
37
+const covers = [
38
+  'https://gw.alipayobjects.com/zos/rmsportal/uMfMFlvUuceEyPpotzlq.png',
39
+  'https://gw.alipayobjects.com/zos/rmsportal/iZBVOIhGJiAnhplqjvZW.png',
40
+  'https://gw.alipayobjects.com/zos/rmsportal/uVZonEtjWwmUZPBQfycs.png',
41
+  'https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png',
42
+];
43
+const desc = [
44
+  '那是一种内在的东西, 他们到达不了,也无法触及的',
45
+  '希望是一个好东西,也许是最好的,好东西是不会消亡的',
46
+  '生命就像一盒巧克力,结果往往出人意料',
47
+  '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆',
48
+  '那时候我只会想自己想要什么,从不想自己拥有什么',
49
+];
50
+
51
+const user = [
52
+  '付小小',
53
+  '曲丽丽',
54
+  '林东东',
55
+  '周星星',
56
+  '吴加好',
57
+  '朱偏右',
58
+  '鱼酱',
59
+  '乐哥',
60
+  '谭小仪',
61
+  '仲尼',
62
+];
63
+
64
+export function fakeList(count) {
65
+  const list = [];
66
+  for (let i = 0; i < count; i += 1) {
67
+    list.push({
68
+      id: `fake-list-${i}`,
69
+      owner: user[i % 10],
70
+      title: titles[i % 8],
71
+      avatar: avatars[i % 8],
72
+      cover: parseInt(i / 4, 10) % 2 === 0 ? covers[i % 4] : covers[3 - i % 4],
73
+      status: ['active', 'exception', 'normal'][i % 3],
74
+      percent: Math.ceil(Math.random() * 50) + 50,
75
+      logo: avatars[i % 8],
76
+      href: 'https://ant.design',
77
+      updatedAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i),
78
+      createdAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i),
79
+      subDescription: desc[i % 5],
80
+      description:
81
+        '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。',
82
+      activeUser: Math.ceil(Math.random() * 100000) + 100000,
83
+      newUser: Math.ceil(Math.random() * 1000) + 1000,
84
+      star: Math.ceil(Math.random() * 100) + 100,
85
+      like: Math.ceil(Math.random() * 100) + 100,
86
+      message: Math.ceil(Math.random() * 10) + 10,
87
+      content:
88
+        '段落示意:蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。',
89
+      members: [
90
+        {
91
+          avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png',
92
+          name: '曲丽丽',
93
+        },
94
+        {
95
+          avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png',
96
+          name: '王昭君',
97
+        },
98
+        {
99
+          avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png',
100
+          name: '董娜娜',
101
+        },
102
+      ],
103
+    });
104
+  }
105
+
106
+  return list;
107
+}
108
+
109
+export function getFakeList(req, res, u) {
110
+  let url = u;
111
+  if (!url || Object.prototype.toString.call(url) !== '[object String]') {
112
+    url = req.url; // eslint-disable-line
113
+  }
114
+
115
+  const params = parse(url, true).query;
116
+
117
+  const count = params.count * 1 || 20;
118
+
119
+  const result = fakeList(count);
120
+
121
+  if (res && res.json) {
122
+    res.json(result);
123
+  } else {
124
+    return result;
125
+  }
126
+}
127
+
128
+export const getNotice = [
129
+  {
130
+    id: 'xxx1',
131
+    title: titles[0],
132
+    logo: avatars[0],
133
+    description: '那是一种内在的东西,他们到达不了,也无法触及的',
134
+    updatedAt: new Date(),
135
+    member: '科学搬砖组',
136
+    href: '',
137
+    memberLink: '',
138
+  },
139
+  {
140
+    id: 'xxx2',
141
+    title: titles[1],
142
+    logo: avatars[1],
143
+    description: '希望是一个好东西,也许是最好的,好东西是不会消亡的',
144
+    updatedAt: new Date('2017-07-24'),
145
+    member: '全组都是吴彦祖',
146
+    href: '',
147
+    memberLink: '',
148
+  },
149
+  {
150
+    id: 'xxx3',
151
+    title: titles[2],
152
+    logo: avatars[2],
153
+    description: '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆',
154
+    updatedAt: new Date(),
155
+    member: '中二少女团',
156
+    href: '',
157
+    memberLink: '',
158
+  },
159
+  {
160
+    id: 'xxx4',
161
+    title: titles[3],
162
+    logo: avatars[3],
163
+    description: '那时候我只会想自己想要什么,从不想自己拥有什么',
164
+    updatedAt: new Date('2017-07-23'),
165
+    member: '程序员日常',
166
+    href: '',
167
+    memberLink: '',
168
+  },
169
+  {
170
+    id: 'xxx5',
171
+    title: titles[4],
172
+    logo: avatars[4],
173
+    description: '凛冬将至',
174
+    updatedAt: new Date('2017-07-23'),
175
+    member: '高逼格设计天团',
176
+    href: '',
177
+    memberLink: '',
178
+  },
179
+  {
180
+    id: 'xxx6',
181
+    title: titles[5],
182
+    logo: avatars[5],
183
+    description: '生命就像一盒巧克力,结果往往出人意料',
184
+    updatedAt: new Date('2017-07-23'),
185
+    member: '骗你来学计算机',
186
+    href: '',
187
+    memberLink: '',
188
+  },
189
+];
190
+
191
+export const getActivities = [
192
+  {
193
+    id: 'trend-1',
194
+    updatedAt: new Date(),
195
+    user: {
196
+      name: '曲丽丽',
197
+      avatar: avatars2[0],
198
+    },
199
+    group: {
200
+      name: '高逼格设计天团',
201
+      link: 'http://github.com/',
202
+    },
203
+    project: {
204
+      name: '六月迭代',
205
+      link: 'http://github.com/',
206
+    },
207
+    template: '在 @{group} 新建项目 @{project}',
208
+  },
209
+  {
210
+    id: 'trend-2',
211
+    updatedAt: new Date(),
212
+    user: {
213
+      name: '付小小',
214
+      avatar: avatars2[1],
215
+    },
216
+    group: {
217
+      name: '高逼格设计天团',
218
+      link: 'http://github.com/',
219
+    },
220
+    project: {
221
+      name: '六月迭代',
222
+      link: 'http://github.com/',
223
+    },
224
+    template: '在 @{group} 新建项目 @{project}',
225
+  },
226
+  {
227
+    id: 'trend-3',
228
+    updatedAt: new Date(),
229
+    user: {
230
+      name: '林东东',
231
+      avatar: avatars2[2],
232
+    },
233
+    group: {
234
+      name: '中二少女团',
235
+      link: 'http://github.com/',
236
+    },
237
+    project: {
238
+      name: '六月迭代',
239
+      link: 'http://github.com/',
240
+    },
241
+    template: '在 @{group} 新建项目 @{project}',
242
+  },
243
+  {
244
+    id: 'trend-4',
245
+    updatedAt: new Date(),
246
+    user: {
247
+      name: '周星星',
248
+      avatar: avatars2[4],
249
+    },
250
+    project: {
251
+      name: '5 月日常迭代',
252
+      link: 'http://github.com/',
253
+    },
254
+    template: '将 @{project} 更新至已发布状态',
255
+  },
256
+  {
257
+    id: 'trend-5',
258
+    updatedAt: new Date(),
259
+    user: {
260
+      name: '朱偏右',
261
+      avatar: avatars2[3],
262
+    },
263
+    project: {
264
+      name: '工程效能',
265
+      link: 'http://github.com/',
266
+    },
267
+    comment: {
268
+      name: '留言',
269
+      link: 'http://github.com/',
270
+    },
271
+    template: '在 @{project} 发布了 @{comment}',
272
+  },
273
+  {
274
+    id: 'trend-6',
275
+    updatedAt: new Date(),
276
+    user: {
277
+      name: '乐哥',
278
+      avatar: avatars2[5],
279
+    },
280
+    group: {
281
+      name: '程序员日常',
282
+      link: 'http://github.com/',
283
+    },
284
+    project: {
285
+      name: '品牌迭代',
286
+      link: 'http://github.com/',
287
+    },
288
+    template: '在 @{group} 新建项目 @{project}',
289
+  },
290
+];
291
+
292
+export default {
293
+  getNotice,
294
+  getActivities,
295
+  getFakeList,
296
+};

+ 197
- 0
mock/chart.js View File

@@ -0,0 +1,197 @@
1
+import moment from 'moment';
2
+
3
+// mock data
4
+const visitData = [];
5
+const beginDay = new Date().getTime();
6
+
7
+const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5];
8
+for (let i = 0; i < fakeY.length; i += 1) {
9
+  visitData.push({
10
+    x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
11
+    y: fakeY[i],
12
+  });
13
+}
14
+
15
+const visitData2 = [];
16
+const fakeY2 = [1, 6, 4, 8, 3, 7, 2];
17
+for (let i = 0; i < fakeY2.length; i += 1) {
18
+  visitData2.push({
19
+    x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
20
+    y: fakeY2[i],
21
+  });
22
+}
23
+
24
+const salesData = [];
25
+for (let i = 0; i < 12; i += 1) {
26
+  salesData.push({
27
+    x: `${i + 1}月`,
28
+    y: Math.floor(Math.random() * 1000) + 200,
29
+  });
30
+}
31
+const searchData = [];
32
+for (let i = 0; i < 50; i += 1) {
33
+  searchData.push({
34
+    index: i + 1,
35
+    keyword: `搜索关键词-${i}`,
36
+    count: Math.floor(Math.random() * 1000),
37
+    range: Math.floor(Math.random() * 100),
38
+    status: Math.floor((Math.random() * 10) % 2),
39
+  });
40
+}
41
+const salesTypeData = [
42
+  {
43
+    x: '家用电器',
44
+    y: 4544,
45
+  },
46
+  {
47
+    x: '食用酒水',
48
+    y: 3321,
49
+  },
50
+  {
51
+    x: '个护健康',
52
+    y: 3113,
53
+  },
54
+  {
55
+    x: '服饰箱包',
56
+    y: 2341,
57
+  },
58
+  {
59
+    x: '母婴产品',
60
+    y: 1231,
61
+  },
62
+  {
63
+    x: '其他',
64
+    y: 1231,
65
+  },
66
+];
67
+
68
+const salesTypeDataOnline = [
69
+  {
70
+    x: '家用电器',
71
+    y: 244,
72
+  },
73
+  {
74
+    x: '食用酒水',
75
+    y: 321,
76
+  },
77
+  {
78
+    x: '个护健康',
79
+    y: 311,
80
+  },
81
+  {
82
+    x: '服饰箱包',
83
+    y: 41,
84
+  },
85
+  {
86
+    x: '母婴产品',
87
+    y: 121,
88
+  },
89
+  {
90
+    x: '其他',
91
+    y: 111,
92
+  },
93
+];
94
+
95
+const salesTypeDataOffline = [
96
+  {
97
+    x: '家用电器',
98
+    y: 99,
99
+  },
100
+  {
101
+    x: '个护健康',
102
+    y: 188,
103
+  },
104
+  {
105
+    x: '服饰箱包',
106
+    y: 344,
107
+  },
108
+  {
109
+    x: '母婴产品',
110
+    y: 255,
111
+  },
112
+  {
113
+    x: '其他',
114
+    y: 65,
115
+  },
116
+];
117
+
118
+const offlineData = [];
119
+for (let i = 0; i < 10; i += 1) {
120
+  offlineData.push({
121
+    name: `门店${i}`,
122
+    cvr: Math.ceil(Math.random() * 9) / 10,
123
+  });
124
+}
125
+const offlineChartData = [];
126
+for (let i = 0; i < 20; i += 1) {
127
+  offlineChartData.push({
128
+    x: new Date().getTime() + 1000 * 60 * 30 * i,
129
+    y1: Math.floor(Math.random() * 100) + 10,
130
+    y2: Math.floor(Math.random() * 100) + 10,
131
+  });
132
+}
133
+
134
+const radarOriginData = [
135
+  {
136
+    name: '个人',
137
+    ref: 10,
138
+    koubei: 8,
139
+    output: 4,
140
+    contribute: 5,
141
+    hot: 7,
142
+  },
143
+  {
144
+    name: '团队',
145
+    ref: 3,
146
+    koubei: 9,
147
+    output: 6,
148
+    contribute: 3,
149
+    hot: 1,
150
+  },
151
+  {
152
+    name: '部门',
153
+    ref: 4,
154
+    koubei: 1,
155
+    output: 6,
156
+    contribute: 5,
157
+    hot: 7,
158
+  },
159
+];
160
+
161
+//
162
+const radarData = [];
163
+const radarTitleMap = {
164
+  ref: '引用',
165
+  koubei: '口碑',
166
+  output: '产量',
167
+  contribute: '贡献',
168
+  hot: '热度',
169
+};
170
+radarOriginData.forEach(item => {
171
+  Object.keys(item).forEach(key => {
172
+    if (key !== 'name') {
173
+      radarData.push({
174
+        name: item.name,
175
+        label: radarTitleMap[key],
176
+        value: item[key],
177
+      });
178
+    }
179
+  });
180
+});
181
+
182
+export const getFakeChartData = {
183
+  visitData,
184
+  visitData2,
185
+  salesData,
186
+  searchData,
187
+  offlineData,
188
+  offlineChartData,
189
+  salesTypeData,
190
+  salesTypeDataOnline,
191
+  salesTypeDataOffline,
192
+  radarData,
193
+};
194
+
195
+export default {
196
+  getFakeChartData,
197
+};

+ 99
- 0
mock/notices.js View File

@@ -0,0 +1,99 @@
1
+export const getNotices = (req, res) => {
2
+  res.json([
3
+    {
4
+      id: '000000001',
5
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
6
+      title: '你收到了 14 份新周报',
7
+      datetime: '2017-08-09',
8
+      type: '通知',
9
+    },
10
+    {
11
+      id: '000000002',
12
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
13
+      title: '你推荐的 曲妮妮 已通过第三轮面试',
14
+      datetime: '2017-08-08',
15
+      type: '通知',
16
+    },
17
+    {
18
+      id: '000000003',
19
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
20
+      title: '这种模板可以区分多种通知类型',
21
+      datetime: '2017-08-07',
22
+      read: true,
23
+      type: '通知',
24
+    },
25
+    {
26
+      id: '000000004',
27
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
28
+      title: '左侧图标用于区分不同的类型',
29
+      datetime: '2017-08-07',
30
+      type: '通知',
31
+    },
32
+    {
33
+      id: '000000005',
34
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
35
+      title: '内容不要超过两行字,超出时自动截断',
36
+      datetime: '2017-08-07',
37
+      type: '通知',
38
+    },
39
+    {
40
+      id: '000000006',
41
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
42
+      title: '曲丽丽 评论了你',
43
+      description: '描述信息描述信息描述信息',
44
+      datetime: '2017-08-07',
45
+      type: '消息',
46
+    },
47
+    {
48
+      id: '000000007',
49
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
50
+      title: '朱偏右 回复了你',
51
+      description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
52
+      datetime: '2017-08-07',
53
+      type: '消息',
54
+    },
55
+    {
56
+      id: '000000008',
57
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
58
+      title: '标题',
59
+      description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
60
+      datetime: '2017-08-07',
61
+      type: '消息',
62
+    },
63
+    {
64
+      id: '000000009',
65
+      title: '任务名称',
66
+      description: '任务需要在 2017-01-12 20:00 前启动',
67
+      extra: '未开始',
68
+      status: 'todo',
69
+      type: '待办',
70
+    },
71
+    {
72
+      id: '000000010',
73
+      title: '第三方紧急代码变更',
74
+      description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
75
+      extra: '马上到期',
76
+      status: 'urgent',
77
+      type: '待办',
78
+    },
79
+    {
80
+      id: '000000011',
81
+      title: '信息安全考试',
82
+      description: '指派竹尔于 2017-01-09 前完成更新并发布',
83
+      extra: '已耗时 8 天',
84
+      status: 'doing',
85
+      type: '待办',
86
+    },
87
+    {
88
+      id: '000000012',
89
+      title: 'ABCD 版本发布',
90
+      description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
91
+      extra: '进行中',
92
+      status: 'processing',
93
+      type: '待办',
94
+    },
95
+  ]);
96
+};
97
+export default {
98
+  getNotices,
99
+};

+ 158
- 0
mock/profile.js View File

@@ -0,0 +1,158 @@
1
+const basicGoods = [
2
+  {
3
+    id: '1234561',
4
+    name: '矿泉水 550ml',
5
+    barcode: '12421432143214321',
6
+    price: '2.00',
7
+    num: '1',
8
+    amount: '2.00',
9
+  },
10
+  {
11
+    id: '1234562',
12
+    name: '凉茶 300ml',
13
+    barcode: '12421432143214322',
14
+    price: '3.00',
15
+    num: '2',
16
+    amount: '6.00',
17
+  },
18
+  {
19
+    id: '1234563',
20
+    name: '好吃的薯片',
21
+    barcode: '12421432143214323',
22
+    price: '7.00',
23
+    num: '4',
24
+    amount: '28.00',
25
+  },
26
+  {
27
+    id: '1234564',
28
+    name: '特别好吃的蛋卷',
29
+    barcode: '12421432143214324',
30
+    price: '8.50',
31
+    num: '3',
32
+    amount: '25.50',
33
+  },
34
+];
35
+
36
+const basicProgress = [
37
+  {
38
+    key: '1',
39
+    time: '2017-10-01 14:10',
40
+    rate: '联系客户',
41
+    status: 'processing',
42
+    operator: '取货员 ID1234',
43
+    cost: '5mins',
44
+  },
45
+  {
46
+    key: '2',
47
+    time: '2017-10-01 14:05',
48
+    rate: '取货员出发',
49
+    status: 'success',
50
+    operator: '取货员 ID1234',
51
+    cost: '1h',
52
+  },
53
+  {
54
+    key: '3',
55
+    time: '2017-10-01 13:05',
56
+    rate: '取货员接单',
57
+    status: 'success',
58
+    operator: '取货员 ID1234',
59
+    cost: '5mins',
60
+  },
61
+  {
62
+    key: '4',
63
+    time: '2017-10-01 13:00',
64
+    rate: '申请审批通过',
65
+    status: 'success',
66
+    operator: '系统',
67
+    cost: '1h',
68
+  },
69
+  {
70
+    key: '5',
71
+    time: '2017-10-01 12:00',
72
+    rate: '发起退货申请',
73
+    status: 'success',
74
+    operator: '用户',
75
+    cost: '5mins',
76
+  },
77
+];
78
+
79
+const advancedOperation1 = [
80
+  {
81
+    key: 'op1',
82
+    type: '订购关系生效',
83
+    name: '曲丽丽',
84
+    status: 'agree',
85
+    updatedAt: '2017-10-03  19:23:12',
86
+    memo: '-',
87
+  },
88
+  {
89
+    key: 'op2',
90
+    type: '财务复审',
91
+    name: '付小小',
92
+    status: 'reject',
93
+    updatedAt: '2017-10-03  19:23:12',
94
+    memo: '不通过原因',
95
+  },
96
+  {
97
+    key: 'op3',
98
+    type: '部门初审',
99
+    name: '周毛毛',
100
+    status: 'agree',
101
+    updatedAt: '2017-10-03  19:23:12',
102
+    memo: '-',
103
+  },
104
+  {
105
+    key: 'op4',
106
+    type: '提交订单',
107
+    name: '林东东',
108
+    status: 'agree',
109
+    updatedAt: '2017-10-03  19:23:12',
110
+    memo: '很棒',
111
+  },
112
+  {
113
+    key: 'op5',
114
+    type: '创建订单',
115
+    name: '汗牙牙',
116
+    status: 'agree',
117
+    updatedAt: '2017-10-03  19:23:12',
118
+    memo: '-',
119
+  },
120
+];
121
+
122
+const advancedOperation2 = [
123
+  {
124
+    key: 'op1',
125
+    type: '订购关系生效',
126
+    name: '曲丽丽',
127
+    status: 'agree',
128
+    updatedAt: '2017-10-03  19:23:12',
129
+    memo: '-',
130
+  },
131
+];
132
+
133
+const advancedOperation3 = [
134
+  {
135
+    key: 'op1',
136
+    type: '创建订单',
137
+    name: '汗牙牙',
138
+    status: 'agree',
139
+    updatedAt: '2017-10-03  19:23:12',
140
+    memo: '-',
141
+  },
142
+];
143
+
144
+export const getProfileBasicData = {
145
+  basicGoods,
146
+  basicProgress,
147
+};
148
+
149
+export const getProfileAdvancedData = {
150
+  advancedOperation1,
151
+  advancedOperation2,
152
+  advancedOperation3,
153
+};
154
+
155
+export default {
156
+  getProfileBasicData,
157
+  getProfileAdvancedData,
158
+};

+ 137
- 0
mock/rule.js View File

@@ -0,0 +1,137 @@
1
+import { parse } from 'url';
2
+
3
+// mock tableListDataSource
4
+let tableListDataSource = [];
5
+for (let i = 0; i < 46; i += 1) {
6
+  tableListDataSource.push({
7
+    key: i,
8
+    disabled: i % 6 === 0,
9
+    href: 'https://ant.design',
10
+    avatar: [
11
+      'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
12
+      'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
13
+    ][i % 2],
14
+    no: `TradeCode ${i}`,
15
+    title: `一个任务名称 ${i}`,
16
+    owner: '曲丽丽',
17
+    description: '这是一段描述',
18
+    callNo: Math.floor(Math.random() * 1000),
19
+    status: Math.floor(Math.random() * 10) % 4,
20
+    updatedAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`),
21
+    createdAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`),
22
+    progress: Math.ceil(Math.random() * 100),
23
+  });
24
+}
25
+
26
+export function getRule(req, res, u) {
27
+  let url = u;
28
+  if (!url || Object.prototype.toString.call(url) !== '[object String]') {
29
+    url = req.url; // eslint-disable-line
30
+  }
31
+
32
+  const params = parse(url, true).query;
33
+
34
+  let dataSource = [...tableListDataSource];
35
+
36
+  if (params.sorter) {
37
+    const s = params.sorter.split('_');
38
+    dataSource = dataSource.sort((prev, next) => {
39
+      if (s[1] === 'descend') {
40
+        return next[s[0]] - prev[s[0]];
41
+      }
42
+      return prev[s[0]] - next[s[0]];
43
+    });
44
+  }
45
+
46
+  if (params.status) {
47
+    const status = params.status.split(',');
48
+    let filterDataSource = [];
49
+    status.forEach(s => {
50
+      filterDataSource = filterDataSource.concat(
51
+        [...dataSource].filter(data => parseInt(data.status, 10) === parseInt(s[0], 10))
52
+      );
53
+    });
54
+    dataSource = filterDataSource;
55
+  }
56
+
57
+  if (params.no) {
58
+    dataSource = dataSource.filter(data => data.no.indexOf(params.no) > -1);
59
+  }
60
+
61
+  let pageSize = 10;
62
+  if (params.pageSize) {
63
+    pageSize = params.pageSize * 1;
64
+  }
65
+
66
+  const result = {
67
+    list: dataSource,
68
+    pagination: {
69
+      total: dataSource.length,
70
+      pageSize,
71
+      current: parseInt(params.currentPage, 10) || 1,
72
+    },
73
+  };
74
+
75
+  if (res && res.json) {
76
+    res.json(result);
77
+  } else {
78
+    return result;
79
+  }
80
+}
81
+
82
+export function postRule(req, res, u, b) {
83
+  let url = u;
84
+  if (!url || Object.prototype.toString.call(url) !== '[object String]') {
85
+    url = req.url; // eslint-disable-line
86
+  }
87
+
88
+  const body = (b && b.body) || req.body;
89
+  const { method, no, description } = body;
90
+
91
+  switch (method) {
92
+    /* eslint no-case-declarations:0 */
93
+    case 'delete':
94
+      tableListDataSource = tableListDataSource.filter(item => no.indexOf(item.no) === -1);
95
+      break;
96
+    case 'post':
97
+      const i = Math.ceil(Math.random() * 10000);
98
+      tableListDataSource.unshift({
99
+        key: i,
100
+        href: 'https://ant.design',
101
+        avatar: [
102
+          'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
103
+          'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
104
+        ][i % 2],
105
+        no: `TradeCode ${i}`,
106
+        title: `一个任务名称 ${i}`,
107
+        owner: '曲丽丽',
108
+        description,
109
+        callNo: Math.floor(Math.random() * 1000),
110
+        status: Math.floor(Math.random() * 10) % 2,
111
+        updatedAt: new Date(),
112
+        createdAt: new Date(),
113
+        progress: Math.ceil(Math.random() * 100),
114
+      });
115
+      break;
116
+    default:
117
+      break;
118
+  }
119
+
120
+  const result = {
121
+    list: tableListDataSource,
122
+    pagination: {
123
+      total: tableListDataSource.length,
124
+    },
125
+  };
126
+
127
+  if (res && res.json) {
128
+    res.json(result);
129
+  } else {
130
+    return result;
131
+  }
132
+}
133
+
134
+export default {
135
+  getRule,
136
+  postRule,
137
+};

+ 102
- 0
package.json View File

@@ -0,0 +1,102 @@
1
+{
2
+  "name": "ant-design-pro",
3
+  "version": "1.3.0",
4
+  "description": "An out-of-box UI solution for enterprise applications",
5
+  "private": true,
6
+  "scripts": {
7
+    "precommit": "npm run lint-staged",
8
+    "start": "cross-env ESLINT=none roadhog dev",
9
+    "start:no-proxy": "cross-env NO_PROXY=true ESLINT=none roadhog dev",
10
+    "build": "cross-env ESLINT=none roadhog build",
11
+    "site": "roadhog-api-doc static && gh-pages -d dist",
12
+    "analyze": "cross-env ANALYZE=true roadhog build",
13
+    "lint:style": "stylelint \"src/**/*.less\" --syntax less",
14
+    "lint": "eslint --ext .js src mock tests && npm run lint:style",
15
+    "lint:fix": "eslint --fix --ext .js src mock tests && npm run lint:style",
16
+    "lint-staged": "lint-staged",
17
+    "lint-staged:js": "eslint --ext .js",
18
+    "test": "roadhog test",
19
+    "test:component": "roadhog test ./src/components",
20
+    "test:all": "node ./tests/run-tests.js",
21
+    "prettier": "prettier --write ./src/**/**/**/*"
22
+  },
23
+  "dependencies": {
24
+    "@antv/data-set": "^0.8.0",
25
+    "@babel/polyfill": "^7.0.0-beta.36",
26
+    "antd": "^3.4.3",
27
+    "babel-plugin-transform-decorators-legacy": "^1.3.4",
28
+    "babel-runtime": "^6.9.2",
29
+    "bizcharts": "^3.1.5",
30
+    "bizcharts-plugin-slider": "^2.0.1",
31
+    "classnames": "^2.2.5",
32
+    "dva": "^2.2.3",
33
+    "dva-loading": "^1.0.4",
34
+    "enquire-js": "^0.2.1",
35
+    "lodash": "^4.17.4",
36
+    "lodash-decorators": "^4.4.1",
37
+    "moment": "^2.19.1",
38
+    "numeral": "^2.0.6",
39
+    "omit.js": "^1.0.0",
40
+    "path-to-regexp": "^2.1.0",
41
+    "prop-types": "^15.5.10",
42
+    "qs": "^6.5.0",
43
+    "rc-drawer-menu": "^0.5.0",
44
+    "react": "^16.2.0",
45
+    "react-container-query": "^0.9.1",
46
+    "react-document-title": "^2.0.3",
47
+    "react-dom": "^16.2.0",
48
+    "react-fittext": "^1.0.0",
49
+    "rollbar": "^2.3.4",
50
+    "url-polyfill": "^1.0.10"
51
+  },
52
+  "devDependencies": {
53
+    "babel-eslint": "^8.1.2",
54
+    "babel-plugin-dva-hmr": "^0.4.1",
55
+    "babel-plugin-import": "^1.6.7",
56
+    "babel-plugin-module-resolver": "^3.1.1",
57
+    "cross-env": "^5.1.1",
58
+    "cross-port-killer": "^1.0.1",
59
+    "enzyme": "^3.1.0",
60
+    "eslint": "^4.14.0",
61
+    "eslint-config-airbnb": "^16.0.0",
62
+    "eslint-config-prettier": "^2.9.0",
63
+    "eslint-plugin-babel": "^4.0.0",
64
+    "eslint-plugin-compat": "^2.1.0",
65
+    "eslint-plugin-import": "^2.8.0",
66
+    "eslint-plugin-jsx-a11y": "^6.0.3",
67
+    "eslint-plugin-markdown": "^1.0.0-beta.6",
68
+    "eslint-plugin-react": "^7.0.1",
69
+    "gh-pages": "^1.0.0",
70
+    "husky": "^0.14.3",
71
+    "lint-staged": "^6.0.0",
72
+    "mockjs": "^1.0.1-beta3",
73
+    "prettier": "1.11.1",
74
+    "pro-download": "^1.0.1",
75
+    "redbox-react": "^1.5.0",
76
+    "regenerator-runtime": "^0.11.1",
77
+    "roadhog": "^2.3.0",
78
+    "roadhog-api-doc": "^1.0.2",
79
+    "stylelint": "^8.4.0",
80
+    "stylelint-config-prettier": "^3.0.4",
81
+    "stylelint-config-standard": "^18.0.0"
82
+  },
83
+  "optionalDependencies": {
84
+    "puppeteer": "^1.1.1"
85
+  },
86
+  "lint-staged": {
87
+    "**/*.{js,jsx,less}": [
88
+      "prettier --wirter",
89
+      "git add"
90
+    ],
91
+    "**/*.{js,jsx}": "lint-staged:js",
92
+    "**/*.less": "stylelint --syntax less"
93
+  },
94
+  "engines": {
95
+    "node": ">=8.0.0"
96
+  },
97
+  "browserslist": [
98
+    "> 1%",
99
+    "last 2 versions",
100
+    "not ie <= 10"
101
+  ]
102
+}

BIN
public/favicon.png View File


+ 43
- 0
src/assets/logo.svg View File

@@ -0,0 +1,43 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<svg width="200px" height="200px" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+    <!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
4
+    <title>Group 28 Copy 5</title>
5
+    <desc>Created with Sketch.</desc>
6
+    <defs>
7
+        <linearGradient x1="62.1023273%" y1="0%" x2="108.19718%" y2="37.8635764%" id="linearGradient-1">
8
+            <stop stop-color="#4285EB" offset="0%"></stop>
9
+            <stop stop-color="#2EC7FF" offset="100%"></stop>
10
+        </linearGradient>
11
+        <linearGradient x1="69.644116%" y1="0%" x2="54.0428975%" y2="108.456714%" id="linearGradient-2">
12
+            <stop stop-color="#29CDFF" offset="0%"></stop>
13
+            <stop stop-color="#148EFF" offset="37.8600687%"></stop>
14
+            <stop stop-color="#0A60FF" offset="100%"></stop>
15
+        </linearGradient>
16
+        <linearGradient x1="69.6908165%" y1="-12.9743587%" x2="16.7228981%" y2="117.391248%" id="linearGradient-3">
17
+            <stop stop-color="#FA816E" offset="0%"></stop>
18
+            <stop stop-color="#F74A5C" offset="41.472606%"></stop>
19
+            <stop stop-color="#F51D2C" offset="100%"></stop>
20
+        </linearGradient>
21
+        <linearGradient x1="68.1279872%" y1="-35.6905737%" x2="30.4400914%" y2="114.942679%" id="linearGradient-4">
22
+            <stop stop-color="#FA8E7D" offset="0%"></stop>
23
+            <stop stop-color="#F74A5C" offset="51.2635191%"></stop>
24
+            <stop stop-color="#F51D2C" offset="100%"></stop>
25
+        </linearGradient>
26
+    </defs>
27
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
28
+        <g id="logo" transform="translate(-20.000000, -20.000000)">
29
+            <g id="Group-28-Copy-5" transform="translate(20.000000, 20.000000)">
30
+                <g id="Group-27-Copy-3">
31
+                    <g id="Group-25" fill-rule="nonzero">
32
+                        <g id="2">
33
+                            <path d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C99.2571609,26.9692191 101.032305,26.9692191 102.20193,28.1378823 L129.985225,55.8983314 C134.193707,60.1033528 141.017005,60.1033528 145.225487,55.8983314 C149.433969,51.69331 149.433969,44.8756232 145.225487,40.6706018 L108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z" id="Shape" fill="url(#linearGradient-1)"></path>
34
+                            <path d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C100.999864,25.6271836 105.751642,20.541824 112.729652,19.3524487 C117.915585,18.4685261 123.585219,20.4140239 129.738554,25.1889424 C125.624663,21.0784292 118.571995,14.0340304 108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z" id="Shape" fill="url(#linearGradient-2)"></path>
35
+                        </g>
36
+                        <path d="M153.685633,135.854579 C157.894115,140.0596 164.717412,140.0596 168.925894,135.854579 L195.959977,108.842726 C200.659183,104.147384 200.659183,96.5636133 195.960527,91.8688194 L168.690777,64.7181159 C164.472332,60.5180858 157.646868,60.5241425 153.435895,64.7316526 C149.227413,68.936674 149.227413,75.7543607 153.435895,79.9593821 L171.854035,98.3623765 C173.02366,99.5310396 173.02366,101.304724 171.854035,102.473387 L153.685633,120.626849 C149.47715,124.83187 149.47715,131.649557 153.685633,135.854579 Z" id="Shape" fill="url(#linearGradient-3)"></path>
37
+                    </g>
38
+                    <ellipse id="Combined-Shape" fill="url(#linearGradient-4)" cx="100.519339" cy="100.436681" rx="23.6001926" ry="23.580786"></ellipse>
39
+                </g>
40
+            </g>
41
+        </g>
42
+    </g>
43
+</svg>

+ 177
- 0
src/common/menu.js View File

@@ -0,0 +1,177 @@
1
+import { isUrl } from '../utils/utils';
2
+
3
+// const menuData = [
4
+//   {
5
+//     name: 'dashboard',
6
+//     icon: 'dashboard',
7
+//     path: 'dashboard',
8
+//     children: [
9
+//       {
10
+//         name: '分析页',
11
+//         path: 'analysis',
12
+//       },
13
+//       {
14
+//         name: '监控页',
15
+//         path: 'monitor',
16
+//       },
17
+//       {
18
+//         name: '工作台',
19
+//         path: 'workplace',
20
+//         // hideInBreadcrumb: true,
21
+//         // hideInMenu: true,
22
+//       },
23
+//     ],
24
+//   },
25
+//   {
26
+//     name: '表单页',
27
+//     icon: 'form',
28
+//     path: 'form',
29
+//     children: [
30
+//       {
31
+//         name: '基础表单',
32
+//         path: 'basic-form',
33
+//       },
34
+//       {
35
+//         name: '分步表单',
36
+//         path: 'step-form',
37
+//       },
38
+//       {
39
+//         name: '高级表单',
40
+//         authority: 'admin',
41
+//         path: 'advanced-form',
42
+//       },
43
+//     ],
44
+//   },
45
+//   {
46
+//     name: '列表页',
47
+//     icon: 'table',
48
+//     path: 'list',
49
+//     children: [
50
+//       {
51
+//         name: '查询表格',
52
+//         path: 'table-list',
53
+//       },
54
+//       {
55
+//         name: '标准列表',
56
+//         path: 'basic-list',
57
+//       },
58
+//       {
59
+//         name: '卡片列表',
60
+//         path: 'card-list',
61
+//       },
62
+//       {
63
+//         name: '搜索列表',
64
+//         path: 'search',
65
+//         children: [
66
+//           {
67
+//             name: '搜索列表(文章)',
68
+//             path: 'articles',
69
+//           },
70
+//           {
71
+//             name: '搜索列表(项目)',
72
+//             path: 'projects',
73
+//           },
74
+//           {
75
+//             name: '搜索列表(应用)',
76
+//             path: 'applications',
77
+//           },
78
+//         ],
79
+//       },
80
+//     ],
81
+//   },
82
+//   {
83
+//     name: '详情页',
84
+//     icon: 'profile',
85
+//     path: 'profile',
86
+//     children: [
87
+//       {
88
+//         name: '基础详情页',
89
+//         path: 'basic',
90
+//       },
91
+//       {
92
+//         name: '高级详情页',
93
+//         path: 'advanced',
94
+//         authority: 'admin',
95
+//       },
96
+//     ],
97
+//   },
98
+//   {
99
+//     name: '结果页',
100
+//     icon: 'check-circle-o',
101
+//     path: 'result',
102
+//     children: [
103
+//       {
104
+//         name: '成功',
105
+//         path: 'success',
106
+//       },
107
+//       {
108
+//         name: '失败',
109
+//         path: 'fail',
110
+//       },
111
+//     ],
112
+//   },
113
+//   {
114
+//     name: '异常页',
115
+//     icon: 'warning',
116
+//     path: 'exception',
117
+//     children: [
118
+//       {
119
+//         name: '403',
120
+//         path: '403',
121
+//       },
122
+//       {
123
+//         name: '404',
124
+//         path: '404',
125
+//       },
126
+//       {
127
+//         name: '500',
128
+//         path: '500',
129
+//       },
130
+//       {
131
+//         name: '触发异常',
132
+//         path: 'trigger',
133
+//         hideInMenu: true,
134
+//       },
135
+//     ],
136
+//   },
137
+//   {
138
+//     name: '账户',
139
+//     icon: 'user',
140
+//     path: 'user',
141
+//     authority: 'guest',
142
+//     children: [
143
+//       {
144
+//         name: '登录',
145
+//         path: 'login',
146
+//       },
147
+//       {
148
+//         name: '注册',
149
+//         path: 'register',
150
+//       },
151
+//       {
152
+//         name: '注册结果',
153
+//         path: 'register-result',
154
+//       },
155
+//     ],
156
+//   },
157
+// ];
158
+
159
+function formatter(data, parentPath = '/', parentAuthority) {
160
+  return data.map(item => {
161
+    let { path } = item;
162
+    if (!isUrl(path)) {
163
+      path = parentPath + item.path;
164
+    }
165
+    const result = {
166
+      ...item,
167
+      path,
168
+      authority: item.authority || parentAuthority,
169
+    };
170
+    if (item.children) {
171
+      result.children = formatter(item.children, `${parentPath}${item.path}/`, item.authority);
172
+    }
173
+    return result;
174
+  });
175
+}
176
+
177
+export const getMenuData = (menuData) => formatter(menuData);

+ 206
- 0
src/common/router.js View File

@@ -0,0 +1,206 @@
1
+import { createElement } from 'react';
2
+import dynamic from 'dva/dynamic';
3
+import pathToRegexp from 'path-to-regexp';
4
+// import { getMenuData } from './menu';
5
+
6
+let routerDataCache;
7
+
8
+const modelNotExisted = (app, model) =>
9
+  // eslint-disable-next-line
10
+  !app._models.some(({ namespace }) => {
11
+    return namespace === model.substring(model.lastIndexOf('/') + 1);
12
+  });
13
+
14
+// wrapper of dynamic
15
+const dynamicWrapper = (app, models, component) => {
16
+  // () => require('module')
17
+  // transformed by babel-plugin-dynamic-import-node-sync
18
+  if (component.toString().indexOf('.then(') < 0) {
19
+    models.forEach(model => {
20
+      if (modelNotExisted(app, model)) {
21
+        // eslint-disable-next-line
22
+        app.model(require(`../models/${model}`).default);
23
+      }
24
+    });
25
+    return props => {
26
+      if (!routerDataCache) {
27
+        routerDataCache = getRouterData(app);
28
+      }
29
+      return createElement(component().default, {
30
+        ...props,
31
+        routerData: routerDataCache,
32
+      });
33
+    };
34
+  }
35
+  // () => import('module')
36
+  return dynamic({
37
+    app,
38
+    models: () =>
39
+      models.filter(model => modelNotExisted(app, model)).map(m => import(`../models/${m}.js`)),
40
+    // add routerData prop
41
+    component: () => {
42
+      if (!routerDataCache) {
43
+        routerDataCache = getRouterData(app);
44
+      }
45
+      return component().then(raw => {
46
+        const Component = raw.default || raw;
47
+        return props =>
48
+          createElement(Component, {
49
+            ...props,
50
+            routerData: routerDataCache,
51
+          });
52
+      });
53
+    },
54
+  });
55
+};
56
+
57
+function getFlatMenuData(menus) {
58
+  let keys = {};
59
+  menus.forEach(item => {
60
+    if (item.children) {
61
+      keys[item.path] = { ...item };
62
+      keys = { ...keys, ...getFlatMenuData(item.children) };
63
+    } else {
64
+      keys[item.path] = { ...item };
65
+    }
66
+  });
67
+  return keys;
68
+}
69
+
70
+export const getRouterData = app => {
71
+  const routerConfig = {
72
+    '/': {
73
+      component: dynamicWrapper(app, ['user', 'login'], () => import('../layouts/BasicLayout')),
74
+    },
75
+    '/dashboard/analysis': {
76
+      component: dynamicWrapper(app, ['chart'], () => import('../routes/Dashboard/Analysis')),
77
+    },
78
+    '/dashboard/monitor': {
79
+      component: dynamicWrapper(app, ['monitor'], () => import('../routes/Dashboard/Monitor')),
80
+    },
81
+    '/dashboard/workplace': {
82
+      component: dynamicWrapper(app, ['project', 'activities', 'chart'], () =>
83
+        import('../routes/Dashboard/Workplace')
84
+      ),
85
+      // hideInBreadcrumb: true,
86
+      // name: '工作台',
87
+      // authority: 'admin',
88
+    },
89
+    '/form/basic-form': {
90
+      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/BasicForm')),
91
+    },
92
+    '/form/step-form': {
93
+      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/StepForm')),
94
+    },
95
+    '/form/step-form/info': {
96
+      name: '分步表单(填写转账信息)',
97
+      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/StepForm/Step1')),
98
+    },
99
+    '/form/step-form/confirm': {
100
+      name: '分步表单(确认转账信息)',
101
+      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/StepForm/Step2')),
102
+    },
103
+    '/form/step-form/result': {
104
+      name: '分步表单(完成)',
105
+      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/StepForm/Step3')),
106
+    },
107
+    '/form/advanced-form': {
108
+      component: dynamicWrapper(app, ['form'], () => import('../routes/Forms/AdvancedForm')),
109
+    },
110
+    '/list/table-list': {
111
+      component: dynamicWrapper(app, ['rule'], () => import('../routes/List/TableList')),
112
+    },
113
+    '/list/basic-list': {
114
+      component: dynamicWrapper(app, ['list'], () => import('../routes/List/BasicList')),
115
+    },
116
+    '/list/card-list': {
117
+      component: dynamicWrapper(app, ['list'], () => import('../routes/List/CardList')),
118
+    },
119
+    '/list/search': {
120
+      component: dynamicWrapper(app, ['list'], () => import('../routes/List/List')),
121
+    },
122
+    '/list/search/projects': {
123
+      component: dynamicWrapper(app, ['list'], () => import('../routes/List/Projects')),
124
+    },
125
+    '/list/search/applications': {
126
+      component: dynamicWrapper(app, ['list'], () => import('../routes/List/Applications')),
127
+    },
128
+    '/list/search/articles': {
129
+      component: dynamicWrapper(app, ['list'], () => import('../routes/List/Articles')),
130
+    },
131
+    '/profile/basic': {
132
+      component: dynamicWrapper(app, ['profile'], () => import('../routes/Profile/BasicProfile')),
133
+    },
134
+    '/profile/advanced': {
135
+      component: dynamicWrapper(app, ['profile'], () =>
136
+        import('../routes/Profile/AdvancedProfile')
137
+      ),
138
+    },
139
+    '/result/success': {
140
+      component: dynamicWrapper(app, [], () => import('../routes/Result/Success')),
141
+    },
142
+    '/result/fail': {
143
+      component: dynamicWrapper(app, [], () => import('../routes/Result/Error')),
144
+    },
145
+    '/exception/403': {
146
+      component: dynamicWrapper(app, [], () => import('../routes/Exception/403')),
147
+    },
148
+    '/exception/404': {
149
+      component: dynamicWrapper(app, [], () => import('../routes/Exception/404')),
150
+    },
151
+    '/exception/500': {
152
+      component: dynamicWrapper(app, [], () => import('../routes/Exception/500')),
153
+    },
154
+    '/exception/trigger': {
155
+      component: dynamicWrapper(app, ['error'], () =>
156
+        import('../routes/Exception/triggerException')
157
+      ),
158
+    },
159
+    '/user': {
160
+      component: dynamicWrapper(app, [], () => import('../layouts/UserLayout')),
161
+    },
162
+    '/user/login': {
163
+      component: dynamicWrapper(app, ['login'], () => import('../routes/User/Login')),
164
+    },
165
+    '/user/register': {
166
+      component: dynamicWrapper(app, ['register'], () => import('../routes/User/Register')),
167
+    },
168
+    '/user/register-result': {
169
+      component: dynamicWrapper(app, [], () => import('../routes/User/RegisterResult')),
170
+    },
171
+    // '/user/:id': {
172
+    //   component: dynamicWrapper(app, [], () => import('../routes/User/SomeComponent')),
173
+    // },
174
+  };
175
+  // Get name from ./menu.js or just set it in the router data.
176
+  // const menuData = getFlatMenuData(getMenuData());
177
+  const menuData = getFlatMenuData([]);
178
+
179
+  // Route configuration data
180
+  // eg. {name,authority ...routerConfig }
181
+  const routerData = {};
182
+  // The route matches the menu
183
+  Object.keys(routerConfig).forEach(path => {
184
+    // Regular match item name
185
+    // eg.  router /user/:id === /user/chen
186
+    const pathRegexp = pathToRegexp(path);
187
+    const menuKey = Object.keys(menuData).find(key => pathRegexp.test(`${key}`));
188
+    let menuItem = {};
189
+    // If menuKey is not empty
190
+    if (menuKey) {
191
+      menuItem = menuData[menuKey];
192
+    }
193
+    let router = routerConfig[path];
194
+    // If you need to configure complex parameter routing,
195
+    // 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
196
+    // eg . /list/:type/user/info/:id
197
+    router = {
198
+      ...router,
199
+      name: router.name || menuItem.name,
200
+      authority: router.authority || menuItem.authority,
201
+      hideInBreadcrumb: router.hideInBreadcrumb || menuItem.hideInBreadcrumb,
202
+    };
203
+    routerData[path] = router;
204
+  });
205
+  return routerData;
206
+};

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

@@ -0,0 +1,82 @@
1
+import React, { Component } from 'react';
2
+
3
+import { MiniArea } from '../Charts';
4
+import NumberInfo from '../NumberInfo';
5
+
6
+import styles from './index.less';
7
+
8
+function fixedZero(val) {
9
+  return val * 1 < 10 ? `0${val}` : val;
10
+}
11
+
12
+function getActiveData() {
13
+  const activeData = [];
14
+  for (let i = 0; i < 24; i += 1) {
15
+    activeData.push({
16
+      x: `${fixedZero(i)}:00`,
17
+      y: Math.floor(Math.random() * 200) + i * 50,
18
+    });
19
+  }
20
+  return activeData;
21
+}
22
+
23
+export default class ActiveChart extends Component {
24
+  state = {
25
+    activeData: getActiveData(),
26
+  };
27
+
28
+  componentDidMount() {
29
+    this.timer = setInterval(() => {
30
+      this.setState({
31
+        activeData: getActiveData(),
32
+      });
33
+    }, 1000);
34
+  }
35
+
36
+  componentWillUnmount() {
37
+    clearInterval(this.timer);
38
+  }
39
+
40
+  render() {
41
+    const { activeData = [] } = this.state;
42
+
43
+    return (
44
+      <div className={styles.activeChart}>
45
+        <NumberInfo subTitle="目标评估" total="有望达到预期" />
46
+        <div style={{ marginTop: 32 }}>
47
+          <MiniArea
48
+            animate={false}
49
+            line
50
+            borderWidth={2}
51
+            height={84}
52
+            scale={{
53
+              y: {
54
+                tickCount: 3,
55
+              },
56
+            }}
57
+            yAxis={{
58
+              tickLine: false,
59
+              label: false,
60
+              title: false,
61
+              line: false,
62
+            }}
63
+            data={activeData}
64
+          />
65
+        </div>
66
+        {activeData && (
67
+          <div className={styles.activeChartGrid}>
68
+            <p>{[...activeData].sort()[activeData.length - 1].y + 200} 亿元</p>
69
+            <p>{[...activeData].sort()[Math.floor(activeData.length / 2)].y} 亿元</p>
70
+          </div>
71
+        )}
72
+        {activeData && (
73
+          <div className={styles.activeChartLegend}>
74
+            <span>00:00</span>
75
+            <span>{activeData[Math.floor(activeData.length / 2)].x}</span>
76
+            <span>{activeData[activeData.length - 1].x}</span>
77
+          </div>
78
+        )}
79
+      </div>
80
+    );
81
+  }
82
+}

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

@@ -0,0 +1,31 @@
1
+.activeChart {
2
+  position: relative;
3
+}
4
+.activeChartGrid {
5
+  p {
6
+    position: absolute;
7
+    top: 80px;
8
+  }
9
+  p:last-child {
10
+    top: 115px;
11
+  }
12
+}
13
+.activeChartLegend {
14
+  position: relative;
15
+  font-size: 0;
16
+  margin-top: 8px;
17
+  height: 20px;
18
+  line-height: 20px;
19
+  span {
20
+    display: inline-block;
21
+    font-size: 12px;
22
+    text-align: center;
23
+    width: 33.33%;
24
+  }
25
+  span:first-child {
26
+    text-align: left;
27
+  }
28
+  span:last-child {
29
+    text-align: right;
30
+  }
31
+}

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

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

+ 19
- 0
src/components/Authorized/AuthorizedRoute.js View File

@@ -0,0 +1,19 @@
1
+import React from 'react';
2
+import { Route, Redirect } from 'react-router-dom';
3
+import Authorized from './Authorized';
4
+
5
+class AuthorizedRoute extends React.Component {
6
+  render() {
7
+    const { component: Component, render, authority, redirectPath, ...rest } = this.props;
8
+    return (
9
+      <Authorized
10
+        authority={authority}
11
+        noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />}
12
+      >
13
+        <Route {...rest} render={props => (Component ? <Component {...props} /> : render(props))} />
14
+      </Authorized>
15
+    );
16
+  }
17
+}
18
+
19
+export default AuthorizedRoute;

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

@@ -0,0 +1,69 @@
1
+import React from 'react';
2
+import PromiseRender from './PromiseRender';
3
+import { CURRENT } from './index';
4
+
5
+function isPromise(obj) {
6
+  return (
7
+    !!obj &&
8
+    (typeof obj === 'object' || typeof obj === 'function') &&
9
+    typeof obj.then === 'function'
10
+  );
11
+}
12
+
13
+/**
14
+ * 通用权限检查方法
15
+ * Common check permissions method
16
+ * @param { 权限判定 Permission judgment type string |array | Promise | Function } authority
17
+ * @param { 你的权限 Your permission description  type:string} currentAuthority
18
+ * @param { 通过的组件 Passing components } target
19
+ * @param { 未通过的组件 no pass components } Exception
20
+ */
21
+const checkPermissions = (authority, currentAuthority, target, Exception) => {
22
+  // 没有判定权限.默认查看所有
23
+  // Retirement authority, return target;
24
+  if (!authority) {
25
+    return target;
26
+  }
27
+  // 数组处理
28
+  if (Array.isArray(authority)) {
29
+    if (authority.indexOf(currentAuthority) >= 0) {
30
+      return target;
31
+    }
32
+    return Exception;
33
+  }
34
+
35
+  // string 处理
36
+  if (typeof authority === 'string') {
37
+    if (authority === currentAuthority) {
38
+      return target;
39
+    }
40
+    return Exception;
41
+  }
42
+
43
+  // Promise 处理
44
+  if (isPromise(authority)) {
45
+    return <PromiseRender ok={target} error={Exception} promise={authority} />;
46
+  }
47
+
48
+  // Function 处理
49
+  if (typeof authority === 'function') {
50
+    try {
51
+      const bool = authority(currentAuthority);
52
+      if (bool) {
53
+        return target;
54
+      }
55
+      return Exception;
56
+    } catch (error) {
57
+      throw error;
58
+    }
59
+  }
60
+  throw new Error('unsupported parameters');
61
+};
62
+
63
+export { checkPermissions };
64
+
65
+const check = (authority, target, Exception) => {
66
+  return checkPermissions(authority, CURRENT, target, Exception);
67
+};
68
+
69
+export default check;

+ 37
- 0
src/components/Authorized/CheckPermissions.test.js View File

@@ -0,0 +1,37 @@
1
+import { checkPermissions } from './CheckPermissions.js';
2
+
3
+const target = 'ok';
4
+const error = 'error';
5
+
6
+describe('test CheckPermissions', () => {
7
+  it('Correct string permission authentication', () => {
8
+    expect(checkPermissions('user', 'user', target, error)).toEqual('ok');
9
+  });
10
+  it('Correct string permission authentication', () => {
11
+    expect(checkPermissions('user', 'NULL', target, error)).toEqual('error');
12
+  });
13
+  it('authority is undefined , return ok', () => {
14
+    expect(checkPermissions(null, 'NULL', target, error)).toEqual('ok');
15
+  });
16
+  it('currentAuthority is undefined , return error', () => {
17
+    expect(checkPermissions('admin', null, target, error)).toEqual('error');
18
+  });
19
+  it('Wrong string permission authentication', () => {
20
+    expect(checkPermissions('admin', 'user', target, error)).toEqual('error');
21
+  });
22
+  it('Correct Array permission authentication', () => {
23
+    expect(checkPermissions(['user', 'admin'], 'user', target, error)).toEqual('ok');
24
+  });
25
+  it('Wrong Array permission authentication,currentAuthority error', () => {
26
+    expect(checkPermissions(['user', 'admin'], 'user,admin', target, error)).toEqual('error');
27
+  });
28
+  it('Wrong Array permission authentication', () => {
29
+    expect(checkPermissions(['user', 'admin'], 'guest', target, error)).toEqual('error');
30
+  });
31
+  it('Wrong Function permission authentication', () => {
32
+    expect(checkPermissions(() => false, 'guest', target, error)).toEqual('error');
33
+  });
34
+  it('Correct Function permission authentication', () => {
35
+    expect(checkPermissions(() => true, 'guest', target, error)).toEqual('ok');
36
+  });
37
+});

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

@@ -0,0 +1,59 @@
1
+import React from 'react';
2
+import { Spin } from 'antd';
3
+
4
+export default class PromiseRender extends React.PureComponent {
5
+  state = {
6
+    component: null,
7
+  };
8
+  componentDidMount() {
9
+    this.setRenderComponent(this.props);
10
+  }
11
+  componentWillReceiveProps(nextProps) {
12
+    // new Props enter
13
+    this.setRenderComponent(nextProps);
14
+  }
15
+  // set render Component : ok or error
16
+  setRenderComponent(props) {
17
+    const ok = this.checkIsInstantiation(props.ok);
18
+    const error = this.checkIsInstantiation(props.error);
19
+    props.promise
20
+      .then(() => {
21
+        this.setState({
22
+          component: ok,
23
+        });
24
+      })
25
+      .catch(() => {
26
+        this.setState({
27
+          component: error,
28
+        });
29
+      });
30
+  }
31
+  // Determine whether the incoming component has been instantiated
32
+  // AuthorizedRoute is already instantiated
33
+  // Authorized  render is already instantiated, children is no instantiated
34
+  // Secured is not instantiated
35
+  checkIsInstantiation = target => {
36
+    if (!React.isValidElement(target)) {
37
+      return target;
38
+    }
39
+    return () => target;
40
+  };
41
+  render() {
42
+    const Component = this.state.component;
43
+    return Component ? (
44
+      <Component {...this.props} />
45
+    ) : (
46
+      <div
47
+        style={{
48
+          width: '100%',
49
+          height: '100%',
50
+          margin: 'auto',
51
+          paddingTop: 50,
52
+          textAlign: 'center',
53
+        }}
54
+      >
55
+        <Spin size="large" />
56
+      </div>
57
+    );
58
+  }
59
+}

+ 55
- 0
src/components/Authorized/Secured.js View File

@@ -0,0 +1,55 @@
1
+import React from 'react';
2
+import Exception from '../Exception/index';
3
+import CheckPermissions from './CheckPermissions';
4
+/**
5
+ * 默认不能访问任何页面
6
+ * default is "NULL"
7
+ */
8
+const Exception403 = () => <Exception type="403" style={{ minHeight: 500, height: '80%' }} />;
9
+
10
+// Determine whether the incoming component has been instantiated
11
+// AuthorizedRoute is already instantiated
12
+// Authorized  render is already instantiated, children is no instantiated
13
+// Secured is not instantiated
14
+const checkIsInstantiation = target => {
15
+  if (!React.isValidElement(target)) {
16
+    return target;
17
+  }
18
+  return () => target;
19
+};
20
+
21
+/**
22
+ * 用于判断是否拥有权限访问此view权限
23
+ * authority 支持传入  string ,funtion:()=>boolean|Promise
24
+ * e.g. 'user' 只有user用户能访问
25
+ * e.g. 'user,admin' user和 admin 都能访问
26
+ * e.g. ()=>boolean 返回true能访问,返回false不能访问
27
+ * e.g. Promise  then 能访问   catch不能访问
28
+ * e.g. authority support incoming string, funtion: () => boolean | Promise
29
+ * e.g. 'user' only user user can access
30
+ * e.g. 'user, admin' user and admin can access
31
+ * e.g. () => boolean true to be able to visit, return false can not be accessed
32
+ * e.g. Promise then can not access the visit to catch
33
+ * @param {string | function | Promise} authority
34
+ * @param {ReactNode} error 非必需参数
35
+ */
36
+const authorize = (authority, error) => {
37
+  /**
38
+   * conversion into a class
39
+   * 防止传入字符串时找不到staticContext造成报错
40
+   * String parameters can cause staticContext not found error
41
+   */
42
+  let classError = false;
43
+  if (error) {
44
+    classError = () => error;
45
+  }
46
+  if (!authority) {
47
+    throw new Error('authority is required');
48
+  }
49
+  return function decideAuthority(targer) {
50
+    const component = CheckPermissions(authority, targer, classError || Exception403);
51
+    return checkIsInstantiation(component);
52
+  };
53
+};
54
+
55
+export default authorize;

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

@@ -0,0 +1,23 @@
1
+---
2
+order: 1
3
+title: 
4
+  zh-CN: 使用数组作为参数
5
+  en-US: Use Array as a parameter
6
+---
7
+
8
+Use Array as a parameter
9
+
10
+```jsx
11
+import RenderAuthorized from 'ant-design-pro/lib/Authorized';
12
+import { Alert } from 'antd';
13
+
14
+const Authorized = RenderAuthorized('user');
15
+const noMatch = <Alert message="No permission." type="error" showIcon />;
16
+
17
+ReactDOM.render(
18
+  <Authorized authority={['user', 'admin']} noMatch={noMatch}>
19
+    <Alert message="Use Array as a parameter passed!" type="success" showIcon />
20
+  </Authorized>,
21
+  mountNode,
22
+);
23
+```

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

@@ -0,0 +1,31 @@
1
+---
2
+order: 2
3
+title: 
4
+  zh-CN: 使用方法作为参数
5
+  en-US: Use function as a parameter
6
+---
7
+
8
+Use Function as a parameter
9
+
10
+```jsx
11
+import RenderAuthorized from 'ant-design-pro/lib/Authorized';
12
+import { Alert } from 'antd';
13
+
14
+const Authorized = RenderAuthorized('user');
15
+const noMatch = <Alert message="No permission." type="error" showIcon />;
16
+
17
+const havePermission = () => {
18
+  return false;
19
+};
20
+
21
+ReactDOM.render(
22
+  <Authorized authority={havePermission} noMatch={noMatch}>
23
+    <Alert
24
+      message="Use Function as a parameter passed!"
25
+      type="success"
26
+      showIcon
27
+    />
28
+  </Authorized>,
29
+  mountNode,
30
+);
31
+```

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

@@ -0,0 +1,25 @@
1
+---
2
+order: 0
3
+title: 
4
+  zh-CN: 基本使用
5
+  en-US: Basic use
6
+---
7
+
8
+Basic use
9
+
10
+```jsx
11
+import RenderAuthorized from 'ant-design-pro/lib/Authorized';
12
+import { Alert } from 'antd';
13
+
14
+const Authorized = RenderAuthorized('user');
15
+const noMatch = <Alert message="No permission." type="error" showIcon />;
16
+
17
+ReactDOM.render(
18
+  <div>
19
+    <Authorized authority="admin" noMatch={noMatch}>
20
+      <Alert message="user Passed!" type="success" showIcon />
21
+    </Authorized>
22
+  </div>,
23
+  mountNode,
24
+);
25
+```

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

@@ -0,0 +1,28 @@
1
+---
2
+order: 3
3
+title: 
4
+  zh-CN: 注解基本使用
5
+  en-US: Basic use secured
6
+---
7
+
8
+secured demo used
9
+
10
+```jsx
11
+import RenderAuthorized from 'ant-design-pro/lib/Authorized';
12
+import { Alert } from 'antd';
13
+
14
+const { Secured } = RenderAuthorized('user');
15
+
16
+@Secured('admin')
17
+class TestSecuredString extends React.Component {
18
+  render() {
19
+    <Alert message="user Passed!" type="success" showIcon />;
20
+  }
21
+}
22
+ReactDOM.render(
23
+  <div>
24
+    <TestSecuredString />
25
+  </div>,
26
+  mountNode,
27
+);
28
+```

+ 43
- 0
src/components/Authorized/index.d.ts View File

@@ -0,0 +1,43 @@
1
+import * as React from 'react';
2
+import { RouteProps } from 'react-router';
3
+
4
+type authorityFN = (currentAuthority?: string) => boolean;
5
+
6
+type authority = string | Array<string> | authorityFN | Promise<any>;
7
+
8
+export type IReactComponent<P = any> =
9
+  | React.StatelessComponent<P>
10
+  | React.ComponentClass<P>
11
+  | React.ClassicComponentClass<P>;
12
+
13
+interface Secured {
14
+  (authority: authority, error?: React.ReactNode): <T extends IReactComponent>(target: T) => T;
15
+}
16
+
17
+export interface AuthorizedRouteProps extends RouteProps {
18
+  authority: authority;
19
+}
20
+export class AuthorizedRoute extends React.Component<AuthorizedRouteProps, any> {}
21
+
22
+interface check {
23
+  <T extends IReactComponent, S extends IReactComponent>(
24
+    authority: authority,
25
+    target: T,
26
+    Exception: S
27
+  ): T | S;
28
+}
29
+
30
+interface AuthorizedProps {
31
+  authority: authority;
32
+  noMatch?: React.ReactNode;
33
+}
34
+
35
+export class Authorized extends React.Component<AuthorizedProps, any> {
36
+  static Secured: Secured;
37
+  static AuthorizedRoute: typeof AuthorizedRoute;
38
+  static check: check;
39
+}
40
+
41
+declare function renderAuthorize(currentAuthority: string): typeof Authorized;
42
+
43
+export default renderAuthorize;

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

@@ -0,0 +1,32 @@
1
+import Authorized from './Authorized';
2
+import AuthorizedRoute from './AuthorizedRoute';
3
+import Secured from './Secured';
4
+import check from './CheckPermissions.js';
5
+
6
+/* eslint-disable import/no-mutable-exports */
7
+let CURRENT = 'NULL';
8
+
9
+Authorized.Secured = Secured;
10
+Authorized.AuthorizedRoute = AuthorizedRoute;
11
+Authorized.check = check;
12
+
13
+/**
14
+ * use  authority or getAuthority
15
+ * @param {string|()=>String} currentAuthority
16
+ */
17
+const renderAuthorize = currentAuthority => {
18
+  if (currentAuthority) {
19
+    if (currentAuthority.constructor.name === 'Function') {
20
+      CURRENT = currentAuthority();
21
+    }
22
+    if (currentAuthority.constructor.name === 'String') {
23
+      CURRENT = currentAuthority;
24
+    }
25
+  } else {
26
+    CURRENT = 'NULL';
27
+  }
28
+  return Authorized;
29
+};
30
+
31
+export { CURRENT };
32
+export default renderAuthorize;

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

@@ -0,0 +1,58 @@
1
+---
2
+title:
3
+  en-US: Authorized
4
+  zh-CN: Authorized
5
+subtitle: 权限
6
+cols: 1
7
+order: 15
8
+---
9
+
10
+权限组件,通过比对现有权限与准入权限,决定相关元素的展示。
11
+
12
+## API
13
+
14
+### RenderAuthorized
15
+
16
+`RenderAuthorized: (currentAuthority: string | () => string) => Authorized`
17
+
18
+权限组件默认 export RenderAuthorized 函数,它接收当前权限作为参数,返回一个权限对象,该对象提供以下几种使用方式。
19
+
20
+
21
+### Authorized
22
+
23
+最基础的权限控制。
24
+
25
+| 参数      | 说明                                      | 类型         | 默认值 |
26
+|----------|------------------------------------------|-------------|-------|
27
+| children    | 正常渲染的元素,权限判断通过时展示           | ReactNode  | - |
28
+| authority   | 准入权限/权限判断         | `string | array | Promise | (currentAuthority) => boolean` | - |
29
+| noMatch     | 权限异常渲染元素,权限判断不通过时展示        | ReactNode  | - |
30
+
31
+### Authorized.AuthorizedRoute
32
+
33
+| 参数      | 说明                                      | 类型         | 默认值 |
34
+|----------|------------------------------------------|-------------|-------|
35
+| authority     | 准入权限/权限判断         | `string | array | Promise | (currentAuthority) => boolean` | - |
36
+| redirectPath  | 权限异常时重定向的页面路由                | string  | - |
37
+
38
+其余参数与 `Route` 相同。
39
+
40
+### Authorized.Secured
41
+
42
+注解方式,`@Authorized.Secured(authority, error)`
43
+
44
+| 参数      | 说明                                      | 类型         | 默认值 |
45
+|----------|------------------------------------------|-------------|-------|
46
+| authority     | 准入权限/权限判断         | `string | Promise | (currentAuthority) => boolean` | - |
47
+| error  | 权限异常时渲染元素                |  ReactNode | <Exception type="403" /> |
48
+
49
+### Authorized.check
50
+
51
+函数形式的 Authorized,用于某些不能被 HOC 包裹的组件。 `Authorized.check(authority, target, Exception)`  
52
+注意:传入一个 Promise 时,无论正确还是错误返回的都是一个 ReactClass。
53
+
54
+| 参数      | 说明                                      | 类型         | 默认值 |
55
+|----------|------------------------------------------|-------------|-------|
56
+| authority     | 准入权限/权限判断         | `string | Promise | (currentAuthority) => boolean` | - |
57
+| target     | 权限判断通过时渲染的元素         | ReactNode | - |
58
+| Exception  | 权限异常时渲染元素                |  ReactNode | - |

+ 10
- 0
src/components/AvatarList/AvatarItem.d.ts View File

@@ -0,0 +1,10 @@
1
+import * as React from 'react';
2
+export interface IAvatarItemProps {
3
+  tips: React.ReactNode;
4
+  src: string;
5
+  style?: React.CSSProperties;
6
+}
7
+
8
+export default class AvatarItem extends React.Component<IAvatarItemProps, any> {
9
+  constructor(props: IAvatarItemProps);
10
+}

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

@@ -0,0 +1,20 @@
1
+---
2
+order: 0
3
+title: 
4
+  zh-CN: 基础样例 
5
+  en-US: Basic Usage
6
+---
7
+
8
+Simplest of usage.
9
+
10
+````jsx
11
+import AvatarList from 'ant-design-pro/lib/AvatarList';
12
+
13
+ReactDOM.render(
14
+  <AvatarList size="mini">
15
+    <AvatarList.Item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
16
+    <AvatarList.Item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
17
+    <AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
18
+  </AvatarList>
19
+, mountNode);
20
+````

+ 12
- 0
src/components/AvatarList/index.d.ts View File

@@ -0,0 +1,12 @@
1
+import * as React from 'react';
2
+import AvatarItem from './AvatarItem';
3
+
4
+export interface IAvatarListProps {
5
+  size?: 'large' | 'small' | 'mini' | 'default';
6
+  style?: React.CSSProperties;
7
+  children: React.ReactElement<AvatarItem> | Array<React.ReactElement<AvatarItem>>;
8
+}
9
+
10
+export default class AvatarList extends React.Component<IAvatarListProps, any> {
11
+  public static Item: typeof AvatarItem;
12
+}

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

@@ -0,0 +1,22 @@
1
+---
2
+title: AvatarList
3
+order: 1
4
+cols: 1
5
+---
6
+
7
+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.
8
+
9
+## API
10
+
11
+### AvatarList
12
+
13
+| Property | Description | Type | Default |
14
+|----------|------------------------------------------|-------------|-------|
15
+| size | size of list | `large`、`small` 、`mini`, `default` | `default` |
16
+
17
+### AvatarList.Item
18
+
19
+| Property | Description | Type | Default |
20
+|----------|------------------------------------------|-------------|-------|
21
+| tips | title tips for avatar item | ReactNode\/string | - |
22
+| src | the address of the image for an image avatar | string | - |

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

@@ -0,0 +1,43 @@
1
+import React from 'react';
2
+import { Tooltip, Avatar } from 'antd';
3
+import classNames from 'classnames';
4
+
5
+import styles from './index.less';
6
+
7
+const AvatarList = ({ children, size, ...other }) => {
8
+  const childrenWithProps = React.Children.map(children, child =>
9
+    React.cloneElement(child, {
10
+      size,
11
+    })
12
+  );
13
+
14
+  return (
15
+    <div {...other} className={styles.avatarList}>
16
+      <ul> {childrenWithProps} </ul>
17
+    </div>
18
+  );
19
+};
20
+
21
+const Item = ({ src, size, tips, onClick = () => {} }) => {
22
+  const cls = classNames(styles.avatarItem, {
23
+    [styles.avatarItemLarge]: size === 'large',
24
+    [styles.avatarItemSmall]: size === 'small',
25
+    [styles.avatarItemMini]: size === 'mini',
26
+  });
27
+
28
+  return (
29
+    <li className={cls} onClick={onClick}>
30
+      {tips ? (
31
+        <Tooltip title={tips}>
32
+          <Avatar src={src} size={size} style={{ cursor: 'pointer' }} />
33
+        </Tooltip>
34
+      ) : (
35
+        <Avatar src={src} size={size} />
36
+      )}
37
+    </li>
38
+  );
39
+};
40
+
41
+AvatarList.Item = Item;
42
+
43
+export default AvatarList;

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

@@ -0,0 +1,45 @@
1
+@import '~antd/lib/style/themes/default.less';
2
+
3
+.avatarList {
4
+  display: inline-block;
5
+  ul {
6
+    display: inline-block;
7
+    margin-left: 8px;
8
+    font-size: 0;
9
+  }
10
+}
11
+
12
+.avatarItem {
13
+  display: inline-block;
14
+  font-size: @font-size-base;
15
+  margin-left: -8px;
16
+  width: @avatar-size-base;
17
+  height: @avatar-size-base;
18
+  :global {
19
+    .ant-avatar {
20
+      border: 1px solid #fff;
21
+    }
22
+  }
23
+}
24
+
25
+.avatarItemLarge {
26
+  width: @avatar-size-lg;
27
+  height: @avatar-size-lg;
28
+}
29
+
30
+.avatarItemSmall {
31
+  width: @avatar-size-sm;
32
+  height: @avatar-size-sm;
33
+}
34
+
35
+.avatarItemMini {
36
+  width: 20px;
37
+  height: 20px;
38
+  :global {
39
+    .ant-avatar {
40
+      width: 20px;
41
+      height: 20px;
42
+      line-height: 20px;
43
+    }
44
+  }
45
+}

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

@@ -0,0 +1,23 @@
1
+---
2
+title: AvatarList
3
+subtitle: 用户头像列表
4
+order: 1
5
+cols: 1
6
+---
7
+
8
+一组用户头像,常用在项目/团队成员列表。可通过设置 `size` 属性来指定头像大小。
9
+
10
+## API
11
+
12
+### AvatarList
13
+
14
+| 参数      | 说明                                      | 类型         | 默认值 |
15
+|----------|------------------------------------------|-------------|-------|
16
+| size | 头像大小 | `large`、`small` 、`mini`, `default` | `default` |
17
+
18
+### AvatarList.Item
19
+
20
+| 参数      | 说明                                      | 类型         | 默认值 |
21
+|----------|------------------------------------------|-------------|-------|
22
+| tips | 头像展示文案 | ReactNode\/string | - |
23
+| src | 头像图片连接 | string | - |

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

@@ -0,0 +1,15 @@
1
+import * as React from 'react';
2
+export interface IBarProps {
3
+  title: React.ReactNode;
4
+  color?: string;
5
+  padding?: [number, number, number, number];
6
+  height: number;
7
+  data: Array<{
8
+    x: string;
9
+    y: number;
10
+  }>;
11
+  autoLabel?: boolean;
12
+  style?: React.CSSProperties;
13
+}
14
+
15
+export default class Bar extends React.Component<IBarProps, any> {}

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

@@ -0,0 +1,113 @@
1
+import React, { Component } from 'react';
2
+import { Chart, Axis, Tooltip, Geom } from 'bizcharts';
3
+import Debounce from 'lodash-decorators/debounce';
4
+import Bind from 'lodash-decorators/bind';
5
+import autoHeight from '../autoHeight';
6
+import styles from '../index.less';
7
+
8
+@autoHeight()
9
+class Bar extends Component {
10
+  state = {
11
+    autoHideXLabels: false,
12
+  };
13
+
14
+  componentDidMount() {
15
+    window.addEventListener('resize', this.resize);
16
+  }
17
+
18
+  componentWillUnmount() {
19
+    window.removeEventListener('resize', this.resize);
20
+  }
21
+
22
+  @Bind()
23
+  @Debounce(400)
24
+  resize() {
25
+    if (!this.node) {
26
+      return;
27
+    }
28
+    const canvasWidth = this.node.parentNode.clientWidth;
29
+    const { data = [], autoLabel = true } = this.props;
30
+    if (!autoLabel) {
31
+      return;
32
+    }
33
+    const minWidth = data.length * 30;
34
+    const { autoHideXLabels } = this.state;
35
+
36
+    if (canvasWidth <= minWidth) {
37
+      if (!autoHideXLabels) {
38
+        this.setState({
39
+          autoHideXLabels: true,
40
+        });
41
+      }
42
+    } else if (autoHideXLabels) {
43
+      this.setState({
44
+        autoHideXLabels: false,
45
+      });
46
+    }
47
+  }
48
+
49
+  handleRoot = n => {
50
+    this.root = n;
51
+  };
52
+
53
+  handleRef = n => {
54
+    this.node = n;
55
+  };
56
+
57
+  render() {
58
+    const {
59
+      height,
60
+      title,
61
+      forceFit = true,
62
+      data,
63
+      color = 'rgba(24, 144, 255, 0.85)',
64
+      padding,
65
+    } = this.props;
66
+
67
+    const { autoHideXLabels } = this.state;
68
+
69
+    const scale = {
70
+      x: {
71
+        type: 'cat',
72
+      },
73
+      y: {
74
+        min: 0,
75
+      },
76
+    };
77
+
78
+    const tooltip = [
79
+      'x*y',
80
+      (x, y) => ({
81
+        name: x,
82
+        value: y,
83
+      }),
84
+    ];
85
+
86
+    return (
87
+      <div className={styles.chart} style={{ height }} ref={this.handleRoot}>
88
+        <div ref={this.handleRef}>
89
+          {title && <h4 style={{ marginBottom: 20 }}>{title}</h4>}
90
+          <Chart
91
+            scale={scale}
92
+            height={title ? height - 41 : height}
93
+            forceFit={forceFit}
94
+            data={data}
95
+            padding={padding || 'auto'}
96
+          >
97
+            <Axis
98
+              name="x"
99
+              title={false}
100
+              label={autoHideXLabels ? false : {}}
101
+              tickLine={autoHideXLabels ? false : {}}
102
+            />
103
+            <Axis name="y" min={0} />
104
+            <Tooltip showTitle={false} crosshairs={false} />
105
+            <Geom type="interval" position="x*y" color={color} tooltip={tooltip} />
106
+          </Chart>
107
+        </div>
108
+      </div>
109
+    );
110
+  }
111
+}
112
+
113
+export default Bar;

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

@@ -0,0 +1,12 @@
1
+import * as React from 'react';
2
+export interface IChartCardProps {
3
+  title: React.ReactNode;
4
+  action?: React.ReactNode;
5
+  total?: React.ReactNode | number | (() => React.ReactNode | number);
6
+  footer?: React.ReactNode;
7
+  contentHeight?: number;
8
+  avatar?: React.ReactNode;
9
+  style?: React.CSSProperties;
10
+}
11
+
12
+export default class ChartCard extends React.Component<IChartCardProps, any> {}

+ 77
- 0
src/components/Charts/ChartCard/index.js View File

@@ -0,0 +1,77 @@
1
+import React from 'react';
2
+import { Card, Spin } from 'antd';
3
+import classNames from 'classnames';
4
+
5
+import styles from './index.less';
6
+
7
+const renderTotal = total => {
8
+  let totalDom;
9
+  switch (typeof total) {
10
+    case 'undefined':
11
+      totalDom = null;
12
+      break;
13
+    case 'function':
14
+      totalDom = <div className={styles.total}>{total()}</div>;
15
+      break;
16
+    default:
17
+      totalDom = <div className={styles.total}>{total}</div>;
18
+  }
19
+  return totalDom;
20
+};
21
+
22
+const ChartCard = ({
23
+  loading = false,
24
+  contentHeight,
25
+  title,
26
+  avatar,
27
+  action,
28
+  total,
29
+  footer,
30
+  children,
31
+  ...rest
32
+}) => {
33
+  const content = (
34
+    <div className={styles.chartCard}>
35
+      <div
36
+        className={classNames(styles.chartTop, {
37
+          [styles.chartTopMargin]: !children && !footer,
38
+        })}
39
+      >
40
+        <div className={styles.avatar}>{avatar}</div>
41
+        <div className={styles.metaWrap}>
42
+          <div className={styles.meta}>
43
+            <span className={styles.title}>{title}</span>
44
+            <span className={styles.action}>{action}</span>
45
+          </div>
46
+          {renderTotal(total)}
47
+        </div>
48
+      </div>
49
+      {children && (
50
+        <div className={styles.content} style={{ height: contentHeight || 'auto' }}>
51
+          <div className={contentHeight && styles.contentFixed}>{children}</div>
52
+        </div>
53
+      )}
54
+      {footer && (
55
+        <div
56
+          className={classNames(styles.footer, {
57
+            [styles.footerMargin]: !children,
58
+          })}
59
+        >
60
+          {footer}
61
+        </div>
62
+      )}
63
+    </div>
64
+  );
65
+
66
+  return (
67
+    <Card bodyStyle={{ padding: '20px 24px 8px 24px' }} {...rest}>
68
+      {
69
+        <Spin spinning={loading} wrapperClassName={styles.spin}>
70
+          {content}
71
+        </Spin>
72
+      }
73
+    </Card>
74
+  );
75
+};
76
+
77
+export default ChartCard;

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

@@ -0,0 +1,78 @@
1
+@import '~antd/lib/style/themes/default.less';
2
+
3
+.chartCard {
4
+  position: relative;
5
+  .chartTop {
6
+    position: relative;
7
+    overflow: hidden;
8
+    width: 100%;
9
+  }
10
+  .chartTopMargin {
11
+    margin-bottom: 12px;
12
+  }
13
+  .chartTopHasMargin {
14
+    margin-bottom: 20px;
15
+  }
16
+  .metaWrap {
17
+    float: left;
18
+  }
19
+  .avatar {
20
+    position: relative;
21
+    top: 4px;
22
+    float: left;
23
+    margin-right: 20px;
24
+    img {
25
+      border-radius: 100%;
26
+    }
27
+  }
28
+  .meta {
29
+    color: @text-color-secondary;
30
+    font-size: @font-size-base;
31
+    line-height: 22px;
32
+    height: 22px;
33
+  }
34
+  .action {
35
+    cursor: pointer;
36
+    position: absolute;
37
+    top: 0;
38
+    right: 0;
39
+  }
40
+  .total {
41
+    overflow: hidden;
42
+    text-overflow: ellipsis;
43
+    word-break: break-all;
44
+    white-space: nowrap;
45
+    color: @heading-color;
46
+    margin-top: 4px;
47
+    margin-bottom: 0;
48
+    font-size: 30px;
49
+    line-height: 38px;
50
+    height: 38px;
51
+  }
52
+  .content {
53
+    margin-bottom: 12px;
54
+    position: relative;
55
+    width: 100%;
56
+  }
57
+  .contentFixed {
58
+    position: absolute;
59
+    left: 0;
60
+    bottom: 0;
61
+    width: 100%;
62
+  }
63
+  .footer {
64
+    border-top: 1px solid @border-color-split;
65
+    padding-top: 9px;
66
+    margin-top: 8px;
67
+    & > * {
68
+      position: relative;
69
+    }
70
+  }
71
+  .footerMargin {
72
+    margin-top: 20px;
73
+  }
74
+}
75
+
76
+.spin :global(.ant-spin-container) {
77
+  overflow: visible;
78
+}

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

@@ -0,0 +1,8 @@
1
+import * as React from 'react';
2
+export interface IFieldProps {
3
+  label: React.ReactNode;
4
+  value: React.ReactNode;
5
+  style?: React.CSSProperties;
6
+}
7
+
8
+export default class Field extends React.Component<IFieldProps, any> {}

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

@@ -0,0 +1,12 @@
1
+import React from 'react';
2
+
3
+import styles from './index.less';
4
+
5
+const Field = ({ label, value, ...rest }) => (
6
+  <div className={styles.field} {...rest}>
7
+    <span>{label}</span>
8
+    <span>{value}</span>
9
+  </div>
10
+);
11
+
12
+export default Field;

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

@@ -0,0 +1,16 @@
1
+@import '~antd/lib/style/themes/default.less';
2
+
3
+.field {
4
+  white-space: nowrap;
5
+  overflow: hidden;
6
+  text-overflow: ellipsis;
7
+  margin: 0;
8
+  span {
9
+    font-size: @font-size-base;
10
+    line-height: 22px;
11
+  }
12
+  span:last-child {
13
+    margin-left: 8px;
14
+    color: @heading-color;
15
+  }
16
+}

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

@@ -0,0 +1,11 @@
1
+import * as React from 'react';
2
+export interface IGaugeProps {
3
+  title: React.ReactNode;
4
+  color?: string;
5
+  height: number;
6
+  bgColor?: number;
7
+  percent: number;
8
+  style?: React.CSSProperties;
9
+}
10
+
11
+export default class Gauge extends React.Component<IGaugeProps, any> {}

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

@@ -0,0 +1,167 @@
1
+import React from 'react';
2
+import { Chart, Geom, Axis, Coord, Guide, Shape } from 'bizcharts';
3
+import autoHeight from '../autoHeight';
4
+
5
+const { Arc, Html, Line } = Guide;
6
+
7
+const defaultFormatter = val => {
8
+  switch (val) {
9
+    case '2':
10
+      return '差';
11
+    case '4':
12
+      return '中';
13
+    case '6':
14
+      return '良';
15
+    case '8':
16
+      return '优';
17
+    default:
18
+      return '';
19
+  }
20
+};
21
+
22
+Shape.registerShape('point', 'pointer', {
23
+  drawShape(cfg, group) {
24
+    let point = cfg.points[0];
25
+    point = this.parsePoint(point);
26
+    const center = this.parsePoint({
27
+      x: 0,
28
+      y: 0,
29
+    });
30
+    group.addShape('line', {
31
+      attrs: {
32
+        x1: center.x,
33
+        y1: center.y,
34
+        x2: point.x,
35
+        y2: point.y,
36
+        stroke: cfg.color,
37
+        lineWidth: 2,
38
+        lineCap: 'round',
39
+      },
40
+    });
41
+    return group.addShape('circle', {
42
+      attrs: {
43
+        x: center.x,
44
+        y: center.y,
45
+        r: 6,
46
+        stroke: cfg.color,
47
+        lineWidth: 3,
48
+        fill: '#fff',
49
+      },
50
+    });
51
+  },
52
+});
53
+
54
+@autoHeight()
55
+export default class Gauge extends React.Component {
56
+  render() {
57
+    const {
58
+      title,
59
+      height,
60
+      percent,
61
+      forceFit = true,
62
+      formatter = defaultFormatter,
63
+      color = '#2F9CFF',
64
+      bgColor = '#F0F2F5',
65
+    } = this.props;
66
+    const cols = {
67
+      value: {
68
+        type: 'linear',
69
+        min: 0,
70
+        max: 10,
71
+        tickCount: 6,
72
+        nice: true,
73
+      },
74
+    };
75
+    const data = [{ value: percent / 10 }];
76
+    return (
77
+      <Chart height={height} data={data} scale={cols} padding={[-16, 0, 16, 0]} forceFit={forceFit}>
78
+        <Coord type="polar" startAngle={-1.25 * Math.PI} endAngle={0.25 * Math.PI} radius={0.8} />
79
+        <Axis name="1" line={null} />
80
+        <Axis
81
+          line={null}
82
+          tickLine={null}
83
+          subTickLine={null}
84
+          name="value"
85
+          zIndex={2}
86
+          gird={null}
87
+          label={{
88
+            offset: -12,
89
+            formatter,
90
+            textStyle: {
91
+              fontSize: 12,
92
+              fill: 'rgba(0, 0, 0, 0.65)',
93
+              textAlign: 'center',
94
+            },
95
+          }}
96
+        />
97
+        <Guide>
98
+          <Line
99
+            start={[3, 0.905]}
100
+            end={[3, 0.85]}
101
+            lineStyle={{
102
+              stroke: color,
103
+              lineDash: null,
104
+              lineWidth: 2,
105
+            }}
106
+          />
107
+          <Line
108
+            start={[5, 0.905]}
109
+            end={[5, 0.85]}
110
+            lineStyle={{
111
+              stroke: color,
112
+              lineDash: null,
113
+              lineWidth: 3,
114
+            }}
115
+          />
116
+          <Line
117
+            start={[7, 0.905]}
118
+            end={[7, 0.85]}
119
+            lineStyle={{
120
+              stroke: color,
121
+              lineDash: null,
122
+              lineWidth: 3,
123
+            }}
124
+          />
125
+          <Arc
126
+            zIndex={0}
127
+            start={[0, 0.965]}
128
+            end={[10, 0.965]}
129
+            style={{
130
+              stroke: bgColor,
131
+              lineWidth: 10,
132
+            }}
133
+          />
134
+          <Arc
135
+            zIndex={1}
136
+            start={[0, 0.965]}
137
+            end={[data[0].value, 0.965]}
138
+            style={{
139
+              stroke: color,
140
+              lineWidth: 10,
141
+            }}
142
+          />
143
+          <Html
144
+            position={['50%', '95%']}
145
+            html={() => {
146
+              return `
147
+                <div style="width: 300px;text-align: center;font-size: 12px!important;">
148
+                  <p style="font-size: 14px; color: rgba(0,0,0,0.43);margin: 0;">${title}</p>
149
+                  <p style="font-size: 24px;color: rgba(0,0,0,0.85);margin: 0;">
150
+                    ${data[0].value * 10}%
151
+                  </p>
152
+                </div>`;
153
+            }}
154
+          />
155
+        </Guide>
156
+        <Geom
157
+          line={false}
158
+          type="point"
159
+          position="value*1"
160
+          shape="pointer"
161
+          color={color}
162
+          active={false}
163
+        />
164
+      </Chart>
165
+    );
166
+  }
167
+}

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

@@ -0,0 +1,29 @@
1
+import * as React from 'react';
2
+
3
+// g2已经更新到3.0
4
+// 不带的写了
5
+
6
+export interface IAxis {
7
+  title: any;
8
+  line: any;
9
+  gridAlign: any;
10
+  labels: any;
11
+  tickLine: any;
12
+  grid: any;
13
+}
14
+
15
+export interface IMiniAreaProps {
16
+  color?: string;
17
+  height: number;
18
+  borderColor?: string;
19
+  line?: boolean;
20
+  animate?: boolean;
21
+  xAxis?: IAxis;
22
+  yAxis?: IAxis;
23
+  data: Array<{
24
+    x: number;
25
+    y: number;
26
+  }>;
27
+}
28
+
29
+export default class MiniArea extends React.Component<IMiniAreaProps, any> {}

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

@@ -0,0 +1,106 @@
1
+import React from 'react';
2
+import { Chart, Axis, Tooltip, Geom } from 'bizcharts';
3
+import autoHeight from '../autoHeight';
4
+import styles from '../index.less';
5
+
6
+@autoHeight()
7
+export default class MiniArea extends React.Component {
8
+  render() {
9
+    const {
10
+      height,
11
+      data = [],
12
+      forceFit = true,
13
+      color = 'rgba(24, 144, 255, 0.2)',
14
+      borderColor = '#1089ff',
15
+      scale = {},
16
+      borderWidth = 2,
17
+      line,
18
+      xAxis,
19
+      yAxis,
20
+      animate = true,
21
+    } = this.props;
22
+
23
+    const padding = [36, 5, 30, 5];
24
+
25
+    const scaleProps = {
26
+      x: {
27
+        type: 'cat',
28
+        range: [0, 1],
29
+        ...scale.x,
30
+      },
31
+      y: {
32
+        min: 0,
33
+        ...scale.y,
34
+      },
35
+    };
36
+
37
+    const tooltip = [
38
+      'x*y',
39
+      (x, y) => ({
40
+        name: x,
41
+        value: y,
42
+      }),
43
+    ];
44
+
45
+    const chartHeight = height + 54;
46
+
47
+    return (
48
+      <div className={styles.miniChart} style={{ height }}>
49
+        <div className={styles.chartContent}>
50
+          {height > 0 && (
51
+            <Chart
52
+              animate={animate}
53
+              scale={scaleProps}
54
+              height={chartHeight}
55
+              forceFit={forceFit}
56
+              data={data}
57
+              padding={padding}
58
+            >
59
+              <Axis
60
+                key="axis-x"
61
+                name="x"
62
+                label={false}
63
+                line={false}
64
+                tickLine={false}
65
+                grid={false}
66
+                {...xAxis}
67
+              />
68
+              <Axis
69
+                key="axis-y"
70
+                name="y"
71
+                label={false}
72
+                line={false}
73
+                tickLine={false}
74
+                grid={false}
75
+                {...yAxis}
76
+              />
77
+              <Tooltip showTitle={false} crosshairs={false} />
78
+              <Geom
79
+                type="area"
80
+                position="x*y"
81
+                color={color}
82
+                tooltip={tooltip}
83
+                shape="smooth"
84
+                style={{
85
+                  fillOpacity: 1,
86
+                }}
87
+              />
88
+              {line ? (
89
+                <Geom
90
+                  type="line"
91
+                  position="x*y"
92
+                  shape="smooth"
93
+                  color={borderColor}
94
+                  size={borderWidth}
95
+                  tooltip={false}
96
+                />
97
+              ) : (
98
+                <span style={{ display: 'none' }} />
99
+              )}
100
+            </Chart>
101
+          )}
102
+        </div>
103
+      </div>
104
+    );
105
+  }
106
+}

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

@@ -0,0 +1,12 @@
1
+import * as React from 'react';
2
+export interface IMiniBarProps {
3
+  color?: string;
4
+  height: number;
5
+  data: Array<{
6
+    x: number | string;
7
+    y: number;
8
+  }>;
9
+  style?: React.CSSProperties;
10
+}
11
+
12
+export default class MiniBar extends React.Component<IMiniBarProps, any> {}

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

@@ -0,0 +1,50 @@
1
+import React from 'react';
2
+import { Chart, Tooltip, Geom } from 'bizcharts';
3
+import autoHeight from '../autoHeight';
4
+import styles from '../index.less';
5
+
6
+@autoHeight()
7
+export default class MiniBar extends React.Component {
8
+  render() {
9
+    const { height, forceFit = true, color = '#1890FF', data = [] } = this.props;
10
+
11
+    const scale = {
12
+      x: {
13
+        type: 'cat',
14
+      },
15
+      y: {
16
+        min: 0,
17
+      },
18
+    };
19
+
20
+    const padding = [36, 5, 30, 5];
21
+
22
+    const tooltip = [
23
+      'x*y',
24
+      (x, y) => ({
25
+        name: x,
26
+        value: y,
27
+      }),
28
+    ];
29
+
30
+    // for tooltip not to be hide
31
+    const chartHeight = height + 54;
32
+
33
+    return (
34
+      <div className={styles.miniChart} style={{ height }}>
35
+        <div className={styles.chartContent}>
36
+          <Chart
37
+            scale={scale}
38
+            height={chartHeight}
39
+            forceFit={forceFit}
40
+            data={data}
41
+            padding={padding}
42
+          >
43
+            <Tooltip showTitle={false} crosshairs={false} />
44
+            <Geom type="interval" position="x*y" color={color} tooltip={tooltip} />
45
+          </Chart>
46
+        </div>
47
+      </div>
48
+    );
49
+  }
50
+}

+ 10
- 0
src/components/Charts/MiniProgress/index.d.ts View File

@@ -0,0 +1,10 @@
1
+import * as React from 'react';
2
+export interface IMiniProgressProps {
3
+  target: number;
4
+  color?: string;
5
+  strokeWidth?: number;
6
+  percent?: number;
7
+  style?: React.CSSProperties;
8
+}
9
+
10
+export default class MiniProgress extends React.Component<IMiniProgressProps, any> {}

+ 27
- 0
src/components/Charts/MiniProgress/index.js View File

@@ -0,0 +1,27 @@
1
+import React from 'react';
2
+import { Tooltip } from 'antd';
3
+
4
+import styles from './index.less';
5
+
6
+const MiniProgress = ({ target, color = 'rgb(19, 194, 194)', strokeWidth, percent }) => (
7
+  <div className={styles.miniProgress}>
8
+    <Tooltip title={`目标值: ${target}%`}>
9
+      <div className={styles.target} style={{ left: target ? `${target}%` : null }}>
10
+        <span style={{ backgroundColor: color || null }} />
11
+        <span style={{ backgroundColor: color || null }} />
12
+      </div>
13
+    </Tooltip>
14
+    <div className={styles.progressWrap}>
15
+      <div
16
+        className={styles.progress}
17
+        style={{
18
+          backgroundColor: color || null,
19
+          width: percent ? `${percent}%` : null,
20
+          height: strokeWidth || null,
21
+        }}
22
+      />
23
+    </div>
24
+  </div>
25
+);
26
+
27
+export default MiniProgress;

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

@@ -0,0 +1,35 @@
1
+@import '~antd/lib/style/themes/default.less';
2
+
3
+.miniProgress {
4
+  padding: 5px 0;
5
+  position: relative;
6
+  width: 100%;
7
+  .progressWrap {
8
+    background-color: @background-color-base;
9
+    position: relative;
10
+  }
11
+  .progress {
12
+    transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s;
13
+    border-radius: 1px 0 0 1px;
14
+    background-color: @primary-color;
15
+    width: 0;
16
+    height: 100%;
17
+  }
18
+  .target {
19
+    position: absolute;
20
+    top: 0;
21
+    bottom: 0;
22
+    span {
23
+      border-radius: 100px;
24
+      position: absolute;
25
+      top: 0;
26
+      left: 0;
27
+      height: 4px;
28
+      width: 2px;
29
+    }
30
+    span:last-child {
31
+      top: auto;
32
+      bottom: 0;
33
+    }
34
+  }
35
+}

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

@@ -0,0 +1,20 @@
1
+import * as React from 'react';
2
+export interface IPieProps {
3
+  animate?: boolean;
4
+  color?: string;
5
+  height: number;
6
+  hasLegend?: boolean;
7
+  padding?: [number, number, number, number];
8
+  percent?: number;
9
+  data?: Array<{
10
+    x: string | string;
11
+    y: number;
12
+  }>;
13
+  total?: React.ReactNode | number | (() => React.ReactNode | number);
14
+  title?: React.ReactNode;
15
+  tooltip?: boolean;
16
+  valueFormat?: (value: string) => string | React.ReactNode;
17
+  subTitle?: React.ReactNode;
18
+}
19
+
20
+export default class Pie extends React.Component<IPieProps, any> {}

+ 255
- 0
src/components/Charts/Pie/index.js View File

@@ -0,0 +1,255 @@
1
+import React, { Component } from 'react';
2
+import { Chart, Tooltip, Geom, Coord } from 'bizcharts';
3
+import { DataView } from '@antv/data-set';
4
+import { Divider } from 'antd';
5
+import classNames from 'classnames';
6
+import ReactFitText from 'react-fittext';
7
+import Debounce from 'lodash-decorators/debounce';
8
+import Bind from 'lodash-decorators/bind';
9
+import autoHeight from '../autoHeight';
10
+
11
+import styles from './index.less';
12
+
13
+/* eslint react/no-danger:0 */
14
+@autoHeight()
15
+export default class Pie extends Component {
16
+  state = {
17
+    legendData: [],
18
+    legendBlock: false,
19
+  };
20
+
21
+  componentDidMount() {
22
+    this.getLegendData();
23
+    this.resize();
24
+    window.addEventListener('resize', this.resize);
25
+  }
26
+
27
+  componentWillReceiveProps(nextProps) {
28
+    if (this.props.data !== nextProps.data) {
29
+      // because of charts data create when rendered
30
+      // so there is a trick for get rendered time
31
+      this.setState(
32
+        {
33
+          legendData: [...this.state.legendData],
34
+        },
35
+        () => {
36
+          this.getLegendData();
37
+        }
38
+      );
39
+    }
40
+  }
41
+
42
+  componentWillUnmount() {
43
+    window.removeEventListener('resize', this.resize);
44
+    this.resize.cancel();
45
+  }
46
+
47
+  getG2Instance = chart => {
48
+    this.chart = chart;
49
+  };
50
+
51
+  // for custom lengend view
52
+  getLegendData = () => {
53
+    if (!this.chart) return;
54
+    const geom = this.chart.getAllGeoms()[0]; // 获取所有的图形
55
+    const items = geom.get('dataArray') || []; // 获取图形对应的
56
+
57
+    const legendData = items.map(item => {
58
+      /* eslint no-underscore-dangle:0 */
59
+      const origin = item[0]._origin;
60
+      origin.color = item[0].color;
61
+      origin.checked = true;
62
+      return origin;
63
+    });
64
+
65
+    this.setState({
66
+      legendData,
67
+    });
68
+  };
69
+
70
+  // for window resize auto responsive legend
71
+  @Bind()
72
+  @Debounce(300)
73
+  resize() {
74
+    const { hasLegend } = this.props;
75
+    if (!hasLegend || !this.root) {
76
+      window.removeEventListener('resize', this.resize);
77
+      return;
78
+    }
79
+    if (this.root.parentNode.clientWidth <= 380) {
80
+      if (!this.state.legendBlock) {
81
+        this.setState({
82
+          legendBlock: true,
83
+        });
84
+      }
85
+    } else if (this.state.legendBlock) {
86
+      this.setState({
87
+        legendBlock: false,
88
+      });
89
+    }
90
+  }
91
+
92
+  handleRoot = n => {
93
+    this.root = n;
94
+  };
95
+
96
+  handleLegendClick = (item, i) => {
97
+    const newItem = item;
98
+    newItem.checked = !newItem.checked;
99
+
100
+    const { legendData } = this.state;
101
+    legendData[i] = newItem;
102
+
103
+    const filteredLegendData = legendData.filter(l => l.checked).map(l => l.x);
104
+
105
+    if (this.chart) {
106
+      this.chart.filter('x', val => filteredLegendData.indexOf(val) > -1);
107
+    }
108
+
109
+    this.setState({
110
+      legendData,
111
+    });
112
+  };
113
+
114
+  render() {
115
+    const {
116
+      valueFormat,
117
+      subTitle,
118
+      total,
119
+      hasLegend = false,
120
+      className,
121
+      style,
122
+      height,
123
+      forceFit = true,
124
+      percent = 0,
125
+      color,
126
+      inner = 0.75,
127
+      animate = true,
128
+      colors,
129
+      lineWidth = 1,
130
+    } = this.props;
131
+
132
+    const { legendData, legendBlock } = this.state;
133
+    const pieClassName = classNames(styles.pie, className, {
134
+      [styles.hasLegend]: !!hasLegend,
135
+      [styles.legendBlock]: legendBlock,
136
+    });
137
+
138
+    const defaultColors = colors;
139
+    let data = this.props.data || [];
140
+    let selected = this.props.selected || true;
141
+    let tooltip = this.props.tooltip || true;
142
+    let formatColor;
143
+
144
+    const scale = {
145
+      x: {
146
+        type: 'cat',
147
+        range: [0, 1],
148
+      },
149
+      y: {
150
+        min: 0,
151
+      },
152
+    };
153
+
154
+    if (percent) {
155
+      selected = false;
156
+      tooltip = false;
157
+      formatColor = value => {
158
+        if (value === '占比') {
159
+          return color || 'rgba(24, 144, 255, 0.85)';
160
+        } else {
161
+          return '#F0F2F5';
162
+        }
163
+      };
164
+
165
+      data = [
166
+        {
167
+          x: '占比',
168
+          y: parseFloat(percent),
169
+        },
170
+        {
171
+          x: '反比',
172
+          y: 100 - parseFloat(percent),
173
+        },
174
+      ];
175
+    }
176
+
177
+    const tooltipFormat = [
178
+      'x*percent',
179
+      (x, p) => ({
180
+        name: x,
181
+        value: `${(p * 100).toFixed(2)}%`,
182
+      }),
183
+    ];
184
+
185
+    const padding = [12, 0, 12, 0];
186
+
187
+    const dv = new DataView();
188
+    dv.source(data).transform({
189
+      type: 'percent',
190
+      field: 'y',
191
+      dimension: 'x',
192
+      as: 'percent',
193
+    });
194
+
195
+    return (
196
+      <div ref={this.handleRoot} className={pieClassName} style={style}>
197
+        <ReactFitText maxFontSize={25}>
198
+          <div className={styles.chart}>
199
+            <Chart
200
+              scale={scale}
201
+              height={height}
202
+              forceFit={forceFit}
203
+              data={dv}
204
+              padding={padding}
205
+              animate={animate}
206
+              onGetG2Instance={this.getG2Instance}
207
+            >
208
+              {!!tooltip && <Tooltip showTitle={false} />}
209
+              <Coord type="theta" innerRadius={inner} />
210
+              <Geom
211
+                style={{ lineWidth, stroke: '#fff' }}
212
+                tooltip={tooltip && tooltipFormat}
213
+                type="intervalStack"
214
+                position="percent"
215
+                color={['x', percent ? formatColor : defaultColors]}
216
+                selected={selected}
217
+              />
218
+            </Chart>
219
+
220
+            {(subTitle || total) && (
221
+              <div className={styles.total}>
222
+                {subTitle && <h4 className="pie-sub-title">{subTitle}</h4>}
223
+                {/* eslint-disable-next-line */}
224
+                {total && (
225
+                  <div className="pie-stat">{typeof total === 'function' ? total() : total}</div>
226
+                )}
227
+              </div>
228
+            )}
229
+          </div>
230
+        </ReactFitText>
231
+
232
+        {hasLegend && (
233
+          <ul className={styles.legend}>
234
+            {legendData.map((item, i) => (
235
+              <li key={item.x} onClick={() => this.handleLegendClick(item, i)}>
236
+                <span
237
+                  className={styles.dot}
238
+                  style={{
239
+                    backgroundColor: !item.checked ? '#aaa' : item.color,
240
+                  }}
241
+                />
242
+                <span className={styles.legendTitle}>{item.x}</span>
243
+                <Divider type="vertical" />
244
+                <span className={styles.percent}>
245
+                  {`${(isNaN(item.percent) ? 0 : item.percent * 100).toFixed(2)}%`}
246
+                </span>
247
+                <span className={styles.value}>{valueFormat ? valueFormat(item.y) : item.y}</span>
248
+              </li>
249
+            ))}
250
+          </ul>
251
+        )}
252
+      </div>
253
+    );
254
+  }
255
+}

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

@@ -0,0 +1,94 @@
1
+@import '~antd/lib/style/themes/default.less';
2
+
3
+.pie {
4
+  position: relative;
5
+  .chart {
6
+    position: relative;
7
+  }
8
+  &.hasLegend .chart {
9
+    width: ~'calc(100% - 240px)';
10
+  }
11
+  .legend {
12
+    position: absolute;
13
+    right: 0;
14
+    min-width: 200px;
15
+    top: 50%;
16
+    transform: translateY(-50%);
17
+    margin: 0 20px;
18
+    list-style: none;
19
+    padding: 0;
20
+    li {
21
+      cursor: pointer;
22
+      margin-bottom: 16px;
23
+      height: 22px;
24
+      line-height: 22px;
25
+      &:last-child {
26
+        margin-bottom: 0;
27
+      }
28
+    }
29
+  }
30
+  .dot {
31
+    border-radius: 8px;
32
+    display: inline-block;
33
+    margin-right: 8px;
34
+    position: relative;
35
+    top: -1px;
36
+    height: 8px;
37
+    width: 8px;
38
+  }
39
+  .line {
40
+    background-color: @border-color-split;
41
+    display: inline-block;
42
+    margin-right: 8px;
43
+    width: 1px;
44
+    height: 16px;
45
+  }
46
+  .legendTitle {
47
+    color: @text-color;
48
+  }
49
+  .percent {
50
+    color: @text-color-secondary;
51
+  }
52
+  .value {
53
+    position: absolute;
54
+    right: 0;
55
+  }
56
+  .title {
57
+    margin-bottom: 8px;
58
+  }
59
+  .total {
60
+    position: absolute;
61
+    left: 50%;
62
+    top: 50%;
63
+    text-align: center;
64
+    height: 62px;
65
+    transform: translate(-50%, -50%);
66
+    & > h4 {
67
+      color: @text-color-secondary;
68
+      font-size: 14px;
69
+      line-height: 22px;
70
+      height: 22px;
71
+      margin-bottom: 8px;
72
+      font-weight: normal;
73
+    }
74
+    & > p {
75
+      color: @heading-color;
76
+      display: block;
77
+      font-size: 1.2em;
78
+      height: 32px;
79
+      line-height: 32px;
80
+      white-space: nowrap;
81
+    }
82
+  }
83
+}
84
+
85
+.legendBlock {
86
+  &.hasLegend .chart {
87
+    width: 100%;
88
+    margin: 0 0 32px 0;
89
+  }
90
+  .legend {
91
+    position: relative;
92
+    transform: none;
93
+  }
94
+}

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

@@ -0,0 +1,15 @@
1
+import * as React from 'react';
2
+export interface IRadarProps {
3
+  title?: React.ReactNode;
4
+  height: number;
5
+  padding?: [number, number, number, number];
6
+  hasLegend?: boolean;
7
+  data: Array<{
8
+    name: string;
9
+    label: string;
10
+    value: string;
11
+  }>;
12
+  style?: React.CSSProperties;
13
+}
14
+
15
+export default class Radar extends React.Component<IRadarProps, any> {}

+ 180
- 0
src/components/Charts/Radar/index.js View File

@@ -0,0 +1,180 @@
1
+import React, { Component } from 'react';
2
+import { Chart, Tooltip, Geom, Coord, Axis } from 'bizcharts';
3
+import { Row, Col } from 'antd';
4
+import autoHeight from '../autoHeight';
5
+import styles from './index.less';
6
+
7
+/* eslint react/no-danger:0 */
8
+@autoHeight()
9
+export default class Radar extends Component {
10
+  state = {
11
+    legendData: [],
12
+  };
13
+
14
+  componentDidMount() {
15
+    this.getLengendData();
16
+  }
17
+
18
+  componentWillReceiveProps(nextProps) {
19
+    if (this.props.data !== nextProps.data) {
20
+      this.getLengendData();
21
+    }
22
+  }
23
+
24
+  getG2Instance = chart => {
25
+    this.chart = chart;
26
+  };
27
+
28
+  // for custom lengend view
29
+  getLengendData = () => {
30
+    if (!this.chart) return;
31
+    const geom = this.chart.getAllGeoms()[0]; // 获取所有的图形
32
+    const items = geom.get('dataArray') || []; // 获取图形对应的
33
+
34
+    const legendData = items.map(item => {
35
+      // eslint-disable-next-line
36
+      const origins = item.map(t => t._origin);
37
+      const result = {
38
+        name: origins[0].name,
39
+        color: item[0].color,
40
+        checked: true,
41
+        value: origins.reduce((p, n) => p + n.value, 0),
42
+      };
43
+
44
+      return result;
45
+    });
46
+
47
+    this.setState({
48
+      legendData,
49
+    });
50
+  };
51
+
52
+  handleRef = n => {
53
+    this.node = n;
54
+  };
55
+
56
+  handleLegendClick = (item, i) => {
57
+    const newItem = item;
58
+    newItem.checked = !newItem.checked;
59
+
60
+    const { legendData } = this.state;
61
+    legendData[i] = newItem;
62
+
63
+    const filteredLegendData = legendData.filter(l => l.checked).map(l => l.name);
64
+
65
+    if (this.chart) {
66
+      this.chart.filter('name', val => filteredLegendData.indexOf(val) > -1);
67
+      this.chart.repaint();
68
+    }
69
+
70
+    this.setState({
71
+      legendData,
72
+    });
73
+  };
74
+
75
+  render() {
76
+    const defaultColors = [
77
+      '#1890FF',
78
+      '#FACC14',
79
+      '#2FC25B',
80
+      '#8543E0',
81
+      '#F04864',
82
+      '#13C2C2',
83
+      '#fa8c16',
84
+      '#a0d911',
85
+    ];
86
+
87
+    const {
88
+      data = [],
89
+      height = 0,
90
+      title,
91
+      hasLegend = false,
92
+      forceFit = true,
93
+      tickCount = 4,
94
+      padding = [35, 30, 16, 30],
95
+      animate = true,
96
+      colors = defaultColors,
97
+    } = this.props;
98
+
99
+    const { legendData } = this.state;
100
+
101
+    const scale = {
102
+      value: {
103
+        min: 0,
104
+        tickCount,
105
+      },
106
+    };
107
+
108
+    const chartHeight = height - (hasLegend ? 80 : 22);
109
+
110
+    return (
111
+      <div className={styles.radar} style={{ height }}>
112
+        {title && <h4>{title}</h4>}
113
+        <Chart
114
+          scale={scale}
115
+          height={chartHeight}
116
+          forceFit={forceFit}
117
+          data={data}
118
+          padding={padding}
119
+          animate={animate}
120
+          onGetG2Instance={this.getG2Instance}
121
+        >
122
+          <Tooltip />
123
+          <Coord type="polar" />
124
+          <Axis
125
+            name="label"
126
+            line={null}
127
+            tickLine={null}
128
+            grid={{
129
+              lineStyle: {
130
+                lineDash: null,
131
+              },
132
+              hideFirstLine: false,
133
+            }}
134
+          />
135
+          <Axis
136
+            name="value"
137
+            grid={{
138
+              type: 'polygon',
139
+              lineStyle: {
140
+                lineDash: null,
141
+              },
142
+            }}
143
+          />
144
+          <Geom type="line" position="label*value" color={['name', colors]} size={1} />
145
+          <Geom
146
+            type="point"
147
+            position="label*value"
148
+            color={['name', colors]}
149
+            shape="circle"
150
+            size={3}
151
+          />
152
+        </Chart>
153
+        {hasLegend && (
154
+          <Row className={styles.legend}>
155
+            {legendData.map((item, i) => (
156
+              <Col
157
+                span={24 / legendData.length}
158
+                key={item.name}
159
+                onClick={() => this.handleLegendClick(item, i)}
160
+              >
161
+                <div className={styles.legendItem}>
162
+                  <p>
163
+                    <span
164
+                      className={styles.dot}
165
+                      style={{
166
+                        backgroundColor: !item.checked ? '#aaa' : item.color,
167
+                      }}
168
+                    />
169
+                    <span>{item.name}</span>
170
+                  </p>
171
+                  <h6>{item.value}</h6>
172
+                </div>
173
+              </Col>
174
+            ))}
175
+          </Row>
176
+        )}
177
+      </div>
178
+    );
179
+  }
180
+}

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

@@ -0,0 +1,46 @@
1
+@import '~antd/lib/style/themes/default.less';
2
+
3
+.radar {
4
+  .legend {
5
+    margin-top: 16px;
6
+    .legendItem {
7
+      position: relative;
8
+      text-align: center;
9
+      cursor: pointer;
10
+      color: @text-color-secondary;
11
+      line-height: 22px;
12
+      p {
13
+        margin: 0;
14
+      }
15
+      h6 {
16
+        color: @heading-color;
17
+        padding-left: 16px;
18
+        font-size: 24px;
19
+        line-height: 32px;
20
+        margin-top: 4px;
21
+        margin-bottom: 0;
22
+      }
23
+      &:after {
24
+        background-color: @border-color-split;
25
+        position: absolute;
26
+        top: 8px;
27
+        right: 0;
28
+        height: 40px;
29
+        width: 1px;
30
+        content: '';
31
+      }
32
+    }
33
+    > :last-child .legendItem:after {
34
+      display: none;
35
+    }
36
+    .dot {
37
+      border-radius: 6px;
38
+      display: inline-block;
39
+      margin-right: 6px;
40
+      position: relative;
41
+      top: -1px;
42
+      height: 6px;
43
+      width: 6px;
44
+    }
45
+  }
46
+}

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

@@ -0,0 +1,11 @@
1
+import * as React from 'react';
2
+export interface ITagCloudProps {
3
+  data: Array<{
4
+    name: string;
5
+    value: number;
6
+  }>;
7
+  height: number;
8
+  style?: React.CSSProperties;
9
+}
10
+
11
+export default class TagCloud extends React.Component<ITagCloudProps, any> {}

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

@@ -0,0 +1,164 @@
1
+import React, { Component } from 'react';
2
+import { Chart, Geom, Coord, Shape } from 'bizcharts';
3
+import DataSet from '@antv/data-set';
4
+import Debounce from 'lodash-decorators/debounce';
5
+import Bind from 'lodash-decorators/bind';
6
+import classNames from 'classnames';
7
+import autoHeight from '../autoHeight';
8
+import styles from './index.less';
9
+
10
+/* eslint no-underscore-dangle: 0 */
11
+/* eslint no-param-reassign: 0 */
12
+
13
+const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png';
14
+
15
+@autoHeight()
16
+class TagCloud extends Component {
17
+  state = {
18
+    dv: null,
19
+  };
20
+
21
+  componentDidMount() {
22
+    this.initTagCloud();
23
+    this.renderChart();
24
+    window.addEventListener('resize', this.resize);
25
+  }
26
+
27
+  componentWillReceiveProps(nextProps) {
28
+    if (JSON.stringify(nextProps.data) !== JSON.stringify(this.props.data)) {
29
+      this.renderChart(nextProps);
30
+    }
31
+  }
32
+
33
+  componentWillUnmount() {
34
+    this.isUnmount = true;
35
+    window.removeEventListener('resize', this.resize);
36
+  }
37
+
38
+  resize = () => {
39
+    this.renderChart();
40
+  };
41
+
42
+  saveRootRef = node => {
43
+    this.root = node;
44
+  };
45
+
46
+  initTagCloud = () => {
47
+    function getTextAttrs(cfg) {
48
+      return Object.assign(
49
+        {},
50
+        {
51
+          fillOpacity: cfg.opacity,
52
+          fontSize: cfg.origin._origin.size,
53
+          rotate: cfg.origin._origin.rotate,
54
+          text: cfg.origin._origin.text,
55
+          textAlign: 'center',
56
+          fontFamily: cfg.origin._origin.font,
57
+          fill: cfg.color,
58
+          textBaseline: 'Alphabetic',
59
+        },
60
+        cfg.style
61
+      );
62
+    }
63
+
64
+    // 给point注册一个词云的shape
65
+    Shape.registerShape('point', 'cloud', {
66
+      drawShape(cfg, container) {
67
+        const attrs = getTextAttrs(cfg);
68
+        return container.addShape('text', {
69
+          attrs: Object.assign(attrs, {
70
+            x: cfg.x,
71
+            y: cfg.y,
72
+          }),
73
+        });
74
+      },
75
+    });
76
+  };
77
+
78
+  @Bind()
79
+  @Debounce(500)
80
+  renderChart(nextProps) {
81
+    // const colors = ['#1890FF', '#41D9C7', '#2FC25B', '#FACC14', '#9AE65C'];
82
+    const { data, height } = nextProps || this.props;
83
+
84
+    if (data.length < 1 || !this.root) {
85
+      return;
86
+    }
87
+
88
+    const h = height * 4;
89
+    const w = this.root.offsetWidth * 4;
90
+
91
+    const onload = () => {
92
+      const dv = new DataSet.View().source(data);
93
+      const range = dv.range('value');
94
+      const [min, max] = range;
95
+      dv.transform({
96
+        type: 'tag-cloud',
97
+        fields: ['name', 'value'],
98
+        imageMask: this.imageMask,
99
+        font: 'Verdana',
100
+        size: [w, h], // 宽高设置最好根据 imageMask 做调整
101
+        padding: 5,
102
+        timeInterval: 5000, // max execute time
103
+        rotate() {
104
+          return 0;
105
+        },
106
+        fontSize(d) {
107
+          // eslint-disable-next-line
108
+          return Math.pow((d.value - min) / (max - min), 2) * (70 - 20) + 20;
109
+        },
110
+      });
111
+
112
+      if (this.isUnmount) {
113
+        return;
114
+      }
115
+
116
+      this.setState({
117
+        dv,
118
+        w,
119
+        h,
120
+      });
121
+    };
122
+
123
+    if (!this.imageMask) {
124
+      this.imageMask = new Image();
125
+      this.imageMask.crossOrigin = '';
126
+      this.imageMask.src = imgUrl;
127
+
128
+      this.imageMask.onload = onload;
129
+    } else {
130
+      onload();
131
+    }
132
+  }
133
+
134
+  render() {
135
+    const { className, height } = this.props;
136
+    const { dv, w, h } = this.state;
137
+
138
+    return (
139
+      <div
140
+        className={classNames(styles.tagCloud, className)}
141
+        style={{ width: '100%', height }}
142
+        ref={this.saveRootRef}
143
+      >
144
+        {dv && (
145
+          <Chart
146
+            width={w}
147
+            height={h}
148
+            data={dv}
149
+            padding={0}
150
+            scale={{
151
+              x: { nice: false },
152
+              y: { nice: false },
153
+            }}
154
+          >
155
+            <Coord reflect="y" />
156
+            <Geom type="point" position="x*y" color="text" shape="cloud" />
157
+          </Chart>
158
+        )}
159
+      </div>
160
+    );
161
+  }
162
+}
163
+
164
+export default TagCloud;

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

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

+ 14
- 0
src/components/Charts/TimelineChart/index.d.ts View File

@@ -0,0 +1,14 @@
1
+import * as React from 'react';
2
+export interface ITimelineChartProps {
3
+  data: Array<{
4
+    x: string;
5
+    y1: string;
6
+    y2: string;
7
+  }>;
8
+  titleMap: { y1: string; y2: string };
9
+  padding?: [number, number, number, number];
10
+  height?: number;
11
+  style?: React.CSSProperties;
12
+}
13
+
14
+export default class TimelineChart extends React.Component<ITimelineChartProps, any> {}

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

@@ -0,0 +1,123 @@
1
+import React from 'react';
2
+import { Chart, Tooltip, Geom, Legend, Axis } from 'bizcharts';
3
+import DataSet from '@antv/data-set';
4
+import Slider from 'bizcharts-plugin-slider';
5
+import autoHeight from '../autoHeight';
6
+import styles from './index.less';
7
+
8
+@autoHeight()
9
+export default class TimelineChart extends React.Component {
10
+  render() {
11
+    const {
12
+      title,
13
+      height = 400,
14
+      padding = [60, 20, 40, 40],
15
+      titleMap = {
16
+        y1: 'y1',
17
+        y2: 'y2',
18
+      },
19
+      borderWidth = 2,
20
+      data = [
21
+        {
22
+          x: 0,
23
+          y1: 0,
24
+          y2: 0,
25
+        },
26
+      ],
27
+    } = this.props;
28
+
29
+    data.sort((a, b) => a.x - b.x);
30
+
31
+    let max;
32
+    if (data[0] && data[0].y1 && data[0].y2) {
33
+      max = Math.max(
34
+        [...data].sort((a, b) => b.y1 - a.y1)[0].y1,
35
+        [...data].sort((a, b) => b.y2 - a.y2)[0].y2
36
+      );
37
+    }
38
+
39
+    const ds = new DataSet({
40
+      state: {
41
+        start: data[0].x,
42
+        end: data[data.length - 1].x,
43
+      },
44
+    });
45
+
46
+    const dv = ds.createView();
47
+    dv
48
+      .source(data)
49
+      .transform({
50
+        type: 'filter',
51
+        callback: obj => {
52
+          const date = obj.x;
53
+          return date <= ds.state.end && date >= ds.state.start;
54
+        },
55
+      })
56
+      .transform({
57
+        type: 'map',
58
+        callback(row) {
59
+          const newRow = { ...row };
60
+          newRow[titleMap.y1] = row.y1;
61
+          newRow[titleMap.y2] = row.y2;
62
+          return newRow;
63
+        },
64
+      })
65
+      .transform({
66
+        type: 'fold',
67
+        fields: [titleMap.y1, titleMap.y2], // 展开字段集
68
+        key: 'key', // key字段
69
+        value: 'value', // value字段
70
+      });
71
+
72
+    const timeScale = {
73
+      type: 'time',
74
+      tickInterval: 60 * 60 * 1000,
75
+      mask: 'HH:mm',
76
+      range: [0, 1],
77
+    };
78
+
79
+    const cols = {
80
+      x: timeScale,
81
+      value: {
82
+        max,
83
+        min: 0,
84
+      },
85
+    };
86
+
87
+    const SliderGen = () => (
88
+      <Slider
89
+        padding={[0, padding[1] + 20, 0, padding[3]]}
90
+        width="auto"
91
+        height={26}
92
+        xAxis="x"
93
+        yAxis="y1"
94
+        scales={{ x: timeScale }}
95
+        data={data}
96
+        start={ds.state.start}
97
+        end={ds.state.end}
98
+        backgroundChart={{ type: 'line' }}
99
+        onChange={({ startValue, endValue }) => {
100
+          ds.setState('start', startValue);
101
+          ds.setState('end', endValue);
102
+        }}
103
+      />
104
+    );
105
+
106
+    return (
107
+      <div className={styles.timelineChart} style={{ height: height + 30 }}>
108
+        <div>
109
+          {title && <h4>{title}</h4>}
110
+          <Chart height={height} padding={padding} data={dv} scale={cols} forceFit>
111
+            <Axis name="x" />
112
+            <Tooltip />
113
+            <Legend name="key" position="top" />
114
+            <Geom type="line" position="x*value" size={borderWidth} color="key" />
115
+          </Chart>
116
+          <div style={{ marginRight: -20 }}>
117
+            <SliderGen />
118
+          </div>
119
+        </div>
120
+      </div>
121
+    );
122
+  }
123
+}

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

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

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

@@ -0,0 +1,10 @@
1
+import * as React from 'react';
2
+export interface IWaterWaveProps {
3
+  title: React.ReactNode;
4
+  color?: string;
5
+  height: number;
6
+  percent: number;
7
+  style?: React.CSSProperties;
8
+}
9
+
10
+export default class WaterWave extends React.Component<IWaterWaveProps, any> {}

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

@@ -0,0 +1,197 @@
1
+import React, { PureComponent } from 'react';
2
+import autoHeight from '../autoHeight';
3
+import styles from './index.less';
4
+
5
+/* eslint no-return-assign: 0 */
6
+/* eslint no-mixed-operators: 0 */
7
+// riddle: https://riddle.alibaba-inc.com/riddles/2d9a4b90
8
+
9
+@autoHeight()
10
+export default class WaterWave extends PureComponent {
11
+  state = {
12
+    radio: 1,
13
+  };
14
+
15
+  componentDidMount() {
16
+    this.renderChart();
17
+    this.resize();
18
+
19
+    window.addEventListener('resize', this.resize);
20
+  }
21
+
22
+  componentWillUnmount() {
23
+    cancelAnimationFrame(this.timer);
24
+    if (this.node) {
25
+      this.node.innerHTML = '';
26
+    }
27
+    window.removeEventListener('resize', this.resize);
28
+  }
29
+
30
+  resize = () => {
31
+    const { height } = this.props;
32
+    const { offsetWidth } = this.root.parentNode;
33
+    this.setState({
34
+      radio: offsetWidth < height ? offsetWidth / height : 1,
35
+    });
36
+  };
37
+
38
+  renderChart() {
39
+    const { percent, color = '#1890FF' } = this.props;
40
+    const data = percent / 100;
41
+    const self = this;
42
+
43
+    if (!this.node || !data) {
44
+      return;
45
+    }
46
+
47
+    const canvas = this.node;
48
+    const ctx = canvas.getContext('2d');
49
+
50
+    const canvasWidth = canvas.width;
51
+    const canvasHeight = canvas.height;
52
+    const radius = canvasWidth / 2;
53
+    const lineWidth = 2;
54
+    const cR = radius - lineWidth;
55
+
56
+    ctx.beginPath();
57
+    ctx.lineWidth = lineWidth * 2;
58
+
59
+    const axisLength = canvasWidth - lineWidth;
60
+    const unit = axisLength / 8;
61
+    const range = 0.2; // 振幅
62
+    let currRange = range;
63
+    const xOffset = lineWidth;
64
+    let sp = 0; // 周期偏移量
65
+    let currData = 0;
66
+    const waveupsp = 0.005; // 水波上涨速度
67
+
68
+    let arcStack = [];
69
+    const bR = radius - lineWidth;
70
+    const circleOffset = -(Math.PI / 2);
71
+    let circleLock = true;
72
+
73
+    for (let i = circleOffset; i < circleOffset + 2 * Math.PI; i += 1 / (8 * Math.PI)) {
74
+      arcStack.push([radius + bR * Math.cos(i), radius + bR * Math.sin(i)]);
75
+    }
76
+
77
+    const cStartPoint = arcStack.shift();
78
+    ctx.strokeStyle = color;
79
+    ctx.moveTo(cStartPoint[0], cStartPoint[1]);
80
+
81
+    function drawSin() {
82
+      ctx.beginPath();
83
+      ctx.save();
84
+
85
+      const sinStack = [];
86
+      for (let i = xOffset; i <= xOffset + axisLength; i += 20 / axisLength) {
87
+        const x = sp + (xOffset + i) / unit;
88
+        const y = Math.sin(x) * currRange;
89
+        const dx = i;
90
+        const dy = 2 * cR * (1 - currData) + (radius - cR) - unit * y;
91
+
92
+        ctx.lineTo(dx, dy);
93
+        sinStack.push([dx, dy]);
94
+      }
95
+
96
+      const startPoint = sinStack.shift();
97
+
98
+      ctx.lineTo(xOffset + axisLength, canvasHeight);
99
+      ctx.lineTo(xOffset, canvasHeight);
100
+      ctx.lineTo(startPoint[0], startPoint[1]);
101
+
102
+      const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeight);
103
+      gradient.addColorStop(0, '#ffffff');
104
+      gradient.addColorStop(1, '#1890FF');
105
+      ctx.fillStyle = gradient;
106
+      ctx.fill();
107
+      ctx.restore();
108
+    }
109
+
110
+    function render() {
111
+      ctx.clearRect(0, 0, canvasWidth, canvasHeight);
112
+      if (circleLock) {
113
+        if (arcStack.length) {
114
+          const temp = arcStack.shift();
115
+          ctx.lineTo(temp[0], temp[1]);
116
+          ctx.stroke();
117
+        } else {
118
+          circleLock = false;
119
+          ctx.lineTo(cStartPoint[0], cStartPoint[1]);
120
+          ctx.stroke();
121
+          arcStack = null;
122
+
123
+          ctx.globalCompositeOperation = 'destination-over';
124
+          ctx.beginPath();
125
+          ctx.lineWidth = lineWidth;
126
+          ctx.arc(radius, radius, bR, 0, 2 * Math.PI, 1);
127
+
128
+          ctx.beginPath();
129
+          ctx.save();
130
+          ctx.arc(radius, radius, radius - 3 * lineWidth, 0, 2 * Math.PI, 1);
131
+
132
+          ctx.restore();
133
+          ctx.clip();
134
+          ctx.fillStyle = '#1890FF';
135
+        }
136
+      } else {
137
+        if (data >= 0.85) {
138
+          if (currRange > range / 4) {
139
+            const t = range * 0.01;
140
+            currRange -= t;
141
+          }
142
+        } else if (data <= 0.1) {
143
+          if (currRange < range * 1.5) {
144
+            const t = range * 0.01;
145
+            currRange += t;
146
+          }
147
+        } else {
148
+          if (currRange <= range) {
149
+            const t = range * 0.01;
150
+            currRange += t;
151
+          }
152
+          if (currRange >= range) {
153
+            const t = range * 0.01;
154
+            currRange -= t;
155
+          }
156
+        }
157
+        if (data - currData > 0) {
158
+          currData += waveupsp;
159
+        }
160
+        if (data - currData < 0) {
161
+          currData -= waveupsp;
162
+        }
163
+
164
+        sp += 0.07;
165
+        drawSin();
166
+      }
167
+      self.timer = requestAnimationFrame(render);
168
+    }
169
+
170
+    render();
171
+  }
172
+
173
+  render() {
174
+    const { radio } = this.state;
175
+    const { percent, title, height } = this.props;
176
+    return (
177
+      <div
178
+        className={styles.waterWave}
179
+        ref={n => (this.root = n)}
180
+        style={{ transform: `scale(${radio})` }}
181
+      >
182
+        <div style={{ width: height, height, overflow: 'hidden' }}>
183
+          <canvas
184
+            className={styles.waterWaveCanvasWrapper}
185
+            ref={n => (this.node = n)}
186
+            width={height * 2}
187
+            height={height * 2}
188
+          />
189
+        </div>
190
+        <div className={styles.text} style={{ width: height }}>
191
+          {title && <span>{title}</span>}
192
+          <h4>{percent}%</h4>
193
+        </div>
194
+      </div>
195
+    );
196
+  }
197
+}

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

@@ -0,0 +1,28 @@
1
+@import '~antd/lib/style/themes/default.less';
2
+
3
+.waterWave {
4
+  display: inline-block;
5
+  position: relative;
6
+  transform-origin: left;
7
+  .text {
8
+    position: absolute;
9
+    left: 0;
10
+    top: 32px;
11
+    text-align: center;
12
+    width: 100%;
13
+    span {
14
+      color: @text-color-secondary;
15
+      font-size: 14px;
16
+      line-height: 22px;
17
+    }
18
+    h4 {
19
+      color: @heading-color;
20
+      line-height: 32px;
21
+      font-size: 24px;
22
+    }
23
+  }
24
+  .waterWaveCanvasWrapper {
25
+    transform: scale(0.5);
26
+    transform-origin: 0 0;
27
+  }
28
+}

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

@@ -0,0 +1,63 @@
1
+/* eslint eqeqeq: 0 */
2
+import React from 'react';
3
+
4
+function computeHeight(node) {
5
+  const totalHeight = parseInt(getComputedStyle(node).height, 10);
6
+  const padding =
7
+    parseInt(getComputedStyle(node).paddingTop, 10) +
8
+    parseInt(getComputedStyle(node).paddingBottom, 10);
9
+  return totalHeight - padding;
10
+}
11
+
12
+function getAutoHeight(n) {
13
+  if (!n) {
14
+    return 0;
15
+  }
16
+
17
+  let node = n;
18
+
19
+  let height = computeHeight(node);
20
+
21
+  while (!height) {
22
+    node = node.parentNode;
23
+    if (node) {
24
+      height = computeHeight(node);
25
+    } else {
26
+      break;
27
+    }
28
+  }
29
+
30
+  return height;
31
+}
32
+
33
+const autoHeight = () => WrappedComponent => {
34
+  return class extends React.Component {
35
+    state = {
36
+      computedHeight: 0,
37
+    };
38
+
39
+    componentDidMount() {
40
+      const { height } = this.props;
41
+      if (!height) {
42
+        const h = getAutoHeight(this.root);
43
+        // eslint-disable-next-line
44
+        this.setState({ computedHeight: h });
45
+      }
46
+    }
47
+
48
+    handleRoot = node => {
49
+      this.root = node;
50
+    };
51
+
52
+    render() {
53
+      const { height } = this.props;
54
+      const { computedHeight } = this.state;
55
+      const h = height || computedHeight;
56
+      return (
57
+        <div ref={this.handleRoot}>{h > 0 && <WrappedComponent {...this.props} height={h} />}</div>
58
+      );
59
+    }
60
+  };
61
+};
62
+
63
+export default autoHeight;

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

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

+ 95
- 0
src/components/Charts/demo/chart-card.md View File

@@ -0,0 +1,95 @@
1
+---
2
+order: 1
3
+title: 图表卡片
4
+---
5
+
6
+用于展示图表的卡片容器,可以方便的配合其它图表套件展示丰富信息。
7
+
8
+```jsx
9
+import { ChartCard, yuan, Field } from 'ant-design-pro/lib/Charts';
10
+import Trend from 'ant-design-pro/lib/Trend';
11
+import { Row, Col, Icon, Tooltip } from 'antd';
12
+import numeral from 'numeral';
13
+
14
+ReactDOM.render(
15
+  <Row>
16
+    <Col span={24}>
17
+      <ChartCard
18
+        title="销售额"
19
+        action={
20
+          <Tooltip title="指标说明">
21
+            <Icon type="info-circle-o" />
22
+          </Tooltip>
23
+        }
24
+        total={() => (
25
+          <span dangerouslySetInnerHTML={{ __html: yuan(126560) }} />
26
+        )}
27
+        footer={
28
+          <Field label="日均销售额" value={numeral(12423).format("0,0")} />
29
+        }
30
+        contentHeight={46}
31
+      >
32
+        <span>
33
+          周同比
34
+          <Trend flag="up" style={{ marginLeft: 8, color: "rgba(0,0,0,.85)" }}>
35
+            12%
36
+          </Trend>
37
+        </span>
38
+        <span style={{ marginLeft: 16 }}>
39
+          日环比
40
+          <Trend
41
+            flag="down"
42
+            style={{ marginLeft: 8, color: "rgba(0,0,0,.85)" }}
43
+          >
44
+            11%
45
+          </Trend>
46
+        </span>
47
+      </ChartCard>
48
+    </Col>
49
+    <Col span={24} style={{ marginTop: 24 }}>
50
+      <ChartCard
51
+        title="移动指标"
52
+        avatar={
53
+          <img
54
+            style={{ width: 56, height: 56 }}
55
+            src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png"
56
+            alt="indicator"
57
+          />
58
+        }
59
+        action={
60
+          <Tooltip title="指标说明">
61
+            <Icon type="info-circle-o" />
62
+          </Tooltip>
63
+        }
64
+        total={() => (
65
+          <span dangerouslySetInnerHTML={{ __html: yuan(126560) }} />
66
+        )}
67
+        footer={
68
+          <Field label="日均销售额" value={numeral(12423).format("0,0")} />
69
+        }
70
+      />
71
+    </Col>
72
+    <Col span={24} style={{ marginTop: 24 }}>
73
+      <ChartCard
74
+        title="移动指标"
75
+        avatar={
76
+          <img
77
+            alt="indicator"
78
+            style={{ width: 56, height: 56 }}
79
+            src="https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png"
80
+          />
81
+        }
82
+        action={
83
+          <Tooltip title="指标说明">
84
+            <Icon type="info-circle-o" />
85
+          </Tooltip>
86
+        }
87
+        total={() => (
88
+          <span dangerouslySetInnerHTML={{ __html: yuan(126560) }} />
89
+        )}
90
+      />
91
+    </Col>
92
+  </Row>,
93
+  mountNode,
94
+);
95
+```

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

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

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

@@ -0,0 +1,28 @@
1
+---
2
+order: 2
3
+col: 2
4
+title: 迷你区域图
5
+---
6
+
7
+````jsx
8
+import { MiniArea } from 'ant-design-pro/lib/Charts';
9
+import moment from 'moment';
10
+
11
+const visitData = [];
12
+const beginDay = new Date().getTime();
13
+for (let i = 0; i < 20; i += 1) {
14
+  visitData.push({
15
+    x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'),
16
+    y: Math.floor(Math.random() * 100) + 10,
17
+  });
18
+}
19
+
20
+ReactDOM.render(
21
+  <MiniArea
22
+    line
23
+    color="#cceafe"
24
+    height={45}
25
+    data={visitData}
26
+  />
27
+, mountNode);
28
+````

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

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

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

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

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

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

+ 84
- 0
src/components/Charts/demo/mix.md View File

@@ -0,0 +1,84 @@
1
+---
2
+order: 0
3
+title: 图表套件组合展示
4
+---
5
+
6
+利用 Ant Design Pro 提供的图表套件,可以灵活组合符合设计规范的图表来满足复杂的业务需求。
7
+
8
+````jsx
9
+import { ChartCard, Field, MiniArea, MiniBar, MiniProgress } from 'ant-design-pro/lib/Charts';
10
+import Trend from 'ant-design-pro/lib/Trend';
11
+import NumberInfo from 'ant-design-pro/lib/NumberInfo';
12
+import { Row, Col, Icon, Tooltip } from 'antd';
13
+import numeral from 'numeral';
14
+import moment from 'moment';
15
+
16
+const visitData = [];
17
+const beginDay = new Date().getTime();
18
+for (let i = 0; i < 20; i += 1) {
19
+  visitData.push({
20
+    x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'),
21
+    y: Math.floor(Math.random() * 100) + 10,
22
+  });
23
+}
24
+
25
+ReactDOM.render(
26
+  <Row>
27
+    <Col span={24}>
28
+      <ChartCard
29
+        title="搜索用户数量"
30
+        total={numeral(8846).format('0,0')}
31
+        contentHeight={134}
32
+      >
33
+        <NumberInfo
34
+          subTitle={<span>本周访问</span>}
35
+          total={numeral(12321).format('0,0')}
36
+          status="up"
37
+          subTotal={17.1}
38
+        />
39
+        <MiniArea
40
+          line
41
+          height={45}
42
+          data={visitData}
43
+        />
44
+      </ChartCard>
45
+    </Col>
46
+    <Col span={24} style={{ marginTop: 24 }}>
47
+      <ChartCard
48
+        title="访问量"
49
+        action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>}
50
+        total={numeral(8846).format('0,0')}
51
+        footer={<Field label="日访问量" value={numeral(1234).format('0,0')} />}
52
+        contentHeight={46}
53
+      >
54
+        <MiniBar
55
+          height={46}
56
+          data={visitData}
57
+        />
58
+      </ChartCard>
59
+    </Col>
60
+    <Col span={24} style={{ marginTop: 24 }}>
61
+      <ChartCard
62
+        title="线上购物转化率"
63
+        action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>}
64
+        total="78%"
65
+        footer={
66
+          <div>
67
+            <span>
68
+              周同比
69
+              <Trend flag="up" style={{ marginLeft: 8, color: 'rgba(0,0,0,.85)' }}>12%</Trend>
70
+            </span>
71
+            <span style={{ marginLeft: 16 }}>
72
+              日环比
73
+              <Trend flag="down" style={{ marginLeft: 8, color: 'rgba(0,0,0,.85)' }}>11%</Trend>
74
+            </span>
75
+          </div>
76
+        }
77
+        contentHeight={46}
78
+      >
79
+        <MiniProgress percent={78} strokeWidth={8} target={80} />
80
+      </ChartCard>
81
+    </Col>
82
+  </Row>
83
+, mountNode);
84
+````

+ 54
- 0
src/components/Charts/demo/pie.md View File

@@ -0,0 +1,54 @@
1
+---
2
+order: 5
3
+title: 饼状图
4
+---
5
+
6
+```jsx
7
+import { Pie, yuan } from 'ant-design-pro/lib/Charts';
8
+
9
+const salesPieData = [
10
+  {
11
+    x: '家用电器',
12
+    y: 4544,
13
+  },
14
+  {
15
+    x: '食用酒水',
16
+    y: 3321,
17
+  },
18
+  {
19
+    x: '个护健康',
20
+    y: 3113,
21
+  },
22
+  {
23
+    x: '服饰箱包',
24
+    y: 2341,
25
+  },
26
+  {
27
+    x: '母婴产品',
28
+    y: 1231,
29
+  },
30
+  {
31
+    x: '其他',
32
+    y: 1231,
33
+  },
34
+];
35
+
36
+ReactDOM.render(
37
+  <Pie
38
+    hasLegend
39
+    title="销售额"
40
+    subTitle="销售额"
41
+    total={() => (
42
+      <span
43
+        dangerouslySetInnerHTML={{
44
+          __html: yuan(salesPieData.reduce((pre, now) => now.y + pre, 0))
45
+        }}
46
+      />
47
+    )}
48
+    data={salesPieData}
49
+    valueFormat={val => <span dangerouslySetInnerHTML={{ __html: yuan(val) }} />}
50
+    height={294}
51
+  />,
52
+  mountNode,
53
+);
54
+```

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

@@ -0,0 +1,64 @@
1
+---
2
+order: 7
3
+title: 雷达图
4
+---
5
+
6
+````jsx
7
+import { Radar, ChartCard } from 'ant-design-pro/lib/Charts';
8
+
9
+const radarOriginData = [
10
+  {
11
+    name: '个人',
12
+    ref: 10,
13
+    koubei: 8,
14
+    output: 4,
15
+    contribute: 5,
16
+    hot: 7,
17
+  },
18
+  {
19
+    name: '团队',
20
+    ref: 3,
21
+    koubei: 9,
22
+    output: 6,
23
+    contribute: 3,
24
+    hot: 1,
25
+  },
26
+  {
27
+    name: '部门',
28
+    ref: 4,
29
+    koubei: 1,
30
+    output: 6,
31
+    contribute: 5,
32
+    hot: 7,
33
+  },
34
+];
35
+const radarData = [];
36
+const radarTitleMap = {
37
+  ref: '引用',
38
+  koubei: '口碑',
39
+  output: '产量',
40
+  contribute: '贡献',
41
+  hot: '热度',
42
+};
43
+radarOriginData.forEach((item) => {
44
+  Object.keys(item).forEach((key) => {
45
+    if (key !== 'name') {
46
+      radarData.push({
47
+        name: item.name,
48
+        label: radarTitleMap[key],
49
+        value: item[key],
50
+      });
51
+    }
52
+  });
53
+});
54
+
55
+ReactDOM.render(
56
+  <ChartCard title="数据比例">
57
+    <Radar
58
+      hasLegend
59
+      height={286}
60
+      data={radarData}
61
+    />
62
+  </ChartCard>
63
+, mountNode);
64
+````

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

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

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

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

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

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

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

@@ -0,0 +1,15 @@
1
+// 全局 G2 设置
2
+import { track, setTheme } from 'bizcharts';
3
+
4
+track(false);
5
+
6
+const config = {
7
+  defaultColor: '#1089ff',
8
+  shape: {
9
+    interval: {
10
+      fillOpacity: 1,
11
+    },
12
+  },
13
+};
14
+
15
+setTheme(config);

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

@@ -0,0 +1,17 @@
1
+import * as numeral from 'numeral';
2
+export { default as ChartCard } from './ChartCard';
3
+export { default as Bar } from './Bar';
4
+export { default as Pie } from './Pie';
5
+export { default as Radar } from './Radar';
6
+export { default as Gauge } from './Gauge';
7
+export { default as MiniArea } from './MiniArea';
8
+export { default as MiniBar } from './MiniBar';
9
+export { default as MiniProgress } from './MiniProgress';
10
+export { default as Field } from './Field';
11
+export { default as WaterWave } from './WaterWave';
12
+export { default as TagCloud } from './TagCloud';
13
+export { default as TimelineChart } from './TimelineChart';
14
+
15
+declare const yuan: (value: number | string) => string;
16
+
17
+export { yuan };

+ 49
- 0
src/components/Charts/index.js View File

@@ -0,0 +1,49 @@
1
+import numeral from 'numeral';
2
+import './g2';
3
+import ChartCard from './ChartCard';
4
+import Bar from './Bar';
5
+import Pie from './Pie';
6
+import Radar from './Radar';
7
+import Gauge from './Gauge';
8
+import MiniArea from './MiniArea';
9
+import MiniBar from './MiniBar';
10
+import MiniProgress from './MiniProgress';
11
+import Field from './Field';
12
+import WaterWave from './WaterWave';
13
+import TagCloud from './TagCloud';
14
+import TimelineChart from './TimelineChart';
15
+
16
+const yuan = val => `&yen; ${numeral(val).format('0,0')}`;
17
+
18
+const Charts = {
19
+  yuan,
20
+  Bar,
21
+  Pie,
22
+  Gauge,
23
+  Radar,
24
+  MiniBar,
25
+  MiniArea,
26
+  MiniProgress,
27
+  ChartCard,
28
+  Field,
29
+  WaterWave,
30
+  TagCloud,
31
+  TimelineChart,
32
+};
33
+
34
+export {
35
+  Charts as default,
36
+  yuan,
37
+  Bar,
38
+  Pie,
39
+  Gauge,
40
+  Radar,
41
+  MiniBar,
42
+  MiniArea,
43
+  MiniProgress,
44
+  ChartCard,
45
+  Field,
46
+  WaterWave,
47
+  TagCloud,
48
+  TimelineChart,
49
+};

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

@@ -0,0 +1,19 @@
1
+.miniChart {
2
+  position: relative;
3
+  width: 100%;
4
+  .chartContent {
5
+    position: absolute;
6
+    bottom: -28px;
7
+    width: 100%;
8
+    > div {
9
+      margin: 0 -5px;
10
+      overflow: hidden;
11
+    }
12
+  }
13
+  .chartLoading {
14
+    position: absolute;
15
+    top: 16px;
16
+    left: 50%;
17
+    margin-left: -7px;
18
+  }
19
+}

+ 0
- 0
src/components/Charts/index.md View File


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