├── .editorconfig ├── .env ├── .eslintrc ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── launch.json ├── README.md ├── config ├── config.js ├── menu.config.js ├── platform.config.js ├── theme.config.js └── versions.config.json ├── jsconfig.json ├── mock ├── .gitkeep ├── CryptoJS.js ├── global.js ├── login.js ├── menu.js ├── pathAnalysis.js ├── tree.js └── users.js ├── package.json ├── public └── favicon.ico ├── src ├── app.js ├── assets │ ├── d3tree.png │ ├── demo.png │ ├── demo_login.png │ ├── frame.png │ ├── logo.png │ ├── logo_blue_1024.png │ ├── qrcode_for_wechat.jpg │ ├── regionalAnalysis.png │ ├── vew1.png │ └── view3.png ├── components │ ├── Announcement │ │ └── index.js │ ├── Breadcrumb │ │ ├── index.js │ │ └── index.less │ ├── CanvasChart │ │ ├── WaterWave │ │ │ ├── index.js │ │ │ └── index.less │ │ └── autoHeight.js │ ├── Consumer │ │ └── index.js │ ├── D3Chart │ │ ├── Sankey │ │ │ └── index.js │ │ └── Tree │ │ │ ├── _.js │ │ │ └── index.js │ ├── ECharts │ │ ├── A_basic │ │ │ ├── .test.js │ │ │ ├── dataZoom.js │ │ │ ├── dataset.js │ │ │ ├── index.js │ │ │ ├── index.propTypes.js │ │ │ ├── series.js │ │ │ ├── setY2Series.js │ │ │ ├── tooltip.js │ │ │ ├── type.js │ │ │ ├── xAxis.js │ │ │ └── yAxis.js │ │ ├── B_basic │ │ │ ├── .test.js │ │ │ ├── dataset.js │ │ │ ├── index.js │ │ │ ├── index.propTypes.js │ │ │ ├── series.js │ │ │ ├── tooltip.js │ │ │ └── type.js │ │ ├── __tests__ │ │ │ └── index.test.js │ │ ├── charts │ │ │ ├── Area │ │ │ │ └── index.js │ │ │ ├── Bar │ │ │ │ └── index.js │ │ │ ├── Funnel │ │ │ │ └── index.js │ │ │ ├── Line │ │ │ │ └── index.js │ │ │ ├── Pie-doughnut │ │ │ │ └── index.js │ │ │ ├── Pie │ │ │ │ └── index.js │ │ │ ├── Radar │ │ │ │ └── index.js │ │ │ ├── Sankey │ │ │ │ └── index.js │ │ │ ├── bar-y │ │ │ │ └── index.js │ │ │ ├── candlestick │ │ │ │ └── index.js │ │ │ └── chinaMap │ │ │ │ ├── index.js │ │ │ │ └── index.test.js │ │ ├── components │ │ │ ├── grid.js │ │ │ ├── grid.propTypes.js │ │ │ ├── legend.js │ │ │ ├── legend.propTypes.js │ │ │ ├── title.js │ │ │ ├── title.propTypes.js │ │ │ ├── toolbox.js │ │ │ └── toolbox.propTypes.js │ │ ├── config.js │ │ ├── core │ │ │ └── index.js │ │ ├── index.js │ │ ├── mapData │ │ │ └── china.json │ │ └── methods │ │ │ ├── .test.js │ │ │ ├── _formatNumer.js │ │ │ ├── _isData.js │ │ │ ├── _toDataset_A.js │ │ │ ├── _toDataset_B.js │ │ │ └── index.js │ ├── Exception │ │ ├── index.js │ │ ├── index.less │ │ └── typeConfig.js │ ├── GlobalDrawer │ │ └── index.js │ ├── HeaderSearch │ │ ├── index.js │ │ └── index.less │ ├── Icon │ │ └── index.js │ ├── Loader │ │ ├── Loader.js │ │ ├── Loader.less │ │ └── package.json │ ├── PageHeader │ │ ├── breadcrumb.js │ │ ├── index.js │ │ └── index.less │ ├── PageLoading │ │ ├── index.js │ │ └── index.less │ ├── PageWrapper │ │ ├── index.js │ │ └── index.less │ ├── Register │ │ ├── index.js │ │ └── index.less │ ├── ResetPassword │ │ ├── index.js │ │ └── index.less │ ├── dataTable │ │ ├── _.js │ │ ├── header.js │ │ ├── index.js │ │ ├── search.js │ │ ├── select.js │ │ └── tableFooter.js │ └── index.js ├── global.css ├── global.less ├── layouts │ ├── Context.js │ ├── __tests__ │ │ └── index.test.js │ ├── basic │ │ ├── Footer │ │ │ └── index.js │ │ ├── index.js │ │ └── index.less │ ├── components │ │ ├── Authorized │ │ │ └── index.js │ │ ├── GlobalDownload │ │ │ └── index.js │ │ ├── GlobalFooter │ │ │ ├── demo │ │ │ │ └── basic.md │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ └── index.md │ │ ├── GlobalHeader │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── GlobalRoll │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── GlobalSearch │ │ │ ├── _.js │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── GlobalUserCenter │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── HeaderDropdown │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── Menus │ │ │ ├── _.js │ │ │ └── index.js │ │ ├── Notice │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ └── index.less │ │ └── SelectLang │ │ │ ├── index.js │ │ │ └── index.less │ ├── constant.js │ ├── index.js │ └── platform │ │ ├── Footer │ │ ├── index.js │ │ └── index.less │ │ ├── header.js │ │ ├── index.js │ │ ├── index.less │ │ ├── logo.js │ │ └── startedModal.js ├── locales │ ├── en-US.js │ ├── en-US │ │ ├── form.js │ │ ├── gitDataV.js │ │ ├── login.js │ │ └── platform.js │ ├── zh-CN.js │ └── zh-CN │ │ ├── form.js │ │ ├── gitDataV.js │ │ ├── login.js │ │ └── platform.js ├── models │ ├── .gitkeep │ ├── global.js │ └── menu.js ├── pages │ ├── 403.js │ ├── 404.js │ ├── 500.js │ ├── __tests__ │ │ ├── __mocks__ │ │ │ └── umi-plugin-locale.js │ │ └── index.test.js │ ├── document.ejs │ ├── download │ │ └── index.js │ ├── exception │ │ └── $code.js │ ├── frame │ │ ├── $key.js │ │ ├── index.js │ │ └── index.less │ ├── index.js │ ├── login │ │ ├── components │ │ │ └── Login │ │ │ │ ├── index.js │ │ │ │ ├── index.less │ │ │ │ ├── loginQrcode.js │ │ │ │ └── loginQrcode.less │ │ ├── index.js │ │ ├── index.less │ │ ├── model.js │ │ └── service.js │ ├── register │ │ ├── index.css │ │ └── index.js │ ├── resetPassword │ │ ├── index.css │ │ └── index.js │ ├── sys │ │ ├── echarts │ │ │ ├── area.js │ │ │ ├── bar.js │ │ │ ├── components │ │ │ │ ├── config.json │ │ │ │ ├── form.js │ │ │ │ ├── option.js │ │ │ │ ├── option.less │ │ │ │ └── panel.js │ │ │ ├── funnel.js │ │ │ ├── index.js │ │ │ ├── line.js │ │ │ ├── model.js │ │ │ ├── pie.js │ │ │ ├── pieDoughnut.js │ │ │ ├── sankey.js │ │ │ ├── services │ │ │ │ ├── data01.json │ │ │ │ ├── data02.json │ │ │ │ └── data03.json │ │ │ └── yBar.js │ │ ├── githubpro │ │ │ ├── $id │ │ │ │ ├── index.css │ │ │ │ └── index.js │ │ │ ├── components │ │ │ │ ├── account.js │ │ │ │ ├── content.js │ │ │ │ ├── dataTable.js │ │ │ │ ├── footerTable.js │ │ │ │ ├── getStarHistory.js │ │ │ │ ├── header.js │ │ │ │ └── table.less │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ ├── model.js │ │ │ └── service.js │ │ ├── index.js │ │ ├── pathAnalysis │ │ │ ├── components │ │ │ │ ├── form.js │ │ │ │ └── tabs.js │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ ├── model.js │ │ │ └── service.js │ │ ├── regionalAnalysis │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ ├── model.js │ │ │ └── service.js │ │ ├── sankeyPage │ │ │ ├── index.js │ │ │ └── model.js │ │ ├── treePage │ │ │ ├── index.js │ │ │ ├── model.js │ │ │ └── service.js │ │ ├── users │ │ │ ├── $id │ │ │ │ ├── index.css │ │ │ │ └── index.js │ │ │ ├── components │ │ │ │ └── Modal.js │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ ├── model.js │ │ │ └── service.js │ │ └── view │ │ │ ├── components │ │ │ ├── DateDickers.js │ │ │ ├── view.js │ │ │ └── view1.js │ │ │ ├── index.js │ │ │ ├── model.js │ │ │ ├── p1.js │ │ │ ├── p2.js │ │ │ └── service.js │ └── versions │ │ ├── components │ │ ├── version.css │ │ ├── version.js │ │ └── version.less │ │ ├── index.css │ │ ├── index.js │ │ └── index.less ├── services │ ├── global.js │ ├── index.js │ └── menu.js ├── themes │ ├── index.less │ ├── mixin.less │ └── vars.less └── utils │ ├── CryptoJS.js │ ├── _.js │ ├── index.js │ ├── request.js │ └── umiRequest.js └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | BROWSER=none 2 | ESLINT=1 3 | PORT=8088 -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-umi", 3 | "rules": {}, 4 | "parserOptions": { 5 | "ecmaFeatures": { 6 | "experimentalObjectRestSpread": true 7 | } 8 | }, 9 | "globals": { 10 | } 11 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /npm-debug.log* 6 | /yarn-error.log 7 | /yarn.lock 8 | /package-lock.json 9 | # 测试页 10 | /src/pages/test.js 11 | # production 12 | /dist 13 | 14 | # misc 15 | .DS_Store 16 | 17 | # umi 18 | .umi 19 | .umi-production 20 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg 3 | **/*.ejs 4 | **/*.html 5 | package.json 6 | .umi 7 | .umi-production 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 100, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceFolder}\\dev", 12 | "serverReadyAction": { 13 | "action": "openExternally" 14 | } 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /config/config.js: -------------------------------------------------------------------------------- 1 | 2 | // ref: https://umijs.org/config/ 3 | import { resolve } from "path"; 4 | import theme from "./theme.config" 5 | 6 | 7 | export default { 8 | base: '/', 9 | treeShaking: true,//用于描述移除 JavaScript 上下文中的未引用代码 10 | history: 'hash',//hash路由 11 | hash: true,//生成hash文件名 12 | // disableRedirectHoist: true,//禁用 redirect 上提。 13 | // devtool: 'source-map',//生成map文件 14 | targets: {//兼容浏览器版本 15 | // ie: 11, 16 | }, 17 | // 配置模块不打入代码 18 | externals: { 19 | // echarts: 'window.echarts', 20 | d3: 'window.d3', 21 | }, 22 | plugins: [ 23 | // ref: https://umijs.org/plugin/umi-plugin-react.html 24 | ['umi-plugin-react', { 25 | antd: true, 26 | dva: true, 27 | dynamicImport: { 28 | webpackChunkName: true, 29 | loadingComponent: './components/PageLoading/index.js' 30 | }, 31 | title: 'antd-umi-2.6', 32 | dll: true, 33 | locale: { 34 | enable: true, 35 | default: 'zh-CN',//'en-US', 36 | }, 37 | routes: { 38 | exclude: [ 39 | /models\//, 40 | /services\//, 41 | /model\.(t|j)sx?$/, 42 | /service\.(t|j)sx?$/, 43 | /components\//, 44 | ], 45 | }, 46 | // cdn 47 | scripts: [ 48 | // { src: 'https://cdn.bootcss.com/echarts/4.2.1/echarts.min.js' }, 49 | 50 | { src: 'https://cdn.bootcss.com/d3/5.9.2/d3.min.js' }, 51 | ], 52 | }], 53 | ], 54 | alias: { 55 | "@": resolve(__dirname, "../src"), 56 | '@utils': resolve(__dirname, "../src/utils"), 57 | '@context': resolve(__dirname, "../src/layouts/Context"), 58 | // 组件库 59 | '@components': resolve(__dirname, "../src/components"), 60 | // 系统配置 61 | '@platformConfig': resolve(__dirname, "./platform.config"), 62 | // 全局services 63 | '@services': resolve(__dirname, "../src/services"), 64 | // 全局models 65 | '@models': resolve(__dirname, "../src/models"), 66 | //菜单配置项 67 | "@menuConfig": resolve(__dirname, "./menu.config.js"), 68 | // 版本日志管理 69 | "@versionsConfig": resolve(__dirname, './versions.config.json'), 70 | // request请求 71 | "@http": resolve(__dirname, '../src/utils/request.js') 72 | }, 73 | theme, 74 | proxy: { 75 | "/api": { 76 | target: "", 77 | changeOrigin: true, 78 | pathRewrite: { "^/api": "" } 79 | } 80 | }, 81 | } 82 | -------------------------------------------------------------------------------- /config/platform.config.js: -------------------------------------------------------------------------------- 1 | 2 | // 菜单权限:true(开启) false(关闭) 3 | // let GLOBAL_MENU_PERMISSION; 4 | // if (process && process.env.NODE_ENV === 'development') { 5 | // GLOBAL_MENU_PERMISSION = false; 6 | // } else { 7 | // GLOBAL_MENU_PERMISSION = true; 8 | // } 9 | 10 | module.exports = { 11 | // 数据请求api 12 | apiPrefix: document.head.dataset.api || '', 13 | iframePrefix: document.head.dataset.iframe || '', 14 | loginLogo: 'logo_blue_1024.png', 15 | sysLogo: 'logo.png', 16 | // 登录页名称 17 | loginName: '数据平台', 18 | // 系统名称 19 | sysName: 'TEST-Pro', 20 | // 版权 21 | copyright: "2019 mpw0311@163.com.", 22 | // 是否开启菜单权限校验 23 | menuPermission: true, 24 | // table默认一页条数 25 | pageSize: 10, 26 | // iconFont 地址 27 | iconUrl: '//at.alicdn.com/t/font_1030595_depmdbpf3yc.js', 28 | // 系统默认首页 29 | sysDefultPage: { 30 | pathname: '/sys/githubpro', 31 | state: { 32 | key: 'gitDataV', 33 | pathtitles: [{ title: 'gitDataV', icon: 'github' }], 34 | } 35 | }, 36 | }; -------------------------------------------------------------------------------- /config/theme.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "@font-family": "Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', SimSun, sans-serif", 3 | // "@border-color-split": 'hsv(0, 0, 89%)',// split border inside a component 4 | "@border-radius-base": "5px", 5 | "@header-height": "64px", 6 | "@font-size-base": "12px", 7 | "@content-color": "rgb(240, 242, 245)", 8 | "@shadow-1": "4px 4px 20px 0 rgba(0, 0, 0, 0.01)", 9 | "@shadow-2": "4px 4px 40px 0 rgba(0, 0, 0, 0.05)", 10 | "@line-height-base": "1.2", 11 | // "@menu-dark-bg": "#333" 12 | }; 13 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "emitDecoratorMetadata": true, 4 | "experimentalDecorators": true, 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /mock/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpw0311/antd-umi-sys/55ae3bab87d43dcc64e9471f1a9c8cb757cb303d/mock/.gitkeep -------------------------------------------------------------------------------- /mock/CryptoJS.js: -------------------------------------------------------------------------------- 1 | import { AES, enc, mode, pad } from 'crypto-js'; 2 | /** 3 | * 初始化秘钥 4 | * @param {*} key |密钥 16 位 5 | * @param {*} iv |初始向量 initial vector 16 位,key 和 iv 可以一致 6 | */ 7 | const _init = (key = 'ANTD-#6$8Y-oi5&K', iv = key) => { 8 | return { 9 | key: enc.Utf8.parse(key), 10 | iv: enc.Utf8.parse(iv) 11 | }; 12 | }; 13 | 14 | /** 15 | * 加密 16 | * @param {string} str 17 | */ 18 | export const encrypt = (str, aes_key) => { 19 | const { key, iv } = _init(aes_key); 20 | const encrypted = AES.encrypt(str, key, { 21 | iv, 22 | mode: mode.CBC, 23 | padding: pad.Pkcs7 24 | }); 25 | // 转换为字符串 26 | return encrypted.toString(); 27 | }; 28 | /** 29 | * 解密 30 | * @param {string} encrypted 31 | */ 32 | export const decrypt = (encrypted, aes_key) => { 33 | const { key, iv } = _init(aes_key); 34 | const decrypted = AES.decrypt(encrypted, key, { 35 | iv, 36 | mode: mode.CBC, 37 | padding: pad.Pkcs7 38 | }); 39 | 40 | // 转换为 utf8 字符串 41 | return enc.Utf8.stringify(decrypted); 42 | }; -------------------------------------------------------------------------------- /mock/global.js: -------------------------------------------------------------------------------- 1 | const Mock = require('mockjs'); 2 | 3 | const { Random } = Mock; 4 | const info = Mock.mock({ 5 | data: { 6 | userInfo: { 7 | userName: Random.name(), 8 | }, 9 | message: { 10 | news: Random.cparagraph(17, 30), 11 | 'count|18-32': 1 12 | }, 13 | }, 14 | status: 0 15 | }); 16 | module.exports = { 17 | [`POST /getSysInfo`](req, res) { 18 | res.status(200).json(info); 19 | }, 20 | [`GET /logout`](req, res) { 21 | res.status(200).json({ 22 | data: { 23 | message: "退出登录成功!" 24 | }, 25 | status: 0 26 | }); 27 | }, 28 | [`POST /getMessage`](req, res) { 29 | res.status(200).json({ 30 | data: [ 31 | { 32 | title: 'Ant Design Title 1', 33 | description: "Ant Design, a design language for background applications, is refined by Ant UED Team", 34 | type: 'read' 35 | }, 36 | { 37 | title: 'Ant Design Title 2', 38 | description: "Ant Design, a design language for background applications, is refined by Ant UED Team", 39 | type: 'unread' 40 | }, 41 | { 42 | title: 'Ant Design Title 3', 43 | description: "Ant Design, a design language for background applications, is refined by Ant UED Team", 44 | type: 'unread' 45 | }, 46 | { 47 | title: 'Ant Design Title 4', 48 | description: "Ant Design, a design language for background applications, is refined by Ant UED Team", 49 | type: 'unread' 50 | }, 51 | ], 52 | status: 0 53 | }); 54 | } 55 | }; -------------------------------------------------------------------------------- /mock/login.js: -------------------------------------------------------------------------------- 1 | const Mock = require('mockjs');//eslint-disable-line 2 | const { decrypt } = require('./CryptoJS'); 3 | console.log(decrypt); 4 | module.exports = { 5 | [`POST /login`](req, res) { 6 | const { body } = req; 7 | const { password, username } = body; 8 | const pwd = decrypt(password) 9 | if (pwd === 'admin123' && username === 'admin') { 10 | res.status(200).json({ 11 | data: { 12 | alertDesc: '登录成功!' 13 | }, 14 | status: 0 15 | }); 16 | } else { 17 | res.status(200).json({ 18 | data: { 19 | alertDesc: '账号或密码错误!' 20 | }, 21 | status: -1 22 | }); 23 | } 24 | }, 25 | }; -------------------------------------------------------------------------------- /mock/menu.js: -------------------------------------------------------------------------------- 1 | const Mock = require('mockjs'); 2 | const menuData = [ 3 | { 4 | title: "gitDataV", 5 | key: "gitDataV", 6 | }, 7 | { 8 | title: "地域分析", 9 | key: "regionalAnalysis", 10 | }, 11 | { 12 | title: "users", 13 | key: "users", 14 | }, 15 | { 16 | title: "404", 17 | key: "404", 18 | }, 19 | { 20 | title: "用户行为", 21 | key: "yonghuxingwei", 22 | children: [ 23 | { 24 | title: "路径分析", 25 | key: "pathAnalysis", 26 | }, 27 | { 28 | title: "view1", 29 | key: "p1", 30 | }, 31 | { 32 | title: "view2", 33 | key: "p2", 34 | }, 35 | ] 36 | }, 37 | { 38 | title: "echarts组件", 39 | key: "echarts", 40 | children: [ 41 | { 42 | key: 'Bar', 43 | title: 'Bar' 44 | }, 45 | { 46 | key: 'line', 47 | title: 'Line' 48 | }, 49 | { 50 | key: 'area', 51 | title: 'Area' 52 | }, 53 | { 54 | key: 'yBar', 55 | title: 'YBar' 56 | }, 57 | { 58 | key: 'funnel', 59 | title: 'Funnel' 60 | }, 61 | { 62 | key: 'pie', 63 | title: 'Pie' 64 | }, 65 | { 66 | key: 'pieDoughnut', 67 | title: 'PieDoughnut' 68 | }, 69 | { 70 | key: 'sankey', 71 | title: 'Sankey' 72 | }, 73 | ] 74 | }, 75 | { 76 | title: "d3.js组件", 77 | key: "d3Chart", 78 | children: [ 79 | { 80 | title: "树图", 81 | key: "treePage", 82 | }, 83 | ] 84 | }, 85 | { 86 | title: "iframe", 87 | key: "iframe", 88 | children: [ 89 | { 90 | title: "bing", 91 | key: "bing", 92 | } 93 | ] 94 | }, 95 | { 96 | title: "水印", 97 | key: "watermark", 98 | }, 99 | { 100 | title: "图形组件", 101 | key: "react-charts", 102 | }, 103 | { 104 | title: "请给star", 105 | key: "github", 106 | }, 107 | ]; 108 | const data = Mock.mock({ 109 | data: menuData, 110 | status: 0 111 | }); 112 | module.exports = { 113 | [`POST /getMenuData`](req, res) { 114 | res.status(200).json(data); 115 | }, 116 | }; -------------------------------------------------------------------------------- /mock/tree.js: -------------------------------------------------------------------------------- 1 | import { mock } from 'mockjs'; 2 | const getdata = () => { 3 | return mock({ 4 | 'time': '220ms', 5 | 'children|4-6': [ 6 | { 7 | 'name': '@name', 8 | 'value|500-1000': 600, 9 | 'time|1-200': 10, 10 | }, 11 | ], 12 | }); 13 | }; 14 | 15 | module.exports = { 16 | [`POST /api/treePage`](req, res) { 17 | res.status(200).json({ 18 | data: { 19 | ...getdata() 20 | }, 21 | status: 0 22 | }); 23 | } 24 | }; -------------------------------------------------------------------------------- /mock/users.js: -------------------------------------------------------------------------------- 1 | const Mock = require('mockjs'); 2 | 3 | const { Random } = Mock; 4 | let db = Mock.mock({ 5 | 'data|3-6': [{ 6 | id: '@id', 7 | email: '@email', 8 | name: '@name', 9 | website: Random.cparagraph(1), 10 | operation: '@operation', 11 | 'age|18-32': 1 12 | }], 13 | status: 0 14 | }); 15 | 16 | module.exports = { 17 | [`GET /api/users`](req, res) { 18 | res.setHeader('Access-Control-Allow-Origin', '*'); 19 | res.status(200).json(db); 20 | 21 | }, 22 | 23 | [`POST /api/users`](req, res) { 24 | res.setHeader('Access-Control-Allow-Origin', '*'); 25 | let user = req.body; 26 | if (typeof user === 'string') { 27 | user = JSON.parse(user) 28 | } 29 | user.id = Mock.mock('@id'); 30 | db.data.push(user); 31 | res.status(200).json(db); 32 | }, 33 | 34 | [`PATCH /api/users/:id`](req, res) { 35 | res.setHeader('Access-Control-Allow-Origin', '*'); 36 | const { params: { id }, query, body } = req; 37 | console.log(id, query); 38 | // db.data = db.data.map(item => item.id === id ? user : item); 39 | res.status(200).json(db); 40 | }, 41 | 42 | [`DELETE /api/users/:id`](req, res) { 43 | res.setHeader('Access-Control-Allow-Origin', '*'); 44 | const { params: { id } } = req; 45 | db.data = db.data.filter(item => item.id !== id); 46 | res.status(200).json(db); 47 | } 48 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "start": "umi dev", 5 | "build": "umi build", 6 | "test": "umi test", 7 | "lint": "eslint --ext .js src mock tests", 8 | "precommit": "lint-staged", 9 | "add-page": "umi block add https://github.com/mpw0311/umi-blocks/tree/master/newPage --path=/sys/newPage" 10 | }, 11 | "dependencies": { 12 | "antd": "^3.15.0", 13 | "axios": "^0.19.0", 14 | "crypto-js": "^3.1.9-1", 15 | "d3": "^5.9.2", 16 | "d3-sankey": "^0.12.1", 17 | "dva": "^2.5.0-beta.2", 18 | "echarts": "^4.2.1", 19 | "echarts-for-react": "^2.0.15-beta.0", 20 | "memoize-one": "^5.0.2", 21 | "path-to-regexp": "^3.0.0", 22 | "react": "^16.7.0", 23 | "react-container-query": "^0.11.0", 24 | "react-dom": "^16.7.0", 25 | "react-media": "^1.9.2", 26 | "umi-request": "^1.0.7" 27 | }, 28 | "devDependencies": { 29 | "babel-eslint": "^9.0.0", 30 | "eslint": "^5.4.0", 31 | "eslint-config-umi": "^1.4.0", 32 | "eslint-plugin-flowtype": "^2.50.0", 33 | "eslint-plugin-import": "^2.14.0", 34 | "eslint-plugin-jsx-a11y": "^5.1.1", 35 | "eslint-plugin-react": "^7.11.1", 36 | "husky": "^0.14.3", 37 | "lint-staged": "^7.2.2", 38 | "mockjs": "^1.0.1-beta3", 39 | "react-test-renderer": "^16.7.0", 40 | "umi": "^2.6.3", 41 | "umi-plugin-react": "^1.6.0" 42 | }, 43 | "lint-staged": { 44 | "*.{js,jsx}": [ 45 | "eslint --fix", 46 | "git add" 47 | ] 48 | }, 49 | "engines": { 50 | "node": ">=8.0.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpw0311/antd-umi-sys/55ae3bab87d43dcc64e9471f1a9c8cb757cb303d/public/favicon.ico -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | 2 | export const dva = { 3 | config: { 4 | onError(err) { 5 | err.preventDefault(); 6 | console.error(err.message); 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /src/assets/d3tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpw0311/antd-umi-sys/55ae3bab87d43dcc64e9471f1a9c8cb757cb303d/src/assets/d3tree.png -------------------------------------------------------------------------------- /src/assets/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpw0311/antd-umi-sys/55ae3bab87d43dcc64e9471f1a9c8cb757cb303d/src/assets/demo.png -------------------------------------------------------------------------------- /src/assets/demo_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpw0311/antd-umi-sys/55ae3bab87d43dcc64e9471f1a9c8cb757cb303d/src/assets/demo_login.png -------------------------------------------------------------------------------- /src/assets/frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpw0311/antd-umi-sys/55ae3bab87d43dcc64e9471f1a9c8cb757cb303d/src/assets/frame.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpw0311/antd-umi-sys/55ae3bab87d43dcc64e9471f1a9c8cb757cb303d/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/logo_blue_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpw0311/antd-umi-sys/55ae3bab87d43dcc64e9471f1a9c8cb757cb303d/src/assets/logo_blue_1024.png -------------------------------------------------------------------------------- /src/assets/qrcode_for_wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpw0311/antd-umi-sys/55ae3bab87d43dcc64e9471f1a9c8cb757cb303d/src/assets/qrcode_for_wechat.jpg -------------------------------------------------------------------------------- /src/assets/regionalAnalysis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpw0311/antd-umi-sys/55ae3bab87d43dcc64e9471f1a9c8cb757cb303d/src/assets/regionalAnalysis.png -------------------------------------------------------------------------------- /src/assets/vew1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpw0311/antd-umi-sys/55ae3bab87d43dcc64e9471f1a9c8cb757cb303d/src/assets/vew1.png -------------------------------------------------------------------------------- /src/assets/view3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpw0311/antd-umi-sys/55ae3bab87d43dcc64e9471f1a9c8cb757cb303d/src/assets/view3.png -------------------------------------------------------------------------------- /src/components/Announcement/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { Alert } from 'antd'; 8 | 9 | function Announcement(props) { 10 | const { 11 | handleClose = () => { }, 12 | title = "公告", 13 | description = "" 14 | } = props; 15 | const onClose = (e) => { 16 | handleClose(e); 17 | }; 18 | if (description !== "") { 19 | return ( 20 | 27 | ); 28 | } else { 29 | return (); 30 | } 31 | } 32 | export default Announcement; -------------------------------------------------------------------------------- /src/components/Breadcrumb/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 面包屑 6 | */ 7 | import React from 'react'; 8 | import { Breadcrumb } from 'antd'; 9 | import _ from 'lodash'; 10 | import styles from './index.less'; 11 | 12 | function MyBreadcrumb(props) { 13 | const { pathtitles, location = {} } = props; 14 | const { state: pathstate } = location; 15 | const { pathtitles: stateTitles } = pathstate || {}; 16 | const renderItem = (rows) => { 17 | if (_.isArray(rows)) { 18 | return rows.map((elem, key) => {elem}); 19 | } 20 | }; 21 | const getTitles = (stateTitles, pathtitles) => { 22 | if (pathtitles && pathtitles.length > 0 && stateTitles && stateTitles.length > 0) { 23 | return _.uniq([...pathtitles, ...stateTitles]); 24 | } else if (pathtitles && pathtitles.length > 0) { 25 | return pathtitles; 26 | } else if (stateTitles && stateTitles.length > 0) { 27 | return stateTitles; 28 | } 29 | }; 30 | const titles = getTitles(pathtitles, stateTitles); 31 | if (titles && titles.length > 0) { 32 | return ( 33 | 34 | {renderItem(titles)} 35 | 36 | ); 37 | } else { 38 | return false; 39 | } 40 | } 41 | 42 | export default MyBreadcrumb; 43 | -------------------------------------------------------------------------------- /src/components/Breadcrumb/index.less: -------------------------------------------------------------------------------- 1 | .content { 2 | background-color: @content-color; 3 | margin: 0; 4 | font-size: 14px; 5 | height: 28px; 6 | line-height: 25px; 7 | padding: 0 5px; 8 | } -------------------------------------------------------------------------------- /src/components/CanvasChart/WaterWave/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .waterWave { 4 | position: relative; 5 | display: inline-block; 6 | transform-origin: left; 7 | 8 | .text { 9 | position: absolute; 10 | top: 32px; 11 | left: 0; 12 | width: 100%; 13 | text-align: center; 14 | 15 | span { 16 | color: @text-color-secondary; 17 | font-size: 14px; 18 | line-height: 22px; 19 | } 20 | 21 | h4 { 22 | color: @heading-color; 23 | font-size: 24px; 24 | line-height: 32px; 25 | } 26 | } 27 | 28 | .waterWaveCanvasWrapper { 29 | transform: scale(0.5); 30 | transform-origin: 0 0; 31 | } 32 | } -------------------------------------------------------------------------------- /src/components/CanvasChart/autoHeight.js: -------------------------------------------------------------------------------- 1 | import { Component } 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 | let node = n; 16 | 17 | let height = computeHeight(node); 18 | 19 | while (!height) { 20 | node = node.parentNode; 21 | if (node) { 22 | height = computeHeight(node); 23 | } else { 24 | break; 25 | } 26 | } 27 | return height; 28 | } 29 | 30 | const autoHeight = () => WrappedComponent => 31 | class extends Component { 32 | state = { 33 | computedHeight: 0, 34 | } 35 | componentDidMount() { 36 | const { height } = this.props; 37 | if (!height) { 38 | const h = getAutoHeight(this.root); 39 | this.setState({ computedHeight: h }); 40 | } 41 | } 42 | handleRoot = node => { 43 | this.root = node; 44 | } 45 | render() { 46 | const { height } = this.props; 47 | const { computedHeight } = this.state; 48 | const h = height || computedHeight; 49 | return ( 50 |
{h > 0 && }
51 | ); 52 | } 53 | }; 54 | export default autoHeight; -------------------------------------------------------------------------------- /src/components/Consumer/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { PureComponent } from 'react'; 8 | import Context from '@context'; 9 | export default (WrappedComponent) => { 10 | return class extends PureComponent { 11 | render() { 12 | return ( 13 | 14 | {({ theme, location }) => ( 15 | < WrappedComponent theme={theme} location={location} {...this.props} /> 16 | )} 17 | 18 | ); 19 | } 20 | }; 21 | }; -------------------------------------------------------------------------------- /src/components/D3Chart/Tree/_.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpw0311/antd-umi-sys/55ae3bab87d43dcc64e9471f1a9c8cb757cb303d/src/components/D3Chart/Tree/_.js -------------------------------------------------------------------------------- /src/components/ECharts/A_basic/.test.js: -------------------------------------------------------------------------------- 1 | import Basic from './index'; 2 | import renderer from 'react-test-renderer'; 3 | import { mount } from 'enzyme'; 4 | describe('Basic', () => { 5 | const data = { 6 | columns: [ 7 | { 8 | field: "product", 9 | name: "分类", 10 | type: "string" 11 | }, 12 | { 13 | field: "2015", 14 | name: "2015", 15 | type: "number" 16 | }, 17 | { 18 | field: "2016", 19 | name: "2016", 20 | type: "number" 21 | }, 22 | { 23 | field: "2017", 24 | name: "2017", 25 | type: "number" 26 | } 27 | ], 28 | rows: [ 29 | { 30 | product: 'Matcha Latte', 31 | 2015: 43.3, 32 | 2016: 85.8, 33 | 2017: 93.7 34 | }, 35 | { 36 | product: 'Milk Tea', 37 | 2015: 83.1, 38 | 2016: 73.4, 39 | 2017: 55.1 40 | }, 41 | { 42 | product: 'Cheese Cocoa', 43 | 2015: 86.4, 44 | 2016: 65.2, 45 | 2017: 82.5 46 | }, 47 | { 48 | product: 'Walnut Brownie', 49 | 2015: 72.4, 50 | 2016: 53.9, 51 | 2017: 39.1 52 | }, 53 | ] 54 | }; 55 | test('Render A_basic', () => { 56 | const component = mount(); 57 | expect(component.exists()).toBe(true); 58 | expect(component.find('.echarts-for-react').length).toBe(1); 59 | expect(component.getDOMNode().style.height).toBe('300px'); 60 | expect(component.props().loading).toBe(false); 61 | expect(component.props().type.toLowerCase()).toBe('line'); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /src/components/ECharts/A_basic/dataZoom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | * @git 7 | */ 8 | export default (props) => { 9 | const { dataZoom, showDataZoom } = props; 10 | if (showDataZoom) { 11 | return dataZoom || [ 12 | { 13 | show: true, 14 | realtime: true, 15 | start: 0, 16 | end: 100 17 | }, 18 | { 19 | type: 'inside', 20 | realtime: true, 21 | start: 0, 22 | end: 100 23 | } 24 | ]; 25 | } 26 | }; -------------------------------------------------------------------------------- /src/components/ECharts/A_basic/dataset.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | export default (props) => { 8 | const { dataSource } = props; 9 | return { 10 | source: dataSource 11 | }; 12 | }; -------------------------------------------------------------------------------- /src/components/ECharts/A_basic/series.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.1 5 | * @description 6 | */ 7 | import getType from './type'; 8 | import setY2Series from './setY2Series'; 9 | export default (props) => { 10 | const { 11 | series, 12 | dataSource, 13 | type, 14 | seriesLayoutBy, 15 | seriesSettings, 16 | showY2, 17 | stack, 18 | showLabel, 19 | labelPosition, 20 | maxPoint, 21 | minPoint, 22 | averageLine 23 | } = props; 24 | const setting = { ...seriesSettings }; 25 | if (maxPoint === true) { 26 | const { markPoint = {} } = setting; 27 | const { data = [] } = markPoint; 28 | markPoint.data = [...data, { type: 'max', name: '最大值' }]; 29 | setting.markPoint = markPoint; 30 | } 31 | if (minPoint === true) { 32 | const { markPoint = {} } = setting; 33 | const { data = [] } = markPoint; 34 | markPoint.data = [...data, { type: 'min', name: '最小值' }]; 35 | setting.markPoint = markPoint; 36 | } 37 | if (averageLine === true) { 38 | const { markLine = {} } = setting; 39 | const { data = [] } = markLine; 40 | markLine.data = [...data, { type: 'min', name: '最小值' }]; 41 | setting.markLine = markLine; 42 | } 43 | const _getSeries = () => { 44 | const _series = []; 45 | const len = seriesLayoutBy === 'row' ? dataSource.length - 1 : dataSource[0].length - 1; 46 | for (let i = 0; i < len; i++) { 47 | _series.push({ 48 | type: getType(type), 49 | stack: stack === true ? '总量' : null, 50 | ...setting, 51 | seriesLayoutBy, 52 | label: { 53 | normal: { 54 | show: showLabel, 55 | position: labelPosition 56 | } 57 | } 58 | }); 59 | } 60 | return _series; 61 | }; 62 | const y1Series = series || _getSeries(); 63 | if (showY2 === true) { 64 | return setY2Series({ ...props, series: y1Series }); 65 | } 66 | return y1Series; 67 | }; -------------------------------------------------------------------------------- /src/components/ECharts/A_basic/setY2Series.js: -------------------------------------------------------------------------------- 1 | export default (props) => { 2 | const { 3 | series, 4 | dataSource, 5 | seriesLayoutBy, 6 | showY2, 7 | Y2SeriesIndex, 8 | Y2SeriesName, 9 | Y2Series, 10 | Y2Type, 11 | } = props; 12 | 13 | //通过series的name属性查找对应的index 14 | const findIndexByName = (dataSource, name, type) => { 15 | if (seriesLayoutBy === 'row') { 16 | const index = dataSource.findIndex(curr => curr[0] === name) - 1; 17 | if (index < 0) { 18 | console.warn(`echarts-series:未找到要匹配的Y2值(${name})`); 19 | } 20 | return { 21 | name, 22 | type, 23 | index: index 24 | }; 25 | } else { 26 | const index = dataSource[0].indexOf(name) - 1; 27 | if (index < 0) { 28 | console.warn(`echarts-series:未找到要匹配的Y2值(${name})`); 29 | } 30 | return { 31 | name, 32 | type, 33 | index: index 34 | }; 35 | } 36 | }; 37 | /** 38 | * 生成对应的y2轴对应index索引 39 | * index第一个索引从0开始 40 | * Y2Series:[ 41 | * { 42 | * type: 'bar', 43 | * index: 2, 44 | * }, 45 | * { 46 | * type: 'bar', 47 | * name: 'Cheese Cocoa' 48 | * } 49 | * ] 50 | */ 51 | const GetY2Series = (dataSource, Y2Series) => { 52 | return Y2Series.map(item => { 53 | if (!(dataSource && dataSource[0] && item.index === undefined)) return item; 54 | return findIndexByName(dataSource, item.name, item.type); 55 | }) 56 | .filter(item => item.index > -1); 57 | }; 58 | //设置y2轴series 59 | const setY2Series = (_Y2Series) => { 60 | _Y2Series.forEach(item => { 61 | const { type, index } = item; 62 | series[index] = { ...series[index], type, yAxisIndex: 1 }; 63 | }); 64 | }; 65 | if (showY2 === true) { 66 | if (Y2Series) { 67 | const _Y2Series = GetY2Series(dataSource, Y2Series); 68 | setY2Series(_Y2Series); 69 | } else if (Y2SeriesName && Array.isArray(Y2SeriesName)) { 70 | const _Y2SeriesName = Y2SeriesName.map(item => findIndexByName(dataSource, item, Y2Type)); 71 | setY2Series(_Y2SeriesName); 72 | 73 | } else if (Y2SeriesIndex && Array.isArray(Y2SeriesIndex)) { 74 | const _Y2SeriesIndex = Y2SeriesIndex.map(index => ({ 75 | index, 76 | type: Y2Type, 77 | })); 78 | setY2Series(_Y2SeriesIndex); 79 | } 80 | } 81 | return series; 82 | }; 83 | -------------------------------------------------------------------------------- /src/components/ECharts/A_basic/tooltip.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | export default (props) => { 8 | const { tooltip, showTooltip, axisPointer } = props; 9 | const cross = { 10 | type: 'cross', 11 | label: { 12 | backgroundColor: '#6a7985' 13 | } 14 | }; 15 | const shadow = { 16 | type: 'shadow' // 默认为直线,可选为:'line' | 'shadow' 17 | }; 18 | return { 19 | show:showTooltip, 20 | trigger: 'axis', 21 | axisPointer: axisPointer === 'cross' ? cross : axisPointer === 'shadow' ? shadow : undefined, 22 | ...tooltip 23 | }; 24 | }; -------------------------------------------------------------------------------- /src/components/ECharts/A_basic/type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | export default (type) => { 8 | const _type = type.toLowerCase(); 9 | switch (_type) { 10 | case 'area': 11 | return 'line'; 12 | case 'bar-y': 13 | return 'bar'; 14 | default: 15 | return type; 16 | } 17 | }; -------------------------------------------------------------------------------- /src/components/ECharts/A_basic/xAxis.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | export default (props) => { 8 | const { xAxis, xAxisRotate, interval } = props; 9 | return { 10 | type: 'category', 11 | axisLabel: { 12 | interval, 13 | rotate: xAxisRotate, 14 | // formatter: function (value, index) { 15 | // return value.length > 10 ? value.slice(0, 10) + '…' : value; 16 | // } 17 | }, 18 | ...xAxis 19 | }; 20 | }; -------------------------------------------------------------------------------- /src/components/ECharts/A_basic/yAxis.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | export default (props) => { 8 | const { yAxis, YName, YUnit, showY2, Y2Name, Y2Unit, showY2SplitLine } = props; 9 | const YFormatter = YUnit ? { formatter: `{value}${YUnit}` } : {}; 10 | const Y2Formatter = Y2Unit ? { formatter: `{value}${Y2Unit}` } : {}; 11 | if (showY2) { 12 | return [ 13 | { 14 | name: YName, 15 | axisLabel: { 16 | ...YFormatter 17 | }, 18 | }, 19 | { 20 | name: Y2Name, 21 | axisLabel: { 22 | ...Y2Formatter 23 | }, 24 | splitLine: { 25 | show: showY2SplitLine, 26 | lineStyle: { 27 | type: 'dashed', 28 | color: '#ddd' 29 | } 30 | } 31 | } 32 | 33 | ]; 34 | } else { 35 | return { 36 | name: YName, 37 | axisLabel: { 38 | ...YFormatter 39 | }, 40 | ...yAxis, 41 | }; 42 | } 43 | }; -------------------------------------------------------------------------------- /src/components/ECharts/B_basic/.test.js: -------------------------------------------------------------------------------- 1 | import Basic from './index'; 2 | import renderer from 'react-test-renderer'; 3 | import { mount } from 'enzyme'; 4 | describe('Basic', () => { 5 | const data = { 6 | columns: [ 7 | { 8 | field: "product", 9 | name: "分类", 10 | type: "string" 11 | }, 12 | { 13 | field: "2015", 14 | name: "2015", 15 | type: "number" 16 | }, 17 | { 18 | field: "2016", 19 | name: "2016", 20 | type: "number" 21 | }, 22 | { 23 | field: "2017", 24 | name: "2017", 25 | type: "number" 26 | } 27 | ], 28 | rows: [ 29 | { 30 | product: 'Matcha Latte', 31 | 2015: 43.3, 32 | 2016: 85.8, 33 | 2017: 93.7 34 | }, 35 | { 36 | product: 'Milk Tea', 37 | 2015: 83.1, 38 | 2016: 73.4, 39 | 2017: 55.1 40 | }, 41 | { 42 | product: 'Cheese Cocoa', 43 | 2015: 86.4, 44 | 2016: 65.2, 45 | 2017: 82.5 46 | }, 47 | { 48 | product: 'Walnut Brownie', 49 | 2015: 72.4, 50 | 2016: 53.9, 51 | 2017: 39.1 52 | }, 53 | ] 54 | }; 55 | test('Render B_basic', () => { 56 | const setttings = { 57 | radius: '55%', 58 | center: ['40%', '50%'], 59 | }; 60 | const component = mount(); 61 | expect(component.exists()).toBe(true); 62 | expect(component.find('.echarts-for-react').length).toBe(1); 63 | expect(component.getDOMNode().style.height).toBe('300px'); 64 | expect(component.props().loading).toBe(false); 65 | expect(component.props().type.toLowerCase()).toBe('pie'); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /src/components/ECharts/B_basic/dataset.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { _toDataset } from '../methods'; 8 | export default (props) => { 9 | const { data, dataType } = props; 10 | if (dataType === 'dataset') { 11 | const source = _toDataset(data); 12 | return { 13 | source 14 | }; 15 | } else { 16 | return null; 17 | } 18 | }; -------------------------------------------------------------------------------- /src/components/ECharts/B_basic/index.propTypes.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import titlePropTypes from '../components/title.propTypes'; 3 | import legendPropTypes from '../components/legend.propTypes'; 4 | import toolboxPropTypes from '../components/toolbox.propTypes'; 5 | 6 | export default { 7 | ...titlePropTypes, 8 | ...legendPropTypes, 9 | ...toolboxPropTypes, 10 | //调色盘颜色列表 11 | color: PropTypes.array, 12 | //支持的图形类型 13 | type: PropTypes.oneOf(['funnel', 'pie', 'sankey']), 14 | //数据格式校验 15 | data: PropTypes.shape({ 16 | columns: PropTypes.array, 17 | rows: PropTypes.array, 18 | }), 19 | dataType: PropTypes.oneOf(['dataset', 'tabel', 'special']), 20 | //echart组件div样式 21 | style: PropTypes.object, 22 | //是否显示正在加载中 23 | loading: PropTypes.bool, 24 | //可以传入tooltip配置,校验 25 | tooltip: PropTypes.object, 26 | //是否显示tootip 27 | showTooltip: PropTypes.bool, 28 | //图形系列(series)配置项 29 | series: PropTypes.oneOfType([ 30 | PropTypes.array, 31 | PropTypes.object, 32 | ]), 33 | //单个图形系列(series[i])配置项 34 | seriesSettings: PropTypes.object, 35 | //漏斗图数据排序 36 | sort: PropTypes.oneOf(['ascending', 'descending', 'none']), 37 | //图形系列(series)name 38 | seriesName: PropTypes.string, 39 | //when the chart is ready, will callback the function with the echarts object as it's paramter. 40 | onChartReady: PropTypes.func, 41 | //binding the echarts event, will callback with the echarts event object, and the echart object as it's paramters 42 | onEvents: PropTypes.object, 43 | }; -------------------------------------------------------------------------------- /src/components/ECharts/B_basic/series.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import getType from './type'; 8 | export default (props) => { 9 | const { series, seriesSettings, data: { rows }, type, sort, seriesName } = props; 10 | const _geySeries = () => { 11 | return rows.map((item, i) => { 12 | if (i === 0) { 13 | return undefined; 14 | } else { 15 | return { 16 | name: seriesName, 17 | type: getType(type), 18 | sort, 19 | ...seriesSettings, 20 | }; 21 | } 22 | }) 23 | .filter(item => item !== undefined); 24 | }; 25 | 26 | return series || _geySeries(); 27 | }; -------------------------------------------------------------------------------- /src/components/ECharts/B_basic/tooltip.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | export default (props) => { 8 | const { tooltip, showTooltip } = props; 9 | return { 10 | show: showTooltip, 11 | ...tooltip 12 | }; 13 | }; -------------------------------------------------------------------------------- /src/components/ECharts/B_basic/type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | export default (type) => { 8 | const _type = type.toLowerCase(); 9 | switch (_type) { 10 | case 'funnel': 11 | return 'funnel'; 12 | default: 13 | return type; 14 | } 15 | }; -------------------------------------------------------------------------------- /src/components/ECharts/charts/Area/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { PureComponent } from 'react'; 8 | import Chart from '../../A_basic'; 9 | 10 | export default class extends PureComponent { 11 | static defaultProps = { 12 | data: {}, 13 | type: 'line', 14 | loading: false, 15 | stack: true, 16 | seriesSettings: { 17 | areaStyle: {}, 18 | }, 19 | } 20 | render() { 21 | 22 | return ( 23 | 26 | ); 27 | } 28 | } -------------------------------------------------------------------------------- /src/components/ECharts/charts/Bar/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { PureComponent } from 'react'; 8 | import Chart from '../../A_basic'; 9 | 10 | export default class extends PureComponent { 11 | static defaultProps = { 12 | data: {}, 13 | type: 'bar', 14 | loading: false, 15 | } 16 | render() { 17 | 18 | return ( 19 | 22 | ); 23 | } 24 | } -------------------------------------------------------------------------------- /src/components/ECharts/charts/Funnel/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { PureComponent } from 'react'; 8 | import Chart from '../../B_basic'; 9 | 10 | export default class extends PureComponent { 11 | static defaultProps = { 12 | data: {}, 13 | type: 'funnel', 14 | loading: false, 15 | showLegend: false, 16 | tooltip: {}, 17 | sort: 'descending', 18 | seriesSettings: { 19 | left: '10%', 20 | top: 10, 21 | bottom: 10, 22 | width: '80%', 23 | // min: 0, 24 | // max: 100, 25 | // minSize: '0%', 26 | // maxSize: '100%', 27 | gap: 2, 28 | label: { 29 | show: true, 30 | position: 'inside' 31 | }, 32 | labelLine: { 33 | length: 10, 34 | lineStyle: { 35 | width: 1, 36 | type: 'solid' 37 | } 38 | }, 39 | itemStyle: { 40 | borderColor: '#fff', 41 | borderWidth: 1 42 | }, 43 | emphasis: { 44 | label: { 45 | show: true, 46 | fontSize: 20 47 | } 48 | }, 49 | } 50 | } 51 | render() { 52 | 53 | return ( 54 | 57 | ); 58 | } 59 | } -------------------------------------------------------------------------------- /src/components/ECharts/charts/Line/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { PureComponent } from 'react'; 8 | import Chart from '../../A_basic'; 9 | 10 | export default class extends PureComponent { 11 | static defaultProps = { 12 | data: {}, 13 | type: 'line', 14 | loading: false, 15 | } 16 | render() { 17 | 18 | return ( 19 | 22 | ); 23 | } 24 | } -------------------------------------------------------------------------------- /src/components/ECharts/charts/Pie-doughnut/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { PureComponent } from 'react'; 8 | import Chart from '../../B_basic'; 9 | 10 | export default class extends PureComponent { 11 | static defaultProps = { 12 | data: {}, 13 | type: 'pie', 14 | loading: false, 15 | showLegend: false, 16 | tooltip: {}, 17 | seriesSettings: { 18 | radius: ['50%', '70%'], 19 | center: ['40%', '50%'], 20 | } 21 | } 22 | render() { 23 | 24 | return ( 25 | 28 | ); 29 | } 30 | } -------------------------------------------------------------------------------- /src/components/ECharts/charts/Pie/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { PureComponent } from 'react'; 8 | import Chart from '../../B_basic'; 9 | 10 | export default class extends PureComponent { 11 | static defaultProps = { 12 | data: {}, 13 | type: 'pie', 14 | loading: false, 15 | showLegend: false, 16 | tooltip: {}, 17 | seriesSettings: { 18 | radius: '55%', 19 | center: ['40%', '50%'], 20 | } 21 | } 22 | render() { 23 | 24 | return ( 25 | 28 | ); 29 | } 30 | } -------------------------------------------------------------------------------- /src/components/ECharts/charts/Radar/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { PureComponent } from 'react'; 8 | import Chart from '../../B_basic'; 9 | class Index extends PureComponent { 10 | static defaultProps = { 11 | data: {}, 12 | type: 'radar', 13 | loading: false, 14 | showLegend: false, 15 | tooltip: {}, 16 | radar: { 17 | // shape: 'circle', 18 | name: { 19 | textStyle: { 20 | color: '#fff', 21 | backgroundColor: '#999', 22 | borderRadius: 3, 23 | padding: [3, 5] 24 | } 25 | }, 26 | }, 27 | indicator: [] 28 | } 29 | render() { 30 | const { radar, indicator } = this.props; 31 | return ( 32 | 36 | ); 37 | } 38 | } 39 | export default Index; -------------------------------------------------------------------------------- /src/components/ECharts/charts/bar-y/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { PureComponent } from 'react'; 8 | import Chart from '../../A_basic'; 9 | 10 | export default class extends PureComponent { 11 | static defaultProps = { 12 | data: {}, 13 | type: 'bar', 14 | loading: false, 15 | xAxis: { 16 | type: 'value', 17 | }, 18 | yAxis: { 19 | type: 'category', 20 | }, 21 | } 22 | render() { 23 | return ( 24 | 27 | ); 28 | } 29 | } -------------------------------------------------------------------------------- /src/components/ECharts/charts/candlestick/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { PureComponent } from 'react'; 8 | import Chart from '../../core'; 9 | import getTitle from '../../components/title'; 10 | import getToolbox from '../../components/toolbox'; 11 | import getGrid from '../../components/grid'; 12 | 13 | export default class extends PureComponent { 14 | static defaultProps = { 15 | data: {}, 16 | type: 'k', 17 | loading: false, 18 | } 19 | render() { 20 | const { data, height, style, loading, onChartReady, onEvents, YName, yAxis, xAxis } = this.props; 21 | const dataSource = Array.isArray(data) ? data.slice(1) : []; 22 | const xAxisData = Array.isArray(dataSource) ? dataSource.map(item => item[0]) : []; 23 | const seriesData = Array.isArray(data) ? dataSource.map(item => item.slice(1)) : []; 24 | const option = { 25 | title: getTitle(this.props), 26 | toolbox: getToolbox(this.props), 27 | xAxis: { 28 | data: xAxisData, 29 | ...xAxis 30 | }, 31 | yAxis: { 32 | name: YName, 33 | ...yAxis, 34 | }, 35 | series: [{ 36 | type: 'k', 37 | data: seriesData 38 | }], 39 | grid: getGrid(this.props), 40 | }; 41 | return ( 42 | 50 | ); 51 | } 52 | } -------------------------------------------------------------------------------- /src/components/ECharts/components/grid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.1.0 5 | * @description 6 | * @git 7 | */ 8 | export default (props) => { 9 | const { grid, gridTop, gridBottom, gridLeft, gridRight } = props; 10 | 11 | const gridOpt = {}; 12 | if (gridTop && gridTop !== '') { 13 | gridOpt.top = gridTop; 14 | } 15 | if (gridBottom && gridBottom !== '') { 16 | gridOpt.bottom = gridBottom; 17 | } 18 | if (gridLeft && gridLeft !== '') { 19 | gridOpt.left = gridLeft; 20 | } 21 | if (gridRight && gridRight !== '') { 22 | gridOpt.right = gridRight; 23 | } 24 | return { ...gridOpt, ...grid }; 25 | }; -------------------------------------------------------------------------------- /src/components/ECharts/components/grid.propTypes.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | export default { 4 | //直角坐标系内绘图网格配置 5 | grid: PropTypes.object, 6 | gridTop: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 7 | gridBottom: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 8 | gridLeft: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 9 | gridRight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 10 | }; 11 | -------------------------------------------------------------------------------- /src/components/ECharts/components/legend.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | export default (props) => { 8 | const { legend, legendOrient, showLegend, legendLeft, legendRight, legendTop, legendBottom } = props; 9 | 10 | return { 11 | show: showLegend, 12 | left: legendLeft, 13 | right: legendRight, 14 | top: legendTop, 15 | bottom: legendBottom, 16 | orient: legendOrient, 17 | ...legend 18 | }; 19 | }; -------------------------------------------------------------------------------- /src/components/ECharts/components/legend.propTypes.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | export default { 4 | //图形图例配置项 5 | legend: PropTypes.object, 6 | //是否显示图例 7 | showLegend: PropTypes.bool, 8 | //图例列表的布局朝向。 9 | legendOrient: PropTypes.oneOf(['horizontal', 'vertical']), 10 | //图例组件离容器左侧的距离。 11 | legendLeft: PropTypes.oneOfType([ 12 | PropTypes.number, 13 | PropTypes.oneOf(['left', 'right', 'center']), 14 | ]), 15 | //图例组件离容器右侧的距离。 16 | legendRight: PropTypes.oneOfType([ 17 | PropTypes.number, 18 | PropTypes.oneOf(['left', 'right', 'center']), 19 | ]), 20 | //图例组件离容器上侧的距离。 21 | legendTop: PropTypes.oneOfType([ 22 | PropTypes.number, 23 | PropTypes.oneOf(['top', 'bottom', 'middle']), 24 | ]), 25 | //图例组件离容器底侧的距离。 26 | legendBottom: PropTypes.oneOfType([ 27 | PropTypes.number, 28 | PropTypes.oneOf(['top', 'bottom', 'middle']), 29 | ]), 30 | }; 31 | -------------------------------------------------------------------------------- /src/components/ECharts/components/title.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | export default (props) => { 8 | const { 9 | title, 10 | titleText, 11 | titleFontSize = 20, 12 | titleColor = "#333", 13 | titleFontWeight = 'normal', 14 | titleTop, 15 | titleBottom, 16 | titleLeft, 17 | titleRight 18 | } = props; 19 | 20 | return { 21 | text: titleText, 22 | textStyle: { 23 | color: titleColor, 24 | fontSize: titleFontSize, 25 | fontWeight: titleFontWeight, 26 | }, 27 | top: titleTop, 28 | bottom: titleBottom, 29 | left: titleLeft, 30 | right: titleRight, 31 | ...title 32 | }; 33 | }; -------------------------------------------------------------------------------- /src/components/ECharts/components/title.propTypes.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | export default { 4 | //组件标题配置项 5 | title: PropTypes.object, 6 | //组件标题 7 | titleText: PropTypes.string, 8 | titleColor: PropTypes.string, 9 | titleFontSize: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 10 | titleFontWeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 11 | titleTop: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 12 | titleBottom: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 13 | titleLeft: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 14 | titleRight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 15 | }; -------------------------------------------------------------------------------- /src/components/ECharts/components/toolbox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | export default (props) => { 8 | const { 9 | toolbox, 10 | showToolbox, 11 | showToolboxDataZoom, 12 | showToolboxDataView, 13 | showToolboxMagicType, 14 | toolboxMagicType, 15 | showToolboxRestore, 16 | showToolboxSaveAsImage 17 | } = props; 18 | 19 | return { 20 | show: showToolbox, 21 | feature: { 22 | dataZoom: { 23 | show: showToolboxDataZoom, 24 | yAxisIndex: 'none' 25 | }, 26 | dataView: { 27 | show: showToolboxDataView, 28 | readOnly: false 29 | }, 30 | magicType: { 31 | show: showToolboxMagicType, 32 | type: toolboxMagicType 33 | }, 34 | restore: { 35 | show: showToolboxRestore 36 | }, 37 | saveAsImage: { 38 | show: showToolboxSaveAsImage 39 | } 40 | }, 41 | ...toolbox 42 | }; 43 | }; -------------------------------------------------------------------------------- /src/components/ECharts/components/toolbox.propTypes.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | export default { 4 | //工具栏配置项 5 | toolbox: PropTypes.object, 6 | //是否显示工具栏 7 | showToolbox: PropTypes.bool, 8 | //区域缩放 9 | showToolboxDataZoom: PropTypes.bool, 10 | //数据视图 11 | showToolboxDataView: PropTypes.bool, 12 | //是否图形切换 13 | showToolboxMagicType: PropTypes.bool, 14 | //图形切换类型 15 | toolboxMagicType: PropTypes.array, 16 | //刷新还原 17 | showToolboxRestore: PropTypes.bool, 18 | //保存为图片 19 | showToolboxSaveAsImage: PropTypes.bool, 20 | }; -------------------------------------------------------------------------------- /src/components/ECharts/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description echarts基本配置 6 | */ 7 | export default { 8 | notMerge: true, 9 | lazyUpdate: true, 10 | theme: 'light', 11 | loadingOption: { 12 | text: '数据加载中', 13 | } 14 | }; -------------------------------------------------------------------------------- /src/components/ECharts/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import React, { Suspense } from 'react'; 8 | import { Skeleton } from 'antd'; 9 | const getComponent = Component => props => ( 10 | }> 11 | 12 | 13 | ); 14 | 15 | const Area = getComponent(React.lazy(() => import('./charts/Area'))); 16 | const Bar = getComponent(React.lazy(() => import('./charts/Bar'))); 17 | const YBar = getComponent(React.lazy(() => import('./charts/Bar-y'))); 18 | const Line = getComponent(React.lazy(() => import('./charts/Line'))); 19 | const Funnel = getComponent(React.lazy(() => import('./charts/Funnel'))); 20 | const Pie = getComponent(React.lazy(() => import('./charts/Pie'))); 21 | const PieDoughnut = getComponent(React.lazy(() => import('./charts/Pie-doughnut'))); 22 | const Sankey = getComponent(React.lazy(() => import('./charts/Sankey'))); 23 | const Radar = getComponent(React.lazy(() => import('./charts/Radar'))); 24 | const ChinaMap = getComponent(React.lazy(() => import('./charts/chinaMap'))); 25 | const Candlestick = getComponent(React.lazy(() => import('./charts/candlestick'))); 26 | 27 | export { 28 | Area, 29 | Bar, 30 | YBar, 31 | // BarWaterfall, 32 | Line, 33 | Funnel, 34 | Pie, 35 | PieDoughnut, 36 | // Map, 37 | // PieCustom, 38 | // PieNest, 39 | Radar, 40 | Sankey, 41 | ChinaMap, 42 | Candlestick 43 | // Scatter 44 | }; -------------------------------------------------------------------------------- /src/components/ECharts/methods/.test.js: -------------------------------------------------------------------------------- 1 | import { _formatNumer, _isData, _toDataset } from './index'; 2 | // import renderer from 'react-test-renderer'; 3 | 4 | describe('methods', () => { 5 | const data = { 6 | columns: [ 7 | { 8 | field: "product", 9 | name: "分类", 10 | type: "string" 11 | }, 12 | { 13 | field: "2015", 14 | name: "2015", 15 | type: "number" 16 | }, 17 | { 18 | field: "2016", 19 | name: "2016", 20 | type: "number" 21 | }, 22 | { 23 | field: "2017", 24 | name: "2017", 25 | type: "number" 26 | } 27 | ], 28 | rows: [ 29 | { 30 | product: 'Matcha Latte', 31 | 2015: 43.3, 32 | 2016: 85.8, 33 | 2017: 93.7 34 | }, 35 | { 36 | product: 'Milk Tea', 37 | 2015: 83.1, 38 | 2016: 73.4, 39 | 2017: 55.1 40 | }, 41 | { 42 | product: 'Cheese Cocoa', 43 | 2015: 86.4, 44 | 2016: 65.2, 45 | 2017: 82.5 46 | }, 47 | { 48 | product: 'Walnut Brownie', 49 | 2015: 72.4, 50 | 2016: 53.9, 51 | 2017: 39.1 52 | }, 53 | ] 54 | }; 55 | it('_formatNumer', () => { 56 | expect(_formatNumer(10000000000)).toBe('10,000,000,000'); 57 | expect(_formatNumer(10000000)).toBe('10,000,000'); 58 | expect(_formatNumer(10000)).toBe('10,000'); 59 | expect(_formatNumer(1000)).toBe('1,000'); 60 | expect(_formatNumer(1000.12)).toBe('1,000.12'); 61 | expect(_formatNumer(100)).toBe('100'); 62 | expect(_formatNumer([])).toEqual([]); 63 | }); 64 | it('_isData', () => { 65 | expect(_isData(data)).toBe(true); 66 | expect(_isData({ 67 | columns: [], 68 | rows: [] 69 | })).toBe(false); 70 | expect(_isData({})).toBe(false); 71 | }); 72 | it('_toDataset', () => { 73 | expect(_toDataset(data)).toEqual([["分类", "2015", "2016", "2017"], ["Matcha Latte", 43.3, 85.8, 93.7], ["Milk Tea", 83.1, 73.4, 55.1], ["Cheese Cocoa", 86.4, 65.2, 82.5], ["Walnut Brownie", 72.4, 53.9, 39.1]]); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /src/components/ECharts/methods/_formatNumer.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 数字加逗号显示 4 | * @param {number} number 5 | * @param {number} limit 6 | * @returns {string} 7 | */ 8 | export default (num, limit = 1000) => { 9 | if (typeof num !== 'number' && typeof num !== 'string') return num; 10 | num += ""; 11 | if (num === "NaN") return num; 12 | const n = num.indexOf('.'); 13 | let str = ""; 14 | if (n > -1) { 15 | str = num.slice(n); 16 | num = num.slice(0, n); 17 | } 18 | const arr = num.split(""); 19 | const len = arr.length; 20 | if (limit === 1000) { 21 | if (len >= 4) { 22 | arr.splice(-3, 0, ','); 23 | } 24 | if (len >= 7) { 25 | arr.splice(-7, 0, ','); 26 | } 27 | if (len >= 10) { 28 | arr.splice(-11, 0, ','); 29 | } 30 | if (len >= 13) { 31 | arr.splice(-15, 0, ','); 32 | } 33 | } 34 | num = arr.join("") + str; 35 | return num; 36 | }; -------------------------------------------------------------------------------- /src/components/ECharts/methods/_isData.js: -------------------------------------------------------------------------------- 1 | import { isObject, isArray } from 'lodash'; 2 | export default (data, dataType) => { 3 | if (!isObject(data)) { 4 | return false; 5 | } 6 | if (dataType === 'special') { 7 | return true; 8 | } 9 | if (!isArray(data.columns) || !isArray(data.rows)) { 10 | return false; 11 | } 12 | if (data.columns.length === 0 || data.rows.length === 0) { 13 | return false; 14 | } 15 | return true; 16 | }; -------------------------------------------------------------------------------- /src/components/ECharts/methods/_toDataset_A.js: -------------------------------------------------------------------------------- 1 | import memoizeOne from 'memoize-one'; 2 | import isEqual from 'lodash/isEqual'; 3 | /** 4 | * 5 | * { 6 | columns: [ 7 | { 8 | field: "product", 9 | name: "分类", 10 | type: "string" 11 | }, 12 | { 13 | field: "2015", 14 | name: "2015", 15 | type: "number" 16 | }, 17 | { 18 | field: "2016", 19 | name: "2016", 20 | type: "number" 21 | }, 22 | { 23 | field: "2017", 24 | name: "2017", 25 | type: "number" 26 | } 27 | ], 28 | rows: [ 29 | { 30 | product: 'Matcha Latte', 31 | 2015: 43.3, 32 | 2016: 85.8, 33 | 2017: 93.7 34 | }, 35 | { 36 | product: 'Milk Tea', 37 | 2015: 83.1, 38 | 2016: 73.4, 39 | 2017: 55.1 40 | }, 41 | { 42 | product: 'Cheese Cocoa', 43 | 2015: 86.4, 44 | 2016: 65.2, 45 | 2017: 82.5 46 | }, 47 | { 48 | product: 'Walnut Brownie', 49 | 2015: 72.4, 50 | 2016: 53.9, 51 | 2017: 39.1 52 | }, 53 | ] 54 | } 55 | * 56 | * @param {*} data 57 | * [ 58 | ['product', '2015', '2016', '2017'], 59 | ['Matcha Latte', 43.3, 85.8, 93.7], 60 | ['Milk Tea', 83.1, 73.4, 55.1], 61 | ['Cheese Cocoa', 86.4, 65.2, 82.5], 62 | ['Walnut Brownie', 72.4, 53.9, 39.1] 63 | ] 64 | * 65 | */ 66 | const toDataset = (data) => { 67 | const { columns, rows } = data; 68 | const row_0 = columns.map(item => item.name); 69 | const rest = rows.map(row => columns.map(item => row[item.field])); 70 | return [row_0, ...rest]; 71 | } 72 | export default memoizeOne(toDataset, isEqual); -------------------------------------------------------------------------------- /src/components/ECharts/methods/_toDataset_B.js: -------------------------------------------------------------------------------- 1 | import memoizeOne from 'memoize-one'; 2 | import isEqual from 'lodash/isEqual'; 3 | /** 4 | * 5 | * { 6 | columns: [ 7 | { 8 | field: "category", 9 | name: "分类", 10 | type: "string" 11 | }, 12 | { 13 | field: "2015", 14 | name: "2015", 15 | type: "number" 16 | }, 17 | { 18 | field: "2016", 19 | name: "2016", 20 | type: "number" 21 | }, 22 | { 23 | field: "2017", 24 | name: "2017", 25 | type: "number" 26 | } 27 | ], 28 | rows: [ 29 | { 30 | product: 'Matcha Latte', 31 | 2015: 43.3, 32 | 2016: 85.8, 33 | 2017: 93.7 34 | }, 35 | { 36 | product: 'Milk Tea', 37 | 2015: 83.1, 38 | 2016: 73.4, 39 | 2017: 55.1 40 | }, 41 | { 42 | product: 'Cheese Cocoa', 43 | 2015: 86.4, 44 | 2016: 65.2, 45 | 2017: 82.5 46 | }, 47 | { 48 | product: 'Walnut Brownie', 49 | 2015: 72.4, 50 | 2016: 53.9, 51 | 2017: 39.1 52 | }, 53 | ] 54 | } 55 | * 56 | * 57 | * @param {*} data 58 | * { 59 | dimensions: ['product', '2015', '2016', '2017'], 60 | source: [ 61 | {product: 'Matcha Latte', '2015': 43.3, '2016': 85.8, '2017': 93.7}, 62 | {product: 'Milk Tea', '2015': 83.1, '2016': 73.4, '2017': 55.1}, 63 | {product: 'Cheese Cocoa', '2015': 86.4, '2016': 65.2, '2017': 82.5}, 64 | {product: 'Walnut Brownie', '2015': 72.4, '2016': 53.9, '2017': 39.1} 65 | ] 66 | } 67 | * 68 | */ 69 | const toDataset = (data) => { 70 | const { columns, rows } = data; 71 | const dimensions = columns.map(item => item.field); 72 | 73 | return { 74 | dimensions, 75 | source: rows 76 | }; 77 | } 78 | export default memoizeOne(toDataset, isEqual); -------------------------------------------------------------------------------- /src/components/ECharts/methods/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import memoizeOne from 'memoize-one'; 8 | import isEqual from 'lodash/isEqual'; 9 | 10 | import formatNumer from './_formatNumer'; 11 | import toDataset from './_toDataset_A'; 12 | import isData from './_isData'; 13 | 14 | const _formatNumer = memoizeOne(formatNumer); 15 | const _toDataset = memoizeOne(toDataset, isEqual); 16 | const _isData = memoizeOne(isData, isEqual); 17 | 18 | export { 19 | _formatNumer, 20 | _toDataset, 21 | _isData 22 | }; -------------------------------------------------------------------------------- /src/components/Exception/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 异常处理页面403、404、500 6 | */ 7 | import React, { PureComponent } from 'react'; 8 | import { Link } from 'umi'; 9 | import { Button } from 'antd'; 10 | import PropTypes from 'prop-types'; 11 | import config from './typeConfig'; 12 | import styles from './index.less'; 13 | 14 | export default class Exception extends PureComponent { 15 | static defaultProps = { 16 | backText: 'back to home', 17 | redirect: { 18 | pathname: '/sys' 19 | }, 20 | }; 21 | 22 | constructor(props) { 23 | super(props); 24 | this.state = {}; 25 | } 26 | render() { 27 | const { 28 | backText, 29 | type, 30 | title, 31 | desc, 32 | redirect, 33 | img 34 | } = this.props; 35 | const pageType = type in config ? type : '404'; 36 | return ( 37 |
38 |
39 |
43 |
44 |
45 |

{title || config[pageType].title}

46 |
{desc || config[pageType].desc}
47 |
48 | 49 | 50 | 51 |
52 |
53 | 54 |
55 | ); 56 | } 57 | } 58 | Exception.propTypes = { 59 | backText: PropTypes.string, 60 | type: PropTypes.oneOfType([ 61 | PropTypes.string, 62 | PropTypes.number, 63 | ]), 64 | title: PropTypes.string, 65 | desc: PropTypes.string, 66 | redirect: PropTypes.object, 67 | img: PropTypes.string 68 | }; -------------------------------------------------------------------------------- /src/components/Exception/index.less: -------------------------------------------------------------------------------- 1 | @import "../../../node_modules/antd/lib/style/themes/default.less"; 2 | .exception { 3 | display: flex; 4 | flex: auto; 5 | align-items: center; 6 | min-height: 500px; 7 | .imgBlock { 8 | flex: 0 0 62.5%; 9 | width: 62.5%; 10 | padding-right: 152px; 11 | zoom: 1; 12 | &:before, 13 | &:after { 14 | content: ' '; 15 | display: table; 16 | } 17 | &:after { 18 | clear: both; 19 | visibility: hidden; 20 | font-size: 0; 21 | height: 0; 22 | } 23 | } 24 | .imgEle { 25 | height: 360px; 26 | width: 100%; 27 | max-width: 430px; 28 | float: right; 29 | background-repeat: no-repeat; 30 | background-position: 50% 50%; 31 | background-size: contain; 32 | } 33 | .content { 34 | flex: auto; 35 | h1 { 36 | color: #434e59; 37 | font-size: 72px; 38 | font-weight: 600; 39 | line-height: 72px; 40 | margin-bottom: 24px; 41 | } 42 | .desc { 43 | color: @text-color-secondary; 44 | font-size: 20px; 45 | line-height: 28px; 46 | margin-bottom: 16px; 47 | } 48 | .actions { 49 | button:not(:last-child) { 50 | margin-right: 8px; 51 | } 52 | } 53 | } 54 | } 55 | 56 | @media screen and (max-width: @screen-xl) { 57 | .exception { 58 | .imgBlock { 59 | padding-right: 88px; 60 | } 61 | } 62 | } 63 | 64 | @media screen and (max-width: @screen-sm) { 65 | .exception { 66 | display: block; 67 | text-align: center; 68 | .imgBlock { 69 | padding-right: 0; 70 | margin: 0 auto 24px; 71 | } 72 | } 73 | } 74 | 75 | @media screen and (max-width: @screen-xs) { 76 | .exception { 77 | .imgBlock { 78 | margin-bottom: -24px; 79 | overflow: hidden; 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /src/components/Exception/typeConfig.js: -------------------------------------------------------------------------------- 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/GlobalDrawer/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { Component } from 'react'; 8 | import { Drawer } from 'antd'; 9 | class GlobalDrawer extends Component { 10 | constructor(props) { 11 | super(props); 12 | const { visible = false } = props; 13 | this.state = { 14 | visible: visible 15 | }; 16 | } 17 | onClose = () => { 18 | const { visible } = this.state; 19 | this.setState({ 20 | visible: !visible 21 | }); 22 | } 23 | render() { 24 | return ( 25 | 32 |

Some contents...

33 |

Some contents...

34 |

Some contents...

35 |
36 | ); 37 | } 38 | } 39 | 40 | export default GlobalDrawer; 41 | -------------------------------------------------------------------------------- /src/components/HeaderSearch/index.less: -------------------------------------------------------------------------------- 1 | .headerSearch { 2 | :global(.anticon-search) { 3 | cursor: pointer; 4 | font-size: 16px; 5 | } 6 | // color: #ffffff; 7 | .input { 8 | transition: width 0.3s, margin-left 0.3s; 9 | width: 0; 10 | background: transparent; 11 | border-radius: 0; 12 | :global(.ant-select-selection) { 13 | background: transparent; 14 | } 15 | input { 16 | border: 0; 17 | padding-left: 0; 18 | padding-right: 0; 19 | box-shadow: none !important; 20 | // color: #ffffff; 21 | } 22 | &, 23 | &:hover, 24 | &:focus { 25 | border-bottom: 1px solid #d9d9d9; 26 | } 27 | &.show { 28 | width: 140px; 29 | margin-left: 8px; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/components/Icon/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description icon组件,兼容iconfont.cn/提供的标签 6 | * @link https://www.iconfont.cn 7 | */ 8 | import { Icon } from 'antd'; 9 | import { iconUrl } from '@platformConfig'; 10 | import PropTypes from 'prop-types'; 11 | function Index(props) { 12 | const { type = "bars", style = {}, spin = false } = props; 13 | if (type.indexOf("icon") > -1) { 14 | const MyIcon = Icon.createFromIconfontCN({ 15 | scriptUrl: iconUrl, // 在 iconfont.cn 上生成 16 | }); 17 | return ( 18 | 19 | ); 20 | } else { 21 | 22 | return ( 23 | 24 | ); 25 | } 26 | } 27 | export default Index; 28 | Index.propTypes = { 29 | //icon类型 30 | type: PropTypes.string, 31 | //icon 样式 32 | style: PropTypes.object, 33 | //是否加载中 34 | spin: PropTypes.bool, 35 | }; -------------------------------------------------------------------------------- /src/components/Loader/Loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import React from 'react'; 8 | import PropTypes from 'prop-types'; 9 | import classNames from 'classnames'; 10 | import styles from './Loader.less'; 11 | 12 | const Loader = ({ spinning, fullScreen }) => { 13 | return (
18 |
19 |
20 |
LOADING
21 |
22 |
); 23 | }; 24 | 25 | 26 | Loader.propTypes = { 27 | spinning: PropTypes.bool, 28 | fullScreen: PropTypes.bool, 29 | }; 30 | 31 | export default Loader; 32 | -------------------------------------------------------------------------------- /src/components/Loader/Loader.less: -------------------------------------------------------------------------------- 1 | .loader { 2 | display: block; 3 | background-color: #fff; 4 | width: 100%; 5 | position: absolute; 6 | top: 0; 7 | bottom: 0; 8 | left: 0; 9 | z-index: 100000; 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | opacity: 1; 14 | text-align: center; 15 | margin-top: 25px; 16 | &.fullScreen { 17 | position: fixed; 18 | } 19 | .warpper { 20 | width: 100px; 21 | height: 100px; 22 | display: inline-flex; 23 | flex-direction: column; 24 | justify-content: space-around; 25 | } 26 | .inner { 27 | width: 40px; 28 | height: 40px; 29 | margin: 0 auto; 30 | text-indent: -12345px; 31 | border-top: 1px solid rgba(0, 0, 0, 0.08); 32 | border-right: 1px solid rgba(0, 0, 0, 0.08); 33 | border-bottom: 1px solid rgba(0, 0, 0, 0.08); 34 | border-left: 1px solid rgba(0, 0, 0, 0.7); 35 | border-radius: 50%; 36 | z-index: 100001; 37 | :local { 38 | animation: spinner 600ms infinite linear; 39 | } 40 | } 41 | .text { 42 | width: 100px; 43 | height: 20px; 44 | text-align: center; 45 | font-size: 12px; 46 | letter-spacing: 4px; 47 | color: #000; 48 | } 49 | &.hidden { 50 | z-index: -1; 51 | opacity: 0; 52 | transition: opacity 1s ease 0.5s, z-index 0.1s ease 1.5s; 53 | } 54 | } 55 | 56 | @keyframes spinner { 57 | 0% { 58 | transform: rotate(0deg); 59 | } 60 | 100% { 61 | transform: rotate(360deg); 62 | } 63 | } -------------------------------------------------------------------------------- /src/components/Loader/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Loader", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./Loader.js" 6 | } 7 | -------------------------------------------------------------------------------- /src/components/PageHeader/breadcrumb.js: -------------------------------------------------------------------------------- 1 | import { PureComponent } from 'react'; 2 | import { Breadcrumb } from 'antd'; 3 | import { Link } from 'umi'; 4 | import isEqual from 'lodash/isEqual'; 5 | import { Icon } from '@components'; 6 | import styles from './index.less'; 7 | class BreadcrumbView extends PureComponent { 8 | state = { 9 | breadcrumbItems: null, 10 | }; 11 | static defaultProps = { 12 | breadcrumbList: [], 13 | // linkElement: 'a', 14 | breadcrumbSeparator: '/' 15 | }; 16 | componentDidMount() { 17 | this.getBreadcrumbDom(); 18 | } 19 | componentDidUpdate(preProps) { 20 | const { breadcrumbList } = this.props; 21 | if (!isEqual(breadcrumbList, preProps.breadcrumbList)) { 22 | this.getBreadcrumbDom(); 23 | } 24 | } 25 | getBreadcrumbDom = () => { 26 | const breadcrumbItems = this.itemRender(); 27 | this.setState({ 28 | breadcrumbItems, 29 | }); 30 | } 31 | itemRender = () => { 32 | const { breadcrumbList } = this.props; 33 | const len = breadcrumbList.length - 1; 34 | return breadcrumbList.map((item, i) => { 35 | const { title, icon, link, query, state } = typeof item === 'object' ? item : {}; 36 | return ( 37 | 38 | {icon && } 39 | {link && i !== len ? {title} : {title || item}} 40 | 41 | ); 42 | }); 43 | } 44 | render() { 45 | const { breadcrumbSeparator } = this.props; 46 | const { breadcrumbItems } = this.state; 47 | return ( 48 | 49 | {breadcrumbItems} 50 | 51 | ); 52 | } 53 | } 54 | export default BreadcrumbView; -------------------------------------------------------------------------------- /src/components/PageHeader/index.less: -------------------------------------------------------------------------------- 1 | //height=16-8+8+8+22+10=56px 2 | .wrapper { 3 | padding-top: 8px; 4 | padding-bottom: 8px; 5 | margin-top: -7px; 6 | margin-left: -16px; 7 | margin-right: -16px; 8 | padding-left: 24px; 9 | background-color: #ffffff; 10 | } 11 | 12 | .breadcrumb { 13 | line-height: 22px; 14 | font-size: 14px; 15 | } 16 | 17 | .title { 18 | margin: 10px 0; 19 | font-weight: normal; 20 | } 21 | 22 | .describe { 23 | font-size: 12px; 24 | } 25 | 26 | .hide { 27 | display: none; 28 | } -------------------------------------------------------------------------------- /src/components/PageLoading/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 全局loading,用于页面加载时 6 | */ 7 | import React from 'react'; 8 | import { Spin } from 'antd'; 9 | import styles from './index.less'; 10 | 11 | // https://umijs.org/plugin/umi-plugin-react.html#dynamicimport 12 | export default () => ( 13 |
14 | 15 |
16 | ); 17 | -------------------------------------------------------------------------------- /src/components/PageLoading/index.less: -------------------------------------------------------------------------------- 1 | .loader { 2 | width: 100%; 3 | height: 100%; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | } -------------------------------------------------------------------------------- /src/components/PageWrapper/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 页面wrapper组件 6 | */ 7 | import React, { PureComponent } from 'react'; 8 | import { connect } from 'dva'; 9 | import PropTypes from 'prop-types'; 10 | import classNames from 'classnames'; 11 | import PageHeader from '../PageHeader'; 12 | import Context from '@context'; 13 | // import Loader from '../Loader'; 14 | import styles from './index.less'; 15 | 16 | @connect(({ menu }) => { 17 | const { flattenMenuData, breadcrumbList } = menu; 18 | return { 19 | flattenMenuData, 20 | breadcrumbList 21 | }; 22 | }) 23 | class PageWrapper extends PureComponent { 24 | static defaultProps = { 25 | loading: false, 26 | showHeader: true, 27 | flex: false, 28 | } 29 | render() { 30 | const { 31 | className, 32 | children, 33 | loading, 34 | pathtitles, 35 | title, 36 | description, 37 | showHeader, 38 | flex, 39 | flattenMenuData, 40 | style 41 | } = this.props; 42 | return ( 43 | 44 | {({ location }) => ( 45 |
50 | 58 |
64 | {/* {loading && } */} 65 | {children} 66 |
67 |
68 | )} 69 |
70 | ); 71 | } 72 | } 73 | 74 | export default PageWrapper; 75 | 76 | PageWrapper.propTypes = { 77 | className: PropTypes.string, 78 | children: PropTypes.node, 79 | loading: PropTypes.bool, 80 | inner: PropTypes.bool, 81 | }; 82 | -------------------------------------------------------------------------------- /src/components/PageWrapper/index.less: -------------------------------------------------------------------------------- 1 | .contentInner { 2 | padding: 0; 3 | box-shadow: @shadow-1; 4 | flex: auto; 5 | display: flex; 6 | flex-direction: column; 7 | } 8 | 9 | .children { 10 | flex: auto; 11 | margin-top: 20px; 12 | background-color: #fff; 13 | padding: 10px; 14 | position: relative; 15 | } 16 | 17 | .loading { 18 | overflow: 'hidden' 19 | } -------------------------------------------------------------------------------- /src/components/Register/index.less: -------------------------------------------------------------------------------- 1 | 2 | .getCaptcha { 3 | display: block; 4 | width: 100%; 5 | } 6 | 7 | .submit { 8 | width: 50%; 9 | } 10 | 11 | .login { 12 | float: right; 13 | // line-height: @btn-height-lg; 14 | } 15 | 16 | .success, 17 | .warning, 18 | .error { 19 | transition: color 0.3s; 20 | } 21 | 22 | .success { 23 | // color: @success-color; 24 | } 25 | 26 | .warning { 27 | // color: @warning-color; 28 | } 29 | 30 | .error { 31 | // color: @error-color; 32 | } -------------------------------------------------------------------------------- /src/components/ResetPassword/index.less: -------------------------------------------------------------------------------- 1 | .submit { 2 | margin-left: 50%; 3 | width: 100px; 4 | } -------------------------------------------------------------------------------- /src/components/dataTable/header.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.1.0 5 | * @description datatable组件 6 | */ 7 | import { PureComponent } from 'react'; 8 | import PropTypes from 'prop-types'; 9 | import { Button, Row, Col } from 'antd'; 10 | import Search from './search'; 11 | import TableSelect from './select'; 12 | 13 | 14 | export default class DataTable extends PureComponent { 15 | static defaultProps = { 16 | selectProps: {}, // select属性 17 | searchProps: {}, // search属性 18 | download: {}, 19 | headerStyle: {}, 20 | handleSearch: () => { } 21 | } 22 | render() { 23 | const { searchProps, selectProps, download, headerStyle, handleSearch } = this.props; 24 | 25 | const { 26 | show: selectShow, 27 | data: optionData, 28 | defaultValue, 29 | onChange: selectChange, 30 | } = selectProps; 31 | 32 | const { 33 | show: searchShow, 34 | } = searchProps; 35 | const { 36 | show: downloadShow, 37 | handleClick = () => { } 38 | } = download; 39 | const handleSelectChange = (value) => { 40 | if (typeof selectChange === "function") { 41 | selectChange(value); 42 | } 43 | }; 44 | const onSearch = key => { 45 | if (typeof handleSearch === "function") { 46 | handleSearch(key); 47 | } 48 | }; 49 | 50 | const tableSelect = ; 55 | const search = ; 58 | const down = ; 59 | const layout1 = 60 | 61 | {selectShow ? tableSelect : ''} 62 | 63 | 64 | {searchShow ? search : ''} 65 | 66 | ; 67 | const layout2 = 68 | 69 | {searchShow ? search : ''} 70 | 71 | 72 | {down} 73 | 74 | ; 75 | 76 | return downloadShow ? layout2 : layout1; 77 | } 78 | } 79 | DataTable.propTypes = { 80 | searchProps: PropTypes.object, 81 | selectProps: PropTypes.object, 82 | download: PropTypes.object, 83 | headerStyle: PropTypes.object, 84 | handleSearch: PropTypes.func 85 | }; -------------------------------------------------------------------------------- /src/components/dataTable/search.js: -------------------------------------------------------------------------------- 1 | import { Input } from 'antd'; 2 | import { trim } from 'lodash'; 3 | 4 | function TableSearch(props) { 5 | const { onSearch = () => { } } = props; 6 | return ( 7 | { 10 | const { target } = e; 11 | const { value } = target; 12 | onSearch(trim(value)); 13 | }} 14 | style={{ width: 200 }} 15 | /> 16 | ); 17 | } 18 | export default TableSearch; -------------------------------------------------------------------------------- /src/components/dataTable/select.js: -------------------------------------------------------------------------------- 1 | import { Select } from 'antd'; 2 | 3 | const { Option } = Select; 4 | function TableSelect(props) { 5 | const { data = [], onChange: handleChange, defaultValue } = props; 6 | if (data.length === 0) { 7 | return ''; 8 | } 9 | const getOpts = (category) => { 10 | return category.map((item, i) => { 11 | const { name, value } = item; 12 | return (); 13 | }); 14 | }; 15 | 16 | return (); 27 | } 28 | export default TableSelect; -------------------------------------------------------------------------------- /src/components/dataTable/tableFooter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @description tableFooter 4 | */ 5 | import { PureComponent } from 'react'; 6 | import moment from 'moment'; 7 | export default class tableFooter extends PureComponent { 8 | render() { 9 | return ( 10 | {`表格数据更新时间:${this.props.time || moment().format('HH:mm:ss')}`} 11 | ); 12 | } 13 | } -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import Register from './Register'; 2 | import Icon from './Icon'; 3 | import DataTable from './DataTable'; 4 | import Breadcrumb from './Breadcrumb'; 5 | import Announcement from './Announcement'; 6 | import PageHeader from './PageHeader'; 7 | import ResetPassword from './ResetPassword'; 8 | import Page from './PageWrapper'; 9 | import GlobalDrawer from './GlobalDrawer'; 10 | import Exception from './Exception'; 11 | import HeaderSearch from './HeaderSearch'; 12 | import Consumer from './Consumer'; 13 | 14 | export { 15 | Icon, 16 | DataTable, 17 | Breadcrumb, 18 | Announcement, 19 | PageHeader, 20 | ResetPassword, 21 | Register, 22 | Page, 23 | GlobalDrawer, 24 | Exception, 25 | HeaderSearch, 26 | Consumer, 27 | }; 28 | -------------------------------------------------------------------------------- /src/global.less: -------------------------------------------------------------------------------- 1 | @import url("./themes/index.less"); 2 | 3 | html, 4 | body, 5 | #root { 6 | height: 100%; 7 | } 8 | 9 | body { 10 | margin: 0; 11 | // height: 100%; 12 | .scrollbar(); 13 | } 14 | 15 | .hideScrollbar { 16 | .scrollbar(); 17 | } 18 | 19 | .progressbar { 20 | /*火狐下隐藏滚动条*/ 21 | scrollbar-width: none; 22 | 23 | &::-webkit-scrollbar { 24 | /*滚动条整体样式*/ 25 | width: 4px; 26 | /*高宽分别对应横竖滚动条的尺寸*/ 27 | height: 4px; 28 | } 29 | 30 | &::-webkit-scrollbar-thumb { 31 | /*滚动条里面小方块*/ 32 | border-radius: 10px; 33 | -webkit-box-shadow: inset -1px 0 5px rgba(0, 33, 64, 0.7); // background: #999999; 34 | background-color: rgba(240, 242, 245, 0.9) 35 | } 36 | 37 | &::-webkit-scrollbar-track { 38 | /*滚动条里面轨道*/ 39 | // -webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2); 40 | // border-radius: 10px; 41 | // background: #EDEDED; 42 | } 43 | } -------------------------------------------------------------------------------- /src/layouts/Context.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { createContext } from 'react'; 8 | 9 | export default createContext(); 10 | -------------------------------------------------------------------------------- /src/layouts/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | import BasicLayout from '..'; 2 | import renderer from 'react-test-renderer'; 3 | 4 | describe('Layout: BasicLayout', () => { 5 | it('Render correctly', () => { 6 | const wrapper = renderer.create(); 7 | expect(wrapper.root.children.length).toBe(1); 8 | const outerLayer = wrapper.root.children[0]; 9 | expect(outerLayer.type).toBe('div'); 10 | // const title = outerLayer.children[0]; 11 | // expect(title.type).toBe('h1'); 12 | // expect(title.children[0]).toBe('Yay! Welcome to umi!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/layouts/basic/Footer/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { copyright } from '@platformConfig'; 8 | import React, { Fragment } from 'react'; 9 | import { Layout, Icon } from 'antd'; 10 | import GlobalFooter from '../../components/GlobalFooter'; 11 | 12 | const { Footer } = Layout; 13 | const FooterView = () => ( 14 |
15 | , 26 | href: 'https://github.com/mpw0311/antd-umi-sys', 27 | blankTarget: true, 28 | }, 29 | { 30 | key: 'Ant Design', 31 | title: 'Ant Design', 32 | href: 'https://ant.design', 33 | blankTarget: true, 34 | }, 35 | ]} 36 | copyright={ 37 | 38 | Copyright {copyright} 39 | 40 | } 41 | /> 42 |
43 | ); 44 | export default FooterView; 45 | -------------------------------------------------------------------------------- /src/layouts/basic/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { Component } from 'react'; 8 | import { Layout } from 'antd'; 9 | import MyFooter from './Footer'; 10 | import styles from './index.less'; 11 | 12 | const { 13 | Footer, Content, 14 | } = Layout; 15 | class Index extends Component { 16 | render() { 17 | const { children } = this.props; 18 | return ( 19 | 20 | 21 | {children} 22 | 23 |
24 | 25 |
26 |
27 | ); 28 | } 29 | } 30 | export default Index; -------------------------------------------------------------------------------- /src/layouts/basic/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: 100vh; 3 | display: flex; 4 | flex-direction: column; 5 | background-image: url(https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg); 6 | background-repeat: no-repeat; 7 | background-position: center 110px; 8 | background-size: 100%; 9 | } 10 | 11 | .content { 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | } -------------------------------------------------------------------------------- /src/layouts/components/Authorized/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 路由权限组件 6 | */ 7 | import { PureComponent } from 'react'; 8 | import { Page } from '@components'; 9 | export default class Authorized extends PureComponent { 10 | render() { 11 | const { children, noMatch, diffMenuData, location: { pathname } } = this.props; 12 | const [res] = diffMenuData.filter(item => item.link === pathname); 13 | return res ? ({noMatch}) : children; 14 | } 15 | } -------------------------------------------------------------------------------- /src/layouts/components/GlobalDownload/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { Icon } from 'antd'; 8 | import { Link } from 'umi'; 9 | import { Consumer } from '@components'; 10 | 11 | const themeConfig = { 12 | dark: { 13 | color: '#fff' 14 | }, 15 | light: { 16 | color: 'rgba(0, 0, 0, 0.65)' 17 | }, 18 | }; 19 | function Download(props) { 20 | const { userInfo, theme = 'dark' } = props; 21 | const { userName } = userInfo; 22 | return ( 23 | 27 | 下载 28 | 29 | ); 30 | } 31 | export default Consumer(Download); -------------------------------------------------------------------------------- /src/layouts/components/GlobalFooter/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 演示 4 | iframe: 400 5 | --- 6 | 7 | 基本页脚。 8 | 9 | ````jsx 10 | import GlobalFooter from 'ant-design-pro/lib/GlobalFooter'; 11 | import { Icon } from 'antd'; 12 | 13 | const links = [{ 14 | key: '帮助', 15 | title: '帮助', 16 | href: '', 17 | }, { 18 | key: 'github', 19 | title: , 20 | href: 'https://github.com/ant-design/ant-design-pro', 21 | blankTarget: true, 22 | }, { 23 | key: '条款', 24 | title: '条款', 25 | href: '', 26 | blankTarget: true, 27 | }]; 28 | 29 | const copyright =
Copyright 2017 蚂蚁金服体验技术部出品
; 30 | 31 | ReactDOM.render( 32 |
33 |
34 | 35 |
36 | , mountNode); 37 | ```` 38 | -------------------------------------------------------------------------------- /src/layouts/components/GlobalFooter/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IGlobalFooterProps { 3 | links?: Array<{ 4 | key?: string; 5 | title: React.ReactNode; 6 | href: string; 7 | blankTarget?: boolean; 8 | }>; 9 | copyright?: React.ReactNode; 10 | style?: React.CSSProperties; 11 | } 12 | 13 | export default class GlobalFooter extends React.Component {} 14 | -------------------------------------------------------------------------------- /src/layouts/components/GlobalFooter/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import React from 'react'; 8 | import classNames from 'classnames'; 9 | import styles from './index.less'; 10 | 11 | const GlobalFooter = ({ className, links, copyright }) => { 12 | const clsString = classNames(styles.globalFooter, className); 13 | return ( 14 |
15 | {links && ( 16 |
17 | {links.map(link => ( 18 | 24 | {link.title} 25 | 26 | ))} 27 |
28 | )} 29 | {copyright &&
{copyright}
} 30 |
31 | ); 32 | }; 33 | 34 | export default GlobalFooter; 35 | -------------------------------------------------------------------------------- /src/layouts/components/GlobalFooter/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .globalFooter { 4 | padding: 0 16px; 5 | margin: 20px 0 10px 0; 6 | text-align: center; 7 | 8 | .links { 9 | margin-bottom: 8px; 10 | 11 | a { 12 | color: @text-color-secondary; 13 | transition: all 0.3s; 14 | 15 | &:not(:last-child) { 16 | margin-right: 40px; 17 | } 18 | 19 | &:hover { 20 | color: @text-color; 21 | } 22 | } 23 | } 24 | 25 | .copyright { 26 | color: @text-color-secondary; 27 | font-size: @font-size-base; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/layouts/components/GlobalFooter/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: GlobalFooter 4 | zh-CN: GlobalFooter 5 | subtitle: 全局页脚 6 | cols: 1 7 | order: 7 8 | --- 9 | 10 | 页脚属于全局导航的一部分,作为对顶部导航的补充,通过传递数据控制展示内容。 11 | 12 | ## API 13 | 14 | 参数 | 说明 | 类型 | 默认值 15 | ----|------|-----|------ 16 | links | 链接数据 | array<{ title: ReactNode, href: string, blankTarget?: boolean }> | - 17 | copyright | 版权信息 | ReactNode | - 18 | -------------------------------------------------------------------------------- /src/layouts/components/GlobalHeader/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description layout header组件 6 | */ 7 | import User from '../GlobalUserCenter'; 8 | import Search from '../GlobalSearch'; 9 | import Notice from '../Notice'; 10 | import SelectLang from '../SelectLang'; 11 | import styles from './index.less'; 12 | 13 | 14 | function Header(props) { 15 | const { 16 | userInfo = {}, 17 | message, 18 | handleLoadMore = () => { }, 19 | handleSetting = () => { } 20 | } = props; 21 | return ( 22 |
23 | 24 | 30 | 35 | 36 |
37 | ); 38 | } 39 | export default Header; -------------------------------------------------------------------------------- /src/layouts/components/GlobalHeader/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @pro-header-hover-bg: rgba(0, 0, 0, 0.025); 3 | .rightCenter { 4 | text-align: right; 5 | height: 100%; 6 | } 7 | 8 | .action { 9 | display: inline-block; 10 | height: 100%; 11 | padding: 0 12px; 12 | cursor: pointer; 13 | transition: all 0.3s; 14 | >i { 15 | color: @text-color; 16 | vertical-align: middle; 17 | } 18 | &:hover { 19 | background: @pro-header-hover-bg; 20 | } 21 | &:global(.opened) { 22 | background: @pro-header-hover-bg; 23 | } 24 | } 25 | 26 | .search { 27 | padding: 0 12px; 28 | &:hover { 29 | background: transparent; 30 | } 31 | } 32 | 33 | .account { 34 | .avatar { 35 | margin:~'calc((@{layout-header-height} - 24px) / 2)' 0; 36 | margin-right: 8px; 37 | color: @primary-color; 38 | vertical-align: top; 39 | background: rgba(255, 255, 255, 0.85); 40 | } 41 | } -------------------------------------------------------------------------------- /src/layouts/components/GlobalRoll/index.css: -------------------------------------------------------------------------------- 1 | .rollbox { 2 | overflow: hidden; 3 | width: 100%; 4 | } 5 | .rollbox .roll { 6 | color: #ffffff; 7 | margin: 0; 8 | padding: 0; 9 | white-space: nowrap; 10 | float: left; 11 | animation-name: roll; 12 | -webkit-animation-name: roll; 13 | animation-iteration-count: infinite; 14 | -webkit-animation-iteration-count: infinite; 15 | animation-timing-function: linear; 16 | -webkit-animation-timing-function: linear; 17 | } 18 | .rollbox .roll:hover { 19 | animation-play-state: paused; 20 | -webkit-animation-play-state: paused; 21 | cursor: progress; 22 | } 23 | @-webkit-keyframes roll { 24 | 0% { 25 | transform: translateX(1200px); 26 | -webkit-transform: translateX(1200px); 27 | } 28 | 100% { 29 | transform: translateX(-100%); 30 | -webkit-transform: translateX(-100%); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/layouts/components/GlobalRoll/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import styles from './index.less'; 8 | 9 | function Roll(props) { 10 | const { 11 | notification = '',// "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.----------------------------------------------------" 12 | } = props; 13 | const len = notification.length; 14 | const ss = Math.floor(len / 12); 15 | return ( 16 |
17 |
18 | {notification} 19 |
20 |
21 | ); 22 | } 23 | export default Roll; -------------------------------------------------------------------------------- /src/layouts/components/GlobalRoll/index.less: -------------------------------------------------------------------------------- 1 | .rollbox { 2 | overflow: hidden; 3 | width: 100%; 4 | .roll { 5 | color: #ffffff; 6 | margin: 0; 7 | padding: 0; 8 | white-space: nowrap; 9 | float: left; // animation: roll 10s infinite linear; 10 | // -webkit-animation: roll 10s infinite linear; // animation-delay:1s; 11 | animation-name: roll; 12 | -webkit-animation-name: roll; 13 | animation-iteration-count: infinite; 14 | -webkit-animation-iteration-count: infinite; 15 | animation-timing-function: linear; 16 | -webkit-animation-timing-function: linear; // animation-delay: 5s; 17 | // -webkit-animation-delay: 5s; 18 | &:hover { 19 | animation-play-state: paused; 20 | -webkit-animation-play-state: paused; 21 | cursor: progress; 22 | } 23 | } 24 | } 25 | 26 | @-webkit-keyframes roll { 27 | 0% { 28 | transform: translateX(1200px); 29 | -webkit-transform: translateX(1200px); 30 | } 31 | 100% { 32 | transform: translateX(-100%); 33 | -webkit-transform: translateX(-100%); 34 | } 35 | } -------------------------------------------------------------------------------- /src/layouts/components/GlobalSearch/_.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | export const searchFilter = (value, data, names = [], pathtitles = []) => { 3 | if (value === undefined || value === null) return []; 4 | data = _.cloneDeep(data); 5 | data.forEach((item) => { 6 | const { title, children } = item; 7 | if (children && children.length > 0) { 8 | searchFilter(value, children, names, pathtitles.concat(title)); 9 | } else if (title.indexOf(value) > -1) { 10 | names.push({ ...item, pathtitles: pathtitles.concat(title) }); 11 | } 12 | }); 13 | return names; 14 | }; 15 | export const searchEqual = (value, data) => { 16 | data = _.cloneDeep(data); 17 | if (value === undefined || value === null || _.trim(value) === "") return {}; 18 | value = _.trim(value); 19 | let res = {}; 20 | for (const item of data) { 21 | const { key, children } = item; 22 | if (children && children.length > 0) { 23 | return searchFilter(value, children); 24 | } else if (key === value) { 25 | res = item; 26 | break; 27 | } 28 | } 29 | return res; 30 | }; -------------------------------------------------------------------------------- /src/layouts/components/GlobalSearch/index.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable at-rule-empty-line-before,at-rule-name-space-after,at-rule-no-unknown */ 2 | /* stylelint-disable no-duplicate-selectors */ 3 | /* stylelint-disable */ 4 | /* stylelint-disable declaration-bang-space-before,no-duplicate-selectors,string-no-newline */ 5 | .text-overflow { 6 | white-space: nowrap; 7 | text-overflow: ellipsis; 8 | overflow: hidden; 9 | } 10 | .headerSearch { 11 | height: 64px; 12 | display: inline-block; 13 | } 14 | -------------------------------------------------------------------------------- /src/layouts/components/GlobalSearch/index.less: -------------------------------------------------------------------------------- 1 | .headerSearch { 2 | height: @header-height; 3 | display: inline-block; 4 | } -------------------------------------------------------------------------------- /src/layouts/components/GlobalUserCenter/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import React, { PureComponent } from 'react'; 8 | import { formatMessage } from 'umi/locale'; 9 | import { Menu, Icon, Spin } from 'antd'; 10 | import classNames from 'classnames'; 11 | import HeaderDropdown from '../HeaderDropdown'; 12 | import styles from './index.less'; 13 | 14 | export default class SelectLang extends PureComponent { 15 | 16 | 17 | render() { 18 | const { userInfo = {}, onSetting = () => { }, theme, className } = this.props; 19 | const { userName } = userInfo; 20 | const handleMenuClick = (param) => { 21 | onSetting(param); 22 | }; 23 | const menu = ( 24 | 25 | {formatMessage({ id: 'platform.userCenter' })} 26 | 27 | {/* 28 | 修改密码 29 | */} 30 | 31 | {formatMessage({ id: 'platform.settings' })} 32 | 33 | 34 | {formatMessage({ id: 'platform.log' })} 35 | 36 | 37 | 38 | {formatMessage({ id: 'platform.logout' })} 39 | 40 | ); 41 | 42 | return userName ? ( 43 | 44 | 46 | {userName} 47 | 48 | ) 49 | : 50 | (); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/layouts/components/GlobalUserCenter/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | .menu { 3 | :global(.anticon) { 4 | margin-right: 8px; 5 | } // :global(.ant-dropdown-menu-item) { 6 | // min-width: 160px; 7 | // } 8 | } 9 | 10 | .dropDown { 11 | line-height: @layout-header-height; 12 | vertical-align: top; 13 | cursor: pointer; 14 | >i { 15 | font-size: 16px !important; 16 | transform: none !important; 17 | svg { 18 | position: relative; 19 | top: -1px; 20 | } 21 | } 22 | } 23 | 24 | .dark { 25 | color: #ffffff; 26 | } -------------------------------------------------------------------------------- /src/layouts/components/HeaderDropdown/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 菜单栏dropdown 6 | */ 7 | import React, { PureComponent } from 'react'; 8 | import { Dropdown } from 'antd'; 9 | import classNames from 'classnames'; 10 | import styles from './index.less'; 11 | 12 | export default class HeaderDropdown extends PureComponent { 13 | render() { 14 | const { overlayClassName, ...props } = this.props; 15 | return ( 16 | 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/layouts/components/HeaderDropdown/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .container > * { 4 | background-color: #fff; 5 | border-radius: 4px; 6 | box-shadow: @shadow-1-down; 7 | } 8 | 9 | @media screen and (max-width: @screen-xs) { 10 | .container { 11 | width: 100% !important; 12 | } 13 | .container > * { 14 | border-radius: 0 !important; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/layouts/components/Menus/_.js: -------------------------------------------------------------------------------- 1 | import { isArray, cloneDeep } from 'lodash'; 2 | import memoizeOne from 'memoize-one'; 3 | import isEqual from 'lodash/isEqual'; 4 | const _queryKeysByPath = (pathname, menusData) => { 5 | if (typeof (pathname) !== "string" || (typeof (pathname) === "string" && pathname.indexOf("frame") > -1)) return {}; 6 | const reg = /(^\/*)|(\/*$)/g;// 匹配字符串首尾斜杠 7 | const path = pathname.replace(reg, ''); 8 | const searchMenu = (value, data, pathtitles = []) => { 9 | if (value === undefined || value === null) return {}; 10 | data = cloneDeep(data); 11 | for (const item of data) { 12 | const { title, link, children } = item; 13 | if (children && children.length > 0) { 14 | return searchMenu(value, children, pathtitles.concat(title)); 15 | } else if (value === link.replace(reg, '')) { 16 | return { ...item, pathtitles: pathtitles.concat(title) }; 17 | } 18 | } 19 | }; 20 | const result = searchMenu(path, menusData); 21 | return { key: undefined, ...result }; 22 | }; 23 | export const queryKeysByPath = memoizeOne(_queryKeysByPath, isEqual); 24 | const _testMenusData = (item, err) => { 25 | const { title, icon, key, link, url, children } = item; 26 | if (!(title && key)) { 27 | return err && err(); 28 | } 29 | if (children) { 30 | if (isArray(children) && icon) { 31 | _testMenusData(children, err); 32 | } else { 33 | err(); 34 | }; 35 | } else { 36 | if (!(url || link)) { 37 | return err && err(); 38 | } 39 | } 40 | } 41 | export const testMenusData = memoizeOne(_testMenusData, isEqual); -------------------------------------------------------------------------------- /src/layouts/components/Notice/index.css: -------------------------------------------------------------------------------- 1 | .popover { 2 | width: 360px; 3 | } 4 | .popover :global(.ant-popover-inner-content) { 5 | padding: 0; 6 | } 7 | .noticeButton { 8 | cursor: pointer; 9 | display: inline-block; 10 | transition: all 0.3s; 11 | } 12 | .icon { 13 | font-size: 16px; 14 | vertical-align: middle; 15 | padding: 4px; 16 | } 17 | .tabs :global .ant-tabs-nav-scroll { 18 | text-align: center; 19 | } 20 | .tabs :global .ant-tabs-bar { 21 | margin-bottom: 4px; 22 | } 23 | .tabs .ant-tabs-nav .ant-tabs-tab { 24 | margin: 0 !important; 25 | } 26 | .more { 27 | text-align: center; 28 | margin-top: 4px; 29 | height: 28; 30 | line-height: 28px; 31 | cursor: pointer; 32 | } 33 | -------------------------------------------------------------------------------- /src/layouts/components/Notice/index.less: -------------------------------------------------------------------------------- 1 | .popover { 2 | width: 360px; 3 | :global(.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 | vertical-align: middle; 17 | padding: 4px; 18 | } 19 | 20 | .tabs { 21 | :global { 22 | .ant-tabs-nav-scroll { 23 | text-align: center; 24 | } 25 | .ant-tabs-bar { 26 | margin-bottom: 4px; 27 | } 28 | } 29 | .ant-tabs-nav .ant-tabs-tab { 30 | margin: 0 !important; 31 | } 32 | } 33 | 34 | .more { 35 | text-align: center; 36 | margin-top: 4px; 37 | height: 28; 38 | line-height: 28px; 39 | cursor: pointer; 40 | } -------------------------------------------------------------------------------- /src/layouts/components/SelectLang/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 语言选择组件 6 | */ 7 | import React, { PureComponent } from 'react'; 8 | import { formatMessage, setLocale, getLocale } from 'umi/locale'; 9 | import { Menu, Icon } from 'antd'; 10 | import classNames from 'classnames'; 11 | import HeaderDropdown from '../HeaderDropdown'; 12 | import styles from './index.less'; 13 | 14 | export default class SelectLang extends PureComponent { 15 | changeLang = ({ key }) => { 16 | setLocale(key); 17 | }; 18 | 19 | render() { 20 | const { className } = this.props; 21 | const selectedLang = getLocale(); 22 | const locales = ['zh-CN', 'en-US']; 23 | const languageLabels = { 24 | 'zh-CN': '简体中文', 25 | 'zh-TW': '繁体中文', 26 | 'en-US': 'English', 27 | 'pt-BR': 'Português', 28 | }; 29 | const languageIcons = { 30 | 'zh-CN': '🇨🇳', 31 | 'zh-TW': '🇭🇰', 32 | 'en-US': '🇬🇧', 33 | 'pt-BR': '🇧🇷', 34 | }; 35 | const langMenu = ( 36 | 37 | {locales.map(locale => ( 38 | 39 | 40 | {languageIcons[locale]} 41 | {' '} 42 | {languageLabels[locale]} 43 | 44 | ))} 45 | 46 | ); 47 | return ( 48 | 49 | 50 | 51 | 52 | 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/layouts/components/SelectLang/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | .menu { 3 | :global(.anticon) { 4 | margin-right: 8px; 5 | } 6 | // :global(.ant-dropdown-menu-item) { 7 | // min-width: 160px; 8 | // } 9 | } 10 | 11 | .dropDown { 12 | line-height: @layout-header-height; 13 | vertical-align: top; 14 | cursor: pointer; 15 | >i { 16 | font-size: 16px !important; 17 | transform: none !important; 18 | svg { 19 | position: relative; 20 | top: -1px; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/layouts/constant.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | export const query = { 8 | 'screen-xs': { 9 | maxWidth: 575, 10 | }, 11 | 'screen-sm': { 12 | minWidth: 576, 13 | maxWidth: 767, 14 | }, 15 | 'screen-md': { 16 | minWidth: 768, 17 | maxWidth: 991, 18 | }, 19 | 'screen-lg': { 20 | minWidth: 992, 21 | maxWidth: 1199, 22 | }, 23 | 'screen-xl': { 24 | minWidth: 1200, 25 | maxWidth: 1599, 26 | }, 27 | 'screen-xxl': { 28 | minWidth: 1600, 29 | }, 30 | }; -------------------------------------------------------------------------------- /src/layouts/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import BasicLayout from './basic'; 8 | import PlatformLayout from './platform'; 9 | 10 | function Index(props) { 11 | const { location, children } = props; 12 | const { pathname } = location; 13 | if ( 14 | pathname === '/' || 15 | pathname === '/login' || 16 | pathname === '/register' || 17 | /^\/initialize/.test(pathname) || 18 | /^\/exception/.test(pathname) 19 | ) { 20 | return ({children}); 21 | } 22 | return ({children}); 23 | } 24 | 25 | export default Index; 26 | -------------------------------------------------------------------------------- /src/layouts/platform/Footer/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 页脚组件 6 | */ 7 | import { copyright } from '@platformConfig'; 8 | import React, { Fragment } from 'react'; 9 | import { Layout, Icon } from 'antd'; 10 | import GlobalFooter from '../../components/GlobalFooter'; 11 | 12 | const { Footer } = Layout; 13 | const FooterView = () => ( 14 |
15 | , 26 | href: 'https://github.com/mpw0311/antd-umi-sys', 27 | blankTarget: true, 28 | }, 29 | { 30 | key: 'Ant Design', 31 | title: 'Ant Design', 32 | href: 'https://ant.design', 33 | blankTarget: true, 34 | }, 35 | ]} 36 | copyright={ 37 | 38 | Copyright {copyright} 39 | 40 | } 41 | /> 42 |
43 | ); 44 | export default FooterView; 45 | -------------------------------------------------------------------------------- /src/layouts/platform/Footer/index.less: -------------------------------------------------------------------------------- 1 | 2 | .footer { 3 | text-align: center; 4 | font-size: 14px; 5 | padding: 10px 50px; 6 | } -------------------------------------------------------------------------------- /src/layouts/platform/header.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description header组件 6 | */ 7 | import { routerRedux } from 'dva/router'; 8 | import { connect } from 'dva'; 9 | import { PureComponent } from 'react'; 10 | import GlobalHeader from '../components/GlobalHeader'; 11 | class Index extends PureComponent { 12 | handleLoadMore = () => { 13 | const { dispatch } = this.props; 14 | dispatch({ 15 | type: 'global/getMessage', 16 | payload: { 17 | size: 10, 18 | }, 19 | }); 20 | } 21 | handleSetting = (param) => { 22 | const { dispatch } = this.props; 23 | const { key, item } = param; 24 | const { state } = item.props; 25 | if (key === 'logout') { 26 | dispatch({ 27 | type: "global/logout", 28 | payload: { 29 | ...state, 30 | }, 31 | }); 32 | } else { 33 | dispatch(routerRedux.push({ 34 | pathname: key, 35 | state, 36 | })); 37 | } 38 | } 39 | render() { 40 | const { userInfo, message, notification } = this.props; 41 | return ( 42 |
43 | 50 |
51 | ); 52 | } 53 | } 54 | export default connect(({ global: { userInfo, message, notification } }) => { 55 | return { 56 | userInfo, 57 | message, 58 | notification 59 | }; 60 | })(Index); -------------------------------------------------------------------------------- /src/layouts/platform/index.less: -------------------------------------------------------------------------------- 1 | @import '../../themes/index.less'; 2 | 3 | .wrap { 4 | min-height: 100vh; 5 | 6 | /***侧边栏***/ 7 | .sider { 8 | height: 100vh; 9 | position: fixed; 10 | left: 0; 11 | } 12 | 13 | /***系统主体部分***/ 14 | .container { 15 | display: flex; 16 | flex-direction: column; 17 | 18 | /***系统头部***/ 19 | .contentHeader { 20 | background: #fff; 21 | padding: 0; 22 | display: flex; 23 | justify-content: space-between 24 | } 25 | 26 | /***系统内容区域***/ 27 | .content { 28 | flex: auto; 29 | margin: 10px 16px; 30 | margin-bottom: 0; 31 | display: flex; 32 | } 33 | } 34 | } 35 | 36 | 37 | 38 | .trigger { 39 | font-size: 18px; 40 | line-height: 64px; 41 | padding: 0 24px; 42 | cursor: pointer; 43 | transition: color .3s; 44 | } 45 | 46 | .trigger:hover { 47 | color: #1890ff; 48 | } 49 | 50 | 51 | 52 | .logo { 53 | height: 64px; 54 | background: #002140; 55 | padding: 10px 16px; 56 | } 57 | 58 | @keyframes fadeIn { 59 | 0% { 60 | opacity: 0; 61 | /*初始状态 透明度为0*/ 62 | } 63 | 64 | 50% { 65 | opacity: 0.5; 66 | /*中间状态 透明度为0*/ 67 | } 68 | 69 | 100% { 70 | opacity: 1; 71 | /*结尾状态 透明度为1*/ 72 | } 73 | } 74 | 75 | @-webkit-keyframes fadeIn { 76 | 0% { 77 | opacity: 0; 78 | /*初始状态 透明度为0*/ 79 | } 80 | 81 | 50% { 82 | opacity: 0.5; 83 | /*中间状态 透明度为0*/ 84 | } 85 | 86 | 100% { 87 | opacity: 1; 88 | /*结尾状态 透明度为1*/ 89 | } 90 | } 91 | 92 | .animation { 93 | /*动画名称*/ 94 | animation-name: fadeIn; 95 | -webkit-animation-name: fadeIn; 96 | /*动画持续时间*/ 97 | animation-duration: 0.6s; 98 | -webkit-animation-duration: 0.6s; 99 | /*动画次数*/ 100 | animation-iteration-count: 1; 101 | -webkit-animation-iteration-count: 1; 102 | /*延迟时间*/ 103 | animation-delay: 0s; 104 | -webkit-animation-delay: 0s; 105 | } -------------------------------------------------------------------------------- /src/layouts/platform/logo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import React, { PureComponent } from 'react'; 8 | import PropTypes from 'prop-types'; 9 | import { Row, Col } from 'antd'; 10 | import { sysName } from '@platformConfig'; 11 | import styles from './index.less'; 12 | import logo from '../../assets/logo.png'; 13 | class Index extends PureComponent { 14 | render() { 15 | const { collapsed } = this.props; 16 | const imgLogo = pro; 17 | let logoPage; 18 | if (collapsed) { 19 | logoPage = imgLogo; 20 | } else { 21 | logoPage = ( 22 | 23 | 24 | {imgLogo} 25 | 26 | 27 |

28 | {sysName} 29 |

30 | 31 |
32 | ); 33 | } 34 | return
35 | {logoPage} 36 |
37 | } 38 | } 39 | 40 | Index.propTypes = { 41 | collapsed: PropTypes.bool 42 | }; 43 | 44 | export default Index; 45 | -------------------------------------------------------------------------------- /src/layouts/platform/startedModal.js: -------------------------------------------------------------------------------- 1 | import { PureComponent } from 'react'; 2 | import { Modal } from 'antd'; 3 | export default class Started extends PureComponent { 4 | state = { 5 | visible: false 6 | } 7 | componentDidMount() { 8 | this.testStarted(); 9 | } 10 | testStarted = () => { 11 | const started = localStorage.getItem('started'); 12 | if (started !== 'true') { 13 | clearTimeout(this.timer); 14 | this.timer = setTimeout(() => { 15 | this.setState({ 16 | visible: true 17 | }); 18 | }, 6000) 19 | } 20 | } 21 | giveStar = () => { 22 | localStorage.setItem('started', true); 23 | this.setState({ 24 | visible: false 25 | }); 26 | window.location.href = 'https://github.com/mpw0311/antd-umi-sys'; 27 | } 28 | handleCancel = () => { 29 | this.setState({ 30 | visible: false 31 | }); 32 | } 33 | render() { 34 | const { visible } = this.state; 35 | return ( 36 | 43 | {/* eslint-disable-next-line */} 44 |

如果你喜欢这个项目请给一个⭐,谢谢!

45 |

Please give me a Star if you like this project.Thank you so much.

46 |
47 | ); 48 | } 49 | } -------------------------------------------------------------------------------- /src/locales/en-US.js: -------------------------------------------------------------------------------- 1 | import platform from './en-US/platform'; 2 | import login from './en-US/login'; 3 | import gitDataV from './en-US/gitDataV'; 4 | export default { 5 | 'navBar.lang': 'Languages', 6 | 'layout.user.link.help': 'Help', 7 | 'layout.user.link.privacy': 'Privacy', 8 | 'layout.user.link.terms': 'Terms', 9 | ...platform, 10 | ...login, 11 | ...gitDataV 12 | }; 13 | -------------------------------------------------------------------------------- /src/locales/en-US/form.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'form.get-captcha': 'Get Captcha', 3 | 'form.captcha.second': 'sec', 4 | 'form.optional': ' (optional) ', 5 | 'form.submit': 'Submit', 6 | 'form.save': 'Save', 7 | 'form.email.placeholder': 'Email', 8 | 'form.password.placeholder': 'Password', 9 | 'form.confirm-password.placeholder': 'Confirm password', 10 | 'form.phone-number.placeholder': 'Phone number', 11 | 'form.verification-code.placeholder': 'Verification code', 12 | 'form.title.label': 'Title', 13 | 'form.title.placeholder': 'Give the target a name', 14 | 'form.date.label': 'Start and end date', 15 | 'form.date.placeholder.start': 'Start date', 16 | 'form.date.placeholder.end': 'End date', 17 | 'form.goal.label': 'Goal description', 18 | 'form.goal.placeholder': 'Please enter your work goals', 19 | 'form.standard.label': 'Metrics', 20 | 'form.standard.placeholder': 'Please enter a metric', 21 | 'form.client.label': 'Client', 22 | 'form.client.label.tooltip': 'Target service object', 23 | 'form.client.placeholder': 24 | 'Please describe your customer service, internal customers directly @ Name / job number', 25 | 'form.invites.label': 'Inviting critics', 26 | 'form.invites.placeholder': 'Please direct @ Name / job number, you can invite up to 5 people', 27 | 'form.weight.label': 'Weight', 28 | 'form.weight.placeholder': 'Please enter weight', 29 | 'form.public.label': 'Target disclosure', 30 | 'form.public.label.help': 'Customers and invitees are shared by default', 31 | 'form.public.radio.public': 'Public', 32 | 'form.public.radio.partially-public': 'Partially public', 33 | 'form.public.radio.private': 'Private', 34 | 'form.publicUsers.placeholder': 'Open to', 35 | 'form.publicUsers.option.A': 'Colleague A', 36 | 'form.publicUsers.option.B': 'Colleague B', 37 | 'form.publicUsers.option.C': 'Colleague C', 38 | }; 39 | -------------------------------------------------------------------------------- /src/locales/en-US/gitDataV.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'gitDataV.desc': 'A GitHub data visualization platform built on react+umi,shows more intuitively views of some data you have on GitHub.', 3 | 'gitDataV.repositories': 'repositories', 4 | 'gitDataV.followers': 'followers', 5 | 'gitDataV.following': 'following', 6 | 'gitDataV.news': 'news', 7 | 'gitDataV.more': 'more', 8 | 'gitDataV.submit': 'submit', 9 | 'gitDataV.repos.overview': 'Top 10', 10 | 'gitDataV.repos.list': 'GitHub repositories list', 11 | 'gitDataV.stargazers.info': 'Top 20', 12 | 'gitDataV.stargazers.list': 'Stargazers list', 13 | } -------------------------------------------------------------------------------- /src/locales/en-US/login.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'login.userName': 'userName', 3 | 'login.password': 'password', 4 | 'login.remember-me': 'Remember me', 5 | 'login.forgot-password': 'Forgot your password?', 6 | 'login.signup': 'Sign up', 7 | 'login.login': 'Login', 8 | 'register.register': 'Register', 9 | 'register.get-verification-code': 'Get code', 10 | 'register.sign-in': 'Already have an account?', 11 | 'validation.email.required': 'Please enter your email!', 12 | 'validation.email.wrong-format': 'The email address is in the wrong format!', 13 | 'validation.userName.required': 'Please enter your userName!', 14 | 'validation.password.required': 'Please enter your password!', 15 | 'validation.password.twice': 'The passwords entered twice do not match!', 16 | 'validation.password.strength.msg': 17 | "Please enter at least 6 characters and don't use passwords that are easy to guess.", 18 | 'validation.password.strength.strong': 'Strength: strong', 19 | 'validation.password.strength.medium': 'Strength: medium', 20 | 'validation.password.strength.short': 'Strength: too short', 21 | 'validation.confirm-password.required': 'Please confirm your password!', 22 | 'validation.phone-number.required': 'Please enter your phone number!', 23 | 'validation.phone-number.wrong-format': 'Malformed phone number!', 24 | 'validation.verification-code.required': 'Please enter the verification code!', 25 | 'validation.title.required': 'Please enter a title', 26 | 'validation.date.required': 'Please select the start and end date', 27 | 'validation.goal.required': 'Please enter a description of the goal', 28 | 'validation.standard.required': 'Please enter a metric', 29 | } -------------------------------------------------------------------------------- /src/locales/en-US/platform.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'platform.userCenter': 'Account Center', 3 | 'platform.settings': 'Account Settings', 4 | 'platform.log': 'Change Log', 5 | 'platform.logout': 'Logout', 6 | } -------------------------------------------------------------------------------- /src/locales/zh-CN.js: -------------------------------------------------------------------------------- 1 | import platform from './zh-CN/platform'; 2 | import login from './zh-CN/login'; 3 | import gitDataV from './zh-CN/gitDataV'; 4 | export default { 5 | 'navBar.lang': '语言', 6 | 'layout.user.link.help': '帮助', 7 | 'layout.user.link.privacy': '隐私', 8 | 'layout.user.link.terms': '条款', 9 | ...platform, 10 | ...login, 11 | ...gitDataV 12 | }; 13 | -------------------------------------------------------------------------------- /src/locales/zh-CN/form.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'form.get-captcha': '获取验证码', 3 | 'form.captcha.second': '秒', 4 | 'form.optional': '(选填)', 5 | 'form.submit': '提交', 6 | 'form.save': '保存', 7 | 'form.email.placeholder': '邮箱', 8 | 'form.password.placeholder': '至少6位密码,区分大小写', 9 | 'form.confirm-password.placeholder': '确认密码', 10 | 'form.phone-number.placeholder': '手机号', 11 | 'form.verification-code.placeholder': '验证码', 12 | 'form.title.label': '标题', 13 | 'form.title.placeholder': '给目标起个名字', 14 | 'form.date.label': '起止日期', 15 | 'form.date.placeholder.start': '开始日期', 16 | 'form.date.placeholder.end': '结束日期', 17 | 'form.goal.label': '目标描述', 18 | 'form.goal.placeholder': '请输入你的阶段性工作目标', 19 | 'form.standard.label': '衡量标准', 20 | 'form.standard.placeholder': '请输入衡量标准', 21 | 'form.client.label': '客户', 22 | 'form.client.label.tooltip': '目标的服务对象', 23 | 'form.client.placeholder': '请描述你服务的客户,内部客户直接 @姓名/工号', 24 | 'form.invites.label': '邀评人', 25 | 'form.invites.placeholder': '请直接 @姓名/工号,最多可邀请 5 人', 26 | 'form.weight.label': '权重', 27 | 'form.weight.placeholder': '请输入', 28 | 'form.public.label': '目标公开', 29 | 'form.public.label.help': '客户、邀评人默认被分享', 30 | 'form.public.radio.public': '公开', 31 | 'form.public.radio.partially-public': '部分公开', 32 | 'form.public.radio.private': '不公开', 33 | 'form.publicUsers.placeholder': '公开给', 34 | 'form.publicUsers.option.A': '同事甲', 35 | 'form.publicUsers.option.B': '同事乙', 36 | 'form.publicUsers.option.C': '同事丙', 37 | }; 38 | -------------------------------------------------------------------------------- /src/locales/zh-CN/gitDataV.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'gitDataV.desc': '一个用React+UmiJS构建的GitHub"数据可视化平台"。通过它,您可以更加直观看到您在GitHub里的一些数据情况', 3 | 'gitDataV.repositories': '公开仓库数', 4 | 'gitDataV.followers': '粉丝', 5 | 'gitDataV.following': '跟随', 6 | 'gitDataV.news': '最新消息', 7 | 'gitDataV.more': '更多', 8 | 'gitDataV.submit': '查询', 9 | 'gitDataV.repos.overview': 'GitHub仓库Top 10概览', 10 | 'gitDataV.repos.list': 'GitHub仓库列表', 11 | 'gitDataV.stargazers.info': 'Top 20', 12 | 'gitDataV.stargazers.list': 'Stargazers list', 13 | 14 | } -------------------------------------------------------------------------------- /src/locales/zh-CN/login.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'login.userName': '用户名', 3 | 'login.password': '扫描二维码关注公众号获取密码', 4 | 'login.remember-me': '自动登录', 5 | 'login.forgot-password': '忘记密码', 6 | 'login.signup': '注册账户', 7 | 'login.login': '登录', 8 | 'register.register': '注册', 9 | 'register.get-verification-code': '获取验证码', 10 | 'register.sign-in': '使用已有账户登录', 11 | 'validation.email.required': '请输入邮箱地址!', 12 | 'validation.email.wrong-format': '邮箱地址格式错误!', 13 | 'validation.userName.required': '请输入用户名!', 14 | 'validation.password.required': '请输入密码!', 15 | 'validation.password.twice': '两次输入的密码不匹配!', 16 | 'validation.password.strength.msg': '请至少输入 6 个字符。请不要使用容易被猜到的密码。', 17 | 'validation.password.strength.strong': '强度:强', 18 | 'validation.password.strength.medium': '强度:中', 19 | 'validation.password.strength.short': '强度:太短', 20 | 'validation.confirm-password.required': '请确认密码!', 21 | 'validation.phone-number.required': '请输入手机号!', 22 | 'validation.phone-number.wrong-format': '手机号格式错误!', 23 | 'validation.verification-code.required': '请输入验证码!', 24 | 'validation.title.required': '请输入标题', 25 | 'validation.date.required': '请选择起止日期', 26 | 'validation.goal.required': '请输入目标描述', 27 | 'validation.standard.required': '请输入衡量标准', 28 | } -------------------------------------------------------------------------------- /src/locales/zh-CN/platform.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'platform.userCenter':'用户中心', 3 | 'platform.settings':'设置', 4 | 'platform.log':'更新日志', 5 | 'platform.logout':'退出登录', 6 | } -------------------------------------------------------------------------------- /src/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpw0311/antd-umi-sys/55ae3bab87d43dcc64e9471f1a9c8cb757cb303d/src/models/.gitkeep -------------------------------------------------------------------------------- /src/models/global.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @author M 4 | * @email mpw0311@163.com 5 | * @version 1.0.0 6 | * @description 全局model 7 | */ 8 | import { routerRedux } from 'dva/router'; 9 | import { message } from 'antd'; 10 | import * as api from '@services'; 11 | export default { 12 | namespace: 'global', 13 | state: { 14 | userInfo: {}, 15 | message: [], 16 | notification: undefined, 17 | }, 18 | subscriptions: { 19 | // setupHistory({ dispatch, history }) { 20 | // history.listen((location) => { 21 | // const { pathname, query, state } = location; 22 | // if (/^\/sys/.test(pathname)) { 23 | // } else if (/^\/login/.test(pathname)) { 24 | // } 25 | // }); 26 | // }, 27 | }, 28 | 29 | effects: { 30 | * logout({ payload }, { call, put }) { 31 | const { data, status } = yield call(api.logout, { ...payload }); 32 | const { message: msg } = data; 33 | if (status === 0) { 34 | message.success(msg || "退出系统"); 35 | sessionStorage.setItem('isLogin', false); 36 | yield put(routerRedux.push('/login')); 37 | } 38 | }, 39 | * getSysInfo(_, { call, put }) { 40 | const { data = {}, status } = yield call(api.getSysInfo, {}); 41 | if (status === 0) { 42 | const { userInfo = {}, notification } = data; 43 | yield put({ 44 | type: 'save', 45 | payload: { 46 | userInfo, 47 | notification 48 | } 49 | }); 50 | } 51 | 52 | }, 53 | // 请求消息通知栏数据 54 | *getMessage({ payload = {} }, { call, put, select }) { 55 | const { size = 0 } = payload; 56 | let count = yield select(({ global }) => global.message.length); 57 | const { data = [] } = yield call(api.getMessage, { size: count + size }); 58 | yield put({ 59 | type: 'save', 60 | payload: { 61 | message: data, 62 | } 63 | }); 64 | }, 65 | }, 66 | 67 | reducers: { 68 | save(state, action) { 69 | return { ...state, ...action.payload }; 70 | }, 71 | clear(state) { 72 | return { 73 | ...state, 74 | userInfo: {}, 75 | message: [], 76 | notification: undefined, 77 | }; 78 | } 79 | }, 80 | }; -------------------------------------------------------------------------------- /src/models/menu.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @author M 4 | * @email mpw0311@163.com 5 | * @version 1.0.0 6 | * @description 菜单栏model 7 | */ 8 | import * as api from '../services'; 9 | import orginalData from '@menuConfig'; 10 | import { munesFilter, flattenMenu } from '@utils/_'; 11 | import { menuPermission } from '@platformConfig'; 12 | export default { 13 | namespace: 'menu', 14 | state: { 15 | menusData: [], 16 | flattenMenuData: [], 17 | diffMenuData: [], 18 | }, 19 | subscriptions: { 20 | setup({ dispatch, history }) { // eslint-disable-line 21 | }, 22 | }, 23 | 24 | effects: { 25 | *getMenuData(_, { call, put, select }) { 26 | let menusData = yield select(({ menu }) => menu.menuData); 27 | if (!(menusData && menusData.length > 0)) { 28 | const { data = [] } = yield call(api.getMenuData, {}); 29 | const { menusData, diffMenuData } = munesFilter(orginalData, data, menuPermission); 30 | const flattenMenuData = flattenMenu(menusData); 31 | yield put({ 32 | type: 'save', 33 | payload: { 34 | menusData, 35 | diffMenuData, 36 | flattenMenuData 37 | } 38 | }); 39 | } 40 | } 41 | }, 42 | 43 | reducers: { 44 | save(state, action) { 45 | return { ...state, ...action.payload }; 46 | }, 47 | clear(state) { 48 | return { 49 | ...state, 50 | menusData: [], 51 | flattenMenuData: [], 52 | diffMenuData: [], 53 | }; 54 | } 55 | }, 56 | 57 | }; 58 | -------------------------------------------------------------------------------- /src/pages/403.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { Page, Exception } from '@components'; 8 | 9 | export default function () { 10 | return ( 11 | 15 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { Page, Exception } from '@components'; 8 | 9 | export default function () { 10 | return ( 11 | 15 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/pages/500.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { Page, Exception } from '@components'; 8 | 9 | export default function () { 10 | return ( 11 | 15 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/pages/__tests__/__mocks__/umi-plugin-locale.js: -------------------------------------------------------------------------------- 1 | export const formatMessage = () => 'Mock text'; 2 | -------------------------------------------------------------------------------- /src/pages/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | import Index from '..'; 2 | import renderer from 'react-test-renderer'; 3 | 4 | jest.mock('umi-plugin-locale'); 5 | 6 | describe('Page: index', () => { 7 | it('Render correctly', () => { 8 | const wrapper = renderer.create(); 9 | expect(wrapper.root.children.length).toBe(1); 10 | const outerLayer = wrapper.root.children[0]; 11 | expect(outerLayer.type).toBe('div'); 12 | expect(outerLayer.children.length).toBe(2); 13 | const getStartLink = outerLayer.findAllByProps({ 14 | href: 'https://umijs.org/guide/getting-started.html', 15 | }); 16 | expect(getStartLink.length).toBe(1); 17 | expect(getStartLink[0].children).toMatchObject(['Mock text']); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/pages/document.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | antd-umi-sys 8 | 9 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/pages/download/index.js: -------------------------------------------------------------------------------- 1 | import { Page } from '@components'; 2 | import { Row, Col } from 'antd'; 3 | 4 | export default function () { 5 | return ( 6 | 10 | 11 | 12 | download…… 13 | 14 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/exception/$code.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description 6 | */ 7 | import { Exception } from '@components'; 8 | import pathToRegexp from 'path-to-regexp'; 9 | 10 | export default function (props) { 11 | const { location: { pathname } } = props; 12 | const [, code] = pathToRegexp('/exception/:code').exec(pathname); 13 | return ( 14 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/pages/frame/$key.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author M 3 | * @email mpw0311@163.com 4 | * @version 1.0.0 5 | * @description iframe兼容第三方页面 6 | */ 7 | import React, { Component } from "react"; 8 | import { connect } from 'dva'; 9 | import Redirect from 'umi/redirect'; 10 | import { Page } from '@components'; 11 | import { Spin } from 'antd'; 12 | // import pathToRegexp from 'path-to-regexp'; 13 | import styles from './index.less'; 14 | const _filter = (menus, pathname) => { 15 | for (const item of menus) { 16 | if (item.link === pathname) { 17 | return item; 18 | } 19 | }; 20 | return {}; 21 | }; 22 | class Index extends Component { 23 | constructor(props) { 24 | super(props); 25 | this.state = { 26 | loading: true 27 | }; 28 | } 29 | componentDidUpdate(nextProps) { 30 | const { location: nextLocation } = nextProps; 31 | const { pathname: nextPathname } = nextLocation; 32 | const { location } = this.props; 33 | const { pathname } = location; 34 | const backTopDom = document.getElementById('backTop'); 35 | backTopDom && (backTopDom.scrollTop = 0); 36 | if (pathname !== nextPathname) { 37 | this.setState({ 38 | loading: true 39 | }); 40 | } 41 | } 42 | handlerLoad = () => { 43 | const { loading } = this.state; 44 | if (loading) { 45 | this.setState({ 46 | loading: false 47 | }); 48 | } 49 | } 50 | render() { 51 | const { location, flattenMenuData } = this.props; 52 | const { pathname, state = {}, query = {} } = location; 53 | // const key = pathToRegexp('/frame/:key').exec(pathname)[1]; 54 | const { h } = query; 55 | const { pathtitles, title = '', url } = state && state.url ? state : _filter(flattenMenuData, pathname); 56 | const { loading } = this.state; 57 | const frame = ( 58 | 59 | 60 |