├── .babelrc.js ├── .eslintrc ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .roadhogrc.mock.js ├── .stylelintrc ├── .travis.yml ├── .webpackrc.js ├── LICENSE ├── README.md ├── _config.yml ├── appveyor.yml ├── mock ├── .gitkeep ├── api.js ├── chart.js ├── notices.js ├── profile.js └── rule.js ├── package.json ├── public ├── favicon.png └── image │ └── ant.jpeg ├── 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 │ ├── 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 │ │ ├── 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.js │ │ ├── index.less │ │ ├── index.md │ │ └── responsive.js │ ├── EditableItem │ │ ├── index.js │ │ └── index.less │ ├── EditableLinkGroup │ │ ├── index.js │ │ └── index.less │ ├── Ellipsis │ │ ├── demo │ │ │ ├── line.md │ │ │ └── number.md │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── Exception │ │ ├── demo │ │ │ ├── 403.md │ │ │ ├── 404.md │ │ │ └── 500.md │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.less │ │ ├── index.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 │ ├── ImageWrapper │ │ ├── index.js │ │ └── index.less │ ├── 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.js │ │ ├── index.less │ │ └── index.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 │ ├── TreeCheck │ │ └── index.js │ ├── Trend │ │ ├── demo │ │ │ └── basic.md │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ └── _utils │ │ ├── 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 ├── rollbar.js ├── router.js ├── routes │ ├── Component │ │ ├── Hooks.js │ │ ├── SelectTree.js │ │ ├── Table.js │ │ ├── Test.js │ │ └── Test.less │ ├── Dashboard │ │ ├── Analysis.js │ │ ├── Analysis.less │ │ ├── Monitor.js │ │ ├── Monitor.less │ │ ├── Workplace.js │ │ └── Workplace.less │ ├── 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 │ ├── Home │ │ └── Home.js │ ├── Libraries │ │ ├── BraftEditor.js │ │ └── Drag │ │ │ ├── Basic.js │ │ │ ├── Collections.js │ │ │ ├── DragHandle.js │ │ │ └── index.js │ ├── 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 └── run-tests.js /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | [ 4 | 'babel-plugin-module-resolver', 5 | { 6 | alias: { 7 | components: './src/components', 8 | }, 9 | }, 10 | ], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": ["airbnb", "prettier"], 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "es6": true, 8 | "mocha": true, 9 | "jest": true, 10 | "jasmine": true 11 | }, 12 | "rules": { 13 | "generator-star-spacing": [0], 14 | "consistent-return": [0], 15 | "react/forbid-prop-types": [0], 16 | "react/jsx-filename-extension": [1, { "extensions": [".js"] }], 17 | "global-require": [1], 18 | "import/prefer-default-export": [0], 19 | "react/jsx-no-bind": [0], 20 | "react/prop-types": [0], 21 | "react/prefer-stateless-function": [0], 22 | "react/jsx-wrap-multilines": [ 23 | "error", 24 | { 25 | "declaration": "parens-new-line", 26 | "assignment": "parens-new-line", 27 | "return": "parens-new-line", 28 | "arrow": "parens-new-line", 29 | "condition": "parens-new-line", 30 | "logical": "parens-new-line", 31 | "prop": "ignore" 32 | } 33 | ], 34 | "no-else-return": [0], 35 | "no-restricted-syntax": [0], 36 | "import/no-extraneous-dependencies": [0], 37 | "no-use-before-define": [0], 38 | "jsx-a11y/no-static-element-interactions": [0], 39 | "jsx-a11y/no-noninteractive-element-interactions": [0], 40 | "jsx-a11y/click-events-have-key-events": [0], 41 | "jsx-a11y/anchor-is-valid": [0], 42 | "no-nested-ternary": [0], 43 | "arrow-body-style": [0], 44 | "import/extensions": [0], 45 | "no-bitwise": [0], 46 | "no-cond-assign": [0], 47 | "import/no-unresolved": [0], 48 | "comma-dangle": [ 49 | "error", 50 | { 51 | "arrays": "always-multiline", 52 | "objects": "always-multiline", 53 | "imports": "always-multiline", 54 | "exports": "always-multiline", 55 | "functions": "ignore" 56 | } 57 | ], 58 | "object-curly-newline": [0], 59 | "function-paren-newline": [0], 60 | "no-restricted-globals": [0], 61 | "require-yield": [1] 62 | }, 63 | "parserOptions": { 64 | "ecmaFeatures": { 65 | "experimentalObjectRestSpread": true 66 | } 67 | }, 68 | "settings": { 69 | "polyfills": ["fetch", "promises"] 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /.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 | 14 | # misc 15 | .DS_Store 16 | npm-debug.log* 17 | yarn-error.log 18 | 19 | /coverage 20 | .idea 21 | yarn.lock 22 | package-lock.json 23 | *bak 24 | jsconfig.json 25 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg 3 | 4 | package.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "printWidth": 100, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { 9 | "parser": "json" 10 | } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.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: [ 6 | // 'transform-decorators-legacy', 7 | ['import', { libraryName: 'antd', libraryDirectory: 'es', style: true }], 8 | ], 9 | env: { 10 | development: { 11 | extraBabelPlugins: ['dva-hmr'], 12 | }, 13 | }, 14 | alias: { 15 | components: path.resolve(__dirname, 'src/components/'), 16 | }, 17 | ignoreMomentLocale: true, 18 | theme: './src/theme.js', 19 | html: { 20 | template: './src/index.ejs', 21 | }, 22 | disableDynamicImport: true, 23 | publicPath: '', 24 | hash: true, 25 | }; 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 776248185@qq.com 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 |

ant-design-tabs(支持多标签tabs)

