├── .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 | logo 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 |
17 | 18 |
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 |
    61 |
    {children}
    62 |
    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 |
    25 |
    33 |
    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 |
    25 |
    29 |
    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 |
    10 | {links.map(link => ( 11 | 12 | {link.title} 13 | 14 | ))} 15 |
    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 10 | /** 11 | * @param {*} props 12 | * @memberof Footer 13 | */ 14 | constructor(props) { 15 | super(props); 16 | this.links = [ 17 | { 18 | key: '大佬们', 19 | title: '大佬们', 20 | href: 'https://github.com/Jackyzm/react-app-ts', 21 | }, 22 | { 23 | key: 'github', 24 | title: , 25 | href: 'https://github.com/Jackyzm/react-app-ts', 26 | }, 27 | { 28 | key: '点个星吧', 29 | title: '点个星吧', 30 | href: 'https://github.com/Jackyzm/react-app-ts', 31 | }, 32 | ] 33 | } 34 | public render() { 35 | return ( 36 |
    37 |
    38 | {this.links.map( (link:{key: string, href: string, title: string | JSX.Element}) => ( 39 | 40 | {link.title} 41 | 42 | ))} 43 |
    44 |
    Copyright 2018 蚂蚁金服体验技术部出品
    45 |
    46 | ); 47 | } 48 | }; 49 | 50 | export default Footer; 51 | -------------------------------------------------------------------------------- /src/components/MyHeader/index.less: -------------------------------------------------------------------------------- 1 | .ant-layout-header{ 2 | .header { 3 | height: 64px; 4 | padding: 0 12px 0 0; 5 | background: #fff; 6 | box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); 7 | position: relative; 8 | } 9 | .logo { 10 | height: 64px; 11 | line-height: 58px; 12 | vertical-align: top; 13 | display: inline-block; 14 | padding: 0 0 0 24px; 15 | cursor: pointer; 16 | font-size: 20px; 17 | img { 18 | display: inline-block; 19 | vertical-align: middle; 20 | } 21 | } 22 | i.trigger { 23 | font-size: 20px; 24 | line-height: 64px; 25 | cursor: pointer; 26 | transition: all 0.3s, padding 0s; 27 | padding: 0 24px; 28 | &:hover { 29 | background: #e6f7ff; 30 | } 31 | } 32 | 33 | .right { 34 | float: right; 35 | height: 100%; 36 | .action { 37 | cursor: pointer; 38 | padding: 0 12px; 39 | display: inline-block; 40 | transition: all 0.3s; 41 | height: 100%; 42 | > i { 43 | font-size: 16px; 44 | vertical-align: middle; 45 | color: rgba(0,0,0,.65); 46 | } 47 | &:hover, 48 | &.ant-popover-open { 49 | background: #e6f7ff; 50 | } 51 | } 52 | .search { 53 | padding: 0; 54 | margin: 0 12px; 55 | &:hover { 56 | background: transparent; 57 | } 58 | } 59 | .account { 60 | .avatar { 61 | margin: 20px 8px 20px 0; 62 | color: #1890ff; 63 | background: rgba(255, 255, 255, 0.85); 64 | vertical-align: middle; 65 | } 66 | } 67 | } 68 | } 69 | .menu { 70 | .anticon { 71 | margin-right: 8px; 72 | } 73 | .ant-dropdown-menu-item { 74 | width: 160px; 75 | } 76 | } 77 | .ant-layout { 78 | min-height: 100vh; 79 | overflow-x: hidden; 80 | } 81 | 82 | @media only screen and (max-width: 992px) { 83 | .header { 84 | .ant-divider-vertical { 85 | vertical-align: unset; 86 | } 87 | .name { 88 | display: none; 89 | } 90 | i.trigger { 91 | padding: 0 12px; 92 | } 93 | .logo { 94 | padding-right: 12px; 95 | position: relative; 96 | } 97 | .right { 98 | position: absolute; 99 | right: 12px; 100 | top: 0; 101 | background: #fff; 102 | .account { 103 | .avatar { 104 | margin-right: 0; 105 | } 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/components/MyMenu/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { NavLink, withRouter } from 'react-router-dom'; 3 | import { Menu, Icon } from 'antd'; 4 | import menuData from './menu'; 5 | 6 | const { SubMenu } = Menu; 7 | 8 | /** 9 | * @class MyMenu 10 | */ 11 | @withRouter 12 | class MyMenu extends React.Component<{}, { collapsed: boolean, openKeys: string[] }> { 13 | private rootSubmenuKeys = []; 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | collapsed: false, 18 | openKeys: [], 19 | } 20 | } 21 | public componentWillMount() { 22 | menuData.map((item) => { 23 | this.rootSubmenuKeys.push(item.path); 24 | }); 25 | } 26 | private onOpenChange(openKeys) { 27 | const latestOpenKey = openKeys.find(key => this.state.openKeys.indexOf(key) === -1); 28 | if (this.rootSubmenuKeys.indexOf(latestOpenKey) === -1) { 29 | this.setState({ openKeys }); 30 | } else { 31 | this.setState({ 32 | openKeys: latestOpenKey ? [latestOpenKey] : [], 33 | }); 34 | } 35 | } 36 | public render() { 37 | return ( 38 | this.onOpenChange(key)} 44 | > 45 | { 46 | menuData.map((item: { children: [{ name: string, path: string, hideInMenu?: boolean }], icon: string, name: string, path: string }, index: number) => { 47 | if (!item.children) { 48 | return ( 49 | 50 | 51 | {item.name} 52 | 53 | ); 54 | } else { 55 | return ( 56 | {item.name}}> 57 | { 58 | item.children.map((value: { children?, path: string, name: string }, num: number, ) => { 59 | if (value.children) { 60 | return ( 61 | {value.name}}> 62 | {value.children.map((val, numb) => { 63 | return ( 64 | 65 | 66 | {val.name} 67 | 68 | 69 | ) 70 | })} 71 | 72 | ) 73 | } else { 74 | return ( 75 | 76 | 77 | {value.name} 78 | 79 | 80 | ); 81 | } 82 | }) 83 | } 84 | 85 | ); 86 | } 87 | }) 88 | } 89 | 90 | ); 91 | } 92 | } 93 | 94 | export default MyMenu; 95 | -------------------------------------------------------------------------------- /src/components/MyMenu/menu.ts: -------------------------------------------------------------------------------- 1 | const menuData = [ 2 | { 3 | name: 'dashboard', 4 | icon: 'dashboard', 5 | path: 'dashboard', 6 | children: [ 7 | { 8 | name: '分析页', 9 | path: 'analysis', 10 | }, 11 | { 12 | name: '监控页', 13 | path: 'monitor', 14 | }, 15 | { 16 | name: '工作台', 17 | path: 'workplace', 18 | }, 19 | ], 20 | }, 21 | { 22 | name: '表单页', 23 | icon: 'form', 24 | path: 'form', 25 | children: [ 26 | { 27 | name: '基础表单', 28 | path: 'basic-form', 29 | }, 30 | { 31 | name: '分步表单', 32 | path: 'step-form', 33 | }, 34 | { 35 | name: '高级表单', 36 | authority: 'admin', 37 | path: 'advanced-form', 38 | }, 39 | ], 40 | }, 41 | { 42 | name: '列表页', 43 | icon: 'table', 44 | path: 'list', 45 | children: [ 46 | { 47 | name: '查询表格', 48 | path: 'table-list', 49 | }, 50 | { 51 | name: '标准列表', 52 | path: 'basic-list', 53 | }, 54 | { 55 | name: '卡片列表', 56 | path: 'card-list', 57 | }, 58 | { 59 | name: '搜索列表', 60 | path: 'search', 61 | children: [ 62 | { 63 | name: '搜索列表(文章)', 64 | path: 'articles', 65 | }, 66 | { 67 | name: '搜索列表(项目)', 68 | path: 'projects', 69 | }, 70 | { 71 | name: '搜索列表(应用)', 72 | path: 'applications', 73 | }, 74 | ], 75 | }, 76 | ], 77 | }, 78 | { 79 | name: '详情页', 80 | icon: 'profile', 81 | path: 'profile', 82 | children: [ 83 | { 84 | name: '基础详情页', 85 | path: 'basic', 86 | }, 87 | { 88 | name: '高级详情页', 89 | path: 'advanced', 90 | authority: 'admin', 91 | }, 92 | ], 93 | }, 94 | { 95 | name: '结果页', 96 | icon: 'check-circle-o', 97 | path: 'result', 98 | children: [ 99 | { 100 | name: '成功', 101 | path: 'success', 102 | }, 103 | { 104 | name: '失败', 105 | path: 'fail', 106 | }, 107 | ], 108 | }, 109 | { 110 | name: '异常页', 111 | icon: 'warning', 112 | path: 'exception', 113 | children: [ 114 | { 115 | name: '403', 116 | path: '403', 117 | }, 118 | { 119 | name: '404', 120 | path: '404', 121 | }, 122 | { 123 | name: '500', 124 | path: '500', 125 | }, 126 | { 127 | name: '触发异常', 128 | path: 'trigger', 129 | hideInMenu: true, 130 | }, 131 | ], 132 | }, 133 | { 134 | name: '账户', 135 | icon: 'user', 136 | path: 'user', 137 | authority: 'guest', 138 | children: [ 139 | { 140 | name: '登录', 141 | path: 'login', 142 | }, 143 | { 144 | name: '注册', 145 | path: 'register', 146 | }, 147 | { 148 | name: '注册结果', 149 | path: 'register-result', 150 | }, 151 | ], 152 | }, 153 | ]; 154 | 155 | export default menuData; 156 | -------------------------------------------------------------------------------- /src/components/NoticeIcon/NoticeList.less: -------------------------------------------------------------------------------- 1 | .listContent{ 2 | .list { 3 | max-height: 400px; 4 | overflow: auto; 5 | .item { 6 | transition: all 0.3s; 7 | overflow: hidden; 8 | cursor: pointer; 9 | padding-left: 24px; 10 | padding-right: 24px; 11 | 12 | .meta { 13 | width: 100%; 14 | } 15 | 16 | .avatar { 17 | background: #fff; 18 | margin-top: 4px; 19 | } 20 | 21 | &.read { 22 | opacity: 0.4; 23 | } 24 | &:last-child { 25 | border-bottom: 0; 26 | } 27 | &:hover { 28 | background: #e6f7ff; 29 | } 30 | .title { 31 | font-weight: normal; 32 | margin-bottom: 8px; 33 | } 34 | .description { 35 | font-size: 12px; 36 | line-height: 22px; 37 | } 38 | .datetime { 39 | font-size: 12px; 40 | margin-top: 4px; 41 | line-height: 1.5; 42 | } 43 | .extra { 44 | float: right; 45 | color: rgba(0,0,0,.45); 46 | font-weight: normal; 47 | margin-right: 0; 48 | margin-top: -1.5px; 49 | } 50 | } 51 | } 52 | .clear { 53 | height: 46px; 54 | line-height: 46px; 55 | text-align: center; 56 | color: rgba(0,0,0,.65); 57 | border-radius: 0 0 4px 4px; 58 | border-top: 1px solid #e8e8e8; 59 | transition: all 0.3s; 60 | cursor: pointer; 61 | 62 | &:hover { 63 | color: rgba(0,0,0,.85); 64 | } 65 | } 66 | } 67 | 68 | .notFound { 69 | text-align: center; 70 | padding: 73px 0 88px 0; 71 | color: rgba(0,0,0,.45); 72 | img { 73 | display: inline-block; 74 | margin-bottom: 16px; 75 | height: 76px; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/components/NoticeIcon/NoticeList.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Avatar, List } from 'antd'; 3 | import './NoticeList.less'; 4 | 5 | export interface INoticeListProps { 6 | data: [{read: boolean, avatar: string, title: string, extra, description: string, datetime: string, key: string }], 7 | onClick: (item) => void, 8 | onClear: () => void, 9 | title: string, 10 | locale: { emptyText: string, clear: string }, 11 | emptyText: string, 12 | emptyImage: string, 13 | } 14 | class NoticeList extends React.Component{ 15 | constructor(props) { 16 | super(props); 17 | } 18 | public render() { 19 | const { data, emptyImage, emptyText, locale, onClick, onClear, title } = this.props; 20 | if (!data || !data.length) { 21 | return ( 22 |
    23 | {emptyImage ? not found : null} 24 |
    {emptyText || locale.emptyText}
    25 |
    26 | ); 27 | } 28 | return ( 29 |
    30 | ( 34 | 35 |
    onClick(item)}> 36 | : null} 39 | title={ 40 |
    41 | {item.title} 42 |
    {item.extra}
    43 |
    44 | } 45 | description={ 46 |
    47 |
    48 | {item.description} 49 |
    50 |
    {item.datetime}
    51 |
    52 | } 53 | /> 54 |
    55 |
    56 | )} 57 | /> 58 |
    59 | {locale.clear} 60 | {title} 61 |
    62 |
    63 | ); 64 | } 65 | } 66 | 67 | export default NoticeList; 68 | -------------------------------------------------------------------------------- /src/components/NoticeIcon/index.less: -------------------------------------------------------------------------------- 1 | .ant-popover { 2 | width: 336px; 3 | .ant-popover-inner-content { 4 | padding: 0; 5 | } 6 | } 7 | 8 | .noticeButton { 9 | cursor: pointer; 10 | display: inline-block; 11 | transition: all 0.3s; 12 | } 13 | 14 | .icon { 15 | font-size: 16px; 16 | padding: 4px; 17 | } 18 | 19 | .tabs { 20 | .ant-tabs-nav-scroll { 21 | text-align: center; 22 | } 23 | .ant-tabs-bar { 24 | margin-bottom: 4px; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/NumberInfo/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .numberInfo { 4 | .suffix { 5 | color: @text-color; 6 | font-size: 16px; 7 | font-style: normal; 8 | margin-left: 4px; 9 | } 10 | .numberInfoTitle { 11 | color: @text-color; 12 | font-size: @font-size-lg; 13 | margin-bottom: 16px; 14 | transition: all 0.3s; 15 | } 16 | .numberInfoSubTitle { 17 | color: @text-color-secondary; 18 | font-size: @font-size-base; 19 | height: 22px; 20 | line-height: 22px; 21 | overflow: hidden; 22 | text-overflow: ellipsis; 23 | word-break: break-all; 24 | white-space: nowrap; 25 | } 26 | .numberInfoValue { 27 | margin-top: 4px; 28 | font-size: 0; 29 | overflow: hidden; 30 | text-overflow: ellipsis; 31 | word-break: break-all; 32 | white-space: nowrap; 33 | & > span { 34 | color: @heading-color; 35 | display: inline-block; 36 | line-height: 32px; 37 | height: 32px; 38 | font-size: 24px; 39 | margin-right: 32px; 40 | } 41 | .subTotal { 42 | color: @text-color-secondary; 43 | font-size: @font-size-lg; 44 | vertical-align: top; 45 | margin-right: 0; 46 | i { 47 | font-size: 12px; 48 | transform: scale(0.82); 49 | margin-left: 4px; 50 | } 51 | :global { 52 | .anticon-caret-up { 53 | color: @red-6; 54 | } 55 | .anticon-caret-down { 56 | color: @green-6; 57 | } 58 | } 59 | } 60 | } 61 | } 62 | .numberInfolight { 63 | .numberInfoValue { 64 | & > span { 65 | color: @text-color; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/components/NumberInfo/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Icon } from 'antd'; 3 | import './index.less'; 4 | 5 | 6 | export interface INumberInfoProps { 7 | title?: React.ReactNode | string; 8 | subTitle?: React.ReactNode | string; 9 | total?: React.ReactNode | string; 10 | status?: 'up' | 'down'; 11 | theme?: string; 12 | gap?: number; 13 | subTotal?: number; 14 | style?: React.CSSProperties; 15 | suffix?: number | string 16 | } 17 | 18 | class NumberInfo extends React.Component { 19 | public render() { 20 | const { theme, title, subTitle, total, subTotal, status, suffix, gap, ...rest } = this.props; 21 | return ( 22 |
    26 | {title &&
    {title}
    } 27 | {subTitle &&
    {subTitle}
    } 28 |
    29 | 30 | {total} 31 | {suffix && {suffix}} 32 | 33 | {(status || subTotal) && ( 34 | 35 | {subTotal} 36 | {status && } 37 | 38 | )} 39 |
    40 |
    41 | ) 42 | } 43 | } 44 | 45 | export default NumberInfo; 46 | -------------------------------------------------------------------------------- /src/components/PageHeader/index.less: -------------------------------------------------------------------------------- 1 | .pageHeader { 2 | background: #fff; 3 | padding: 16px 32px 0 32px; 4 | border-bottom: 1px solid #e8e8e8; 5 | 6 | .detail { 7 | display: flex; 8 | } 9 | 10 | .row { 11 | display: flex; 12 | width: 100%; 13 | } 14 | 15 | .breadcrumb { 16 | margin-bottom: 16px; 17 | } 18 | 19 | .tabs { 20 | margin: 0 0 -17px -8px; 21 | 22 | .ant-tabs-bar { 23 | border-bottom: 1px solid #e8e8e8; 24 | } 25 | } 26 | 27 | .logo { 28 | flex: 0 1 auto; 29 | margin-right: 16px; 30 | padding-top: 1px; 31 | > img { 32 | width: 28px; 33 | height: 28px; 34 | border-radius: 4px; 35 | display: block; 36 | } 37 | } 38 | 39 | .title { 40 | font-size: 20px; 41 | font-weight: 500; 42 | color: rgba(0,0,0,.85) 43 | } 44 | 45 | .action { 46 | margin-left: 56px; 47 | min-width: 266px; 48 | 49 | .ant-btn-group:not(:last-child), 50 | .ant-btn:not(:last-child) { 51 | margin-right: 8px; 52 | } 53 | 54 | .ant-btn-group > .ant-btn { 55 | margin-right: 0; 56 | } 57 | } 58 | 59 | .title, 60 | .action, 61 | .content, 62 | .extraContent, 63 | .main { 64 | flex: 0 1 auto; 65 | } 66 | .main { 67 | width: 100%; 68 | } 69 | .title, 70 | .action { 71 | margin-bottom: 16px; 72 | } 73 | 74 | .logo, 75 | .extraContent { 76 | margin-bottom: 16px; 77 | } 78 | 79 | .action, 80 | .extraContent { 81 | text-align: right; 82 | } 83 | 84 | .extraContent { 85 | margin-left: 88px; 86 | min-width: 242px; 87 | } 88 | } 89 | 90 | @media screen and (max-width: 1200px) { 91 | .pageHeader { 92 | .extraContent { 93 | margin-left: 44px; 94 | } 95 | } 96 | } 97 | 98 | @media screen and (max-width: 992px) { 99 | .pageHeader { 100 | .extraContent { 101 | margin-left: 20px; 102 | } 103 | } 104 | } 105 | 106 | @media screen and (max-width: 768px) { 107 | .pageHeader { 108 | .row { 109 | display: block; 110 | } 111 | 112 | .action, 113 | .extraContent { 114 | margin-left: 0; 115 | text-align: left; 116 | } 117 | } 118 | } 119 | 120 | @media screen and (max-width: 576px) { 121 | .pageHeader { 122 | .detail { 123 | display: block; 124 | } 125 | } 126 | } 127 | 128 | @media screen and (max-width: 576px) { 129 | .pageHeader { 130 | .action { 131 | .ant-btn-group, 132 | .ant-btn { 133 | display: block; 134 | margin-bottom: 8px; 135 | } 136 | .ant-btn-group > .ant-btn { 137 | display: inline-block; 138 | margin-bottom: 0; 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/components/PageHeaderLayout/PageHeaderLayout.less: -------------------------------------------------------------------------------- 1 | .content { 2 | margin: 24px; 3 | } 4 | 5 | @media screen and (max-width: 576px) { 6 | .content { 7 | margin: 24px 0 0; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/PageHeaderLayout/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import PageHeader from '../PageHeader'; 4 | import './PageHeaderLayout.less'; 5 | 6 | export interface IPageHeaderLayoutProps { 7 | children?: React.ReactNode | string, 8 | wrapperClassName?: string, 9 | top?: React.ReactNode | string, 10 | content?: any, 11 | extraContent?: any, 12 | title?:string, 13 | tabActiveKey?: string, 14 | tabList?, 15 | onTabChange?, 16 | logo?, 17 | action? 18 | } 19 | class PageHeaderLayout extends React.Component { 20 | public render() { 21 | const { children, wrapperClassName, top, content, extraContent } = this.props; 22 | return ( 23 |
    24 | {top} 25 | 26 | {children ?
    {children}
    : null} 27 |
    28 | ) 29 | } 30 | } 31 | 32 | export default PageHeaderLayout; 33 | -------------------------------------------------------------------------------- /src/components/ReactLoadable.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Loadable from "react-loadable"; 3 | import { Spin } from "antd"; 4 | 5 | export const ReactLoadable = loadComponent => { 6 | return Loadable({ 7 | loader: loadComponent, 8 | loading: () => ( 9 |
    10 | 11 |
    12 | ) 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /src/components/Result/index.less: -------------------------------------------------------------------------------- 1 | .result { 2 | text-align: center; 3 | width: 72%; 4 | margin: 0 auto; 5 | @media screen and (max-width: 480px) { 6 | width: 100%; 7 | } 8 | 9 | .icon { 10 | font-size: 72px; 11 | line-height: 72px; 12 | margin-bottom: 24px; 13 | & > .success { 14 | color: #52c41a; 15 | } 16 | & > .error { 17 | color: #f5222d; 18 | } 19 | } 20 | 21 | .title { 22 | font-size: 24px; 23 | color: rgba(0, 0, 0, 0.85); 24 | font-weight: 500; 25 | line-height: 32px; 26 | margin-bottom: 16px; 27 | } 28 | 29 | .description { 30 | font-size: 14px; 31 | line-height: 22px; 32 | color: rgba(0, 0, 0, 0.45); 33 | margin-bottom: 24px; 34 | } 35 | 36 | .extra { 37 | background: #fafafa; 38 | padding: 24px 40px; 39 | border-radius: 2px; 40 | text-align: left; 41 | 42 | @media screen and (max-width: 480px) { 43 | padding: 18px 20px; 44 | } 45 | } 46 | 47 | .actions { 48 | margin-top: 32px; 49 | button:not(:last-child) { 50 | margin-right: 8px; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/components/Result/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Icon } from 'antd'; 3 | import './index.less'; 4 | 5 | export interface IResultProps { 6 | type: 'success' | 'error'; 7 | title: React.ReactNode; 8 | description?: React.ReactNode; 9 | extra?: React.ReactNode; 10 | actions?: React.ReactNode; 11 | style?: React.CSSProperties; 12 | className?: string 13 | } 14 | class Result extends React.Component { 15 | public render() { 16 | const { className, type, title, description, extra, actions, ...restProps} = this.props; 17 | const iconMap = { 18 | error: , 19 | success: , 20 | }; 21 | return ( 22 |
    23 |
    {iconMap[type]}
    24 |
    {title}
    25 | {description &&
    {description}
    } 26 | {extra &&
    {extra}
    } 27 | {actions &&
    {actions}
    } 28 |
    29 | ); 30 | } 31 | } 32 | 33 | export default Result; 34 | -------------------------------------------------------------------------------- /src/components/StandardFormRow/index.less: -------------------------------------------------------------------------------- 1 | .standardFormRow { 2 | border-bottom: 1px dashed #e8e8e8; 3 | padding-bottom: 16px; 4 | margin-bottom: 16px; 5 | display: flex; 6 | .ant-form-item { 7 | margin-right: 24px; 8 | } 9 | .ant-form-item-label label { 10 | color: rgba(0, 0, 0, 0.65); 11 | margin-right: 0; 12 | } 13 | .ant-form-item-label, 14 | .ant-form-item-control { 15 | padding: 0; 16 | line-height: 32px; 17 | } 18 | .label { 19 | color: rgba(0, 0, 0, 0.85); 20 | font-size: 14px; 21 | margin-right: 24px; 22 | flex: 0 0 auto; 23 | text-align: right; 24 | & > span { 25 | display: inline-block; 26 | height: 32px; 27 | line-height: 32px; 28 | &:after { 29 | content: ':'; 30 | } 31 | } 32 | } 33 | .content { 34 | flex: 1 1 0; 35 | .ant-form-item:last-child { 36 | margin-right: 0; 37 | } 38 | } 39 | } 40 | 41 | .standardFormRowLast { 42 | border: none; 43 | padding-bottom: 0; 44 | margin-bottom: 0; 45 | } 46 | 47 | .standardFormRowBlock { 48 | .ant-form-item, 49 | div.ant-form-item-control-wrapper { 50 | display: block; 51 | } 52 | } 53 | 54 | .standardFormRowGrid { 55 | .ant-form-item, 56 | div.ant-form-item-control-wrapper { 57 | display: block; 58 | } 59 | .ant-form-item-label { 60 | float: left; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/components/StandardFormRow/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import './index.less'; 3 | 4 | export interface IStandardFormRowProps { 5 | title: string, 6 | last?: boolean, 7 | block?: boolean, 8 | grid?: boolean, 9 | style?: React.CSSProperties; 10 | } 11 | 12 | class StandardFormRow extends React.Component { 13 | public render() { 14 | const { title, children, last, block, grid, ...rest } = this.props; 15 | const cls = `standardFormRow ${block? 'standardFormRowBlock' : ''} ${last? 'standardFormRowLast' : ''} ${grid? 'standardFormRowGrid' : ''}` 16 | return( 17 |
    18 | {title && ( 19 |
    20 | {title} 21 |
    22 | )} 23 |
    {children}
    24 |
    25 | ) 26 | } 27 | } 28 | 29 | // const StandardFormRow = ({ title, children, last, block, grid, ...rest }) => { 30 | // // const cls = classNames(styles.standardFormRow, { 31 | // // [styles.standardFormRowBlock]: block, 32 | // // [styles.standardFormRowLast]: last, 33 | // // [styles.standardFormRowGrid]: grid, 34 | // // }); 35 | // const cls = `standardFormRow ${block? 'standardFormRowBlock' : ''} ${last? 'standardFormRowLast' : ''} ${grid? 'standardFormRowGrid' : ''}` 36 | // return ( 37 | //
    38 | // {title && ( 39 | //
    40 | // {title} 41 | //
    42 | // )} 43 | //
    {children}
    44 | //
    45 | // ); 46 | // }; 47 | 48 | export default StandardFormRow; 49 | -------------------------------------------------------------------------------- /src/components/StandardTable/index.less: -------------------------------------------------------------------------------- 1 | .standardTable { 2 | .ant-table-pagination { 3 | margin-top: 24px; 4 | } 5 | 6 | .tableAlert { 7 | margin-bottom: 16px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/TagSelect/index.less: -------------------------------------------------------------------------------- 1 | 2 | .tagSelect { 3 | user-select: none; 4 | margin-left: -8px; 5 | position: relative; 6 | overflow: hidden; 7 | max-height: 32px; 8 | line-height: 32px; 9 | transition: all 0.3s; 10 | .ant-tag { 11 | padding: 0 8px; 12 | margin-right: 24px; 13 | font-size: 14px; 14 | } 15 | &.expanded { 16 | transition: all 0.3s; 17 | max-height: 200px; 18 | } 19 | .trigger { 20 | position: absolute; 21 | top: 0; 22 | right: 0; 23 | i { 24 | font-size: 12px; 25 | } 26 | } 27 | &.hasExpandTag { 28 | padding-right: 50px; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/Trend/index.less: -------------------------------------------------------------------------------- 1 | .trendItem { 2 | display: inline-block; 3 | font-size: 14px; 4 | line-height: 22px; 5 | 6 | .up, 7 | .down { 8 | margin-left: 4px; 9 | position: relative; 10 | top: 1px; 11 | i { 12 | font-size: 12px; 13 | transform: scale(0.83); 14 | } 15 | } 16 | .up { 17 | color: #f5222d; 18 | } 19 | .down { 20 | color: #52c41a; 21 | top: -1px; 22 | } 23 | 24 | &.trendItemGrey .up, 25 | &.trendItemGrey .down { 26 | color: rgba(0,0,0,.85); 27 | } 28 | 29 | &.reverseColor .up { 30 | color: #52c41a; 31 | } 32 | &.reverseColor .down { 33 | color: #f5222d; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/Trend/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Icon } from 'antd'; 3 | import './index.less'; 4 | 5 | export interface ITrendProps { 6 | colorful?: boolean; 7 | flag: 'up' | 'down'; 8 | style?: React.CSSProperties; 9 | reverseColor?: boolean; 10 | className?: string, 11 | } 12 | 13 | class Trend extends React.Component { 14 | public render() { 15 | const { colorful = true, reverseColor = false, flag, children, className, ...rest } = this.props; 16 | return ( 17 |
    18 | {children} 19 | {flag && ( 20 | 21 | 22 | 23 | )} 24 |
    25 | ); 26 | } 27 | } 28 | 29 | export default Trend; 30 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { HashRouter as Router, Route, Switch } from 'react-router-dom'; 4 | import { LocaleProvider } from 'antd'; 5 | import { Provider } from 'mobx-react'; 6 | import zhCN from 'antd/lib/locale-provider/zh_CN'; 7 | 8 | import stores from './stores/index'; 9 | import App from './App'; 10 | import User from './routes/User'; 11 | 12 | ReactDOM.render( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | , 23 | document.getElementById('root') as HTMLElement 24 | ); 25 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Analysis.less: -------------------------------------------------------------------------------- 1 | @import "../../utils/utils.less"; 2 | 3 | .iconGroup { 4 | i { 5 | transition: color 0.32s; 6 | color: rgba(0, 0, 0, 0.45); 7 | cursor: pointer; 8 | margin-left: 16px; 9 | &:hover { 10 | color: rgba(0, 0, 0, 0.65); 11 | } 12 | } 13 | } 14 | .ant-card { 15 | .content { 16 | margin: 0; 17 | } 18 | } 19 | .rankingList { 20 | margin: 25px 0 0; 21 | padding: 0; 22 | list-style: none; 23 | li { 24 | .clearfix(); 25 | margin-top: 16px; 26 | span { 27 | color: rgba(0, 0, 0, 0.65); 28 | font-size: 14px; 29 | line-height: 22px; 30 | } 31 | span:first-child { 32 | background-color: #f5f5f5; 33 | border-radius: 20px; 34 | display: inline-block; 35 | font-size: 12px; 36 | font-weight: 600; 37 | margin-right: 24px; 38 | height: 20px; 39 | line-height: 20px; 40 | width: 20px; 41 | text-align: center; 42 | } 43 | span.active { 44 | //background-color: @primary-color; 45 | background-color: #314659; 46 | color: #fff; 47 | } 48 | span:last-child { 49 | float: right; 50 | } 51 | } 52 | } 53 | 54 | .salesExtra { 55 | display: inline-block; 56 | margin-right: 24px; 57 | a { 58 | color: rgba(0, 0, 0, 0.65); 59 | margin-left: 24px; 60 | &:hover { 61 | color: #1890ff; 62 | } 63 | &.currentDate { 64 | color: #1890ff; 65 | } 66 | } 67 | } 68 | 69 | .salesCard { 70 | .salesBar { 71 | padding: 0 0 32px 32px; 72 | } 73 | .salesRank { 74 | padding: 0 32px 32px 72px; 75 | } 76 | .ant-tabs-bar { 77 | padding-left: 16px; 78 | .ant-tabs-nav .ant-tabs-tab { 79 | padding-top: 16px; 80 | padding-bottom: 14px; 81 | line-height: 24px; 82 | } 83 | } 84 | .ant-tabs-extra-content { 85 | padding-right: 24px; 86 | line-height: 55px; 87 | } 88 | .ant-card-head { 89 | position: relative; 90 | } 91 | } 92 | 93 | .salesCardExtra { 94 | height: 21px; 95 | } 96 | 97 | .salesTypeRadio { 98 | position: absolute; 99 | right: 54px; 100 | bottom: 12px; 101 | } 102 | 103 | .offlineCard { 104 | .ant-tabs-ink-bar { 105 | bottom: auto; 106 | } 107 | .ant-tabs-bar { 108 | border-bottom: none; 109 | } 110 | .ant-tabs-nav-container-scrolling { 111 | padding-left: 40px; 112 | padding-right: 40px; 113 | } 114 | .ant-tabs-tab-prev-icon:before { 115 | position: relative; 116 | left: 6px; 117 | } 118 | .ant-tabs-tab-next-icon:before { 119 | position: relative; 120 | right: 6px; 121 | } 122 | 123 | .ant-tabs-tab-active h4 { 124 | color: #1890ff; 125 | } 126 | } 127 | 128 | .trendText { 129 | margin-left: 8px; 130 | color: rgba(0, 0, 0, 0.85); 131 | } 132 | 133 | @media screen and (max-width: 1200px) { 134 | .salesExtra { 135 | display: none; 136 | } 137 | 138 | .rankingList { 139 | li { 140 | span:first-child { 141 | margin-right: 8px; 142 | } 143 | } 144 | } 145 | } 146 | 147 | @media screen and (max-width: 992px) { 148 | .rankingTitle { 149 | margin-top: 16px; 150 | } 151 | 152 | .salesCard .salesBar { 153 | padding: 16px; 154 | } 155 | } 156 | 157 | @media screen and (max-width: 992px) { 158 | .salesExtraWrap { 159 | display: none; 160 | } 161 | 162 | .salesCard { 163 | .ant-tabs-content { 164 | padding-top: 30px; 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Monitor.less: -------------------------------------------------------------------------------- 1 | @import '../../utils/utils.less'; 2 | 3 | .mapChart { 4 | padding-top: 24px; 5 | height: 457px; 6 | text-align: center; 7 | img { 8 | display: inline-block; 9 | max-width: 100%; 10 | max-height: 437px; 11 | } 12 | } 13 | 14 | .pieCard .pie-stat { 15 | font-size: 24px !important; 16 | } 17 | 18 | @media screen and (max-width: 1200px) { 19 | .mapChart { 20 | height: auto; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/routes/Dashboard/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Route, Switch } from "react-router-dom"; 3 | 4 | // import Analysis from "./Analysis"; 5 | // import Monitor from "./Monitor"; 6 | // import Workplace from "./Workplace"; 7 | 8 | import { ReactLoadable } from "../../components/ReactLoadable"; 9 | 10 | const PARENT_URL = "/dashboard"; 11 | 12 | const routeMap = [ 13 | { 14 | name: "分析页", 15 | path: `${PARENT_URL}/analysis`, 16 | component: ReactLoadable(() => import("./Analysis")) 17 | }, 18 | { 19 | name: "监控页", 20 | path: `${PARENT_URL}/monitor`, 21 | component: ReactLoadable(() => import("./Monitor")) 22 | }, 23 | { 24 | name: "工作台", 25 | path: `${PARENT_URL}/workplace`, 26 | component: ReactLoadable(() => import("./Workplace")) 27 | } 28 | ]; 29 | 30 | /** 31 | * @class 32 | */ 33 | class Dashboard extends React.Component { 34 | public render() { 35 | return ( 36 |
    37 | 38 | {routeMap.map((item, index) => ( 39 | 46 | ))} 47 | 48 |
    49 | ); 50 | } 51 | } 52 | 53 | export default Dashboard; 54 | -------------------------------------------------------------------------------- /src/routes/Exception/403.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import Exception from '../../components/Exception'; 4 | 5 | const FourZeroThree = () => ( 6 | 7 | ); 8 | 9 | export default FourZeroThree; 10 | -------------------------------------------------------------------------------- /src/routes/Exception/404.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import Exception from '../../components/Exception'; 4 | 5 | const FourZeroFour = () => ( 6 | 7 | ); 8 | 9 | export default FourZeroFour; 10 | -------------------------------------------------------------------------------- /src/routes/Exception/500.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import Exception from '../../components/Exception'; 4 | 5 | const Five = () => ( 6 | 7 | ); 8 | 9 | export default Five; 10 | -------------------------------------------------------------------------------- /src/routes/Exception/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Route, Switch } from "react-router-dom"; 3 | import "./style.less"; 4 | 5 | // import FourZeroThree from './403'; 6 | // import FourZeroFour from './404'; 7 | // import Five from './500'; 8 | // import TriggerException from './triggerException'; 9 | 10 | import { ReactLoadable } from "../../components/ReactLoadable"; 11 | 12 | const PARENT_URL = "/exception"; 13 | 14 | const routeMap = [ 15 | { 16 | name: "403", 17 | path: `${PARENT_URL}/403`, 18 | component: ReactLoadable(() => import("./403")) 19 | }, 20 | { 21 | name: "404", 22 | path: `${PARENT_URL}/404`, 23 | component: ReactLoadable(() => import("./404")) 24 | }, 25 | { 26 | name: "500", 27 | path: `${PARENT_URL}/500`, 28 | component: ReactLoadable(() => import("./500")) 29 | }, 30 | { 31 | name: "触发异常", 32 | path: `${PARENT_URL}/trigger`, 33 | component: ReactLoadable(() => import("./triggerException")) 34 | } 35 | ]; 36 | 37 | /** 38 | * @class 39 | */ 40 | class Exception extends React.Component { 41 | public render() { 42 | return ( 43 |
    44 | 45 | {routeMap.map((item, index) => ( 46 | 53 | ))} 54 | 55 |
    56 | ); 57 | } 58 | } 59 | 60 | export default Exception; 61 | -------------------------------------------------------------------------------- /src/routes/Exception/style.less: -------------------------------------------------------------------------------- 1 | .trigger { 2 | background: 'red'; 3 | .ant-btn { 4 | margin-right: 8px; 5 | margin-bottom: 12px; 6 | } 7 | } 8 | 9 | .Exception{ 10 | .desc{ 11 | padding: 0; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/routes/Exception/triggerException.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { observer, inject } from 'mobx-react'; 3 | import { Button, Spin, Card } from 'antd'; 4 | import './style.less'; 5 | 6 | // @connect(state => ({ 7 | // isloading: state.error.isloading, 8 | // })) 9 | @inject( (store: {ErrorStore}) => { 10 | return { 11 | setError: store.ErrorStore.setError, 12 | } 13 | }) 14 | @observer 15 | export default class TriggerException extends React.Component<{setError:(code)=>void}, {isloading:boolean}> { 16 | constructor(props) { 17 | super(props); 18 | this.state = { 19 | isloading: false, 20 | } 21 | } 22 | 23 | private triggerError = code => { 24 | this.setState({ 25 | isloading: true, 26 | }); 27 | this.props.setError(code); 28 | }; 29 | public render() { 30 | return ( 31 | 32 | 33 | 36 | 39 | 42 | 45 | 46 | 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/routes/Form/StepForm/Step2.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Form, Input, Button, Alert, Divider } from 'antd'; 3 | import { digitUppercase } from '../../../utils/utils'; 4 | import './style.less'; 5 | 6 | const formItemLayout = { 7 | labelCol: { 8 | span: 5, 9 | }, 10 | wrapperCol: { 11 | span: 19, 12 | }, 13 | }; 14 | 15 | class Step2 extends React.Component<{form, data, submitting:boolean, history}> { 16 | public render() { 17 | const { form, data = {payAccount: 'ant-design@alipay.com',receiverAccount: 'test@example.com', receiverName: 'Alex', amount: '500'}, submitting } = this.props; 18 | const { getFieldDecorator, validateFields } = form; 19 | const onPrev = () => { 20 | this.props.history.push('/form/step-form'); 21 | // dispatch(routerRedux.push('/form/step-form')); 22 | }; 23 | const onValidateForm = e => { 24 | e.preventDefault(); 25 | validateFields((err, values) => { 26 | if (!err) { 27 | // dispatch({ 28 | // type: 'form/submitStepForm', 29 | // payload: { 30 | // ...data, 31 | // ...values, 32 | // }, 33 | // }); 34 | this.props.history.push('/form/step-form/result'); 35 | } 36 | }); 37 | }; 38 | return ( 39 |
    40 | 46 | 47 | {data.payAccount} 48 | 49 | 50 | {data.receiverAccount} 51 | 52 | 53 | {data.receiverName} 54 | 55 | 56 | {data.amount} 57 | ({digitUppercase(data.amount)}) 58 | 59 | 60 | 61 | {getFieldDecorator('password', { 62 | initialValue: '123456', 63 | rules: [ 64 | { 65 | required: true, 66 | message: '需要支付密码才能进行支付', 67 | }, 68 | ], 69 | })()} 70 | 71 | 82 | 85 | 88 | 89 | 90 | ); 91 | } 92 | } 93 | 94 | export default Form.create()(Step2); 95 | -------------------------------------------------------------------------------- /src/routes/Form/StepForm/Step3.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Button, Row, Col } from 'antd'; 3 | import Result from '../../../components/Result'; 4 | import './style.less'; 5 | 6 | class Step3 extends React.Component<{data, history}> { 7 | public render() { 8 | const { data = {payAccount: 'ant-design@alipay.com',receiverAccount: 'test@example.com', receiverName: 'Alex', amount: '500'} } = this.props; 9 | const onFinish = () => { 10 | this.props.history.push('/form/step-form'); 11 | // dispatch(routerRedux.push('/form/step-form')); 12 | }; 13 | const information = ( 14 |
    15 | 16 | 17 | 付款账户: 18 | 19 | 20 | {data.payAccount} 21 | 22 | 23 | 24 | 25 | 收款账户: 26 | 27 | 28 | {data.receiverAccount} 29 | 30 | 31 | 32 | 33 | 收款人姓名: 34 | 35 | 36 | {data.receiverName} 37 | 38 | 39 | 40 | 41 | 转账金额: 42 | 43 | 44 | {data.amount} 元 45 | 46 | 47 |
    48 | ); 49 | const actions = ( 50 | 51 | 54 | 55 | 56 | ); 57 | return ( 58 | 66 | ); 67 | } 68 | } 69 | 70 | export default Step3; 71 | -------------------------------------------------------------------------------- /src/routes/Form/StepForm/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Route, Redirect, Switch } from 'react-router-dom'; 3 | import { Card, Steps } from 'antd'; 4 | import PageHeaderLayout from '../../../components/PageHeaderLayout'; 5 | import NotFound from '../../Exception/404'; 6 | import Step1 from './Step1'; 7 | import Step2 from './Step2'; 8 | import Step3 from './Step3'; 9 | import './style.less'; 10 | 11 | const { Step } = Steps; 12 | 13 | class StepForm extends React.Component<{location: {pathname: string}}> { 14 | private getCurrentStep() { 15 | const { location } = this.props; 16 | const { pathname } = location; 17 | const pathList = pathname.split('/'); 18 | switch (pathList[pathList.length - 1]) { 19 | case 'info': 20 | return 0; 21 | case 'confirm': 22 | return 1; 23 | case 'result': 24 | return 2; 25 | default: 26 | return 0; 27 | } 28 | } 29 | public render() { 30 | const { location } = this.props; 31 | return ( 32 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | { 46 | [ 47 | { key: 0, name: '步骤1', path: `/form/step-form/info`, component: Step1 }, 48 | { key: 1, name: '步骤2', path: `/form/step-form/confirm`, component: Step2 }, 49 | { key: 32, name: '结果页', path: `/form/step-form/result`, component: Step3 } 50 | ].map(item => ( 51 | 57 | ))} 58 | 59 | 60 | 61 | 62 | 63 | 64 | ); 65 | } 66 | } 67 | 68 | export default StepForm; 69 | -------------------------------------------------------------------------------- /src/routes/Form/StepForm/style.less: -------------------------------------------------------------------------------- 1 | .stepForm { 2 | margin: 40px auto 0; 3 | max-width: 500px; 4 | } 5 | 6 | .stepFormText { 7 | margin-bottom: 24px; 8 | .ant-form-item-label, 9 | .ant-form-item-control { 10 | line-height: 22px; 11 | } 12 | } 13 | 14 | .result { 15 | margin: 0 auto; 16 | max-width: 560px; 17 | padding: 24px 0 8px; 18 | } 19 | 20 | .desc { 21 | padding: 0 56px; 22 | color: rgba(0, 0, 0, 0.45); 23 | h3 { 24 | font-size: 16px; 25 | margin: 0 0 12px 0; 26 | color: rgba(0, 0, 0, 0.45); 27 | line-height: 32px; 28 | } 29 | h4 { 30 | margin: 0 0 4px 0; 31 | color: rgba(0, 0, 0, 0.45); 32 | font-size: 14px; 33 | line-height: 22px; 34 | } 35 | p { 36 | margin-top: 0; 37 | margin-bottom: 12px; 38 | line-height: 22px; 39 | } 40 | } 41 | 42 | @media screen and (max-width: 768px) { 43 | .desc { 44 | padding: 0; 45 | } 46 | } 47 | 48 | .information { 49 | line-height: 22px; 50 | .ant-row:not(:last-child) { 51 | margin-bottom: 24px; 52 | } 53 | .label { 54 | color: rgba(0, 0, 0, 0.85); 55 | text-align: right; 56 | padding-right: 8px; 57 | @media screen and (max-width: 576px) { 58 | text-align: left; 59 | } 60 | } 61 | } 62 | 63 | .money { 64 | font-family: 'Helvetica Neue', sans-serif; 65 | font-weight: 500; 66 | font-size: 20px; 67 | line-height: 14px; 68 | } 69 | 70 | .uppercase { 71 | font-size: 12px; 72 | } 73 | -------------------------------------------------------------------------------- /src/routes/Form/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Route, Switch } from "react-router-dom"; 3 | 4 | // import BasicForm from "./BasicForm"; 5 | // import StepForm from "./StepForm/index"; 6 | // import AdvancedForm from "./AdvancedForm"; 7 | 8 | import { ReactLoadable } from "../../components/ReactLoadable"; 9 | 10 | const PARENT_URL = "/form"; 11 | 12 | const routeMap = [ 13 | { 14 | name: "基础表单", 15 | path: `${PARENT_URL}/basic-form`, 16 | component: ReactLoadable(() => import("./BasicForm")) 17 | }, 18 | { 19 | name: "分步表单", 20 | path: `${PARENT_URL}/step-form`, 21 | component: ReactLoadable(() => import("./StepForm/index")) 22 | }, 23 | { 24 | name: "高级表单", 25 | path: `${PARENT_URL}/advanced-form`, 26 | component: ReactLoadable(() => import("./AdvancedForm")) 27 | } 28 | ]; 29 | 30 | /** 31 | * @class 32 | */ 33 | class Form extends React.Component { 34 | public render() { 35 | return ( 36 |
    37 | 38 | {routeMap.map((item, index) => { 39 | if (index === 1) { 40 | return ( 41 | 48 | ); 49 | } else { 50 | return ( 51 | 58 | ); 59 | } 60 | })} 61 | 62 |
    63 | ); 64 | } 65 | } 66 | 67 | export default Form; 68 | -------------------------------------------------------------------------------- /src/routes/Form/style.less: -------------------------------------------------------------------------------- 1 | .card { 2 | margin-bottom: 24px; 3 | } 4 | 5 | .heading { 6 | font-size: 14px; 7 | line-height: 22px; 8 | margin: 0 0 16px 0; 9 | } 10 | 11 | .steps .ant-steps { 12 | max-width: 750px; 13 | margin: 16px auto; 14 | } 15 | 16 | .errorIcon { 17 | cursor: pointer; 18 | color: #f5222d;; 19 | margin-right: 24px; 20 | i { 21 | margin-right: 4px; 22 | } 23 | } 24 | 25 | .errorPopover { 26 | .ant-popover-inner-content { 27 | padding: 0; 28 | max-height: 290px; 29 | overflow: auto; 30 | min-width: 256px; 31 | } 32 | } 33 | 34 | .errorListItem { 35 | list-style: none; 36 | border-bottom: 1px solid #e8e8e8; 37 | padding: 8px 16px; 38 | cursor: pointer; 39 | transition: all 0.3s; 40 | &:hover { 41 | background: #e6f7ff;; 42 | } 43 | &:last-child { 44 | border: 0; 45 | } 46 | .errorIcon { 47 | color: #f5222d; 48 | float: left; 49 | margin-top: 4px; 50 | margin-right: 12px; 51 | padding-bottom: 22px; 52 | } 53 | .errorField { 54 | font-size: 12px; 55 | color: rgba(0, 0, 0, 0.45); 56 | margin-top: 2px; 57 | } 58 | } 59 | 60 | .editable { 61 | td { 62 | padding-top: 13px !important; 63 | padding-bottom: 12.5px !important; 64 | } 65 | } 66 | 67 | // custom footer for fixed footer toolbar 68 | .advancedForm + div { 69 | padding-bottom: 64px; 70 | } 71 | 72 | .advancedForm { 73 | .ant-form .ant-row:last-child .ant-form-item { 74 | margin-bottom: 24px; 75 | } 76 | .ant-table td { 77 | transition: none !important; 78 | } 79 | } 80 | 81 | .optional { 82 | color: rgba(0, 0, 0, 0.45); 83 | font-style: normal; 84 | } 85 | -------------------------------------------------------------------------------- /src/routes/List/Applications.less: -------------------------------------------------------------------------------- 1 | @import '../../utils/utils.less'; 2 | 3 | .List{ 4 | .trigger{ 5 | font-size: 14px !important; 6 | line-height: 32px !important; 7 | padding: 0 !important; 8 | } 9 | .ant-list{ 10 | margin-top: 24px; 11 | } 12 | } 13 | .filterCardList { 14 | margin-bottom: -24px; 15 | .ant-card-meta-content { 16 | margin-top: 0; 17 | } 18 | // disabled white space 19 | .ant-card-meta-avatar { 20 | font-size: 0; 21 | } 22 | .ant-card-actions { 23 | background: #f7f9fa; 24 | } 25 | .ant-list .ant-list-item-content-single { 26 | max-width: 100%; 27 | } 28 | .cardInfo { 29 | .clearfix(); 30 | margin-top: 16px; 31 | margin-left: 40px; 32 | & > div { 33 | position: relative; 34 | text-align: left; 35 | float: left; 36 | width: 50%; 37 | p { 38 | line-height: 32px; 39 | font-size: 24px; 40 | margin: 0; 41 | } 42 | p:first-child { 43 | color: rgba(0, 0, 0, 0.45); 44 | font-size: 12px; 45 | line-height: 20px; 46 | margin-bottom: 4px; 47 | } 48 | } 49 | } 50 | } 51 | 52 | .wan { 53 | position: relative; 54 | top: -2px; 55 | font-size: 14px; 56 | font-style: normal; 57 | line-height: 20px; 58 | margin-left: 2px; 59 | } 60 | -------------------------------------------------------------------------------- /src/routes/List/Articles.less: -------------------------------------------------------------------------------- 1 | @import '../../utils/utils.less'; 2 | 3 | .List{ 4 | .pageHeader .tabs{ 5 | margin: 0; 6 | margin-bottom: -5px; 7 | .ant-tabs-nav.ant-tabs-nav-animated{ 8 | float: left; 9 | } 10 | } 11 | } 12 | .listContent { 13 | .description { 14 | line-height: 22px; 15 | max-width: 720px; 16 | } 17 | .extra { 18 | color: rgba(0, 0, 0, 0.45); 19 | margin-top: 16px; 20 | line-height: 22px; 21 | & > .ant-avatar { 22 | vertical-align: top; 23 | margin-right: 8px; 24 | width: 20px; 25 | height: 20px; 26 | position: relative; 27 | top: 1px; 28 | } 29 | & > em { 30 | color: rgba(0, 0, 0, 0.25); 31 | font-style: normal; 32 | margin-left: 16px; 33 | } 34 | } 35 | } 36 | a.listItemMetaTitle { 37 | color: rgba(0, 0, 0, 0.85); 38 | } 39 | .listItemExtra { 40 | width: 272px; 41 | height: 1px; 42 | } 43 | .selfTrigger { 44 | margin-left: 12px; 45 | } 46 | 47 | @media screen and (max-width: 480px) { 48 | .selfTrigger { 49 | display: block; 50 | margin-left: 0; 51 | } 52 | .listContent { 53 | .extra { 54 | & > em { 55 | display: block; 56 | margin-left: 0; 57 | margin-top: 8px; 58 | } 59 | } 60 | } 61 | } 62 | @media screen and (max-width: 768px) { 63 | .selfTrigger { 64 | display: block; 65 | margin-left: 0; 66 | } 67 | } 68 | @media screen and (max-width: 992px) { 69 | .listItemExtra { 70 | width: 0; 71 | height: 1px; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/routes/List/BasicList.less: -------------------------------------------------------------------------------- 1 | @import '../../utils/utils.less'; 2 | 3 | .standardList { 4 | .ant-card-head { 5 | border-bottom: none; 6 | } 7 | .ant-card-head-title { 8 | line-height: 32px; 9 | padding: 24px 0; 10 | } 11 | .ant-card-extra { 12 | padding: 24px 0; 13 | } 14 | .ant-list-pagination { 15 | text-align: right; 16 | margin-top: 24px; 17 | } 18 | .ant-avatar-lg { 19 | width: 48px; 20 | height: 48px; 21 | line-height: 48px; 22 | } 23 | .headerInfo { 24 | position: relative; 25 | text-align: center; 26 | & > span { 27 | color: rgba(0,0,0,0.45); 28 | display: inline-block; 29 | font-size: 14px; 30 | line-height: 22px; 31 | margin-bottom: 4px; 32 | } 33 | & > p { 34 | color: rgba(0,0,0,0.85); 35 | font-size: 24px; 36 | line-height: 32px; 37 | margin: 0; 38 | } 39 | & > em { 40 | background-color: #e8e8e8; 41 | position: absolute; 42 | height: 56px; 43 | width: 1px; 44 | top: 0; 45 | right: 0; 46 | } 47 | } 48 | .listContent { 49 | font-size: 0; 50 | .listContentItem { 51 | color: rgba(0,0,0,0.45); 52 | display: inline-block; 53 | vertical-align: middle; 54 | font-size: 14px; 55 | margin-left: 40px; 56 | > span { 57 | line-height: 20px; 58 | } 59 | > p { 60 | margin-top: 4px; 61 | margin-bottom: 0; 62 | line-height: 22px; 63 | } 64 | } 65 | } 66 | .extraContentSearch { 67 | margin-left: 16px; 68 | width: 272px; 69 | } 70 | } 71 | 72 | @media screen and (max-width: 480px) { 73 | .standardList { 74 | .ant-list-item-content { 75 | display: block; 76 | flex: none; 77 | width: 100%; 78 | } 79 | .ant-list-item-action { 80 | margin-left: 0; 81 | } 82 | .listContent { 83 | margin-left: 0; 84 | & > div { 85 | margin-left: 0; 86 | } 87 | } 88 | .listCard { 89 | .ant-card-head-title { 90 | overflow: visible; 91 | } 92 | } 93 | } 94 | } 95 | 96 | @media screen and (max-width: 576px) { 97 | .standardList { 98 | .extraContentSearch { 99 | margin-left: 0; 100 | width: 100%; 101 | } 102 | .headerInfo { 103 | margin-bottom: 16px; 104 | & > em { 105 | display: none; 106 | } 107 | } 108 | } 109 | } 110 | 111 | @media screen and (max-width: 768px) { 112 | .standardList { 113 | .listContent { 114 | & > div { 115 | display: block; 116 | } 117 | & > div:last-child { 118 | top: 0; 119 | width: 100%; 120 | } 121 | } 122 | } 123 | .listCard { 124 | .ant-radio-group { 125 | display: block; 126 | margin-bottom: 8px; 127 | } 128 | } 129 | } 130 | 131 | @media screen and (max-width: 992px) and (min-width: 768px) { 132 | .standardList { 133 | .listContent { 134 | & > div { 135 | display: block; 136 | } 137 | & > div:last-child { 138 | top: 0; 139 | width: 100%; 140 | } 141 | } 142 | } 143 | } 144 | 145 | @media screen and (max-width: 1200px) { 146 | .standardList { 147 | .listContent { 148 | & > div { 149 | margin-left: 24px; 150 | } 151 | & > div:last-child { 152 | top: 0; 153 | } 154 | } 155 | } 156 | } 157 | 158 | @media screen and (max-width: 1400px) { 159 | .standardList { 160 | .listContent { 161 | text-align: right; 162 | & > div:last-child { 163 | top: 0; 164 | } 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/routes/List/CardList.less: -------------------------------------------------------------------------------- 1 | @import '../../utils/utils.less'; 2 | 3 | .cardList { 4 | margin-bottom: -24px; 5 | 6 | .card { 7 | .ant-card-meta-title { 8 | margin-bottom: 12px; 9 | & > a { 10 | color: rgba(0, 0, 0, 0.85); 11 | display: inline-block; 12 | max-width: 100%; 13 | } 14 | } 15 | .ant-card-actions { 16 | background: #f7f9fa; 17 | } 18 | .ant-card-body:hover { 19 | .ant-card-meta-title > a { 20 | color: #1890ff; 21 | } 22 | } 23 | } 24 | .item { 25 | height: 64px; 26 | } 27 | 28 | .ant-list .ant-list-item-content-single { 29 | max-width: 100%; 30 | } 31 | } 32 | 33 | .extraImg { 34 | margin-top: -60px; 35 | text-align: center; 36 | width: 195px; 37 | img { 38 | width: 100%; 39 | } 40 | } 41 | 42 | .newButton { 43 | background-color: #fff; 44 | border-color: #d9d9d9; 45 | border-radius: 2px; 46 | color: rgba(0, 0, 0, 0.45); 47 | width: 100%; 48 | height: 188px; 49 | } 50 | 51 | .cardAvatar { 52 | width: 48px; 53 | height: 48px; 54 | border-radius: 48px; 55 | } 56 | 57 | .cardDescription { 58 | .textOverflowMulti(); 59 | } 60 | 61 | .List{ 62 | .pageHeaderContent { 63 | position: relative; 64 | display: block; 65 | } 66 | } 67 | 68 | .contentLink { 69 | margin-top: 16px; 70 | a { 71 | margin-right: 32px; 72 | img { 73 | width: 24px; 74 | } 75 | } 76 | img { 77 | vertical-align: middle; 78 | margin-right: 8px; 79 | } 80 | } 81 | 82 | @media screen and (max-width: 992px) { 83 | .contentLink { 84 | a { 85 | margin-right: 16px; 86 | } 87 | } 88 | } 89 | @media screen and (max-width: 768px) { 90 | .extraImg { 91 | display: none; 92 | } 93 | } 94 | 95 | @media screen and (max-width: 576px) { 96 | .pageHeaderContent { 97 | padding-bottom: 30px; 98 | } 99 | .contentLink { 100 | position: absolute; 101 | left: 0; 102 | bottom: -4px; 103 | width: 1000px; 104 | a { 105 | margin-right: 16px; 106 | } 107 | img { 108 | margin-right: 4px; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/routes/List/CardList.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { observer, inject} from 'mobx-react'; 3 | import { Card, Button, Icon, List } from 'antd'; 4 | 5 | import Ellipsis from '../../components/Ellipsis'; 6 | import PageHeaderLayout from '../../components/PageHeaderLayout'; 7 | 8 | import './CardList.less'; 9 | 10 | @inject( (store: {FakeList} )=>{ 11 | return { 12 | list: store.FakeList.list, 13 | getList: store.FakeList.getList, 14 | clearList: store.FakeList.clearList, 15 | loading: store.FakeList.loading, 16 | } 17 | }) 18 | @observer 19 | class CardList extends React.Component<{list, loading:boolean, getList:(params)=>void, clearList: ()=>void}> { 20 | public componentDidMount() { 21 | this.props.getList({count: 8}); 22 | } 23 | 24 | public componentWillUnmount() { 25 | this.props.clearList(); 26 | } 27 | 28 | public render() { 29 | const { list, loading } = this.props; 30 | 31 | const content = ( 32 |
    33 |

    34 | 段落示意:蚂蚁金服务设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态, 35 | 提供跨越设计与开发的体验解决方案。 36 |

    37 | 51 |
    52 | ); 53 | 54 | const extraContent = ( 55 |
    56 | 这是一个标题 60 |
    61 | ); 62 | 63 | return ( 64 | 65 |
    66 | 72 | item ? ( 73 | 74 | 操作一, 操作二]}> 75 | } 77 | title={{item.title}} 78 | description={ 79 | 80 | {item.description} 81 | 82 | } 83 | /> 84 | 85 | 86 | ) : ( 87 | 88 | 91 | 92 | ) 93 | } 94 | /> 95 |
    96 |
    97 | ); 98 | } 99 | } 100 | 101 | export default CardList; 102 | -------------------------------------------------------------------------------- /src/routes/List/List.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Route, Switch } from 'react-router-dom'; 3 | import { Input } from 'antd'; 4 | import PageHeaderLayout from '../../components/PageHeaderLayout'; 5 | 6 | import Articles from './Articles'; 7 | import Projects from './Projects'; 8 | import Applications from './Applications'; 9 | 10 | const PARENT_URL = '/list/search'; 11 | 12 | const routeMap = [ 13 | { name: '搜索列表(文章)', path: `${PARENT_URL}/articles`, component: Articles }, 14 | { name: '搜索列表(项目)', path: `${PARENT_URL}/projects`, component: Projects }, 15 | { name: '搜索列表(应用)', path: `${PARENT_URL}/applications`, component: Applications }, 16 | ] 17 | 18 | class SearchList extends React.Component<{match, history, location }> { 19 | private handleFormSubmit = (key) => { 20 | console.debug(key); 21 | } 22 | private handleTabChange = key => { 23 | const { match } = this.props; 24 | switch (key) { 25 | case 'articles': 26 | this.props.history.push(`${match.url}/articles`); 27 | // dispatch(routerRedux.push(`${match.url}/articles`)); 28 | break; 29 | case 'applications': 30 | this.props.history.push(`${match.url}/applications`); 31 | // dispatch(routerRedux.push(`${match.url}/applications`)); 32 | break; 33 | case 'projects': 34 | this.props.history.push(`${match.url}/projects`); 35 | // dispatch(routerRedux.push(`${match.url}/projects`)); 36 | break; 37 | default: 38 | break; 39 | } 40 | }; 41 | 42 | public render() { 43 | const tabList = [ 44 | { 45 | key: 'articles', 46 | tab: '文章', 47 | }, 48 | { 49 | key: 'applications', 50 | tab: '应用', 51 | }, 52 | { 53 | key: 'projects', 54 | tab: '项目', 55 | }, 56 | ]; 57 | 58 | const mainSearch = ( 59 |
    60 | 67 |
    68 | ); 69 | 70 | const { match, location } = this.props; 71 | 72 | return ( 73 | 80 | 81 | {routeMap.map((item, index) => ( 82 | 83 | ))} 84 | 85 | 86 | ); 87 | } 88 | } 89 | 90 | export default SearchList; 91 | -------------------------------------------------------------------------------- /src/routes/List/Projects.less: -------------------------------------------------------------------------------- 1 | @import '../../utils/utils.less'; 2 | 3 | .coverCardList { 4 | margin-bottom: -24px; 5 | 6 | .card { 7 | .ant-card-meta-title { 8 | margin-bottom: 4px; 9 | & > a { 10 | color: rgba(0, 0, 0, 0.85); 11 | display: inline-block; 12 | max-width: 100%; 13 | } 14 | } 15 | .ant-card-meta-description { 16 | height: 44px; 17 | line-height: 22px; 18 | overflow: hidden; 19 | } 20 | 21 | &:hover { 22 | .ant-card-meta-title > a { 23 | color: #1890ff; 24 | } 25 | } 26 | } 27 | 28 | .cardItemContent { 29 | display: flex; 30 | margin-top: 16px; 31 | margin-bottom: -4px; 32 | line-height: 20px; 33 | height: 20px; 34 | & > span { 35 | color: rgba(0, 0, 0, 0.45); 36 | flex: 1; 37 | font-size: 12px; 38 | } 39 | .avatarList { 40 | flex: 0 1 auto; 41 | } 42 | } 43 | .cardList { 44 | margin-top: 24px; 45 | } 46 | 47 | .ant-list .ant-list-item-content-single { 48 | max-width: 100%; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/routes/List/TableList.less: -------------------------------------------------------------------------------- 1 | @import '../../utils/utils.less'; 2 | 3 | .tableList { 4 | .tableListOperator { 5 | margin-bottom: 16px; 6 | button { 7 | margin-right: 8px; 8 | } 9 | } 10 | } 11 | 12 | .tableListForm { 13 | .ant-form-item { 14 | margin-bottom: 24px; 15 | margin-right: 0; 16 | display: flex; 17 | > .ant-form-item-label { 18 | width: auto; 19 | line-height: 32px; 20 | padding-right: 8px; 21 | } 22 | .ant-form-item-control { 23 | line-height: 32px; 24 | } 25 | } 26 | .ant-form-item-control-wrapper { 27 | flex: 1; 28 | } 29 | .submitButtons { 30 | white-space: nowrap; 31 | margin-bottom: 24px; 32 | } 33 | } 34 | 35 | @media screen and (max-width: 992px) { 36 | .tableListForm .ant-form-item { 37 | margin-right: 24px; 38 | } 39 | } 40 | 41 | @media screen and (max-width: 768px) { 42 | .tableListForm .ant-form-item { 43 | margin-right: 8px; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/routes/List/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Route, Switch } from "react-router-dom"; 3 | 4 | // import TableList from "./TableList"; 5 | // import BasicList from "./BasicList"; 6 | // import CardList from "./CardList"; 7 | // import Search from "./List"; 8 | 9 | import { ReactLoadable } from "../../components/ReactLoadable"; 10 | 11 | const PARENT_URL = "/list"; 12 | 13 | const routeMap = [ 14 | { 15 | name: "查询表格", 16 | path: `${PARENT_URL}/table-list`, 17 | component: ReactLoadable(() => import("./TableList")) 18 | }, 19 | { 20 | name: "标准列表", 21 | path: `${PARENT_URL}/basic-list`, 22 | component: ReactLoadable(() => import("./BasicList")) 23 | }, 24 | { 25 | name: "卡片列表", 26 | path: `${PARENT_URL}/card-list`, 27 | component: ReactLoadable(() => import("./CardList")) 28 | }, 29 | { 30 | name: "搜索列表", 31 | path: `${PARENT_URL}/search`, 32 | component: ReactLoadable(() => import("./List")) 33 | } 34 | ]; 35 | 36 | /** 37 | * @class 38 | */ 39 | class Form extends React.Component { 40 | public render() { 41 | return ( 42 |
    43 | 44 | {routeMap.map((item, index) => { 45 | if (index === 3) { 46 | return ( 47 | 54 | ); 55 | } else { 56 | return ( 57 | 64 | ); 65 | } 66 | })} 67 | 68 |
    69 | ); 70 | } 71 | } 72 | 73 | export default Form; 74 | -------------------------------------------------------------------------------- /src/routes/MainPages.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Switch, Route, Redirect } from 'react-router-dom'; 3 | 4 | import Dashboard from './Dashboard'; 5 | import Form from './Form'; 6 | import List from './List'; 7 | import Profile from './Profile'; 8 | import Result from './Result'; 9 | import Exception from './Exception'; 10 | 11 | /** 12 | * @class MainPages 13 | * @extends {React.Component} 14 | */ 15 | class MainPages extends React.Component { 16 | public render() { 17 | return ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | } 29 | } 30 | 31 | export default MainPages; 32 | -------------------------------------------------------------------------------- /src/routes/Profile/AdvancedProfile.less: -------------------------------------------------------------------------------- 1 | .headerList { 2 | margin-bottom: 4px; 3 | } 4 | 5 | .tabsCard { 6 | .ant-card-head { 7 | padding: 0 16px; 8 | } 9 | } 10 | 11 | .noData { 12 | color: rgba(0, 0, 0, 0.25); 13 | text-align: center; 14 | line-height: 64px; 15 | font-size: 16px; 16 | i { 17 | font-size: 24px; 18 | margin-right: 16px; 19 | position: relative; 20 | top: 3px; 21 | } 22 | } 23 | 24 | .heading { 25 | color: rgba(0, 0, 0, 0.85); 26 | font-size: 20px; 27 | } 28 | 29 | .stepDescription { 30 | font-size: 14px; 31 | position: relative; 32 | left: 38px; 33 | & > div { 34 | margin-top: 8px; 35 | margin-bottom: 4px; 36 | } 37 | } 38 | 39 | .textSecondary { 40 | color: rgba(0, 0, 0, 0.45); 41 | } 42 | 43 | @media screen and (max-width: 576px) { 44 | .stepDescription { 45 | left: 8px; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/routes/Profile/BasicProfile.less: -------------------------------------------------------------------------------- 1 | .title.BasicProfile { 2 | color: rgba(0, 0, 0, 0.85); 3 | font-size: 16px; 4 | font-weight: 500; 5 | margin-bottom: 16px; 6 | } 7 | -------------------------------------------------------------------------------- /src/routes/Profile/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Route, Switch } from "react-router-dom"; 3 | 4 | // import BasicProfile from './BasicProfile'; 5 | // import AdvancedProfile from './AdvancedProfile'; 6 | 7 | import { ReactLoadable } from "../../components/ReactLoadable"; 8 | 9 | const PARENT_URL = "/profile"; 10 | 11 | const routeMap = [ 12 | { 13 | name: "基础详情页", 14 | path: `${PARENT_URL}/basic`, 15 | component: ReactLoadable(() => import("./BasicProfile")) 16 | }, 17 | { 18 | name: "高级详情页", 19 | path: `${PARENT_URL}/advanced`, 20 | component: ReactLoadable(() => import("./AdvancedProfile")) 21 | } 22 | ]; 23 | 24 | /** 25 | * @class 26 | */ 27 | class Profile extends React.Component { 28 | public render() { 29 | return ( 30 |
    31 | 32 | {routeMap.map((item, index) => { 33 | return ( 34 | 41 | ); 42 | })} 43 | 44 |
    45 | ); 46 | } 47 | } 48 | 49 | export default Profile; 50 | -------------------------------------------------------------------------------- /src/routes/Result/Error.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Button, Icon, Card } from 'antd'; 3 | import Result from '../../components/Result'; 4 | import PageHeaderLayout from '../../components/PageHeaderLayout'; 5 | 6 | const extra = ( 7 | 8 |
    16 | 您提交的内容有如下错误: 17 |
    18 |
    19 | 您的账户已被冻结 20 | 21 | 立即解冻 22 | 23 |
    24 |
    25 | 您的账户还不具备申请资格 26 | 27 | 立即升级 28 | 29 |
    30 |
    31 | ); 32 | 33 | const actions = ; 34 | 35 | export default () => ( 36 | 37 | 38 | 46 | 47 | 48 | ); 49 | -------------------------------------------------------------------------------- /src/routes/Result/Success.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Button, Row, Col, Icon, Steps, Card } from 'antd'; 3 | import Result from '../../components/Result'; 4 | import PageHeaderLayout from '../../components/PageHeaderLayout'; 5 | 6 | const { Step } = Steps; 7 | 8 | const desc1 = ( 9 |
    17 |
    18 | 曲丽丽 19 |
    20 |
    2016-12-12 12:32
    21 |
    22 | ); 23 | 24 | const desc2 = ( 25 |
    26 |
    27 | 周毛毛 28 |
    29 |
    30 | 催一下 31 |
    32 |
    33 | ); 34 | 35 | const extra = ( 36 | 37 |
    45 | 项目名称 46 |
    47 | 48 | 49 | 项目 ID: 50 | 23421 51 | 52 | 53 | 负责人: 54 | 曲丽丽 55 | 56 | 57 | 生效时间: 58 | 2016-12-12 ~ 2017-12-12 59 | 60 | 61 | 62 | 创建项目} description={desc1} /> 63 | 部门初审} description={desc2} /> 64 | 财务复核} /> 65 | 完成} /> 66 | 67 |
    68 | ); 69 | 70 | const actions = ( 71 | 72 | 73 | 74 | 75 | 76 | ); 77 | 78 | export default () => ( 79 | 80 | 81 | 92 | 93 | 94 | ); 95 | -------------------------------------------------------------------------------- /src/routes/Result/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Route, Switch } from "react-router-dom"; 3 | 4 | // import Success from './Success'; 5 | // import Fail from './Error'; 6 | 7 | import { ReactLoadable } from "../../components/ReactLoadable"; 8 | 9 | const PARENT_URL = "/result"; 10 | 11 | const routeMap = [ 12 | { 13 | name: "成功", 14 | path: `${PARENT_URL}/success`, 15 | component: ReactLoadable(() => import("./Success")) 16 | }, 17 | { 18 | name: "失败", 19 | path: `${PARENT_URL}/fail`, 20 | component: ReactLoadable(() => import("./Error")) 21 | } 22 | ]; 23 | 24 | /** 25 | * @class 26 | */ 27 | class Form extends React.Component { 28 | public render() { 29 | return ( 30 |
    31 | 32 | {routeMap.map((item, index) => { 33 | return ( 34 | 41 | ); 42 | })} 43 | 44 |
    45 | ); 46 | } 47 | } 48 | 49 | export default Form; 50 | -------------------------------------------------------------------------------- /src/routes/User/Login.less: -------------------------------------------------------------------------------- 1 | .LoginContent{ 2 | .main.LoginBox { 3 | width: 368px; 4 | margin: 0 auto; 5 | @media screen and (max-width: 576px) { 6 | width: 95%; 7 | } 8 | 9 | .icon { 10 | font-size: 24px; 11 | color: rgba(0, 0, 0, 0.2); 12 | margin-left: 16px; 13 | vertical-align: middle; 14 | cursor: pointer; 15 | transition: color 0.3s; 16 | 17 | &:hover { 18 | color: #1890ff; 19 | } 20 | } 21 | 22 | .other { 23 | text-align: left; 24 | margin-top: 24px; 25 | line-height: 22px; 26 | 27 | .register { 28 | float: right; 29 | } 30 | } 31 | .login { 32 | width: 100%; 33 | .tabs { 34 | padding: 0 2px; 35 | margin: 0 -2px; 36 | .ant-tabs-tab { 37 | font-size: 16px; 38 | line-height: 24px; 39 | } 40 | .ant-input-affix-wrapper .ant-input:not(:first-child) { 41 | padding-left: 34px; 42 | } 43 | } 44 | 45 | .ant-tabs .ant-tabs-bar { 46 | border-bottom: 0; 47 | margin-bottom: 24px; 48 | text-align: center; 49 | } 50 | 51 | .ant-form-item { 52 | margin-bottom: 24px; 53 | } 54 | 55 | .prefixIcon { 56 | font-size: 14px; 57 | color: rgba(0, 0, 0, 0.25); 58 | } 59 | 60 | .getCaptcha { 61 | display: block; 62 | width: 100%; 63 | height: 42px; 64 | } 65 | 66 | .submit { 67 | width: 100% !important; 68 | margin-top: 24px; 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/routes/User/Register.less: -------------------------------------------------------------------------------- 1 | .LoginContent{ 2 | .main.Register { 3 | width: 368px; 4 | margin: 0 auto; 5 | 6 | .ant-form-item { 7 | margin-bottom: 24px; 8 | } 9 | 10 | h3 { 11 | font-size: 16px; 12 | margin-bottom: 20px; 13 | } 14 | 15 | .getCaptcha { 16 | display: block; 17 | width: 100%; 18 | } 19 | 20 | .submit { 21 | width: 50%; 22 | } 23 | 24 | .login { 25 | float: right; 26 | line-height: 40px; 27 | } 28 | } 29 | 30 | .success, 31 | .warning, 32 | .error { 33 | transition: color 0.3s; 34 | } 35 | 36 | .success { 37 | color: #52c41a; 38 | } 39 | 40 | .warning { 41 | color: #faad14; 42 | } 43 | 44 | .error { 45 | color: #f5222d; 46 | } 47 | 48 | .progress-pass > .progress { 49 | .ant-progress-bg { 50 | background-color: #faad14; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/routes/User/RegisterResult.less: -------------------------------------------------------------------------------- 1 | .registerResult { 2 | .anticon { 3 | font-size: 64px; 4 | } 5 | .title { 6 | margin-top: 32px; 7 | font-size: 20px; 8 | line-height: 28px; 9 | } 10 | .actions { 11 | margin-top: 40px; 12 | a + a { 13 | margin-left: 8px; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/routes/User/RegisterResult.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'antd'; 3 | import { Link } from 'react-router-dom'; 4 | import Result from '../../components/Result'; 5 | import './RegisterResult.less'; 6 | 7 | const actions = ( 8 |
    9 | 10 | 13 | 14 | 15 | 16 | 17 |
    18 | ); 19 | 20 | export default ({ location }) => ( 21 | 26 | 你的账户:{location.state ? location.state.account : 'AntDesign@example.com'} 注册成功 27 | 28 | } 29 | description="激活邮件已发送到你的邮箱中,邮件有效期为24小时。请及时登录邮箱,点击邮件中的链接激活帐户。" 30 | actions={actions} 31 | style={{ marginTop: 56 }} 32 | /> 33 | ); 34 | -------------------------------------------------------------------------------- /src/routes/User/index.less: -------------------------------------------------------------------------------- 1 | .LoginContent{ 2 | &.container { 3 | display: flex; 4 | flex-direction: column; 5 | height: 100vh; 6 | overflow: auto; 7 | background: #f0f2f5; 8 | } 9 | 10 | .content { 11 | padding: 32px 0; 12 | flex: 1; 13 | } 14 | .top { 15 | text-align: center; 16 | } 17 | 18 | .header { 19 | height: 44px; 20 | line-height: 44px; 21 | a { 22 | text-decoration: none; 23 | } 24 | } 25 | 26 | .logo { 27 | height: 44px; 28 | vertical-align: top; 29 | margin-right: 16px; 30 | } 31 | 32 | .title { 33 | font-size: 33px; 34 | color: rgba(0, 0, 0, 0.85); 35 | font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif; 36 | font-weight: 600; 37 | position: relative; 38 | top: 2px; 39 | } 40 | 41 | .desc { 42 | font-size: 14px; 43 | color: rgba(0, 0, 0, 0.45); 44 | margin-top: 12px; 45 | margin-bottom: 40px; 46 | } 47 | } 48 | 49 | @media (min-width: 768px) { 50 | .LoginContent.container { 51 | background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg'); 52 | background-repeat: no-repeat; 53 | background-position: center 110px; 54 | background-size: 100%; 55 | } 56 | 57 | .LoginContent .content { 58 | padding: 112px 0 24px 0; 59 | } 60 | } 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/routes/User/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Route, Switch, Link } from "react-router-dom"; 3 | import { Icon } from "antd"; 4 | import GlobalFooter from "../../components/GlobalFooter"; 5 | 6 | import "./index.less"; 7 | 8 | // import Login from './Login'; 9 | // import Register from './Register'; 10 | // import RegisterResult from './RegisterResult'; 11 | 12 | import { ReactLoadable } from "../../components/ReactLoadable"; 13 | 14 | const PARENT_URL = "/user"; 15 | 16 | const routeMap = [ 17 | { 18 | name: "登录", 19 | path: `${PARENT_URL}/login`, 20 | component: ReactLoadable(() => import("./Login")) 21 | }, 22 | { 23 | name: "注册", 24 | path: `${PARENT_URL}/register`, 25 | component: ReactLoadable(() => import("./Register")) 26 | }, 27 | { 28 | name: "注册结果", 29 | path: `${PARENT_URL}/register-result`, 30 | component: ReactLoadable(() => import("./RegisterResult")) 31 | } 32 | ]; 33 | 34 | const links = [ 35 | { 36 | key: "help", 37 | title: "帮助", 38 | href: "" 39 | }, 40 | { 41 | key: "privacy", 42 | title: "隐私", 43 | href: "" 44 | }, 45 | { 46 | key: "terms", 47 | title: "条款", 48 | href: "" 49 | } 50 | ]; 51 | 52 | const copyright = ( 53 | 54 | Copyright 2018 蚂蚁金服体验技术部出品 55 | 56 | ); 57 | 58 | /** 59 | * @class 60 | */ 61 | class User extends React.Component { 62 | public render() { 63 | return ( 64 |
    65 |
    66 |
    67 |
    68 | 69 | logo 74 | Ant Design 75 | 76 |
    77 |
    78 | Ant Design 是西湖区最具影响力的 Web 设计规范 79 |
    80 |
    81 | 82 | {routeMap.map((item, index) => { 83 | return ( 84 | 91 | ); 92 | })} 93 | 94 |
    95 | 96 |
    97 | ); 98 | } 99 | } 100 | 101 | export default User; 102 | -------------------------------------------------------------------------------- /src/stores/Dashboard/Analysis.ts: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | import { getCharts } from '../../utils/api'; 3 | 4 | /** 5 | * @class home 6 | */ 7 | class Analysis { 8 | @observable public chart:object = {}; 9 | 10 | @action public getChartsData = () => { 11 | getCharts().then((res)=>{ 12 | if (res) { 13 | this.chart = res; 14 | } 15 | }) 16 | } 17 | 18 | @action public clearChartsData = () =>{ 19 | this.chart = {}; 20 | } 21 | 22 | } 23 | 24 | export default new Analysis(); 25 | -------------------------------------------------------------------------------- /src/stores/Dashboard/Monitor.ts: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | import { getTags } from '../../utils/api'; 3 | 4 | /** 5 | * @class home 6 | */ 7 | class Monitor { 8 | @observable public monitor; 9 | 10 | @action public getTagsData = () => { 11 | getTags().then((res)=>{ 12 | if (res) { 13 | // console.debug(res.list); 14 | this.monitor = res.list; 15 | } 16 | }) 17 | } 18 | 19 | @action public clearTagsData = () =>{ 20 | this.monitor = []; 21 | } 22 | 23 | } 24 | 25 | export default new Monitor(); 26 | -------------------------------------------------------------------------------- /src/stores/Dashboard/Workplace.ts: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | import { getNotice, getActivities } from '../../utils/api'; 3 | 4 | /** 5 | * @class home 6 | */ 7 | class Workplace { 8 | @observable public notice; 9 | @observable public activities; 10 | 11 | @action public getNoticeData = () => { 12 | getNotice().then((res)=>{ 13 | if (res) { 14 | this.notice = res; 15 | } 16 | }) 17 | } 18 | 19 | @action public getActivitiesData = () => { 20 | getActivities().then((res)=>{ 21 | if (res) { 22 | this.activities = res; 23 | } 24 | }) 25 | } 26 | 27 | @action public clearWorkplace = () =>{ 28 | this.notice = []; 29 | this.activities = []; 30 | } 31 | 32 | } 33 | 34 | export default new Workplace(); 35 | -------------------------------------------------------------------------------- /src/stores/Dashboard/index.ts: -------------------------------------------------------------------------------- 1 | import Analysis from './Analysis'; 2 | import Monitor from './Monitor'; 3 | import Workplace from './Workplace'; 4 | 5 | export { 6 | Analysis, 7 | Monitor, 8 | Workplace 9 | } 10 | -------------------------------------------------------------------------------- /src/stores/ErrorStore.ts: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | import { query } from '../utils/api'; 3 | 4 | /** 5 | * @class home 6 | */ 7 | class ErrorStore { 8 | @observable public loading=false; 9 | 10 | @action public setError = (code) => { 11 | this.loading = true; 12 | query(code).then((res)=>{ 13 | if (res) { 14 | console.debug(res); 15 | } 16 | }) 17 | } 18 | } 19 | 20 | export default new ErrorStore(); 21 | -------------------------------------------------------------------------------- /src/stores/Form/BasicForm.ts: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | import { basicFormSubmit } from '../../utils/api'; 3 | import { message } from 'antd'; 4 | 5 | /** 6 | * @class home 7 | */ 8 | class BasicForm { 9 | @observable public res:object = {}; 10 | 11 | @action public submitBasicFormData = (params) => { 12 | basicFormSubmit(params).then((res)=>{ 13 | // console.debug(res); 14 | if (res && res.success) { 15 | message.success('提交成功!'); 16 | this.res = res; 17 | } 18 | }) 19 | } 20 | 21 | @action public clearSubmitBasicForm = () =>{ 22 | this.res = {}; 23 | } 24 | 25 | } 26 | 27 | export default new BasicForm(); 28 | -------------------------------------------------------------------------------- /src/stores/Form/index.ts: -------------------------------------------------------------------------------- 1 | import BasicForm from './BasicForm'; 2 | 3 | export { 4 | BasicForm, 5 | } 6 | -------------------------------------------------------------------------------- /src/stores/Header.ts: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | import { getHeaderNotices, getUserCurrent } from '../utils/api'; 3 | /** 4 | * @class home 5 | */ 6 | class Header { 7 | @observable public list = []; 8 | @observable public fetchNotice = true; 9 | @observable public userCurrent: {notifyCount?: number} = {}; 10 | 11 | @action public getHeaderNotice = () => { 12 | getHeaderNotices().then((res)=>{ 13 | this.list = res; 14 | }); 15 | }; 16 | 17 | @action public changeFetchNotice = () => { 18 | const status = this.fetchNotice; 19 | setTimeout(()=>{ 20 | this.fetchNotice = !status; 21 | }, 500) 22 | }; 23 | 24 | @action public getUserCurrentData = () => { 25 | getUserCurrent().then((res)=>{ 26 | this.userCurrent = res; 27 | }); 28 | } 29 | 30 | @action public clearNotices = (type) => { 31 | const newList = this.list.filter(item => item.type !== type); 32 | this.list = newList; 33 | this.changeNoticesLength(this.list.length); 34 | } 35 | 36 | @action public changeNoticesLength = (num) =>{ 37 | if ( this.userCurrent && this.userCurrent.notifyCount ) { 38 | this.userCurrent.notifyCount = num; 39 | } 40 | } 41 | } 42 | 43 | export default new Header(); 44 | -------------------------------------------------------------------------------- /src/stores/Home.ts: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | /** 3 | * @class home 4 | */ 5 | class App { 6 | @observable public num = 0; 7 | @observable public list = []; 8 | 9 | @action public addNum = (num: number) => { 10 | this.list = ['1', '2', '3']; 11 | this.num = num+1; 12 | } 13 | 14 | @action public cutNum = (num: number) => { 15 | this.list = ['1', '2', '3']; 16 | this.num = num-1; 17 | } 18 | } 19 | 20 | export default new App(); 21 | -------------------------------------------------------------------------------- /src/stores/List/FakeList.ts: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | import { getFakeList } from '../../utils/api'; 3 | 4 | /** 5 | * @class home 6 | */ 7 | class FakeList { 8 | @observable public list=[]; 9 | @observable public loading=false; 10 | 11 | @action public getList = (params) => { 12 | this.loading = true; 13 | getFakeList(params).then((res)=>{ 14 | if (res) { 15 | this.loading = false; 16 | this.list = res; 17 | } 18 | }) 19 | } 20 | 21 | @action public clearList = () =>{ 22 | this.list = []; 23 | } 24 | 25 | @action public getMoreList = (params) =>{ 26 | this.loading = true; 27 | getFakeList(params).then((res)=>{ 28 | if (res) { 29 | this.loading = false; 30 | this.list = this.list.concat(res); 31 | } 32 | }) 33 | } 34 | } 35 | 36 | export default new FakeList(); 37 | -------------------------------------------------------------------------------- /src/stores/List/TableList.ts: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | import { getTableList, addTableList, deleteTableList } from '../../utils/api'; 3 | 4 | /** 5 | * @class home 6 | */ 7 | class TableList { 8 | @observable public list=[]; 9 | @observable public loading=false; 10 | 11 | @action public getList = (params) => { 12 | this.loading = true; 13 | getTableList(params).then((res)=>{ 14 | if (res) { 15 | this.loading = false; 16 | this.list = res; 17 | } 18 | }) 19 | } 20 | 21 | @action public clearList = () =>{ 22 | this.list = []; 23 | } 24 | 25 | @action public addList = (params) =>{ 26 | this.loading = true; 27 | addTableList(params).then((res)=>{ 28 | if (res) { 29 | this.loading = false; 30 | this.list = res; 31 | } 32 | }) 33 | } 34 | 35 | @action public deleteList = (params, callback) =>{ 36 | this.loading = true; 37 | deleteTableList(params).then((res)=>{ 38 | if (res) { 39 | this.loading = false; 40 | this.list = res; 41 | callback(); 42 | } 43 | }) 44 | } 45 | 46 | } 47 | 48 | export default new TableList(); 49 | -------------------------------------------------------------------------------- /src/stores/List/index.ts: -------------------------------------------------------------------------------- 1 | import TableList from './TableList'; 2 | import FakeList from './FakeList'; 3 | 4 | export { 5 | TableList, 6 | FakeList 7 | } 8 | -------------------------------------------------------------------------------- /src/stores/Profile/AdvancedProfile.ts: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | import { getAdvancedProfile } from '../../utils/api'; 3 | 4 | /** 5 | * @class home 6 | */ 7 | class AdvancedProfile { 8 | @observable public list={}; 9 | @observable public loading=false; 10 | 11 | @action public getList = () => { 12 | this.loading = true; 13 | getAdvancedProfile().then((res)=>{ 14 | if (res) { 15 | this.loading = false; 16 | this.list = res; 17 | } 18 | }) 19 | } 20 | 21 | @action public clearList = () =>{ 22 | this.list = []; 23 | } 24 | } 25 | 26 | export default new AdvancedProfile(); 27 | -------------------------------------------------------------------------------- /src/stores/Profile/BasicProfile.ts: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | import { getBasicProfile } from '../../utils/api'; 3 | 4 | /** 5 | * @class home 6 | */ 7 | class BasicProfile { 8 | @observable public list={}; 9 | @observable public loading=false; 10 | 11 | @action public getList = () => { 12 | this.loading = true; 13 | getBasicProfile().then((res)=>{ 14 | if (res) { 15 | this.loading = false; 16 | this.list = res; 17 | } 18 | }) 19 | } 20 | 21 | @action public clearList = () =>{ 22 | this.list = []; 23 | } 24 | } 25 | 26 | export default new BasicProfile(); 27 | -------------------------------------------------------------------------------- /src/stores/Profile/index.ts: -------------------------------------------------------------------------------- 1 | import BasicProfile from './BasicProfile'; 2 | import AdvancedProfile from './AdvancedProfile'; 3 | 4 | export { 5 | BasicProfile, 6 | AdvancedProfile 7 | } 8 | -------------------------------------------------------------------------------- /src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import Home from './Home'; 2 | import Header from './Header'; 3 | import { Analysis, Monitor, Workplace } from './Dashboard'; 4 | import { BasicForm } from './Form'; 5 | import { TableList, FakeList } from './List'; 6 | import { BasicProfile, AdvancedProfile } from './Profile'; 7 | import ErrorStore from './ErrorStore'; 8 | 9 | export default { 10 | Home, 11 | Header, 12 | Analysis, 13 | Monitor, 14 | Workplace, 15 | BasicForm, 16 | TableList, 17 | FakeList, 18 | BasicProfile, 19 | AdvancedProfile, 20 | ErrorStore 21 | }; 22 | -------------------------------------------------------------------------------- /src/theme.js: -------------------------------------------------------------------------------- 1 | // https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less 2 | module.exports = { 3 | // 'primary-color': '#10e99b', 4 | 'card-actions-background': '#f5f8fa' 5 | }; 6 | -------------------------------------------------------------------------------- /src/utils/api.ts: -------------------------------------------------------------------------------- 1 | import qs from 'qs'; 2 | import request from './request'; 3 | 4 | export async function getHeaderNotices() { 5 | return await request('/api/notices', {}); 6 | } 7 | 8 | export async function getCharts() { 9 | return await request('/api/charts', {}); 10 | } 11 | 12 | export async function getUserCurrent() { 13 | return await request('/api/userCurrent', {}); 14 | } 15 | 16 | export async function getTags() { 17 | return await request('/api/tags', {}); 18 | } 19 | 20 | export async function getNotice() { 21 | return await request('/api/project/notice', {}); 22 | } 23 | 24 | export async function getActivities() { 25 | return await request('/api/activities', {}); 26 | } 27 | 28 | export async function basicFormSubmit(params) { 29 | return await request('/api/form-basic', { 30 | method: 'POST', 31 | body: params, 32 | }); 33 | } 34 | 35 | export async function getTableList(params) { 36 | return await request(`/api/table-list?${qs.stringify(params)}`, {}); 37 | } 38 | 39 | export async function addTableList(params) { 40 | return await request('/api/table-list-put', { 41 | method: 'PUT', 42 | body: params, 43 | }); 44 | } 45 | 46 | export async function deleteTableList(params) { 47 | return await request('/api/table-list-delete', { 48 | method: 'POST', 49 | body: params, 50 | }); 51 | } 52 | 53 | export async function getFakeList(params) { 54 | return await request(`/api/fake-list?${qs.stringify(params)}`, {}); 55 | } 56 | 57 | export async function getBasicProfile() { 58 | return await request('/api/profile/basic', {}); 59 | } 60 | 61 | export async function getAdvancedProfile() { 62 | return await request('/api/profile/advanced', {}); 63 | } 64 | 65 | export async function query(code) { 66 | return await request(`/api/${code}`, {}); 67 | } 68 | -------------------------------------------------------------------------------- /src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import { notification } from 'antd'; 2 | import * as history from 'history'; 3 | 4 | const myHistory = history.createHashHistory(); 5 | 6 | const codeMessage = { 7 | 200: '服务器成功返回请求的数据。', 8 | 201: '新建或修改数据成功。', 9 | 202: '一个请求已经进入后台排队(异步任务)。', 10 | 204: '删除数据成功。', 11 | 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。', 12 | 401: '用户没有权限(令牌、用户名、密码错误)。', 13 | 403: '用户得到授权,但是访问是被禁止的。', 14 | 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。', 15 | 406: '请求的格式不可得。', 16 | 410: '请求的资源被永久删除,且不会再得到的。', 17 | 422: '当创建一个对象时,发生一个验证错误。', 18 | 500: '服务器发生错误,请检查服务器。', 19 | 502: '网关错误。', 20 | 503: '服务不可用,服务器暂时过载或维护。', 21 | 504: '网关超时。', 22 | }; 23 | 24 | function checkStatus(response) { 25 | if (response.status >= 200 && response.status < 300) { 26 | return response; 27 | } 28 | const errortext = codeMessage[response.status] || response.statusText; 29 | notification.error({ 30 | message: `请求错误 ${response.status}: ${response.url}`, 31 | description: errortext, 32 | }); 33 | const error = new Error(errortext); 34 | error.name = response.status; 35 | error.message = response; 36 | throw error; 37 | } 38 | 39 | /** 40 | * Requests a URL, returning a promise. 41 | * 42 | * @param {string} url The URL we want to request 43 | * @param {object} [options] The options we want to pass to "fetch" 44 | * @return {object} An object containing either "data" or "err" 45 | */ 46 | interface IMyOptionObj { 47 | method?: string, body?: any, headers?: any, credentials?: any 48 | } 49 | 50 | export default function request(url: string, options: object) { 51 | const defaultOptions = { 52 | credentials: 'include', 53 | }; 54 | const newOptions: IMyOptionObj = { ...defaultOptions, ...options }; 55 | if (newOptions.method === 'POST' || newOptions.method === 'PUT') { 56 | if (!(newOptions.body instanceof FormData)) { 57 | newOptions.headers = { 58 | Accept: 'application/json', 59 | 'Content-Type': 'application/json; charset=utf-8', 60 | ...newOptions.headers, 61 | }; 62 | newOptions.body = JSON.stringify(newOptions.body); 63 | } else { 64 | // newOptions.body is FormData 65 | newOptions.headers = { 66 | Accept: 'application/json', 67 | ...newOptions.headers, 68 | }; 69 | } 70 | } 71 | 72 | return fetch(url, newOptions) 73 | .then(checkStatus) 74 | .then(response => { 75 | if (newOptions.method === 'DELETE' || response.status === 204) { 76 | return Promise.resolve(response.text()); 77 | } 78 | return Promise.resolve(response.json()); 79 | }) 80 | .catch(err => { 81 | const status = err.name; 82 | if (status === 401) { 83 | myHistory.push('/user/login'); 84 | return; 85 | } 86 | if (status === 403) { 87 | myHistory.push('/exception/403'); 88 | return; 89 | } 90 | if (status <= 504 && status >= 500) { 91 | myHistory.push('/exception/500'); 92 | return; 93 | } 94 | if (status >= 404 && status < 422) { 95 | myHistory.push('/exception/404'); 96 | } 97 | }); 98 | } 99 | -------------------------------------------------------------------------------- /src/utils/utils.less: -------------------------------------------------------------------------------- 1 | .textOverflow() { 2 | overflow: hidden; 3 | text-overflow: ellipsis; 4 | word-break: break-all; 5 | white-space: nowrap; 6 | } 7 | 8 | .textOverflowMulti(@line: 3, @bg: #fff) { 9 | overflow: hidden; 10 | position: relative; 11 | line-height: 1.5em; 12 | max-height: @line * 1.5em; 13 | text-align: justify; 14 | margin-right: -1em; 15 | padding-right: 1em; 16 | &:before { 17 | background: @bg; 18 | content: '...'; 19 | padding: 0 1px; 20 | position: absolute; 21 | right: 14px; 22 | bottom: 0; 23 | } 24 | &:after { 25 | background: white; 26 | content: ''; 27 | margin-top: 0.2em; 28 | position: absolute; 29 | right: 14px; 30 | width: 1em; 31 | height: 1em; 32 | } 33 | } 34 | 35 | // mixins for clearfix 36 | // ------------------------ 37 | .clearfix() { 38 | zoom: 1; 39 | &:before, 40 | &:after { 41 | content: ' '; 42 | display: table; 43 | } 44 | &:after { 45 | clear: both; 46 | visibility: hidden; 47 | font-size: 0; 48 | height: 0; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", //你的工程src根目录 4 | "traceResolution": false, //在debug的时候可以设置为true,这个属性的具体用法见下文 5 | "sourceMap": true, //这个都知道,debug的时候打开吧 6 | "allowJs": true, //是否允许工程中js和ts同时存在。 7 | "checkJs": true, //是否对js文件开启静态检查,如果true的话,你的js文件中就可能很多红色的波浪线了。 8 | "jsx": "react", //react工程必备 9 | "target": "es5", //编译的目标语言,当然是最老的es5 10 | "module": "ESNext", //模块引入方式,如果你想用import的话 11 | "moduleResolution": "node", //模块搜索方式,按照node的来,一般没有说明异议 12 | "allowSyntheticDefaultImports": true, //允许从没有默认导出的模块进行默认导入。这不会影响代码发出,只会影响类型检查。 13 | "noImplicitAny": false, //使用隐含的“any”类型警告表达式和声明。 14 | "noUnusedLocals": false, // true: 如果有未使用的块级变量,编译器会报错。 15 | "noUnusedParameters": false, // true: 如果有未使用的参数,编译器会报错。鉴于js的动态性,这个我一般关掉 16 | "removeComments": false, // 删除注释,debug的时候不开启 17 | "preserveConstEnums": false, // 不要在生成的代码中擦除const枚举声明 18 | "skipLibCheck": false, // 跳过lib文件的静态检查,哎,不是所有的lib都给你写得规规整整的。 19 | "experimentalDecorators": true, // 要实用装饰器语法的话,打开该项 20 | "emitDecoratorMetadata": true, 21 | "lib": [ 22 | "es6", 23 | "dom" 24 | ] 25 | }, 26 | "include": [ 27 | // "./src/**/*" 28 | "./src/index.tsx" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], 3 | "linterOptions": { 4 | "exclude": [ 5 | "./public/", 6 | "./node_modules/", 7 | "./src/theme.js" 8 | ] 9 | }, 10 | "rules": { 11 | "no-console": [ 12 | true, 13 | { 14 | "allow": [ 15 | "warn", 16 | "error", 17 | "debug", 18 | "info" 19 | ] 20 | } 21 | ], 22 | "ordered-imports": false, // import 排序 23 | "object-literal-sort-keys": false, // obj key 排序 24 | "jsx-no-lambda": false, // 行内箭头函数 25 | "member-ordering": false, // 强制声明类型一致 26 | // "no-unused-variable": [true, {"ignore-pattern": "pattern"}], // 禁止未使用的import 27 | "no-string-literal": false // object不可以通过string访问 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); 3 | const tsImportPluginFactory = require("ts-import-plugin"); 4 | // const entryPath = path.join(__dirname, './src'); 5 | 6 | const outputPath = path.join(__dirname, "./dist"); 7 | 8 | const commonConfig = { 9 | // mode: 'none', 10 | output: { 11 | // Next line is not used in dev but WebpackDevServer crashes without it: 12 | path: outputPath, 13 | // This does not produce a real file. It's just the virtual path that is 14 | // served by WebpackDevServer in development. This is the JS bundle 15 | // containing code from all our entry points, and the Webpack runtime. 16 | filename: "[name].[hash].js", 17 | // There are also additional JS chunk files if you use code splitting. 18 | chunkFilename: "[name].[hash].chunk.js", 19 | // Point sourcemap entries to original disk location (format as URL on Windows) 20 | devtoolModuleFilenameTemplate: info => 21 | path.resolve(info.absoluteResourcePath).replace(/\\/g, "/") 22 | }, 23 | resolve: { 24 | alias: { 25 | // 目录统一变量 26 | img: path.resolve("public/img/") 27 | // 'components': path.resolve('src/components/'), 28 | }, 29 | extensions: [ 30 | ".mjs", 31 | ".web.ts", 32 | ".ts", 33 | ".web.tsx", 34 | ".tsx", 35 | ".web.js", 36 | ".js", 37 | ".json", 38 | ".web.jsx", 39 | ".jsx" 40 | ], 41 | plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })] 42 | }, 43 | module: { 44 | rules: [ 45 | { 46 | oneOf: [ 47 | { 48 | test: /\.(ts|tsx)$/, 49 | exclude: [/node_modules/, /lib/], 50 | use: [ 51 | { 52 | loader: require.resolve("ts-loader"), 53 | options: { 54 | // disable type checker - we will use it in fork plugin 55 | transpileOnly: true, 56 | getCustomTransformers: () => ({ 57 | before: [ 58 | tsImportPluginFactory({ 59 | libraryDirectory: "lib", 60 | libraryName: "antd", 61 | style: true 62 | }) 63 | ] 64 | }), 65 | compilerOptions: { 66 | module: "es2015" 67 | } 68 | } 69 | } 70 | ] 71 | }, 72 | { 73 | enforce: "pre", 74 | test: /\.js$/, 75 | exclude: [/node_modules/, /lib/], 76 | loader: require.resolve("source-map-loader") 77 | }, 78 | { 79 | test: /\.html$/, 80 | loader: require.resolve("html-loader") 81 | }, 82 | { 83 | test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/, 84 | loader: require.resolve("file-loader"), 85 | options: { 86 | name: "fonts/[name].[ext]" 87 | } 88 | }, 89 | { 90 | test: /\.(png|jpg|gif)$/, 91 | use: { 92 | loader: require.resolve("file-loader"), 93 | options: { 94 | limit: 8192, 95 | name: "img/[name].[ext]" 96 | } 97 | } 98 | } 99 | ] 100 | } 101 | ] 102 | } 103 | }; 104 | 105 | module.exports = commonConfig; 106 | --------------------------------------------------------------------------------