├── .editorconfig
├── .gitignore
├── LICENSE
├── README.md
├── mock
├── api.notices.json
└── api.userCurrent.json
├── package.json
├── public
├── fonts
│ ├── demo.css
│ ├── demo_fontclass.html
│ ├── demo_symbol.html
│ ├── demo_unicode.html
│ ├── iconfont.css
│ ├── iconfont.eot
│ ├── iconfont.js
│ ├── iconfont.svg
│ ├── iconfont.ttf
│ └── iconfont.woff
├── img
│ ├── 0]LJM$ZE]B}GVUU()TV4I`T.png
│ ├── OQF~ACO}(XYTCQ65G[LR9$4.png
│ ├── favicon.ico
│ └── logo.svg
└── index.html
├── server
├── api.js
├── chart.js
├── getData.js
├── profile.js
├── rule.js
└── server.js
├── src
├── App.less
├── App.tsx
├── components
│ ├── ActiveChart
│ │ ├── index.less
│ │ └── index.tsx
│ ├── AvatarList
│ │ ├── index.less
│ │ └── index.tsx
│ ├── Charts
│ │ ├── Bar
│ │ │ └── index.tsx
│ │ ├── ChartCard
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── Field
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── Gauge
│ │ │ └── index.tsx
│ │ ├── MiniArea
│ │ │ └── index.tsx
│ │ ├── MiniBar
│ │ │ └── index.tsx
│ │ ├── MiniProgress
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── Pie
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── Radar
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── TagCloud
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── TimelineChart
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── WaterWave
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── autoHeight.tsx
│ │ ├── g2.ts
│ │ ├── index.less
│ │ └── index.ts
│ ├── CountDown
│ │ └── index.tsx
│ ├── DescriptionList
│ │ ├── Description.tsx
│ │ ├── DescriptionList.tsx
│ │ ├── index.less
│ │ ├── index.ts
│ │ └── responsive.ts
│ ├── EditableLinkGroup
│ │ ├── index.less
│ │ └── index.tsx
│ ├── Ellipsis
│ │ ├── index.less
│ │ └── index.tsx
│ ├── Exception
│ │ ├── index.less
│ │ ├── index.tsx
│ │ └── typeConfig.ts
│ ├── FooterToolbar
│ │ ├── index.less
│ │ └── index.tsx
│ ├── GlobalFooter
│ │ ├── index.less
│ │ └── index.tsx
│ ├── HeaderSearch
│ │ ├── index.less
│ │ └── index.tsx
│ ├── MyFooter
│ │ ├── index.less
│ │ └── index.tsx
│ ├── MyHeader
│ │ ├── index.less
│ │ └── index.tsx
│ ├── MyMenu
│ │ ├── index.tsx
│ │ └── menu.ts
│ ├── NoticeIcon
│ │ ├── NoticeList.less
│ │ ├── NoticeList.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── NumberInfo
│ │ ├── index.less
│ │ └── index.tsx
│ ├── PageHeader
│ │ ├── index.less
│ │ └── index.tsx
│ ├── PageHeaderLayout
│ │ ├── PageHeaderLayout.less
│ │ └── index.tsx
│ ├── ReactLoadable.tsx
│ ├── Result
│ │ ├── index.less
│ │ └── index.tsx
│ ├── StandardFormRow
│ │ ├── index.less
│ │ └── index.tsx
│ ├── StandardTable
│ │ ├── index.less
│ │ └── index.tsx
│ ├── TagSelect
│ │ ├── index.less
│ │ └── index.tsx
│ └── Trend
│ │ ├── index.less
│ │ └── index.tsx
├── index.tsx
├── routes
│ ├── Dashboard
│ │ ├── Analysis.less
│ │ ├── Analysis.tsx
│ │ ├── Monitor.less
│ │ ├── Monitor.tsx
│ │ ├── Workplace.less
│ │ ├── Workplace.tsx
│ │ └── index.tsx
│ ├── Exception
│ │ ├── 403.tsx
│ │ ├── 404.tsx
│ │ ├── 500.tsx
│ │ ├── index.tsx
│ │ ├── style.less
│ │ └── triggerException.tsx
│ ├── Form
│ │ ├── AdvancedForm.tsx
│ │ ├── BasicForm.tsx
│ │ ├── StepForm
│ │ │ ├── Step1.tsx
│ │ │ ├── Step2.tsx
│ │ │ ├── Step3.tsx
│ │ │ ├── index.tsx
│ │ │ └── style.less
│ │ ├── TableForm.tsx
│ │ ├── index.tsx
│ │ └── style.less
│ ├── List
│ │ ├── Applications.less
│ │ ├── Applications.tsx
│ │ ├── Articles.less
│ │ ├── Articles.tsx
│ │ ├── BasicList.less
│ │ ├── BasicList.tsx
│ │ ├── CardList.less
│ │ ├── CardList.tsx
│ │ ├── List.tsx
│ │ ├── Projects.less
│ │ ├── Projects.tsx
│ │ ├── TableList.less
│ │ ├── TableList.tsx
│ │ └── index.tsx
│ ├── MainPages.tsx
│ ├── Profile
│ │ ├── AdvancedProfile.less
│ │ ├── AdvancedProfile.tsx
│ │ ├── BasicProfile.less
│ │ ├── BasicProfile.tsx
│ │ └── index.tsx
│ ├── Result
│ │ ├── Error.tsx
│ │ ├── Success.tsx
│ │ └── index.tsx
│ └── User
│ │ ├── Login.less
│ │ ├── Login.tsx
│ │ ├── Register.less
│ │ ├── Register.tsx
│ │ ├── RegisterResult.less
│ │ ├── RegisterResult.tsx
│ │ ├── index.less
│ │ └── index.tsx
├── stores
│ ├── Dashboard
│ │ ├── Analysis.ts
│ │ ├── Monitor.ts
│ │ ├── Workplace.ts
│ │ └── index.ts
│ ├── ErrorStore.ts
│ ├── Form
│ │ ├── BasicForm.ts
│ │ └── index.ts
│ ├── Header.ts
│ ├── Home.ts
│ ├── List
│ │ ├── FakeList.ts
│ │ ├── TableList.ts
│ │ └── index.ts
│ ├── Profile
│ │ ├── AdvancedProfile.ts
│ │ ├── BasicProfile.ts
│ │ └── index.ts
│ └── index.ts
├── theme.js
└── utils
│ ├── api.ts
│ ├── request.ts
│ ├── utils.less
│ └── utils.ts
├── tsconfig.json
├── tslint.json
├── webpack.common.js
├── webpack.dev.js
├── webpack.prod.js
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | # A special property that should be specified at the top of the file outside of
4 | # any sections. Set to true to stop .editor config file search on current file
5 | root = true
6 |
7 | [*]
8 | # Indentation style
9 | # Possible values - tab, space
10 | indent_style = space
11 |
12 | # Indentation size in single-spaced characters
13 | # Possible values - an integer, tab
14 | indent_size = 4
15 |
16 | # Line ending file format
17 | # Possible values - lf, crlf, cr
18 | end_of_line = crlf
19 |
20 | # File character encoding
21 | # Possible values - latin1, utf-8, utf-16be, utf-16le
22 | charset = utf-8
23 |
24 | # Denotes whether to trim whitespace at the end of lines
25 | # Possible values - true, false
26 | trim_trailing_whitespace = true
27 |
28 | # Denotes whether file should end with a newline
29 | # Possible values - true, false
30 | insert_final_newline = true
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 | package-lock.json
6 |
7 | # testing
8 | coverage
9 |
10 | # production
11 | build
12 |
13 | # misc
14 | .DS_Store
15 | .idea
16 | npm-debug.log
17 | compilation-stats.json
18 | /dist
19 |
20 | .vscode/
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Jackyzm
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-app
2 |
3 | 使用 webpack 自行搭建 react 环境,使用 react 16.4 + react-router 4.3 + typescript + mobx 实现 antd-pro
4 | \
5 | 使用 node + mock 模拟数据
6 |
7 | ## 启动项目
8 |
9 | ```sh
10 | 获取项目
11 | $ git clone https://github.com/Jackyzm/react-app-ts.git
12 |
13 | 进入目录
14 | $ cd react-app-ts
15 |
16 | 安装依赖
17 | $ yarn install
18 |
19 | 启动项目
20 | $ yarn start
21 |
22 | 启动模拟数据
23 | $ yarn serve
24 |
25 | 在浏览器中输入 http://localhost:8000
26 | ```
27 |
28 | ## 项目示例如下
29 |
30 | (使用 ts+mobx 仿 ant-pro)
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/mock/api.notices.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "000000001",
4 | "avatar": "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
5 | "title": "你收到了 14 份新周报",
6 | "datetime": "2017-08-09",
7 | "type": "通知"
8 | },
9 | {
10 | "id": "000000002",
11 | "avatar": "https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png",
12 | "title": "你推荐的 曲妮妮 已通过第三轮面试",
13 | "datetime": "2017-08-08",
14 | "type": "通知"
15 | },
16 | {
17 | "id": "000000003",
18 | "avatar": "https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png",
19 | "title": "这种模板可以区分多种通知类型",
20 | "datetime": "2017-08-07",
21 | "read": true,
22 | "type": "通知"
23 | },
24 | {
25 | "id": "000000004",
26 | "avatar": "https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
27 | "title": "左侧图标用于区分不同的类型",
28 | "datetime": "2017-08-07",
29 | "type": "通知"
30 | },
31 | {
32 | "id": "000000005",
33 | "avatar": "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
34 | "title": "内容不要超过两行字,超出时自动截断",
35 | "datetime": "2017-08-07",
36 | "type": "通知"
37 | },
38 | {
39 | "id": "000000006",
40 | "avatar": "https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
41 | "title": "曲丽丽 评论了你",
42 | "description": "描述信息描述信息描述信息",
43 | "datetime": "2017-08-07",
44 | "type": "消息"
45 | },
46 | {
47 | "id": "000000007",
48 | "avatar": "https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
49 | "title": "朱偏右 回复了你",
50 | "description": "这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像",
51 | "datetime": "2017-08-07",
52 | "type": "消息"
53 | },
54 | {
55 | "id": "000000008",
56 | "avatar": "https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
57 | "title": "标题",
58 | "description": "这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像",
59 | "datetime": "2017-08-07",
60 | "type": "消息"
61 | },
62 | {
63 | "id": "000000009",
64 | "title": "任务名称",
65 | "description": "任务需要在 2017-01-12 20:00 前启动",
66 | "extra": "未开始",
67 | "status": "todo",
68 | "type": "待办"
69 | },
70 | {
71 | "id": "000000010",
72 | "title": "第三方紧急代码变更",
73 | "description": "冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务",
74 | "extra": "马上到期",
75 | "status": "urgent",
76 | "type": "待办"
77 | },
78 | {
79 | "id": "000000011",
80 | "title": "信息安全考试",
81 | "description": "指派竹尔于 2017-01-09 前完成更新并发布",
82 | "extra": "已耗时 8 天",
83 | "status": "doing",
84 | "type": "待办"
85 | },
86 | {
87 | "id": "000000012",
88 | "title": "ABCD 版本发布",
89 | "description": "冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务",
90 | "extra": "进行中",
91 | "status": "processing",
92 | "type": "待办"
93 | }
94 | ]
95 |
--------------------------------------------------------------------------------
/mock/api.userCurrent.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "皮皮瞎",
3 | "avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
4 | "userid": "00000001",
5 | "notifyCount": 12
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-app-ts",
3 | "version": "1.0.0",
4 | "description": "react+ts",
5 | "main": "index.tsx",
6 | "scripts": {
7 | "build": "rimraf dist && webpack --config webpack.prod.js --profile",
8 | "start": "webpack-dev-server --config webpack.dev.js --colors",
9 | "serve": "node ./server/server.js"
10 | },
11 | "author": "Jackyzm",
12 | "license": "MIT",
13 | "dependencies": {
14 | "@antv/data-set": "^0.9.1",
15 | "antd": "^3.10.1",
16 | "bizcharts": "^3.2.1-beta.2",
17 | "bizcharts-plugin-slider": "^2.0.4",
18 | "lodash": "^4.17.21",
19 | "lodash-decorators": "^6.0.0",
20 | "mobx": "^5.0.3",
21 | "mobx-react": "^5.2.3",
22 | "numeral": "^2.0.6",
23 | "react": "^16.4.1",
24 | "react-dom": "^16.4.1",
25 | "react-fittext": "^1.0.0",
26 | "react-loadable": "^5.5.0",
27 | "react-router-dom": "^4.3.1",
28 | "typescript": "^3.1.3",
29 | "whatwg-fetch": "^2.0.4"
30 | },
31 | "devDependencies": {
32 | "@types/history": "^4.7.0",
33 | "@types/react": "^16.4.6",
34 | "@types/react-dom": "^16.0.6",
35 | "autoprefixer": "^9.0.1",
36 | "body-parser": "^1.18.3",
37 | "copy-webpack-plugin": "^4.5.2",
38 | "css-loader": "^1.0.0",
39 | "express": "^4.16.3",
40 | "file-loader": "^1.1.11",
41 | "fork-ts-checker-webpack-plugin": "^0.4.3",
42 | "html-loader": "^0.5.5",
43 | "html-webpack-plugin": "^3.2.0",
44 | "less": "^3.7.1",
45 | "less-loader": "^4.1.0",
46 | "mini-css-extract-plugin": "^0.4.4",
47 | "mockjs": "^1.0.1-beta3",
48 | "optimize-css-assets-webpack-plugin": "^5.0.1",
49 | "postcss-flexbugs-fixes": "^4.0.0",
50 | "postcss-loader": "^2.1.6",
51 | "postcss-safe-parser": "^4.0.1",
52 | "progress-bar-webpack-plugin": "^1.11.0",
53 | "react-dev-utils": "^11.0.4",
54 | "source-map-loader": "^0.2.3",
55 | "style-loader": "^0.21.0",
56 | "ts-import-plugin": "^1.5.4",
57 | "ts-loader": "^4.4.2",
58 | "tsconfig-paths-webpack-plugin": "^3.2.0",
59 | "tslint": "^5.11.0",
60 | "tslint-config-prettier": "^1.14.0",
61 | "tslint-react": "^3.6.0",
62 | "uglifyjs-webpack-plugin": "^1.2.7",
63 | "webpack": "^4.16.1",
64 | "webpack-cli": "^3.1.0",
65 | "webpack-dev-server": "^3.1.5",
66 | "webpack-merge": "^4.1.3"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/public/fonts/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackyzm/react-app-ts/14d0d77bb4273690a953193c05ec081d9608ef60/public/fonts/iconfont.eot
--------------------------------------------------------------------------------
/public/fonts/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackyzm/react-app-ts/14d0d77bb4273690a953193c05ec081d9608ef60/public/fonts/iconfont.ttf
--------------------------------------------------------------------------------
/public/fonts/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackyzm/react-app-ts/14d0d77bb4273690a953193c05ec081d9608ef60/public/fonts/iconfont.woff
--------------------------------------------------------------------------------
/public/img/0]LJM$ZE]B}GVUU()TV4I`T.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackyzm/react-app-ts/14d0d77bb4273690a953193c05ec081d9608ef60/public/img/0]LJM$ZE]B}GVUU()TV4I`T.png
--------------------------------------------------------------------------------
/public/img/OQF~ACO}(XYTCQ65G[LR9$4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackyzm/react-app-ts/14d0d77bb4273690a953193c05ec081d9608ef60/public/img/OQF~ACO}(XYTCQ65G[LR9$4.png
--------------------------------------------------------------------------------
/public/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackyzm/react-app-ts/14d0d77bb4273690a953193c05ec081d9608ef60/public/img/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Ant Design
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/server/api.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const Router = express.Router();
3 | const mockjs = require('mockjs');
4 |
5 | const notices = require('../mock/api.notices.json');
6 | const charts = require('./chart');
7 | const userCurrent = require('../mock/api.userCurrent.json');
8 | const projectNotice = require('./getData').getNotice;
9 | const getActivities = require('./getData').getActivities;
10 | const getFakeList = require('./getData').getFakeList;
11 | const getRule = require('./rule').getRule;
12 | const putRule = require('./rule').putRule;
13 | const deleteRule = require('./rule').deleteRule;
14 | const getProfileBasicData = require('./profile').getProfileBasicData;
15 | const getProfileAdvancedData = require('./profile').getProfileAdvancedData;
16 |
17 | // post 获取参数 console.log(req.body);
18 | // get 获取参数 console.log(req.query);
19 |
20 | Router.get('/notices', function(req, res) {
21 | return res.json(notices);
22 | });
23 |
24 | Router.get('/charts', function(req, res) {
25 | return res.json(charts);
26 | });
27 |
28 | Router.get('/userCurrent', function(req, res) {
29 | return res.json(userCurrent);
30 | });
31 |
32 | Router.get('/tags', function(req, res) {
33 | const list = mockjs.mock({
34 | 'list|100': [{ name: '@city', 'value|1-100': 150, 'type|0-2': 1 }],
35 | })
36 | return res.json(list);
37 | });
38 |
39 | Router.get('/project/notice', function(req, res) {
40 | return res.json(projectNotice);
41 | });
42 |
43 | Router.get('/activities', function(req, res) {
44 | return res.json(getActivities);
45 | });
46 |
47 | Router.post('/form-basic', function(req, res) {
48 | return res.json({"code":200, "success":true, "data": null});
49 | });
50 |
51 | Router.get('/table-list', function(req, res) {
52 | return res.json(getRule(req.query));
53 | });
54 |
55 | Router.put('/table-list-put', function(req, res) {
56 | return res.json(putRule(req.body));
57 | });
58 |
59 | Router.post('/table-list-delete', function(req, res) {
60 | return res.json(deleteRule(req.body));
61 | });
62 |
63 | Router.get('/fake-list', function(req, res) {
64 | return res.json(getFakeList(req.query));
65 | });
66 |
67 | Router.get('/profile/basic', function(req, res) {
68 | return res.json(getProfileBasicData);
69 | });
70 |
71 | Router.get('/profile/advanced', function(req, res) {
72 | return res.json(getProfileAdvancedData);
73 | });
74 |
75 | Router.get('/401', function(req, res) {
76 | return res.status(401).send('Sorry!');
77 | });
78 |
79 | Router.get('/403', function(req, res) {
80 | return res.status(403).send('Sorry, 403 Forbidden!');
81 | });
82 |
83 | Router.get('/404', function(req, res) {
84 | return res.status(404).send('Sorry, 404 Not found!');
85 | });
86 |
87 | Router.get('/500', function(req, res) {
88 | return res.status(500).send({ error: 'something blew up' });
89 | });
90 |
91 | module.exports = Router;
92 |
--------------------------------------------------------------------------------
/server/chart.js:
--------------------------------------------------------------------------------
1 | const moment = require('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 | 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 | module.exports = getFakeChartData;
196 |
--------------------------------------------------------------------------------
/server/profile.js:
--------------------------------------------------------------------------------
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 | const getProfileBasicData = {
145 | basicGoods,
146 | basicProgress,
147 | };
148 |
149 | const getProfileAdvancedData = {
150 | advancedOperation1,
151 | advancedOperation2,
152 | advancedOperation3,
153 | };
154 |
155 | module.exports = {
156 | getProfileBasicData,
157 | getProfileAdvancedData,
158 | };
159 |
--------------------------------------------------------------------------------
/server/rule.js:
--------------------------------------------------------------------------------
1 | const parse = require('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 | function getRule(query) {
27 | const params = query;
28 |
29 | let dataSource = [...tableListDataSource];
30 |
31 | if (params.sorter) {
32 | const s = params.sorter.split('_');
33 | dataSource = dataSource.sort((prev, next) => {
34 | if (s[1] === 'descend') {
35 | return next[s[0]] - prev[s[0]];
36 | }
37 | return prev[s[0]] - next[s[0]];
38 | });
39 | }
40 |
41 | if (params.status) {
42 | const status = params.status.split(',');
43 | let filterDataSource = [];
44 | status.forEach(s => {
45 | filterDataSource = filterDataSource.concat(
46 | [...dataSource].filter(data => parseInt(data.status, 10) === parseInt(s[0], 10))
47 | );
48 | });
49 | dataSource = filterDataSource;
50 | }
51 |
52 | if (params.no) {
53 | dataSource = dataSource.filter(data => data.no.indexOf(params.no) > -1);
54 | }
55 |
56 | let pageSize = 10;
57 | if (params.pageSize) {
58 | pageSize = params.pageSize * 1;
59 | }
60 |
61 | const result = {
62 | list: dataSource,
63 | pagination: {
64 | total: dataSource.length,
65 | pageSize,
66 | current: parseInt(params.currentPage, 10) || 1,
67 | },
68 | };
69 |
70 | return result;
71 | }
72 |
73 | function putRule(body) {
74 | const { description } = body;
75 |
76 | const i = Math.ceil(Math.random() * 10000);
77 | tableListDataSource.unshift({
78 | key: i,
79 | href: 'https://ant.design',
80 | avatar: [
81 | 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
82 | 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
83 | ][i % 2],
84 | no: `TradeCode ${i}`,
85 | title: `一个任务名称 ${i}`,
86 | owner: '曲丽丽',
87 | description,
88 | callNo: Math.floor(Math.random() * 1000),
89 | status: Math.floor(Math.random() * 10) % 2,
90 | updatedAt: new Date(),
91 | createdAt: new Date(),
92 | progress: Math.ceil(Math.random() * 100),
93 | });
94 |
95 | const result = {
96 | list: tableListDataSource,
97 | pagination: {
98 | total: tableListDataSource.length,
99 | },
100 | };
101 |
102 | return result;
103 | }
104 |
105 | function deleteRule(body) {
106 | const { no } = body;
107 |
108 | tableListDataSource = tableListDataSource.filter(item => no.indexOf(item.no) === -1);
109 |
110 | const result = {
111 | list: tableListDataSource,
112 | pagination: {
113 | total: tableListDataSource.length,
114 | },
115 | };
116 |
117 | return result;
118 | }
119 |
120 | module.exports = {
121 | getRule,
122 | putRule,
123 | deleteRule
124 | };
125 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const bodyParser = require('body-parser');
3 | const userRouter = require('./api');
4 | const path = require('path');
5 | const app = express();
6 |
7 | let rootPath = 'dist';
8 | app.use(express.static(rootPath));
9 | app.use('index.html', function (req, res) {
10 | res.sendFile(path.resolve(rootPath, 'index.html'))
11 | });
12 |
13 | app.use(bodyParser.json());
14 | app.use('/api', userRouter);
15 |
16 | app.listen(9000, function () {
17 | console.log('Node app start at port 9000');
18 | })
19 |
--------------------------------------------------------------------------------
/src/App.less:
--------------------------------------------------------------------------------
1 | #root {
2 | canvas {
3 | display: block;
4 | }
5 | height: 100%;
6 | > .ant-layout {
7 | overflow: hidden;
8 | .trigger {
9 | font-size: 18px;
10 | line-height: 64px;
11 | padding: 0 24px;
12 | cursor: pointer;
13 | transition: color 0.3s;
14 | }
15 | .trigger:hover {
16 | color: #1890ff;
17 | }
18 | .logo {
19 | height: 64px;
20 | position: relative;
21 | line-height: 64px;
22 | padding-left: 24px;
23 | transition: all 0.3s;
24 | background: #002140;
25 | overflow: hidden;
26 | img {
27 | display: inline-block;
28 | vertical-align: middle;
29 | height: 32px;
30 | }
31 | h1 {
32 | color: white;
33 | display: inline-block;
34 | vertical-align: middle;
35 | font-size: 18px;
36 | margin: 0 0 0 12px;
37 | font-weight: 600;
38 | }
39 | }
40 | }
41 | }
42 | html,
43 | body,
44 | #root {
45 | height: 100%;
46 | }
47 |
48 | canvas {
49 | display: block;
50 | }
51 |
52 | body {
53 | text-rendering: optimizeLegibility;
54 | -webkit-font-smoothing: antialiased;
55 | -moz-osx-font-smoothing: grayscale;
56 | }
57 |
58 | .globalSpin {
59 | width: 100%;
60 | margin: 40px 0 !important;
61 | }
62 |
63 | // temp fix for https://github.com/ant-design/ant-design/commit/a1fafb5b727b62cb0be29ce6e9eca8f579d4f8b7
64 | .ant-spin-container {
65 | overflow: visible !important;
66 | }
67 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { observer, inject } from "mobx-react";
3 | import { Link } from "react-router-dom";
4 | import { Layout, message } from "antd";
5 | import MyMenu from "./components/MyMenu";
6 | import MyFooter from "./components/MyFooter";
7 | import MyHeader from "./components/MyHeader";
8 | import MainPages from "./routes/MainPages";
9 | import "./App.less";
10 |
11 | const { Header, Sider, Content, Footer } = Layout;
12 |
13 | /**
14 | * @class Home
15 | */
16 | @inject((store: { Header }) => {
17 | return {
18 | changeFetchNotice: store.Header.changeFetchNotice,
19 | clearNotices: store.Header.clearNotices
20 | };
21 | })
22 | @observer
23 | class App extends React.Component<
24 | {
25 | changeFetchNotice: () => void;
26 | clearNotices: (type: string) => void;
27 | history;
28 | },
29 | { collapsed: boolean }
30 | > {
31 | /**
32 | * @param {*} props
33 | * @memberof Home
34 | */
35 | constructor(props) {
36 | super(props);
37 | this.state = {
38 | collapsed: false
39 | };
40 | }
41 | private onCollapse() {
42 | this.setState({ collapsed: !this.state.collapsed });
43 | }
44 | private handleNoticeVisibleChange() {
45 | this.props.changeFetchNotice();
46 | }
47 | private handleNoticeClear = type => {
48 | message.success(`清空了${type}`);
49 | this.props.clearNotices(type);
50 | };
51 | private handleMenuClick({ key }) {
52 | if (key === "triggerError") {
53 | this.props.history.push("/exception/trigger");
54 | return;
55 | }
56 | if (key === "logout") {
57 | this.props.history.push("/user/login");
58 | }
59 | }
60 | public render() {
61 | return (
62 |
63 |
69 |
70 |
71 |

72 |
Ant Design Pro
73 |
74 |
75 |
76 |
77 |
78 |
79 | this.onCollapse()}
82 | // currentUser={{avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', name: 'Serati Ma', notifyCount: 15}}
83 | onMenuClick={key => this.handleMenuClick(key)}
84 | onNoticeVisibleChange={() =>
85 | this.handleNoticeVisibleChange()
86 | }
87 | onNoticeClear={type => this.handleNoticeClear(type)}
88 | />
89 |
90 |
91 |
92 |
93 |
96 |
97 |
98 | );
99 | }
100 | }
101 |
102 | export default App;
103 |
--------------------------------------------------------------------------------
/src/components/ActiveChart/index.less:
--------------------------------------------------------------------------------
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 | }
32 |
--------------------------------------------------------------------------------
/src/components/ActiveChart/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { MiniArea } from '../Charts';
4 | import NumberInfo from '../NumberInfo';
5 |
6 | import './index.less';
7 |
8 | const fixedZero = (val) => {
9 | return val * 1 < 10 ? `0${val}` : val;
10 | }
11 |
12 | const 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 | class ActiveChart extends React.Component<{}, {activeData}> {
24 | private timer;
25 | constructor(props) {
26 | super(props);
27 |
28 | this.state = {
29 | activeData: getActiveData(),
30 | };
31 | }
32 |
33 | public componentDidMount() {
34 | this.timer = setInterval(() => {
35 | this.setState({
36 | activeData: getActiveData(),
37 | });
38 | }, 1000);
39 | }
40 |
41 | public componentWillUnmount() {
42 | clearInterval(this.timer);
43 | }
44 |
45 | public render() {
46 | const { activeData = [] } = this.state;
47 |
48 | return (
49 |
50 |
51 |
52 |
70 |
71 | {activeData && (
72 |
73 |
{[...activeData].sort()[activeData.length - 1].y + 200} 亿元
74 |
{[...activeData].sort()[Math.floor(activeData.length / 2)].y} 亿元
75 |
76 | )}
77 | {activeData && (
78 |
79 | 00:00
80 | {activeData[Math.floor(activeData.length / 2)].x}
81 | {activeData[activeData.length - 1].x}
82 |
83 | )}
84 |
85 | );
86 | }
87 | }
88 |
89 | export default ActiveChart;
90 |
--------------------------------------------------------------------------------
/src/components/AvatarList/index.less:
--------------------------------------------------------------------------------
1 | .avatarList {
2 | display: inline-block;
3 | ul {
4 | display: inline-block;
5 | margin-left: 8px;
6 | font-size: 0;
7 | }
8 | }
9 |
10 | .avatarItem {
11 | display: inline-block;
12 | font-size: 14px;
13 | margin-left: -8px;
14 | width: 32px;
15 | height: 32px;
16 | .ant-avatar {
17 | border: 1px solid #fff;
18 | }
19 | }
20 |
21 | .avatarItemLarge {
22 | width: 24px;
23 | height: 24px;
24 | }
25 |
26 | .avatarItemSmall {
27 | width: 20px;
28 | height: 20px;
29 | }
30 |
31 | .avatarItemMini {
32 | width: 20px;
33 | height: 20px;
34 | .ant-avatar {
35 | width: 20px;
36 | height: 20px;
37 | line-height: 20px;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/AvatarList/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Tooltip, Avatar } from 'antd';
3 |
4 | import './index.less';
5 | class AvatarList extends React.Component<{size?: 'large' | 'small' | 'mini' | 'default', style?: React.CSSProperties;}> {
6 | public static Item: typeof Item;
7 | public render() {
8 | const { children, size, ...other } = this.props;
9 |
10 | const childrenWithProps = React.Children.map(children, (child:React.ReactElement<{size?}>)=>
11 | React.cloneElement(child, {
12 | size,
13 | })
14 | );
15 | return(
16 |
19 | )
20 | }
21 | }
22 |
23 | const Item = (props:{src, size?, tips, onClick?}) => {
24 | const { src, size, tips, onClick } = props;
25 | const cls = `avatarItem ${size === 'large'? 'avatarItemLarge': ''} ${size === 'small'? 'avatarItemSmall': ''} ${size === 'mini'? 'avatarItemMini': ''}`;
26 | return (
27 |
28 | {tips ? (
29 |
30 |
31 |
32 | ) : (
33 |
34 | )}
35 |
36 | );
37 | };
38 |
39 | AvatarList.Item = Item;
40 |
41 | export default AvatarList;
42 |
--------------------------------------------------------------------------------
/src/components/Charts/Bar/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React 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 '../index.less';
7 |
8 |
9 | export interface IBarProps {
10 | title?: React.ReactNode;
11 | color?: string;
12 | padding?: any;
13 | height?: number;
14 | data?: Array<{
15 | x: string;
16 | y: number;
17 | }>;
18 | autoLabel?: boolean;
19 | style?: React.CSSProperties;
20 | forceFit?: boolean
21 | }
22 | @autoHeight()
23 | class Bar extends React.Component {
24 | public node;
25 | public root;
26 |
27 | constructor(props) {
28 | super(props);
29 | this.state = {
30 | autoHideXLabels: false,
31 | }
32 | }
33 |
34 | public componentDidMount() {
35 | window.addEventListener('resize', this.resize);
36 | }
37 |
38 | public componentWillUnmount() {
39 | window.removeEventListener('resize', this.resize);
40 | }
41 |
42 | @Bind()
43 | @Debounce(400)
44 | public resize() {
45 | if (!this.node) {
46 | return;
47 | }
48 | const canvasWidth = this.node.parentNode.clientWidth;
49 | const { data = [], autoLabel = true } = this.props;
50 | if (!autoLabel) {
51 | return;
52 | }
53 | const minWidth = data.length * 30;
54 | const { autoHideXLabels } = this.state;
55 |
56 | if (canvasWidth <= minWidth) {
57 | if (!autoHideXLabels) {
58 | this.setState({
59 | autoHideXLabels: true,
60 | });
61 | }
62 | } else if (autoHideXLabels) {
63 | this.setState({
64 | autoHideXLabels: false,
65 | });
66 | }
67 | }
68 |
69 | public handleRoot = n => {
70 | this.root = n;
71 | };
72 |
73 | public handleRef = n => {
74 | this.node = n;
75 | };
76 |
77 | public render() {
78 | const {
79 | height,
80 | title,
81 | forceFit = true,
82 | data,
83 | color = 'rgba(24, 144, 255, 0.85)',
84 | padding,
85 | } = this.props;
86 |
87 | const { autoHideXLabels } = this.state;
88 |
89 | const scale = {
90 | x: {
91 | type: 'cat',
92 | },
93 | y: {
94 | min: 0,
95 | },
96 | };
97 |
98 | const tooltip:[string, (x?: any, y?: any) => { name?: string; value: string; }] = [
99 | 'x*y',
100 | (x, y) => ({
101 | name: x,
102 | value: y,
103 | }),
104 | ];
105 |
106 | return (
107 |
108 |
109 | {title &&
{title}
}
110 | {data ?
117 |
123 |
127 |
131 |
137 | : '' }
138 |
139 |
140 | );
141 | }
142 | }
143 |
144 | export default Bar;
145 |
--------------------------------------------------------------------------------
/src/components/Charts/ChartCard/index.less:
--------------------------------------------------------------------------------
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 .ant-spin-container {
77 | overflow: visible;
78 | }
79 |
--------------------------------------------------------------------------------
/src/components/Charts/ChartCard/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Card, Spin } from 'antd';
3 | import './index.less';
4 |
5 | export interface IChartCardProps {
6 | title: React.ReactNode;
7 | action?: React.ReactNode;
8 | total?: React.ReactNode | number | (() => React.ReactNode | number);
9 | footer?: React.ReactNode;
10 | contentHeight?: number;
11 | avatar?: React.ReactNode;
12 | style?: React.CSSProperties;
13 | loading?: boolean,
14 | bordered?: boolean,
15 | }
16 |
17 | const renderTotal = total => {
18 | let totalDom;
19 | switch (typeof total) {
20 | case 'undefined':
21 | totalDom = null;
22 | break;
23 | case 'function':
24 | totalDom = {total()}
;
25 | break;
26 | default:
27 | totalDom = {total}
;
28 | }
29 | return totalDom;
30 | };
31 |
32 | class ChartCard extends React.Component {
33 | public render() {
34 | const {
35 | loading = false,
36 | contentHeight,
37 | title,
38 | avatar,
39 | action,
40 | total,
41 | footer,
42 | children,
43 | ...rest
44 | } = this.props;
45 | const content = (
46 |
47 |
50 |
{avatar}
51 |
52 |
53 | {title}
54 | {action}
55 |
56 | {renderTotal(total)}
57 |
58 |
59 | {children && (
60 |
63 | )}
64 | {footer && (
65 |
68 | {footer}
69 |
70 | )}
71 |
72 | );
73 | return (
74 |
75 | {
76 |
77 | {content}
78 |
79 | }
80 |
81 | );
82 | }
83 | }
84 | // const ChartCard = () => {
85 |
86 | export default ChartCard;
87 |
--------------------------------------------------------------------------------
/src/components/Charts/Field/index.less:
--------------------------------------------------------------------------------
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 | }
17 |
--------------------------------------------------------------------------------
/src/components/Charts/Field/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import './index.less';
4 |
5 | const Field = ({ label, value, ...rest }) => (
6 |
7 | {label}
8 | {value}
9 |
10 | );
11 |
12 | export default Field;
13 |
--------------------------------------------------------------------------------
/src/components/Charts/MiniBar/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Chart, Tooltip, Geom } from 'bizcharts';
3 | import autoHeight from '../autoHeight';
4 | import '../index.less';
5 |
6 | export interface IMiniBarProps {
7 | color?: string;
8 | height?: number;
9 | data?: Array<{
10 | x: number | string;
11 | y: number;
12 | }>;
13 | style?: React.CSSProperties;
14 | forceFit?:boolean,
15 | }
16 |
17 | @autoHeight()
18 | export default class MiniBar extends React.Component {
19 | public render() {
20 | const { height, forceFit = true, color = '#1890FF', data } = this.props;
21 | // console.debug(data);
22 | const scale = {
23 | x: {
24 | type: 'cat',
25 | },
26 | y: {
27 | min: 0,
28 | },
29 | };
30 |
31 | const padding:any = [36, 5, 30, 5];
32 |
33 | const tooltip:[string, (x?: any, y?: any) => { name?: string; value: string; }] = [
34 | 'x*y',
35 | (x, y) => ({
36 | name: x,
37 | value: y,
38 | }),
39 | ];
40 |
41 | // for tooltip not to be hide
42 | const chartHeight = height + 54;
43 |
44 | return (
45 |
46 |
47 |
54 |
58 |
64 |
65 |
66 |
67 | );
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/components/Charts/MiniProgress/index.less:
--------------------------------------------------------------------------------
1 | .miniProgress {
2 | padding: 5px 0;
3 | position: relative;
4 | width: 100%;
5 | .progressWrap {
6 | background-color: #f5f5f5;
7 | position: relative;
8 | }
9 | .progress {
10 | transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s;
11 | border-radius: 1px 0 0 1px;
12 | background-color: #1890ff;
13 | width: 0;
14 | height: 100%;
15 | }
16 | .target {
17 | position: absolute;
18 | top: 0;
19 | bottom: 0;
20 | span {
21 | border-radius: 100px;
22 | position: absolute;
23 | top: 0;
24 | left: 0;
25 | height: 4px;
26 | width: 2px;
27 | }
28 | span:last-child {
29 | top: auto;
30 | bottom: 0;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/Charts/MiniProgress/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Tooltip } from 'antd';
3 | import './index.less';
4 |
5 | export interface IMiniProgressProps {
6 | target: number,
7 | color?: string,
8 | strokeWidth?: number,
9 | percent?: number,
10 | style?: React.CSSProperties,
11 | }
12 |
13 | class MiniProgress extends React.Component {
14 | public render() {
15 | const { target, color, percent, strokeWidth } = this.props;
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
34 |
35 | )
36 | }
37 | }
38 |
39 | export default MiniProgress;
40 |
--------------------------------------------------------------------------------
/src/components/Charts/Pie/index.less:
--------------------------------------------------------------------------------
1 | .pie {
2 | position: relative;
3 | .chart {
4 | position: relative;
5 | }
6 | &.hasLegend .chart {
7 | width: ~'calc(100% - 240px)';
8 | }
9 | .legend {
10 | position: absolute;
11 | right: 0;
12 | min-width: 200px;
13 | top: 50%;
14 | transform: translateY(-50%);
15 | margin: 0 20px;
16 | list-style: none;
17 | padding: 0;
18 | li {
19 | cursor: pointer;
20 | margin-bottom: 16px;
21 | height: 22px;
22 | line-height: 22px;
23 | &:last-child {
24 | margin-bottom: 0;
25 | }
26 | }
27 | }
28 | .dot {
29 | border-radius: 8px;
30 | display: inline-block;
31 | margin-right: 8px;
32 | position: relative;
33 | top: -1px;
34 | height: 8px;
35 | width: 8px;
36 | }
37 | .line {
38 | background-color: rgba(0,0,0,.65);
39 | display: inline-block;
40 | margin-right: 8px;
41 | width: 1px;
42 | height: 16px;
43 | }
44 | .legendTitle {
45 | color: rgba(0,0,0,.65);
46 | }
47 | .percent {
48 | color: rgba(0,0,0,.45);
49 | }
50 | .value {
51 | position: absolute;
52 | right: 0;
53 | }
54 | .title {
55 | margin-bottom: 8px;
56 | }
57 | .total {
58 | position: absolute;
59 | left: 50%;
60 | top: 50%;
61 | text-align: center;
62 | height: 62px;
63 | transform: translate(-50%, -50%);
64 | & > h4 {
65 | color: rgba(0,0,0,.45);
66 | font-size: 14px;
67 | line-height: 22px;
68 | height: 22px;
69 | margin-bottom: 8px;
70 | font-weight: normal;
71 | }
72 | & > p {
73 | color: rgba(0,0,0,.45);
74 | display: block;
75 | font-size: 1.2em;
76 | height: 32px;
77 | line-height: 32px;
78 | white-space: nowrap;
79 | }
80 | }
81 | }
82 |
83 | .legendBlock {
84 | &.hasLegend .chart {
85 | width: 100%;
86 | margin: 0 0 32px 0;
87 | }
88 | .legend {
89 | position: relative;
90 | transform: none;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/components/Charts/Radar/index.less:
--------------------------------------------------------------------------------
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 | }
47 |
--------------------------------------------------------------------------------
/src/components/Charts/TagCloud/index.less:
--------------------------------------------------------------------------------
1 | .tagCloud {
2 | overflow: hidden;
3 | canvas {
4 | transform: scale(0.25);
5 | transform-origin: 0 0;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/Charts/TimelineChart/index.less:
--------------------------------------------------------------------------------
1 | .timelineChart {
2 | background: #fff;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/Charts/TimelineChart/index.tsx:
--------------------------------------------------------------------------------
1 | import * as 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 './index.less';
7 |
8 | export interface ITimelineChartProps {
9 | data?: Array<{
10 | x: number,
11 | y1: number,
12 | y2: number,
13 | }>,
14 | titleMap?: { y1: string; y2: string },
15 | padding?: any,
16 | height?: number,
17 | style?: React.CSSProperties,
18 | title?: string,
19 | borderWidth?: number,
20 | }
21 | @autoHeight()
22 | export default class TimelineChart extends React.Component {
23 | public render() {
24 | const {
25 | title,
26 | height = 400,
27 | padding = [60, 20, 40, 40],
28 | titleMap = {
29 | y1: 'y1',
30 | y2: 'y2',
31 | },
32 | borderWidth = 2,
33 | data,
34 | } = this.props;
35 |
36 | data.sort((a, b) => a.x - b.x);
37 |
38 | let max;
39 | if (data[0] && data[0].y1 && data[0].y2) {
40 | max = Math.max(
41 | [...data].sort((a, b) => b.y1 - a.y1)[0].y1,
42 | [...data].sort((a, b) => b.y2 - a.y2)[0].y2
43 | );
44 | }
45 |
46 | const ds = new DataSet({
47 | state: {
48 | start: data[0].x,
49 | end: data[data.length - 1].x,
50 | },
51 | });
52 |
53 | const dv = ds.createView();
54 | dv
55 | .source(data)
56 | .transform({
57 | type: 'filter',
58 | callback: obj => {
59 | const date = obj.x;
60 | return date <= ds.state.end && date >= ds.state.start;
61 | },
62 | })
63 | .transform({
64 | type: 'map',
65 | callback(row) {
66 | const newRow = { ...row };
67 | newRow[titleMap.y1] = row.y1;
68 | newRow[titleMap.y2] = row.y2;
69 | return newRow;
70 | },
71 | })
72 | .transform({
73 | type: 'fold',
74 | fields: [titleMap.y1, titleMap.y2], // 展开字段集
75 | key: 'key', // key字段
76 | value: 'value', // value字段
77 | });
78 |
79 | const timeScale = {
80 | type: 'time',
81 | tickInterval: 60 * 60 * 1000,
82 | mask: 'HH:mm',
83 | range: [0, 1],
84 | };
85 |
86 | const cols = {
87 | x: timeScale,
88 | value: {
89 | max,
90 | min: 0,
91 | },
92 | };
93 |
94 | const SliderGen = () => (
95 | {
107 | ds.setState('start', startValue);
108 | ds.setState('end', endValue);
109 | }}
110 | />
111 | );
112 |
113 | return (
114 |
115 |
116 | {title &&
{title}
}
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 | );
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/components/Charts/WaterWave/index.less:
--------------------------------------------------------------------------------
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 | }
29 |
--------------------------------------------------------------------------------
/src/components/Charts/autoHeight.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | function computeHeight(node) {
4 | const totalHeight = parseInt(getComputedStyle(node).height, 10);
5 | const padding =
6 | parseInt(getComputedStyle(node).paddingTop, 10) +
7 | parseInt(getComputedStyle(node).paddingBottom, 10);
8 | return totalHeight - padding;
9 | }
10 |
11 | function getAutoHeight(n) {
12 | if (!n) {
13 | return 0;
14 | }
15 |
16 | let node = n;
17 |
18 | let height = computeHeight(node);
19 |
20 | while (!height) {
21 | node = node.parentNode;
22 | if (node) {
23 | height = computeHeight(node);
24 | } else {
25 | break;
26 | }
27 | }
28 |
29 | return height;
30 | }
31 |
32 | const autoHeight = () => WrappedComponent => {
33 | return class extends React.Component<{height?}, {computedHeight?}> {
34 | public root;
35 | public timer;
36 | public node;
37 | public resize;
38 | public renderChart;
39 | public handleRef;
40 | public chart;
41 | public getG2Instance;
42 | public getLegendData;
43 | public handleLegendClick;
44 | public getLengendData;
45 | public isUnmount;
46 | public imageMask;
47 | public saveRootRef;
48 | public initTagCloud;
49 | constructor(props) {
50 | super(props);
51 | this.state={
52 | computedHeight: 0,
53 | }
54 | }
55 | public componentWillUnmount() {
56 | // qqq
57 | }
58 | public componentDidMount() {
59 | const { height } = this.props;
60 | if (!height) {
61 | const h = getAutoHeight(this.root);
62 | this.setState({ computedHeight: h });
63 | }
64 | }
65 |
66 | public componentWillReceiveProps() {
67 | // because of charts data create when rendered
68 | // so there is a trick for get rendered time
69 | }
70 |
71 | public handleRoot = node => {
72 | this.root = node;
73 | };
74 |
75 | public render() {
76 | const { height } = this.props;
77 | const { computedHeight } = this.state;
78 | const h = height || computedHeight;
79 | return (
80 | {h > 0 && }
81 | );
82 | }
83 | };
84 | };
85 |
86 | export default autoHeight;
87 |
--------------------------------------------------------------------------------
/src/components/Charts/g2.ts:
--------------------------------------------------------------------------------
1 | // 全局 G2 设置
2 | import { G2 } from 'bizcharts';
3 |
4 | G2.track(false);
5 |
6 | const config = {
7 | defaultColor: '#1089ff',
8 | shape: {
9 | interval: {
10 | fillOpacity: 1,
11 | },
12 | },
13 | };
14 |
15 | G2.Global.setTheme('default');
16 |
--------------------------------------------------------------------------------
/src/components/Charts/index.less:
--------------------------------------------------------------------------------
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 | }
20 |
--------------------------------------------------------------------------------
/src/components/Charts/index.ts:
--------------------------------------------------------------------------------
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 => `¥ ${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 | };
50 |
--------------------------------------------------------------------------------
/src/components/CountDown/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 |
4 | export interface ICountDownProps {
5 | format?: (time: number) => void;
6 | target: Date | number;
7 | onEnd?: () => void;
8 | style?: React.CSSProperties;
9 | }
10 |
11 |
12 | function fixedZero(val) {
13 | return val * 1 < 10 ? `0${val}` : val;
14 | }
15 |
16 | class CountDown extends React.Component {
17 | constructor(props) {
18 | super(props);
19 |
20 | const { lastTime } = this.initTime(props);
21 |
22 | this.state = {
23 | lastTime,
24 | };
25 | }
26 |
27 | public componentDidMount() {
28 | this.tick();
29 | }
30 |
31 | public componentWillReceiveProps(nextProps) {
32 | if (this.props.target !== nextProps.target) {
33 | clearTimeout(this.timer);
34 | const { lastTime } = this.initTime(nextProps);
35 | this.setState(
36 | {
37 | lastTime,
38 | },
39 | () => {
40 | this.tick();
41 | }
42 | );
43 | }
44 | }
45 |
46 | public componentWillUnmount() {
47 | clearTimeout(this.timer);
48 | }
49 |
50 | private timer:any = 0;
51 | private interval = 1000;
52 | private initTime = props => {
53 | let lastTime = 0;
54 | let targetTime = 0;
55 | try {
56 | if (Object.prototype.toString.call(props.target) === '[object Date]') {
57 | targetTime = props.target.getTime();
58 | } else {
59 | targetTime = new Date(props.target).getTime();
60 | }
61 | } catch (e) {
62 | throw new Error('invalid target prop');
63 | }
64 |
65 | lastTime = targetTime - new Date().getTime();
66 | return {
67 | lastTime: lastTime < 0 ? 0 : lastTime,
68 | };
69 | };
70 | // defaultFormat = time => (
71 | // {moment(time).format('hh:mm:ss')}
72 | // );
73 | private defaultFormat = time => {
74 | const hours = 60 * 60 * 1000;
75 | const minutes = 60 * 1000;
76 |
77 | const h = Math.floor(time / hours);
78 | const m = Math.floor((time - h * hours) / minutes);
79 | const s = Math.floor((time - h * hours - m * minutes) / 1000);
80 | return (
81 |
82 | {fixedZero(h)}:{fixedZero(m)}:{fixedZero(s)}
83 |
84 | );
85 | };
86 | private tick = () => {
87 | const { onEnd } = this.props;
88 | let { lastTime } = this.state;
89 |
90 | this.timer = setTimeout(() => {
91 | if (lastTime < this.interval) {
92 | clearTimeout(this.timer);
93 | this.setState( { lastTime: 0 }, () => { if (onEnd) { onEnd()} });
94 | } else {
95 | lastTime -= this.interval;
96 | this.setState({ lastTime }, () => { this.tick() } );
97 | }
98 | }, this.interval);
99 | };
100 |
101 | public render() {
102 | const { format, onEnd, ...rest } = this.props;
103 | const { lastTime } = this.state;
104 | let result;
105 | if (format) {
106 | result = format(lastTime);
107 | } else {
108 | result = this.defaultFormat(lastTime);
109 | }
110 |
111 | return {result};
112 | }
113 | }
114 |
115 | export default CountDown;
116 |
--------------------------------------------------------------------------------
/src/components/DescriptionList/Description.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Col } from 'antd';
3 | import './index.less';
4 | import responsive from './responsive';
5 |
6 | const Description = (props:{term?, column?, className?, children}) => {
7 | const { term, column, className, children, ...restProps } = props;
8 | return (
9 |
10 | {term && {term}
}
11 | {children && {children}
}
12 |
13 | );
14 | };
15 |
16 | // Description.defaultProps = {
17 | // term: '',
18 | // };
19 |
20 | // Description.propTypes = {
21 | // term: PropTypes.node,
22 | // };
23 |
24 | export default Description;
25 |
--------------------------------------------------------------------------------
/src/components/DescriptionList/DescriptionList.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Row } from 'antd';
3 | import './index.less';
4 | import Description from './Description';
5 |
6 | export interface IDescriptionListProps {
7 | className?: string,
8 | layout?: 'horizontal' | 'vertical';
9 | col?: number | string;
10 | title?: React.ReactNode;
11 | gutter?: number;
12 | size?: 'large' | 'small';
13 | style?: React.CSSProperties;
14 | }
15 |
16 | class DescriptionList extends React.Component {
17 | public static Description: typeof Description;
18 | public render() {
19 | const { className, title, col = 3, layout = 'horizontal', gutter = 32, children, size, ...restProps } = this.props;
20 | const clsString = `descriptionList ${layout} ${className} ${size === 'small'? 'small':''} ${size === 'large'? 'large':''}`;
21 | const column = col > 4 ? 4 : col;
22 | return(
23 |
24 | {title ?
{title}
: null}
25 |
26 | {React.Children.map(children, (child:React.ReactElement<{column?}>) => child ? React.cloneElement(child, { column }) : child)}
27 |
28 |
29 | )
30 | }
31 | }
32 |
33 | export default DescriptionList;
34 |
--------------------------------------------------------------------------------
/src/components/DescriptionList/index.less:
--------------------------------------------------------------------------------
1 | .descriptionList {
2 | .ant-row {
3 | margin-bottom: -16px;
4 | overflow: hidden;
5 | }
6 |
7 | .title {
8 | font-size: 14px;
9 | color: rgba(0, 0, 0, 0.85);
10 | font-weight: 500;
11 | margin-bottom: 16px;
12 | }
13 |
14 | .term {
15 | line-height: 20px;
16 | padding-bottom: 16px;
17 | margin-right: 8px;
18 | color: rgba(0, 0, 0, 0.85);
19 | white-space: nowrap;
20 | display: table-cell;
21 |
22 | &:after {
23 | content: ':';
24 | margin: 0 8px 0 2px;
25 | position: relative;
26 | top: -0.5px;
27 | }
28 | }
29 |
30 | .detail {
31 | line-height: 22px;
32 | width: 100%;
33 | padding-bottom: 16px;
34 | color: rgba(0, 0, 0, 0.65);
35 | display: table-cell;
36 | }
37 |
38 | &.small {
39 | // offset the padding-bottom of last row
40 | .ant-row {
41 | margin-bottom: -8px;
42 | }
43 | .title {
44 | margin-bottom: 12px;
45 | color: rgba(0, 0, 0, 0.65);
46 | }
47 | .term,
48 | .detail {
49 | padding-bottom: 8px;
50 | }
51 | }
52 |
53 | &.large {
54 | .title {
55 | font-size: 16px;
56 | }
57 | }
58 |
59 | &.vertical {
60 | .term {
61 | padding-bottom: 8px;
62 | display: block;
63 | }
64 |
65 | .detail {
66 | display: block;
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/components/DescriptionList/index.ts:
--------------------------------------------------------------------------------
1 | import DescriptionList from './DescriptionList';
2 | import Description from './Description';
3 |
4 | DescriptionList.Description = Description;
5 | export default DescriptionList;
6 |
--------------------------------------------------------------------------------
/src/components/DescriptionList/responsive.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 1: { xs: 24 },
3 | 2: { xs: 24, sm: 12 },
4 | 3: { xs: 24, sm: 12, md: 8 },
5 | 4: { xs: 24, sm: 12, md: 6 },
6 | };
7 |
--------------------------------------------------------------------------------
/src/components/EditableLinkGroup/index.less:
--------------------------------------------------------------------------------
1 | .linkGroup {
2 | padding: 20px 0 8px 24px;
3 | font-size: 0;
4 | & > a {
5 | color: rgba(0,0,0,.65);
6 | display: inline-block;
7 | font-size: 14px;
8 | margin-bottom: 13px;
9 | width: 25%;
10 | &:hover {
11 | color: #1890ff;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/EditableLinkGroup/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Button } from 'antd';
3 | import './index.less';
4 |
5 |
6 | export interface IEditableLinkGroup {
7 | links?: any,
8 | onAdd: ()=>void,
9 | linkElement?: string,
10 | }
11 | // TODO: 添加逻辑
12 |
13 | class EditableLinkGroup extends React.PureComponent {
14 |
15 | // static propTypes = {
16 | // links: PropTypes.array,
17 | // onAdd: PropTypes.func,
18 | // linkElement: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
19 | // };
20 |
21 | public render() {
22 | const { links, linkElement='a', onAdd } = this.props;
23 | return (
24 |
25 | {links.map(link =>
26 | React.createElement(
27 | linkElement,
28 | {
29 | key: `linkGroup-item-${link.id || link.title}`,
30 | to: link.href,
31 | href: link.href,
32 | },
33 | link.title
34 | )
35 | )}
36 | {
37 |
40 | }
41 |
42 | );
43 | }
44 | }
45 |
46 | export default EditableLinkGroup;
47 |
--------------------------------------------------------------------------------
/src/components/Ellipsis/index.less:
--------------------------------------------------------------------------------
1 | .ellipsis {
2 | overflow: hidden;
3 | display: inline-block;
4 | word-break: break-all;
5 | width: 100%;
6 | }
7 |
8 | .lines {
9 | position: relative;
10 | .shadow {
11 | display: block;
12 | position: relative;
13 | color: transparent;
14 | opacity: 0;
15 | z-index: -999;
16 | }
17 | }
18 |
19 | .lineClamp {
20 | position: relative;
21 | overflow: hidden;
22 | text-overflow: ellipsis;
23 | display: -webkit-box;
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/Exception/index.less:
--------------------------------------------------------------------------------
1 | @import '~antd/lib/style/themes/default.less';
2 |
3 | .exception {
4 | display: flex;
5 | align-items: center;
6 | height: 100%;
7 |
8 | .imgBlock {
9 | flex: 0 0 62.5%;
10 | width: 62.5%;
11 | padding-right: 152px;
12 | zoom: 1;
13 | &:before,
14 | &:after {
15 | content: ' ';
16 | display: table;
17 | }
18 | &:after {
19 | clear: both;
20 | visibility: hidden;
21 | font-size: 0;
22 | height: 0;
23 | }
24 | }
25 |
26 | .imgEle {
27 | height: 360px;
28 | width: 100%;
29 | max-width: 430px;
30 | float: right;
31 | background-repeat: no-repeat;
32 | background-position: 50% 50%;
33 | background-size: contain;
34 | }
35 |
36 | .content {
37 | flex: auto;
38 |
39 | h1 {
40 | color: #434e59;
41 | font-size: 72px;
42 | font-weight: 600;
43 | line-height: 72px;
44 | margin-bottom: 24px;
45 | }
46 |
47 | .desc {
48 | color: @text-color-secondary;
49 | font-size: 20px;
50 | line-height: 28px;
51 | margin-bottom: 16px;
52 | }
53 |
54 | .actions {
55 | button:not(:last-child) {
56 | margin-right: 8px;
57 | }
58 | }
59 | }
60 | }
61 |
62 | @media screen and (max-width: @screen-xl) {
63 | .exception {
64 | .imgBlock {
65 | padding-right: 88px;
66 | }
67 | }
68 | }
69 |
70 | @media screen and (max-width: @screen-sm) {
71 | .exception {
72 | display: block;
73 | text-align: center;
74 | .imgBlock {
75 | padding-right: 0;
76 | margin: 0 auto 24px;
77 | }
78 | }
79 | }
80 |
81 | @media screen and (max-width: @screen-xs) {
82 | .exception {
83 | .imgBlock {
84 | margin-bottom: -24px;
85 | overflow: hidden;
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/components/Exception/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Button } from 'antd';
3 | import config from './typeConfig';
4 | import './index.less';
5 |
6 | export interface IExceptionProps{
7 | className?: string,
8 | linkElement?: any,
9 | type?: '403' | '404' | '500';
10 | title?: React.ReactNode;
11 | desc?: React.ReactNode;
12 | img?: string,
13 | actions?: React.ReactNode;
14 | rest?: object,
15 | style?: React.CSSProperties;
16 | }
17 |
18 | class Exception extends React.Component {
19 | public render() {
20 | const { className, linkElement = 'a', type, title, desc, img, actions, ...rest } = this.props;
21 | const pageType = type in config ? type : '404';
22 | return (
23 |
24 |
30 |
31 |
{title || config[pageType].title}
32 |
{desc || config[pageType].desc}
33 |
34 | {actions ||
35 | React.createElement(
36 | linkElement,
37 | {
38 | to: '/',
39 | href: '/',
40 | },
41 |
42 | )}
43 |
44 |
45 |
46 | );
47 | }
48 | }
49 |
50 | export default Exception;
51 |
--------------------------------------------------------------------------------
/src/components/Exception/typeConfig.ts:
--------------------------------------------------------------------------------
1 | const config = {
2 | 403: {
3 | img: 'https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg',
4 | title: '403',
5 | desc: '抱歉,你无权访问该页面',
6 | },
7 | 404: {
8 | img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg',
9 | title: '404',
10 | desc: '抱歉,你访问的页面不存在',
11 | },
12 | 500: {
13 | img: 'https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg',
14 | title: '500',
15 | desc: '抱歉,服务器出错了',
16 | },
17 | };
18 |
19 | export default config;
20 |
--------------------------------------------------------------------------------
/src/components/FooterToolbar/index.less:
--------------------------------------------------------------------------------
1 | .toolbar {
2 | position: fixed;
3 | width: 100%;
4 | bottom: 0;
5 | right: 0;
6 | height: 56px;
7 | line-height: 56px;
8 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.03);
9 | background: #fff;
10 | border-top: 1px solid #d9d9d9;
11 | padding: 0 24px;
12 | z-index: 9;
13 |
14 | &:after {
15 | content: '';
16 | display: block;
17 | clear: both;
18 | }
19 |
20 | .left {
21 | float: left;
22 | }
23 |
24 | .right {
25 | float: right;
26 | }
27 |
28 | button + button {
29 | margin-left: 8px;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/FooterToolbar/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import './index.less';
3 |
4 | export default class FooterToolbar extends React.Component<{className?:string, extra?:React.ReactNode, style?: React.CSSProperties;}> {
5 | public render() {
6 | const { children, className, extra, ...restProps } = this.props;
7 | return (
8 |
9 |
{extra}
10 |
{children}
11 |
12 | );
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/GlobalFooter/index.less:
--------------------------------------------------------------------------------
1 | .LoginContent {
2 | .globalFooter {
3 | padding: 0 16px;
4 | margin: 48px 0 24px 0;
5 | text-align: center;
6 |
7 | .links {
8 | margin-bottom: 8px;
9 |
10 | a {
11 | color: rgba(0, 0, 0, 0.45);
12 | transition: all 0.3s;
13 |
14 | &:not(:last-child) {
15 | margin-right: 40px;
16 | }
17 |
18 | &:hover {
19 | color: rgba(0, 0, 0, 0.65);
20 | }
21 | }
22 | }
23 |
24 | .copyright {
25 | color: rgba(0, 0, 0, 0.45);
26 | font-size: 14px;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/GlobalFooter/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import './index.less';
3 |
4 | const GlobalFooter = (props:{className?, links, copyright}) => {
5 | const { className, links, copyright } = props;
6 | return (
7 |
8 | {links && (
9 |
16 | )}
17 | {copyright &&
{copyright}
}
18 |
19 | );
20 | };
21 |
22 | export default GlobalFooter;
23 |
--------------------------------------------------------------------------------
/src/components/HeaderSearch/index.less:
--------------------------------------------------------------------------------
1 | .headerSearch {
2 | .anticon-search {
3 | cursor: pointer;
4 | font-size: 16px;
5 | vertical-align: middle;
6 | margin: 0 10px;
7 | }
8 | .input {
9 | transition: width 0.3s, margin-left 0.3s;
10 | width: 0;
11 | background: transparent;
12 | border-radius: 0;
13 | .ant-select-selection {
14 | background: transparent;
15 | }
16 | input {
17 | border: 0;
18 | padding-left: 0;
19 | padding-right: 0;
20 | box-shadow: none !important;
21 | }
22 | &,
23 | &:hover,
24 | &:focus {
25 | border-bottom: 1px solid #d9d9d9;
26 | }
27 | &.show {
28 | width: 210px;
29 | margin-left: 8px;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/HeaderSearch/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Input, Icon, AutoComplete } from 'antd';
3 | import './index.less';
4 |
5 | export interface IHeaderSearchProps {
6 | defaultOpen: boolean,
7 | onPressEnter: (value:string)=>void,
8 | onChange?: ()=>void,
9 | placeholder: string,
10 | dataSource?: string[],
11 | onSearch?: (value: string) => void,
12 | style?: React.CSSProperties,
13 | className?: string,
14 | }
15 |
16 | class HeaderSearch extends React.PureComponent {
17 | // static defaultProps = {
18 | // defaultActiveFirstOption: false,
19 | // onPressEnter: () => { },
20 | // onSearch: () => { },
21 | // className: '',
22 | // placeholder: '',
23 | // dataSource: [],
24 | // defaultOpen: false,
25 | // };
26 | // static propTypes = {
27 | // className: PropTypes.string,
28 | // placeholder: PropTypes.string,
29 | // onSearch: PropTypes.func,
30 | // onPressEnter: PropTypes.func,
31 | // defaultActiveFirstOption: PropTypes.bool,
32 | // dataSource: PropTypes.array,
33 | // defaultOpen: PropTypes.bool,
34 | // };
35 | private timeout;
36 | private input;
37 | constructor(props) {
38 | super(props);
39 | this.state = {
40 | searchMode: this.props.defaultOpen,
41 | value: '',
42 | }
43 | }
44 | public componentWillUnmount() {
45 | clearTimeout(this.timeout);
46 | }
47 | private onKeyDown = e => {
48 | if (e.key === 'Enter') {
49 | this.timeout = setTimeout(() => {
50 | this.props.onPressEnter(this.state.value); // Fix duplicate onPressEnter
51 | }, 0);
52 | }
53 | };
54 | private onChange = value => {
55 | this.setState({ value });
56 | if (this.props.onChange) {
57 | this.props.onChange();
58 | }
59 | };
60 | private enterSearchMode = () => {
61 | this.setState({ searchMode: true }, () => {
62 | if (this.state.searchMode) {
63 | this.input.focus();
64 | }
65 | });
66 | };
67 | private leaveSearchMode = () => {
68 | this.setState({
69 | searchMode: false,
70 | value: '',
71 | });
72 | };
73 | public render() {
74 | const { placeholder, ...restProps } = this.props;
75 | // delete restProps.defaultOpen; // for rc-select not affected
76 | return (
77 |
78 |
79 |
86 | {
89 | this.input = node;
90 | }}
91 | onKeyDown={this.onKeyDown}
92 | onBlur={this.leaveSearchMode}
93 | />
94 |
95 |
96 | );
97 | }
98 | }
99 |
100 | export default HeaderSearch;
101 |
--------------------------------------------------------------------------------
/src/components/MyFooter/index.less:
--------------------------------------------------------------------------------
1 | .globalFooter {
2 | padding: 0 16px;
3 | margin: 48px 0 24px 0;
4 | text-align: center;
5 |
6 | .links {
7 | margin-bottom: 8px;
8 | a {
9 | color: rgba(0,0,0,.45);
10 | transition: all 0.3s;
11 |
12 | &:not(:last-child) {
13 | margin-right: 40px;
14 | }
15 |
16 | &:hover {
17 | color: rgba(0,0,0,.65);
18 | }
19 | }
20 | }
21 |
22 | .copyright {
23 | color: rgba(0,0,0,.45);
24 | font-size: 14px;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/MyFooter/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Icon } from 'antd';
3 | import './index.less';
4 |
5 | /**
6 | * @class MyMenu
7 | */
8 | class Footer extends React.Component<{}, {}> {
9 | private links: Array