2 | 3 |
4 | 5 |
6 | 7 | ## 体验环境 8 | 9 | 体验环境:https://kuhami.github.io/react-ant-pro 账号:admin/user 密码:888888/123456 10 | 11 | 由于体验环境发出的请求针对的是不存在的记录,服务器没有进行操作会报请求错误 404 。 12 | 13 | ## 特性gh-pages 14 | 15 | - :gem: **优雅美观**:基于 Ant Tabs 体系精心设计 16 | - :triangular_ruler: **常见设计模式**:提炼自中后台应用的典型页面和场景 17 | - :rocket: **最新技术栈**:使用 React/umi/dva/antd 等前端前沿技术开发 18 | - :iphone: **响应式**:针对不同屏幕大小设计 19 | - :art: **主题**:可配置的主题满足多样化的品牌诉求 20 | - :globe_with_meridians: **国际化**:内建业界通用的国际化方案 21 | - :gear: **最佳实践**:良好的工程实践助您持续产出高质量代码 22 | - :1234: **Mock 数据**:实用的本地数据调试方案 23 | - :white_check_mark: **UI 测试**:自动化测试保障前端产品质量 24 | 25 | ## 模板 26 | 27 | ``` 28 | - 社区精选组件 29 | - 拖拽 30 | - 富文本编译器 31 | - 组件封装 32 | - 多功能Table 33 | - SelectTree 34 | - React hooks 35 | - Dashboard 36 | - 分析页 37 | - 监控页 38 | - 工作台 39 | - 表单页 40 | - 基础表单页 41 | - 分步表单页 42 | - 高级表单页 43 | - 列表页 44 | - 查询表格 45 | - 标准列表 46 | - 卡片列表 47 | - 搜索列表(项目/应用/文章) 48 | - 详情页 49 | - 基础详情页 50 | - 高级详情页 51 | - 用户 52 | - 用户中心页 53 | - 用户设置页 54 | - 结果 55 | - 成功页 56 | - 失败页 57 | - 异常 58 | - 403 无权限 59 | - 404 找不到 60 | - 500 服务器出错 61 | - 帐户 62 | - 注册成功 63 | ``` 64 | 65 | ## 使用 66 | 67 | ### 使用命令行 68 | ```bash 69 | $ git clone https://github.com/kuhami/react-ant-pro.git 70 | $ cd react-ant-pro 71 | $ npm install 72 | $ npm start # 访问 http://localhost:8000 73 | ``` 74 | 75 | 更多信息请参考 [使用文档](https://pro.ant.design/docs/getting-started)。 76 | 77 | ## 参与贡献 78 | 79 | 我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :smiley:: 80 | 81 | - 在你的公司或个人项目中使用 Ant Tabs。 82 | - 提交 [Pull Request](https://github.com/kuhami/react-ant-pro) 改进 Pro 的代码。 83 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /mock/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuhami/react-ant-pro/bbcce6f325fdef7c95d255d04a0ea15aff3fafd3/mock/.gitkeep -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuhami/react-ant-pro/bbcce6f325fdef7c95d255d04a0ea15aff3fafd3/public/favicon.png -------------------------------------------------------------------------------- /public/image/ant.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuhami/react-ant-pro/bbcce6f325fdef7c95d255d04a0ea15aff3fafd3/public/image/ant.jpeg -------------------------------------------------------------------------------- /src/components/ActiveChart/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import { MiniArea } from '../Charts'; 4 | import NumberInfo from '../NumberInfo'; 5 | 6 | import styles from './index.less'; 7 | 8 | function fixedZero(val) { 9 | return val * 1 < 10 ? `0${val}` : val; 10 | } 11 | 12 | function getActiveData() { 13 | const activeData = []; 14 | for (let i = 0; i < 24; i += 1) { 15 | activeData.push({ 16 | x: `${fixedZero(i)}:00`, 17 | y: Math.floor(Math.random() * 200) + i * 50, 18 | }); 19 | } 20 | return activeData; 21 | } 22 | 23 | export default class ActiveChart extends Component { 24 | state = { 25 | activeData: getActiveData(), 26 | }; 27 | 28 | componentDidMount() { 29 | this.timer = setInterval(() => { 30 | this.setState({ 31 | activeData: getActiveData(), 32 | }); 33 | }, 1000); 34 | } 35 | 36 | componentWillUnmount() { 37 | clearInterval(this.timer); 38 | } 39 | 40 | render() { 41 | const { activeData = [] } = this.state; 42 | 43 | return ( 44 |
45 | 46 |
47 | 65 |
66 | {activeData && ( 67 |
68 |

{[...activeData].sort()[activeData.length - 1].y + 200} 亿元

69 |

{[...activeData].sort()[Math.floor(activeData.length / 2)].y} 亿元

70 |
71 | )} 72 | {activeData && ( 73 |
74 | 00:00 75 | {activeData[Math.floor(activeData.length / 2)].x} 76 | {activeData[activeData.length - 1].x} 77 |
78 | )} 79 |
80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /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, ...rest } = this.props; 8 | return ( 9 | } />} 12 | > 13 | (Component ? : render(props))} /> 14 | 15 | ); 16 | } 17 | } 18 | 19 | export default AuthorizedRoute; 20 | -------------------------------------------------------------------------------- /src/components/Authorized/CheckPermissions.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PromiseRender from './PromiseRender'; 3 | import { CURRENT } from './index'; 4 | 5 | function isPromise(obj) { 6 | return ( 7 | !!obj && 8 | (typeof obj === 'object' || typeof obj === 'function') && 9 | typeof obj.then === 'function' 10 | ); 11 | } 12 | 13 | /** 14 | * 通用权限检查方法 15 | * Common check permissions method 16 | * @param { 权限判定 Permission judgment type string |array | Promise | Function } authority 17 | * @param { 你的权限 Your permission description type:string} currentAuthority 18 | * @param { 通过的组件 Passing components } target 19 | * @param { 未通过的组件 no pass components } Exception 20 | */ 21 | const checkPermissions = (authority, currentAuthority, target, Exception) => { 22 | // 没有判定权限.默认查看所有 23 | // Retirement authority, return target; 24 | if (!authority) { 25 | return target; 26 | } 27 | // 数组处理 28 | if (Array.isArray(authority)) { 29 | if (authority.indexOf(currentAuthority) >= 0) { 30 | return target; 31 | } 32 | return Exception; 33 | } 34 | 35 | // string 处理 36 | if (typeof authority === 'string') { 37 | if (authority === currentAuthority) { 38 | return target; 39 | } 40 | return Exception; 41 | } 42 | 43 | // Promise 处理 44 | if (isPromise(authority)) { 45 | return ; 46 | } 47 | 48 | // Function 处理 49 | if (typeof authority === 'function') { 50 | try { 51 | const bool = authority(currentAuthority); 52 | if (bool) { 53 | return target; 54 | } 55 | return Exception; 56 | } catch (error) { 57 | throw error; 58 | } 59 | } 60 | throw new Error('unsupported parameters'); 61 | }; 62 | 63 | export { checkPermissions }; 64 | 65 | const check = (authority, target, Exception) => { 66 | return checkPermissions(authority, CURRENT, target, Exception); 67 | }; 68 | 69 | export default check; 70 | -------------------------------------------------------------------------------- /src/components/Authorized/CheckPermissions.test.js: -------------------------------------------------------------------------------- 1 | import { checkPermissions } from './CheckPermissions.js'; 2 | 3 | const target = 'ok'; 4 | const error = 'error'; 5 | 6 | describe('test CheckPermissions', () => { 7 | it('Correct string permission authentication', () => { 8 | expect(checkPermissions('user', 'user', target, error)).toEqual('ok'); 9 | }); 10 | it('Correct string permission authentication', () => { 11 | expect(checkPermissions('user', 'NULL', target, error)).toEqual('error'); 12 | }); 13 | it('authority is undefined , return ok', () => { 14 | expect(checkPermissions(null, 'NULL', target, error)).toEqual('ok'); 15 | }); 16 | it('currentAuthority is undefined , return error', () => { 17 | expect(checkPermissions('admin', null, target, error)).toEqual('error'); 18 | }); 19 | it('Wrong string permission authentication', () => { 20 | expect(checkPermissions('admin', 'user', target, error)).toEqual('error'); 21 | }); 22 | it('Correct Array permission authentication', () => { 23 | expect(checkPermissions(['user', 'admin'], 'user', target, error)).toEqual('ok'); 24 | }); 25 | it('Wrong Array permission authentication,currentAuthority error', () => { 26 | expect(checkPermissions(['user', 'admin'], 'user,admin', target, error)).toEqual('error'); 27 | }); 28 | it('Wrong Array permission authentication', () => { 29 | expect(checkPermissions(['user', 'admin'], 'guest', target, error)).toEqual('error'); 30 | }); 31 | it('Wrong Function permission authentication', () => { 32 | expect(checkPermissions(() => false, 'guest', target, error)).toEqual('error'); 33 | }); 34 | it('Correct Function permission authentication', () => { 35 | expect(checkPermissions(() => true, 'guest', target, error)).toEqual('ok'); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /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 | componentDidMount() { 9 | this.setRenderComponent(this.props); 10 | } 11 | componentWillReceiveProps(nextProps) { 12 | // new Props enter 13 | this.setRenderComponent(nextProps); 14 | } 15 | // set render Component : ok or error 16 | setRenderComponent(props) { 17 | const ok = this.checkIsInstantiation(props.ok); 18 | const error = this.checkIsInstantiation(props.error); 19 | props.promise 20 | .then(() => { 21 | this.setState({ 22 | component: ok, 23 | }); 24 | }) 25 | .catch(() => { 26 | this.setState({ 27 | component: error, 28 | }); 29 | }); 30 | } 31 | // Determine whether the incoming component has been instantiated 32 | // AuthorizedRoute is already instantiated 33 | // Authorized render is already instantiated, children is no instantiated 34 | // Secured is not instantiated 35 | checkIsInstantiation = target => { 36 | if (!React.isValidElement(target)) { 37 | return target; 38 | } 39 | return () => target; 40 | }; 41 | render() { 42 | const Component = this.state.component; 43 | return Component ? ( 44 | 45 | ) : ( 46 |
55 | 56 |
57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /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(targer) { 50 | const component = CheckPermissions(authority, targer, 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 | 6 | /* eslint-disable import/no-mutable-exports */ 7 | let CURRENT = 'NULL'; 8 | 9 | Authorized.Secured = Secured; 10 | Authorized.AuthorizedRoute = AuthorizedRoute; 11 | Authorized.check = check; 12 | 13 | /** 14 | * use authority or getAuthority 15 | * @param {string|()=>String} currentAuthority 16 | */ 17 | const renderAuthorize = currentAuthority => { 18 | if (currentAuthority) { 19 | if (currentAuthority.constructor.name === 'Function') { 20 | CURRENT = currentAuthority(); 21 | } 22 | if (currentAuthority.constructor.name === 'String') { 23 | CURRENT = currentAuthority; 24 | } 25 | } else { 26 | CURRENT = 'NULL'; 27 | } 28 | return Authorized; 29 | }; 30 | 31 | export { CURRENT }; 32 | export default renderAuthorize; 33 | -------------------------------------------------------------------------------- /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` | - | 29 | | noMatch | 权限异常渲染元素,权限判断不通过时展示 | ReactNode | - | 30 | 31 | ### Authorized.AuthorizedRoute 32 | 33 | | 参数 | 说明 | 类型 | 默认值 | 34 | |----------|------------------------------------------|-------------|-------| 35 | | authority | 准入权限/权限判断 | `string | array | Promise | (currentAuthority) => boolean` | - | 36 | | redirectPath | 权限异常时重定向的页面路由 | string | - | 37 | 38 | 其余参数与 `Route` 相同。 39 | 40 | ### Authorized.Secured 41 | 42 | 注解方式,`@Authorized.Secured(authority, error)` 43 | 44 | | 参数 | 说明 | 类型 | 默认值 | 45 | |----------|------------------------------------------|-------------|-------| 46 | | authority | 准入权限/权限判断 | `string | Promise | (currentAuthority) => boolean` | - | 47 | | error | 权限异常时渲染元素 | ReactNode | | 48 | 49 | ### Authorized.check 50 | 51 | 函数形式的 Authorized,用于某些不能被 HOC 包裹的组件。 `Authorized.check(authority, target, Exception)` 52 | 注意:传入一个 Promise 时,无论正确还是错误返回的都是一个 ReactClass。 53 | 54 | | 参数 | 说明 | 类型 | 默认值 | 55 | |----------|------------------------------------------|-------------|-------| 56 | | authority | 准入权限/权限判断 | `string | Promise | (currentAuthority) => boolean` | - | 57 | | target | 权限判断通过时渲染的元素 | ReactNode | - | 58 | | Exception | 权限异常时渲染元素 | ReactNode | - | 59 | -------------------------------------------------------------------------------- /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 | function | 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, Spin } from 'antd'; 3 | import classNames from 'classnames'; 4 | 5 | import styles from './index.less'; 6 | 7 | const renderTotal = total => { 8 | let totalDom; 9 | switch (typeof total) { 10 | case undefined: 11 | totalDom = null; 12 | break; 13 | case 'function': 14 | totalDom =
    {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 | { 69 | 70 | {content} 71 | 72 | } 73 | 74 | ); 75 | }; 76 | 77 | export default ChartCard; 78 | -------------------------------------------------------------------------------- /src/components/Charts/ChartCard/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .chartCard { 4 | position: relative; 5 | .chartTop { 6 | position: relative; 7 | overflow: hidden; 8 | width: 100%; 9 | } 10 | .chartTopMargin { 11 | margin-bottom: 12px; 12 | } 13 | .chartTopHasMargin { 14 | margin-bottom: 20px; 15 | } 16 | .metaWrap { 17 | float: left; 18 | } 19 | .avatar { 20 | position: relative; 21 | top: 4px; 22 | float: left; 23 | margin-right: 20px; 24 | img { 25 | border-radius: 100%; 26 | } 27 | } 28 | .meta { 29 | color: @text-color-secondary; 30 | font-size: @font-size-base; 31 | line-height: 22px; 32 | height: 22px; 33 | } 34 | .action { 35 | cursor: pointer; 36 | position: absolute; 37 | top: 0; 38 | right: 0; 39 | } 40 | .total { 41 | overflow: hidden; 42 | text-overflow: ellipsis; 43 | word-break: break-all; 44 | white-space: nowrap; 45 | color: @heading-color; 46 | margin-top: 4px; 47 | margin-bottom: 0; 48 | font-size: 30px; 49 | line-height: 38px; 50 | height: 38px; 51 | } 52 | .content { 53 | margin-bottom: 12px; 54 | position: relative; 55 | width: 100%; 56 | } 57 | .contentFixed { 58 | position: absolute; 59 | left: 0; 60 | bottom: 0; 61 | width: 100%; 62 | } 63 | .footer { 64 | border-top: 1px solid @border-color-split; 65 | padding-top: 9px; 66 | margin-top: 8px; 67 | & > * { 68 | position: relative; 69 | } 70 | } 71 | .footerMargin { 72 | margin-top: 20px; 73 | } 74 | } 75 | 76 | .spin :global(.ant-spin-container) { 77 | overflow: visible; 78 | } 79 | -------------------------------------------------------------------------------- /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?: string | function; 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 | 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: string; 5 | y1: string; 6 | y2: string; 7 | }>; 8 | titleMap: { y1: string; y2: string }; 9 | padding?: [number, number, number, number]; 10 | height?: number; 11 | style?: React.CSSProperties; 12 | } 13 | 14 | export default class TimelineChart extends React.Component {} 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/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 | export { 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 | -------------------------------------------------------------------------------- /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 &&
    {children}
    } 14 | 15 | ); 16 | }; 17 | 18 | Description.defaultProps = { 19 | term: '', 20 | }; 21 | 22 | Description.propTypes = { 23 | term: PropTypes.node, 24 | }; 25 | 26 | export default Description; 27 | -------------------------------------------------------------------------------- /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 | export default ({ 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(children, child => React.cloneElement(child, { column }))} 26 | 27 |
    28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/DescriptionList/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: Basic 4 | --- 5 | 6 | 基本描述列表。 7 | 8 | ````jsx 9 | import DescriptionList from 'ant-design-pro/lib/DescriptionList'; 10 | 11 | const { Description } = DescriptionList; 12 | 13 | ReactDOM.render( 14 | 15 | 16 | A free, open source, cross-platform, 17 | graphical web browser developed by the 18 | Mozilla Corporation and hundreds of 19 | volunteers. 20 | 21 | 22 | A free, open source, cross-platform, 23 | graphical web browser developed by the 24 | Mozilla Corporation and hundreds of 25 | volunteers. 26 | 27 | 28 | A free, open source, cross-platform, 29 | graphical web browser developed by the 30 | Mozilla Corporation and hundreds of 31 | volunteers. 32 | 33 | 34 | , mountNode); 35 | ```` 36 | -------------------------------------------------------------------------------- /src/components/DescriptionList/demo/vertical.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: Vertical 4 | --- 5 | 6 | 垂直布局。 7 | 8 | ````jsx 9 | import DescriptionList from 'ant-design-pro/lib/DescriptionList'; 10 | 11 | const { Description } = DescriptionList; 12 | 13 | ReactDOM.render( 14 | 15 | 16 | A free, open source, cross-platform, 17 | graphical web browser developed by the 18 | Mozilla Corporation and hundreds of 19 | volunteers. 20 | 21 | 22 | A free, open source, cross-platform, 23 | graphical web browser developed by the 24 | Mozilla Corporation and hundreds of 25 | volunteers. 26 | 27 | 28 | A free, open source, cross-platform, 29 | graphical web browser developed by the 30 | Mozilla Corporation and hundreds of 31 | volunteers. 32 | 33 | 34 | , mountNode); 35 | ```` 36 | -------------------------------------------------------------------------------- /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.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: 22px; 21 | padding-bottom: 16px; 22 | margin-right: 8px; 23 | color: @heading-color; 24 | white-space: nowrap; 25 | display: table-cell; 26 | 27 | &:after { 28 | content: ':'; 29 | margin: 0 8px 0 2px; 30 | position: relative; 31 | top: -0.5px; 32 | } 33 | } 34 | 35 | .detail { 36 | line-height: 22px; 37 | width: 100%; 38 | padding-bottom: 16px; 39 | color: @text-color; 40 | display: table-cell; 41 | } 42 | 43 | &.small { 44 | // offset the padding-bottom of last row 45 | :global { 46 | .ant-row { 47 | margin-bottom: -8px; 48 | } 49 | } 50 | .title { 51 | margin-bottom: 12px; 52 | color: @text-color; 53 | } 54 | .term, 55 | .detail { 56 | padding-bottom: 8px; 57 | } 58 | } 59 | 60 | &.large { 61 | .title { 62 | font-size: 16px; 63 | } 64 | } 65 | 66 | &.vertical { 67 | .term { 68 | padding-bottom: 8px; 69 | display: block; 70 | } 71 | 72 | .detail { 73 | display: block; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/components/DescriptionList/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: DescriptionList 4 | zh-CN: DescriptionList 5 | subtitle: 描述列表 6 | cols: 1 7 | order: 4 8 | --- 9 | 10 | 成组展示多个只读字段,常见于详情页的信息展示。 11 | 12 | ## API 13 | 14 | ### DescriptionList 15 | 16 | | 参数 | 说明 | 类型 | 默认值 | 17 | |----------|------------------------------------------|-------------|-------| 18 | | layout | 布局方式 | Enum{'horizontal', 'vertical'} | 'horizontal' | 19 | | col | 指定信息最多分几列展示,最终一行几列由 col 配置结合[响应式规则](/components/DescriptionList#响应式规则)决定 | number(0 < col <= 4) | 3 | 20 | | title | 列表标题 | ReactNode | - | 21 | | gutter | 列表项间距,单位为 `px` | number | 32 | 22 | | size | 列表型号,可以设置为 `large` `small` | Enum{'large', 'small'} | - | 23 | 24 | #### 响应式规则 25 | 26 | | 窗口宽度 | 展示列数 | 27 | |---------------------|---------------------------------------------| 28 | | `≥768px` | `col` | 29 | | `≥576px` | `col < 2 ? col : 2` | 30 | | `<576px` | `1` | 31 | 32 | ### DescriptionList.Description 33 | 34 | | 参数 | 说明 | 类型 | 默认值 | 35 | |----------|------------------------------------------|-------------|-------| 36 | | term | 列表项标题 | ReactNode | - | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /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 | state = { 7 | value: this.props.value, 8 | editable: false, 9 | }; 10 | handleChange = e => { 11 | const { value } = e.target; 12 | this.setState({ value }); 13 | }; 14 | check = () => { 15 | this.setState({ editable: false }); 16 | if (this.props.onChange) { 17 | this.props.onChange(this.state.value); 18 | } 19 | }; 20 | edit = () => { 21 | this.setState({ editable: true }); 22 | }; 23 | render() { 24 | const { value, editable } = this.state; 25 | return ( 26 |
    27 | {editable ? ( 28 |
    29 | 30 | 31 |
    32 | ) : ( 33 |
    34 | {value || ' '} 35 | 36 |
    37 | )} 38 |
    39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /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 defaultProps = { 10 | links: [], 11 | onAdd: () => {}, 12 | linkElement: 'a', 13 | }; 14 | 15 | static propTypes = { 16 | links: PropTypes.array, 17 | onAdd: PropTypes.func, 18 | linkElement: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), 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 | --- 5 | 6 | 通过设置 `lines` 属性指定最大行数,如果超过这个行数的文本会自动截取。但是在这种模式下所有 `children` 将会被转换成纯文本。 7 | 8 | 并且注意在这种模式下,外容器需要有指定的宽度(或设置自身宽度)。 9 | 10 | ````jsx 11 | import Ellipsis from 'ant-design-pro/lib/Ellipsis'; 12 | 13 | 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.

    ; 14 | 15 | ReactDOM.render( 16 |
    17 | {article} 18 |
    19 | , mountNode); 20 | ```` 21 | -------------------------------------------------------------------------------- /src/components/Ellipsis/demo/number.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 按照字符数省略 4 | --- 5 | 6 | 通过设置 `length` 属性指定文本最长长度,如果超过这个长度会自动截取。 7 | 8 | ````jsx 9 | import Ellipsis from 'ant-design-pro/lib/Ellipsis'; 10 | 11 | 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.'; 12 | 13 | ReactDOM.render( 14 |
    15 | {article} 16 |

    Show Tooltip

    17 | {article} 18 |
    19 | , mountNode); 20 | ```` 21 | -------------------------------------------------------------------------------- /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 | } 9 | 10 | export default class Ellipsis extends React.Component {} 11 | -------------------------------------------------------------------------------- /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.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: Ellipsis 4 | zh-CN: Ellipsis 5 | subtitle: 文本自动省略号 6 | cols: 1 7 | order: 10 8 | --- 9 | 10 | 文本过长自动处理省略号,支持按照文本长度和最大行数两种方式截取。 11 | 12 | ## API 13 | 14 | 参数 | 说明 | 类型 | 默认值 15 | ----|------|-----|------ 16 | tooltip | 移动到文本展示完整内容的提示 | boolean | - 17 | length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | - 18 | lines | 在按照行数截取下最大的行数,超过则截取省略 | number | `1` 19 | -------------------------------------------------------------------------------- /src/components/Exception/demo/403.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | title: 403 4 | --- 5 | 6 | 403 页面,配合自定义操作。 7 | 8 | ````jsx 9 | import Exception from 'ant-design-pro/lib/Exception'; 10 | import { Button } from 'antd'; 11 | 12 | const actions = ( 13 |
    14 | 15 | 16 |
    17 | ); 18 | ReactDOM.render( 19 | 20 | , mountNode); 21 | ```` 22 | -------------------------------------------------------------------------------- /src/components/Exception/demo/404.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 404 4 | --- 5 | 6 | 404 页面。 7 | 8 | ````jsx 9 | import Exception from 'ant-design-pro/lib/Exception'; 10 | 11 | ReactDOM.render( 12 | 13 | , mountNode); 14 | ```` 15 | -------------------------------------------------------------------------------- /src/components/Exception/demo/500.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: 500 4 | --- 5 | 6 | 500 页面。 7 | 8 | ````jsx 9 | import Exception from 'ant-design-pro/lib/Exception'; 10 | 11 | ReactDOM.render( 12 | 13 | , mountNode); 14 | ```` 15 | -------------------------------------------------------------------------------- /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.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 | export default ({ 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 | -------------------------------------------------------------------------------- /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.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: Exception 4 | zh-CN: Exception 5 | subtitle: 异常 6 | cols: 1 7 | order: 5 8 | --- 9 | 10 | 异常页用于对页面特定的异常状态进行反馈。通常,它包含对错误状态的阐述,并向用户提供建议或操作,避免用户感到迷失和困惑。 11 | 12 | ## API 13 | 14 | | 参数 | 说明 | 类型 | 默认值 | 15 | |-------------|------------------------------------------|-------------|-------| 16 | | type | 页面类型,若配置,则自带对应类型默认的 `title`,`desc`,`img`,此默认设置可以被 `title`,`desc`,`img` 覆盖 | Enum {'403', '404', '500'} | - | 17 | | title | 标题 | ReactNode | - | 18 | | desc | 补充描述 | ReactNode | - | 19 | | img | 背景图片地址 | string | - | 20 | | actions | 建议操作,配置此属性时默认的『返回首页』按钮不生效 | ReactNode | - | 21 | | linkElement | 定义链接的元素,默认为 `a` | string\|ReactElement | - | 22 | -------------------------------------------------------------------------------- /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 classNames from 'classnames'; 3 | import styles from './index.less'; 4 | 5 | export default class FooterToolbar extends Component { 6 | render() { 7 | const { children, className, extra, ...restProps } = this.props; 8 | return ( 9 |
    10 |
    {extra}
    11 |
    {children}
    12 |
    13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /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 | title: React.ReactNode; 5 | href: string; 6 | blankTarget?: boolean; 7 | }>; 8 | copyright?: React.ReactNode; 9 | style?: React.CSSProperties; 10 | } 11 | 12 | export default class GlobalFooter extends React.Component {} 13 | -------------------------------------------------------------------------------- /src/components/GlobalFooter/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import styles from './index.less'; 4 | 5 | export default ({ 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 | -------------------------------------------------------------------------------- /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 | } 10 | 11 | export default class HeaderSearch extends React.Component {} 12 | -------------------------------------------------------------------------------- /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/ImageWrapper/index.js: -------------------------------------------------------------------------------- 1 | // index.js 2 | import React from 'react'; 3 | import styles from './index.less'; // import style in css modules way 4 | 5 | export default ({ src, desc, style }) => ( 6 |
    7 | {desc} 8 | {desc &&
    {desc}
    } 9 |
    10 | ); 11 | -------------------------------------------------------------------------------- /src/components/ImageWrapper/index.less: -------------------------------------------------------------------------------- 1 | // index.less 2 | .imageWrapper { 3 | padding: 0 20px 8px; 4 | background: #f2f4f5; 5 | width: 400px; 6 | margin: 0 auto; 7 | text-align: center; 8 | } 9 | 10 | .img { 11 | vertical-align: middle; 12 | max-width: calc(100% - 32px); 13 | margin: 2.4em 1em; 14 | box-shadow: 0 8px 20px rgba(143, 168, 191, 0.35); 15 | } 16 | -------------------------------------------------------------------------------- /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 | export default ({ 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 | 8 | .icon { 9 | font-size: 72px; 10 | line-height: 72px; 11 | margin-bottom: 24px; 12 | 13 | & > .success { 14 | color: @success-color; 15 | } 16 | 17 | & > .error { 18 | color: @error-color; 19 | } 20 | } 21 | 22 | .title { 23 | font-size: 24px; 24 | color: @heading-color; 25 | font-weight: 500; 26 | line-height: 32px; 27 | margin-bottom: 16px; 28 | } 29 | 30 | .description { 31 | font-size: 14px; 32 | line-height: 22px; 33 | color: @text-color-secondary; 34 | margin-bottom: 24px; 35 | } 36 | 37 | .extra { 38 | background: #fafafa; 39 | padding: 24px 40px; 40 | border-radius: @border-radius-sm; 41 | text-align: left; 42 | } 43 | 44 | .actions { 45 | margin-top: 32px; 46 | 47 | button:not(:last-child) { 48 | margin-right: 8px; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /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 { getMeunMatcheys } from './SiderMenu'; 2 | 3 | const meun = ['/dashboard', '/userinfo', '/dashboard/name', '/userinfo/:id', '/userinfo/:id/info']; 4 | 5 | describe('test meun match', () => { 6 | it('simple path', () => { 7 | expect(getMeunMatcheys(meun, '/dashboard')).toEqual(['/dashboard']); 8 | }); 9 | it('error path', () => { 10 | expect(getMeunMatcheys(meun, '/dashboardname')).toEqual([]); 11 | }); 12 | 13 | it('Secondary path', () => { 14 | expect(getMeunMatcheys(meun, '/dashboard/name')).toEqual(['/dashboard/name']); 15 | }); 16 | 17 | it('Parameter path', () => { 18 | expect(getMeunMatcheys(meun, '/userinfo/2144')).toEqual(['/userinfo/:id']); 19 | }); 20 | 21 | it('three parameter path', () => { 22 | expect(getMeunMatcheys(meun, '/userinfo/2144/info')).toEqual(['/userinfo/:id/info']); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/components/SiderMenu/index.js: -------------------------------------------------------------------------------- 1 | import 'rc-drawer-menu/assets/index.css'; 2 | import React from 'react'; 3 | import DrawerMenu from 'rc-drawer-menu'; 4 | import SiderMenu from './SiderMenu'; 5 | 6 | export default props => 7 | props.isMobile ? ( 8 | { 14 | props.onCollapse(true); 15 | }} 16 | width="256px" 17 | > 18 | 19 | 20 | ) : ( 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /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 | export default ({ 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 | -------------------------------------------------------------------------------- /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/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 | } 8 | 9 | export default class Trend extends React.Component {} 10 | -------------------------------------------------------------------------------- /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, flag, children, className, ...rest }) => { 7 | const classString = classNames( 8 | styles.trendItem, 9 | { 10 | [styles.trendItemGrey]: !colorful, 11 | }, 12 | className 13 | ); 14 | return ( 15 |
    16 | {children} 17 | {flag && ( 18 | 19 | 20 | 21 | )} 22 |
    23 | ); 24 | }; 25 | 26 | export default Trend; 27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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(); 6 | const page = await browser.newPage(); 7 | await page.goto('http://localhost:8000'); 8 | await page.waitForSelector('h1'); 9 | const text = await page.evaluate(() => document.body.innerHTML); 10 | expect(text).toContain('

    Ant Tabs

    '); 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(); 9 | }); 10 | 11 | beforeEach(async () => { 12 | page = await browser.newPage(); 13 | await page.goto('http://localhost:8000/#/user/login'); 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.type('#userName', 'mockuser'); 21 | await page.type('#password', 'wrong_password'); 22 | await page.click('button[type="submit"]'); 23 | await page.waitForSelector('.ant-alert-error'); // should display error 24 | }); 25 | 26 | it('should login successfully', async () => { 27 | await page.type('#userName', 'admin'); 28 | await page.type('#password', '888888'); 29 | await page.click('button[type="submit"]'); 30 | await page.waitForSelector('.ant-layout-sider h1'); // should display error 31 | const text = await page.evaluate(() => document.body.innerHTML); 32 | expect(text).toContain('

    Ant Tabs

    '); 33 | }); 34 | 35 | afterAll(() => browser.close()); 36 | }); 37 | -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Ant Tabs 9 | 10 | 11 | 12 | 13 |
    14 | 15 | 16 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill'; 2 | import 'url-polyfill'; 3 | import dva from 'dva'; 4 | 5 | import createHistory from 'history/createHashHistory'; 6 | // user BrowserHistory 7 | // import createHistory from 'history/createBrowserHistory'; 8 | import createLoading from 'dva-loading'; 9 | import 'moment/locale/zh-cn'; 10 | import './rollbar'; 11 | 12 | import './index.less'; 13 | // 1. Initialize 14 | const app = dva({ 15 | history: createHistory(), 16 | }); 17 | 18 | // 2. Plugins 19 | app.use(createLoading()); 20 | 21 | // 3. Register global model 22 | app.model(require('./models/global').default); 23 | 24 | // 4. Router 25 | app.router(require('./router').default); 26 | 27 | // 5. Start 28 | app.start('#root'); 29 | 30 | export default app._store; // eslint-disable-line 31 | -------------------------------------------------------------------------------- /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 | min-height: 100%; 7 | background: #f0f2f5; 8 | } 9 | 10 | .content { 11 | padding: 32px 0; 12 | flex: 1; 13 | } 14 | 15 | @media (min-width: @screen-md-min) { 16 | .container { 17 | background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg'); 18 | background-repeat: no-repeat; 19 | background-position: center 110px; 20 | background-size: 100%; 21 | } 22 | 23 | .content { 24 | padding: 112px 0 24px 0; 25 | } 26 | } 27 | 28 | .top { 29 | text-align: center; 30 | } 31 | 32 | .header { 33 | height: 44px; 34 | line-height: 44px; 35 | a { 36 | text-decoration: none; 37 | } 38 | } 39 | 40 | .logo { 41 | height: 44px; 42 | vertical-align: top; 43 | margin-right: 16px; 44 | } 45 | 46 | .title { 47 | font-size: 33px; 48 | color: @heading-color; 49 | font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif; 50 | font-weight: 600; 51 | position: relative; 52 | top: 2px; 53 | } 54 | 55 | .desc { 56 | font-size: @font-size-base; 57 | color: @text-color-secondary; 58 | margin-top: 12px; 59 | margin-bottom: 40px; 60 | } 61 | -------------------------------------------------------------------------------- /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/global.js: -------------------------------------------------------------------------------- 1 | import { queryNotices } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'global', 5 | 6 | state: { 7 | collapsed: false, 8 | notices: [], 9 | }, 10 | 11 | effects: { 12 | *fetchNotices(_, { call, put }) { 13 | const data = yield call(queryNotices); 14 | yield put({ 15 | type: 'saveNotices', 16 | payload: data, 17 | }); 18 | yield put({ 19 | type: 'user/changeNotifyCount', 20 | payload: data.length, 21 | }); 22 | }, 23 | *clearNotices({ payload }, { put, select }) { 24 | yield put({ 25 | type: 'saveClearedNotices', 26 | payload, 27 | }); 28 | const count = yield select(state => state.global.notices.length); 29 | yield put({ 30 | type: 'user/changeNotifyCount', 31 | payload: count, 32 | }); 33 | }, 34 | }, 35 | 36 | reducers: { 37 | changeLayoutCollapsed(state, { payload }) { 38 | return { 39 | ...state, 40 | collapsed: payload, 41 | }; 42 | }, 43 | saveNotices(state, { payload }) { 44 | return { 45 | ...state, 46 | notices: payload, 47 | }; 48 | }, 49 | saveClearedNotices(state, { payload }) { 50 | return { 51 | ...state, 52 | notices: state.notices.filter(item => item.type !== payload), 53 | }; 54 | }, 55 | }, 56 | 57 | subscriptions: { 58 | setup({ history }) { 59 | // Subscribe history(url) change, trigger `load` action if pathname is `/` 60 | return history.listen(({ pathname, search }) => { 61 | if (typeof window.ga !== 'undefined') { 62 | window.ga('send', 'pageview', pathname + search); 63 | } 64 | }); 65 | }, 66 | }, 67 | }; 68 | -------------------------------------------------------------------------------- /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 { setAuthority } 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 | setAuthority(payload.currentAuthority); 51 | return { 52 | ...state, 53 | status: payload.status, 54 | type: payload.type, 55 | }; 56 | }, 57 | }, 58 | }; 59 | -------------------------------------------------------------------------------- /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/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 | payload: { 10 | environment: 'production', 11 | }, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { routerRedux, Route, Switch } from 'dva/router'; 3 | import { LocaleProvider, Spin } from 'antd'; 4 | import zhCN from 'antd/lib/locale-provider/zh_CN'; 5 | import dynamic from 'dva/dynamic'; 6 | import { getRouterData } from './common/router'; 7 | import Authorized from './utils/Authorized'; 8 | import styles from './index.less'; 9 | 10 | const { ConnectedRouter } = routerRedux; 11 | const { AuthorizedRoute } = Authorized; 12 | dynamic.setDefaultLoadingComponent(() => { 13 | return ; 14 | }); 15 | 16 | function RouterConfig({ history, app }) { 17 | const routerData = getRouterData(app); 18 | const UserLayout = routerData['/user'].component; 19 | const BasicLayout = routerData['/'].component; 20 | return ( 21 | 22 | 23 | 24 | 25 | } 28 | authority={['admin', 'user']} 29 | redirectPath="/user/login" 30 | /> 31 | 32 | 33 | 34 | ); 35 | } 36 | 37 | export default RouterConfig; 38 | -------------------------------------------------------------------------------- /src/routes/Component/Hooks.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import {Card, Button } from 'antd'; 3 | function Hooks(props) { 4 | // Declare a new state variable, which we'll call "count" 5 | // console.log(props); 6 | const [count, setCount] = useState(0) 7 | const [width, setWithd] = useState(window.innerWidth); 8 | // Similar to componentDidMount and componentDidUpdate: 9 | useEffect(() => { 10 | // Update the document title using the browser API 11 | document.title = `You clicked ${count} times`; 12 | const handleResize = ()=>{ 13 | setWithd(window.innerWidth); 14 | } 15 | window.addEventListener('resize', handleResize); 16 | 17 | // return () => (document.title = "前端精读"); 18 | 19 | },[width]); 20 | 21 | return ( 22 |
    23 | 24 |
    25 |

    窗口宽度{ width }px

    26 |

    You clicked {count} times

    27 | 30 |
    31 |
    32 |
    33 | ); 34 | } 35 | export default Hooks; 36 | 37 | -------------------------------------------------------------------------------- /src/routes/Component/Test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Row, Col } from 'antd'; 3 | import ImageWrapper from 'components/ImageWrapper'; // aware of the relative path 4 | 5 | export default () => ( 6 |
    7 | 8 | 9 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 33 | 34 | 35 |
    36 | ); 37 | -------------------------------------------------------------------------------- /src/routes/Component/Test.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/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/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 | triggerError = code => { 14 | this.setState({ 15 | isloading: true, 16 | }); 17 | this.props.dispatch({ 18 | type: 'error/query', 19 | payload: { 20 | code, 21 | }, 22 | }); 23 | }; 24 | render() { 25 | return ( 26 | 27 | 28 | 31 | 34 | 37 | 40 | 41 | 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /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 | class Step3 extends React.PureComponent { 9 | render() { 10 | const { dispatch, data } = this.props; 11 | const onFinish = () => { 12 | dispatch(routerRedux.push('/form/step-form')); 13 | }; 14 | const information = ( 15 |
    16 | 17 | 18 | 付款账户: 19 | 20 | {data.payAccount} 21 | 22 | 23 | 24 | 收款账户: 25 | 26 | {data.receiverAccount} 27 | 28 | 29 | 30 | 收款人姓名: 31 | 32 | {data.receiverName} 33 | 34 | 35 | 36 | 转账金额: 37 | 38 | 39 | {data.amount} 元 40 | 41 | 42 |
    43 | ); 44 | const actions = ( 45 | 46 | 49 | 50 | 51 | ); 52 | return ( 53 | 61 | ); 62 | } 63 | } 64 | 65 | export default connect(({ form }) => ({ 66 | data: form.step, 67 | }))(Step3); 68 | -------------------------------------------------------------------------------- /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 | const { Step } = Steps; 10 | 11 | export default class StepForm extends PureComponent { 12 | getCurrentStep() { 13 | const { location } = this.props; 14 | const { pathname } = location; 15 | const pathList = pathname.split('/'); 16 | switch (pathList[pathList.length - 1]) { 17 | case 'info': 18 | return 0; 19 | case 'confirm': 20 | return 1; 21 | case 'result': 22 | return 2; 23 | default: 24 | return 0; 25 | } 26 | } 27 | render() { 28 | const { match, routerData } = this.props; 29 | return ( 30 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {getRoutes(match.path, routerData).map(item => ( 43 | 49 | ))} 50 | 51 | 52 | 53 | 54 | 55 | 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /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 | } 64 | } 65 | 66 | .money { 67 | font-family: 'Helvetica Neue', sans-serif; 68 | font-weight: 500; 69 | font-size: 20px; 70 | line-height: 14px; 71 | } 72 | 73 | .uppercase { 74 | font-size: 12px; 75 | } 76 | -------------------------------------------------------------------------------- /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/Home/Home.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | 3 | export default class Home extends Component { 4 | constructor(props){ 5 | super(props); 6 | 7 | } 8 | componentDidMount() { 9 | 10 | } 11 | 12 | render() { 13 | 14 | return ( 15 |
    16 |

    欢迎使用多标签Ant-Tabs

    17 |
    18 | ); 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/routes/Libraries/BraftEditor.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react' 2 | import BraftEditor from 'braft-editor' 3 | import 'braft-editor/dist/index.css' 4 | import { Card } from 'antd'; 5 | 6 | export default class BasicDemo extends Component { 7 | constructor(props){ 8 | super(props); 9 | 10 | this.state = { 11 | editorState: BraftEditor.createEditorState('

    Hello World!

    MY Github 欢迎 Start(https://github.com/kuhami/react-ant-pro)😉

    '), // 设置编辑器初始内容 12 | outputHTML: '

    ' 13 | } 14 | } 15 | 16 | componentDidMount () { 17 | this.isLivinig = true 18 | // 3秒后更改编辑器内容 19 | // setTimeout(this.setEditorContentAsync, 3000) 20 | } 21 | 22 | componentWillUnmount () { 23 | this.isLivinig = false 24 | } 25 | 26 | handleChange = (editorState) => { 27 | this.setState({ 28 | editorState: editorState, 29 | outputHTML: editorState.toHTML() 30 | }) 31 | } 32 | 33 | setEditorContentAsync = () => { 34 | this.isLivinig && this.setState({ 35 | editorState: BraftEditor.createEditorState('

    你好,世界!

    ') 36 | }) 37 | } 38 | 39 | render () { 40 | 41 | const { editorState, outputHTML } = this.state 42 | const title = 富文本编译器(braft-editor) 43 | return ( 44 | 45 |

    46 | 50 |
    51 |
    输出内容
    52 |
    {outputHTML}
    53 | 54 | ) 55 | 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /src/routes/Libraries/Drag/Basic.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import {render} from 'react-dom'; 3 | import { Card } from 'antd'; 4 | import {SortableContainer, SortableElement} from 'react-sortable-hoc'; 5 | import arrayMove from 'array-move'; 6 | 7 | const SortableItem = SortableElement(({value}) =>
  • {value}
  • ); 8 | const SortableList = SortableContainer(({items}) => { 9 | return ( 10 |
      11 | {items.map((value, index) => ( 12 | 13 | ))} 14 |
    15 | ); 16 | }); 17 | export default class Basic extends Component { 18 | constructor(props){ 19 | super(props); 20 | this.state = { 21 | items: ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6'], 22 | } 23 | } 24 | componentDidMount() { 25 | 26 | } 27 | onSortEnd = ({oldIndex, newIndex}) => { 28 | this.setState(({items}) => ({ 29 | items: arrayMove(items, oldIndex, newIndex), 30 | })); 31 | }; 32 | render() { 33 | const title = 拖拽(react-sortable-hoc) 34 | return ( 35 | 36 | ); 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/routes/Libraries/Drag/Collections.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import {render} from 'react-dom'; 3 | import { Card } from 'antd'; 4 | import {sortableContainer, sortableElement} from 'react-sortable-hoc'; 5 | import arrayMove from 'array-move'; 6 | 7 | const SortableItem = sortableElement(({value}) =>
  • {value}
  • ); 8 | 9 | const SortableContainer = sortableContainer(({children}) => { 10 | return
    {children}
    ; 11 | }); 12 | export default class Collections extends Component { 13 | constructor(props){ 14 | super(props); 15 | this.state = { 16 | collections: [[0, 1, 2], [0, 1, 2, 3, 4], [0, 1, 2]], 17 | } 18 | } 19 | 20 | onSortEnd = ({oldIndex, newIndex, collection}) => { 21 | this.setState(({collections}) => { 22 | const newCollections = [...collections]; 23 | 24 | newCollections[collection] = arrayMove( 25 | collections[collection], 26 | oldIndex, 27 | newIndex, 28 | ); 29 | 30 | return {collections: newCollections}; 31 | }); 32 | }; 33 | render() { 34 | const {collections} = this.state; 35 | return ( 36 | 37 | {collections.map((items, index) => ( 38 | 39 | LIST {index} 40 |
      41 | {items.map((item, i) => ( 42 | 48 | ))} 49 |
    50 |
    51 | ))} 52 |
    53 |
    ); 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/routes/Libraries/Drag/DragHandle.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import {render} from 'react-dom'; 3 | import { Card } from 'antd'; 4 | import { 5 | sortableContainer, 6 | sortableElement, 7 | sortableHandle,} from 'react-sortable-hoc'; 8 | import arrayMove from 'array-move'; 9 | 10 | const DragHandles = sortableHandle(() => ::++::); 11 | 12 | const SortableItem = sortableElement(({value}) => ( 13 |
  • 14 | 15 | {value} 16 |
  • 17 | )); 18 | 19 | const SortableContainer = sortableContainer(({children}) => { 20 | return
      {children}
    ; 21 | }); 22 | export default class DragHandle extends Component { 23 | constructor(props){ 24 | super(props); 25 | this.state = { 26 | items: ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6'], 27 | } 28 | } 29 | 30 | onSortEnd = ({oldIndex, newIndex}) => { 31 | this.setState(({items}) => ({ 32 | items: arrayMove(items, oldIndex, newIndex), 33 | })); 34 | }; 35 | render() { 36 | const {items} = this.state; 37 | return ( 38 | 39 | {items.map((value, index) => ( 40 | 41 | ))} 42 | 43 | ); 44 | } 45 | } -------------------------------------------------------------------------------- /src/routes/Libraries/Drag/index.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import {render} from 'react-dom'; 3 | import { Row,Card,Col } from 'antd'; 4 | import Basic from './Basic.js'//DragHandle.js 5 | import Collections from './Collections.js' 6 | import DragHandle from './DragHandle.js' 7 | 8 | export default class Drag extends Component { 9 | constructor(props){ 10 | super(props); 11 | this.state = { 12 | items: ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6'], 13 | } 14 | } 15 | componentDidMount() { 16 | 17 | } 18 | 19 | render() { 20 | 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /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 | @connect() 9 | export default class SearchList extends Component { 10 | handleTabChange = key => { 11 | const { dispatch, match } = this.props; 12 | switch (key) { 13 | case 'articles': 14 | dispatch(routerRedux.push(`${match.url}/articles`)); 15 | break; 16 | case 'applications': 17 | dispatch(routerRedux.push(`${match.url}/applications`)); 18 | break; 19 | case 'projects': 20 | dispatch(routerRedux.push(`${match.url}/projects`)); 21 | break; 22 | default: 23 | break; 24 | } 25 | }; 26 | 27 | render() { 28 | const tabList = [ 29 | { 30 | key: 'articles', 31 | tab: '文章', 32 | }, 33 | { 34 | key: 'applications', 35 | tab: '应用', 36 | }, 37 | { 38 | key: 'projects', 39 | tab: '项目', 40 | }, 41 | ]; 42 | 43 | const mainSearch = ( 44 |
    45 | 52 |
    53 | ); 54 | 55 | const { match, routerData, location } = this.props; 56 | const routes = getRoutes(match.path, routerData); 57 | 58 | return ( 59 | 66 | 67 | {routes.map(item => ( 68 | 69 | ))} 70 | 71 | 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /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 | const actions = ; 34 | 35 | export default () => ( 36 | 37 | 38 | 46 | 47 | 48 | ); 49 | -------------------------------------------------------------------------------- /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 | 7 | .icon { 8 | font-size: 24px; 9 | color: rgba(0, 0, 0, 0.2); 10 | margin-left: 16px; 11 | vertical-align: middle; 12 | cursor: pointer; 13 | transition: color 0.3s; 14 | 15 | &:hover { 16 | color: @primary-color; 17 | } 18 | } 19 | 20 | .other { 21 | text-align: left; 22 | margin-top: 24px; 23 | line-height: 22px; 24 | 25 | .register { 26 | float: right; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /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 | import ImageWrapper from 'components/ImageWrapper'; // aware of the relative path 7 | 8 | const actions = ( 9 |
    10 | 11 | 14 | 15 | 16 | 17 | 18 |
    19 | ); 20 | 21 | export default ({ location }) => ( 22 |
    23 | 28 | 你的账户:{location.state ? location.state.account : 'AntDesign@example.com'} 注册成功 29 |
    30 | } 31 | description="激活邮件已发送到你的邮箱中,邮件有效期为24小时。请及时登录邮箱,点击邮件中的链接激活帐户。" 32 | actions={actions} 33 | style={{ marginTop: 56 }} 34 | /> 35 | 40 |
    41 | ); 42 | -------------------------------------------------------------------------------- /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 | 20 | .imgIndex{ 21 | .desc{ 22 | color: red; 23 | } 24 | } 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/services/api.js: -------------------------------------------------------------------------------- 1 | import { stringify } from 'qs'; 2 | import request from '../utils/request'; 3 | 4 | export async function queryProjectNotice() { 5 | return request('/api/project/notice'); 6 | } 7 | 8 | export async function queryActivities() { 9 | return request('/api/activities'); 10 | } 11 | 12 | export async function queryRule(params) { 13 | return request(`/api/rule?${stringify(params)}`); 14 | } 15 | 16 | export async function removeRule(params) { 17 | return request('/api/rule', { 18 | method: 'POST', 19 | body: { 20 | ...params, 21 | method: 'delete', 22 | }, 23 | }); 24 | } 25 | 26 | export async function addRule(params) { 27 | return request('/api/rule', { 28 | method: 'POST', 29 | body: { 30 | ...params, 31 | method: 'post', 32 | }, 33 | }); 34 | } 35 | 36 | export async function fakeSubmitForm(params) { 37 | return request('/api/forms', { 38 | method: 'POST', 39 | body: params, 40 | }); 41 | } 42 | 43 | export async function fakeChartData() { 44 | return request('/api/fake_chart_data'); 45 | } 46 | 47 | export async function queryTags() { 48 | return request('/api/tags'); 49 | } 50 | 51 | export async function queryBasicProfile() { 52 | return request('/api/profile/basic'); 53 | } 54 | 55 | export async function queryAdvancedProfile() { 56 | return request('/api/profile/advanced'); 57 | } 58 | 59 | export async function queryFakeList(params) { 60 | return request(`/api/fake_list?${stringify(params)}`); 61 | } 62 | 63 | export async function fakeAccountLogin(params) { 64 | return request('/api/login/account', { 65 | method: 'POST', 66 | body: params, 67 | }); 68 | } 69 | 70 | export async function fakeRegister(params) { 71 | return request('/api/register', { 72 | method: 'POST', 73 | body: params, 74 | }); 75 | } 76 | 77 | export async function queryNotices() { 78 | return request('/api/notices'); 79 | } 80 | -------------------------------------------------------------------------------- /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 | export function getAuthority() { 3 | return localStorage.getItem('antd-pro-authority') || 'admin'; 4 | } 5 | 6 | export function setAuthority(authority) { 7 | return localStorage.setItem('antd-pro-authority', authority); 8 | } 9 | -------------------------------------------------------------------------------- /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/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 home.e2e.js 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', () => { 34 | startServer.kill(); 35 | }); 36 | } 37 | }); 38 | --------------------------------------------------------------------------------