├── .babelrc.js ├── .circleci └── config.yml ├── .editorconfig ├── .eslintrc.js ├── .ga ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .roadhogrc.mock.js ├── .stylelintrc ├── .travis.yml ├── .webpackrc.js ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── README.ru-RU.md ├── README.zh-CN.md ├── appveyor.yml ├── jest.config.js ├── jsconfig.json ├── mock ├── .gitkeep ├── api.js ├── chart.js ├── menuData.js ├── notices.js ├── profile.js └── rule.js ├── package.json ├── public └── favicon.png ├── src ├── assets │ └── logo.svg ├── common │ ├── menu.js │ └── router.js ├── components │ ├── ActiveChart │ │ ├── index.js │ │ └── index.less │ ├── Authorized │ │ ├── Authorized.js │ │ ├── AuthorizedRoute.js │ │ ├── CheckPermissions.js │ │ ├── CheckPermissions.test.js │ │ ├── PromiseRender.js │ │ ├── Secured.js │ │ ├── demo │ │ │ ├── AuthorizedArray.md │ │ │ ├── AuthorizedFunction.md │ │ │ ├── basic.md │ │ │ └── secured.md │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.md │ │ └── renderAuthorize.js │ ├── AvatarList │ │ ├── AvatarItem.d.ts │ │ ├── demo │ │ │ └── simple.md │ │ ├── index.d.ts │ │ ├── index.en-US.md │ │ ├── index.js │ │ ├── index.less │ │ └── index.zh-CN.md │ ├── Charts │ │ ├── Bar │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── ChartCard │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── Field │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── Gauge │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── MiniArea │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── MiniBar │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── MiniProgress │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── Pie │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── Radar │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── TagCloud │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── TimelineChart │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── WaterWave │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── autoHeight.js │ │ ├── bizcharts.d.ts │ │ ├── bizcharts.js │ │ ├── demo │ │ │ ├── bar.md │ │ │ ├── chart-card.md │ │ │ ├── gauge.md │ │ │ ├── mini-area.md │ │ │ ├── mini-bar.md │ │ │ ├── mini-pie.md │ │ │ ├── mini-progress.md │ │ │ ├── mix.md │ │ │ ├── pie.md │ │ │ ├── radar.md │ │ │ ├── tag-cloud.md │ │ │ ├── timeline-chart.md │ │ │ └── waterwave.md │ │ ├── g2.js │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── CountDown │ │ ├── demo │ │ │ └── simple.md │ │ ├── index.d.ts │ │ ├── index.en-US.md │ │ ├── index.js │ │ └── index.zh-CN.md │ ├── DescriptionList │ │ ├── Description.d.ts │ │ ├── Description.js │ │ ├── DescriptionList.js │ │ ├── demo │ │ │ ├── basic.md │ │ │ └── vertical.md │ │ ├── index.d.ts │ │ ├── index.en-US.md │ │ ├── index.js │ │ ├── index.less │ │ ├── index.zh-CN.md │ │ └── responsive.js │ ├── EditableItem │ │ ├── index.js │ │ └── index.less │ ├── EditableLinkGroup │ │ ├── index.js │ │ └── index.less │ ├── Ellipsis │ │ ├── demo │ │ │ ├── line.md │ │ │ └── number.md │ │ ├── index.d.ts │ │ ├── index.en-US.md │ │ ├── index.js │ │ ├── index.less │ │ ├── index.test.js │ │ └── index.zh-CN.md │ ├── Exception │ │ ├── demo │ │ │ ├── 403.md │ │ │ ├── 404.md │ │ │ └── 500.md │ │ ├── index.d.ts │ │ ├── index.en-US.md │ │ ├── index.js │ │ ├── index.less │ │ ├── index.zh-CN.md │ │ └── typeConfig.js │ ├── FooterToolbar │ │ ├── demo │ │ │ └── basic.md │ │ ├── index.d.ts │ │ ├── index.en-US.md │ │ ├── index.js │ │ ├── index.less │ │ └── index.zh-CN.md │ ├── GlobalFooter │ │ ├── demo │ │ │ └── basic.md │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── GlobalHeader │ │ ├── index.js │ │ └── index.less │ ├── HeaderSearch │ │ ├── demo │ │ │ └── basic.md │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── Login │ │ ├── LoginItem.js │ │ ├── LoginSubmit.js │ │ ├── LoginTab.js │ │ ├── demo │ │ │ └── basic.md │ │ ├── index.d.ts │ │ ├── index.en-US.md │ │ ├── index.js │ │ ├── index.less │ │ ├── index.zh-CN.md │ │ └── map.js │ ├── NoticeIcon │ │ ├── NoticeIconTab.d.ts │ │ ├── NoticeList.js │ │ ├── NoticeList.less │ │ ├── demo │ │ │ ├── basic.md │ │ │ └── popover.md │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── NumberInfo │ │ ├── demo │ │ │ └── basic.md │ │ ├── index.d.ts │ │ ├── index.en-US.md │ │ ├── index.js │ │ ├── index.less │ │ └── index.zh-CN.md │ ├── PageHeader │ │ ├── demo │ │ │ ├── image.md │ │ │ ├── simple.md │ │ │ ├── standard.md │ │ │ └── structure.md │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.less │ │ ├── index.md │ │ └── index.test.js │ ├── Result │ │ ├── demo │ │ │ ├── classic.md │ │ │ ├── error.md │ │ │ └── structure.md │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── SiderMenu │ │ ├── SiderMenu.js │ │ ├── SilderMenu.test.js │ │ ├── index.js │ │ └── index.less │ ├── StandardFormRow │ │ ├── index.js │ │ └── index.less │ ├── StandardTable │ │ ├── index.js │ │ └── index.less │ ├── TagSelect │ │ ├── TagSelectOption.d.ts │ │ ├── demo │ │ │ ├── expandable.md │ │ │ └── simple.md │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── Trend │ │ ├── demo │ │ │ ├── basic.md │ │ │ └── reverse.md │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── _utils │ │ ├── pathTools.js │ │ └── pathTools.test.js │ └── utils │ │ ├── AppMenu.js │ │ ├── AppNoMenu.js │ │ ├── pathTools.js │ │ └── pathTools.test.js ├── e2e │ ├── home.e2e.js │ └── login.e2e.js ├── index.ejs ├── index.js ├── index.less ├── layouts │ ├── BasicLayout.js │ ├── BlankLayout.js │ ├── PageHeaderLayout.js │ ├── PageHeaderLayout.less │ ├── UserLayout.js │ └── UserLayout.less ├── models │ ├── activities.js │ ├── chart.js │ ├── error.js │ ├── form.js │ ├── global.js │ ├── index.js │ ├── list.js │ ├── login.js │ ├── monitor.js │ ├── profile.js │ ├── project.js │ ├── register.js │ ├── rule.js │ └── user.js ├── polyfill.js ├── rollbar.js ├── router.js ├── routes │ ├── Dashboard │ │ ├── Analysis.js │ │ ├── Analysis.less │ │ ├── Monitor.js │ │ ├── Monitor.less │ │ ├── Workplace.js │ │ └── Workplace.less │ ├── Demo │ │ ├── DemoOne.js │ │ ├── DemoThree.js │ │ └── DemoTwo.js │ ├── Exception │ │ ├── 403.js │ │ ├── 404.js │ │ ├── 500.js │ │ ├── style.less │ │ └── triggerException.js │ ├── Forms │ │ ├── AdvancedForm.js │ │ ├── BasicForm.js │ │ ├── StepForm │ │ │ ├── Step1.js │ │ │ ├── Step2.js │ │ │ ├── Step3.js │ │ │ ├── index.js │ │ │ └── style.less │ │ ├── TableForm.js │ │ └── style.less │ ├── List │ │ ├── Applications.js │ │ ├── Applications.less │ │ ├── Articles.js │ │ ├── Articles.less │ │ ├── BasicList.js │ │ ├── BasicList.less │ │ ├── CardList.js │ │ ├── CardList.less │ │ ├── List.js │ │ ├── Projects.js │ │ ├── Projects.less │ │ ├── TableList.js │ │ └── TableList.less │ ├── Profile │ │ ├── AdvancedProfile.js │ │ ├── AdvancedProfile.less │ │ ├── BasicProfile.js │ │ └── BasicProfile.less │ ├── Result │ │ ├── Error.js │ │ ├── Success.js │ │ └── Success.test.js │ └── User │ │ ├── Login.js │ │ ├── Login.less │ │ ├── Register.js │ │ ├── Register.less │ │ ├── RegisterResult.js │ │ └── RegisterResult.less ├── services │ ├── api.js │ ├── error.js │ └── user.js ├── theme.js └── utils │ ├── Authorized.js │ ├── authority.js │ ├── request.js │ ├── utils.js │ └── utils.less └── tests ├── fix_puppeteer.sh └── run-tests.js /.babelrc.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | plugins: [ 5 | [ 6 | 'module-resolver', 7 | { 8 | alias: { 9 | components: path.join(__dirname, './src/components'), 10 | }, 11 | }, 12 | ], 13 | [ 14 | 'import', 15 | { 16 | libraryName: 'antd', 17 | style: true, // or 'css' 18 | }, 19 | ], 20 | ], 21 | }; 22 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:8.11.2 6 | steps: 7 | - checkout 8 | - run: npm install 9 | - run: npm run build 10 | test: 11 | docker: 12 | - image: circleci/node:8.11.2 13 | steps: 14 | - checkout 15 | - run: sh ./tests/fix_puppeteer.sh 16 | - run: npm install 17 | - run: npm run test:all 18 | workflows: 19 | version: 2 20 | build_and_test: 21 | jobs: 22 | - build 23 | - test -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'babel-eslint', 3 | extends: ['airbnb', 'prettier'], 4 | env: { 5 | browser: true, 6 | node: true, 7 | es6: true, 8 | mocha: true, 9 | jest: true, 10 | jasmine: true, 11 | }, 12 | rules: { 13 | 'generator-star-spacing': [0], 14 | 'consistent-return': [0], 15 | 'react/forbid-prop-types': [0], 16 | 'react/jsx-filename-extension': [1, { extensions: ['.js'] }], 17 | 'global-require': [1], 18 | 'import/prefer-default-export': [0], 19 | 'react/jsx-no-bind': [0], 20 | 'react/prop-types': [0], 21 | 'react/prefer-stateless-function': [0], 22 | 'react/jsx-one-expression-per-line': [0], 23 | 'react/jsx-wrap-multilines': [ 24 | 'error', 25 | { 26 | declaration: 'parens-new-line', 27 | assignment: 'parens-new-line', 28 | return: 'parens-new-line', 29 | arrow: 'parens-new-line', 30 | condition: 'parens-new-line', 31 | logical: 'parens-new-line', 32 | prop: 'ignore', 33 | }, 34 | ], 35 | 'no-else-return': [0], 36 | 'no-restricted-syntax': [0], 37 | 'import/no-extraneous-dependencies': [0], 38 | 'no-use-before-define': [0], 39 | 'jsx-a11y/no-static-element-interactions': [0], 40 | 'jsx-a11y/no-noninteractive-element-interactions': [0], 41 | 'jsx-a11y/click-events-have-key-events': [0], 42 | 'jsx-a11y/anchor-is-valid': [0], 43 | 'no-nested-ternary': [0], 44 | 'arrow-body-style': [0], 45 | 'import/extensions': [0], 46 | 'no-bitwise': [0], 47 | 'no-cond-assign': [0], 48 | 'import/no-unresolved': [0], 49 | 'comma-dangle': [ 50 | 'error', 51 | { 52 | arrays: 'always-multiline', 53 | objects: 'always-multiline', 54 | imports: 'always-multiline', 55 | exports: 'always-multiline', 56 | functions: 'ignore', 57 | }, 58 | ], 59 | 'object-curly-newline': [0], 60 | 'function-paren-newline': [0], 61 | 'no-restricted-globals': [0], 62 | 'require-yield': [1], 63 | }, 64 | parserOptions: { 65 | ecmaFeatures: { 66 | experimentalObjectRestSpread: true, 67 | }, 68 | }, 69 | settings: { 70 | polyfills: ['fetch', 'promises'], 71 | }, 72 | }; 73 | -------------------------------------------------------------------------------- /.ga: -------------------------------------------------------------------------------- 1 | { 2 | "code":"UA-72788897-6" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | **/node_modules 5 | # roadhog-api-doc ignore 6 | /src/utils/request-temp.js 7 | _roadhog-api-doc 8 | 9 | # production 10 | /dist 11 | /.vscode 12 | 13 | # misc 14 | .DS_Store 15 | npm-debug.log* 16 | yarn-error.log 17 | 18 | /coverage 19 | .idea 20 | yarn.lock 21 | package-lock.json 22 | *bak 23 | 24 | # visual studio code 25 | .history -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg 3 | **/*.html 4 | **/*.ejs 5 | package.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "printWidth": 100, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard", "stylelint-config-prettier"], 3 | "rules": { 4 | "selector-pseudo-class-no-unknown": null, 5 | "shorthand-property-no-redundant-values": null, 6 | "at-rule-empty-line-before": null, 7 | "at-rule-name-space-after": null, 8 | "comment-empty-line-before": null, 9 | "declaration-bang-space-before": null, 10 | "declaration-empty-line-before": null, 11 | "function-comma-newline-after": null, 12 | "function-name-case": null, 13 | "function-parentheses-newline-inside": null, 14 | "function-max-empty-lines": null, 15 | "function-whitespace-after": null, 16 | "number-leading-zero": null, 17 | "number-no-trailing-zeros": null, 18 | "rule-empty-line-before": null, 19 | "selector-combinator-space-after": null, 20 | "selector-descendant-combinator-no-non-space": null, 21 | "selector-list-comma-newline-after": null, 22 | "selector-pseudo-element-colon-notation": null, 23 | "unit-no-unknown": null, 24 | "no-descending-specificity": null, 25 | "value-list-max-empty-lines": null 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8" 5 | 6 | env: 7 | matrix: 8 | - TEST_TYPE=lint 9 | - TEST_TYPE=build 10 | - TEST_TYPE=test-all 11 | - TEST_TYPE=test-dist 12 | 13 | addons: 14 | apt: 15 | packages: 16 | - xvfb 17 | 18 | install: 19 | - export DISPLAY=':99.0' 20 | - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 21 | - npm install 22 | 23 | script: 24 | - | 25 | if [ "$TEST_TYPE" = lint ]; then 26 | npm run lint 27 | elif [ "$TEST_TYPE" = build ]; then 28 | npm run build 29 | elif [ "$TEST_TYPE" = test-all ]; then 30 | npm run test:all 31 | elif [ "$TEST_TYPE" = test-dist ]; then 32 | npm run site 33 | mv dist/* ./ 34 | php -S localhost:8000 & 35 | DEBUG=* npm test .e2e.js 36 | fi 37 | -------------------------------------------------------------------------------- /.webpackrc.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | export default { 4 | entry: 'src/index.js', 5 | extraBabelPlugins: [['import', { libraryName: 'antd', libraryDirectory: 'es', style: true }]], 6 | env: { 7 | development: { 8 | extraBabelPlugins: ['dva-hmr'], 9 | }, 10 | }, 11 | externals: { 12 | '@antv/data-set': 'DataSet', 13 | rollbar: 'rollbar', 14 | }, 15 | alias: { 16 | components: path.resolve(__dirname, 'src/components/'), 17 | }, 18 | ignoreMomentLocale: true, 19 | theme: './src/theme.js', 20 | html: { 21 | template: './src/index.ejs', 22 | }, 23 | lessLoaderOptions: { 24 | javascriptEnabled: true, 25 | }, 26 | disableDynamicImport: true, 27 | publicPath: '/', 28 | hash: true, 29 | }; 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Alipay.inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## dynamic-menu-router 2 | 3 | ant design pro动态路由权限,目前基于ant design pro1.4.4版本,只用于左侧菜单权限的设置和按钮权限的设置,关于最新版本权限的更新,敬请留意(演示请看demo列表)。 4 | 5 | ## 对common中menu.js和router.js的拆分和组合(具体细节请参考代码)。 6 | 7 | * 将menu.js中的menuData提取出去,暂时放在mock数据中(menuData.js),因为这些数据要用真实的接口来获取。 8 | 9 | * 在router.js中getRouterConfig的底部增加以下代码。目的是将routerConfig对象通过reducer传入到models/global中备用。 10 | 11 | ``` 12 | // eslint-disable-next-line 13 | app._store.dispatch({ 14 | type: 'global/saveRouterConfig', 15 | payload: routerConfig, 16 | }); 17 | ``` 18 | 19 | * 将router.js中getFlatMenuData方法提取到menu.js中(getFlatMenuData:将数据处理为以路径为键的数据)。 20 | 21 | * 将router.js中routerConfig和menuData进行合并处理的函数提取到menu.js中,变为getRouterData(getRouterData:将routerConfig和menuData进行合并)。 22 | 23 | * 在models/global.js中的effects中加入fetchMenus(用于获取最终处理好的路由),在reducers中加入saveRouterConfig(用于将router.js中getRouterConfig放到reduce中)。 24 | 25 | * 在components/utils中加入AppMenu高阶组件,用于获取最终的路由列表。 26 | 27 | ## 权限校验 28 | 29 | 重写utils/authority,这个主要用于权限的校验。 30 | 31 | ## 按钮权限 32 | 33 | menuData中增加actions数组,用于存放权限字段,demo-noe页面用于测试按钮权限 34 | 35 | ## 报错修改 36 | 37 | * 第一种:将node_modules/history/esm/history.js的esm换位es6即可 38 | 39 | ``` 40 | ./node_modules/history/esm/history.js 41 | Module not found: Can't resolve '@babel/runtime/helpers/esm/extends' 42 | ``` 43 | 44 | ## 温馨提示 45 | 46 | 亲们,如果帮助到您了,请给个星,-_- 47 | 48 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Test against the latest version of this Node.js version 2 | environment: 3 | nodejs_version: "8" 4 | 5 | # this is how to allow failing jobs in the matrix 6 | matrix: 7 | fast_finish: true # set this flag to immediately finish build once one of the jobs fails. 8 | 9 | # Install scripts. (runs after repo cloning) 10 | install: 11 | # Get the latest stable version of Node.js or io.js 12 | - ps: Install-Product node $env:nodejs_version 13 | # install modules 14 | - npm install 15 | # Output useful info for debugging. 16 | - node --version 17 | - npm --version 18 | 19 | # Post-install test scripts. 20 | test_script: 21 | - npm run lint 22 | - npm run test:all 23 | - npm run build 24 | 25 | # Don't actually build. 26 | build: off 27 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testURL: 'http://localhost:8000', 3 | }; 4 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "emitDecoratorMetadata": true, 4 | "experimentalDecorators": true, 5 | "baseUrl": ".", 6 | "paths": { 7 | "components/*": ["./src/components/*"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /mock/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hf1120/dynamic-menu-router/4d5214d7f2e06ff659c95fc670f44df0012b9a39/mock/.gitkeep -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hf1120/dynamic-menu-router/4d5214d7f2e06ff659c95fc670f44df0012b9a39/public/favicon.png -------------------------------------------------------------------------------- /src/components/ActiveChart/index.less: -------------------------------------------------------------------------------- 1 | .activeChart { 2 | position: relative; 3 | } 4 | .activeChartGrid { 5 | p { 6 | position: absolute; 7 | top: 80px; 8 | } 9 | p:last-child { 10 | top: 115px; 11 | } 12 | } 13 | .activeChartLegend { 14 | position: relative; 15 | font-size: 0; 16 | margin-top: 8px; 17 | height: 20px; 18 | line-height: 20px; 19 | span { 20 | display: inline-block; 21 | font-size: 12px; 22 | text-align: center; 23 | width: 33.33%; 24 | } 25 | span:first-child { 26 | text-align: left; 27 | } 28 | span:last-child { 29 | text-align: right; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/Authorized/Authorized.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CheckPermissions from './CheckPermissions'; 3 | 4 | class Authorized extends React.Component { 5 | render() { 6 | const { children, authority, noMatch = null } = this.props; 7 | const childrenRender = typeof children === 'undefined' ? null : children; 8 | return CheckPermissions(authority, childrenRender, noMatch); 9 | } 10 | } 11 | 12 | export default Authorized; 13 | -------------------------------------------------------------------------------- /src/components/Authorized/AuthorizedRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Redirect } from 'react-router-dom'; 3 | import Authorized from './Authorized'; 4 | 5 | class AuthorizedRoute extends React.Component { 6 | render() { 7 | const { component: Component, render, authority, redirectPath, actions, ...rest } = this.props; 8 | return ( 9 | } />} 12 | > 13 | (Component ? : render(props))} 16 | /> 17 | 18 | ); 19 | } 20 | } 21 | 22 | export default AuthorizedRoute; 23 | -------------------------------------------------------------------------------- /src/components/Authorized/PromiseRender.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Spin } from 'antd'; 3 | 4 | export default class PromiseRender extends React.PureComponent { 5 | state = { 6 | component: null, 7 | }; 8 | 9 | componentDidMount() { 10 | this.setRenderComponent(this.props); 11 | } 12 | 13 | componentWillReceiveProps(nextProps) { 14 | // new Props enter 15 | this.setRenderComponent(nextProps); 16 | } 17 | 18 | // set render Component : ok or error 19 | setRenderComponent(props) { 20 | const ok = this.checkIsInstantiation(props.ok); 21 | const error = this.checkIsInstantiation(props.error); 22 | props.promise 23 | .then(() => { 24 | this.setState({ 25 | component: ok, 26 | }); 27 | }) 28 | .catch(() => { 29 | this.setState({ 30 | component: error, 31 | }); 32 | }); 33 | } 34 | 35 | // Determine whether the incoming component has been instantiated 36 | // AuthorizedRoute is already instantiated 37 | // Authorized render is already instantiated, children is no instantiated 38 | // Secured is not instantiated 39 | checkIsInstantiation = target => { 40 | if (!React.isValidElement(target)) { 41 | return target; 42 | } 43 | return () => target; 44 | }; 45 | 46 | render() { 47 | const { component: Component } = this.state; 48 | return Component ? ( 49 | 50 | ) : ( 51 |
60 | 61 |
62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/Authorized/Secured.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Exception from '../Exception/index'; 3 | import CheckPermissions from './CheckPermissions'; 4 | /** 5 | * 默认不能访问任何页面 6 | * default is "NULL" 7 | */ 8 | const Exception403 = () => ; 9 | 10 | // Determine whether the incoming component has been instantiated 11 | // AuthorizedRoute is already instantiated 12 | // Authorized render is already instantiated, children is no instantiated 13 | // Secured is not instantiated 14 | const checkIsInstantiation = target => { 15 | if (!React.isValidElement(target)) { 16 | return target; 17 | } 18 | return () => target; 19 | }; 20 | 21 | /** 22 | * 用于判断是否拥有权限访问此view权限 23 | * authority 支持传入 string ,funtion:()=>boolean|Promise 24 | * e.g. 'user' 只有user用户能访问 25 | * e.g. 'user,admin' user和 admin 都能访问 26 | * e.g. ()=>boolean 返回true能访问,返回false不能访问 27 | * e.g. Promise then 能访问 catch不能访问 28 | * e.g. authority support incoming string, funtion: () => boolean | Promise 29 | * e.g. 'user' only user user can access 30 | * e.g. 'user, admin' user and admin can access 31 | * e.g. () => boolean true to be able to visit, return false can not be accessed 32 | * e.g. Promise then can not access the visit to catch 33 | * @param {string | function | Promise} authority 34 | * @param {ReactNode} error 非必需参数 35 | */ 36 | const authorize = (authority, error) => { 37 | /** 38 | * conversion into a class 39 | * 防止传入字符串时找不到staticContext造成报错 40 | * String parameters can cause staticContext not found error 41 | */ 42 | let classError = false; 43 | if (error) { 44 | classError = () => error; 45 | } 46 | if (!authority) { 47 | throw new Error('authority is required'); 48 | } 49 | return function decideAuthority(target) { 50 | const component = CheckPermissions(authority, target, classError || Exception403); 51 | return checkIsInstantiation(component); 52 | }; 53 | }; 54 | 55 | export default authorize; 56 | -------------------------------------------------------------------------------- /src/components/Authorized/demo/AuthorizedArray.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: 4 | zh-CN: 使用数组作为参数 5 | en-US: Use Array as a parameter 6 | --- 7 | 8 | Use Array as a parameter 9 | 10 | ```jsx 11 | import RenderAuthorized from 'ant-design-pro/lib/Authorized'; 12 | import { Alert } from 'antd'; 13 | 14 | const Authorized = RenderAuthorized('user'); 15 | const noMatch = ; 16 | 17 | ReactDOM.render( 18 | 19 | 20 | , 21 | mountNode, 22 | ); 23 | ``` 24 | -------------------------------------------------------------------------------- /src/components/Authorized/demo/AuthorizedFunction.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | title: 4 | zh-CN: 使用方法作为参数 5 | en-US: Use function as a parameter 6 | --- 7 | 8 | Use Function as a parameter 9 | 10 | ```jsx 11 | import RenderAuthorized from 'ant-design-pro/lib/Authorized'; 12 | import { Alert } from 'antd'; 13 | 14 | const Authorized = RenderAuthorized('user'); 15 | const noMatch = ; 16 | 17 | const havePermission = () => { 18 | return false; 19 | }; 20 | 21 | ReactDOM.render( 22 | 23 | 28 | , 29 | mountNode, 30 | ); 31 | ``` 32 | -------------------------------------------------------------------------------- /src/components/Authorized/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 4 | zh-CN: 基本使用 5 | en-US: Basic use 6 | --- 7 | 8 | Basic use 9 | 10 | ```jsx 11 | import RenderAuthorized from 'ant-design-pro/lib/Authorized'; 12 | import { Alert } from 'antd'; 13 | 14 | const Authorized = RenderAuthorized('user'); 15 | const noMatch = ; 16 | 17 | ReactDOM.render( 18 |
19 | 20 | 21 | 22 |
, 23 | mountNode, 24 | ); 25 | ``` 26 | -------------------------------------------------------------------------------- /src/components/Authorized/demo/secured.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 3 3 | title: 4 | zh-CN: 注解基本使用 5 | en-US: Basic use secured 6 | --- 7 | 8 | secured demo used 9 | 10 | ```jsx 11 | import RenderAuthorized from 'ant-design-pro/lib/Authorized'; 12 | import { Alert } from 'antd'; 13 | 14 | const { Secured } = RenderAuthorized('user'); 15 | 16 | @Secured('admin') 17 | class TestSecuredString extends React.Component { 18 | render() { 19 | ; 20 | } 21 | } 22 | ReactDOM.render( 23 |
24 | 25 |
, 26 | mountNode, 27 | ); 28 | ``` 29 | -------------------------------------------------------------------------------- /src/components/Authorized/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RouteProps } from 'react-router'; 3 | 4 | type authorityFN = (currentAuthority?: string) => boolean; 5 | 6 | type authority = string | Array | authorityFN | Promise; 7 | 8 | export type IReactComponent

= 9 | | React.StatelessComponent

10 | | React.ComponentClass

11 | | React.ClassicComponentClass

; 12 | 13 | interface Secured { 14 | (authority: authority, error?: React.ReactNode): (target: T) => T; 15 | } 16 | 17 | export interface AuthorizedRouteProps extends RouteProps { 18 | authority: authority; 19 | } 20 | export class AuthorizedRoute extends React.Component {} 21 | 22 | interface check { 23 | ( 24 | authority: authority, 25 | target: T, 26 | Exception: S 27 | ): T | S; 28 | } 29 | 30 | interface AuthorizedProps { 31 | authority: authority; 32 | noMatch?: React.ReactNode; 33 | } 34 | 35 | export class Authorized extends React.Component { 36 | static Secured: Secured; 37 | static AuthorizedRoute: typeof AuthorizedRoute; 38 | static check: check; 39 | } 40 | 41 | declare function renderAuthorize(currentAuthority: string): typeof Authorized; 42 | 43 | export default renderAuthorize; 44 | -------------------------------------------------------------------------------- /src/components/Authorized/index.js: -------------------------------------------------------------------------------- 1 | import Authorized from './Authorized'; 2 | import AuthorizedRoute from './AuthorizedRoute'; 3 | import Secured from './Secured'; 4 | import check from './CheckPermissions.js'; 5 | import renderAuthorize from './renderAuthorize'; 6 | 7 | Authorized.Secured = Secured; 8 | Authorized.AuthorizedRoute = AuthorizedRoute; 9 | Authorized.check = check; 10 | 11 | export default renderAuthorize(Authorized); 12 | -------------------------------------------------------------------------------- /src/components/Authorized/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: Authorized 4 | zh-CN: Authorized 5 | subtitle: 权限 6 | cols: 1 7 | order: 15 8 | --- 9 | 10 | 权限组件,通过比对现有权限与准入权限,决定相关元素的展示。 11 | 12 | ## API 13 | 14 | ### RenderAuthorized 15 | 16 | `RenderAuthorized: (currentAuthority: string | () => string) => Authorized` 17 | 18 | 权限组件默认 export RenderAuthorized 函数,它接收当前权限作为参数,返回一个权限对象,该对象提供以下几种使用方式。 19 | 20 | 21 | ### Authorized 22 | 23 | 最基础的权限控制。 24 | 25 | | 参数 | 说明 | 类型 | 默认值 | 26 | |----------|------------------------------------------|-------------|-------| 27 | | children | 正常渲染的元素,权限判断通过时展示 | ReactNode | - | 28 | | authority | 准入权限/权限判断 | `string | array | Promise | (currentAuthority) => boolean | Promise` | - | 29 | | noMatch | 权限异常渲染元素,权限判断不通过时展示 | ReactNode | - | 30 | 31 | ### Authorized.AuthorizedRoute 32 | 33 | | 参数 | 说明 | 类型 | 默认值 | 34 | |----------|------------------------------------------|-------------|-------| 35 | | authority | 准入权限/权限判断 | `string | array | Promise | (currentAuthority) => boolean | Promise` | - | 36 | | redirectPath | 权限异常时重定向的页面路由 | string | - | 37 | 38 | 其余参数与 `Route` 相同。 39 | 40 | ### Authorized.Secured 41 | 42 | 注解方式,`@Authorized.Secured(authority, error)` 43 | 44 | | 参数 | 说明 | 类型 | 默认值 | 45 | |----------|------------------------------------------|-------------|-------| 46 | | authority | 准入权限/权限判断 | `string | Promise | (currentAuthority) => boolean | Promise` | - | 47 | | error | 权限异常时渲染元素 | ReactNode | | 48 | 49 | ### Authorized.check 50 | 51 | 函数形式的 Authorized,用于某些不能被 HOC 包裹的组件。 `Authorized.check(authority, target, Exception)` 52 | 注意:传入一个 Promise 时,无论正确还是错误返回的都是一个 ReactClass。 53 | 54 | | 参数 | 说明 | 类型 | 默认值 | 55 | |----------|------------------------------------------|-------------|-------| 56 | | authority | 准入权限/权限判断 | `string | Promise | (currentAuthority) => boolean | Promise` | - | 57 | | target | 权限判断通过时渲染的元素 | ReactNode | - | 58 | | Exception | 权限异常时渲染元素 | ReactNode | - | 59 | -------------------------------------------------------------------------------- /src/components/Authorized/renderAuthorize.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-mutable-exports */ 2 | let CURRENT = 'NULL'; 3 | /** 4 | * use authority or getAuthority 5 | * @param {string|()=>String} currentAuthority 6 | */ 7 | const renderAuthorize = Authorized => { 8 | return currentAuthority => { 9 | if (currentAuthority) { 10 | if (currentAuthority.constructor.name === 'Function') { 11 | CURRENT = currentAuthority(); 12 | } 13 | if ( 14 | currentAuthority.constructor.name === 'String' || 15 | currentAuthority.constructor.name === 'Array' 16 | ) { 17 | CURRENT = currentAuthority; 18 | } 19 | } else { 20 | CURRENT = 'NULL'; 21 | } 22 | return Authorized; 23 | }; 24 | }; 25 | 26 | export { CURRENT }; 27 | export default Authorized => renderAuthorize(Authorized); 28 | -------------------------------------------------------------------------------- /src/components/AvatarList/AvatarItem.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IAvatarItemProps { 3 | tips: React.ReactNode; 4 | src: string; 5 | style?: React.CSSProperties; 6 | } 7 | 8 | export default class AvatarItem extends React.Component { 9 | constructor(props: IAvatarItemProps); 10 | } 11 | -------------------------------------------------------------------------------- /src/components/AvatarList/demo/simple.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 4 | zh-CN: 基础样例 5 | en-US: Basic Usage 6 | --- 7 | 8 | Simplest of usage. 9 | 10 | ````jsx 11 | import AvatarList from 'ant-design-pro/lib/AvatarList'; 12 | 13 | ReactDOM.render( 14 | 15 | 16 | 17 | 18 | 19 | , mountNode); 20 | ```` 21 | -------------------------------------------------------------------------------- /src/components/AvatarList/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import AvatarItem from './AvatarItem'; 3 | 4 | export interface IAvatarListProps { 5 | size?: 'large' | 'small' | 'mini' | 'default'; 6 | style?: React.CSSProperties; 7 | children: React.ReactElement | Array>; 8 | } 9 | 10 | export default class AvatarList extends React.Component { 11 | public static Item: typeof AvatarItem; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/AvatarList/index.en-US.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: AvatarList 3 | order: 1 4 | cols: 1 5 | --- 6 | 7 | A list of user's avatar for project or group member list frequently. If a large or small AvatarList is desired, set the `size` property to either `large` or `small` and `mini` respectively. Omit the `size` property for a AvatarList with the default size. 8 | 9 | ## API 10 | 11 | ### AvatarList 12 | 13 | | Property | Description | Type | Default | 14 | |----------|------------------------------------------|-------------|-------| 15 | | size | size of list | `large`、`small` 、`mini`, `default` | `default` | 16 | 17 | ### AvatarList.Item 18 | 19 | | Property | Description | Type | Default | 20 | |----------|------------------------------------------|-------------|-------| 21 | | tips | title tips for avatar item | ReactNode\/string | - | 22 | | src | the address of the image for an image avatar | string | - | 23 | -------------------------------------------------------------------------------- /src/components/AvatarList/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tooltip, Avatar } from 'antd'; 3 | import classNames from 'classnames'; 4 | 5 | import styles from './index.less'; 6 | 7 | const AvatarList = ({ children, size, ...other }) => { 8 | const childrenWithProps = React.Children.map(children, child => 9 | React.cloneElement(child, { 10 | size, 11 | }) 12 | ); 13 | 14 | return ( 15 |

16 |
    {childrenWithProps}
17 |
18 | ); 19 | }; 20 | 21 | const Item = ({ src, size, tips, onClick = () => {} }) => { 22 | const cls = classNames(styles.avatarItem, { 23 | [styles.avatarItemLarge]: size === 'large', 24 | [styles.avatarItemSmall]: size === 'small', 25 | [styles.avatarItemMini]: size === 'mini', 26 | }); 27 | 28 | return ( 29 |
  • 30 | {tips ? ( 31 | 32 | 33 | 34 | ) : ( 35 | 36 | )} 37 |
  • 38 | ); 39 | }; 40 | 41 | AvatarList.Item = Item; 42 | 43 | export default AvatarList; 44 | -------------------------------------------------------------------------------- /src/components/AvatarList/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .avatarList { 4 | display: inline-block; 5 | ul { 6 | display: inline-block; 7 | margin-left: 8px; 8 | font-size: 0; 9 | } 10 | } 11 | 12 | .avatarItem { 13 | display: inline-block; 14 | font-size: @font-size-base; 15 | margin-left: -8px; 16 | width: @avatar-size-base; 17 | height: @avatar-size-base; 18 | :global { 19 | .ant-avatar { 20 | border: 1px solid #fff; 21 | } 22 | } 23 | } 24 | 25 | .avatarItemLarge { 26 | width: @avatar-size-lg; 27 | height: @avatar-size-lg; 28 | } 29 | 30 | .avatarItemSmall { 31 | width: @avatar-size-sm; 32 | height: @avatar-size-sm; 33 | } 34 | 35 | .avatarItemMini { 36 | width: 20px; 37 | height: 20px; 38 | :global { 39 | .ant-avatar { 40 | width: 20px; 41 | height: 20px; 42 | line-height: 20px; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/AvatarList/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: AvatarList 3 | subtitle: 用户头像列表 4 | order: 1 5 | cols: 1 6 | --- 7 | 8 | 一组用户头像,常用在项目/团队成员列表。可通过设置 `size` 属性来指定头像大小。 9 | 10 | ## API 11 | 12 | ### AvatarList 13 | 14 | | 参数 | 说明 | 类型 | 默认值 | 15 | |----------|------------------------------------------|-------------|-------| 16 | | size | 头像大小 | `large`、`small` 、`mini`, `default` | `default` | 17 | 18 | ### AvatarList.Item 19 | 20 | | 参数 | 说明 | 类型 | 默认值 | 21 | |----------|------------------------------------------|-------------|-------| 22 | | tips | 头像展示文案 | ReactNode\/string | - | 23 | | src | 头像图片连接 | string | - | 24 | -------------------------------------------------------------------------------- /src/components/Charts/Bar/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IBarProps { 3 | title: React.ReactNode; 4 | color?: string; 5 | padding?: [number, number, number, number]; 6 | height: number; 7 | data: Array<{ 8 | x: string; 9 | y: number; 10 | }>; 11 | autoLabel?: boolean; 12 | style?: React.CSSProperties; 13 | } 14 | 15 | export default class Bar extends React.Component {} 16 | -------------------------------------------------------------------------------- /src/components/Charts/ChartCard/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IChartCardProps { 3 | title: React.ReactNode; 4 | action?: React.ReactNode; 5 | total?: React.ReactNode | number | (() => React.ReactNode | number); 6 | footer?: React.ReactNode; 7 | contentHeight?: number; 8 | avatar?: React.ReactNode; 9 | style?: React.CSSProperties; 10 | } 11 | 12 | export default class ChartCard extends React.Component {} 13 | -------------------------------------------------------------------------------- /src/components/Charts/ChartCard/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card } from 'antd'; 3 | import classNames from 'classnames'; 4 | 5 | import styles from './index.less'; 6 | 7 | const renderTotal = total => { 8 | let totalDom; 9 | switch (typeof total) { 10 | case 'undefined': 11 | totalDom = null; 12 | break; 13 | case 'function': 14 | totalDom =
    {total()}
    ; 15 | break; 16 | default: 17 | totalDom =
    {total}
    ; 18 | } 19 | return totalDom; 20 | }; 21 | 22 | const ChartCard = ({ 23 | loading = false, 24 | contentHeight, 25 | title, 26 | avatar, 27 | action, 28 | total, 29 | footer, 30 | children, 31 | ...rest 32 | }) => { 33 | const content = ( 34 |
    35 |
    40 |
    {avatar}
    41 |
    42 |
    43 | {title} 44 | {action} 45 |
    46 | {renderTotal(total)} 47 |
    48 |
    49 | {children && ( 50 |
    51 |
    {children}
    52 |
    53 | )} 54 | {footer && ( 55 |
    60 | {footer} 61 |
    62 | )} 63 |
    64 | ); 65 | 66 | return ( 67 | 68 | {content} 69 | 70 | ); 71 | }; 72 | 73 | export default ChartCard; 74 | -------------------------------------------------------------------------------- /src/components/Charts/ChartCard/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .chartCard { 4 | position: relative; 5 | .chartTop { 6 | position: relative; 7 | overflow: hidden; 8 | width: 100%; 9 | } 10 | .chartTopMargin { 11 | margin-bottom: 12px; 12 | } 13 | .chartTopHasMargin { 14 | margin-bottom: 20px; 15 | } 16 | .metaWrap { 17 | float: left; 18 | } 19 | .avatar { 20 | position: relative; 21 | top: 4px; 22 | float: left; 23 | margin-right: 20px; 24 | img { 25 | border-radius: 100%; 26 | } 27 | } 28 | .meta { 29 | color: @text-color-secondary; 30 | font-size: @font-size-base; 31 | line-height: 22px; 32 | height: 22px; 33 | } 34 | .action { 35 | cursor: pointer; 36 | position: absolute; 37 | top: 0; 38 | right: 0; 39 | } 40 | .total { 41 | overflow: hidden; 42 | text-overflow: ellipsis; 43 | word-break: break-all; 44 | white-space: nowrap; 45 | color: @heading-color; 46 | margin-top: 4px; 47 | margin-bottom: 0; 48 | font-size: 30px; 49 | line-height: 38px; 50 | height: 38px; 51 | } 52 | .content { 53 | margin-bottom: 12px; 54 | position: relative; 55 | width: 100%; 56 | } 57 | .contentFixed { 58 | position: absolute; 59 | left: 0; 60 | bottom: 0; 61 | width: 100%; 62 | } 63 | .footer { 64 | border-top: 1px solid @border-color-split; 65 | padding-top: 9px; 66 | margin-top: 8px; 67 | & > * { 68 | position: relative; 69 | } 70 | } 71 | .footerMargin { 72 | margin-top: 20px; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/components/Charts/Field/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IFieldProps { 3 | label: React.ReactNode; 4 | value: React.ReactNode; 5 | style?: React.CSSProperties; 6 | } 7 | 8 | export default class Field extends React.Component {} 9 | -------------------------------------------------------------------------------- /src/components/Charts/Field/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import styles from './index.less'; 4 | 5 | const Field = ({ label, value, ...rest }) => ( 6 |
    7 | {label} 8 | {value} 9 |
    10 | ); 11 | 12 | export default Field; 13 | -------------------------------------------------------------------------------- /src/components/Charts/Field/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .field { 4 | white-space: nowrap; 5 | overflow: hidden; 6 | text-overflow: ellipsis; 7 | margin: 0; 8 | span { 9 | font-size: @font-size-base; 10 | line-height: 22px; 11 | } 12 | span:last-child { 13 | margin-left: 8px; 14 | color: @heading-color; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Charts/Gauge/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IGaugeProps { 3 | title: React.ReactNode; 4 | color?: string; 5 | height: number; 6 | bgColor?: number; 7 | percent: number; 8 | style?: React.CSSProperties; 9 | } 10 | 11 | export default class Gauge extends React.Component {} 12 | -------------------------------------------------------------------------------- /src/components/Charts/MiniArea/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | // g2已经更新到3.0 4 | // 不带的写了 5 | 6 | export interface IAxis { 7 | title: any; 8 | line: any; 9 | gridAlign: any; 10 | labels: any; 11 | tickLine: any; 12 | grid: any; 13 | } 14 | 15 | export interface IMiniAreaProps { 16 | color?: string; 17 | height: number; 18 | borderColor?: string; 19 | line?: boolean; 20 | animate?: boolean; 21 | xAxis?: IAxis; 22 | yAxis?: IAxis; 23 | data: Array<{ 24 | x: number; 25 | y: number; 26 | }>; 27 | } 28 | 29 | export default class MiniArea extends React.Component {} 30 | -------------------------------------------------------------------------------- /src/components/Charts/MiniBar/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IMiniBarProps { 3 | color?: string; 4 | height: number; 5 | data: Array<{ 6 | x: number | string; 7 | y: number; 8 | }>; 9 | style?: React.CSSProperties; 10 | } 11 | 12 | export default class MiniBar extends React.Component {} 13 | -------------------------------------------------------------------------------- /src/components/Charts/MiniBar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chart, Tooltip, Geom } from 'bizcharts'; 3 | import autoHeight from '../autoHeight'; 4 | import styles from '../index.less'; 5 | 6 | @autoHeight() 7 | export default class MiniBar extends React.Component { 8 | render() { 9 | const { height, forceFit = true, color = '#1890FF', data = [] } = this.props; 10 | 11 | const scale = { 12 | x: { 13 | type: 'cat', 14 | }, 15 | y: { 16 | min: 0, 17 | }, 18 | }; 19 | 20 | const padding = [36, 5, 30, 5]; 21 | 22 | const tooltip = [ 23 | 'x*y', 24 | (x, y) => ({ 25 | name: x, 26 | value: y, 27 | }), 28 | ]; 29 | 30 | // for tooltip not to be hide 31 | const chartHeight = height + 54; 32 | 33 | return ( 34 |
    35 |
    36 | 43 | 44 | 45 | 46 |
    47 |
    48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/Charts/MiniProgress/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IMiniProgressProps { 3 | target: number; 4 | color?: string; 5 | strokeWidth?: number; 6 | percent?: number; 7 | style?: React.CSSProperties; 8 | } 9 | 10 | export default class MiniProgress extends React.Component {} 11 | -------------------------------------------------------------------------------- /src/components/Charts/MiniProgress/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tooltip } from 'antd'; 3 | 4 | import styles from './index.less'; 5 | 6 | const MiniProgress = ({ target, color = 'rgb(19, 194, 194)', strokeWidth, percent }) => ( 7 |
    8 | 9 |
    10 | 11 | 12 |
    13 |
    14 |
    15 |
    23 |
    24 |
    25 | ); 26 | 27 | export default MiniProgress; 28 | -------------------------------------------------------------------------------- /src/components/Charts/MiniProgress/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .miniProgress { 4 | padding: 5px 0; 5 | position: relative; 6 | width: 100%; 7 | .progressWrap { 8 | background-color: @background-color-base; 9 | position: relative; 10 | } 11 | .progress { 12 | transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s; 13 | border-radius: 1px 0 0 1px; 14 | background-color: @primary-color; 15 | width: 0; 16 | height: 100%; 17 | } 18 | .target { 19 | position: absolute; 20 | top: 0; 21 | bottom: 0; 22 | span { 23 | border-radius: 100px; 24 | position: absolute; 25 | top: 0; 26 | left: 0; 27 | height: 4px; 28 | width: 2px; 29 | } 30 | span:last-child { 31 | top: auto; 32 | bottom: 0; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/Charts/Pie/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IPieProps { 3 | animate?: boolean; 4 | color?: string; 5 | height: number; 6 | hasLegend?: boolean; 7 | padding?: [number, number, number, number]; 8 | percent?: number; 9 | data?: Array<{ 10 | x: string | string; 11 | y: number; 12 | }>; 13 | total?: React.ReactNode | number | (() => React.ReactNode | number); 14 | title?: React.ReactNode; 15 | tooltip?: boolean; 16 | valueFormat?: (value: string) => string | React.ReactNode; 17 | subTitle?: React.ReactNode; 18 | } 19 | 20 | export default class Pie extends React.Component {} 21 | -------------------------------------------------------------------------------- /src/components/Charts/Pie/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .pie { 4 | position: relative; 5 | .chart { 6 | position: relative; 7 | } 8 | &.hasLegend .chart { 9 | width: ~'calc(100% - 240px)'; 10 | } 11 | .legend { 12 | position: absolute; 13 | right: 0; 14 | min-width: 200px; 15 | top: 50%; 16 | transform: translateY(-50%); 17 | margin: 0 20px; 18 | list-style: none; 19 | padding: 0; 20 | li { 21 | cursor: pointer; 22 | margin-bottom: 16px; 23 | height: 22px; 24 | line-height: 22px; 25 | &:last-child { 26 | margin-bottom: 0; 27 | } 28 | } 29 | } 30 | .dot { 31 | border-radius: 8px; 32 | display: inline-block; 33 | margin-right: 8px; 34 | position: relative; 35 | top: -1px; 36 | height: 8px; 37 | width: 8px; 38 | } 39 | .line { 40 | background-color: @border-color-split; 41 | display: inline-block; 42 | margin-right: 8px; 43 | width: 1px; 44 | height: 16px; 45 | } 46 | .legendTitle { 47 | color: @text-color; 48 | } 49 | .percent { 50 | color: @text-color-secondary; 51 | } 52 | .value { 53 | position: absolute; 54 | right: 0; 55 | } 56 | .title { 57 | margin-bottom: 8px; 58 | } 59 | .total { 60 | position: absolute; 61 | left: 50%; 62 | top: 50%; 63 | text-align: center; 64 | max-height: 62px; 65 | transform: translate(-50%, -50%); 66 | & > h4 { 67 | color: @text-color-secondary; 68 | font-size: 14px; 69 | line-height: 22px; 70 | height: 22px; 71 | margin-bottom: 8px; 72 | font-weight: normal; 73 | } 74 | & > p { 75 | color: @heading-color; 76 | display: block; 77 | font-size: 1.2em; 78 | height: 32px; 79 | line-height: 32px; 80 | white-space: nowrap; 81 | } 82 | } 83 | } 84 | 85 | .legendBlock { 86 | &.hasLegend .chart { 87 | width: 100%; 88 | margin: 0 0 32px 0; 89 | } 90 | .legend { 91 | position: relative; 92 | transform: none; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/components/Charts/Radar/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IRadarProps { 3 | title?: React.ReactNode; 4 | height: number; 5 | padding?: [number, number, number, number]; 6 | hasLegend?: boolean; 7 | data: Array<{ 8 | name: string; 9 | label: string; 10 | value: string; 11 | }>; 12 | style?: React.CSSProperties; 13 | } 14 | 15 | export default class Radar extends React.Component {} 16 | -------------------------------------------------------------------------------- /src/components/Charts/Radar/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .radar { 4 | .legend { 5 | margin-top: 16px; 6 | .legendItem { 7 | position: relative; 8 | text-align: center; 9 | cursor: pointer; 10 | color: @text-color-secondary; 11 | line-height: 22px; 12 | p { 13 | margin: 0; 14 | } 15 | h6 { 16 | color: @heading-color; 17 | padding-left: 16px; 18 | font-size: 24px; 19 | line-height: 32px; 20 | margin-top: 4px; 21 | margin-bottom: 0; 22 | } 23 | &:after { 24 | background-color: @border-color-split; 25 | position: absolute; 26 | top: 8px; 27 | right: 0; 28 | height: 40px; 29 | width: 1px; 30 | content: ''; 31 | } 32 | } 33 | > :last-child .legendItem:after { 34 | display: none; 35 | } 36 | .dot { 37 | border-radius: 6px; 38 | display: inline-block; 39 | margin-right: 6px; 40 | position: relative; 41 | top: -1px; 42 | height: 6px; 43 | width: 6px; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/components/Charts/TagCloud/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface ITagCloudProps { 3 | data: Array<{ 4 | name: string; 5 | value: number; 6 | }>; 7 | height: number; 8 | style?: React.CSSProperties; 9 | } 10 | 11 | export default class TagCloud extends React.Component {} 12 | -------------------------------------------------------------------------------- /src/components/Charts/TagCloud/index.less: -------------------------------------------------------------------------------- 1 | .tagCloud { 2 | overflow: hidden; 3 | canvas { 4 | transform: scale(0.25); 5 | transform-origin: 0 0; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/Charts/TimelineChart/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface ITimelineChartProps { 3 | data: Array<{ 4 | x: number; 5 | y1: number; 6 | y2?: number; 7 | }>; 8 | titleMap: { y1: string; y2?: string }; 9 | padding?: [number, number, number, number]; 10 | height?: number; 11 | style?: React.CSSProperties; 12 | } 13 | 14 | export default class TimelineChart extends React.Component {} 15 | -------------------------------------------------------------------------------- /src/components/Charts/TimelineChart/index.less: -------------------------------------------------------------------------------- 1 | .timelineChart { 2 | background: #fff; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/Charts/WaterWave/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IWaterWaveProps { 3 | title: React.ReactNode; 4 | color?: string; 5 | height: number; 6 | percent: number; 7 | style?: React.CSSProperties; 8 | } 9 | 10 | export default class WaterWave extends React.Component {} 11 | -------------------------------------------------------------------------------- /src/components/Charts/WaterWave/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .waterWave { 4 | display: inline-block; 5 | position: relative; 6 | transform-origin: left; 7 | .text { 8 | position: absolute; 9 | left: 0; 10 | top: 32px; 11 | text-align: center; 12 | width: 100%; 13 | span { 14 | color: @text-color-secondary; 15 | font-size: 14px; 16 | line-height: 22px; 17 | } 18 | h4 { 19 | color: @heading-color; 20 | line-height: 32px; 21 | font-size: 24px; 22 | } 23 | } 24 | .waterWaveCanvasWrapper { 25 | transform: scale(0.5); 26 | transform-origin: 0 0; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/Charts/autoHeight.js: -------------------------------------------------------------------------------- 1 | /* eslint eqeqeq: 0 */ 2 | import React from 'react'; 3 | 4 | function computeHeight(node) { 5 | const totalHeight = parseInt(getComputedStyle(node).height, 10); 6 | const padding = 7 | parseInt(getComputedStyle(node).paddingTop, 10) + 8 | parseInt(getComputedStyle(node).paddingBottom, 10); 9 | return totalHeight - padding; 10 | } 11 | 12 | function getAutoHeight(n) { 13 | if (!n) { 14 | return 0; 15 | } 16 | 17 | let node = n; 18 | 19 | let height = computeHeight(node); 20 | 21 | while (!height) { 22 | node = node.parentNode; 23 | if (node) { 24 | height = computeHeight(node); 25 | } else { 26 | break; 27 | } 28 | } 29 | 30 | return height; 31 | } 32 | 33 | const autoHeight = () => WrappedComponent => { 34 | return class extends React.Component { 35 | state = { 36 | computedHeight: 0, 37 | }; 38 | 39 | componentDidMount() { 40 | const { height } = this.props; 41 | if (!height) { 42 | const h = getAutoHeight(this.root); 43 | // eslint-disable-next-line 44 | this.setState({ computedHeight: h }); 45 | } 46 | } 47 | 48 | handleRoot = node => { 49 | this.root = node; 50 | }; 51 | 52 | render() { 53 | const { height } = this.props; 54 | const { computedHeight } = this.state; 55 | const h = height || computedHeight; 56 | return ( 57 |
    {h > 0 && }
    58 | ); 59 | } 60 | }; 61 | }; 62 | 63 | export default autoHeight; 64 | -------------------------------------------------------------------------------- /src/components/Charts/bizcharts.d.ts: -------------------------------------------------------------------------------- 1 | import * as BizChart from 'bizcharts'; 2 | 3 | export = BizChart; 4 | -------------------------------------------------------------------------------- /src/components/Charts/bizcharts.js: -------------------------------------------------------------------------------- 1 | import * as BizChart from 'bizcharts'; 2 | 3 | export default BizChart; 4 | -------------------------------------------------------------------------------- /src/components/Charts/demo/bar.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 4 3 | title: 柱状图 4 | --- 5 | 6 | 通过设置 `x`,`y` 属性,可以快速的构建出一个漂亮的柱状图,各种纬度的关系则是通过自定义的数据展现。 7 | 8 | ````jsx 9 | import { Bar } from 'ant-design-pro/lib/Charts'; 10 | 11 | const salesData = []; 12 | for (let i = 0; i < 12; i += 1) { 13 | salesData.push({ 14 | x: `${i + 1}月`, 15 | y: Math.floor(Math.random() * 1000) + 200, 16 | }); 17 | } 18 | 19 | ReactDOM.render( 20 | 25 | , mountNode); 26 | ```` 27 | -------------------------------------------------------------------------------- /src/components/Charts/demo/gauge.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 7 3 | title: 仪表盘 4 | --- 5 | 6 | 仪表盘是一种进度展示方式,可以更直观的展示当前的进展情况,通常也可表示占比。 7 | 8 | ````jsx 9 | import { Gauge } from 'ant-design-pro/lib/Charts'; 10 | 11 | ReactDOM.render( 12 | 17 | , mountNode); 18 | ```` 19 | -------------------------------------------------------------------------------- /src/components/Charts/demo/mini-area.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | col: 2 4 | title: 迷你区域图 5 | --- 6 | 7 | ````jsx 8 | import { MiniArea } from 'ant-design-pro/lib/Charts'; 9 | import moment from 'moment'; 10 | 11 | const visitData = []; 12 | const beginDay = new Date().getTime(); 13 | for (let i = 0; i < 20; i += 1) { 14 | visitData.push({ 15 | x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'), 16 | y: Math.floor(Math.random() * 100) + 10, 17 | }); 18 | } 19 | 20 | ReactDOM.render( 21 | 27 | , mountNode); 28 | ```` 29 | -------------------------------------------------------------------------------- /src/components/Charts/demo/mini-bar.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | col: 2 4 | title: 迷你柱状图 5 | --- 6 | 7 | 迷你柱状图更适合展示简单的区间数据,简洁的表现方式可以很好的减少大数据量的视觉展现压力。 8 | 9 | ````jsx 10 | import { MiniBar } from 'ant-design-pro/lib/Charts'; 11 | import moment from 'moment'; 12 | 13 | const visitData = []; 14 | const beginDay = new Date().getTime(); 15 | for (let i = 0; i < 20; i += 1) { 16 | visitData.push({ 17 | x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'), 18 | y: Math.floor(Math.random() * 100) + 10, 19 | }); 20 | } 21 | 22 | ReactDOM.render( 23 | 27 | , mountNode); 28 | ```` 29 | -------------------------------------------------------------------------------- /src/components/Charts/demo/mini-pie.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 6 3 | title: 迷你饼状图 4 | --- 5 | 6 | 通过简化 `Pie` 属性的设置,可以快速的实现极简的饼状图,可配合 `ChartCard` 组合展 7 | 现更多业务场景。 8 | 9 | ```jsx 10 | import { Pie } from 'ant-design-pro/lib/Charts'; 11 | 12 | ReactDOM.render( 13 | , 14 | mountNode 15 | ); 16 | ``` 17 | -------------------------------------------------------------------------------- /src/components/Charts/demo/mini-progress.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 3 3 | title: 迷你进度条 4 | --- 5 | 6 | ````jsx 7 | import { MiniProgress } from 'ant-design-pro/lib/Charts'; 8 | 9 | ReactDOM.render( 10 | 11 | , mountNode); 12 | ```` 13 | -------------------------------------------------------------------------------- /src/components/Charts/demo/pie.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 5 3 | title: 饼状图 4 | --- 5 | 6 | ```jsx 7 | import { Pie, yuan } from 'ant-design-pro/lib/Charts'; 8 | 9 | const salesPieData = [ 10 | { 11 | x: '家用电器', 12 | y: 4544, 13 | }, 14 | { 15 | x: '食用酒水', 16 | y: 3321, 17 | }, 18 | { 19 | x: '个护健康', 20 | y: 3113, 21 | }, 22 | { 23 | x: '服饰箱包', 24 | y: 2341, 25 | }, 26 | { 27 | x: '母婴产品', 28 | y: 1231, 29 | }, 30 | { 31 | x: '其他', 32 | y: 1231, 33 | }, 34 | ]; 35 | 36 | ReactDOM.render( 37 | ( 42 | now.y + pre, 0)) 45 | }} 46 | /> 47 | )} 48 | data={salesPieData} 49 | valueFormat={val => } 50 | height={294} 51 | />, 52 | mountNode, 53 | ); 54 | ``` 55 | -------------------------------------------------------------------------------- /src/components/Charts/demo/radar.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 7 3 | title: 雷达图 4 | --- 5 | 6 | ````jsx 7 | import { Radar, ChartCard } from 'ant-design-pro/lib/Charts'; 8 | 9 | const radarOriginData = [ 10 | { 11 | name: '个人', 12 | ref: 10, 13 | koubei: 8, 14 | output: 4, 15 | contribute: 5, 16 | hot: 7, 17 | }, 18 | { 19 | name: '团队', 20 | ref: 3, 21 | koubei: 9, 22 | output: 6, 23 | contribute: 3, 24 | hot: 1, 25 | }, 26 | { 27 | name: '部门', 28 | ref: 4, 29 | koubei: 1, 30 | output: 6, 31 | contribute: 5, 32 | hot: 7, 33 | }, 34 | ]; 35 | const radarData = []; 36 | const radarTitleMap = { 37 | ref: '引用', 38 | koubei: '口碑', 39 | output: '产量', 40 | contribute: '贡献', 41 | hot: '热度', 42 | }; 43 | radarOriginData.forEach((item) => { 44 | Object.keys(item).forEach((key) => { 45 | if (key !== 'name') { 46 | radarData.push({ 47 | name: item.name, 48 | label: radarTitleMap[key], 49 | value: item[key], 50 | }); 51 | } 52 | }); 53 | }); 54 | 55 | ReactDOM.render( 56 | 57 | 62 | 63 | , mountNode); 64 | ```` 65 | -------------------------------------------------------------------------------- /src/components/Charts/demo/tag-cloud.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 9 3 | title: 标签云 4 | --- 5 | 6 | 标签云是一套相关的标签以及与此相应的权重展示方式,一般典型的标签云有 30 至 150 个标签,而权重影响使用的字体大小或其他视觉效果。 7 | 8 | ````jsx 9 | import { TagCloud } from 'ant-design-pro/lib/Charts'; 10 | 11 | const tags = []; 12 | for (let i = 0; i < 50; i += 1) { 13 | tags.push({ 14 | name: `TagClout-Title-${i}`, 15 | value: Math.floor((Math.random() * 50)) + 20, 16 | }); 17 | } 18 | 19 | ReactDOM.render( 20 | 24 | , mountNode); 25 | ```` 26 | -------------------------------------------------------------------------------- /src/components/Charts/demo/timeline-chart.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 9 3 | title: 带有时间轴的图表 4 | --- 5 | 6 | 使用 `TimelineChart` 组件可以实现带有时间轴的柱状图展现,而其中的 `x` 属性,则是时间值的指向,默认最多支持同时展现两个指标,分别是 `y1` 和 `y2`。 7 | 8 | ````jsx 9 | import { TimelineChart } from 'ant-design-pro/lib/Charts'; 10 | 11 | const chartData = []; 12 | for (let i = 0; i < 20; i += 1) { 13 | chartData.push({ 14 | x: (new Date().getTime()) + (1000 * 60 * 30 * i), 15 | y1: Math.floor(Math.random() * 100) + 1000, 16 | y2: Math.floor(Math.random() * 100) + 10, 17 | }); 18 | } 19 | 20 | ReactDOM.render( 21 | 26 | , mountNode); 27 | ```` 28 | -------------------------------------------------------------------------------- /src/components/Charts/demo/waterwave.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 8 3 | title: 水波图 4 | --- 5 | 6 | 水波图是一种比例的展示方式,可以更直观的展示关键值的占比。 7 | 8 | ````jsx 9 | import { WaterWave } from 'ant-design-pro/lib/Charts'; 10 | 11 | ReactDOM.render( 12 |
    13 | 18 |
    19 | , mountNode); 20 | ```` 21 | -------------------------------------------------------------------------------- /src/components/Charts/g2.js: -------------------------------------------------------------------------------- 1 | // 全局 G2 设置 2 | import { track, setTheme } from 'bizcharts'; 3 | 4 | track(false); 5 | 6 | const config = { 7 | defaultColor: '#1089ff', 8 | shape: { 9 | interval: { 10 | fillOpacity: 1, 11 | }, 12 | }, 13 | }; 14 | 15 | setTheme(config); 16 | -------------------------------------------------------------------------------- /src/components/Charts/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as numeral from 'numeral'; 2 | export { default as ChartCard } from './ChartCard'; 3 | export { default as Bar } from './Bar'; 4 | export { default as Pie } from './Pie'; 5 | export { default as Radar } from './Radar'; 6 | export { default as Gauge } from './Gauge'; 7 | export { default as MiniArea } from './MiniArea'; 8 | export { default as MiniBar } from './MiniBar'; 9 | export { default as MiniProgress } from './MiniProgress'; 10 | export { default as Field } from './Field'; 11 | export { default as WaterWave } from './WaterWave'; 12 | export { default as TagCloud } from './TagCloud'; 13 | export { default as TimelineChart } from './TimelineChart'; 14 | 15 | declare const yuan: (value: number | string) => string; 16 | 17 | export { yuan }; 18 | -------------------------------------------------------------------------------- /src/components/Charts/index.js: -------------------------------------------------------------------------------- 1 | import numeral from 'numeral'; 2 | import './g2'; 3 | import ChartCard from './ChartCard'; 4 | import Bar from './Bar'; 5 | import Pie from './Pie'; 6 | import Radar from './Radar'; 7 | import Gauge from './Gauge'; 8 | import MiniArea from './MiniArea'; 9 | import MiniBar from './MiniBar'; 10 | import MiniProgress from './MiniProgress'; 11 | import Field from './Field'; 12 | import WaterWave from './WaterWave'; 13 | import TagCloud from './TagCloud'; 14 | import TimelineChart from './TimelineChart'; 15 | 16 | const yuan = val => `¥ ${numeral(val).format('0,0')}`; 17 | 18 | const Charts = { 19 | yuan, 20 | Bar, 21 | Pie, 22 | Gauge, 23 | Radar, 24 | MiniBar, 25 | MiniArea, 26 | MiniProgress, 27 | ChartCard, 28 | Field, 29 | WaterWave, 30 | TagCloud, 31 | TimelineChart, 32 | }; 33 | 34 | export { 35 | Charts as default, 36 | yuan, 37 | Bar, 38 | Pie, 39 | Gauge, 40 | Radar, 41 | MiniBar, 42 | MiniArea, 43 | MiniProgress, 44 | ChartCard, 45 | Field, 46 | WaterWave, 47 | TagCloud, 48 | TimelineChart, 49 | }; 50 | -------------------------------------------------------------------------------- /src/components/Charts/index.less: -------------------------------------------------------------------------------- 1 | .miniChart { 2 | position: relative; 3 | width: 100%; 4 | .chartContent { 5 | position: absolute; 6 | bottom: -28px; 7 | width: 100%; 8 | > div { 9 | margin: 0 -5px; 10 | overflow: hidden; 11 | } 12 | } 13 | .chartLoading { 14 | position: absolute; 15 | top: 16px; 16 | left: 50%; 17 | margin-left: -7px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/CountDown/demo/simple.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 4 | zh-CN: 基本 5 | en-US: Basic 6 | --- 7 | 8 | ## zh-CN 9 | 10 | 简单的倒计时组件使用。 11 | 12 | ## en-US 13 | 14 | The simplest usage. 15 | 16 | ````jsx 17 | import CountDown from 'ant-design-pro/lib/CountDown'; 18 | 19 | const targetTime = new Date().getTime() + 3900000; 20 | 21 | ReactDOM.render( 22 | 23 | , mountNode); 24 | ```` 25 | -------------------------------------------------------------------------------- /src/components/CountDown/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface ICountDownProps { 3 | format?: (time: number) => void; 4 | target: Date | number; 5 | onEnd?: () => void; 6 | style?: React.CSSProperties; 7 | } 8 | 9 | export default class CountDown extends React.Component {} 10 | -------------------------------------------------------------------------------- /src/components/CountDown/index.en-US.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CountDown 3 | cols: 1 4 | order: 3 5 | --- 6 | 7 | Simple CountDown Component. 8 | 9 | ## API 10 | 11 | | Property | Description | Type | Default | 12 | |----------|------------------------------------------|-------------|-------| 13 | | format | Formatter of time | Function(time) | | 14 | | target | Target time | Date | - | 15 | | onEnd | Countdown to the end callback | funtion | -| 16 | -------------------------------------------------------------------------------- /src/components/CountDown/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CountDown 3 | subtitle: 倒计时 4 | cols: 1 5 | order: 3 6 | --- 7 | 8 | 倒计时组件。 9 | 10 | ## API 11 | 12 | | 参数 | 说明 | 类型 | 默认值 | 13 | |----------|------------------------------------------|-------------|-------| 14 | | format | 时间格式化显示 | Function(time) | | 15 | | target | 目标时间 | Date | - | 16 | | onEnd | 倒计时结束回调 | funtion | -| 17 | -------------------------------------------------------------------------------- /src/components/DescriptionList/Description.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export default class Description extends React.Component< 4 | { 5 | term: React.ReactNode; 6 | style?: React.CSSProperties; 7 | }, 8 | any 9 | > {} 10 | -------------------------------------------------------------------------------- /src/components/DescriptionList/Description.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import { Col } from 'antd'; 5 | import styles from './index.less'; 6 | import responsive from './responsive'; 7 | 8 | const Description = ({ term, column, className, children, ...restProps }) => { 9 | const clsString = classNames(styles.description, className); 10 | return ( 11 | 12 | {term &&
    {term}
    } 13 | {children !== null && 14 | children !== undefined &&
    {children}
    } 15 | 16 | ); 17 | }; 18 | 19 | Description.defaultProps = { 20 | term: '', 21 | }; 22 | 23 | Description.propTypes = { 24 | term: PropTypes.node, 25 | }; 26 | 27 | export default Description; 28 | -------------------------------------------------------------------------------- /src/components/DescriptionList/DescriptionList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import { Row } from 'antd'; 4 | import styles from './index.less'; 5 | 6 | const DescriptionList = ({ 7 | className, 8 | title, 9 | col = 3, 10 | layout = 'horizontal', 11 | gutter = 32, 12 | children, 13 | size, 14 | ...restProps 15 | }) => { 16 | const clsString = classNames(styles.descriptionList, styles[layout], className, { 17 | [styles.small]: size === 'small', 18 | [styles.large]: size === 'large', 19 | }); 20 | const column = col > 4 ? 4 : col; 21 | return ( 22 |
    23 | {title ?
    {title}
    : null} 24 | 25 | {React.Children.map( 26 | children, 27 | child => (child ? React.cloneElement(child, { column }) : child) 28 | )} 29 | 30 |
    31 | ); 32 | }; 33 | 34 | export default DescriptionList; 35 | -------------------------------------------------------------------------------- /src/components/DescriptionList/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 4 | zh-CN: 基本 5 | en-US: Basic 6 | --- 7 | 8 | ## zh-CN 9 | 10 | 基本描述列表。 11 | 12 | ## en-US 13 | 14 | Basic DescriptionList. 15 | 16 | ````jsx 17 | import DescriptionList from 'ant-design-pro/lib/DescriptionList'; 18 | 19 | const { Description } = DescriptionList; 20 | 21 | ReactDOM.render( 22 | 23 | 24 | A free, open source, cross-platform, 25 | graphical web browser developed by the 26 | Mozilla Corporation and hundreds of 27 | volunteers. 28 | 29 | 30 | A free, open source, cross-platform, 31 | graphical web browser developed by the 32 | Mozilla Corporation and hundreds of 33 | volunteers. 34 | 35 | 36 | A free, open source, cross-platform, 37 | graphical web browser developed by the 38 | Mozilla Corporation and hundreds of 39 | volunteers. 40 | 41 | 42 | , mountNode); 43 | ```` 44 | -------------------------------------------------------------------------------- /src/components/DescriptionList/demo/vertical.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: 4 | zh-CN: 垂直型 5 | en-US: Vertical 6 | --- 7 | 8 | ## zh-CN 9 | 10 | 垂直布局。 11 | 12 | ## en-US 13 | 14 | Vertical layout. 15 | 16 | ````jsx 17 | import DescriptionList from 'ant-design-pro/lib/DescriptionList'; 18 | 19 | const { Description } = DescriptionList; 20 | 21 | ReactDOM.render( 22 | 23 | 24 | A free, open source, cross-platform, 25 | graphical web browser developed by the 26 | Mozilla Corporation and hundreds of 27 | volunteers. 28 | 29 | 30 | A free, open source, cross-platform, 31 | graphical web browser developed by the 32 | Mozilla Corporation and hundreds of 33 | volunteers. 34 | 35 | 36 | A free, open source, cross-platform, 37 | graphical web browser developed by the 38 | Mozilla Corporation and hundreds of 39 | volunteers. 40 | 41 | 42 | , mountNode); 43 | ```` 44 | -------------------------------------------------------------------------------- /src/components/DescriptionList/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Description from './Description'; 3 | 4 | export interface IDescriptionListProps { 5 | layout?: 'horizontal' | 'vertical'; 6 | col?: number; 7 | title: React.ReactNode; 8 | gutter?: number; 9 | size?: 'large' | 'small'; 10 | style?: React.CSSProperties; 11 | } 12 | 13 | export default class DescriptionList extends React.Component { 14 | public static Description: typeof Description; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/DescriptionList/index.en-US.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: DescriptionList 3 | cols: 1 4 | order: 4 5 | --- 6 | 7 | Groups display multiple read-only fields, which are common to informational displays on detail pages. 8 | 9 | ## API 10 | 11 | ### DescriptionList 12 | 13 | | Property | Description | Type | Default | 14 | |----------|------------------------------------------|-------------|---------| 15 | | layout | type of layout | Enum{'horizontal', 'vertical'} | 'horizontal' | 16 | | col | specify the maximum number of columns to display, the final columns number is determined by col setting combined with [Responsive Rules](/components/DescriptionList#Responsive-Rules) | number(0 < col <= 4) | 3 | 17 | | title | title | ReactNode | - | 18 | | gutter | specify the distance between two items, unit is `px` | number | 32 | 19 | | size | size of list | Enum{'large', 'small'} | - | 20 | 21 | #### Responsive Rules 22 | 23 | | Window Width | Columns Number | 24 | |---------------------|---------------------------------------------| 25 | | `≥768px` | `col` | 26 | | `≥576px` | `col < 2 ? col : 2` | 27 | | `<576px` | `1` | 28 | 29 | ### DescriptionList.Description 30 | 31 | | Property | Description | Type | Default | 32 | |----------|------------------------------------------|-------------|-------| 33 | | term | item title | ReactNode | - | 34 | -------------------------------------------------------------------------------- /src/components/DescriptionList/index.js: -------------------------------------------------------------------------------- 1 | import DescriptionList from './DescriptionList'; 2 | import Description from './Description'; 3 | 4 | DescriptionList.Description = Description; 5 | export default DescriptionList; 6 | -------------------------------------------------------------------------------- /src/components/DescriptionList/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .descriptionList { 4 | // offset the padding-bottom of last row 5 | :global { 6 | .ant-row { 7 | margin-bottom: -16px; 8 | overflow: hidden; 9 | } 10 | } 11 | 12 | .title { 13 | font-size: 14px; 14 | color: @heading-color; 15 | font-weight: 500; 16 | margin-bottom: 16px; 17 | } 18 | 19 | .term { 20 | // Line-height is 22px IE dom height will calculate error 21 | line-height: 20px; 22 | padding-bottom: 16px; 23 | margin-right: 8px; 24 | color: @heading-color; 25 | white-space: nowrap; 26 | display: table-cell; 27 | 28 | &:after { 29 | content: ':'; 30 | margin: 0 8px 0 2px; 31 | position: relative; 32 | top: -0.5px; 33 | } 34 | } 35 | 36 | .detail { 37 | line-height: 22px; 38 | width: 100%; 39 | padding-bottom: 16px; 40 | color: @text-color; 41 | display: table-cell; 42 | } 43 | 44 | &.small { 45 | // offset the padding-bottom of last row 46 | :global { 47 | .ant-row { 48 | margin-bottom: -8px; 49 | } 50 | } 51 | .title { 52 | margin-bottom: 12px; 53 | color: @text-color; 54 | } 55 | .term, 56 | .detail { 57 | padding-bottom: 8px; 58 | } 59 | } 60 | 61 | &.large { 62 | .title { 63 | font-size: 16px; 64 | } 65 | } 66 | 67 | &.vertical { 68 | .term { 69 | padding-bottom: 8px; 70 | display: block; 71 | } 72 | 73 | .detail { 74 | display: block; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/components/DescriptionList/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: DescriptionList 3 | subtitle: 描述列表 4 | cols: 1 5 | order: 4 6 | --- 7 | 8 | 成组展示多个只读字段,常见于详情页的信息展示。 9 | 10 | ## API 11 | 12 | ### DescriptionList 13 | 14 | | 参数 | 说明 | 类型 | 默认值 | 15 | |----------|------------------------------------------|-------------|-------| 16 | | layout | 布局方式 | Enum{'horizontal', 'vertical'} | 'horizontal' | 17 | | col | 指定信息最多分几列展示,最终一行几列由 col 配置结合[响应式规则](/components/DescriptionList#响应式规则)决定 | number(0 < col <= 4) | 3 | 18 | | title | 列表标题 | ReactNode | - | 19 | | gutter | 列表项间距,单位为 `px` | number | 32 | 20 | | size | 列表型号 | Enum{'large', 'small'} | - | 21 | 22 | #### 响应式规则 23 | 24 | | 窗口宽度 | 展示列数 | 25 | |---------------------|---------------------------------------------| 26 | | `≥768px` | `col` | 27 | | `≥576px` | `col < 2 ? col : 2` | 28 | | `<576px` | `1` | 29 | 30 | ### DescriptionList.Description 31 | 32 | | 参数 | 说明 | 类型 | 默认值 | 33 | |----------|------------------------------------------|-------------|-------| 34 | | term | 列表项标题 | ReactNode | - | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/components/DescriptionList/responsive.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 1: { xs: 24 }, 3 | 2: { xs: 24, sm: 12 }, 4 | 3: { xs: 24, sm: 12, md: 8 }, 5 | 4: { xs: 24, sm: 12, md: 6 }, 6 | }; 7 | -------------------------------------------------------------------------------- /src/components/EditableItem/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Input, Icon } from 'antd'; 3 | import styles from './index.less'; 4 | 5 | export default class EditableItem extends PureComponent { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | value: props.value, 10 | editable: false, 11 | }; 12 | } 13 | 14 | handleChange = e => { 15 | const { value } = e.target; 16 | this.setState({ value }); 17 | }; 18 | 19 | check = () => { 20 | this.setState({ editable: false }); 21 | const { onChange } = this.props; 22 | const { value } = this.state; 23 | if (onChange) { 24 | onChange(value); 25 | } 26 | }; 27 | 28 | edit = () => { 29 | this.setState({ editable: true }); 30 | }; 31 | 32 | render() { 33 | const { value, editable } = this.state; 34 | return ( 35 |
    36 | {editable ? ( 37 |
    38 | 39 | 40 |
    41 | ) : ( 42 |
    43 | {value || ' '} 44 | 45 |
    46 | )} 47 |
    48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/EditableItem/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .editableItem { 4 | line-height: @input-height-base; 5 | display: table; 6 | width: 100%; 7 | margin-top: (@font-size-base * @line-height-base - @input-height-base) / 2; 8 | 9 | .wrapper { 10 | display: table-row; 11 | 12 | & > * { 13 | display: table-cell; 14 | } 15 | 16 | & > *:first-child { 17 | width: 85%; 18 | } 19 | 20 | .icon { 21 | cursor: pointer; 22 | text-align: right; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/EditableLinkGroup/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent, createElement } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Button } from 'antd'; 4 | import styles from './index.less'; 5 | 6 | // TODO: 添加逻辑 7 | 8 | class EditableLinkGroup extends PureComponent { 9 | static propTypes = { 10 | links: PropTypes.array, 11 | onAdd: PropTypes.func, 12 | linkElement: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), 13 | }; 14 | 15 | static defaultProps = { 16 | links: [], 17 | onAdd: () => {}, 18 | linkElement: 'a', 19 | }; 20 | 21 | render() { 22 | const { links, linkElement, onAdd } = this.props; 23 | return ( 24 |
    25 | {links.map(link => 26 | createElement( 27 | linkElement, 28 | { 29 | key: `linkGroup-item-${link.id || link.title}`, 30 | to: link.href, 31 | href: link.href, 32 | }, 33 | link.title 34 | ) 35 | )} 36 | { 37 | 40 | } 41 |
    42 | ); 43 | } 44 | } 45 | 46 | export default EditableLinkGroup; 47 | -------------------------------------------------------------------------------- /src/components/EditableLinkGroup/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .linkGroup { 4 | padding: 20px 0 8px 24px; 5 | font-size: 0; 6 | & > a { 7 | color: @text-color; 8 | display: inline-block; 9 | font-size: @font-size-base; 10 | margin-bottom: 13px; 11 | width: 25%; 12 | &:hover { 13 | color: @primary-color; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Ellipsis/demo/line.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: 4 | zh-CN: 按照行数省略 5 | en-US: Truncate according to the number of rows 6 | --- 7 | 8 | ## zh-CN 9 | 10 | 通过设置 `lines` 属性指定最大行数,如果超过这个行数的文本会自动截取。但是在这种模式下所有 `children` 将会被转换成纯文本。 11 | 12 | 并且注意在这种模式下,外容器需要有指定的宽度(或设置自身宽度)。 13 | 14 | ## en-US 15 | 16 | `lines` attribute specifies the maximum number of rows where the text will automatically be truncated when exceeded. In this mode, all children will be converted to plain text. 17 | 18 | Also note that, in this mode, the outer container needs to have a specified width (or set its own width). 19 | 20 | 21 | ````jsx 22 | import Ellipsis from 'ant-design-pro/lib/Ellipsis'; 23 | 24 | const article =

    There were injuries alleged in three cases in 2015, and a fourth incident in September, according to the safety recall report. After meeting with US regulators in October, the firm decided to issue a voluntary recall.

    ; 25 | 26 | ReactDOM.render( 27 |
    28 | {article} 29 |
    30 | , mountNode); 31 | ```` 32 | -------------------------------------------------------------------------------- /src/components/Ellipsis/demo/number.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 4 | zh-CN: 按照字符数省略 5 | en-US: Truncate according to the number of character 6 | --- 7 | 8 | ## zh-CN 9 | 10 | 通过设置 `length` 属性指定文本最长长度,如果超过这个长度会自动截取。 11 | 12 | ## en-US 13 | 14 | `length` attribute specifies the maximum length where the text will automatically be truncated when exceeded. 15 | 16 | ````jsx 17 | import Ellipsis from 'ant-design-pro/lib/Ellipsis'; 18 | 19 | const article = 'There were injuries alleged in three cases in 2015, and a fourth incident in September, according to the safety recall report. After meeting with US regulators in October, the firm decided to issue a voluntary recall.'; 20 | 21 | ReactDOM.render( 22 |
    23 | {article} 24 |

    Show Tooltip

    25 | {article} 26 |
    27 | , mountNode); 28 | ```` 29 | -------------------------------------------------------------------------------- /src/components/Ellipsis/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IEllipsisProps { 3 | tooltip?: boolean; 4 | length?: number; 5 | lines?: number; 6 | style?: React.CSSProperties; 7 | className?: string; 8 | fullWidthRecognition?: boolean; 9 | } 10 | 11 | export default class Ellipsis extends React.Component {} 12 | -------------------------------------------------------------------------------- /src/components/Ellipsis/index.en-US.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Ellipsis 3 | cols: 1 4 | order: 10 5 | --- 6 | 7 | When the text is too long, the Ellipsis automatically shortens it according to its length or the maximum number of lines. 8 | 9 | ## API 10 | 11 | Property | Description | Type | Default 12 | ----|------|-----|------ 13 | tooltip | tooltip for showing the full text content when hovering over | boolean | - 14 | length | maximum number of characters in the text before being truncated | number | - 15 | lines | maximum number of rows in the text before being truncated | number | `1` 16 | fullWidthRecognition | whether consider full-width character length as 2 when calculate string length | boolean | - 17 | -------------------------------------------------------------------------------- /src/components/Ellipsis/index.less: -------------------------------------------------------------------------------- 1 | .ellipsis { 2 | overflow: hidden; 3 | display: inline-block; 4 | word-break: break-all; 5 | width: 100%; 6 | } 7 | 8 | .lines { 9 | position: relative; 10 | .shadow { 11 | display: block; 12 | position: relative; 13 | color: transparent; 14 | opacity: 0; 15 | z-index: -999; 16 | } 17 | } 18 | 19 | .lineClamp { 20 | position: relative; 21 | overflow: hidden; 22 | text-overflow: ellipsis; 23 | display: -webkit-box; 24 | } 25 | -------------------------------------------------------------------------------- /src/components/Ellipsis/index.test.js: -------------------------------------------------------------------------------- 1 | import { getStrFullLength, cutStrByFullLength } from './index.js'; 2 | 3 | describe('test calculateShowLength', () => { 4 | it('get full length', () => { 5 | expect(getStrFullLength('一二,a,')).toEqual(8); 6 | }); 7 | it('cut str by full length', () => { 8 | expect(cutStrByFullLength('一二,a,', 7)).toEqual('一二,a'); 9 | }); 10 | it('cut str when length small', () => { 11 | expect(cutStrByFullLength('一22三', 5)).toEqual('一22'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/components/Ellipsis/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Ellipsis 3 | subtitle: 文本自动省略号 4 | cols: 1 5 | order: 10 6 | --- 7 | 8 | 文本过长自动处理省略号,支持按照文本长度和最大行数两种方式截取。 9 | 10 | ## API 11 | 12 | 参数 | 说明 | 类型 | 默认值 13 | ----|------|-----|------ 14 | tooltip | 移动到文本展示完整内容的提示 | boolean | - 15 | length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | - 16 | lines | 在按照行数截取下最大的行数,超过则截取省略 | number | `1` 17 | fullWidthRecognition | 是否将全角字符的长度视为2来计算字符串长度 | boolean | - 18 | -------------------------------------------------------------------------------- /src/components/Exception/demo/403.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | title: 4 | zh-CN: 403 5 | en-US: 403 6 | --- 7 | 8 | ## zh-CN 9 | 10 | 403 页面,配合自定义操作。 11 | 12 | ## en-US 13 | 14 | 403 page with custom operations. 15 | 16 | ````jsx 17 | import Exception from 'ant-design-pro/lib/Exception'; 18 | import { Button } from 'antd'; 19 | 20 | const actions = ( 21 |
    22 | 23 | 24 |
    25 | ); 26 | ReactDOM.render( 27 | 28 | , mountNode); 29 | ```` 30 | -------------------------------------------------------------------------------- /src/components/Exception/demo/404.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 4 | zh-CN: 404 5 | en-US: 404 6 | --- 7 | 8 | ## zh-CN 9 | 10 | 404 页面。 11 | 12 | ## en-US 13 | 14 | 404 page. 15 | 16 | ````jsx 17 | import Exception from 'ant-design-pro/lib/Exception'; 18 | 19 | ReactDOM.render( 20 | 21 | , mountNode); 22 | ```` 23 | -------------------------------------------------------------------------------- /src/components/Exception/demo/500.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: 4 | zh-CN: 500 5 | en-US: 500 6 | --- 7 | 8 | ## zh-CN 9 | 10 | 500 页面。 11 | 12 | ## en-US 13 | 14 | 500 page. 15 | 16 | ````jsx 17 | import Exception from 'ant-design-pro/lib/Exception'; 18 | 19 | ReactDOM.render( 20 | 21 | , mountNode); 22 | ```` 23 | -------------------------------------------------------------------------------- /src/components/Exception/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IExceptionProps { 3 | type?: '403' | '404' | '500'; 4 | title?: React.ReactNode; 5 | desc?: React.ReactNode; 6 | img?: string; 7 | actions?: React.ReactNode; 8 | linkElement?: React.ReactNode; 9 | style?: React.CSSProperties; 10 | } 11 | 12 | export default class Exception extends React.Component {} 13 | -------------------------------------------------------------------------------- /src/components/Exception/index.en-US.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Exception 3 | cols: 1 4 | order: 5 5 | --- 6 | 7 | Exceptions page is used to provide feedback on specific abnormal state. Usually, it contains an explanation of the error status, and provides users with suggestions or operations, to prevent users from feeling lost and confused. 8 | 9 | ## API 10 | 11 | Property | Description | Type | Default 12 | ---------|-------------|------|-------- 13 | type | type of exception, the corresponding default `title`, `desc`, `img` will be given if set, which can be overridden by explicit setting of `title`, `desc`, `img` | Enum {'403', '404', '500'} | - 14 | title | title | ReactNode | - 15 | desc | supplementary description | ReactNode | - 16 | img | the url of background image | string | - 17 | actions | suggested operations, a default 'Home' link will show if not set | ReactNode | - 18 | linkElement | to specify the element of link | string\|ReactElement | 'a' -------------------------------------------------------------------------------- /src/components/Exception/index.js: -------------------------------------------------------------------------------- 1 | import React, { createElement } from 'react'; 2 | import classNames from 'classnames'; 3 | import { Button } from 'antd'; 4 | import config from './typeConfig'; 5 | import styles from './index.less'; 6 | 7 | const Exception = ({ className, linkElement = 'a', type, title, desc, img, actions, ...rest }) => { 8 | const pageType = type in config ? type : '404'; 9 | const clsString = classNames(styles.exception, className); 10 | return ( 11 |
    12 |
    13 |
    17 |
    18 |
    19 |

    {title || config[pageType].title}

    20 |
    {desc || config[pageType].desc}
    21 |
    22 | {actions || 23 | createElement( 24 | linkElement, 25 | { 26 | to: '/', 27 | href: '/', 28 | }, 29 | 30 | )} 31 |
    32 |
    33 |
    34 | ); 35 | }; 36 | 37 | export default Exception; 38 | -------------------------------------------------------------------------------- /src/components/Exception/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .exception { 4 | display: flex; 5 | align-items: center; 6 | height: 100%; 7 | 8 | .imgBlock { 9 | flex: 0 0 62.5%; 10 | width: 62.5%; 11 | padding-right: 152px; 12 | zoom: 1; 13 | &:before, 14 | &:after { 15 | content: ' '; 16 | display: table; 17 | } 18 | &:after { 19 | clear: both; 20 | visibility: hidden; 21 | font-size: 0; 22 | height: 0; 23 | } 24 | } 25 | 26 | .imgEle { 27 | height: 360px; 28 | width: 100%; 29 | max-width: 430px; 30 | float: right; 31 | background-repeat: no-repeat; 32 | background-position: 50% 50%; 33 | background-size: contain; 34 | } 35 | 36 | .content { 37 | flex: auto; 38 | 39 | h1 { 40 | color: #434e59; 41 | font-size: 72px; 42 | font-weight: 600; 43 | line-height: 72px; 44 | margin-bottom: 24px; 45 | } 46 | 47 | .desc { 48 | color: @text-color-secondary; 49 | font-size: 20px; 50 | line-height: 28px; 51 | margin-bottom: 16px; 52 | } 53 | 54 | .actions { 55 | button:not(:last-child) { 56 | margin-right: 8px; 57 | } 58 | } 59 | } 60 | } 61 | 62 | @media screen and (max-width: @screen-xl) { 63 | .exception { 64 | .imgBlock { 65 | padding-right: 88px; 66 | } 67 | } 68 | } 69 | 70 | @media screen and (max-width: @screen-sm) { 71 | .exception { 72 | display: block; 73 | text-align: center; 74 | .imgBlock { 75 | padding-right: 0; 76 | margin: 0 auto 24px; 77 | } 78 | } 79 | } 80 | 81 | @media screen and (max-width: @screen-xs) { 82 | .exception { 83 | .imgBlock { 84 | margin-bottom: -24px; 85 | overflow: hidden; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/components/Exception/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Exception 3 | subtitle: 异常 4 | cols: 1 5 | order: 5 6 | --- 7 | 8 | 异常页用于对页面特定的异常状态进行反馈。通常,它包含对错误状态的阐述,并向用户提供建议或操作,避免用户感到迷失和困惑。 9 | 10 | ## API 11 | 12 | | 参数 | 说明 | 类型 | 默认值 | 13 | |-------------|------------------------------------------|-------------|-------| 14 | | type | 页面类型,若配置,则自带对应类型默认的 `title`,`desc`,`img`,此默认设置可以被 `title`,`desc`,`img` 覆盖 | Enum {'403', '404', '500'} | - | 15 | | title | 标题 | ReactNode | - | 16 | | desc | 补充描述 | ReactNode | - | 17 | | img | 背景图片地址 | string | - | 18 | | actions | 建议操作,配置此属性时默认的『返回首页』按钮不生效 | ReactNode | - | 19 | | linkElement | 定义链接的元素 | string\|ReactElement | 'a' | 20 | -------------------------------------------------------------------------------- /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/FooterToolbar/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 4 | zh-CN: 演示 5 | en-US: demo 6 | iframe: 400 7 | --- 8 | 9 | ## zh-CN 10 | 11 | 浮动固定页脚。 12 | 13 | ## en-US 14 | 15 | Fixed to the footer. 16 | 17 | ````jsx 18 | import FooterToolbar from 'ant-design-pro/lib/FooterToolbar'; 19 | import { Button } from 'antd'; 20 | 21 | ReactDOM.render( 22 |
    23 |

    Content Content Content Content

    24 |

    Content Content Content Content

    25 |

    Content Content Content Content

    26 |

    Content Content Content Content

    27 |

    Content Content Content Content

    28 |

    Content Content Content Content

    29 |

    Content Content Content Content

    30 |

    Content Content Content Content

    31 |

    Content Content Content Content

    32 |

    Content Content Content Content

    33 |

    Content Content Content Content

    34 |

    Content Content Content Content

    35 |

    Content Content Content Content

    36 |

    Content Content Content Content

    37 |

    Content Content Content Content

    38 | 39 | 40 | 41 | 42 |
    43 | , mountNode); 44 | ```` -------------------------------------------------------------------------------- /src/components/FooterToolbar/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IFooterToolbarProps { 3 | extra: React.ReactNode; 4 | style?: React.CSSProperties; 5 | } 6 | 7 | export default class FooterToolbar extends React.Component {} 8 | -------------------------------------------------------------------------------- /src/components/FooterToolbar/index.en-US.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: FooterToolbar 3 | cols: 1 4 | order: 6 5 | --- 6 | 7 | A toolbar fixed at the bottom. 8 | 9 | ## Usage 10 | 11 | It is fixed at the bottom of the content area and does not move along with the scroll bar, which is usually used for data collection and submission for long pages. 12 | 13 | ## API 14 | 15 | Property | Description | Type | Default 16 | ---------|-------------|------|-------- 17 | children | toolbar content, align to the right | ReactNode | - 18 | extra | extra information, align to the left | ReactNode | - -------------------------------------------------------------------------------- /src/components/FooterToolbar/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import styles from './index.less'; 5 | 6 | export default class FooterToolbar extends Component { 7 | static contextTypes = { 8 | isMobile: PropTypes.bool, 9 | }; 10 | 11 | state = { 12 | width: undefined, 13 | }; 14 | 15 | componentDidMount() { 16 | window.addEventListener('resize', this.resizeFooterToolbar); 17 | this.resizeFooterToolbar(); 18 | } 19 | 20 | componentWillUnmount() { 21 | window.removeEventListener('resize', this.resizeFooterToolbar); 22 | } 23 | 24 | resizeFooterToolbar = () => { 25 | const sider = document.querySelector('.ant-layout-sider'); 26 | if (sider == null) { 27 | return; 28 | } 29 | const { isMobile } = this.context; 30 | const width = isMobile ? null : `calc(100% - ${sider.style.width})`; 31 | const { width: stateWidth } = this.state; 32 | if (stateWidth !== width) { 33 | this.setState({ width }); 34 | } 35 | }; 36 | 37 | render() { 38 | const { children, className, extra, ...restProps } = this.props; 39 | const { width } = this.state; 40 | return ( 41 |
    42 |
    {extra}
    43 |
    {children}
    44 |
    45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/FooterToolbar/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .toolbar { 4 | position: fixed; 5 | width: 100%; 6 | bottom: 0; 7 | right: 0; 8 | height: 56px; 9 | line-height: 56px; 10 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.03); 11 | background: #fff; 12 | border-top: 1px solid @border-color-split; 13 | padding: 0 24px; 14 | z-index: 9; 15 | 16 | &:after { 17 | content: ''; 18 | display: block; 19 | clear: both; 20 | } 21 | 22 | .left { 23 | float: left; 24 | } 25 | 26 | .right { 27 | float: right; 28 | } 29 | 30 | button + button { 31 | margin-left: 8px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/FooterToolbar/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: FooterToolbar 3 | subtitle: 底部工具栏 4 | cols: 1 5 | order: 6 6 | --- 7 | 8 | 固定在底部的工具栏。 9 | 10 | ## 何时使用 11 | 12 | 固定在内容区域的底部,不随滚动条移动,常用于长页面的数据搜集和提交工作。 13 | 14 | ## API 15 | 16 | 参数 | 说明 | 类型 | 默认值 17 | ----|------|-----|------ 18 | children | 工具栏内容,向右对齐 | ReactNode | - 19 | extra | 额外信息,向左对齐 | ReactNode | - 20 | -------------------------------------------------------------------------------- /src/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/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/components/GlobalFooter/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import styles from './index.less'; 4 | 5 | const GlobalFooter = ({ className, links, copyright }) => { 6 | const clsString = classNames(styles.globalFooter, className); 7 | return ( 8 |
    9 | {links && ( 10 |
    11 | {links.map(link => ( 12 | 13 | {link.title} 14 | 15 | ))} 16 |
    17 | )} 18 | {copyright &&
    {copyright}
    } 19 |
    20 | ); 21 | }; 22 | 23 | export default GlobalFooter; 24 | -------------------------------------------------------------------------------- /src/components/GlobalFooter/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .globalFooter { 4 | padding: 0 16px; 5 | margin: 48px 0 24px 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/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/components/HeaderSearch/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 全局搜索 4 | --- 5 | 6 | 通常放置在导航工具条右侧。(点击搜索图标预览效果) 7 | 8 | ````jsx 9 | import HeaderSearch from 'ant-design-pro/lib/HeaderSearch'; 10 | 11 | ReactDOM.render( 12 |
    22 | { 26 | console.log('input', value); // eslint-disable-line 27 | }} 28 | onPressEnter={(value) => { 29 | console.log('enter', value); // eslint-disable-line 30 | }} 31 | /> 32 |
    33 | , mountNode); 34 | ```` 35 | -------------------------------------------------------------------------------- /src/components/HeaderSearch/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IHeaderSearchProps { 3 | placeholder?: string; 4 | dataSource?: string[]; 5 | onSearch?: (value: string) => void; 6 | onChange?: (value: string) => void; 7 | onPressEnter?: (value: string) => void; 8 | style?: React.CSSProperties; 9 | className?: string; 10 | } 11 | 12 | export default class HeaderSearch extends React.Component {} 13 | -------------------------------------------------------------------------------- /src/components/HeaderSearch/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .headerSearch { 4 | :global(.anticon-search) { 5 | cursor: pointer; 6 | font-size: 16px; 7 | } 8 | .input { 9 | transition: width 0.3s, margin-left 0.3s; 10 | width: 0; 11 | background: transparent; 12 | border-radius: 0; 13 | :global(.ant-select-selection) { 14 | background: transparent; 15 | } 16 | input { 17 | border: 0; 18 | padding-left: 0; 19 | padding-right: 0; 20 | box-shadow: none !important; 21 | } 22 | &, 23 | &:hover, 24 | &:focus { 25 | border-bottom: 1px solid @border-color-base; 26 | } 27 | &.show { 28 | width: 210px; 29 | margin-left: 8px; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/HeaderSearch/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: HeaderSearch 4 | zh-CN: HeaderSearch 5 | subtitle: 顶部搜索框 6 | cols: 1 7 | order: 8 8 | --- 9 | 10 | 通常作为全局搜索的入口,放置在导航工具条右侧。 11 | 12 | ## API 13 | 14 | 参数 | 说明 | 类型 | 默认值 15 | ----|------|-----|------ 16 | placeholder | 占位文字 | string | - 17 | dataSource | 当前提示内容列表 | string[] | - 18 | onSearch | 选择某项或按下回车时的回调 | function(value) | - 19 | onChange | 输入搜索字符的回调 | function(value) | - 20 | onPressEnter | 按下回车时的回调 | function(value) | - 21 | defaultOpen | 输入框首次显示是否打开 | boolean | false 22 | -------------------------------------------------------------------------------- /src/components/Login/LoginSubmit.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import { Button, Form } from 'antd'; 4 | import styles from './index.less'; 5 | 6 | const FormItem = Form.Item; 7 | 8 | const LoginSubmit = ({ className, ...rest }) => { 9 | const clsString = classNames(styles.submit, className); 10 | return ( 11 | 12 |
    40 | , mountNode); 41 | ```` 42 | 43 | 69 | -------------------------------------------------------------------------------- /src/components/PageHeader/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IPageHeaderProps { 3 | title?: React.ReactNode | string; 4 | logo?: React.ReactNode | string; 5 | action?: React.ReactNode | string; 6 | content?: React.ReactNode; 7 | extraContent?: React.ReactNode; 8 | routes?: any[]; 9 | params?: any; 10 | breadcrumbList?: Array<{ title: React.ReactNode; href?: string }>; 11 | tabList?: Array<{ key: string; tab: React.ReactNode }>; 12 | tabActiveKey?: string; 13 | tabDefaultActiveKey?: string; 14 | onTabChange?: (key: string) => void; 15 | tabBarExtraContent?: React.ReactNode; 16 | linkElement?: React.ReactNode; 17 | style?: React.CSSProperties; 18 | } 19 | 20 | export default class PageHeader extends React.Component {} 21 | -------------------------------------------------------------------------------- /src/components/PageHeader/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: PageHeader 4 | zh-CN: PageHeader 5 | subtitle: 页头 6 | cols: 1 7 | order: 11 8 | --- 9 | 10 | 页头用来声明页面的主题,包含了用户所关注的最重要的信息,使用户可以快速理解当前页面是什么以及它的功能。 11 | 12 | ## API 13 | 14 | | 参数 | 说明 | 类型 | 默认值 | 15 | |----------|------------------------------------------|-------------|-------| 16 | | title | title 区域 | ReactNode | - | 17 | | logo | logo区域 | ReactNode | - | 18 | | action | 操作区,位于 title 行的行尾 | ReactNode | - | 19 | | content | 内容区 | ReactNode | - | 20 | | extraContent | 额外内容区,位于content的右侧 | ReactNode | - | 21 | | breadcrumbList | 面包屑数据,配置了此属性时 `routes` `params` `location` `breadcrumbNameMap` 无效 | array<{title: ReactNode, href?: string}> | - | 22 | | routes | 面包屑相关属性,router 的路由栈信息 | object[] | - | 23 | | params | 面包屑相关属性,路由的参数 | object | - | 24 | | location | 面包屑相关属性,当前的路由信息 | object | - | 25 | | breadcrumbNameMap | 面包屑相关属性,路由的地址-名称映射表 | object | - | 26 | | tabList | tab 标题列表 | array<{key: string, tab: ReactNode}> | - | 27 | | tabActiveKey | 当前高亮的 tab 项 | string | - | 28 | | tabDefaultActiveKey | 默认高亮的 tab 项 | string | 第一项 | 29 | | onTabChange | 切换面板的回调 | (key) => void | - | 30 | | linkElement | 定义链接的元素,默认为 `a`,可传入 react-router 的 Link | string\|ReactElement | - | 31 | 32 | > 面包屑的配置方式有三种,一是直接配置 `breadcrumbList`,二是结合 `react-router@2` `react-router@3`,配置 `routes` 及 `params` 实现,类似 [面包屑 Demo](https://ant.design/components/breadcrumb-cn/#components-breadcrumb-demo-router),三是结合 `react-router@4`,配置 `location` `breadcrumbNameMap`,优先级依次递减,脚手架中使用最后一种。 对于后两种用法,你也可以将 `routes` `params` 及 `location` `breadcrumbNameMap` 放到 context 中,组件会自动获取。 33 | -------------------------------------------------------------------------------- /src/components/PageHeader/index.test.js: -------------------------------------------------------------------------------- 1 | import { getBreadcrumb } from './index'; 2 | import { urlToList } from '../_utils/pathTools'; 3 | 4 | const routerData = { 5 | '/dashboard/analysis': { 6 | name: '分析页', 7 | }, 8 | '/userinfo': { 9 | name: '用户列表', 10 | }, 11 | '/userinfo/:id': { 12 | name: '用户信息', 13 | }, 14 | '/userinfo/:id/addr': { 15 | name: '收货订单', 16 | }, 17 | }; 18 | describe('test getBreadcrumb', () => { 19 | it('Simple url', () => { 20 | expect(getBreadcrumb(routerData, '/dashboard/analysis').name).toEqual('分析页'); 21 | }); 22 | it('Parameters url', () => { 23 | expect(getBreadcrumb(routerData, '/userinfo/2144').name).toEqual('用户信息'); 24 | }); 25 | it('The middle parameter url', () => { 26 | expect(getBreadcrumb(routerData, '/userinfo/2144/addr').name).toEqual('收货订单'); 27 | }); 28 | it('Loop through the parameters', () => { 29 | const urlNameList = urlToList('/userinfo/2144/addr').map(url => { 30 | return getBreadcrumb(routerData, url).name; 31 | }); 32 | expect(urlNameList).toEqual(['用户列表', '用户信息', '收货订单']); 33 | }); 34 | 35 | it('a path', () => { 36 | const urlNameList = urlToList('/userinfo').map(url => { 37 | return getBreadcrumb(routerData, url).name; 38 | }); 39 | expect(urlNameList).toEqual(['用户列表']); 40 | }); 41 | it('Secondary path', () => { 42 | const urlNameList = urlToList('/userinfo/2144').map(url => { 43 | return getBreadcrumb(routerData, url).name; 44 | }); 45 | expect(urlNameList).toEqual(['用户列表', '用户信息']); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/components/Result/demo/error.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | title: Failed 4 | --- 5 | 6 | 提交失败。 7 | 8 | ````jsx 9 | import Result from 'ant-design-pro/lib/Result'; 10 | import { Button, Icon } from 'antd'; 11 | 12 | const extra = ( 13 |
    14 |
    15 | 您提交的内容有如下错误: 16 |
    17 |
    18 | 您的账户已被冻结 19 | 立即解冻 20 |
    21 |
    22 | 您的账户还不具备申请资格 23 | 立即升级 24 |
    25 |
    26 | ); 27 | 28 | const actions = ; 29 | 30 | ReactDOM.render( 31 | 38 | , mountNode); 39 | ```` 40 | -------------------------------------------------------------------------------- /src/components/Result/demo/structure.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: Structure 4 | --- 5 | 6 | 结构包含 `处理结果`,`补充信息` 以及 `操作建议` 三个部分,其中 `处理结果` 由 `提示图标`,`标题` 和 `结果描述` 组成。 7 | 8 | ````jsx 9 | import Result from 'ant-design-pro/lib/Result'; 10 | 11 | ReactDOM.render( 12 | 标题
    } 15 | description={
    结果描述
    } 16 | extra="其他补充信息,自带灰底效果" 17 | actions={
    操作建议,一般放置按钮组
    } 18 | /> 19 | , mountNode); 20 | ```` 21 | -------------------------------------------------------------------------------- /src/components/Result/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IResultProps { 3 | type: 'success' | 'error'; 4 | title: React.ReactNode; 5 | description?: React.ReactNode; 6 | extra?: React.ReactNode; 7 | actions?: React.ReactNode; 8 | style?: React.CSSProperties; 9 | } 10 | 11 | export default class Result extends React.Component {} 12 | -------------------------------------------------------------------------------- /src/components/Result/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import { Icon } from 'antd'; 4 | import styles from './index.less'; 5 | 6 | export default function Result({ 7 | className, 8 | type, 9 | title, 10 | description, 11 | extra, 12 | actions, 13 | ...restProps 14 | }) { 15 | const iconMap = { 16 | error: , 17 | success: , 18 | }; 19 | const clsString = classNames(styles.result, className); 20 | return ( 21 |
    22 |
    {iconMap[type]}
    23 |
    {title}
    24 | {description &&
    {description}
    } 25 | {extra &&
    {extra}
    } 26 | {actions &&
    {actions}
    } 27 |
    28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/Result/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .result { 4 | text-align: center; 5 | width: 72%; 6 | margin: 0 auto; 7 | @media screen and (max-width: @screen-xs) { 8 | width: 100%; 9 | } 10 | 11 | .icon { 12 | font-size: 72px; 13 | line-height: 72px; 14 | margin-bottom: 24px; 15 | 16 | & > .success { 17 | color: @success-color; 18 | } 19 | 20 | & > .error { 21 | color: @error-color; 22 | } 23 | } 24 | 25 | .title { 26 | font-size: 24px; 27 | color: @heading-color; 28 | font-weight: 500; 29 | line-height: 32px; 30 | margin-bottom: 16px; 31 | } 32 | 33 | .description { 34 | font-size: 14px; 35 | line-height: 22px; 36 | color: @text-color-secondary; 37 | margin-bottom: 24px; 38 | } 39 | 40 | .extra { 41 | background: #fafafa; 42 | padding: 24px 40px; 43 | border-radius: @border-radius-sm; 44 | text-align: left; 45 | 46 | @media screen and (max-width: @screen-xs) { 47 | padding: 18px 20px; 48 | } 49 | } 50 | 51 | .actions { 52 | margin-top: 32px; 53 | 54 | button:not(:last-child) { 55 | margin-right: 8px; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/components/Result/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: Result 4 | zh-CN: Result 5 | subtitle: 处理结果 6 | cols: 1 7 | order: 12 8 | --- 9 | 10 | 结果页用于对用户进行的一系列任务处理结果进行反馈。 11 | 12 | ## API 13 | 14 | | 参数 | 说明 | 类型 | 默认值 | 15 | |----------|------------------------------------------|-------------|-------| 16 | | type | 类型,不同类型自带对应的图标 | Enum {'success', 'error'} | - | 17 | | title | 标题 | ReactNode | - | 18 | | description | 结果描述 | ReactNode | - | 19 | | extra | 补充信息,有默认的灰色背景 | ReactNode | - | 20 | | actions | 操作建议,推荐放置跳转链接,按钮组等 | ReactNode | - | 21 | -------------------------------------------------------------------------------- /src/components/SiderMenu/SilderMenu.test.js: -------------------------------------------------------------------------------- 1 | import { urlToList } from '../_utils/pathTools'; 2 | import { getFlatMenuKeys, getMenuMatchKeys } from './SiderMenu'; 3 | 4 | const menu = [ 5 | { 6 | path: '/dashboard', 7 | children: [ 8 | { 9 | path: '/dashboard/name', 10 | }, 11 | ], 12 | }, 13 | { 14 | path: '/userinfo', 15 | children: [ 16 | { 17 | path: '/userinfo/:id', 18 | children: [ 19 | { 20 | path: '/userinfo/:id/info', 21 | }, 22 | ], 23 | }, 24 | ], 25 | }, 26 | ]; 27 | 28 | const flatMenuKeys = getFlatMenuKeys(menu); 29 | 30 | describe('test convert nested menu to flat menu', () => { 31 | it('simple menu', () => { 32 | expect(flatMenuKeys).toEqual([ 33 | '/dashboard', 34 | '/dashboard/name', 35 | '/userinfo', 36 | '/userinfo/:id', 37 | '/userinfo/:id/info', 38 | ]); 39 | }); 40 | }); 41 | 42 | describe('test menu match', () => { 43 | it('simple path', () => { 44 | expect(getMenuMatchKeys(flatMenuKeys, urlToList('/dashboard'))).toEqual(['/dashboard']); 45 | }); 46 | 47 | it('error path', () => { 48 | expect(getMenuMatchKeys(flatMenuKeys, urlToList('/dashboardname'))).toEqual([]); 49 | }); 50 | 51 | it('Secondary path', () => { 52 | expect(getMenuMatchKeys(flatMenuKeys, urlToList('/dashboard/name'))).toEqual([ 53 | '/dashboard', 54 | '/dashboard/name', 55 | ]); 56 | }); 57 | 58 | it('Parameter path', () => { 59 | expect(getMenuMatchKeys(flatMenuKeys, urlToList('/userinfo/2144'))).toEqual([ 60 | '/userinfo', 61 | '/userinfo/:id', 62 | ]); 63 | }); 64 | 65 | it('three parameter path', () => { 66 | expect(getMenuMatchKeys(flatMenuKeys, urlToList('/userinfo/2144/info'))).toEqual([ 67 | '/userinfo', 68 | '/userinfo/:id', 69 | '/userinfo/:id/info', 70 | ]); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /src/components/SiderMenu/index.js: -------------------------------------------------------------------------------- 1 | import 'rc-drawer/assets/index.css'; 2 | import React from 'react'; 3 | import DrawerMenu from 'rc-drawer'; 4 | import SiderMenu from './SiderMenu'; 5 | 6 | const SiderMenuWrapper = props => { 7 | const { isMobile, collapsed } = props; 8 | return isMobile ? ( 9 | } 13 | onHandleClick={() => { 14 | props.onCollapse(!collapsed); 15 | }} 16 | open={!collapsed} 17 | onMaskClick={() => { 18 | props.onCollapse(true); 19 | }} 20 | > 21 | 22 | 23 | ) : ( 24 | 25 | ); 26 | }; 27 | 28 | export default SiderMenuWrapper; 29 | -------------------------------------------------------------------------------- /src/components/SiderMenu/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @ease-in-out-circ: cubic-bezier(0.78, 0.14, 0.15, 0.86); 3 | .logo { 4 | height: 64px; 5 | position: relative; 6 | line-height: 64px; 7 | padding-left: (@menu-collapsed-width - 32px) / 2; 8 | transition: all 0.3s; 9 | background: #002140; 10 | overflow: hidden; 11 | img { 12 | display: inline-block; 13 | vertical-align: middle; 14 | height: 32px; 15 | } 16 | h1 { 17 | color: white; 18 | display: inline-block; 19 | vertical-align: middle; 20 | font-size: 20px; 21 | margin: 0 0 0 12px; 22 | font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif; 23 | font-weight: 600; 24 | } 25 | } 26 | 27 | .sider { 28 | min-height: 100vh; 29 | box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); 30 | position: relative; 31 | z-index: 10; 32 | &.ligth { 33 | background-color: white; 34 | .logo { 35 | background: white; 36 | h1 { 37 | color: #002140; 38 | } 39 | } 40 | } 41 | } 42 | 43 | .icon { 44 | width: 14px; 45 | margin-right: 10px; 46 | } 47 | 48 | :global { 49 | .drawer .drawer-content { 50 | background: #001529; 51 | } 52 | .ant-menu-inline-collapsed { 53 | & > .ant-menu-item .sider-menu-item-img + span, 54 | & 55 | > .ant-menu-item-group 56 | > .ant-menu-item-group-list 57 | > .ant-menu-item 58 | .sider-menu-item-img 59 | + span, 60 | & > .ant-menu-submenu > .ant-menu-submenu-title .sider-menu-item-img + span { 61 | max-width: 0; 62 | display: inline-block; 63 | opacity: 0; 64 | } 65 | } 66 | .ant-menu-item .sider-menu-item-img + span, 67 | .ant-menu-submenu-title .sider-menu-item-img + span { 68 | transition: opacity 0.3s @ease-in-out, width 0.3s @ease-in-out; 69 | opacity: 1; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/components/StandardFormRow/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import styles from './index.less'; 4 | 5 | const StandardFormRow = ({ title, children, last, block, grid, ...rest }) => { 6 | const cls = classNames(styles.standardFormRow, { 7 | [styles.standardFormRowBlock]: block, 8 | [styles.standardFormRowLast]: last, 9 | [styles.standardFormRowGrid]: grid, 10 | }); 11 | 12 | return ( 13 |
    14 | {title && ( 15 |
    16 | {title} 17 |
    18 | )} 19 |
    {children}
    20 |
    21 | ); 22 | }; 23 | 24 | export default StandardFormRow; 25 | -------------------------------------------------------------------------------- /src/components/StandardFormRow/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .standardFormRow { 4 | border-bottom: 1px dashed @border-color-split; 5 | padding-bottom: 16px; 6 | margin-bottom: 16px; 7 | display: flex; 8 | :global { 9 | .ant-form-item { 10 | margin-right: 24px; 11 | } 12 | .ant-form-item-label label { 13 | color: @text-color; 14 | margin-right: 0; 15 | } 16 | .ant-form-item-label, 17 | .ant-form-item-control { 18 | padding: 0; 19 | line-height: 32px; 20 | } 21 | } 22 | .label { 23 | color: @heading-color; 24 | font-size: @font-size-base; 25 | margin-right: 24px; 26 | flex: 0 0 auto; 27 | text-align: right; 28 | & > span { 29 | display: inline-block; 30 | height: 32px; 31 | line-height: 32px; 32 | &:after { 33 | content: ':'; 34 | } 35 | } 36 | } 37 | .content { 38 | flex: 1 1 0; 39 | :global { 40 | .ant-form-item:last-child { 41 | margin-right: 0; 42 | } 43 | } 44 | } 45 | } 46 | 47 | .standardFormRowLast { 48 | border: none; 49 | padding-bottom: 0; 50 | margin-bottom: 0; 51 | } 52 | 53 | .standardFormRowBlock { 54 | :global { 55 | .ant-form-item, 56 | div.ant-form-item-control-wrapper { 57 | display: block; 58 | } 59 | } 60 | } 61 | 62 | .standardFormRowGrid { 63 | :global { 64 | .ant-form-item, 65 | div.ant-form-item-control-wrapper { 66 | display: block; 67 | } 68 | .ant-form-item-label { 69 | float: left; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/components/StandardTable/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .standardTable { 4 | :global { 5 | .ant-table-pagination { 6 | margin-top: 24px; 7 | } 8 | } 9 | 10 | .tableAlert { 11 | margin-bottom: 16px; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/TagSelect/TagSelectOption.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface ITagSelectOptionProps { 4 | value: string | number; 5 | style?: React.CSSProperties; 6 | } 7 | 8 | export default class TagSelectOption extends React.Component {} 9 | -------------------------------------------------------------------------------- /src/components/TagSelect/demo/expandable.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: 可展开和收起 4 | --- 5 | 6 | 使用 `expandable` 属性,让标签组可以收起,避免过高。 7 | 8 | ````jsx 9 | import TagSelect from 'ant-design-pro/lib/TagSelect'; 10 | 11 | function handleFormSubmit(checkedValue) { 12 | console.log(checkedValue); 13 | } 14 | 15 | ReactDOM.render( 16 | 17 | 类目一 18 | 类目二 19 | 类目三 20 | 类目四 21 | 类目五 22 | 类目六 23 | 类目七 24 | 类目八 25 | 类目九 26 | 类目十 27 | 类目十一 28 | 类目十二 29 | 30 | , mountNode); 31 | ```` 32 | -------------------------------------------------------------------------------- /src/components/TagSelect/demo/simple.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 基础样例 4 | --- 5 | 6 | 结合 `Tag` 的 `TagSelect` 组件,方便的应用于筛选类目的业务场景中。 7 | 8 | ````jsx 9 | import TagSelect from 'ant-design-pro/lib/TagSelect'; 10 | 11 | function handleFormSubmit(checkedValue) { 12 | console.log(checkedValue); 13 | } 14 | 15 | ReactDOM.render( 16 | 17 | 类目一 18 | 类目二 19 | 类目三 20 | 类目四 21 | 类目五 22 | 类目六 23 | 24 | , mountNode); 25 | ```` 26 | -------------------------------------------------------------------------------- /src/components/TagSelect/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import TagSelectOption from './TagSelectOption'; 3 | 4 | export interface ITagSelectProps { 5 | onChange?: (value: string[]) => void; 6 | expandable?: boolean; 7 | value?: string[] | number[]; 8 | style?: React.CSSProperties; 9 | } 10 | 11 | export default class TagSelect extends React.Component { 12 | public static Option: typeof TagSelectOption; 13 | private children: 14 | | React.ReactElement 15 | | Array>; 16 | } 17 | -------------------------------------------------------------------------------- /src/components/TagSelect/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .tagSelect { 4 | user-select: none; 5 | margin-left: -8px; 6 | position: relative; 7 | overflow: hidden; 8 | max-height: 32px; 9 | line-height: 32px; 10 | transition: all 0.3s; 11 | :global { 12 | .ant-tag { 13 | padding: 0 8px; 14 | margin-right: 24px; 15 | font-size: @font-size-base; 16 | } 17 | } 18 | &.expanded { 19 | transition: all 0.3s; 20 | max-height: 200px; 21 | } 22 | .trigger { 23 | position: absolute; 24 | top: 0; 25 | right: 0; 26 | i { 27 | font-size: 12px; 28 | } 29 | } 30 | &.hasExpandTag { 31 | padding-right: 50px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/TagSelect/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: TagSelect 4 | zh-CN: TagSelect 5 | subtitle: 标签选择器 6 | cols: 1 7 | order: 13 8 | --- 9 | 10 | 可进行多选,带折叠收起和展开更多功能,常用于对列表进行筛选。 11 | 12 | ## API 13 | 14 | ### TagSelect 15 | 16 | | 参数 | 说明 | 类型 | 默认值 | 17 | |----------|------------------------------------------|-------------|-------| 18 | | value |选中的项 |string[] \| number[] | | 19 | | defaultValue |默认选中的项 |string[] \| number[] | | 20 | | onChange | 标签选择的回调函数 | Function(checkedTags) | | 21 | | expandable | 是否展示 `展开/收起` 按钮 | Boolean | false | 22 | 23 | 24 | ### TagSelectOption 25 | 26 | | 参数 | 说明 | 类型 | 默认值 | 27 | |----------|------------------------------------------|-------------|-------| 28 | | value | TagSelect的值 | string\| number | - | 29 | | children | tag的内容 | string \| ReactNode | - | 30 | -------------------------------------------------------------------------------- /src/components/Trend/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 演示 4 | --- 5 | 6 | 在数值背后添加一个小图标来标识涨跌情况。 7 | 8 | ````jsx 9 | import Trend from 'ant-design-pro/lib/Trend'; 10 | 11 | ReactDOM.render( 12 |
    13 | 12% 14 | 11% 15 |
    16 | , mountNode); 17 | ```` 18 | -------------------------------------------------------------------------------- /src/components/Trend/demo/reverse.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 颜色反转 4 | --- 5 | 6 | 在数值背后添加一个小图标来标识涨跌情况。 7 | 8 | ````jsx 9 | import Trend from 'ant-design-pro/lib/Trend'; 10 | 11 | ReactDOM.render( 12 |
    13 | 12% 14 | 11% 15 |
    16 | , mountNode); 17 | ```` 18 | -------------------------------------------------------------------------------- /src/components/Trend/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface ITrendProps { 4 | colorful?: boolean; 5 | flag: 'up' | 'down'; 6 | style?: React.CSSProperties; 7 | reverseColor?: boolean; 8 | } 9 | 10 | export default class Trend extends React.Component {} 11 | -------------------------------------------------------------------------------- /src/components/Trend/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from 'antd'; 3 | import classNames from 'classnames'; 4 | import styles from './index.less'; 5 | 6 | const Trend = ({ colorful = true, reverseColor = false, flag, children, className, ...rest }) => { 7 | const classString = classNames( 8 | styles.trendItem, 9 | { 10 | [styles.trendItemGrey]: !colorful, 11 | [styles.reverseColor]: reverseColor && colorful, 12 | }, 13 | className 14 | ); 15 | return ( 16 |
    17 | {children} 18 | {flag && ( 19 | 20 | 21 | 22 | )} 23 |
    24 | ); 25 | }; 26 | 27 | export default Trend; 28 | -------------------------------------------------------------------------------- /src/components/Trend/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .trendItem { 4 | display: inline-block; 5 | font-size: @font-size-base; 6 | line-height: 22px; 7 | 8 | .up, 9 | .down { 10 | margin-left: 4px; 11 | position: relative; 12 | top: 1px; 13 | i { 14 | font-size: 12px; 15 | transform: scale(0.83); 16 | } 17 | } 18 | .up { 19 | color: @red-6; 20 | } 21 | .down { 22 | color: @green-6; 23 | top: -1px; 24 | } 25 | 26 | &.trendItemGrey .up, 27 | &.trendItemGrey .down { 28 | color: @text-color; 29 | } 30 | 31 | &.reverseColor .up { 32 | color: @green-6; 33 | } 34 | &.reverseColor .down { 35 | color: @red-6; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Trend/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: Trend 4 | zh-CN: Trend 5 | subtitle: 趋势标记 6 | cols: 1 7 | order: 14 8 | --- 9 | 10 | 趋势符号,标记上升和下降趋势。通常用绿色代表“好”,红色代表“不好”,股票涨跌场景除外。 11 | 12 | ## API 13 | 14 | ```html 15 | 50% 16 | ``` 17 | 18 | | 参数 | 说明 | 类型 | 默认值 | 19 | |----------|------------------------------------------|-------------|-------| 20 | | colorful | 是否彩色标记 | Boolean | true | 21 | | flag | 上升下降标识:`up|down` | string | - | 22 | | reverseColor | 颜色反转 | Boolean | false | 23 | -------------------------------------------------------------------------------- /src/components/_utils/pathTools.js: -------------------------------------------------------------------------------- 1 | // /userinfo/2144/id => ['/userinfo','/useinfo/2144,'/userindo/2144/id'] 2 | export function urlToList(url) { 3 | const urllist = url.split('/').filter(i => i); 4 | return urllist.map((urlItem, index) => { 5 | return `/${urllist.slice(0, index + 1).join('/')}`; 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /src/components/_utils/pathTools.test.js: -------------------------------------------------------------------------------- 1 | import { urlToList } from './pathTools'; 2 | 3 | describe('test urlToList', () => { 4 | it('A path', () => { 5 | expect(urlToList('/userinfo')).toEqual(['/userinfo']); 6 | }); 7 | it('Secondary path', () => { 8 | expect(urlToList('/userinfo/2144')).toEqual(['/userinfo', '/userinfo/2144']); 9 | }); 10 | it('Three paths', () => { 11 | expect(urlToList('/userinfo/2144/addr')).toEqual([ 12 | '/userinfo', 13 | '/userinfo/2144', 14 | '/userinfo/2144/addr', 15 | ]); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/utils/AppMenu.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'dva'; 3 | import { Spin } from 'antd'; 4 | import styles from '../../index.less'; 5 | 6 | const AppMenu = WrappedComponent => { 7 | @connect(state => ({ 8 | global: state.global, 9 | })) 10 | class AppMenuInner extends Component { 11 | componentDidMount() { 12 | const { 13 | global: { menus }, 14 | dispatch, 15 | } = this.props; 16 | if (menus.length <= 0) { 17 | dispatch({ 18 | type: 'global/fetchMenus', 19 | }); 20 | } 21 | } 22 | 23 | render() { 24 | const { 25 | global: { menus, routerData }, 26 | } = this.props; 27 | if (menus.length <= 0 || routerData.length <= 0) { 28 | return ; 29 | } else { 30 | return ; 31 | } 32 | } 33 | } 34 | 35 | return AppMenuInner; 36 | }; 37 | 38 | export default AppMenu; 39 | -------------------------------------------------------------------------------- /src/components/utils/AppNoMenu.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'dva'; 3 | import { Spin } from 'antd'; 4 | import { getRouterDataSync } from '../../services/api.js'; 5 | 6 | const AppNoMenu = WrappedComponent => { 7 | @connect(state => ({ 8 | global: state.global, 9 | })) 10 | class AppMenuInner extends Component { 11 | render() { 12 | const { 13 | global: { routerConfig }, 14 | } = this.props; 15 | const routerData = getRouterDataSync(routerConfig, []); 16 | if (!routerData || routerData.length <= 0) { 17 | return ; 18 | } else { 19 | return ; 20 | } 21 | } 22 | } 23 | 24 | return AppMenuInner; 25 | }; 26 | 27 | export default AppNoMenu; 28 | -------------------------------------------------------------------------------- /src/components/utils/pathTools.js: -------------------------------------------------------------------------------- 1 | // /userinfo/2144/id => ['/userinfo','/useinfo/2144,'/userindo/2144/id'] 2 | export function urlToList(url) { 3 | const urllist = url.split('/').filter(i => i); 4 | return urllist.map((urlItem, index) => { 5 | return `/${urllist.slice(0, index + 1).join('/')}`; 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /src/components/utils/pathTools.test.js: -------------------------------------------------------------------------------- 1 | import { urlToList } from './pathTools'; 2 | 3 | describe('test urlToList', () => { 4 | it('A path', () => { 5 | expect(urlToList('/userinfo')).toEqual(['/userinfo']); 6 | }); 7 | it('Secondary path', () => { 8 | expect(urlToList('/userinfo/2144')).toEqual(['/userinfo', '/userinfo/2144']); 9 | }); 10 | it('Three paths', () => { 11 | expect(urlToList('/userinfo/2144/addr')).toEqual([ 12 | '/userinfo', 13 | '/userinfo/2144', 14 | '/userinfo/2144/addr', 15 | ]); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/e2e/home.e2e.js: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | 3 | describe('Homepage', () => { 4 | it('it should have logo text', async () => { 5 | const browser = await puppeteer.launch({ args: ['--no-sandbox'] }); 6 | const page = await browser.newPage(); 7 | await page.goto('http://localhost:8000', { waitUntil: 'networkidle2' }); 8 | await page.waitForSelector('h1'); 9 | const text = await page.evaluate(() => document.body.innerHTML); 10 | expect(text).toContain('

    Ant Design Pro

    '); 11 | await page.close(); 12 | browser.close(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/e2e/login.e2e.js: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | 3 | describe('Login', () => { 4 | let browser; 5 | let page; 6 | 7 | beforeAll(async () => { 8 | browser = await puppeteer.launch({ args: ['--no-sandbox'] }); 9 | }); 10 | 11 | beforeEach(async () => { 12 | page = await browser.newPage(); 13 | await page.goto('http://localhost:8000/#/user/login', { waitUntil: 'networkidle2' }); 14 | await page.evaluate(() => window.localStorage.setItem('antd-pro-authority', 'guest')); 15 | }); 16 | 17 | afterEach(() => page.close()); 18 | 19 | it('should login with failure', async () => { 20 | await page.waitForSelector('#userName', { 21 | timeout: 2000, 22 | }); 23 | await page.type('#userName', 'mockuser'); 24 | await page.type('#password', 'wrong_password'); 25 | await page.click('button[type="submit"]'); 26 | await page.waitForSelector('.ant-alert-error'); // should display error 27 | }); 28 | 29 | it('should login successfully', async () => { 30 | await page.waitForSelector('#userName', { 31 | timeout: 2000, 32 | }); 33 | await page.type('#userName', 'admin'); 34 | await page.type('#password', '888888'); 35 | await page.click('button[type="submit"]'); 36 | await page.waitForSelector('.ant-layout-sider h1'); // should display error 37 | const text = await page.evaluate(() => document.body.innerHTML); 38 | expect(text).toContain('

    Ant Design Pro

    '); 39 | }); 40 | 41 | afterAll(() => browser.close()); 42 | }); 43 | -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Ant Design Pro 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 | 17 | 18 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './polyfill'; 2 | import dva from 'dva'; 3 | 4 | import createHistory from 'history/createHashHistory'; 5 | // user BrowserHistory 6 | // import createHistory from 'history/createBrowserHistory'; 7 | import createLoading from 'dva-loading'; 8 | import 'moment/locale/zh-cn'; 9 | import './rollbar'; 10 | 11 | import './index.less'; 12 | // 1. Initialize 13 | const app = dva({ 14 | history: createHistory(), 15 | }); 16 | 17 | // 2. Plugins 18 | app.use(createLoading()); 19 | 20 | // 3. Register global model 21 | app.model(require('./models/global').default); 22 | 23 | // 4. Router 24 | app.router(require('./router').default); 25 | 26 | // 5. Start 27 | app.start('#root'); 28 | 29 | export default app._store; // eslint-disable-line 30 | -------------------------------------------------------------------------------- /src/index.less: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | :global(#root) { 4 | height: 100%; 5 | } 6 | 7 | :global(.ant-layout) { 8 | min-height: 100%; 9 | } 10 | 11 | canvas { 12 | display: block; 13 | } 14 | 15 | body { 16 | text-rendering: optimizeLegibility; 17 | -webkit-font-smoothing: antialiased; 18 | -moz-osx-font-smoothing: grayscale; 19 | } 20 | 21 | // temp fix for https://github.com/ant-design/ant-design/commit/a1fafb5b727b62cb0be29ce6e9eca8f579d4f8b7 22 | :global { 23 | .ant-spin-container { 24 | overflow: visible !important; 25 | } 26 | 27 | .global-spin { 28 | width: 100%; 29 | margin: 40px 0 !important; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/layouts/BlankLayout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default props =>
    ; 4 | -------------------------------------------------------------------------------- /src/layouts/PageHeaderLayout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'dva/router'; 3 | import PageHeader from '../components/PageHeader'; 4 | import styles from './PageHeaderLayout.less'; 5 | 6 | export default ({ children, wrapperClassName, top, ...restProps }) => ( 7 |
    8 | {top} 9 | 10 | {children ?
    {children}
    : null} 11 |
    12 | ); 13 | -------------------------------------------------------------------------------- /src/layouts/PageHeaderLayout.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .content { 4 | margin: 24px 24px 0; 5 | } 6 | 7 | @media screen and (max-width: @screen-sm) { 8 | .content { 9 | margin: 24px 0 0; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/layouts/UserLayout.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .container { 4 | display: flex; 5 | flex-direction: column; 6 | height: 100vh; 7 | overflow: auto; 8 | background: #f0f2f5; 9 | } 10 | 11 | .content { 12 | padding: 32px 0; 13 | flex: 1; 14 | } 15 | 16 | @media (min-width: @screen-md-min) { 17 | .container { 18 | background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg'); 19 | background-repeat: no-repeat; 20 | background-position: center 110px; 21 | background-size: 100%; 22 | } 23 | 24 | .content { 25 | padding: 112px 0 24px 0; 26 | } 27 | } 28 | 29 | .top { 30 | text-align: center; 31 | } 32 | 33 | .header { 34 | height: 44px; 35 | line-height: 44px; 36 | a { 37 | text-decoration: none; 38 | } 39 | } 40 | 41 | .logo { 42 | height: 44px; 43 | vertical-align: top; 44 | margin-right: 16px; 45 | } 46 | 47 | .title { 48 | font-size: 33px; 49 | color: @heading-color; 50 | font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif; 51 | font-weight: 600; 52 | position: relative; 53 | top: 2px; 54 | } 55 | 56 | .desc { 57 | font-size: @font-size-base; 58 | color: @text-color-secondary; 59 | margin-top: 12px; 60 | margin-bottom: 40px; 61 | } 62 | -------------------------------------------------------------------------------- /src/models/activities.js: -------------------------------------------------------------------------------- 1 | import { queryActivities } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'activities', 5 | 6 | state: { 7 | list: [], 8 | }, 9 | 10 | effects: { 11 | *fetchList(_, { call, put }) { 12 | const response = yield call(queryActivities); 13 | yield put({ 14 | type: 'saveList', 15 | payload: Array.isArray(response) ? response : [], 16 | }); 17 | }, 18 | }, 19 | 20 | reducers: { 21 | saveList(state, action) { 22 | return { 23 | ...state, 24 | list: action.payload, 25 | }; 26 | }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/models/chart.js: -------------------------------------------------------------------------------- 1 | import { fakeChartData } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'chart', 5 | 6 | state: { 7 | visitData: [], 8 | visitData2: [], 9 | salesData: [], 10 | searchData: [], 11 | offlineData: [], 12 | offlineChartData: [], 13 | salesTypeData: [], 14 | salesTypeDataOnline: [], 15 | salesTypeDataOffline: [], 16 | radarData: [], 17 | loading: false, 18 | }, 19 | 20 | effects: { 21 | *fetch(_, { call, put }) { 22 | const response = yield call(fakeChartData); 23 | yield put({ 24 | type: 'save', 25 | payload: response, 26 | }); 27 | }, 28 | *fetchSalesData(_, { call, put }) { 29 | const response = yield call(fakeChartData); 30 | yield put({ 31 | type: 'save', 32 | payload: { 33 | salesData: response.salesData, 34 | }, 35 | }); 36 | }, 37 | }, 38 | 39 | reducers: { 40 | save(state, { payload }) { 41 | return { 42 | ...state, 43 | ...payload, 44 | }; 45 | }, 46 | clear() { 47 | return { 48 | visitData: [], 49 | visitData2: [], 50 | salesData: [], 51 | searchData: [], 52 | offlineData: [], 53 | offlineChartData: [], 54 | salesTypeData: [], 55 | salesTypeDataOnline: [], 56 | salesTypeDataOffline: [], 57 | radarData: [], 58 | }; 59 | }, 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /src/models/error.js: -------------------------------------------------------------------------------- 1 | import { routerRedux } from 'dva/router'; 2 | import { query } from '../services/error'; 3 | 4 | export default { 5 | namespace: 'error', 6 | 7 | state: { 8 | error: '', 9 | isloading: false, 10 | }, 11 | 12 | effects: { 13 | *query({ payload }, { call, put }) { 14 | yield call(query, payload.code); 15 | // redirect on client when network broken 16 | yield put(routerRedux.push(`/exception/${payload.code}`)); 17 | yield put({ 18 | type: 'trigger', 19 | payload: payload.code, 20 | }); 21 | }, 22 | }, 23 | 24 | reducers: { 25 | trigger(state, action) { 26 | return { 27 | error: action.payload, 28 | }; 29 | }, 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /src/models/form.js: -------------------------------------------------------------------------------- 1 | import { routerRedux } from 'dva/router'; 2 | import { message } from 'antd'; 3 | import { fakeSubmitForm } from '../services/api'; 4 | 5 | export default { 6 | namespace: 'form', 7 | 8 | state: { 9 | step: { 10 | payAccount: 'ant-design@alipay.com', 11 | receiverAccount: 'test@example.com', 12 | receiverName: 'Alex', 13 | amount: '500', 14 | }, 15 | }, 16 | 17 | effects: { 18 | *submitRegularForm({ payload }, { call }) { 19 | yield call(fakeSubmitForm, payload); 20 | message.success('提交成功'); 21 | }, 22 | *submitStepForm({ payload }, { call, put }) { 23 | yield call(fakeSubmitForm, payload); 24 | yield put({ 25 | type: 'saveStepFormData', 26 | payload, 27 | }); 28 | yield put(routerRedux.push('/form/step-form/result')); 29 | }, 30 | *submitAdvancedForm({ payload }, { call }) { 31 | yield call(fakeSubmitForm, payload); 32 | message.success('提交成功'); 33 | }, 34 | }, 35 | 36 | reducers: { 37 | saveStepFormData(state, { payload }) { 38 | return { 39 | ...state, 40 | step: { 41 | ...state.step, 42 | ...payload, 43 | }, 44 | }; 45 | }, 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /src/models/index.js: -------------------------------------------------------------------------------- 1 | // Use require.context to require reducers automatically 2 | // Ref: https://webpack.js.org/guides/dependency-management/#require-context 3 | const context = require.context('./', false, /\.js$/); 4 | export default context 5 | .keys() 6 | .filter(item => item !== './index.js') 7 | .map(key => context(key)); 8 | -------------------------------------------------------------------------------- /src/models/list.js: -------------------------------------------------------------------------------- 1 | import { queryFakeList } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'list', 5 | 6 | state: { 7 | list: [], 8 | }, 9 | 10 | effects: { 11 | *fetch({ payload }, { call, put }) { 12 | const response = yield call(queryFakeList, payload); 13 | yield put({ 14 | type: 'queryList', 15 | payload: Array.isArray(response) ? response : [], 16 | }); 17 | }, 18 | *appendFetch({ payload }, { call, put }) { 19 | const response = yield call(queryFakeList, payload); 20 | yield put({ 21 | type: 'appendList', 22 | payload: Array.isArray(response) ? response : [], 23 | }); 24 | }, 25 | }, 26 | 27 | reducers: { 28 | queryList(state, action) { 29 | return { 30 | ...state, 31 | list: action.payload, 32 | }; 33 | }, 34 | appendList(state, action) { 35 | return { 36 | ...state, 37 | list: state.list.concat(action.payload), 38 | }; 39 | }, 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /src/models/login.js: -------------------------------------------------------------------------------- 1 | import { routerRedux } from 'dva/router'; 2 | import { fakeAccountLogin } from '../services/api'; 3 | import { setToken, setTokenExpired } from '../utils/authority'; 4 | import { reloadAuthorized } from '../utils/Authorized'; 5 | 6 | export default { 7 | namespace: 'login', 8 | 9 | state: { 10 | status: undefined, 11 | }, 12 | 13 | effects: { 14 | *login({ payload }, { call, put }) { 15 | const response = yield call(fakeAccountLogin, payload); 16 | yield put({ 17 | type: 'changeLoginStatus', 18 | payload: response, 19 | }); 20 | // Login successfully 21 | if (response.status === 'ok') { 22 | reloadAuthorized(); 23 | yield put(routerRedux.push('/')); 24 | } 25 | }, 26 | *logout(_, { put, select }) { 27 | try { 28 | // get location pathname 29 | const urlParams = new URL(window.location.href); 30 | const pathname = yield select(state => state.routing.location.pathname); 31 | // add the parameters in the url 32 | urlParams.searchParams.set('redirect', pathname); 33 | window.history.replaceState(null, 'login', urlParams.href); 34 | } finally { 35 | yield put({ 36 | type: 'changeLoginStatus', 37 | payload: { 38 | status: false, 39 | currentAuthority: 'guest', 40 | }, 41 | }); 42 | reloadAuthorized(); 43 | yield put(routerRedux.push('/user/login')); 44 | } 45 | }, 46 | }, 47 | 48 | reducers: { 49 | changeLoginStatus(state, { payload }) { 50 | if (payload.data) { 51 | setToken(payload.data); 52 | } else { 53 | setTokenExpired(); 54 | } 55 | return { 56 | ...state, 57 | status: payload.status, 58 | type: payload.type, 59 | }; 60 | }, 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /src/models/monitor.js: -------------------------------------------------------------------------------- 1 | import { queryTags } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'monitor', 5 | 6 | state: { 7 | tags: [], 8 | }, 9 | 10 | effects: { 11 | *fetchTags(_, { call, put }) { 12 | const response = yield call(queryTags); 13 | yield put({ 14 | type: 'saveTags', 15 | payload: response.list, 16 | }); 17 | }, 18 | }, 19 | 20 | reducers: { 21 | saveTags(state, action) { 22 | return { 23 | ...state, 24 | tags: action.payload, 25 | }; 26 | }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/models/profile.js: -------------------------------------------------------------------------------- 1 | import { queryBasicProfile, queryAdvancedProfile } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'profile', 5 | 6 | state: { 7 | basicGoods: [], 8 | advancedOperation1: [], 9 | advancedOperation2: [], 10 | advancedOperation3: [], 11 | }, 12 | 13 | effects: { 14 | *fetchBasic(_, { call, put }) { 15 | const response = yield call(queryBasicProfile); 16 | yield put({ 17 | type: 'show', 18 | payload: response, 19 | }); 20 | }, 21 | *fetchAdvanced(_, { call, put }) { 22 | const response = yield call(queryAdvancedProfile); 23 | yield put({ 24 | type: 'show', 25 | payload: response, 26 | }); 27 | }, 28 | }, 29 | 30 | reducers: { 31 | show(state, { payload }) { 32 | return { 33 | ...state, 34 | ...payload, 35 | }; 36 | }, 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /src/models/project.js: -------------------------------------------------------------------------------- 1 | import { queryProjectNotice } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'project', 5 | 6 | state: { 7 | notice: [], 8 | }, 9 | 10 | effects: { 11 | *fetchNotice(_, { call, put }) { 12 | const response = yield call(queryProjectNotice); 13 | yield put({ 14 | type: 'saveNotice', 15 | payload: Array.isArray(response) ? response : [], 16 | }); 17 | }, 18 | }, 19 | 20 | reducers: { 21 | saveNotice(state, action) { 22 | return { 23 | ...state, 24 | notice: action.payload, 25 | }; 26 | }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/models/register.js: -------------------------------------------------------------------------------- 1 | import { fakeRegister } from '../services/api'; 2 | // import { setAuthority } from '../utils/authority'; 3 | import { reloadAuthorized } from '../utils/Authorized'; 4 | 5 | export default { 6 | namespace: 'register', 7 | 8 | state: { 9 | status: undefined, 10 | }, 11 | 12 | effects: { 13 | *submit(_, { call, put }) { 14 | const response = yield call(fakeRegister); 15 | yield put({ 16 | type: 'registerHandle', 17 | payload: response, 18 | }); 19 | }, 20 | }, 21 | 22 | reducers: { 23 | registerHandle(state, { payload }) { 24 | // setAuthority('user'); 25 | reloadAuthorized(); 26 | return { 27 | ...state, 28 | status: payload.status, 29 | }; 30 | }, 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /src/models/rule.js: -------------------------------------------------------------------------------- 1 | import { queryRule, removeRule, addRule } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'rule', 5 | 6 | state: { 7 | data: { 8 | list: [], 9 | pagination: {}, 10 | }, 11 | }, 12 | 13 | effects: { 14 | *fetch({ payload }, { call, put }) { 15 | const response = yield call(queryRule, payload); 16 | yield put({ 17 | type: 'save', 18 | payload: response, 19 | }); 20 | }, 21 | *add({ payload, callback }, { call, put }) { 22 | const response = yield call(addRule, payload); 23 | yield put({ 24 | type: 'save', 25 | payload: response, 26 | }); 27 | if (callback) callback(); 28 | }, 29 | *remove({ payload, callback }, { call, put }) { 30 | const response = yield call(removeRule, payload); 31 | yield put({ 32 | type: 'save', 33 | payload: response, 34 | }); 35 | if (callback) callback(); 36 | }, 37 | }, 38 | 39 | reducers: { 40 | save(state, action) { 41 | return { 42 | ...state, 43 | data: action.payload, 44 | }; 45 | }, 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /src/models/user.js: -------------------------------------------------------------------------------- 1 | import { query as queryUsers, queryCurrent } from '../services/user'; 2 | 3 | export default { 4 | namespace: 'user', 5 | 6 | state: { 7 | list: [], 8 | currentUser: {}, 9 | }, 10 | 11 | effects: { 12 | *fetch(_, { call, put }) { 13 | const response = yield call(queryUsers); 14 | yield put({ 15 | type: 'save', 16 | payload: response, 17 | }); 18 | }, 19 | *fetchCurrent(_, { call, put }) { 20 | const response = yield call(queryCurrent); 21 | yield put({ 22 | type: 'saveCurrentUser', 23 | payload: response, 24 | }); 25 | }, 26 | }, 27 | 28 | reducers: { 29 | save(state, action) { 30 | return { 31 | ...state, 32 | list: action.payload, 33 | }; 34 | }, 35 | saveCurrentUser(state, action) { 36 | return { 37 | ...state, 38 | currentUser: action.payload || {}, 39 | }; 40 | }, 41 | changeNotifyCount(state, action) { 42 | return { 43 | ...state, 44 | currentUser: { 45 | ...state.currentUser, 46 | notifyCount: action.payload, 47 | }, 48 | }; 49 | }, 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /src/polyfill.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill'; 2 | import 'url-polyfill'; 3 | import setprototypeof from 'setprototypeof'; 4 | 5 | // React depends on set/map/requestAnimationFrame 6 | // https://reactjs.org/docs/javascript-environment-requirements.html 7 | // import 'core-js/es6/set'; 8 | // import 'core-js/es6/map'; 9 | // import 'raf/polyfill'; 只兼容到IE10不需要,况且fetch的polyfill whatwg-fetch也只兼容到IE10 10 | 11 | // https://github.com/umijs/umi/issues/413 12 | Object.setPrototypeOf = setprototypeof; 13 | -------------------------------------------------------------------------------- /src/rollbar.js: -------------------------------------------------------------------------------- 1 | import Rollbar from 'rollbar'; 2 | 3 | // Track error by rollbar.com 4 | if (location.host === 'preview.pro.ant.design') { 5 | Rollbar.init({ 6 | accessToken: '033ca6d7c0eb4cc1831cf470c2649971', 7 | captureUncaught: true, 8 | captureUnhandledRejections: true, 9 | hostWhiteList: ['ant.design'], 10 | payload: { 11 | environment: 'production', 12 | }, 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { routerRedux, Route, Switch } from 'dva/router'; 3 | import { LocaleProvider } from 'antd'; 4 | import zhCN from 'antd/lib/locale-provider/zh_CN'; 5 | import { getRouterConfig } from './common/router'; 6 | import Authorized from './utils/Authorized'; 7 | import { getQueryPath } from './utils/utils'; 8 | import { hasPermission } from './utils/authority'; 9 | 10 | const { ConnectedRouter } = routerRedux; 11 | const { AuthorizedRoute } = Authorized; 12 | 13 | function RouterConfig({ history, app }) { 14 | const routerConfig = getRouterConfig(app); 15 | const UserLayout = routerConfig['/user'].component; 16 | const BasicLayout = routerConfig['/'].component; 17 | return ( 18 | 19 | 20 | 21 | 22 | } 25 | authority={() => hasPermission('root')} 26 | redirectPath={getQueryPath('/user/login', { 27 | redirect: window.location.href, 28 | })} 29 | /> 30 | 31 | 32 | 33 | ); 34 | } 35 | 36 | export default RouterConfig; 37 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Monitor.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @import '../../utils/utils.less'; 3 | 4 | .mapChart { 5 | padding-top: 24px; 6 | height: 457px; 7 | text-align: center; 8 | img { 9 | display: inline-block; 10 | max-width: 100%; 11 | max-height: 437px; 12 | } 13 | } 14 | 15 | .pieCard :global(.pie-stat) { 16 | font-size: 24px !important; 17 | } 18 | 19 | @media screen and (max-width: @screen-lg) { 20 | .mapChart { 21 | height: auto; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/routes/Demo/DemoOne.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Button, Alert } from 'antd'; 3 | 4 | export default class DemoOne extends Component { 5 | /** 6 | * @description 验证是否有操作按钮的权限 7 | * @param {String} item 按钮 8 | */ 9 | verifyAuthority = item => { 10 | const { actions } = this.props; 11 | return actions.indexOf(item) > -1; 12 | }; 13 | 14 | render() { 15 | return ( 16 |
    17 |

    demo-one

    18 | 19 | {this.verifyAuthority('add') && } 20 | {this.verifyAuthority('edit') && } 21 | {this.verifyAuthority('save') && } 22 |
    23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/routes/Demo/DemoThree.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class DemoOne extends Component { 4 | render() { 5 | return ( 6 |
    7 |

    demo-three

    8 |
    9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/routes/Demo/DemoTwo.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class DemoOne extends Component { 4 | render() { 5 | return ( 6 |
    7 |

    demo-two

    8 |
    9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/routes/Exception/403.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'dva/router'; 3 | import Exception from 'components/Exception'; 4 | 5 | export default () => ( 6 | 7 | ); 8 | -------------------------------------------------------------------------------- /src/routes/Exception/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'dva/router'; 3 | import Exception from 'components/Exception'; 4 | 5 | export default () => ( 6 | 7 | ); 8 | -------------------------------------------------------------------------------- /src/routes/Exception/500.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'dva/router'; 3 | import Exception from 'components/Exception'; 4 | 5 | export default () => ( 6 | 7 | ); 8 | -------------------------------------------------------------------------------- /src/routes/Exception/style.less: -------------------------------------------------------------------------------- 1 | .trigger { 2 | background: 'red'; 3 | :global(.ant-btn) { 4 | margin-right: 8px; 5 | margin-bottom: 12px; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/routes/Exception/triggerException.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Button, Spin, Card } from 'antd'; 3 | import { connect } from 'dva'; 4 | import styles from './style.less'; 5 | 6 | @connect(state => ({ 7 | isloading: state.error.isloading, 8 | })) 9 | export default class TriggerException extends PureComponent { 10 | state = { 11 | isloading: false, 12 | }; 13 | 14 | triggerError = code => { 15 | this.setState({ 16 | isloading: true, 17 | }); 18 | const { dispatch } = this.props; 19 | dispatch({ 20 | type: 'error/query', 21 | payload: { 22 | code, 23 | }, 24 | }); 25 | }; 26 | 27 | render() { 28 | const { isloading } = this.state; 29 | return ( 30 | 31 | 32 | 35 | 38 | 41 | 44 | 45 | 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/routes/Forms/StepForm/Step3.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import { connect } from 'dva'; 3 | import { Button, Row, Col } from 'antd'; 4 | import { routerRedux } from 'dva/router'; 5 | import Result from 'components/Result'; 6 | import styles from './style.less'; 7 | 8 | @connect(({ form }) => ({ 9 | data: form.step, 10 | })) 11 | export default class Step3 extends React.PureComponent { 12 | render() { 13 | const { dispatch, data } = this.props; 14 | const onFinish = () => { 15 | dispatch(routerRedux.push('/form/step-form')); 16 | }; 17 | const information = ( 18 |
    19 | 20 | 21 | 付款账户: 22 | 23 | 24 | {data.payAccount} 25 | 26 | 27 | 28 | 29 | 收款账户: 30 | 31 | 32 | {data.receiverAccount} 33 | 34 | 35 | 36 | 37 | 收款人姓名: 38 | 39 | 40 | {data.receiverName} 41 | 42 | 43 | 44 | 45 | 转账金额: 46 | 47 | 48 | {data.amount} 元 49 | 50 | 51 |
    52 | ); 53 | const actions = ( 54 | 55 | 58 | 59 | 60 | ); 61 | return ( 62 | 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/routes/Forms/StepForm/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent, Fragment } from 'react'; 2 | import { Route, Redirect, Switch } from 'dva/router'; 3 | import { Card, Steps } from 'antd'; 4 | import PageHeaderLayout from '../../../layouts/PageHeaderLayout'; 5 | import NotFound from '../../Exception/404'; 6 | import { getRoutes } from '../../../utils/utils'; 7 | import styles from '../style.less'; 8 | 9 | import AppMenu from '../../../components/utils/AppMenu'; 10 | 11 | const { Step } = Steps; 12 | 13 | class StepForm extends PureComponent { 14 | getCurrentStep() { 15 | const { location } = this.props; 16 | const { pathname } = location; 17 | const pathList = pathname.split('/'); 18 | switch (pathList[pathList.length - 1]) { 19 | case 'info': 20 | return 0; 21 | case 'confirm': 22 | return 1; 23 | case 'result': 24 | return 2; 25 | default: 26 | return 0; 27 | } 28 | } 29 | 30 | render() { 31 | const { match, routerData, location } = this.props; 32 | return ( 33 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {getRoutes(match.path, routerData).map(item => ( 47 | 53 | ))} 54 | 55 | 56 | 57 | 58 | 59 | 60 | ); 61 | } 62 | } 63 | 64 | export default AppMenu(StepForm); 65 | -------------------------------------------------------------------------------- /src/routes/Forms/StepForm/style.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .stepForm { 4 | margin: 40px auto 0; 5 | max-width: 500px; 6 | } 7 | 8 | .stepFormText { 9 | margin-bottom: 24px; 10 | :global { 11 | .ant-form-item-label, 12 | .ant-form-item-control { 13 | line-height: 22px; 14 | } 15 | } 16 | } 17 | 18 | .result { 19 | margin: 0 auto; 20 | max-width: 560px; 21 | padding: 24px 0 8px; 22 | } 23 | 24 | .desc { 25 | padding: 0 56px; 26 | color: @text-color-secondary; 27 | h3 { 28 | font-size: 16px; 29 | margin: 0 0 12px 0; 30 | color: @text-color-secondary; 31 | line-height: 32px; 32 | } 33 | h4 { 34 | margin: 0 0 4px 0; 35 | color: @text-color-secondary; 36 | font-size: 14px; 37 | line-height: 22px; 38 | } 39 | p { 40 | margin-top: 0; 41 | margin-bottom: 12px; 42 | line-height: 22px; 43 | } 44 | } 45 | 46 | @media screen and (max-width: @screen-md) { 47 | .desc { 48 | padding: 0; 49 | } 50 | } 51 | 52 | .information { 53 | line-height: 22px; 54 | :global { 55 | .ant-row:not(:last-child) { 56 | margin-bottom: 24px; 57 | } 58 | } 59 | .label { 60 | color: @heading-color; 61 | text-align: right; 62 | padding-right: 8px; 63 | @media screen and (max-width: @screen-sm) { 64 | text-align: left; 65 | } 66 | } 67 | } 68 | 69 | .money { 70 | font-family: 'Helvetica Neue', sans-serif; 71 | font-weight: 500; 72 | font-size: 20px; 73 | line-height: 14px; 74 | } 75 | 76 | .uppercase { 77 | font-size: 12px; 78 | } 79 | -------------------------------------------------------------------------------- /src/routes/Forms/style.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .card { 4 | margin-bottom: 24px; 5 | } 6 | 7 | .heading { 8 | font-size: 14px; 9 | line-height: 22px; 10 | margin: 0 0 16px 0; 11 | } 12 | 13 | .steps:global(.ant-steps) { 14 | max-width: 750px; 15 | margin: 16px auto; 16 | } 17 | 18 | .errorIcon { 19 | cursor: pointer; 20 | color: @error-color; 21 | margin-right: 24px; 22 | i { 23 | margin-right: 4px; 24 | } 25 | } 26 | 27 | .errorPopover { 28 | :global { 29 | .ant-popover-inner-content { 30 | padding: 0; 31 | max-height: 290px; 32 | overflow: auto; 33 | min-width: 256px; 34 | } 35 | } 36 | } 37 | 38 | .errorListItem { 39 | list-style: none; 40 | border-bottom: 1px solid @border-color-split; 41 | padding: 8px 16px; 42 | cursor: pointer; 43 | transition: all 0.3s; 44 | &:hover { 45 | background: @primary-1; 46 | } 47 | &:last-child { 48 | border: 0; 49 | } 50 | .errorIcon { 51 | color: @error-color; 52 | float: left; 53 | margin-top: 4px; 54 | margin-right: 12px; 55 | padding-bottom: 22px; 56 | } 57 | .errorField { 58 | font-size: 12px; 59 | color: @text-color-secondary; 60 | margin-top: 2px; 61 | } 62 | } 63 | 64 | .editable { 65 | td { 66 | padding-top: 13px !important; 67 | padding-bottom: 12.5px !important; 68 | } 69 | } 70 | 71 | // custom footer for fixed footer toolbar 72 | .advancedForm + div { 73 | padding-bottom: 64px; 74 | } 75 | 76 | .advancedForm { 77 | :global { 78 | .ant-form .ant-row:last-child .ant-form-item { 79 | margin-bottom: 24px; 80 | } 81 | .ant-table td { 82 | transition: none !important; 83 | } 84 | } 85 | } 86 | 87 | .optional { 88 | color: @text-color-secondary; 89 | font-style: normal; 90 | } 91 | -------------------------------------------------------------------------------- /src/routes/List/Applications.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @import '../../utils/utils.less'; 3 | 4 | .filterCardList { 5 | margin-bottom: -24px; 6 | :global { 7 | .ant-card-meta-content { 8 | margin-top: 0; 9 | } 10 | // disabled white space 11 | .ant-card-meta-avatar { 12 | font-size: 0; 13 | } 14 | .ant-card-actions { 15 | background: #f7f9fa; 16 | } 17 | .ant-list .ant-list-item-content-single { 18 | max-width: 100%; 19 | } 20 | } 21 | .cardInfo { 22 | .clearfix(); 23 | margin-top: 16px; 24 | margin-left: 40px; 25 | & > div { 26 | position: relative; 27 | text-align: left; 28 | float: left; 29 | width: 50%; 30 | p { 31 | line-height: 32px; 32 | font-size: 24px; 33 | margin: 0; 34 | } 35 | p:first-child { 36 | color: @text-color-secondary; 37 | font-size: 12px; 38 | line-height: 20px; 39 | margin-bottom: 4px; 40 | } 41 | } 42 | } 43 | } 44 | 45 | .wan { 46 | position: relative; 47 | top: -2px; 48 | font-size: @font-size-base; 49 | font-style: normal; 50 | line-height: 20px; 51 | margin-left: 2px; 52 | } 53 | -------------------------------------------------------------------------------- /src/routes/List/Articles.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @import '../../utils/utils.less'; 3 | 4 | .listContent { 5 | .description { 6 | line-height: 22px; 7 | max-width: 720px; 8 | } 9 | .extra { 10 | color: @text-color-secondary; 11 | margin-top: 16px; 12 | line-height: 22px; 13 | & > :global(.ant-avatar) { 14 | vertical-align: top; 15 | margin-right: 8px; 16 | width: 20px; 17 | height: 20px; 18 | position: relative; 19 | top: 1px; 20 | } 21 | & > em { 22 | color: @disabled-color; 23 | font-style: normal; 24 | margin-left: 16px; 25 | } 26 | } 27 | } 28 | a.listItemMetaTitle { 29 | color: @heading-color; 30 | } 31 | .listItemExtra { 32 | width: 272px; 33 | height: 1px; 34 | } 35 | .selfTrigger { 36 | margin-left: 12px; 37 | } 38 | 39 | @media screen and (max-width: @screen-xs) { 40 | .selfTrigger { 41 | display: block; 42 | margin-left: 0; 43 | } 44 | .listContent { 45 | .extra { 46 | & > em { 47 | display: block; 48 | margin-left: 0; 49 | margin-top: 8px; 50 | } 51 | } 52 | } 53 | } 54 | @media screen and (max-width: @screen-md) { 55 | .selfTrigger { 56 | display: block; 57 | margin-left: 0; 58 | } 59 | } 60 | @media screen and (max-width: @screen-lg) { 61 | .listItemExtra { 62 | width: 0; 63 | height: 1px; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/routes/List/List.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { routerRedux, Route, Switch } from 'dva/router'; 3 | import { connect } from 'dva'; 4 | import { Input } from 'antd'; 5 | import PageHeaderLayout from '../../layouts/PageHeaderLayout'; 6 | import { getRoutes } from '../../utils/utils'; 7 | 8 | import AppMenu from '../../components/utils/AppMenu'; 9 | 10 | class SearchList extends Component { 11 | handleTabChange = key => { 12 | const { dispatch, match } = this.props; 13 | switch (key) { 14 | case 'articles': 15 | dispatch(routerRedux.push(`${match.url}/articles`)); 16 | break; 17 | case 'applications': 18 | dispatch(routerRedux.push(`${match.url}/applications`)); 19 | break; 20 | case 'projects': 21 | dispatch(routerRedux.push(`${match.url}/projects`)); 22 | break; 23 | default: 24 | break; 25 | } 26 | }; 27 | 28 | render() { 29 | const tabList = [ 30 | { 31 | key: 'articles', 32 | tab: '文章', 33 | }, 34 | { 35 | key: 'applications', 36 | tab: '应用', 37 | }, 38 | { 39 | key: 'projects', 40 | tab: '项目', 41 | }, 42 | ]; 43 | 44 | const mainSearch = ( 45 |
    46 | 53 |
    54 | ); 55 | 56 | const { match, routerData, location } = this.props; 57 | const routes = getRoutes(match.path, routerData); 58 | 59 | return ( 60 | 67 | 68 | {routes.map(item => ( 69 | 70 | ))} 71 | 72 | 73 | ); 74 | } 75 | } 76 | 77 | export default connect()(AppMenu(SearchList)); 78 | -------------------------------------------------------------------------------- /src/routes/List/Projects.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @import '../../utils/utils.less'; 3 | 4 | .coverCardList { 5 | margin-bottom: -24px; 6 | 7 | .card { 8 | :global { 9 | .ant-card-meta-title { 10 | margin-bottom: 4px; 11 | & > a { 12 | color: @heading-color; 13 | display: inline-block; 14 | max-width: 100%; 15 | } 16 | } 17 | .ant-card-meta-description { 18 | height: 44px; 19 | line-height: 22px; 20 | overflow: hidden; 21 | } 22 | } 23 | 24 | &:hover { 25 | :global { 26 | .ant-card-meta-title > a { 27 | color: @primary-color; 28 | } 29 | } 30 | } 31 | } 32 | 33 | .cardItemContent { 34 | display: flex; 35 | margin-top: 16px; 36 | margin-bottom: -4px; 37 | line-height: 20px; 38 | height: 20px; 39 | & > span { 40 | color: @text-color-secondary; 41 | flex: 1; 42 | font-size: 12px; 43 | } 44 | .avatarList { 45 | flex: 0 1 auto; 46 | } 47 | } 48 | .cardList { 49 | margin-top: 24px; 50 | } 51 | 52 | :global { 53 | .ant-list .ant-list-item-content-single { 54 | max-width: 100%; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/routes/List/TableList.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @import '../../utils/utils.less'; 3 | 4 | .tableList { 5 | .tableListOperator { 6 | margin-bottom: 16px; 7 | button { 8 | margin-right: 8px; 9 | } 10 | } 11 | } 12 | 13 | .tableListForm { 14 | :global { 15 | .ant-form-item { 16 | margin-bottom: 24px; 17 | margin-right: 0; 18 | display: flex; 19 | > .ant-form-item-label { 20 | width: auto; 21 | line-height: 32px; 22 | padding-right: 8px; 23 | } 24 | .ant-form-item-control { 25 | line-height: 32px; 26 | } 27 | } 28 | .ant-form-item-control-wrapper { 29 | flex: 1; 30 | } 31 | } 32 | .submitButtons { 33 | white-space: nowrap; 34 | margin-bottom: 24px; 35 | } 36 | } 37 | 38 | @media screen and (max-width: @screen-lg) { 39 | .tableListForm :global(.ant-form-item) { 40 | margin-right: 24px; 41 | } 42 | } 43 | 44 | @media screen and (max-width: @screen-md) { 45 | .tableListForm :global(.ant-form-item) { 46 | margin-right: 8px; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/routes/Profile/AdvancedProfile.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .headerList { 4 | margin-bottom: 4px; 5 | } 6 | 7 | .tabsCard { 8 | :global { 9 | .ant-card-head { 10 | padding: 0 16px; 11 | } 12 | } 13 | } 14 | 15 | .noData { 16 | color: @disabled-color; 17 | text-align: center; 18 | line-height: 64px; 19 | font-size: 16px; 20 | i { 21 | font-size: 24px; 22 | margin-right: 16px; 23 | position: relative; 24 | top: 3px; 25 | } 26 | } 27 | 28 | .heading { 29 | color: @heading-color; 30 | font-size: 20px; 31 | } 32 | 33 | .stepDescription { 34 | font-size: 14px; 35 | position: relative; 36 | left: 38px; 37 | & > div { 38 | margin-top: 8px; 39 | margin-bottom: 4px; 40 | } 41 | } 42 | 43 | .textSecondary { 44 | color: @text-color-secondary; 45 | } 46 | 47 | @media screen and (max-width: @screen-sm) { 48 | .stepDescription { 49 | left: 8px; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/routes/Profile/BasicProfile.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .title { 4 | color: @heading-color; 5 | font-size: 16px; 6 | font-weight: 500; 7 | margin-bottom: 16px; 8 | } 9 | -------------------------------------------------------------------------------- /src/routes/Result/Error.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import { Button, Icon, Card } from 'antd'; 3 | import Result from 'components/Result'; 4 | import PageHeaderLayout from '../../layouts/PageHeaderLayout'; 5 | 6 | const extra = ( 7 | 8 |
    16 | 您提交的内容有如下错误: 17 |
    18 |
    19 | 20 | 您的账户已被冻结 21 | 22 | 立即解冻 23 | 24 |
    25 |
    26 | 27 | 您的账户还不具备申请资格 28 | 29 | 立即升级 30 | 31 |
    32 |
    33 | ); 34 | 35 | const actions = ; 36 | 37 | export default () => ( 38 | 39 | 40 | 48 | 49 | 50 | ); 51 | -------------------------------------------------------------------------------- /src/routes/Result/Success.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import Success from './Success'; 4 | 5 | it('renders with Result', () => { 6 | const wrapper = shallow(); 7 | expect(wrapper.find('Result').length).toBe(1); 8 | expect(wrapper.find('Result').prop('type')).toBe('success'); 9 | }); 10 | -------------------------------------------------------------------------------- /src/routes/User/Login.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .main { 4 | width: 368px; 5 | margin: 0 auto; 6 | @media screen and (max-width: @screen-sm) { 7 | width: 95%; 8 | } 9 | 10 | .icon { 11 | font-size: 24px; 12 | color: rgba(0, 0, 0, 0.2); 13 | margin-left: 16px; 14 | vertical-align: middle; 15 | cursor: pointer; 16 | transition: color 0.3s; 17 | 18 | &:hover { 19 | color: @primary-color; 20 | } 21 | } 22 | 23 | .other { 24 | text-align: left; 25 | margin-top: 24px; 26 | line-height: 22px; 27 | 28 | .register { 29 | float: right; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/routes/User/Register.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .main { 4 | width: 368px; 5 | margin: 0 auto; 6 | 7 | :global { 8 | .ant-form-item { 9 | margin-bottom: 24px; 10 | } 11 | } 12 | 13 | h3 { 14 | font-size: 16px; 15 | margin-bottom: 20px; 16 | } 17 | 18 | .getCaptcha { 19 | display: block; 20 | width: 100%; 21 | } 22 | 23 | .submit { 24 | width: 50%; 25 | } 26 | 27 | .login { 28 | float: right; 29 | line-height: @btn-height-lg; 30 | } 31 | } 32 | 33 | .success, 34 | .warning, 35 | .error { 36 | transition: color 0.3s; 37 | } 38 | 39 | .success { 40 | color: @success-color; 41 | } 42 | 43 | .warning { 44 | color: @warning-color; 45 | } 46 | 47 | .error { 48 | color: @error-color; 49 | } 50 | 51 | .progress-pass > .progress { 52 | :global { 53 | .ant-progress-bg { 54 | background-color: @warning-color; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/routes/User/RegisterResult.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'antd'; 3 | import { Link } from 'dva/router'; 4 | import Result from 'components/Result'; 5 | import styles from './RegisterResult.less'; 6 | 7 | const actions = ( 8 |
    9 | 10 | 13 | 14 | 15 | 16 | 17 |
    18 | ); 19 | 20 | export default ({ location }) => ( 21 | 26 | 你的账户: 27 | {location.state ? location.state.account : 'AntDesign@example.com'} 注册成功 28 |
    29 | } 30 | description="激活邮件已发送到你的邮箱中,邮件有效期为24小时。请及时登录邮箱,点击邮件中的链接激活帐户。" 31 | actions={actions} 32 | style={{ marginTop: 56 }} 33 | /> 34 | ); 35 | -------------------------------------------------------------------------------- /src/routes/User/RegisterResult.less: -------------------------------------------------------------------------------- 1 | .registerResult { 2 | :global { 3 | .anticon { 4 | font-size: 64px; 5 | } 6 | } 7 | .title { 8 | margin-top: 32px; 9 | font-size: 20px; 10 | line-height: 28px; 11 | } 12 | .actions { 13 | margin-top: 40px; 14 | a + a { 15 | margin-left: 8px; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/services/error.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | 3 | export async function query(code) { 4 | return request(`/api/${code}`); 5 | } 6 | -------------------------------------------------------------------------------- /src/services/user.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | 3 | export async function query() { 4 | return request('/api/users'); 5 | } 6 | 7 | export async function queryCurrent() { 8 | return request('/api/currentUser'); 9 | } 10 | -------------------------------------------------------------------------------- /src/theme.js: -------------------------------------------------------------------------------- 1 | // https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less 2 | module.exports = { 3 | // 'primary-color': '#10e99b', 4 | 'card-actions-background': '#f5f8fa', 5 | }; 6 | -------------------------------------------------------------------------------- /src/utils/Authorized.js: -------------------------------------------------------------------------------- 1 | import RenderAuthorized from '../components/Authorized'; 2 | import { getAuthority } from './authority'; 3 | 4 | let Authorized = RenderAuthorized(getAuthority()); // eslint-disable-line 5 | 6 | // Reload the rights component 7 | const reloadAuthorized = () => { 8 | Authorized = RenderAuthorized(getAuthority()); 9 | }; 10 | 11 | export { reloadAuthorized }; 12 | export default Authorized; 13 | -------------------------------------------------------------------------------- /src/utils/authority.js: -------------------------------------------------------------------------------- 1 | // use localStorage to store the authority info, which might be sent from server in actual project. 2 | import store from '../index'; 3 | 4 | /** 5 | * 获取token 6 | */ 7 | export function getAuthority() { 8 | const token = getToken(); 9 | // 判断token是否过期 10 | if (!token) { 11 | return null; 12 | } 13 | const nowTime = new Date() - 0; 14 | if (token.expireIn > nowTime) { 15 | return token.token; 16 | } 17 | return null; 18 | } 19 | 20 | export function setToken(token) { 21 | return localStorage.setItem('security.token', JSON.stringify(token)); 22 | } 23 | 24 | export function getToken() { 25 | const tokenStr = localStorage.getItem('security.token'); 26 | if (!tokenStr) { 27 | return null; 28 | } 29 | 30 | const token = JSON.parse(tokenStr); 31 | return token; 32 | } 33 | 34 | export function setTokenExpired() { 35 | localStorage.removeItem('security.token'); 36 | } 37 | 38 | /** 39 | * 判断是否有权限 40 | * @param {string} [authorityIdentity] 41 | */ 42 | export function hasPermission(authorityIdentity) { 43 | // 通用页面直接允许通过 44 | if (authorityIdentity === 'common') { 45 | return true; 46 | } 47 | 48 | const token = getAuthority(); 49 | if (!authorityIdentity || !token) { 50 | return false; 51 | } 52 | 53 | const rootIdentity = 'root'; 54 | // 已登录且是首页 55 | if (authorityIdentity === rootIdentity && token) { 56 | return true; 57 | } 58 | 59 | if (!store) { 60 | return false; 61 | } 62 | 63 | // 从redux中获取菜单 64 | const { 65 | global: { menus }, 66 | } = store.getState(); 67 | if (!menus || menus.length <= 0) { 68 | return false; 69 | } 70 | return checkAuth(authorityIdentity, menus); 71 | } 72 | 73 | function checkAuth(identity, menus) { 74 | if (!menus || menus.length <= 0) { 75 | return false; 76 | } 77 | let result = false; 78 | for (let i = 0; i < menus.length; i += 1) { 79 | const menu = menus[i]; 80 | if (identity === menu.identity) { 81 | return true; 82 | } 83 | if (menu.children && menu.children.length) { 84 | result = checkAuth(identity, menu.children); 85 | if (result) { 86 | return true; 87 | } 88 | } 89 | } 90 | 91 | return false; 92 | } 93 | -------------------------------------------------------------------------------- /src/utils/utils.less: -------------------------------------------------------------------------------- 1 | .textOverflow() { 2 | overflow: hidden; 3 | text-overflow: ellipsis; 4 | word-break: break-all; 5 | white-space: nowrap; 6 | } 7 | 8 | .textOverflowMulti(@line: 3, @bg: #fff) { 9 | overflow: hidden; 10 | position: relative; 11 | line-height: 1.5em; 12 | max-height: @line * 1.5em; 13 | text-align: justify; 14 | margin-right: -1em; 15 | padding-right: 1em; 16 | &:before { 17 | background: @bg; 18 | content: '...'; 19 | padding: 0 1px; 20 | position: absolute; 21 | right: 14px; 22 | bottom: 0; 23 | } 24 | &:after { 25 | background: white; 26 | content: ''; 27 | margin-top: 0.2em; 28 | position: absolute; 29 | right: 14px; 30 | width: 1em; 31 | height: 1em; 32 | } 33 | } 34 | 35 | // mixins for clearfix 36 | // ------------------------ 37 | .clearfix() { 38 | zoom: 1; 39 | &:before, 40 | &:after { 41 | content: ' '; 42 | display: table; 43 | } 44 | &:after { 45 | clear: both; 46 | visibility: hidden; 47 | font-size: 0; 48 | height: 0; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/fix_puppeteer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo apt-get update 4 | sudo apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \ 5 | libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \ 6 | libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \ 7 | libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \ 8 | ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget 9 | -------------------------------------------------------------------------------- /tests/run-tests.js: -------------------------------------------------------------------------------- 1 | const { spawn } = require('child_process'); 2 | const { kill } = require('cross-port-killer'); 3 | 4 | const env = Object.create(process.env); 5 | env.BROWSER = 'none'; 6 | const startServer = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['start'], { 7 | env, 8 | }); 9 | 10 | startServer.stderr.on('data', data => { 11 | // eslint-disable-next-line 12 | console.log(data); 13 | }); 14 | 15 | startServer.on('exit', () => { 16 | kill(process.env.PORT || 8000); 17 | }); 18 | 19 | // eslint-disable-next-line 20 | console.log('Starting development server for e2e tests...'); 21 | startServer.stdout.on('data', data => { 22 | // eslint-disable-next-line 23 | console.log(data.toString()); 24 | if ( 25 | data.toString().indexOf('Compiled successfully') >= 0 || 26 | data.toString().indexOf('Compiled with warnings') >= 0 27 | ) { 28 | // eslint-disable-next-line 29 | console.log('Development server is started, ready to run tests.'); 30 | const testCmd = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['test'], { 31 | stdio: 'inherit', 32 | }); 33 | testCmd.on('exit', code => { 34 | startServer.kill(); 35 | process.exit(code); 36 | }); 37 | } 38 | }); 39 | --------------------------------------------------------------------------------