├── .editorconfig ├── .env ├── .eslintignore ├── .eslintrc ├── .github └── issue_template.md ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .stylelintrc.json ├── .travis.yml ├── .umirc.js ├── LICENSE ├── README-zh_CN.md ├── README.md ├── assets └── standard.md ├── docs ├── .nojekyll ├── API-configuration.md ├── README.md ├── _media │ ├── favicon.ico │ ├── logo.svg │ ├── term_build.svg │ ├── term_i18n.svg │ ├── term_js_build.svg │ └── term_js_i18n.svg ├── _sidebar.md ├── change-log.md ├── configuration.md ├── deploy.md ├── faq.md ├── getting-started.md ├── i18n.md ├── index.html ├── layout.md ├── request.md ├── sw.js └── zh-cn │ ├── API-configuration.md │ ├── README.md │ ├── _sidebar.md │ ├── change-log.md │ ├── configuration.md │ ├── deploy.md │ ├── faq.md │ ├── getting-started.md │ ├── i18n.md │ ├── layout.md │ ├── request.md │ └── router.md ├── jest.config.js ├── manifest.json ├── mock ├── _utils.js ├── dashboard.js ├── post.js ├── route.js └── user.js ├── package.json ├── public ├── america.svg ├── china.svg ├── favicon.ico ├── logo.svg ├── logo │ ├── logo@128.png │ ├── logo@144.png │ ├── logo@152.png │ ├── logo@192.png │ ├── logo@384.png │ ├── logo@512.png │ ├── logo@72.png │ └── logo@96.png └── portugal.svg ├── scripts └── translate.js └── src ├── components ├── DropOption │ ├── DropOption.js │ └── package.json ├── Editor │ ├── Editor.js │ ├── Editor.less │ └── package.json ├── Ellipsis │ ├── index.d.ts │ ├── index.js │ ├── index.less │ ├── index.md │ └── index.test.js ├── FilterItem │ ├── FilterItem.js │ ├── FilterItem.less │ └── package.json ├── GlobalFooter │ ├── index.d.ts │ ├── index.js │ ├── index.less │ └── index.md ├── Layout │ ├── Bread.js │ ├── Bread.less │ ├── Header.js │ ├── Header.less │ ├── Menu.js │ ├── Sider.js │ ├── Sider.less │ └── index.js ├── Loader │ ├── Loader.js │ ├── Loader.less │ └── package.json ├── Page │ ├── Page.js │ ├── Page.less │ └── package.json ├── ScrollBar │ ├── index.js │ └── index.less └── index.js ├── e2e └── login.e2e.js ├── layouts ├── BaseLayout.js ├── BaseLayout.less ├── PrimaryLayout.js ├── PrimaryLayout.less ├── PublicLayout.js └── index.js ├── locales ├── en │ └── messages.json ├── pt-br │ └── messages.json └── zh │ └── messages.json ├── models └── app.js ├── pages ├── 404.less ├── 404.tsx ├── chart │ ├── ECharts │ │ ├── AirportCoordComponent.js │ │ ├── BubbleGradientComponent.js │ │ ├── CalendarComponent.js │ │ ├── ChartAPIComponent.js │ │ ├── ChartShowLoadingComponent.js │ │ ├── ChartWithEventComponent.js │ │ ├── DynamicChartComponent.js │ │ ├── EchartsComponent.js │ │ ├── GCalendarComponent.js │ │ ├── GaugeComponent.js │ │ ├── GraphComponent.js │ │ ├── LiquidfillComponent.js │ │ ├── LunarCalendarComponent.js │ │ ├── MainPageComponent.js │ │ ├── MapChartComponent.js │ │ ├── ModuleLoadChartComponent.js │ │ ├── SimpleChartComponent.js │ │ ├── ThemeChartComponent.js │ │ ├── TransparentBar3DComPonent.js │ │ ├── TreemapComponent.js │ │ ├── index.js │ │ ├── index.less │ │ ├── map │ │ │ └── js │ │ │ │ └── china.js │ │ └── theme │ │ │ ├── macarons.js │ │ │ └── shine.js │ ├── Recharts │ │ ├── AreaChartComponent.js │ │ ├── BarChartComponent.js │ │ ├── Container.js │ │ ├── Container.less │ │ ├── LineChartComponent.js │ │ ├── ReChartsComponent.js │ │ ├── index.js │ │ └── index.less │ └── highCharts │ │ ├── HighChartsComponent.js │ │ ├── HighMoreComponent.js │ │ ├── HighmapsComponent.js │ │ ├── HighstockComponent.js │ │ ├── index.js │ │ ├── index.less │ │ └── mapdata │ │ └── europe.js ├── dashboard │ ├── components │ │ ├── browser.js │ │ ├── browser.less │ │ ├── comments.js │ │ ├── comments.less │ │ ├── completed.js │ │ ├── completed.less │ │ ├── cpu.js │ │ ├── cpu.less │ │ ├── index.js │ │ ├── numberCard.js │ │ ├── numberCard.less │ │ ├── quote.js │ │ ├── quote.less │ │ ├── recentSales.js │ │ ├── recentSales.less │ │ ├── sales.js │ │ ├── sales.less │ │ ├── user-background.png │ │ ├── user.js │ │ ├── user.less │ │ ├── weather.js │ │ └── weather.less │ ├── index.js │ ├── index.less │ ├── model.js │ └── services │ │ ├── dashboard.js │ │ └── weather.js ├── editor │ └── index.js ├── index.js ├── login │ ├── index.js │ ├── index.less │ └── model.js ├── post │ ├── components │ │ ├── List.js │ │ └── List.less │ ├── index.js │ └── model.js ├── request │ ├── index.js │ └── index.less └── user │ ├── [id] │ ├── index.js │ ├── index.less │ └── models │ │ └── detail.js │ ├── components │ ├── Filter.js │ ├── List.js │ ├── List.less │ └── Modal.js │ ├── index.js │ └── model.js ├── plugins └── onError.js ├── services ├── api.js └── index.js ├── themes ├── default.less ├── index.less ├── mixin.less └── vars.less └── utils ├── city.js ├── config.js ├── constant.js ├── iconMap.jsx ├── index.js ├── index.test.js ├── model.js ├── request.js └── theme.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | BROWSER=none 2 | HOST=0.0.0.0 3 | PORT=7000 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/**/*-test.js 2 | src/public 3 | src/routes/chart/ECharts/theme 4 | src/routes/chart/highCharts/mapdata 5 | src/locales/_build/ 6 | src/locales/**/*.js 7 | docs/**/*.js 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app", 3 | "rules": { 4 | "jsx-a11y/href-no-hash": "off", 5 | "no-console": "warn", 6 | "valid-jsdoc": "warn" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | > 请酌情提供细节,并保持issue精简便于查阅 2 | > 1. 功能需求 => 详细说明 3 | > 2. Bug反馈 4 | > 2.1 简单描述下报错 5 | > 2.2 你期望什么结果 6 | > 2.3 如何操作导致的 7 | > 2.4 可提供运行环境信息 8 | > 3. 代码求助 => 可以提,未必及时答复 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | node_modules 4 | npm-debug.log 5 | yarn-error.log 6 | yarn.lock 7 | package-lock.json 8 | 9 | # ide 10 | .idea 11 | 12 | # Mac General 13 | .DS_Store 14 | .AppleDouble 15 | .LSOverride 16 | 17 | # umi 18 | .umi 19 | .umi-production 20 | 21 | # jslingui 22 | src/locales/_build 23 | src/locales/**/*.js -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.svg 2 | *.ejs 3 | .DS_Store 4 | .umi 5 | .umi-production 6 | src/locales/**/*.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard", "stylelint-config-prettier"], 3 | "rules": { 4 | "declaration-empty-line-before": null, 5 | "no-descending-specificity": null, 6 | "selector-pseudo-class-no-unknown": null, 7 | "selector-pseudo-element-colon-notation": null 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | script: 5 | - npm run build 6 | before_install: 7 | - yarn global add now 8 | after_script: 9 | - | 10 | if [[ $TRAVIS_BRANCH == 'master' ]]; then 11 | echo $PROD_SITE_NOW_CONFIG >> dist/vercel.json && echo $PROD_DOC_NOW_CONFIG >> docs/vercel.json; 12 | else 13 | echo $DEV_SITE_NOW_CONFIG >> dist/vercel.json && echo $DEV_DOC_NOW_CONFIG >> docs/vercel.json; 14 | fi 15 | - cd ./dist 16 | - now -A vercel.json -t $NOW_TOKEN --prod -c 17 | - cd ../docs 18 | - now -A vercel.json -t $NOW_TOKEN --prod -c 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright (c) 2016 zuiidea (zuiiidea@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /assets/standard.md: -------------------------------------------------------------------------------- 1 | ### 开发环境 2 | 3 | - OS:Windows 4 | - 编辑器:Atom 5 | - cmd:Cmder 6 | 7 | ### Atom 插件 8 | 9 | - [atom-beautify](https://atom.io/packages/atom-beautify) 10 | 11 | #### Less 12 | 13 | - [x] Beautify On Save 14 | 15 | #### Javascript 16 | 17 | - [ ] Disable Beautifying Language 18 | 19 | #### Markdown 20 | 21 | - [x] Beautify On Save 22 | - [x] Default Beautifier:Remark 23 | 24 | #### HTML 25 | 26 | - [x] Beautify On Save 27 | 28 | - [linter](https://atom.io/packages/linter) 29 | - [ ] Lint on Change 30 | - [linter-eslint](https://atom.io/packages/linter-eslint) 31 | - [x] Fix errors on save 32 | 33 | ### Cmder 主题 34 | 35 | - [Panda-Theme-Cmder](https://github.com/HamidFaraji/Panda-Theme-Cmder) 36 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zuiidea/antd-admin/67fc31a00892215e2d9971a91aa300e33ba48321/docs/.nojekyll -------------------------------------------------------------------------------- /docs/API-configuration.md: -------------------------------------------------------------------------------- 1 | # API configuration 2 | 3 | ## Why 4 | 5 | In the use of `redux` or `dva` projects, we often write functions like the following `service` layer to make the code structure clearer, but it is easy to see that we will write a lot of similar code in `antd -admin@5.0`, using the more concise configuration method to achieve the same function. 6 | 7 | ```javascript 8 | export async function login(data) { 9 |   return request({ 10 |     url: '/api/v1/user/login', 11 |     method: 'post', 12 |     data, 13 |   }) 14 | } 15 | ``` 16 | 17 | ## Configuration and use 18 | 19 | In the `src/services/api.js` file, you will see the following configuration object, the object's key is used to call the function name, the object's value is the requested `url`, the default request method is `GET`, The format of the value of the other request mode object is `'method url'`. 20 | 21 | ```javascript 22 | export default { 23 |   ... 24 |   queryUser: '/user/:id', 25 |   queryUserList: '/users', 26 |   updateUser: 'Patch /user/:id', 27 |   createUser: 'POST /user/:id', 28 |   removeUser: 'DELETE /user/:id', 29 |   removeUserList: 'POST /users/delete', 30 |   ... 31 | } 32 | ``` 33 | 34 | Used in other files 35 | 36 | ```javascript 37 | import { queryUser } from 'api' 38 | 39 | // in the general file 40 | ... 41 | queryUser(option).then(data => console.log(data)) 42 | ... 43 | 44 | / / Model file 45 | ... 46 | yield call(queryUser, option) 47 | ... 48 | ``` 49 | 50 | ## Method to realize 51 | 52 | Refer to the `src/services/index.js` file to traverse the api configuration. Each property returns the corresponding encapsulated request function. 53 | 54 | ```javascript 55 | import request from 'utils/request' 56 | import { apiPrefix } from 'utils/config' 57 | 58 | import api from './api' 59 | 60 | const gen = params => { 61 |   let url = apiPrefix + params 62 |   let method = 'GET' 63 | 64 |   const paramsArray = params.split(' ') 65 |   if (paramsArray.length === 2) { 66 |     method = paramsArray[0] 67 |     url = apiPrefix + paramsArray[1] 68 |   } 69 | 70 |   return function(data) { 71 |     return request({ 72 |       url, 73 |       data, 74 |       method, 75 |     }) 76 |   } 77 | } 78 | 79 | const APIFunction = {} 80 | for (const key in api) { 81 |   APIFunction[key] = gen(api[key]) 82 | } 83 | 84 | module.exports = APIFunction 85 | 86 | ``` -------------------------------------------------------------------------------- /docs/_media/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zuiidea/antd-admin/67fc31a00892215e2d9971a91aa300e33ba48321/docs/_media/favicon.ico -------------------------------------------------------------------------------- /docs/_media/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Created with Sketch. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | - Getting started 2 | - [Quick Start](getting-started.md) 3 | - Customization 4 | - [Configuration](configuration.md) 5 | - [API Configuration](API-configuration.md) 6 | - [I18n](i18n.md) 7 | - [Layout](layout.md) 8 | - [Request](request.md) 9 | - Guide 10 | - [Deploy](deploy.md) 11 | - [Change Log](change-log.md) 12 | - [FAQ](faq.md) 13 | -------------------------------------------------------------------------------- /docs/change-log.md: -------------------------------------------------------------------------------- 1 | ## 5.0.0 2 | 3 | #### Optimization 4 | 5 | - Try to use decorators to simplify code writing and improve code readability. 6 | 7 | - API configurization to simplify the way data is obtained. 8 | 9 | - The files in `utils` are split and each has its own role. 10 | 11 | - Simplify the `utils/request` file without special handling. 12 | 13 | #### Specification 14 | 15 | - Functions add comments, parameters, return values, etc., ambiguous code adds comments, canonical reference [Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html#appendices-jsdoc-tag-reference). 16 |    17 | - Semantic version number, specification participation [semantic version 2.0.0](https://semver.org/lang/zh-CN/). 18 | 19 | - Static code checking, unified code style, will use `prettier`, `stylelint`, `eslint` specification code before code submission. 20 | 21 | - Git submits information normalization, [git-commit-emoji-cn](https://github.com/liuchengxu/git-commit-emoji-cn). 22 | 23 | - Based on the pre-defined routing of `Umi`, there is no need to write a routing configuration file. 24 | 25 | - Use `React 16` new features such as `Fragment`, `Context`, `PureComponent`, etc. 26 | 27 | #### Features 28 | 29 | - Support internationalization, extract source fields from source code, load language packs on demand, and automatically translate online. 30 | 31 | - Support for the introduction `lodash` functions on demand. 32 |    33 | - Support multiple layouts, which rules can be used according to the rules. 34 | 35 | - Support Antd Admin to automatically compile and deploy on Travis. 36 | 37 | - Generate a documentation website using `Docsify`. 38 | 39 | 40 | #### Style 41 | 42 | - Added Antd Admin standalone Logo. 43 | 44 | - Rewrite the overall layout component, optimize the menu, automatic breadcrumb navigation, menu auto-expansion and other logic. 45 | 46 | - The mobile menu is changed to drawer. 47 | 48 | #### Other 49 | 50 | - Discard components such as `IconFont`, `Search`, `DataTable` because they are well supported and replaceable in `Antd`. -------------------------------------------------------------------------------- /docs/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | You can do some custom configuration in `/src/utils/config.js`: 4 | 5 | ## siteName 6 | 7 | - Type `String` 8 | 9 | Configure the site name, apply it to the login box, and display the title text at the top of the sidebar. 10 | 11 | ## copyright 12 | 13 | - Type: `String` 14 | 15 | Configure the copyright notice to apply to the login page, at the bottom of the `Primary` layout. 16 | 17 | ## logoPath 18 | 19 | - Type: `String` 20 | 21 | Configure the site logo to apply to the login box and the Logo display at the top of the sidebar. 22 | 23 | ## apiPrefix 24 | 25 | - Type: `String` 26 | 27 | Configure the prefix of the interface in the project. The interface related documents can be viewed [API configuration](API-configuration.md) 28 | 29 | ## fixedHeader 30 | 31 | - Type: `String` 32 | 33 | Under the `Primary` layout, whether the top of the page is fixed when scrolling。 34 | 35 | ## layouts 36 | 37 | - Type: `Array` 38 | 39 | Configuration? Which routes use which layout, unspecified route uses the default layout `Public`, the project currently has `Primary` and `Public` layouts, 40 |      The default configuration is as follows: 41 | 42 | ```javascript 43 | layouts: [ 44 | { 45 | name: 'primary', 46 | include: [/.*/], 47 | exclude: [/(\/(en|zh))*\/login/], 48 | }, 49 | ], 50 | ``` 51 | 52 | The object properties for each layout are as follows: 53 | 54 | - `name` - The name of the layout; 55 | 56 | - `include` - Specifies a list of routing rules that use this layout, which can be a regular expression or a string; 57 | 58 | - `exclude` - Specifies a list of routing rules that do not use this layout, which can be a regular expression or a string. 59 | 60 | > Note: `exclude` takes precedence over `include`, and the layout previous it has a higher priority than the behind layout. The development process may need to be combined with the layout in the `src/layouts` directory. For details, see [Using Layout](./layout.md). 61 | 62 | ## i18n 63 | 64 | - Type: `Object` 65 | 66 | Configure internationalization, the default configuration is as follows: 67 | 68 | ```javascript 69 | i18n: { 70 | languages: [ 71 | { 72 | key: 'en', 73 | title: 'English', 74 | flag: '/america.svg', 75 | }, 76 | { 77 | key: 'zh', 78 | title: '中文', 79 | flag: '/china.svg', 80 | }, 81 | ], 82 | defaultLanguage: 'en', 83 | } 84 | ``` 85 | 86 | ### i18n.languages 87 | 88 | - Type: `Array` 89 | 90 | Specify which languages the app supports, and the object properties for each language are as follows: 91 | 92 | - `key` - The `key` of the language is applied to the page url to distinguish the language, and also corresponds to the language package folder name in the `src/locales` directory; 93 | 94 | - `title` - The name of the language, at the bottom of the login page, at the top of the `Primary` layout, the language switch is displayed; 95 | 96 | - `flag` - The path of the flag icon of the language, the language switching display at the top of the `Primary` layout. 97 | 98 | ### i18n.defaultLanguage 99 | 100 | - Type: `String` 101 | 102 | Configure the default language. 103 | -------------------------------------------------------------------------------- /docs/deploy.md: -------------------------------------------------------------------------------- 1 | # Deploy 2 | 3 | After the development is completed and verified in the development environment, it needs to be deployed to our users. 4 | 5 | ![i18n](./_media/term_build.svg) 6 | 7 | ## Build 8 | 9 | First execute the following command, 10 | 11 | ```bash 12 | npm run build 13 | ``` 14 | 15 | After a few seconds, the output should look like this: 16 | 17 | ```bash 18 | > antd-admin@5.0.0-beta build /Users/zuiidea/web/antd-admin 19 | > umi build 20 | 21 | [21:13:17] webpack compiled in 43s 868ms 22 | DONE Compiled successfully in 43877ms 21:13:17 23 | 24 | File sizes after gzip: 25 | 26 | 1.3 MB dist/vendors.async.js 27 | 308.21 KB dist/umi.js 28 | 45.49 KB dist/vendors.chunk.css 29 | 36.08 KB dist/p__chart__highCharts__index.async.js 30 | 33.53 KB dist/p__user__index.async.js 31 | 22.36 KB dist/p__chart__ECharts__index.async.js 32 | 4.21 KB dist/p__dashboard__index.async.js 33 | 4.06 KB dist/umi.css 34 | ... 35 | ``` 36 | 37 | The `build` command will package all resources, including JavaScript, CSS, web fonts, images, html, and more. You can find these files in the `dist/` directory. 38 | 39 | > If you have requirements for using HashHistory , deploying html to non-root directories, statics, etc., check out [Umi Deployment] (https://umijs.org/en/guide/deploy.html). 40 | 41 | ## Local verification 42 | 43 | 44 | Local verification can be done via `serve` before publishing. 45 | 46 | ``` 47 | $ yarn global add serve 48 | $ serve ./dist 49 | 50 | Serving! 51 | 52 | - Local: http://localhost:5000 53 | - On Your Network: http://{Your IP}:5000 54 | 55 | Copied local address to clipboard! 56 | 57 | ``` 58 | 59 | Access [http://localhost:5000](http://localhost:5000), under normal circumstances, it should be consistent with `npm start` (The API may not get the correct data). 60 | 61 | 62 | ## Deploy 63 | 64 | Next, we can upload the static file to the server. If you use Nginx as the Web server, you can configure it in `ngnix.conf`: 65 | ``` 66 | server 67 | { 68 | listen 80; 69 | # Specify an accessible domain name 70 | server_name antd-admin.zuiidea.com; 71 | # The directory where the compiled files are stored 72 | root /home/www/antd-admin/dist; 73 | 74 | # Proxy server interface to avoid cross-domain 75 | location /api { 76 | proxy_pass http://localhost:7000/api; 77 | } 78 | 79 | Because the front end uses BrowserHistory, it will route backback to index.html 80 | location / { 81 | index index.html; 82 | try_files $uri $uri/ /index.html; 83 | } 84 | } 85 | ``` 86 | 87 | Restart the web server and access [http://antd-admin.zuiidea.com](http://antd-admin.zuiidea.com) , You will see the correct page. 88 | 89 | ```bash 90 | nginx -s reload 91 | ``` 92 | 93 | Similarly, if you use Caddy as a web server, you can do this in `Caddyfile`: 94 | 95 | ``` 96 | antd-admin.zuiidea.com { 97 | gzip 98 | root /home/www/antd-admin/dist 99 | proxy /api http://localhost:7000 100 | 101 | rewrite { 102 | if {path} not_match ^/api 103 | to {path} {path}/ / 104 | } 105 | } 106 | 107 | 108 | antd-admin.zuiidea.com/public { 109 | gzip 110 | root /home/www/antd-admin/dist/static/public 111 | } 112 | 113 | ``` 114 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | Most asked 4 | 5 | ## create new page 6 | 7 | 1. just copy a page in /src/pages (route here auto generated by [umi](https://umijs.org/guide/router.html#basic-routing)) 8 | 2. modify namespace/pathToRegexp in model.js 9 | 3. modify mock route.js to add a route -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Quick Start 2 | 3 | > Before delving into Ant Design React, a good knowledge base of [React](http://facebook.github.io/react/) 、 [ES2015+](http://es6.ruanyifeng.com/) 、 [Antd Design](https://ant.design/docs/react/introduce-cn) . Learn about [UmiJS](https://umijs.org/) , [Dva](http://github.com/dvajs/dva) . And properly installed and configured [Node.js](https://nodejs.org/) v8 or above, [Git](https://git-scm.com/). It would be helpful if you have pre-existing knowledge on those. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | git clone https://github.com/zuiidea/antd-admin.git my-project 9 | cd my-project 10 | ``` 11 | 12 | ## Scaffolding 13 | 14 | The project layout is as follows: 15 | 16 | ```bash 17 | ├── dist/ # Default build output directory 18 | ├── mock/ # Mock files 19 | ├── public/ # Static resource 20 | ├── src/ # Source code 21 | │ ├── components/ # Components 22 | │ ├── e2e/ # Integrated Test Case 23 | │ ├── layouts/ # Common Layouts 24 | │ ├── locales/ # i18n resources 25 | │ ├── models/ # Global dva Model 26 | │ ├── pages/ # Sub-pages and templates 27 | │ ├── services/ # Backend Services 28 | │ │ ├── api.js # API configuration 29 | │ │ └── index.js # API export 30 | │ ├── themes/ # Themes 31 | │ │ ├── default.less # Less variable 32 | │ │ ├── index.less # Global style 33 | │ │ ├── mixin.less # Less mixin 34 | │ │ └── vars.less # Less variable and mixin 35 | │ ├── utils/ # Utility 36 | │ │ ├── config.js # Application configuration 37 | │ │ ├── constant.js # Static constant 38 | │ │ ├── index.js # Utility methods 39 | │ │ ├── request.js # Request function(axios) 40 | │ │ └── theme.js # Style variables used in js 41 | ├── .editorconfig 42 | ├── .env 43 | ├── .eslintrc 44 | ├── .gitignore 45 | ├── .prettierignore 46 | ├── .prettierrc 47 | ├── .stylelintrc.json 48 | ├── .travis.yml 49 | └── .umirc.js 50 | └── package.json 51 | ``` 52 | 53 | ## Development 54 | 55 | 1. Install Dependencies. 56 | 57 | ```bash 58 | yarn install 59 | ``` 60 | 61 | Or 62 | 63 | ```bash 64 | npm install 65 | ``` 66 | 67 | 2. Start local server. 68 | 69 | ```bash 70 | npm run start 71 | ``` 72 | 73 | 3. After the startup is complete, open a browser and visit [http://localhost:7000](http://localhost:7000), If you need to change the startup port, you can configure it in the `.env` file. 74 | -------------------------------------------------------------------------------- /docs/i18n.md: -------------------------------------------------------------------------------- 1 | # globalization 2 | 3 | ## Add language 4 | 5 | Take Japanese as an example. 6 | 7 | ![i18n](../_media/term_i18n.svg) 8 | 9 | 1. Add a language pack local file, `ja` is the Japanese language code, and a list of languages ​​that support translation [有道智云](http://ai.youdao.com/docs/doc-trans-api.s#p05), the `src/locales/ja/messages.json` file will be generated after running the following command. 10 | 11 | ```bash  12 | npm run add-locale ja 13 | ``` 14 | 15 | 2. Extract the fields in the code that need to be translated, ie `?message`, `` t`message `` in the `message` field, run the following command after `src/locales/ja /messages.json` will appear after the extracted field configuration. 16 | 17 | ```bash  18 | npm run extract 19 | ``` 20 | 21 | You will see the following information: 22 | 23 | ```bash 24 | Catalog statistics: 25 | ┌─────────────┬─────────────┬─────────┐ 26 | │ Language │ Total count │ Missing │ 27 | ├─────────────┼─────────────┼─────────┤ 28 | │ en (source) │ 52 │ - │ 29 | │ ja │ 52 │ 52 │ 30 | │ zh │ 52 │ 0 │ 31 | └─────────────┴─────────────┴─────────┘ 32 | ``` 33 | 34 | 3. At the same time, we have added the relevant configuration in `src/utils/config.js`. 35 | 36 | ```javascript 37 | { 38 | ... 39 | i18n: { 40 | languages: [ 41 | ... 42 | { 43 | key:'ja', 44 | title: '日本語', 45 | flag: '/japanese.svg', 46 | }, 47 | ], 48 | }, 49 | } 50 | ``` 51 | 52 | > Routing related effects, after the configuration `npm run start` takes effect after restart. 53 | 54 | 4. Use the built-in commands for automatic translation. You will see the translated configuration in `src/locales/ja/messages.json`. 55 | 56 | ```bash 57 | npm run trans:only 58 | ``` 59 | 60 | You will see the following information: 61 | 62 | ```bash 63 | start: en -> ja 64 | ... 65 | youdao: en -> ja: Unpublished -> 未発表 66 | youdao: en -> ja: Update -> 更新 67 | youdao: en -> ja: Update User -> ユーザーの更新 68 | youdao: en -> ja: Username -> 名 69 | ... 70 | All translations have been completed. 71 | ``` 72 | 73 | > `npm run trans` will execute `npm run extract` and `npm run trans:only` in order. 74 | 75 | 5. Finally, you can adjust the inaccurate fields in `src/locales/ja/messages.json`. Start the development mode `npm run start`, open [http://localhost:7000/ja/login](http://localhost:7000/ja/login) and you will see the Japanese version of the app. 76 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | antd-admin - An admin dashboard application demo built upon Ant Design and UmiJS 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 |
Loading...
20 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /docs/layout.md: -------------------------------------------------------------------------------- 1 | # Layout 2 | 3 | ## Add a new layout 4 | 5 | Take a new layout named `secondary` as an example to make the route starting with `secondary` use this layout. 6 | 7 | 1. Add related configuration in `src/utils/config.js`. For details, please refer to [layouts](/configuration?id=layouts). 8 | 9 | ```javascript 10 |    layouts: [ 11 |            { 12 |                name: 'primary', 13 |                include: [/.*/], 14 |                exclude: [/(\/(en|zh))*\/login/, /(\/(en|zh))*\/secondary\/(.*)/], 15 |            }, 16 |            { 17 |                name: 'secondary', 18 |                include: [/(\/(en|zh))*\/secondary\/(.*)/], 19 |            }, 20 |    ], 21 | ``` 22 | 23 | 2. Add the `secondary` layout component to the `src/layouts/BaseLayout.js` file. 24 | 25 | ```javascript 26 |    import SecondaryLayout from './SecondaryLayout' 27 | 28 |    const LayoutMap = { 29 |      primary: PrimaryLayout, 30 |      public: PublicLayout, 31 |      secondary: SecondaryLayout, 32 |    } 33 | ``` 34 | 35 | 3. Add the `SecondaryLayout.js` file to the `src/layouts/` directory. 36 | 37 | ```javascript 38 |    import React from 'react' 39 | 40 |    export default ({ children }) => { 41 |      return ( 42 |        
43 |          

Secondary

44 |          {children} 45 |        
46 |      ) 47 |    } 48 | ``` 49 | 50 | 4. Add a `secondary/index.js` file to the `src/pages/` directory. 51 | 52 | ```javascript 53 |    import React from 'react' 54 | 55 |    export default ({ children }) => { 56 |      Return
Secondary page Content
57 |    } 58 | ``` 59 | 60 | 5. Finally, start the development mode `npm run start`, open [http://localhost:7000/secondary/](http://localhost:7000/secondary/) and you will see the page for the `secondary` layout. 61 | -------------------------------------------------------------------------------- /docs/request.md: -------------------------------------------------------------------------------- 1 | # HTTP request 2 | 3 | this project use axios for http service, file located in src/utils/request.js 4 | 5 | ## Custom Header 6 | 7 | As for privilege access or modify cookie, you could add header param by yourself 8 | 9 | ``` 10 | axios.defaults.headers.common['Authorization'] = 'token' 11 | ``` 12 | 13 | Or 14 | 15 | ``` 16 | axios.interceptors.request.use(function (config) { 17 | config.headers.token = window.localStorage.getItem('token'); 18 | return config; 19 | }, function (error) { 20 | return Promise.reject(error); 21 | }); 22 | ``` -------------------------------------------------------------------------------- /docs/sw.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * docsify sw.js 3 | * =========================================================== 4 | * Copyright 2016 @huxpro 5 | * Licensed under Apache 2.0 6 | * Register service worker. 7 | * ========================================================== */ 8 | 9 | const RUNTIME = 'docsify' 10 | const HOSTNAME_WHITELIST = [ 11 | self.location.hostname, 12 | 'fonts.gstatic.com', 13 | 'fonts.googleapis.com', 14 | 'unpkg.com' 15 | ] 16 | 17 | // The Util Function to hack URLs of intercepted requests 18 | const getFixedUrl = (req) => { 19 | var now = Date.now() 20 | var url = new URL(req.url) 21 | 22 | // 1. fixed http URL 23 | // Just keep syncing with location.protocol 24 | // fetch(httpURL) belongs to active mixed content. 25 | // And fetch(httpRequest) is not supported yet. 26 | url.protocol = self.location.protocol 27 | 28 | // 2. add query for caching-busting. 29 | // Github Pages served with Cache-Control: max-age=600 30 | // max-age on mutable content is error-prone, with SW life of bugs can even extend. 31 | // Until cache mode of Fetch API landed, we have to workaround cache-busting with query string. 32 | // Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190 33 | if (url.hostname === self.location.hostname) { 34 | url.search += (url.search ? '&' : '?') + 'cache-bust=' + now 35 | } 36 | return url.href 37 | } 38 | 39 | /** 40 | * @Lifecycle Activate 41 | * New one activated when old isnt being used. 42 | * 43 | * waitUntil(): activating ====> activated 44 | */ 45 | self.addEventListener('activate', event => { 46 | event.waitUntil(self.clients.claim()) 47 | }) 48 | 49 | /** 50 | * @Functional Fetch 51 | * All network requests are being intercepted here. 52 | * 53 | * void respondWith(Promise r) 54 | */ 55 | self.addEventListener('fetch', event => { 56 | // Skip some of cross-origin requests, like those for Google Analytics. 57 | if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) { 58 | // Stale-while-revalidate 59 | // similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale 60 | // Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1 61 | const cached = caches.match(event.request) 62 | const fixedUrl = getFixedUrl(event.request) 63 | const fetched = fetch(fixedUrl, { cache: 'no-store' }) 64 | const fetchedCopy = fetched.then(resp => resp.clone()) 65 | 66 | // Call respondWith() with whatever we get first. 67 | // If the fetch fails (e.g disconnected), wait for the cache. 68 | // If there’s nothing in cache, wait for the fetch. 69 | // If neither yields a response, return offline pages. 70 | event.respondWith( 71 | Promise.race([fetched.catch(_ => cached), cached]) 72 | .then(resp => resp || fetched) 73 | .catch(_ => { /* eat any errors */ }) 74 | ) 75 | 76 | // Update the cache with the version we fetched (only for ok status) 77 | event.waitUntil( 78 | Promise.all([fetchedCopy, caches.open(RUNTIME)]) 79 | .then(([response, cache]) => response.ok && cache.put(event.request, response)) 80 | .catch(_ => { /* eat any errors */ }) 81 | ) 82 | } 83 | }) -------------------------------------------------------------------------------- /docs/zh-cn/API-configuration.md: -------------------------------------------------------------------------------- 1 | # 接口配置 2 | 3 | ## 为什么 4 | 5 | 在使用了`redux`或者`dva`项目中,我们经常会写类似下面的`service`层的函数,使代码结构更清晰,但是很容易看出,我们会写很多相似的代码,在`antd-admin@5.0`中,使用了更加简洁的配置方式实现了相同的功能。 6 | 7 | ```javascript 8 | export async function login(data) { 9 | return request({ 10 | url: '/api/v1/user/login', 11 | method: 'post', 12 | data, 13 | }) 14 | } 15 | ``` 16 | 17 | ## 配置和使用 18 | 19 | 在`src/services/api.js`文件中,你会看到如下配置对象,对象的键用于调用时的函数名称,对象的值为请求的`url`,默认请求方式为`GET`,如果是其他请求方式对象的值的格式则为`'method url'`。 20 | 21 | ```javascript 22 | export default { 23 | ... 24 | queryUser: '/user/:id', 25 | queryUserList: '/users', 26 | updateUser: 'Patch /user/:id', 27 | createUser: 'POST /user/:id', 28 | removeUser: 'DELETE /user/:id', 29 | removeUserList: 'POST /users/delete', 30 | ... 31 | } 32 | ``` 33 | 34 | 在其他文件中使用 35 | 36 | ```javascript 37 | import { queryUser } from 'api' 38 | 39 | // 一般文件中 40 | ... 41 | queryUser(option).then(data => console.log(data)) 42 | ... 43 | 44 | // model文件中 45 | ... 46 | yield call(queryUser, option) 47 | ... 48 | ``` 49 | 50 | ## 实现方式 51 | 52 | 参考`src/services/index.js`文件,对api配置进行遍历,每个属性都返回对应的封装后的request函数。 53 | 54 | ```javascript 55 | import request from 'utils/request' 56 | import { apiPrefix } from 'utils/config' 57 | 58 | import api from './api' 59 | 60 | const gen = params => { 61 | let url = apiPrefix + params 62 | let method = 'GET' 63 | 64 | const paramsArray = params.split(' ') 65 | if (paramsArray.length === 2) { 66 | method = paramsArray[0] 67 | url = apiPrefix + paramsArray[1] 68 | } 69 | 70 | return function(data) { 71 | return request({ 72 | url, 73 | data, 74 | method, 75 | }) 76 | } 77 | } 78 | 79 | const APIFunction = {} 80 | for (const key in api) { 81 | APIFunction[key] = gen(api[key]) 82 | } 83 | 84 | module.exports = APIFunction 85 | 86 | ``` -------------------------------------------------------------------------------- /docs/zh-cn/_sidebar.md: -------------------------------------------------------------------------------- 1 | - 入门 2 | - [快速上手](zh-cn/getting-started.md) 3 | - 定制化 4 | - [配置项](zh-cn/configuration.md) 5 | - [接口配置](zh-cn/API-configuration.md) 6 | - [国际化](zh-cn/i18n.md) 7 | - [布局](zh-cn/layout.md) 8 | - [http 请求](zh-cn/request.md) 9 | - 指南 10 | - [部署](zh-cn/deploy.md) 11 | - [更新日志](zh-cn/change-log.md) 12 | -------------------------------------------------------------------------------- /docs/zh-cn/change-log.md: -------------------------------------------------------------------------------- 1 | ## 5.0.0 2 | 3 | #### 优化 4 | 5 | - 尽量使用修饰器,简化代码编写,提高代码可读性。 6 | 7 | - API 配置化,简化获取数据方式。 8 | 9 | - `utils` 内文件拆分,各司其职。 10 | 11 | - 简化`utils/request`文件,不做特殊处理。 12 | 13 | #### 规范 14 | 15 | - 函数添加描述、参数、返回值等注释,含糊不清的代码增加注释,规范参考 [Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html#appendices-jsdoc-tag-reference)。 16 | 17 | - 语义化版本号,规范参加 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/)。 18 | 19 | - 静态代码检查,统一代码风格,代码提交前将会使用 `prettier`、`stylelint`、`eslint` 规范代码。 20 | 21 | - Git 提交信息规范化,[git-commit-emoji-cn](https://github.com/liuchengxu/git-commit-emoji-cn)。 22 | 23 | - 基于 `Umi` 的约定式路由,无需再写路由配置文件。 24 | 25 | - 使用 `React 16` 新特性,如 `Fragment`、`Context`、 `PureComponent`等。 26 | 27 | #### 功能 28 | 29 | - 支持国际化,源码中抽离翻译字段,按需加载语言包,自动在线翻译。 30 | 31 | - 支持按需引入 `lodash` 函数。 32 | 33 | - 支持多布局,可根据规则规定哪些路由使用哪种布局。 34 | 35 | - 支持 Antd Admin 在 Travis 上自动编译和部署。 36 | 37 | - 使用 `Docsify` 生成文档网站。 38 | 39 | 40 | #### 样式 41 | 42 | - 新增 Antd Admin 独立 Logo。 43 | 44 | - 重写整体布局组件,优化菜单、面包屑导航自动高亮,菜单自动展开等逻辑。 45 | 46 | - 移动端菜单更改为抽屉式。 47 | 48 | #### 其他 49 | 50 | - 废弃 `IconFont`、 `Search`、`DataTable`等组件,因为在 `Antd` 中有很好的支持和可替代的。 51 | 52 | 53 | -------------------------------------------------------------------------------- /docs/zh-cn/configuration.md: -------------------------------------------------------------------------------- 1 | # 配置项 2 | 3 | 你可以在 `/src/utils/config.js` 里做一些自定义配置: 4 | 5 | ## siteName 6 | 7 | - 类型: `String` 8 | 9 | 配置站点名称,应用到登录框,侧边栏顶部的标题文字显示。 10 | 11 | ## copyright 12 | 13 | - 类型: `String` 14 | 15 | 配置版权声明,应用到登录页、`Primay`布局底部。 16 | 17 | ## logoPath 18 | 19 | - 类型: `String` 20 | 21 | 配置站点 Logo,应用到登录框,侧边栏顶部的 Logo 显示。 22 | 23 | ## apiPrefix 24 | 25 | - 类型: `String` 26 | 27 | 配置项目中接口的前缀,接口相关文档可查看 [接口配置](API-configuration.md) 28 | 29 | ## fixedHeader 30 | 31 | - 类型: `String` 32 | 33 | 在`Primary`布局下,页面滚动时是否固定顶部。 34 | 35 | ## layouts 36 | 37 | - 类型: `Array` 38 | 39 | 配置哪些路由使用哪种布局,未指定路由使用默认布局 `Public`,项目中目前有 `Primary` 和 `Public` 两种布局, 40 | 默认配置如下: 41 | 42 | ```javascript 43 | layouts: [ 44 | { 45 | name: 'primary', 46 | include: [/.*/], 47 | exclude: [/(\/(en|zh))*\/login/], 48 | }, 49 | ], 50 | ``` 51 | 52 | 每种布局的对象属性如下: 53 | 54 | - `name` - 布局的名称; 55 | 56 | - `include` - 指定使用该布局的路由规则列表,规则可为正则表达式或者字符串; 57 | 58 | - `exclude` - 指定不使用该布局的路由规则列表,规则可为正则表达式或者字符串。 59 | 60 | > 注意:`exclude` 优先级高于 `include`,前面的布局优先级高于后面的布局。开发过程中可能需要结合`src/layouts`目录下的布局使用,具体方法可查看 [使用布局](./layout.md)。 61 | 62 | ## i18n 63 | 64 | - 类型: `Object` 65 | 66 | 配置国际化,默认配置如下: 67 | 68 | ```javascript 69 | i18n: { 70 | languages: [ 71 | { 72 | key: 'en', 73 | title: 'English', 74 | flag: '/america.svg', 75 | }, 76 | { 77 | key: 'zh', 78 | title: '中文', 79 | flag: '/china.svg', 80 | }, 81 | ], 82 | defaultLanguage: 'en', 83 | } 84 | ``` 85 | 86 | ### i18n.languages 87 | 88 | - 类型: `Array` 89 | 90 | 指定应用支持哪些语言,每种语言的对象属性如下: 91 | 92 | - `key` - 语言的`key`,应用到页面 url 上以区分语言,也对应 `src/locales` 目录下的语言包文件夹名; 93 | 94 | - `title` - 语言名称,在登录页底部、`Primay` 布局顶部语言切换显示; 95 | 96 | - `flag` - 语言的国旗图标的路径,在 `Primay` 布局顶部语言切换显示。 97 | 98 | ### i18n.defaultLanguage 99 | 100 | - 类型: `String` 101 | 102 | 配置默认语言。 103 | -------------------------------------------------------------------------------- /docs/zh-cn/deploy.md: -------------------------------------------------------------------------------- 1 | # 部署 2 | 3 | 完成开发并且在开发环境验证之后,就需要部署给我们的用户了。 4 | 5 | ![i18n](../_media/term_build.svg) 6 | 7 | ## 构建 8 | 9 | 先执行下面的命令, 10 | 11 | ```bash 12 | npm run build 13 | ``` 14 | 15 | 几秒后,输出应该如下: 16 | 17 | ```bash 18 | > antd-admin@5.0.0-beta build /Users/zuiidea/web/antd-admin 19 | > umi build 20 | 21 | [21:13:17] webpack compiled in 43s 868ms 22 | DONE Compiled successfully in 43877ms 21:13:17 23 | 24 | File sizes after gzip: 25 | 26 | 1.3 MB dist/vendors.async.js 27 | 308.21 KB dist/umi.js 28 | 45.49 KB dist/vendors.chunk.css 29 | 36.08 KB dist/p__chart__highCharts__index.async.js 30 | 33.53 KB dist/p__user__index.async.js 31 | 22.36 KB dist/p__chart__ECharts__index.async.js 32 | 4.21 KB dist/p__dashboard__index.async.js 33 | 4.06 KB dist/umi.css 34 | ... 35 | ``` 36 | 37 | `build` 命令会打包所有的资源,包含 JavaScript, CSS, web fonts, images, html 等。你可以在 `dist/` 目录下找到这些文件。 38 | 39 | > 如果有使用 HashHistory 、 部署 html 到非根目录、静态化等需求,请查看[Umi 部署](https://umijs.org/zh/guide/deploy.html)。 40 | 41 | ## 本地验证 42 | 43 | 44 | 发布之前,可以通过 `serve` 做本地验证, 45 | 46 | ``` 47 | $ yarn global add serve 48 | $ serve ./dist 49 | 50 | Serving! 51 | 52 | - Local: http://localhost:5000 53 | - On Your Network: http://{Your IP}:5000 54 | 55 | Copied local address to clipboard! 56 | 57 | ``` 58 | 59 | 访问 [http://localhost:5000](http://localhost:5000),正常情况下法应该是和 `npm start` 一致的(接口可能无法获取到正确数据)。 60 | 61 | 62 | ## 部署 63 | 64 | 接下来,我们可以把静态文件上传到服务器,如果使用 Nginx 作为 Web server,你可以在 `ngnix.conf` 中这样配置: 65 | 66 | ``` 67 | server 68 | { 69 | listen 80; 70 | # 指定可访问的域名 71 | server_name antd-admin.zuiidea.com; 72 | # 编译后的文件存放的目录 73 | root /home/www/antd-admin/dist; 74 | 75 | # 代理服务端接口,避免跨域 76 | location /api { 77 | proxy_pass http://localhost:7000/api; 78 | } 79 | 80 | # 因为前端使用了BrowserHistory,所以将路由 fallback 到 index.html 81 | location / { 82 | index index.html; 83 | try_files $uri $uri/ /index.html; 84 | } 85 | } 86 | ``` 87 | 88 | 重启 Web server,访问 [http://antd-admin.zuiidea.com](http://antd-admin.zuiidea.com) ,你将看到正确的页面。 89 | 90 | ```bash 91 | nginx -s reload 92 | ``` 93 | 94 | 类似的,如果你使用 Caddy 作为 Web server,你可以在 `Caddyfile` 中这样配置: 95 | 96 | ``` 97 | antd-admin.zuiidea.com { 98 | gzip 99 | root /home/www/antd-admin/dist 100 | proxy /api http://localhost:7000 101 | 102 | rewrite { 103 | if {path} not_match ^/api 104 | to {path} {path}/ / 105 | } 106 | } 107 | 108 | 109 | antd-admin.zuiidea.com/public { 110 | gzip 111 | root /home/www/antd-admin/dist/static/public 112 | } 113 | 114 | ``` 115 | -------------------------------------------------------------------------------- /docs/zh-cn/faq.md: -------------------------------------------------------------------------------- 1 | # 问题集锦 2 | 3 | ## 新建页面 4 | 5 | 1. 直接从/src/pages复制一个page (会自动创建路由[umi](https://umijs.org/zh/guide/router.html#%E7%BA%A6%E5%AE%9A%E5%BC%8F%E8%B7%AF%E7%94%B1)) 6 | 2. 修改 namespace/pathToRegexp 在 model.js 7 | 3. 修改 mock中route.js增加一条route -------------------------------------------------------------------------------- /docs/zh-cn/getting-started.md: -------------------------------------------------------------------------------- 1 | # 快速上手 2 | 3 | > 在开始之前,推荐先学习 [React](http://facebook.github.io/react/) 、 [ES2015+](http://es6.ruanyifeng.com/) 、 [Antd Design](https://ant.design/docs/react/introduce-cn) , 了解 [UmiJS](https://umijs.org/) 、[Dva](http://github.com/dvajs/dva) ,并正确安装和配置了 [Node.js](https://nodejs.org/) v8 或以上 、[Git](https://git-scm.com/)。提前了解和学习这些知识会非常有帮助。 4 | 5 | ## 安装 6 | 7 | ```bash 8 | git clone https://github.com/zuiidea/antd-admin.git my-project 9 | cd my-project 10 | ``` 11 | 12 | ## 目录结构 13 | 14 | 应用的目录结构如下 15 | 16 | ```bash 17 | ├── dist/ # 默认build输出目录 18 | ├── mock/ # Mock文件目录 19 | ├── public/ # 静态资源文件目录 20 | ├── src/ # 源码目录 21 | │ ├── components/ # 组件目录 22 | │ ├── e2e/ # e2e目录 23 | │ ├── layouts/ # 布局目录 24 | │ ├── locales/ # 国际化文件目录 25 | │ ├── models/ # 数据模型目录 26 | │ ├── pages/ # 页面组件目录 27 | │ ├── services/ # 数据接口目录 28 | │ │ ├── api.js # 接口配置 29 | │ │ └── index.js # 接口输出 30 | │ ├── themes/ # 项目样式目录 31 | │ │ ├── default.less # 样式变量 32 | │ │ ├── index.less # 全局样式 33 | │ │ ├── mixin.less # 样式函数 34 | │ │ └── vars.less # 样式变量及函数 35 | │ ├── utils/ # 工具函数目录 36 | │ │ ├── config.js # 项目配置 37 | │ │ ├── constant.js # 静态常量 38 | │ │ ├── index.js # 工具函数 39 | │ │ ├── request.js # 异步请求函数(axios) 40 | │ │ └── theme.js # 项目需要在js中使用到样式变量 41 | ├── .editorconfig # 编辑器配置 42 | ├── .env # 环境变量 43 | ├── .eslintrc # ESlint配置 44 | ├── .gitignore # Git忽略文件配置 45 | ├── .prettierignore # Prettier忽略文件配置 46 | ├── .prettierrc # Prettier配置 47 | ├── .stylelintrc.json # Stylelint配置 48 | ├── .travis.yml # Travis配置 49 | └── .umirc.js # Umi配置 50 | └── package.json # 项目信息 51 | ``` 52 | 53 | ## 本地开发 54 | 55 | 1. 进入目录安装依赖,国内用户推荐使用 [cnpm](https://cnpmjs.org) 进行加速 56 | 57 | ```bash 58 | yarn install 59 | ``` 60 | 61 | 或者 62 | 63 | ```bash 64 | npm install 65 | ``` 66 | 67 | 2. 启动本地服务器 68 | 69 | ```bash 70 | npm run start 71 | ``` 72 | 73 | 3. 启动完成后打开浏览器访问 [http://localhost:7000](http://localhost:7000),如果需要更改启动端口,可在 `.env` 文件中配置。 74 | -------------------------------------------------------------------------------- /docs/zh-cn/i18n.md: -------------------------------------------------------------------------------- 1 | # 国际化 2 | 3 | ## 新增应用语言 4 | 5 | 以新增日语为例。 6 | 7 | ![i18n](../_media/term_i18n.svg) 8 | 9 | 1. 添加语言包本地文件,`ja` 为日语的语言代码,支持翻译的语言列表参考 [有道智云](http://ai.youdao.com/docs/doc-trans-api.s#p05),运行下面命令后会生成 `src/locales/ja/messages.json` 文件。 10 | 11 | ```bash 12 | npm run add-locale ja 13 | ``` 14 | 15 | 2. 提取代码中需要翻译的字段,即 `message`、`` intl.formatMessage({ id: 'message `` 中 `message` 字段,运行下面命令后 `src/locales/ja/messages.json' }) 将会出现提取后的字段配置。 16 | 17 | ```bash 18 | npm run extract 19 | ``` 20 | 21 | 你将看到如下信息: 22 | 23 | ```bash 24 | Catalog statistics: 25 | ┌─────────────┬─────────────┬─────────┐ 26 | │ Language │ Total count │ Missing │ 27 | ├─────────────┼─────────────┼─────────┤ 28 | │ en (source) │ 52 │ - │ 29 | │ ja │ 52 │ 52 │ 30 | │ zh │ 52 │ 0 │ 31 | └─────────────┴─────────────┴─────────┘ 32 | ``` 33 | 34 | 3. 与此同时,我们在 `src/utils/config.js` 新增相关配置。 35 | 36 | ```javascript 37 | { 38 | ... 39 | i18n: { 40 | languages: [ 41 | ... 42 | { 43 | key:'ja', 44 | title: '日本語', 45 | flag: '/japanese.svg', 46 | }, 47 | ], 48 | }, 49 | } 50 | ``` 51 | 52 | >  路由相关效果,配置后 `npm run start` 重启后生效。 53 | 54 | 4. 使用内置的命令进行自动翻译,在 `src/locales/ja/messages.json` 中将会看到翻译后的配置。 55 | 56 | ```bash 57 | npm run trans:only 58 | ``` 59 | 60 | 你将看到如下信息: 61 | 62 | ```bash 63 | start: en -> ja 64 | ... 65 | youdao: en -> ja: Unpublished -> 未発表 66 | youdao: en -> ja: Update -> 更新 67 | youdao: en -> ja: Update User -> ユーザーの更新 68 | youdao: en -> ja: Username -> 名 69 | ... 70 | All translations have been completed. 71 | ``` 72 | 73 | > `npm run trans` 将会依次执行 `npm run extract` 和 `npm run trans:only` 74 | 75 | 5. 最后,可以在 `src/locales/ja/messages.json` 中对翻译不准确的的字段进行调整。启动开发模式 `npm run start`,打开 [http://localhost:7000/ja/login](http://localhost:7000/ja/login),你将看到日语版本的应用。 76 | -------------------------------------------------------------------------------- /docs/zh-cn/layout.md: -------------------------------------------------------------------------------- 1 | # 布局 2 | 3 | ## 新增布局 4 | 5 | 以新增名为 `secondary` 的布局为例,使以 `secondary` 开头的路由都使用该布局。 6 | 7 | 1. 在 `src/utils/config.js` 新增相关配置,参数详细请查看 [layouts](/zh-cn/configuration?id=layouts)。 8 | 9 | ```javascript 10 | layouts: [ 11 | { 12 | name: 'primary', 13 | include: [/.*/], 14 | exclude: [/(\/(en|zh))*\/login/, /(\/(en|zh))*\/secondary\/(.*)/], 15 | }, 16 | { 17 | name: 'secondary', 18 | include: [/(\/(en|zh))*\/secondary\/(.*)/], 19 | }, 20 | ], 21 | ``` 22 | 23 | 2. 在`src/layouts/BaseLayout.js` 文件中新增 `secondary` 布局组件。 24 | 25 | ```javascript 26 | import SecondaryLayout from './SecondaryLayout' 27 | 28 | const LayoutMap = { 29 | primary: PrimaryLayout, 30 | public: PublicLayout, 31 | secondary: SecondaryLayout, 32 | } 33 | ``` 34 | 35 | 3. 在`src/layouts/` 目录中新增 `SecondaryLayout.js` 文件。 36 | 37 | ```javascript 38 | import React from 'react' 39 | 40 | export default ({ children }) => { 41 | return ( 42 |
43 |

Secondary

44 | {children} 45 |
46 | ) 47 | } 48 | ``` 49 | 50 | 4. 在`src/pages/` 目录中新增 `secondary/index.js` 文件。 51 | 52 | ```javascript 53 | import React from 'react' 54 | 55 | export default ({ children }) => { 56 | return
Secondary page Content
57 | } 58 | ``` 59 | 60 | 5. 最后,启动开发模式 `npm run start`,打开 [http://localhost:7000/secondary/](http://localhost:7000/secondary/),你将看到 `secondary` 布局的页面。 61 | -------------------------------------------------------------------------------- /docs/zh-cn/request.md: -------------------------------------------------------------------------------- 1 | # HTTP请求 2 | 3 | 本项目使用了axios提供http请求服务,文件在src/utils/request.js 4 | 5 | ## 自定义Header 6 | 7 | 为了提供鉴权、修改cookie等服务,可以手动修改Header 8 | 9 | ``` 10 | axios.defaults.headers.common['Authorization'] = 'token' 11 | ``` 12 | 13 | 或者 14 | 15 | ``` 16 | // 添加请求拦截器 17 | axios.interceptors.request.use(function (config) { 18 | // 在发送请求之前做些什么 19 | config.headers.token = window.localStorage.getItem('token'); 20 | return config; 21 | }, function (error) { 22 | return Promise.reject(error); 23 | }); 24 | ``` -------------------------------------------------------------------------------- /docs/zh-cn/router.md: -------------------------------------------------------------------------------- 1 | # 路由 2 | 3 | 本项目中采用约定式路由 4 | 5 | 参考[umi 路由](https://umijs.org/zh/guide/router.html) 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testURL: 'http://localhost:8000', 3 | } 4 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Antd-Admin", 3 | "start_url": ".", 4 | "display": "standalone", 5 | "background_color": "#fff", 6 | "description": "A front-end solution for enterprise applications built upon Ant Design and UmiJS", 7 | "icons": [{ 8 | "src": "logo/logo@96.png", 9 | "sizes": "72x72" 10 | }, 11 | { 12 | "src": "logo/logo@128.png", 13 | "sizes": "128x128" 14 | }, 15 | { 16 | "src": "logo/logo@144.png", 17 | "sizes": "144x144" 18 | }, 19 | { 20 | "src": "logo/logo@152.png", 21 | "sizes": "152x152" 22 | }, 23 | { 24 | "src": "logo/logo@192.png", 25 | "sizes": "192x192" 26 | }, 27 | { 28 | "src": "logo/logo@384.png", 29 | "sizes": "384x384" 30 | }, 31 | { 32 | "src": "logo/logo@512.png", 33 | "sizes": "512x512" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /mock/_utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Query objects that specify keys and values in an array where all values are objects. 3 | * @param {array} array An array where all values are objects, like [{key:1},{key:2}]. 4 | * @param {string} key The key of the object that needs to be queried. 5 | * @param {string} value The value of the object that needs to be queried. 6 | * @return {object|undefined} Return frist object when query success. 7 | */ 8 | export function queryArray(array, key, value) { 9 | if (!Array.isArray(array)) { 10 | return 11 | } 12 | return array.filter(_ => _[key] === value) 13 | } 14 | 15 | export function randomNumber(min, max) { 16 | return Math.floor(Math.random() * (max - min) + min) 17 | } 18 | 19 | export function randomAvatar() { 20 | const avatarList = [ 21 | 'photo-1549492864-2ec7d66ffb04.jpeg', 22 | 'photo-1480535339474-e083439a320d.jpeg', 23 | 'photo-1523419409543-a5e549c1faa8.jpeg', 24 | 'photo-1519648023493-d82b5f8d7b8a.jpeg', 25 | 'photo-1523307730650-594bc63f9d67.jpeg', 26 | 'photo-1522962506050-a2f0267e4895.jpeg', 27 | 'photo-1489779162738-f81aed9b0a25.jpeg', 28 | 'photo-1534308143481-c55f00be8bd7.jpeg', 29 | 'photo-1519336555923-59661f41bb45.jpeg', 30 | 'photo-1551438632-e8c7d9a5d1b7.jpeg', 31 | 'photo-1525879000488-bff3b1c387cf.jpeg', 32 | 'photo-1487412720507-e7ab37603c6f.jpeg', 33 | 'photo-1510227272981-87123e259b17.jpeg' 34 | ] 35 | return `//image.zuiidea.com/${avatarList[randomNumber(0, avatarList.length - 1)]}?imageView2/1/w/200/h/200/format/webp/q/75|imageslim` 36 | } 37 | 38 | export const Constant = { 39 | ApiPrefix: '/api/v1', 40 | NotFound: { 41 | message: 'Not Found', 42 | documentation_url: '', 43 | }, 44 | Color: { 45 | green: '#64ea91', 46 | blue: '#8fc9fb', 47 | purple: '#d897eb', 48 | red: '#f69899', 49 | yellow: '#f8c82e', 50 | peach: '#f797d6', 51 | borderBase: '#e5e5e5', 52 | borderSplit: '#f4f4f4', 53 | grass: '#d6fbb5', 54 | sky: '#c1e0fc', 55 | }, 56 | } 57 | 58 | export Mock from 'mockjs' 59 | export qs from 'qs' 60 | -------------------------------------------------------------------------------- /mock/dashboard.js: -------------------------------------------------------------------------------- 1 | import { Mock, Constant } from './_utils' 2 | 3 | const { ApiPrefix, Color } = Constant 4 | 5 | const Dashboard = Mock.mock({ 6 | 'sales|8': [ 7 | { 8 | 'name|+1': 2008, 9 | 'Clothes|200-500': 1, 10 | 'Food|180-400': 1, 11 | 'Electronics|300-550': 1, 12 | }, 13 | ], 14 | cpu: { 15 | 'usage|50-600': 1, 16 | space: 825, 17 | 'cpu|40-90': 1, 18 | 'data|20': [ 19 | { 20 | 'cpu|20-80': 1, 21 | }, 22 | ], 23 | }, 24 | browser: [ 25 | { 26 | name: 'Google Chrome', 27 | percent: 43.3, 28 | status: 1, 29 | }, 30 | { 31 | name: 'Mozilla Firefox', 32 | percent: 33.4, 33 | status: 2, 34 | }, 35 | { 36 | name: 'Apple Safari', 37 | percent: 34.6, 38 | status: 3, 39 | }, 40 | { 41 | name: 'Internet Explorer', 42 | percent: 12.3, 43 | status: 4, 44 | }, 45 | { 46 | name: 'Opera Mini', 47 | percent: 3.3, 48 | status: 1, 49 | }, 50 | { 51 | name: 'Chromium', 52 | percent: 2.53, 53 | status: 1, 54 | }, 55 | ], 56 | user: { 57 | name: 'github', 58 | sales: 3241, 59 | sold: 3556, 60 | }, 61 | 'completed|12': [ 62 | { 63 | 'name|+1': 2008, 64 | 'Task complete|200-1000': 1, 65 | 'Cards Complete|200-1000': 1, 66 | }, 67 | ], 68 | 'comments|5': [ 69 | { 70 | name: '@last', 71 | 'status|1-3': 1, 72 | content: '@sentence', 73 | avatar() { 74 | return Mock.Random.image( 75 | '48x48', 76 | Mock.Random.color(), 77 | '#757575', 78 | 'png', 79 | this.name.substr(0, 1) 80 | ) 81 | }, 82 | date() { 83 | return `2016-${Mock.Random.date('MM-dd')} ${Mock.Random.time( 84 | 'HH:mm:ss' 85 | )}` 86 | }, 87 | }, 88 | ], 89 | 'recentSales|36': [ 90 | { 91 | 'id|+1': 1, 92 | name: '@last', 93 | 'status|1-4': 1, 94 | date() { 95 | return `${Mock.Random.integer(2015, 2016)}-${Mock.Random.date( 96 | 'MM-dd' 97 | )} ${Mock.Random.time('HH:mm:ss')}` 98 | }, 99 | 'price|10-200.1-2': 1, 100 | }, 101 | ], 102 | quote: { 103 | name: 'Joho Doe', 104 | title: 'Graphic Designer', 105 | content: 106 | "I'm selfish, impatient and a little insecure. I make mistakes, I am out of control and at times hard to handle. But if you can't handle me at my worst, then you sure as hell don't deserve me at my best.", 107 | avatar: 108 | '//cdn.antd-admin.zuiidea.com/bc442cf0cc6f7940dcc567e465048d1a8d634493198c4-sPx5BR_fw236', 109 | }, 110 | numbers: [ 111 | { 112 | icon: 'pay-circle-o', 113 | color: Color.green, 114 | title: 'Online Review', 115 | number: 2781, 116 | }, 117 | { 118 | icon: 'team', 119 | color: Color.blue, 120 | title: 'New Customers', 121 | number: 3241, 122 | }, 123 | { 124 | icon: 'message', 125 | color: Color.purple, 126 | title: 'Active Projects', 127 | number: 253, 128 | }, 129 | { 130 | icon: 'shopping-cart', 131 | color: Color.red, 132 | title: 'Referrals', 133 | number: 4324, 134 | }, 135 | ], 136 | }) 137 | 138 | module.exports = { 139 | [`GET ${ApiPrefix}/dashboard`](req, res) { 140 | res.json(Dashboard) 141 | }, 142 | } 143 | -------------------------------------------------------------------------------- /mock/post.js: -------------------------------------------------------------------------------- 1 | import { Mock, Constant } from './_utils' 2 | 3 | const { ApiPrefix } = Constant 4 | 5 | let postId = 0 6 | const database = Mock.mock({ 7 | 'data|100': [ 8 | { 9 | id() { 10 | postId += 1 11 | return postId + 10000 12 | }, 13 | 'status|1-2': 1, 14 | title: '@title', 15 | author: '@last', 16 | categories: '@word', 17 | tags: '@word', 18 | 'views|10-200': 1, 19 | 'comments|10-200': 1, 20 | visibility: () => { 21 | return Mock.mock( 22 | '@pick(["Public",' + '"Password protected", ' + '"Private"])' 23 | ) 24 | }, 25 | date: '@dateTime', 26 | image() { 27 | return Mock.Random.image( 28 | '100x100', 29 | Mock.Random.color(), 30 | '#757575', 31 | 'png', 32 | this.author.substr(0, 1) 33 | ) 34 | }, 35 | }, 36 | ], 37 | }).data 38 | 39 | module.exports = { 40 | [`GET ${ApiPrefix}/posts`](req, res) { 41 | const { query } = req 42 | let { pageSize, page, ...other } = query 43 | pageSize = pageSize || 10 44 | page = page || 1 45 | 46 | let newData = database 47 | for (let key in other) { 48 | if ({}.hasOwnProperty.call(other, key)) { 49 | newData = newData.filter(item => { 50 | if ({}.hasOwnProperty.call(item, key)) { 51 | return ( 52 | String(item[key]) 53 | .trim() 54 | .indexOf(decodeURI(other[key]).trim()) > -1 55 | ) 56 | } 57 | return true 58 | }) 59 | } 60 | } 61 | 62 | res.status(200).json({ 63 | data: newData.slice((page - 1) * pageSize, page * pageSize), 64 | total: newData.length, 65 | }) 66 | }, 67 | } 68 | -------------------------------------------------------------------------------- /mock/route.js: -------------------------------------------------------------------------------- 1 | import { Constant } from './_utils' 2 | const { ApiPrefix } = Constant 3 | 4 | const database = [ 5 | { 6 | id: '1', 7 | icon: 'dashboard', 8 | name: 'Dashboard', 9 | zh: { 10 | name: '仪表盘' 11 | }, 12 | 'pt-br': { 13 | name: 'Dashboard' 14 | }, 15 | route: '/dashboard', 16 | }, 17 | { 18 | id: '2', 19 | breadcrumbParentId: '1', 20 | name: 'Users', 21 | zh: { 22 | name: '用户管理' 23 | }, 24 | 'pt-br': { 25 | name: 'Usuário' 26 | }, 27 | icon: 'user', 28 | route: '/user', 29 | }, 30 | { 31 | id: '7', 32 | breadcrumbParentId: '1', 33 | name: 'Posts', 34 | zh: { 35 | name: '用户管理' 36 | }, 37 | 'pt-br': { 38 | name: 'Posts' 39 | }, 40 | icon: 'shopping-cart', 41 | route: '/post', 42 | }, 43 | { 44 | id: '21', 45 | menuParentId: '-1', 46 | breadcrumbParentId: '2', 47 | name: 'User Detail', 48 | zh: { 49 | name: '用户详情' 50 | }, 51 | 'pt-br': { 52 | name: 'Detalhes do usuário' 53 | }, 54 | route: '/user/:id', 55 | }, 56 | { 57 | id: '3', 58 | breadcrumbParentId: '1', 59 | name: 'Request', 60 | zh: { 61 | name: 'Request' 62 | }, 63 | 'pt-br': { 64 | name: 'Requisição' 65 | }, 66 | icon: 'api', 67 | route: '/request', 68 | }, 69 | { 70 | id: '4', 71 | breadcrumbParentId: '1', 72 | name: 'UI Element', 73 | zh: { 74 | name: 'UI组件' 75 | }, 76 | 'pt-br': { 77 | name: 'Elementos UI' 78 | }, 79 | icon: 'camera-o', 80 | }, 81 | { 82 | id: '45', 83 | breadcrumbParentId: '4', 84 | menuParentId: '4', 85 | name: 'Editor', 86 | zh: { 87 | name: 'Editor' 88 | }, 89 | 'pt-br': { 90 | name: 'Editor' 91 | }, 92 | icon: 'edit', 93 | route: '/editor', 94 | }, 95 | { 96 | id: '5', 97 | breadcrumbParentId: '1', 98 | name: 'Charts', 99 | zh: { 100 | name: 'Charts' 101 | }, 102 | 'pt-br': { 103 | name: 'Graficos' 104 | }, 105 | icon: 'code-o', 106 | }, 107 | { 108 | id: '51', 109 | breadcrumbParentId: '5', 110 | menuParentId: '5', 111 | name: 'ECharts', 112 | zh: { 113 | name: 'ECharts' 114 | }, 115 | 'pt-br': { 116 | name: 'ECharts' 117 | }, 118 | icon: 'line-chart', 119 | route: '/chart/ECharts', 120 | }, 121 | { 122 | id: '52', 123 | breadcrumbParentId: '5', 124 | menuParentId: '5', 125 | name: 'HighCharts', 126 | zh: { 127 | name: 'HighCharts' 128 | }, 129 | 'pt-br': { 130 | name: 'HighCharts' 131 | }, 132 | icon: 'bar-chart', 133 | route: '/chart/highCharts', 134 | }, 135 | { 136 | id: '53', 137 | breadcrumbParentId: '5', 138 | menuParentId: '5', 139 | name: 'Rechartst', 140 | zh: { 141 | name: 'Rechartst' 142 | }, 143 | 'pt-br': { 144 | name: 'Rechartst' 145 | }, 146 | icon: 'area-chart', 147 | route: '/chart/Recharts', 148 | }, 149 | ] 150 | 151 | module.exports = { 152 | [`GET ${ApiPrefix}/routes`](req, res) { 153 | res.status(200).json(database) 154 | }, 155 | } 156 | -------------------------------------------------------------------------------- /public/america.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /public/china.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 14 | 16 | 18 | 20 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zuiidea/antd-admin/67fc31a00892215e2d9971a91aa300e33ba48321/public/favicon.ico -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Created with Sketch. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /public/logo/logo@128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zuiidea/antd-admin/67fc31a00892215e2d9971a91aa300e33ba48321/public/logo/logo@128.png -------------------------------------------------------------------------------- /public/logo/logo@144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zuiidea/antd-admin/67fc31a00892215e2d9971a91aa300e33ba48321/public/logo/logo@144.png -------------------------------------------------------------------------------- /public/logo/logo@152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zuiidea/antd-admin/67fc31a00892215e2d9971a91aa300e33ba48321/public/logo/logo@152.png -------------------------------------------------------------------------------- /public/logo/logo@192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zuiidea/antd-admin/67fc31a00892215e2d9971a91aa300e33ba48321/public/logo/logo@192.png -------------------------------------------------------------------------------- /public/logo/logo@384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zuiidea/antd-admin/67fc31a00892215e2d9971a91aa300e33ba48321/public/logo/logo@384.png -------------------------------------------------------------------------------- /public/logo/logo@512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zuiidea/antd-admin/67fc31a00892215e2d9971a91aa300e33ba48321/public/logo/logo@512.png -------------------------------------------------------------------------------- /public/logo/logo@72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zuiidea/antd-admin/67fc31a00892215e2d9971a91aa300e33ba48321/public/logo/logo@72.png -------------------------------------------------------------------------------- /public/logo/logo@96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zuiidea/antd-admin/67fc31a00892215e2d9971a91aa300e33ba48321/public/logo/logo@96.png -------------------------------------------------------------------------------- /public/portugal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /scripts/translate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Youdao Translate, My private account is for testing purposes only. 3 | * Please go to the official account to apply for an account. Thank you for your cooperation. 4 | * APP ID:055c2d71f9a05214 5 | * Secret key:ZcpuQxQW3NkQeKVkqrXIKQYXH57g2KuN 6 | */ 7 | 8 | /* eslint-disable */ 9 | const md5 = require('md5') 10 | const qs = require('qs') 11 | const fs = require('fs') 12 | const path = require('path') 13 | const axios = require('axios') 14 | const jsonFormat = require('json-format') 15 | const { i18n } = require('../src/utils/config') 16 | const { languages, defaultLanguage } = i18n 17 | 18 | const locales = {} 19 | 20 | languages.forEach(item => { 21 | locales[item.key] = require(`../src/locales/${item.key}/messages.json`) 22 | }) 23 | 24 | const youdao = ({ q, from, to }) => 25 | new Promise((resolve, reject) => { 26 | { 27 | const appid = '055c2d71f9a05214' 28 | const appse = 'ZcpuQxQW3NkQeKVkqrXIKQYXH57g2KuN' 29 | const salt = Date.now() 30 | 31 | const sign = md5(appid + q + salt + appse) 32 | const query = qs.stringify({ 33 | q, 34 | from, 35 | to, 36 | appKey: appid, 37 | salt, 38 | sign, 39 | }) 40 | 41 | axios.get(`http://openapi.youdao.com/api?${query}`).then(({ data }) => { 42 | if (data.query && data.translation[0]) { 43 | resolve(data.translation[0]) 44 | } else { 45 | resolve(q) 46 | } 47 | }) 48 | } 49 | }) 50 | 51 | const transform = async ({ from, to, locales, outputPath }) => { 52 | for (const key in locales[from]) { 53 | if (locales[to][key]) { 54 | console.log(`add to skip: ${key}`) 55 | } else { 56 | let res = key 57 | let way = 'youdao' 58 | if (key.indexOf('/') !== 0) { 59 | const reg = '{([^{}]*)}' 60 | const tasks = key 61 | .match(new RegExp(`${reg}|((?<=(${reg}|^)).*?(?=(${reg}|$)))`, 'g')) 62 | .map(item => { 63 | if (new RegExp(reg).test(item)) { 64 | return Promise.resolve(item) 65 | } 66 | return youdao({ 67 | q: item, 68 | from, 69 | to, 70 | }) 71 | }) 72 | 73 | res = (await Promise.all(tasks)).join('') 74 | } else { 75 | res = `/${to + key}` 76 | way = 'link' 77 | } 78 | if (res !== key) { 79 | locales[to][key] = res 80 | console.log(`${way}: ${from} -> ${to}: ${key} -> ${res}`) 81 | } else { 82 | console.log(`same: ${from} -> ${to}: ${key}`) 83 | } 84 | } 85 | } 86 | await fs.writeFileSync( 87 | path.resolve(__dirname, outputPath), 88 | jsonFormat(locales[to], { 89 | type: 'space', 90 | size: 2, 91 | }) 92 | ) 93 | } 94 | ;(async () => { 95 | const tasks = languages 96 | .map(item => ({ 97 | from: defaultLanguage, 98 | to: item.key, 99 | })) 100 | .filter(item => item.from !== item.to) 101 | 102 | for (const item of tasks) { 103 | console.log(`start: ${item.from} -> ${item.to}`) 104 | await transform({ 105 | from: item.from, 106 | to: item.to, 107 | locales, 108 | outputPath: `../src/locales/${item.to}/messages.json`, 109 | }) 110 | console.log(`completed: ${item.from} -> ${item.to}`) 111 | } 112 | 113 | console.log('All translations have been completed.') 114 | })() 115 | -------------------------------------------------------------------------------- /src/components/DropOption/DropOption.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { BarsOutlined, DownOutlined } from '@ant-design/icons' 4 | import { Dropdown, Button, Menu } from 'antd' 5 | 6 | const DropOption = ({ 7 | onMenuClick, 8 | menuOptions = [], 9 | buttonStyle, 10 | dropdownProps, 11 | }) => { 12 | const menu = menuOptions.map(item => ( 13 | {item.name} 14 | )) 15 | return ( 16 | {menu}} 18 | {...dropdownProps} 19 | > 20 | 24 | 25 | ) 26 | } 27 | 28 | DropOption.propTypes = { 29 | onMenuClick: PropTypes.func, 30 | menuOptions: PropTypes.array.isRequired, 31 | buttonStyle: PropTypes.object, 32 | dropdownProps: PropTypes.object, 33 | } 34 | 35 | export default DropOption 36 | -------------------------------------------------------------------------------- /src/components/DropOption/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DropOption", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./DropOption.js" 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Editor/Editor.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Editor } from 'react-draft-wysiwyg' 3 | import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' 4 | import styles from './Editor.less' 5 | 6 | const DraftEditor = props => { 7 | return ( 8 | 14 | ) 15 | } 16 | 17 | export default DraftEditor 18 | -------------------------------------------------------------------------------- /src/components/Editor/Editor.less: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | height: 500px; 3 | 4 | :global { 5 | .rdw-dropdownoption-default { 6 | padding: 6px; 7 | } 8 | 9 | .rdw-dropdown-optionwrapper { 10 | box-sizing: content-box; 11 | width: 100%; 12 | border-radius: 0 0 2px 2px; 13 | &:hover { 14 | box-shadow: none; 15 | } 16 | } 17 | 18 | .rdw-inline-wrapper { 19 | flex-wrap: wrap; 20 | margin-bottom: 0; 21 | 22 | .rdw-option-wrapper { 23 | margin-bottom: 6px; 24 | } 25 | } 26 | 27 | .rdw-option-active { 28 | box-shadow: 1px 1px 0 #e8e8e8 inset; 29 | } 30 | 31 | .rdw-colorpicker-option { 32 | box-shadow: none; 33 | } 34 | 35 | .rdw-colorpicker-modal, 36 | .rdw-embedded-modal, 37 | .rdw-emoji-modal, 38 | .rdw-image-modal, 39 | .rdw-link-modal { 40 | box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05); 41 | } 42 | 43 | .rdw-colorpicker-modal, 44 | .rdw-embedded-modal, 45 | .rdw-link-modal { 46 | height: auto; 47 | } 48 | 49 | .rdw-emoji-modal { 50 | width: 214px; 51 | } 52 | 53 | .rdw-colorpicker-modal { 54 | width: auto; 55 | } 56 | 57 | .rdw-embedded-modal-btn, 58 | .rdw-image-modal-btn, 59 | .rdw-link-modal-btn { 60 | height: 32px; 61 | margin-top: 12px; 62 | } 63 | 64 | .rdw-embedded-modal-input, 65 | .rdw-embedded-modal-size-input, 66 | .rdw-link-modal-input { 67 | padding: 2px 6px; 68 | height: 32px; 69 | } 70 | 71 | .rdw-dropdown-selectedtext { 72 | color: #000; 73 | } 74 | 75 | .rdw-dropdown-wrapper, 76 | .rdw-option-wrapper { 77 | min-width: 36px; 78 | transition: all 0.2s ease; 79 | height: 30px; 80 | 81 | &:active { 82 | box-shadow: 1px 1px 0 #e8e8e8 inset; 83 | } 84 | 85 | &:hover { 86 | box-shadow: 1px 1px 0 #e8e8e8; 87 | } 88 | } 89 | 90 | .rdw-dropdown-wrapper { 91 | min-width: 60px; 92 | } 93 | 94 | .rdw-editor-main { 95 | box-sizing: border-box; 96 | } 97 | } 98 | 99 | .editor { 100 | border: 1px solid #f1f1f1; 101 | padding: 5px; 102 | border-radius: 2px; 103 | height: auto; 104 | min-height: 200px; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/components/Editor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Editor", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./Editor.js" 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Ellipsis/index.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TooltipProps } from 'antd/lib/tooltip'; 3 | 4 | export interface EllipsisTooltipProps extends TooltipProps { 5 | title?: undefined; 6 | overlayStyle?: undefined; 7 | } 8 | 9 | export interface EllipsisProps { 10 | tooltip?: boolean | EllipsisTooltipProps; 11 | length?: number; 12 | lines?: number; 13 | style?: React.CSSProperties; 14 | className?: string; 15 | fullWidthRecognition?: boolean; 16 | } 17 | 18 | export function getStrFullLength(str: string): number; 19 | export function cutStrByFullLength(str: string, maxLength: number): string; 20 | 21 | export default class Ellipsis extends React.Component {} 22 | -------------------------------------------------------------------------------- /src/components/Ellipsis/index.less: -------------------------------------------------------------------------------- 1 | .ellipsis { 2 | display: inline-block; 3 | width: 100%; 4 | overflow: hidden; 5 | word-break: break-all; 6 | } 7 | 8 | .lines { 9 | position: relative; 10 | .shadow { 11 | position: absolute; 12 | z-index: -999; 13 | display: block; 14 | color: transparent; 15 | opacity: 0; 16 | } 17 | } 18 | 19 | .lineClamp { 20 | position: relative; 21 | display: -webkit-box; 22 | overflow: hidden; 23 | text-overflow: ellipsis; 24 | } 25 | -------------------------------------------------------------------------------- /src/components/Ellipsis/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Ellipsis 3 | subtitle: 文本自动省略号 4 | cols: 1 5 | order: 10 6 | --- 7 | 8 | 文本过长自动处理省略号,支持按照文本长度和最大行数两种方式截取。 9 | 10 | ## API 11 | 12 | | 参数 | 说明 | 类型 | 默认值 | 13 | | -------------------- | ------------------------------------------------ | ------- | ------ | 14 | | tooltip | 移动到文本展示完整内容的提示 | boolean | - | 15 | | length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | - | 16 | | lines | 在按照行数截取下最大的行数,超过则截取省略 | number | `1` | 17 | | fullWidthRecognition | 是否将全角字符的长度视为 2 来计算字符串长度 | boolean | - | 18 | -------------------------------------------------------------------------------- /src/components/Ellipsis/index.test.js: -------------------------------------------------------------------------------- 1 | import { getStrFullLength, cutStrByFullLength } from './index'; 2 | 3 | describe('test calculateShowLength', () => { 4 | it('get full length', () => { 5 | expect(getStrFullLength('一二,a,')).toEqual(8); 6 | }); 7 | it('cut str by full length', () => { 8 | expect(cutStrByFullLength('一二,a,', 7)).toEqual('一二,a'); 9 | }); 10 | it('cut str when length small', () => { 11 | expect(cutStrByFullLength('一22三', 5)).toEqual('一22'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/components/FilterItem/FilterItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import styles from './FilterItem.less' 4 | 5 | const FilterItem = ({ label = '', children }) => { 6 | const labelArray = label.split('') 7 | return ( 8 |
9 | {labelArray.length > 0 && ( 10 |
11 | {labelArray.map((item, index) => ( 12 | 13 | {item} 14 | 15 | ))} 16 |
17 | )} 18 |
{children}
19 |
20 | ) 21 | } 22 | 23 | FilterItem.propTypes = { 24 | label: PropTypes.string, 25 | children: PropTypes.element.isRequired, 26 | } 27 | 28 | export default FilterItem 29 | -------------------------------------------------------------------------------- /src/components/FilterItem/FilterItem.less: -------------------------------------------------------------------------------- 1 | .filterItem { 2 | display: flex; 3 | justify-content: space-between; 4 | 5 | .labelWrap { 6 | width: 64px; 7 | line-height: 28px; 8 | margin-right: 12px; 9 | justify-content: space-between; 10 | display: flex; 11 | overflow: hidden; 12 | } 13 | 14 | .item { 15 | flex: 1; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/FilterItem/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FilterItem", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./FilterItem.js" 6 | } 7 | -------------------------------------------------------------------------------- /src/components/GlobalFooter/index.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | export interface GlobalFooterProps { 3 | links?: Array<{ 4 | key?: string; 5 | title: React.ReactNode; 6 | href: string; 7 | blankTarget?: boolean; 8 | }>; 9 | copyright?: React.ReactNode; 10 | style?: React.CSSProperties; 11 | className?: string; 12 | } 13 | 14 | export default class GlobalFooter extends React.Component {} 15 | -------------------------------------------------------------------------------- /src/components/GlobalFooter/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import styles from './index.less'; 4 | 5 | const GlobalFooter = ({ className, links, copyright }) => { 6 | const clsString = classNames(styles.globalFooter, className); 7 | return ( 8 |
9 | {links && ( 10 |
11 | {links.map(link => ( 12 | 18 | {link.title} 19 | 20 | ))} 21 |
22 | )} 23 | {copyright &&
{copyright}
} 24 |
25 | ); 26 | }; 27 | 28 | export default GlobalFooter; 29 | -------------------------------------------------------------------------------- /src/components/GlobalFooter/index.less: -------------------------------------------------------------------------------- 1 | /* @import '~antd/lib/style/themes/default.less'; */ 2 | 3 | .globalFooter { 4 | margin: 48px 0 24px 0; 5 | padding: 0 16px; 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: GlobalFooter 3 | subtitle: 全局页脚 4 | cols: 1 5 | order: 7 6 | --- 7 | 8 | 页脚属于全局导航的一部分,作为对顶部导航的补充,通过传递数据控制展示内容。 9 | 10 | ## API 11 | 12 | | 参数 | 说明 | 类型 | 默认值 | 13 | | --------- | -------- | ---------------------------------------------------------------- | ------ | 14 | | links | 链接数据 | array<{ title: ReactNode, href: string, blankTarget?: boolean }> | - | 15 | | copyright | 版权信息 | ReactNode | - | 16 | -------------------------------------------------------------------------------- /src/components/Layout/Bread.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent, Fragment } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Breadcrumb } from 'antd' 4 | import { Link, withRouter } from 'umi' 5 | import { t } from "@lingui/macro" 6 | import iconMap from 'utils/iconMap' 7 | const { pathToRegexp } = require('path-to-regexp') 8 | import { queryAncestors } from 'utils' 9 | import styles from './Bread.less' 10 | 11 | @withRouter 12 | class Bread extends PureComponent { 13 | generateBreadcrumbs = (paths) => { 14 | return paths.map((item, key) => { 15 | const content = item && ( 16 | 17 | {item.icon && ( 18 | {iconMap[item.icon]} 19 | )} 20 | {item.name} 21 | 22 | ) 23 | 24 | return ( 25 | item && ( 26 | 27 | {paths.length - 1 !== key ? ( 28 | {content} 29 | ) : ( 30 | content 31 | )} 32 | 33 | ) 34 | ) 35 | }) 36 | } 37 | render() { 38 | const { routeList, location } = this.props 39 | 40 | // Find a route that matches the pathname. 41 | const currentRoute = routeList.find( 42 | (_) => _.route && pathToRegexp(_.route).exec(location.pathname) 43 | ) 44 | 45 | // Find the breadcrumb navigation of the current route match and all its ancestors. 46 | const paths = currentRoute 47 | ? queryAncestors(routeList, currentRoute, 'breadcrumbParentId').reverse() 48 | : [ 49 | routeList[0], 50 | { 51 | id: 404, 52 | name: t`Not Found`, 53 | }, 54 | ] 55 | 56 | return ( 57 | 58 | {this.generateBreadcrumbs(paths)} 59 | 60 | ) 61 | } 62 | } 63 | 64 | Bread.propTypes = { 65 | routeList: PropTypes.array, 66 | } 67 | 68 | export default Bread 69 | -------------------------------------------------------------------------------- /src/components/Layout/Bread.less: -------------------------------------------------------------------------------- 1 | .bread { 2 | margin-bottom: 24px; 3 | 4 | :global { 5 | .ant-breadcrumb { 6 | display: flex; 7 | align-items: center; 8 | } 9 | } 10 | } 11 | 12 | @media (max-width: 767px) { 13 | .bread { 14 | margin-bottom: 12px; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Layout/Header.less: -------------------------------------------------------------------------------- 1 | @import '~themes/vars.less'; 2 | 3 | .header { 4 | padding: 0; 5 | box-shadow: @shadow-2; 6 | position: relative; 7 | display: flex; 8 | justify-content: space-between; 9 | height: 72px; 10 | z-index: 9; 11 | align-items: center; 12 | background-color: #fff; 13 | 14 | &.fixed { 15 | position: fixed; 16 | top: 0; 17 | right: 0; 18 | width: ~'calc(100% - 256px)'; 19 | z-index: 29; 20 | transition: width 0.2s; 21 | 22 | &.collapsed { 23 | width: ~'calc(100% - 80px)'; 24 | } 25 | } 26 | 27 | :global { 28 | .ant-menu-submenu-title { 29 | height: 72px; 30 | display: flex; 31 | align-items: center; 32 | } 33 | 34 | .ant-menu-horizontal { 35 | line-height: 72px; 36 | 37 | & > .ant-menu-submenu:hover { 38 | color: @primary-color; 39 | background-color: @hover-color; 40 | } 41 | } 42 | 43 | .ant-menu { 44 | border-bottom: none; 45 | height: 72px; 46 | } 47 | 48 | .ant-menu-horizontal > .ant-menu-submenu { 49 | top: 0; 50 | margin-top: 0; 51 | } 52 | 53 | .ant-menu-horizontal > .ant-menu-item, 54 | .ant-menu-horizontal > .ant-menu-submenu { 55 | border-bottom: none; 56 | } 57 | 58 | .ant-menu-horizontal > .ant-menu-item-active, 59 | .ant-menu-horizontal > .ant-menu-item-open, 60 | .ant-menu-horizontal > .ant-menu-item-selected, 61 | .ant-menu-horizontal > .ant-menu-item:hover, 62 | .ant-menu-horizontal > .ant-menu-submenu-active, 63 | .ant-menu-horizontal > .ant-menu-submenu-open, 64 | .ant-menu-horizontal > .ant-menu-submenu-selected, 65 | .ant-menu-horizontal > .ant-menu-submenu:hover { 66 | border-bottom: none; 67 | } 68 | } 69 | 70 | .rightContainer { 71 | display: flex; 72 | align-items: center; 73 | } 74 | 75 | .button { 76 | width: 72px; 77 | height: 72px; 78 | line-height: 72px; 79 | text-align: center; 80 | font-size: 18px; 81 | cursor: pointer; 82 | transition: @transition-ease-in; 83 | 84 | &:hover { 85 | color: @primary-color; 86 | background-color: @hover-color; 87 | } 88 | } 89 | } 90 | 91 | .iconButton { 92 | width: 48px; 93 | height: 48px; 94 | display: flex; 95 | justify-content: center; 96 | align-items: center; 97 | border-radius: 24px; 98 | cursor: pointer; 99 | .background-hover(); 100 | 101 | &:hover { 102 | .iconFont { 103 | color: @primary-color; 104 | } 105 | } 106 | 107 | & + .iconButton { 108 | margin-left: 8px; 109 | } 110 | 111 | .iconFont { 112 | color: #b2b0c7; 113 | font-size: 24px; 114 | } 115 | } 116 | 117 | .notification { 118 | padding: 24px 0; 119 | width: 320px; 120 | .notificationItem { 121 | transition: all 0.3s; 122 | padding: 12px 24px; 123 | cursor: pointer; 124 | &:hover { 125 | background-color: @hover-color; 126 | } 127 | } 128 | .clearButton { 129 | text-align: center; 130 | height: 48px; 131 | line-height: 48px; 132 | cursor: pointer; 133 | .background-hover(); 134 | } 135 | } 136 | 137 | .notificationPopover { 138 | :global { 139 | .ant-popover-inner-content { 140 | padding: 0; 141 | } 142 | .ant-popover-arrow { 143 | display: none; 144 | } 145 | .ant-list-item-content { 146 | flex: 0; 147 | margin-left: 16px; 148 | } 149 | } 150 | } 151 | 152 | @media (max-width: 767px) { 153 | .header { 154 | width: 100% !important; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/components/Layout/Menu.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent, Fragment } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Menu } from 'antd' 4 | import { NavLink, withRouter } from 'umi' 5 | import { pathToRegexp } from 'path-to-regexp' 6 | import { arrayToTree, queryAncestors } from 'utils' 7 | import iconMap from 'utils/iconMap' 8 | import store from 'store' 9 | 10 | const { SubMenu } = Menu 11 | 12 | @withRouter 13 | class SiderMenu extends PureComponent { 14 | state = { 15 | openKeys: store.get('openKeys') || [], 16 | } 17 | 18 | onOpenChange = openKeys => { 19 | const { menus } = this.props 20 | const rootSubmenuKeys = menus.filter(_ => !_.menuParentId).map(_ => _.id) 21 | 22 | const latestOpenKey = openKeys.find( 23 | key => this.state.openKeys.indexOf(key) === -1 24 | ) 25 | 26 | let newOpenKeys = openKeys 27 | if (rootSubmenuKeys.indexOf(latestOpenKey) !== -1) { 28 | newOpenKeys = latestOpenKey ? [latestOpenKey] : [] 29 | } 30 | 31 | this.setState({ 32 | openKeys: newOpenKeys, 33 | }) 34 | store.set('openKeys', newOpenKeys) 35 | } 36 | 37 | generateMenus = data => { 38 | return data.map(item => { 39 | if (item.children) { 40 | return ( 41 | 45 | {item.icon && iconMap[item.icon]} 46 | {item.name} 47 | 48 | } 49 | > 50 | {this.generateMenus(item.children)} 51 | 52 | ) 53 | } 54 | return ( 55 | 56 | 57 | {item.icon && iconMap[item.icon]} 58 | {item.name} 59 | 60 | 61 | ) 62 | }) 63 | } 64 | 65 | render() { 66 | const { 67 | collapsed, 68 | theme, 69 | menus, 70 | location, 71 | isMobile, 72 | onCollapseChange, 73 | } = this.props 74 | 75 | // Generating tree-structured data for menu content. 76 | const menuTree = arrayToTree(menus, 'id', 'menuParentId') 77 | 78 | // Find a menu that matches the pathname. 79 | const currentMenu = menus.find( 80 | _ => _.route && pathToRegexp(_.route).exec(location.pathname) 81 | ) 82 | 83 | // Find the key that should be selected according to the current menu. 84 | const selectedKeys = currentMenu 85 | ? queryAncestors(menus, currentMenu, 'menuParentId').map(_ => _.id) 86 | : [] 87 | 88 | const menuProps = collapsed 89 | ? {} 90 | : { 91 | openKeys: this.state.openKeys, 92 | } 93 | 94 | return ( 95 | { 103 | onCollapseChange(true) 104 | } 105 | : undefined 106 | } 107 | {...menuProps} 108 | > 109 | {this.generateMenus(menuTree)} 110 | 111 | ) 112 | } 113 | } 114 | 115 | SiderMenu.propTypes = { 116 | menus: PropTypes.array, 117 | theme: PropTypes.string, 118 | isMobile: PropTypes.bool, 119 | onCollapseChange: PropTypes.func, 120 | } 121 | 122 | export default SiderMenu 123 | -------------------------------------------------------------------------------- /src/components/Layout/Sider.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Switch, Layout } from 'antd' 4 | import { t } from "@lingui/macro" 5 | import { Trans } from "@lingui/macro" 6 | import { BulbOutlined } from '@ant-design/icons' 7 | import ScrollBar from '../ScrollBar' 8 | import { config } from 'utils' 9 | import SiderMenu from './Menu' 10 | import styles from './Sider.less' 11 | 12 | class Sider extends PureComponent { 13 | render() { 14 | const { 15 | menus, 16 | theme, 17 | isMobile, 18 | collapsed, 19 | onThemeChange, 20 | onCollapseChange, 21 | } = this.props 22 | 23 | return ( 24 | {}} 32 | className={styles.sider} 33 | > 34 |
35 |
36 | logo 37 | {!collapsed &&

{config.siteName}

} 38 |
39 |
40 | 41 |
42 | 48 | 55 | 56 |
57 | {!collapsed && ( 58 |
59 | 60 | 61 | Switch Theme 62 | 63 | 72 |
73 | )} 74 |
75 | ) 76 | } 77 | } 78 | 79 | Sider.propTypes = { 80 | menus: PropTypes.array, 81 | theme: PropTypes.string, 82 | isMobile: PropTypes.bool, 83 | collapsed: PropTypes.bool, 84 | onThemeChange: PropTypes.func, 85 | onCollapseChange: PropTypes.func, 86 | } 87 | 88 | export default Sider 89 | -------------------------------------------------------------------------------- /src/components/Layout/Sider.less: -------------------------------------------------------------------------------- 1 | @import '~themes/vars.less'; 2 | 3 | .sider { 4 | box-shadow: fade(@primary-color, 10%) 0 0 28px 0; 5 | z-index: 10; 6 | :global { 7 | .ant-layout-sider-children { 8 | display: flex; 9 | flex-direction: column; 10 | justify-content: space-between; 11 | } 12 | } 13 | } 14 | 15 | .brand { 16 | z-index: 1; 17 | height: 72px; 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | padding: 0 24px; 22 | box-shadow: 0 1px 9px -3px rgba(0, 0, 0, 0.2); 23 | .logo { 24 | display: flex; 25 | align-items: center; 26 | justify-content: center; 27 | 28 | img { 29 | width: 36px; 30 | margin-right: 8px; 31 | } 32 | 33 | h1 { 34 | vertical-align: text-bottom; 35 | font-size: 16px; 36 | text-transform: uppercase; 37 | display: inline-block; 38 | font-weight: 700; 39 | color: @primary-color; 40 | white-space: nowrap; 41 | margin-bottom: 0; 42 | .text-gradient(); 43 | 44 | :local { 45 | animation: fadeRightIn 300ms @ease-in-out; 46 | animation-fill-mode: both; 47 | } 48 | } 49 | } 50 | } 51 | 52 | .menuContainer { 53 | height: ~'calc(100vh - 120px)'; 54 | overflow-x: hidden; 55 | flex: 1; 56 | padding: 24px 0; 57 | 58 | &::-webkit-scrollbar-thumb { 59 | background-color: transparent; 60 | } 61 | 62 | &:hover { 63 | &::-webkit-scrollbar-thumb { 64 | background-color: rgba(0, 0, 0, 0.2); 65 | } 66 | } 67 | 68 | :global { 69 | .ant-menu-inline { 70 | border-right: none; 71 | } 72 | } 73 | } 74 | 75 | .switchTheme { 76 | width: 100%; 77 | height: 48px; 78 | display: flex; 79 | justify-content: space-between; 80 | align-items: center; 81 | padding: 0 16px; 82 | overflow: hidden; 83 | transition: all 0.3s; 84 | 85 | span { 86 | white-space: nowrap; 87 | overflow: hidden; 88 | font-size: 12px; 89 | } 90 | 91 | :global { 92 | .anticon { 93 | min-width: 14px; 94 | margin-right: 4px; 95 | font-size: 14px; 96 | } 97 | } 98 | } 99 | 100 | @keyframes fadeLeftIn { 101 | 0% { 102 | transform: translateX(5px); 103 | opacity: 0; 104 | } 105 | 106 | 100% { 107 | transform: translateX(0); 108 | opacity: 1; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/components/Layout/index.js: -------------------------------------------------------------------------------- 1 | import Header from './Header' 2 | import Menu from './Menu' 3 | import Bread from './Bread' 4 | import Sider from './Sider' 5 | 6 | export { Header, Menu, Bread, Sider } 7 | -------------------------------------------------------------------------------- /src/components/Loader/Loader.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import classNames from 'classnames' 4 | import styles from './Loader.less' 5 | 6 | const Loader = ({ spinning = false, fullScreen }) => { 7 | return ( 8 |
14 |
15 |
16 |
LOADING
17 |
18 |
19 | ) 20 | } 21 | 22 | Loader.propTypes = { 23 | spinning: PropTypes.bool, 24 | fullScreen: PropTypes.bool, 25 | } 26 | 27 | export default Loader 28 | -------------------------------------------------------------------------------- /src/components/Loader/Loader.less: -------------------------------------------------------------------------------- 1 | .loader { 2 | background-color: #fff; 3 | width: 100%; 4 | position: absolute; 5 | top: 0; 6 | bottom: 0; 7 | left: 0; 8 | z-index: 100000; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | opacity: 1; 13 | text-align: center; 14 | 15 | &.fullScreen { 16 | position: fixed; 17 | } 18 | 19 | .warpper { 20 | width: 100px; 21 | height: 100px; 22 | display: inline-flex; 23 | flex-direction: column; 24 | justify-content: space-around; 25 | } 26 | 27 | .inner { 28 | width: 40px; 29 | height: 40px; 30 | margin: 0 auto; 31 | text-indent: -12345px; 32 | border-top: 1px solid rgba(0, 0, 0, 0.08); 33 | border-right: 1px solid rgba(0, 0, 0, 0.08); 34 | border-bottom: 1px solid rgba(0, 0, 0, 0.08); 35 | border-left: 1px solid rgba(0, 0, 0, 0.7); 36 | border-radius: 50%; 37 | z-index: 100001; 38 | 39 | :local { 40 | animation: spinner 600ms infinite linear; 41 | } 42 | } 43 | 44 | .text { 45 | width: 100px; 46 | height: 20px; 47 | text-align: center; 48 | font-size: 12px; 49 | letter-spacing: 4px; 50 | color: #000; 51 | } 52 | 53 | &.hidden { 54 | z-index: -1; 55 | opacity: 0; 56 | transition: opacity 1s ease 0.5s, z-index 0.1s ease 1.5s; 57 | } 58 | } 59 | @keyframes spinner { 60 | 0% { 61 | transform: rotate(0deg); 62 | } 63 | 64 | 100% { 65 | transform: rotate(360deg); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/components/Loader/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Loader", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./Loader.js" 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Page/Page.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import classnames from 'classnames' 4 | import Loader from '../Loader' 5 | import styles from './Page.less' 6 | 7 | export default class Page extends Component { 8 | render() { 9 | const { className, children, loading = false, inner = false } = this.props 10 | const loadingStyle = { 11 | height: 'calc(100vh - 184px)', 12 | overflow: 'hidden', 13 | } 14 | return ( 15 |
21 | {loading ? : ''} 22 | {children} 23 |
24 | ) 25 | } 26 | } 27 | 28 | Page.propTypes = { 29 | className: PropTypes.string, 30 | children: PropTypes.node, 31 | loading: PropTypes.bool, 32 | inner: PropTypes.bool, 33 | } 34 | -------------------------------------------------------------------------------- /src/components/Page/Page.less: -------------------------------------------------------------------------------- 1 | @import '~themes/vars.less'; 2 | 3 | .contentInner { 4 | background: #fff; 5 | padding: 24px; 6 | box-shadow: @shadow-1; 7 | min-height: ~'calc(100vh - 230px)'; 8 | position: relative; 9 | } 10 | 11 | @media (max-width: 767px) { 12 | .contentInner { 13 | padding: 12px; 14 | min-height: ~'calc(100vh - 160px)'; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Page/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Page", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./Page.js" 6 | } 7 | -------------------------------------------------------------------------------- /src/components/ScrollBar/index.js: -------------------------------------------------------------------------------- 1 | import ScrollBar from 'react-perfect-scrollbar' 2 | import 'react-perfect-scrollbar/dist/css/styles.css' 3 | import './index.less' 4 | 5 | export default ScrollBar 6 | -------------------------------------------------------------------------------- /src/components/ScrollBar/index.less: -------------------------------------------------------------------------------- 1 | :global { 2 | .ps--active-x > .ps__rail-x, 3 | .ps--active-y > .ps__rail-y { 4 | background-color: transparent; 5 | } 6 | 7 | .ps__rail-x:hover > .ps__thumb-x, 8 | .ps__rail-x:focus > .ps__thumb-x { 9 | height: 8px; 10 | } 11 | 12 | .ps__rail-y:hover > .ps__thumb-y, 13 | .ps__rail-y:focus > .ps__thumb-y { 14 | width: 8px; 15 | } 16 | 17 | .ps__rail-y, 18 | .ps__rail-x { 19 | z-index: 9; 20 | } 21 | 22 | .ps__thumb-y { 23 | width: 4px; 24 | right: 4px; 25 | } 26 | 27 | .ps__thumb-x { 28 | height: 4px; 29 | bottom: 4px; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import Editor from './Editor' 2 | import FilterItem from './FilterItem' 3 | import DropOption from './DropOption' 4 | import Loader from './Loader' 5 | import ScrollBar from './ScrollBar' 6 | import GlobalFooter from './GlobalFooter' 7 | import Ellipsis from './Ellipsis' 8 | import * as MyLayout from './Layout/index.js' 9 | import Page from './Page' 10 | 11 | export { MyLayout, Editor, GlobalFooter, Ellipsis, FilterItem, DropOption, Loader, Page, ScrollBar } 12 | -------------------------------------------------------------------------------- /src/e2e/login.e2e.js: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer' 2 | 3 | describe('Login', () => { 4 | let browser 5 | let page 6 | 7 | beforeAll(async () => { 8 | browser = await puppeteer.launch({ args: ['--no-sandbox'] }) 9 | }) 10 | 11 | beforeEach(async () => { 12 | page = await browser.newPage() 13 | await page.goto('http://localhost:8000/en/login', { 14 | waitUntil: 'networkidle2', 15 | }) 16 | }) 17 | 18 | afterEach(() => page.close()) 19 | 20 | it('should login with failure', async () => { 21 | await page.waitFor(selector => !!document.querySelector('#username'), { 22 | timeout: 3000, 23 | }) 24 | await page.type('#username', 'wrong_user') 25 | await page.type('#password', 'wrong_password') 26 | await page.click('button[type="button"]') 27 | await page.waitForSelector('.anticon-close-circle') // should display error 28 | }) 29 | 30 | it('should login successfully', async () => { 31 | await page.waitForSelector('#username', { timeout: 3000 }) 32 | await page.type('#username', 'admin') 33 | await page.type('#password', 'admin') 34 | await page.click('button[type="button"]') 35 | await page.waitForSelector('.ant-layout-footer') 36 | const text = await page.evaluate(() => document.body.innerHTML) 37 | expect(text).toContain('Ant Design Admin') 38 | }) 39 | 40 | afterAll(() => browser.close()) 41 | }) 42 | -------------------------------------------------------------------------------- /src/layouts/BaseLayout.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent, Fragment } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'umi' 4 | import { Helmet } from 'react-helmet' 5 | import { Loader } from 'components' 6 | import { queryLayout } from 'utils' 7 | import NProgress from 'nprogress' 8 | import config from 'utils/config' 9 | import { withRouter } from 'umi' 10 | 11 | import PublicLayout from './PublicLayout' 12 | import PrimaryLayout from './PrimaryLayout' 13 | import './BaseLayout.less' 14 | 15 | const LayoutMap = { 16 | primary: PrimaryLayout, 17 | public: PublicLayout, 18 | } 19 | 20 | @withRouter 21 | @connect(({ loading }) => ({ loading })) 22 | class BaseLayout extends PureComponent { 23 | previousPath = '' 24 | 25 | render() { 26 | const { loading, children, location } = this.props 27 | const Container = LayoutMap[queryLayout(config.layouts, location.pathname)] 28 | 29 | const currentPath = location.pathname + location.search 30 | if (currentPath !== this.previousPath) { 31 | NProgress.start() 32 | } 33 | 34 | if (!loading.global) { 35 | NProgress.done() 36 | this.previousPath = currentPath 37 | } 38 | 39 | return ( 40 | 41 | 42 | {config.siteName} 43 | 44 | 45 | {children} 46 | 47 | ) 48 | } 49 | } 50 | 51 | BaseLayout.propTypes = { 52 | loading: PropTypes.object, 53 | } 54 | 55 | export default BaseLayout 56 | -------------------------------------------------------------------------------- /src/layouts/BaseLayout.less: -------------------------------------------------------------------------------- 1 | @import '~themes/vars.less'; 2 | @import '~themes/index.less'; 3 | 4 | :global { 5 | #nprogress { 6 | pointer-events: none; 7 | 8 | .bar { 9 | background: @primary-color; 10 | position: fixed; 11 | z-index: 2048; 12 | top: 0; 13 | left: 0; 14 | right: 0; 15 | width: 100%; 16 | height: 2px; 17 | } 18 | 19 | .peg { 20 | display: block; 21 | position: absolute; 22 | right: 0; 23 | width: 100px; 24 | height: 100%; 25 | box-shadow: 0 0 10px @primary-color, 0 0 5px @primary-color; 26 | opacity: 1; 27 | transform: rotate(3deg) translate(0, -4px); 28 | } 29 | 30 | .spinner { 31 | display: block; 32 | position: fixed; 33 | z-index: 1031; 34 | top: 15px; 35 | right: 15px; 36 | } 37 | 38 | .spinner-icon { 39 | width: 18px; 40 | height: 18px; 41 | box-sizing: border-box; 42 | border: solid 2px transparent; 43 | border-top-color: @primary-color; 44 | border-left-color: @primary-color; 45 | border-radius: 50%; 46 | 47 | :local { 48 | animation: nprogress-spinner 400ms linear infinite; 49 | } 50 | } 51 | } 52 | 53 | .nprogress-custom-parent { 54 | overflow: hidden; 55 | position: relative; 56 | 57 | #nprogress { 58 | .bar, 59 | .spinner { 60 | position: absolute; 61 | } 62 | } 63 | } 64 | } 65 | 66 | @keyframes nprogress-spinner { 67 | 0% { 68 | transform: rotate(0deg); 69 | } 70 | 71 | 100% { 72 | transform: rotate(360deg); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/layouts/PrimaryLayout.less: -------------------------------------------------------------------------------- 1 | @import '~themes/vars.less'; 2 | 3 | .backTop { 4 | right: 50px; 5 | 6 | :global { 7 | .ant-back-top-content { 8 | background: @primary-color; 9 | opacity: 0.3; 10 | transition: all 0.3s; 11 | box-shadow: 0 0 15px 1px rgba(69, 65, 78, 0.1); 12 | 13 | &:hover { 14 | opacity: 1; 15 | } 16 | } 17 | } 18 | } 19 | 20 | .content { 21 | padding: 24px; 22 | min-height: ~'calc(100% - 72px)'; 23 | // overflow-y: scroll; 24 | } 25 | 26 | .container { 27 | height: 100vh; 28 | flex: 1; 29 | width: ~'calc(100% - 256px)'; 30 | overflow-y: scroll; 31 | overflow-x: hidden; 32 | } 33 | 34 | .footer { 35 | background: #fff; 36 | margin-top: 0; 37 | margin-bottom: 0; 38 | padding-top: 24px; 39 | padding-bottom: 24px; 40 | min-height: 72px; 41 | } 42 | 43 | @media (max-width: 767px) { 44 | .content { 45 | padding: 12px; 46 | } 47 | 48 | .backTop { 49 | right: 20px; 50 | bottom: 20px; 51 | } 52 | 53 | .container { 54 | height: 100vh; 55 | flex: 1; 56 | width: 100%; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/layouts/PublicLayout.js: -------------------------------------------------------------------------------- 1 | export default ({ children }) => { 2 | return children 3 | } 4 | -------------------------------------------------------------------------------- /src/layouts/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { withRouter } from 'umi' 3 | import { ConfigProvider } from 'antd' 4 | import { i18n } from "@lingui/core" 5 | import { I18nProvider } from '@lingui/react' 6 | import { getLocale } from 'utils' 7 | import { zh, en, pt } from 'make-plural/plurals' 8 | import zhCN from 'antd/lib/locale/zh_CN' 9 | import enUS from 'antd/lib/locale/en_US' 10 | import ptBR from 'antd/lib/locale/pt_BR' 11 | 12 | import BaseLayout from './BaseLayout' 13 | 14 | i18n.loadLocaleData({ 15 | en: { plurals: en }, 16 | zh: { plurals: zh }, 17 | 'pt-br': { plurals: pt } 18 | }) 19 | 20 | // antd 21 | const languages = { 22 | zh: zhCN, 23 | en: enUS, 24 | 'pt-br': ptBR 25 | } 26 | 27 | const { defaultLanguage } = i18n 28 | 29 | @withRouter 30 | class Layout extends Component { 31 | state = { 32 | } 33 | 34 | componentDidMount() { 35 | } 36 | 37 | loadCatalog = async (lan) => { 38 | const catalog = await import( 39 | `../locales/${lan}/messages.json` 40 | ) 41 | 42 | i18n.load(lan, catalog) 43 | i18n.activate(lan) 44 | } 45 | 46 | render() { 47 | const { children } = this.props 48 | 49 | let language = getLocale() 50 | 51 | if (!languages[language]) language = defaultLanguage 52 | 53 | this.loadCatalog(language) 54 | 55 | return ( 56 | 57 | 58 | {children} 59 | 60 | 61 | ) 62 | } 63 | } 64 | 65 | export default Layout 66 | -------------------------------------------------------------------------------- /src/locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "/dashboard": "/dashboard", 3 | "Add Param": "Add Param", 4 | "Address": "Address", 5 | "Age": "Age", 6 | "Are you sure delete this record?": "Are you sure delete this record?", 7 | "Author": "Author", 8 | "Avatar": "Avatar", 9 | "Categories": "Categories", 10 | "Clear notifications": "Clear notifications", 11 | "Comments": "Comments", 12 | "Create": "Create", 13 | "Create User": "Create User", 14 | "CreateTime": "CreateTime", 15 | "Dark": "Dark", 16 | "Delete": "Delete", 17 | "Email": "Email", 18 | "Female": "Female", 19 | "Gender": "Gender", 20 | "Hi,": "Hi,", 21 | "Image": "Image", 22 | "Light": "Light", 23 | "Male": "Male", 24 | "Name": "Name", 25 | "NickName": "NickName", 26 | "Not Found": "Not Found", 27 | "Operation": "Operation", 28 | "Params": "Params", 29 | "Password": "Password", 30 | "Phone": "Phone", 31 | "Pick an address": "Pick an address", 32 | "Please pick an address": "Please pick an address", 33 | "Publised": "Publised", 34 | "Publish Date": "Publish Date", 35 | "Reset": "Reset", 36 | "Search": "Search", 37 | "Search Name": "Search Name", 38 | "Send": "Send", 39 | "Sign in": "Sign in", 40 | "Sign out": "Sign out", 41 | "Switch Theme": "Switch Theme", 42 | "Tags": "Tags", 43 | "The input is not valid E-mail!": "The input is not valid E-mail!", 44 | "The input is not valid phone!": "The input is not valid phone!", 45 | "Title": "Title", 46 | "Total {total} Items": "Total {total} Items", 47 | "Unpublished": "Unpublished", 48 | "Update": "Update", 49 | "Update User": "Update User", 50 | "Username": "Username", 51 | "Views": "Views", 52 | "Visibility": "Visibility", 53 | "You have viewed all notifications.": "You have viewed all notifications." 54 | } -------------------------------------------------------------------------------- /src/locales/pt-br/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "/dashboard": "/dashboard", 3 | "Add Param": "Add Parametro", 4 | "Address": "Endereço", 5 | "Age": "Ano", 6 | "Are you sure delete this record?": "Tem certeza de excluir este registro?", 7 | "Author": "Autor", 8 | "Avatar": "Avatar", 9 | "Categories": "Categorias", 10 | "Clear notifications": "limpar notificações", 11 | "Comments": "Comentarios", 12 | "Create": "Criar", 13 | "Create User": "Criar Usuário", 14 | "CreateTime": "CreateTime", 15 | "Dark": "Escuro", 16 | "Delete": "Deletar", 17 | "Email": "Email", 18 | "Female": "Feminino", 19 | "Gender": "Genero", 20 | "Hi,": "Olá,", 21 | "Image": "Imagem", 22 | "Light": "Claro", 23 | "Male": "masculino", 24 | "Name": "Nome", 25 | "NickName": "NickName", 26 | "Not Found": "Não Encontrado", 27 | "Operation": "Operation", 28 | "Params": "Parametros", 29 | "Password": "Senha", 30 | "Phone": "Fone", 31 | "Pick an address": "Escolha um endereço", 32 | "Please pick an address": "Por favor, escolha um endereço", 33 | "Publised": "Publicado", 34 | "Publish Date": "Data de publicação", 35 | "Reset": "Reset", 36 | "Search": "procurar", 37 | "Search Name": "Search Name", 38 | "Send": "Enviar", 39 | "Sign in": "Sign in", 40 | "Sign out": "Sign out", 41 | "Switch Theme": "Trocar tema", 42 | "Tags": "Tags", 43 | "The input is not valid E-mail!": "Não é um E-mail valido!", 44 | "The input is not valid phone!": "Não é um telefone Valido!", 45 | "Title": "Titulo", 46 | "Total {total} Items": "Total {total} Items", 47 | "Unpublished": "Não publicado", 48 | "Update": "Atualizar", 49 | "Update User": "Atualizar Usuário", 50 | "Username": "Usuário", 51 | "Views": "visualizações", 52 | "Visibility": "Visibilidade", 53 | "You have viewed all notifications.": "Você visualizou todas as notificações." 54 | } 55 | -------------------------------------------------------------------------------- /src/locales/zh/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "/dashboard": "/zh/dashboard", 3 | "Add Param": "添加参数", 4 | "Address": "地址", 5 | "Age": "年龄", 6 | "Are you sure delete this record?": "您确定要删除这条记录吗?", 7 | "Author": "作者", 8 | "Avatar": "头像", 9 | "Categories": "类别", 10 | "Clear notifications": "清空消息", 11 | "Comments": "评论数", 12 | "Create": "创建", 13 | "Create User": "创建用户", 14 | "CreateTime": "创建时间", 15 | "Dark": "暗", 16 | "Delete": "删除", 17 | "Email": "电子邮件", 18 | "Female": "女", 19 | "Gender": "性别", 20 | "Hi,": "你好,", 21 | "Image": "图像", 22 | "Light": "明", 23 | "Male": "男性", 24 | "Name": "名字", 25 | "NickName": "昵称", 26 | "Not Found": "未找到", 27 | "Operation": "操作", 28 | "Params": "参数", 29 | "Password": "密码", 30 | "Phone": "电话", 31 | "Pick an address": "选择地址", 32 | "Please pick an address": "选择地址", 33 | "Publised": "已发布", 34 | "Publish Date": "发布日期", 35 | "Reset": "重置", 36 | "Search": "搜索", 37 | "Search Name": "搜索名字", 38 | "Send": "发送", 39 | "Sign in": "登录", 40 | "Sign out": "退出登录", 41 | "Switch Theme": "切换主题", 42 | "Tags": "标签", 43 | "The input is not valid E-mail!": "输入的电子邮件无效!", 44 | "The input is not valid phone!": "输入无效的手机!", 45 | "Title": "标题", 46 | "Total {total} Items": "总共 {total} 条记录", 47 | "Unpublished": "未发布", 48 | "Update": "更新", 49 | "Update User": "更新用户", 50 | "Username": "用户名", 51 | "Views": "浏览数", 52 | "Visibility": "可见性", 53 | "You have viewed all notifications.": "您已查看所有通知" 54 | } -------------------------------------------------------------------------------- /src/pages/404.less: -------------------------------------------------------------------------------- 1 | .error { 2 | color: black; 3 | text-align: center; 4 | position: absolute; 5 | top: 30%; 6 | margin-top: -50px; 7 | left: 50%; 8 | margin-left: -100px; 9 | width: 200px; 10 | 11 | :global .anticon { 12 | font-size: 48px; 13 | margin-bottom: 16px; 14 | } 15 | 16 | h1 { 17 | font-family: cursive; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { FrownOutlined } from '@ant-design/icons' 3 | import { Page } from 'components' 4 | import styles from './404.less' 5 | 6 | const Error = () => ( 7 | 8 |
9 | 10 |

404 Not Found

11 |
12 |
13 | ) 14 | 15 | export default Error 16 | -------------------------------------------------------------------------------- /src/pages/chart/ECharts/ChartShowLoadingComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactEcharts from 'echarts-for-react' 3 | 4 | class ChartShowLoadingComponent extends React.Component { 5 | constructor() { 6 | super() 7 | this._t = null 8 | this.onChartReady = this.onChartReady.bind(this) 9 | } 10 | componentWillUnmount() { 11 | clearTimeout(this._t) 12 | } 13 | 14 | onChartReady(chart) { 15 | this._t = setTimeout(() => { 16 | chart.hideLoading() 17 | }, 3000) 18 | } 19 | 20 | render() { 21 | const getOtion = () => { 22 | const option = { 23 | title: { 24 | text: '基础雷达图', 25 | }, 26 | tooltip: {}, 27 | legend: { 28 | data: ['预算分配(Allocated Budget)', '实际开销(Actual Spending)'], 29 | }, 30 | radar: { 31 | indicator: [ 32 | { name: '销售(sales)', max: 6500 }, 33 | { name: '管理(Administration)', max: 16000 }, 34 | { name: '信息技术(Information Techology)', max: 30000 }, 35 | { name: '客服(Customer Support)', max: 38000 }, 36 | { name: '研发(Development)', max: 52000 }, 37 | { name: '市场(Marketing)', max: 25000 }, 38 | ], 39 | }, 40 | series: [ 41 | { 42 | name: '预算 vs 开销(Budget vs spending)', 43 | type: 'radar', 44 | data: [ 45 | { 46 | value: [4300, 10000, 28000, 35000, 50000, 19000], 47 | name: '预算分配(Allocated Budget)', 48 | }, 49 | { 50 | value: [5000, 14000, 28000, 31000, 42000, 21000], 51 | name: '实际开销(Actual Spending)', 52 | }, 53 | ], 54 | }, 55 | ], 56 | } 57 | return option 58 | } 59 | const getLoadingOption = () => { 60 | const option = { 61 | text: '加载中...', 62 | color: '#4413c2', 63 | textColor: '#270240', 64 | maskColor: 'rgba(194, 88, 86, 0.3)', 65 | zlevel: 0, 66 | } 67 | return option 68 | } 69 | 70 | let code = 71 | 'onChartReady: function(chart) {\n' + 72 | " 'chart.hideLoading();\n" + 73 | '}\n\n' + 74 | '' 79 | 80 | return ( 81 |
82 |
83 | 88 | 94 | 95 |
 96 |             {code}
 97 |           
98 |
99 |
100 | ) 101 | } 102 | } 103 | 104 | export default ChartShowLoadingComponent 105 | -------------------------------------------------------------------------------- /src/pages/chart/ECharts/ChartWithEventComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactEcharts from 'echarts-for-react' 3 | 4 | const ChartWithEventComponent = () => { 5 | const onChartReady = echart => { 6 | /* eslint-disable */ 7 | console.log('echart is ready', echart) 8 | } 9 | const onChartLegendselectchanged = (param, echart) => { 10 | console.log(param, echart) 11 | } 12 | const onChartClick = (param, echart) => { 13 | console.log(param, echart) 14 | } 15 | const getOtion = () => { 16 | const option = { 17 | title: { 18 | text: '某站点用户访问来源', 19 | subtext: '纯属虚构', 20 | x: 'center', 21 | }, 22 | tooltip: { 23 | trigger: 'item', 24 | formatter: '{a}
{b} : {c} ({d}%)', 25 | }, 26 | legend: { 27 | orient: 'vertical', 28 | left: 'left', 29 | data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎'], 30 | }, 31 | series: [ 32 | { 33 | name: '访问来源', 34 | type: 'pie', 35 | radius: '55%', 36 | center: ['50%', '60%'], 37 | data: [ 38 | { value: 335, name: '直接访问' }, 39 | { value: 310, name: '邮件营销' }, 40 | { value: 234, name: '联盟广告' }, 41 | { value: 135, name: '视频广告' }, 42 | { value: 1548, name: '搜索引擎' }, 43 | ], 44 | itemStyle: { 45 | emphasis: { 46 | shadowBlur: 10, 47 | shadowOffsetX: 0, 48 | shadowColor: 'rgba(0, 0, 0, 0.5)', 49 | }, 50 | }, 51 | }, 52 | ], 53 | } 54 | return option 55 | } 56 | 57 | let onEvents = { 58 | click: onChartClick, 59 | legendselectchanged: onChartLegendselectchanged, 60 | } 61 | let code = 62 | 'let onEvents = {\n' + 63 | " 'click': onChartClick,\n" + 64 | " 'legendselectchanged': onChartLegendselectchanged\n" + 65 | '}\n\n' + 66 | '' 71 | 72 | return ( 73 |
74 |
75 | 80 | 86 | 87 |
88 |           {code}
89 |         
90 |
91 |
92 | ) 93 | } 94 | 95 | export default ChartWithEventComponent 96 | -------------------------------------------------------------------------------- /src/pages/chart/ECharts/EchartsComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import SimpleChartComponent from './SimpleChartComponent' 5 | import ChartWithEventComponent from './ChartWithEventComponent' 6 | import ThemeChartComponent from './ThemeChartComponent' 7 | import ChartShowLoadingComponent from './ChartShowLoadingComponent' 8 | import ChartAPIComponent from './ChartAPIComponent' 9 | import DynamicChartComponent from './DynamicChartComponent' 10 | import MapChartComponent from './MapChartComponent' 11 | 12 | // v1.2.0 add 7 demo. 13 | import AirportCoordComponent from './AirportCoordComponent' 14 | import CalendarComponent from './CalendarComponent' 15 | import GaugeComponent from './GaugeComponent' 16 | import GCalendarComponent from './GCalendarComponent' 17 | import GraphComponent from './GraphComponent' 18 | import LunarCalendarComponent from './LunarCalendarComponent' 19 | import TreemapComponent from './TreemapComponent' 20 | import LiquidfillComponent from './LiquidfillComponent' 21 | import BubbleGradientComponent from './BubbleGradientComponent' 22 | import TransparentBar3DComPonent from './TransparentBar3DComPonent' 23 | 24 | const EchartsComponent = ({ type }) => { 25 | if (type === 'simple') return 26 | if (type === 'loading') return 27 | if (type === 'api') return 28 | if (type === 'events') return 29 | if (type === 'theme') return 30 | if (type === 'dynamic') return 31 | if (type === 'map') return 32 | if (type === 'airport') return 33 | if (type === 'graph') return 34 | if (type === 'calendar') return 35 | if (type === 'treemap') return 36 | if (type === 'gauge') return 37 | if (type === 'gcalendar') return 38 | if (type === 'lunar') return 39 | if (type === 'liquid') return 40 | if (type === 'BubbleGradientComponent') return 41 | if (type === 'TransparentBar3DComPonent') return 42 | return 43 | } 44 | 45 | EchartsComponent.propTypes = { 46 | type: PropTypes.string, 47 | } 48 | 49 | export default EchartsComponent 50 | -------------------------------------------------------------------------------- /src/pages/chart/ECharts/GCalendarComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactEcharts from 'echarts-for-react' 3 | import * as echarts from 'echarts' 4 | 5 | const GCalendarComponent = () => { 6 | const getVirtulData = year => { 7 | year = year || '2017' 8 | let date = +echarts.number.parseDate(`${year}-01-01`) 9 | let end = +echarts.number.parseDate(`${+year + 1}-01-01`) 10 | let dayTime = 3600 * 24 * 1000 11 | let data = [] 12 | for (let time = date; time < end; time += dayTime) { 13 | data.push([ 14 | echarts.format.formatTime('yyyy-MM-dd', time), 15 | Math.floor(Math.random() * 1000), 16 | ]) 17 | } 18 | return data 19 | } 20 | 21 | const option = { 22 | tooltip: { 23 | position: 'top', 24 | }, 25 | visualMap: { 26 | min: 0, 27 | max: 1000, 28 | calculable: true, 29 | orient: 'horizontal', 30 | left: 'center', 31 | top: 'top', 32 | }, 33 | 34 | calendar: [ 35 | { 36 | range: '2017', 37 | cellSize: ['auto', 20], 38 | }, 39 | { 40 | top: 260, 41 | range: '2016', 42 | cellSize: ['auto', 20], 43 | }, 44 | ], 45 | 46 | series: [ 47 | { 48 | type: 'heatmap', 49 | coordinateSystem: 'calendar', 50 | calendarIndex: 0, 51 | data: getVirtulData(2017), 52 | }, 53 | { 54 | type: 'heatmap', 55 | coordinateSystem: 'calendar', 56 | calendarIndex: 1, 57 | data: getVirtulData(2016), 58 | }, 59 | ], 60 | } 61 | 62 | return ( 63 |
64 |
65 | 66 | 71 |
72 |
73 | ) 74 | } 75 | 76 | export default GCalendarComponent 77 | -------------------------------------------------------------------------------- /src/pages/chart/ECharts/LiquidfillComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactEcharts from 'echarts-for-react' 3 | 4 | require('echarts-liquidfill') 5 | 6 | const LiquidfillComponent = () => { 7 | const option = { 8 | series: [ 9 | { 10 | type: 'liquidFill', 11 | data: [0.6], 12 | }, 13 | ], 14 | } 15 | return ( 16 |
17 |
18 | 19 | 27 |
28 |
29 | ) 30 | } 31 | 32 | export default LiquidfillComponent 33 | -------------------------------------------------------------------------------- /src/pages/chart/ECharts/MainPageComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AdSense from 'react-adsense' 3 | import { Link } from 'umi' 4 | import DynamicChartComponent from './DynamicChartComponent.js' 5 | 6 | const MainPageComponent = () => { 7 | return ( 8 |
9 |

echarts-for-react {this.props.params.type}

10 |

11 | {' '} 12 | A very simple echarts(v3.0) wrapper for React.{' '} 13 | 14 | hustcc/echarts-for-react 15 | 16 |

17 | 18 | 19 | 20 |

21 | Simple demo | 22 | Echarts loading | 23 | Echarts API | 24 | Echarts events | 25 | Echarts theme | 26 | Dynamic chart | 27 | Map chart 28 |

29 |

30 | New 31 | :   32 | Airport | 33 | Graph | 34 | Calendar | 35 | Treemap | 36 | Gauge | 37 | GCalendar | 38 | Lunar | 39 | Liquidfill 40 |

41 | {this.props.children || } 42 | 43 |

44 | Get it on GitHub!{' '} 45 | 46 | hustcc/echarts-for-react 47 | 48 |

49 |
50 | ) 51 | } 52 | 53 | export default MainPageComponent 54 | -------------------------------------------------------------------------------- /src/pages/chart/ECharts/ModuleLoadChartComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactEcharts from 'echarts-for-react' 3 | 4 | const ModuleLoadChartComponent = () => { 5 | const option = { 6 | title: { text: 'ECharts 入门示例' }, 7 | tooltip: {}, 8 | xAxis: { 9 | data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'], 10 | }, 11 | yAxis: {}, 12 | series: [ 13 | { 14 | name: '销量', 15 | type: 'bar', 16 | data: [5, 20, 36, 10, 10, 20], 17 | }, 18 | ], 19 | } 20 | 21 | let code = 22 | '" 27 | return ( 28 |
29 |
30 | 36 | 46 | 47 |
48 |           {code}
49 |         
50 |
51 |
52 | ) 53 | } 54 | 55 | export default ModuleLoadChartComponent 56 | -------------------------------------------------------------------------------- /src/pages/chart/ECharts/SimpleChartComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactEcharts from 'echarts-for-react' 3 | import './theme/macarons.js' 4 | 5 | const SimpleChartComponent = () => { 6 | const option = { 7 | title: { 8 | text: '堆叠区域图', 9 | }, 10 | tooltip: { 11 | trigger: 'axis', 12 | }, 13 | legend: { 14 | data: ['邮件营销', '联盟广告', '视频广告'], 15 | }, 16 | toolbox: { 17 | feature: { 18 | saveAsImage: {}, 19 | }, 20 | }, 21 | grid: { 22 | left: '3%', 23 | right: '4%', 24 | bottom: '3%', 25 | containLabel: true, 26 | }, 27 | xAxis: [ 28 | { 29 | type: 'category', 30 | boundaryGap: false, 31 | data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], 32 | }, 33 | ], 34 | yAxis: [ 35 | { 36 | type: 'value', 37 | }, 38 | ], 39 | series: [ 40 | { 41 | name: '邮件营销', 42 | type: 'line', 43 | stack: '总量', 44 | areaStyle: { normal: {} }, 45 | data: [120, 132, 101, 134, 90, 230, 210], 46 | }, 47 | { 48 | name: '联盟广告', 49 | type: 'line', 50 | stack: '总量', 51 | areaStyle: { normal: {} }, 52 | data: [220, 182, 191, 234, 290, 330, 310], 53 | }, 54 | { 55 | name: '视频广告', 56 | type: 'line', 57 | stack: '总量', 58 | areaStyle: { normal: {} }, 59 | data: [150, 232, 201, 154, 190, 330, 410], 60 | }, 61 | ], 62 | } 63 | let code = 64 | '" 68 | return ( 69 |
70 |
71 | 75 | 81 | 82 |
83 |           {code}
84 |         
85 |
86 |
87 | ) 88 | } 89 | 90 | export default SimpleChartComponent 91 | -------------------------------------------------------------------------------- /src/pages/chart/ECharts/ThemeChartComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactEcharts from 'echarts-for-react' 3 | 4 | import * as echarts from 'echarts' 5 | 6 | const ThemeChartComponent = () => { 7 | const option = { 8 | title: { 9 | text: '阶梯瀑布图', 10 | subtext: 'From ExcelHome', 11 | sublink: 'http://e.weibo.com/1341556070/Aj1J2x5a5', 12 | }, 13 | tooltip: { 14 | trigger: 'axis', 15 | axisPointer: { 16 | // 坐标轴指示器,坐标轴触发有效 17 | type: 'shadow', // 默认为直线,可选为:'line' | 'shadow' 18 | }, 19 | }, 20 | legend: { 21 | data: ['支出', '收入'], 22 | }, 23 | grid: { 24 | left: '3%', 25 | right: '4%', 26 | bottom: '3%', 27 | containLabel: true, 28 | }, 29 | xAxis: { 30 | type: 'category', 31 | splitLine: { show: false }, 32 | data: [ 33 | '11月1日', 34 | '11月2日', 35 | '11月3日', 36 | '11月4日', 37 | '11月5日', 38 | '11月6日', 39 | '11月7日', 40 | '11月8日', 41 | '11月9日', 42 | '11月10日', 43 | '11月11日', 44 | ], 45 | }, 46 | yAxis: { 47 | type: 'value', 48 | }, 49 | series: [ 50 | { 51 | name: '辅助', 52 | type: 'bar', 53 | stack: '总量', 54 | itemStyle: { 55 | normal: { 56 | barBorderColor: 'rgba(0,0,0,0)', 57 | color: 'rgba(0,0,0,0)', 58 | }, 59 | emphasis: { 60 | barBorderColor: 'rgba(0,0,0,0)', 61 | color: 'rgba(0,0,0,0)', 62 | }, 63 | }, 64 | data: [0, 900, 1245, 1530, 1376, 1376, 1511, 1689, 1856, 1495, 1292], 65 | }, 66 | { 67 | name: '收入', 68 | type: 'bar', 69 | stack: '总量', 70 | label: { 71 | normal: { 72 | show: true, 73 | position: 'top', 74 | }, 75 | }, 76 | data: [900, 345, 393, '-', '-', 135, 178, 286, '-', '-', '-'], 77 | }, 78 | { 79 | name: '支出', 80 | type: 'bar', 81 | stack: '总量', 82 | label: { 83 | normal: { 84 | show: true, 85 | position: 'bottom', 86 | }, 87 | }, 88 | data: ['-', '-', '-', 108, 154, '-', '-', '-', 119, 361, 203], 89 | }, 90 | ], 91 | } 92 | 93 | echarts.registerTheme('my_theme', { 94 | backgroundColor: '#f4cccc', 95 | }) 96 | 97 | let code = 98 | "echarts.registerTheme('my_theme', {\n" + 99 | " backgroundColor: '#f4cccc'\n" + 100 | '});\n\n' + 101 | '" 104 | return ( 105 |
106 |
107 | 113 | 114 | 119 |
120 |           {code}
121 |         
122 |
123 |
124 | ) 125 | } 126 | 127 | export default ThemeChartComponent 128 | -------------------------------------------------------------------------------- /src/pages/chart/ECharts/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Radio } from 'antd' 3 | import { Page } from 'components' 4 | import EchartsComponent from './EchartsComponent' 5 | import styles from './index.less' 6 | 7 | const RadioGroup = Radio.Group 8 | 9 | const chartList = [ 10 | { 11 | label: 'SimpleChart', 12 | value: 'simple', 13 | }, 14 | { 15 | label: 'ChartShowLoading', 16 | value: 'loading', 17 | }, 18 | { 19 | label: 'ChartAPI', 20 | value: 'api', 21 | }, 22 | { 23 | label: 'ChartWithEvent', 24 | value: 'events', 25 | }, 26 | { 27 | label: 'ThemeChart', 28 | value: 'theme', 29 | }, 30 | { 31 | label: 'DynamicChart', 32 | value: 'dynamic', 33 | }, 34 | { 35 | label: 'MapChart', 36 | value: 'map', 37 | }, 38 | { 39 | label: 'AirportCoord', 40 | value: 'airport', 41 | }, 42 | { 43 | label: 'Graph', 44 | value: 'graph', 45 | }, 46 | { 47 | label: 'Calendar', 48 | value: 'calendar', 49 | }, 50 | { 51 | label: 'Treemap', 52 | value: 'treemap', 53 | }, 54 | { 55 | label: 'Gauge', 56 | value: 'gauge', 57 | }, 58 | { 59 | label: 'GCalendar', 60 | value: 'gcalendar', 61 | }, 62 | { 63 | label: 'LunarCalendar', 64 | value: 'lunar', 65 | }, 66 | { 67 | label: 'Liquidfill', 68 | value: 'liquid', 69 | }, 70 | { 71 | label: 'BubbleGradient', 72 | value: 'BubbleGradientComponent', 73 | }, 74 | { 75 | label: 'TransparentBar3D', 76 | value: 'TransparentBar3DComPonent', 77 | }, 78 | ] 79 | 80 | class Chart extends React.Component { 81 | constructor() { 82 | super() 83 | this.state = { 84 | type: '', 85 | } 86 | this.handleRadioGroupChange = this.handleRadioGroupChange.bind(this) 87 | } 88 | handleRadioGroupChange(e) { 89 | this.setState({ 90 | type: e.target.value, 91 | }) 92 | } 93 | render() { 94 | return ( 95 | 96 | 101 |
102 | 103 |
104 |
105 | All demos from{' '} 106 | 107 | https://github.com/hustcc/echarts-for-react 108 | 109 |
110 |
111 | ) 112 | } 113 | } 114 | 115 | export default Chart 116 | -------------------------------------------------------------------------------- /src/pages/chart/ECharts/index.less: -------------------------------------------------------------------------------- 1 | .chart { 2 | label { 3 | margin: 24px 0; 4 | display: block; 5 | font-size: 14px; 6 | } 7 | 8 | pre { 9 | padding: 16px; 10 | overflow: auto; 11 | font-size: 12px; 12 | line-height: 2; 13 | background-color: #f6f8fa; 14 | border-radius: 3px; 15 | 16 | code { 17 | display: inline; 18 | max-width: auto; 19 | padding: 0; 20 | margin: 0; 21 | overflow: visible; 22 | line-height: inherit; 23 | word-wrap: normal; 24 | background-color: transparent; 25 | border: 0; 26 | } 27 | } 28 | } 29 | 30 | :global { 31 | .ant-radio-wrapper { 32 | margin-bottom: 16px; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/pages/chart/Recharts/Container.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { ResponsiveContainer } from 'recharts' 4 | import styles from './Container.less' 5 | 6 | const Container = ({ 7 | children, 8 | ratio = 5 / 2, 9 | minHeight = 250, 10 | maxHeight = 350, 11 | }) => ( 12 |
13 |
14 |
15 | {children} 16 |
17 |
18 | ) 19 | 20 | Container.propTypes = { 21 | children: PropTypes.element.isRequired, 22 | ratio: PropTypes.number, 23 | minHeight: PropTypes.number, 24 | maxHeight: PropTypes.number, 25 | } 26 | 27 | export default Container 28 | -------------------------------------------------------------------------------- /src/pages/chart/Recharts/Container.less: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | position: relative; 4 | display: inline-block; 5 | 6 | :global { 7 | .recharts-responsive-container { 8 | width: e('calc(100% + 56px)') !important; 9 | margin-left: -32px; 10 | } 11 | } 12 | } 13 | 14 | .content { 15 | position: absolute; 16 | left: 0; 17 | right: 0; 18 | top: 0; 19 | bottom: 0; 20 | } 21 | -------------------------------------------------------------------------------- /src/pages/chart/Recharts/ReChartsComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import AreaChartComponent from './AreaChartComponent' 5 | import BarChartComponent from './BarChartComponent' 6 | import LineChartComponent from './LineChartComponent' 7 | 8 | const ReChartsComponent = ({ type }) => { 9 | if (type === 'areaChart') return 10 | if (type === 'barChart') return 11 | return 12 | } 13 | 14 | ReChartsComponent.propTypes = { 15 | type: PropTypes.string, 16 | } 17 | 18 | export default ReChartsComponent 19 | -------------------------------------------------------------------------------- /src/pages/chart/Recharts/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Radio } from 'antd' 3 | import { Page } from 'components' 4 | import ReChartsComponent from './ReChartsComponent' 5 | import styles from './index.less' 6 | 7 | const RadioGroup = Radio.Group 8 | 9 | const chartList = [ 10 | { 11 | label: 'lineChart', 12 | value: 'lineChart', 13 | }, 14 | { 15 | label: 'barChart', 16 | value: 'barChart', 17 | }, 18 | { 19 | label: 'areaChart', 20 | value: 'areaChart', 21 | }, 22 | ] 23 | 24 | class Chart extends React.Component { 25 | constructor() { 26 | super() 27 | this.state = { 28 | type: '', 29 | } 30 | this.handleRadioGroupChange = this.handleRadioGroupChange.bind(this) 31 | } 32 | handleRadioGroupChange(e) { 33 | this.setState({ 34 | type: e.target.value, 35 | }) 36 | } 37 | render() { 38 | return ( 39 | 40 | 45 |
46 | 47 |
48 |
49 | ) 50 | } 51 | } 52 | 53 | export default Chart 54 | -------------------------------------------------------------------------------- /src/pages/chart/Recharts/index.less: -------------------------------------------------------------------------------- 1 | .chart { 2 | :global { 3 | .ant-card { 4 | overflow: hidden; 5 | margin-bottom: 24px; 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/pages/chart/highCharts/HighChartsComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import HighstockComponent from './HighstockComponent' 5 | import HighmapsComponent from './HighmapsComponent' 6 | import HighMoreComponent from './HighMoreComponent' 7 | 8 | const HighChartsComponent = ({ type }) => { 9 | if (type === 'Highmaps') return 10 | if (type === 'HighMore') return 11 | return 12 | } 13 | 14 | HighChartsComponent.propTypes = { 15 | type: PropTypes.string, 16 | } 17 | 18 | export default HighChartsComponent 19 | -------------------------------------------------------------------------------- /src/pages/chart/highCharts/HighMoreComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactHighcharts from 'react-highcharts' 3 | import HighchartsExporting from 'highcharts-exporting' 4 | import HighchartsMore from 'highcharts-more' 5 | 6 | HighchartsMore(ReactHighcharts.Highcharts) 7 | HighchartsExporting(ReactHighcharts.Highcharts) 8 | 9 | const config = { 10 | chart: { 11 | polar: true, 12 | }, 13 | xAxis: { 14 | categories: [ 15 | 'Jan', 16 | 'Feb', 17 | 'Mar', 18 | 'Apr', 19 | 'May', 20 | 'Jun', 21 | 'Jul', 22 | 'Aug', 23 | 'Sep', 24 | 'Oct', 25 | 'Nov', 26 | 'Dec', 27 | ], 28 | }, 29 | series: [ 30 | { 31 | data: [ 32 | 29.9, 33 | 71.5, 34 | 106.4, 35 | 129.2, 36 | 144.0, 37 | 176.0, 38 | 135.6, 39 | 148.5, 40 | 216.4, 41 | 194.1, 42 | 95.6, 43 | 54.4, 44 | ], 45 | }, 46 | ], 47 | } 48 | 49 | const HighMoreComponent = () => { 50 | return 51 | } 52 | 53 | export default HighMoreComponent 54 | -------------------------------------------------------------------------------- /src/pages/chart/highCharts/HighmapsComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactHighmaps from 'react-highcharts/ReactHighmaps.src' 3 | import maps from './mapdata/europe' 4 | 5 | const config = { 6 | chart: { 7 | spacingBottom: 20, 8 | }, 9 | title: { 10 | text: 'Europe time zones', 11 | }, 12 | 13 | legend: { 14 | enabled: true, 15 | }, 16 | 17 | plotOptions: { 18 | map: { 19 | allAreas: false, 20 | joinBy: ['iso-a2', 'code'], 21 | dataLabels: { 22 | enabled: true, 23 | color: 'white', 24 | style: { 25 | fontWeight: 'bold', 26 | }, 27 | }, 28 | mapData: maps, 29 | tooltip: { 30 | headerFormat: '', 31 | pointFormat: '{point.name}: {series.name}', 32 | }, 33 | }, 34 | }, 35 | 36 | series: [ 37 | { 38 | name: 'UTC', 39 | data: ['IE', 'IS', 'GB', 'PT'].map(code => { 40 | return { code } 41 | }), 42 | }, 43 | { 44 | name: 'UTC + 1', 45 | data: [ 46 | 'NO', 47 | 'SE', 48 | 'DK', 49 | 'DE', 50 | 'NL', 51 | 'BE', 52 | 'LU', 53 | 'ES', 54 | 'FR', 55 | 'PL', 56 | 'CZ', 57 | 'AT', 58 | 'CH', 59 | 'LI', 60 | 'SK', 61 | 'HU', 62 | 'SI', 63 | 'IT', 64 | 'SM', 65 | 'HR', 66 | 'BA', 67 | 'YF', 68 | 'ME', 69 | 'AL', 70 | 'MK', 71 | ].map(code => { 72 | return { code } 73 | }), 74 | }, 75 | ], 76 | } 77 | 78 | const HighmapsComponent = () => { 79 | return 80 | } 81 | export default HighmapsComponent 82 | -------------------------------------------------------------------------------- /src/pages/chart/highCharts/HighstockComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactHighstock from 'react-highcharts/ReactHighstock.src' 3 | 4 | const data = [ 5 | [1220832000000, 22.56], 6 | [1220918400000, 21.67], 7 | [1221004800000, 21.66], 8 | [1221091200000, 21.81], 9 | [1221177600000, 21.28], 10 | [1221436800000, 20.05], 11 | [1221523200000, 19.98], 12 | [1221609600000, 18.26], 13 | [1221696000000, 19.16], 14 | [1221782400000, 20.13], 15 | [1222041600000, 18.72], 16 | [1222128000000, 18.12], 17 | [1222214400000, 18.39], 18 | [1222300800000, 18.85], 19 | [1222387200000, 18.32], 20 | [1222646400000, 15.04], 21 | [1222732800000, 16.24], 22 | [1222819200000, 15.59], 23 | [1222905600000, 14.3], 24 | [1222992000000, 13.87], 25 | [1223251200000, 14.02], 26 | [1223337600000, 12.74], 27 | [1223424000000, 12.83], 28 | [1223510400000, 12.68], 29 | [1223596800000, 13.8], 30 | [1223856000000, 15.75], 31 | [1223942400000, 14.87], 32 | [1224028800000, 13.99], 33 | [1224115200000, 14.56], 34 | [1224201600000, 13.91], 35 | [1224460800000, 14.06], 36 | [1224547200000, 13.07], 37 | [1224633600000, 13.84], 38 | [1224720000000, 14.03], 39 | [1224806400000, 13.77], 40 | [1225065600000, 13.16], 41 | [1225152000000, 14.27], 42 | [1225238400000, 14.94], 43 | [1225324800000, 15.86], 44 | [1225411200000, 15.37], 45 | [1225670400000, 15.28], 46 | [1225756800000, 15.86], 47 | [1225843200000, 14.76], 48 | [1225929600000, 14.16], 49 | [1226016000000, 14.03], 50 | [1226275200000, 13.7], 51 | [1226361600000, 13.54], 52 | [1226448000000, 12.87], 53 | [1226534400000, 13.78], 54 | [1226620800000, 12.89], 55 | [1226880000000, 12.59], 56 | [1226966400000, 12.84], 57 | [1227052800000, 12.33], 58 | [1227139200000, 11.5], 59 | [1227225600000, 11.8], 60 | [1227484800000, 13.28], 61 | [1227571200000, 12.97], 62 | [1227657600000, 13.57], 63 | [1227830400000, 13.24], 64 | [1228089600000, 12.7], 65 | [1228176000000, 13.21], 66 | [1228262400000, 13.7], 67 | [1228348800000, 13.06], 68 | [1228435200000, 13.43], 69 | [1228694400000, 14.25], 70 | [1228780800000, 14.29], 71 | [1228867200000, 14.03], 72 | [1228953600000, 13.57], 73 | [1229040000000, 14.04], 74 | [1229299200000, 13.54], 75 | ] 76 | 77 | const config = { 78 | rangeSelector: { 79 | selected: 1, 80 | }, 81 | title: { 82 | text: 'AAPL Stock Price', 83 | }, 84 | series: [ 85 | { 86 | name: 'AAPL', 87 | data, 88 | tooltip: { 89 | valueDecimals: 2, 90 | }, 91 | }, 92 | ], 93 | } 94 | 95 | const HighstockComponent = () => { 96 | return 97 | } 98 | 99 | export default HighstockComponent 100 | -------------------------------------------------------------------------------- /src/pages/chart/highCharts/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Radio } from 'antd' 3 | import { Page } from 'components' 4 | import HighChartsComponent from './HighChartsComponent' 5 | import styles from './index.less' 6 | 7 | const RadioGroup = Radio.Group 8 | 9 | const chartList = [ 10 | { 11 | label: 'Highstock', 12 | value: 'Highstock', 13 | }, 14 | { 15 | label: 'Highmaps', 16 | value: 'Highmaps', 17 | }, 18 | { 19 | label: 'HighMore', 20 | value: 'HighMore', 21 | }, 22 | ] 23 | 24 | class Chart extends React.Component { 25 | constructor() { 26 | super() 27 | this.state = { 28 | type: '', 29 | } 30 | this.handleRadioGroupChange = this.handleRadioGroupChange.bind(this) 31 | } 32 | handleRadioGroupChange(e) { 33 | this.setState({ 34 | type: e.target.value, 35 | }) 36 | } 37 | render() { 38 | return ( 39 | 40 | 45 |
46 | 47 |
48 |
49 | ) 50 | } 51 | } 52 | 53 | export default Chart 54 | -------------------------------------------------------------------------------- /src/pages/chart/highCharts/index.less: -------------------------------------------------------------------------------- 1 | .chart { 2 | :global { 3 | .ant-radio-wrapper { 4 | margin-bottom: 16px; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/browser.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Table, Tag } from 'antd' 4 | import { Color } from 'utils' 5 | import styles from './browser.less' 6 | 7 | const status = { 8 | 1: { 9 | color: Color.green, 10 | }, 11 | 2: { 12 | color: Color.red, 13 | }, 14 | 3: { 15 | color: Color.blue, 16 | }, 17 | 4: { 18 | color: Color.yellow, 19 | }, 20 | } 21 | 22 | function Browser({ data }) { 23 | const columns = [ 24 | { 25 | title: 'name', 26 | dataIndex: 'name', 27 | className: styles.name, 28 | }, 29 | { 30 | title: 'percent', 31 | dataIndex: 'percent', 32 | className: styles.percent, 33 | render: (text, it) => {text}%, 34 | }, 35 | ] 36 | return ( 37 | 44 | ) 45 | } 46 | 47 | Browser.propTypes = { 48 | data: PropTypes.array, 49 | } 50 | 51 | export default Browser 52 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/browser.less: -------------------------------------------------------------------------------- 1 | .percent { 2 | text-align: right !important; 3 | } 4 | 5 | .name { 6 | text-align: left !important; 7 | } 8 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/comments.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Table, Tag } from 'antd' 4 | import { Color } from 'utils' 5 | import styles from './comments.less' 6 | 7 | const status = { 8 | 1: { 9 | color: Color.green, 10 | text: 'APPROVED', 11 | }, 12 | 2: { 13 | color: Color.yellow, 14 | text: 'PENDING', 15 | }, 16 | 3: { 17 | color: Color.red, 18 | text: 'REJECTED', 19 | }, 20 | } 21 | 22 | function Comments({ data }) { 23 | const columns = [ 24 | { 25 | title: 'avatar', 26 | dataIndex: 'avatar', 27 | width: 48, 28 | className: styles.avatarcolumn, 29 | render: text => ( 30 | 34 | ), 35 | }, 36 | { 37 | title: 'content', 38 | dataIndex: 'content', 39 | render: (text, it) => ( 40 |
41 |
{it.name}
42 |

{it.content}

43 |
44 | {status[it.status].text} 45 | {it.date} 46 |
47 |
48 | ), 49 | }, 50 | ] 51 | return ( 52 |
53 |
key < 3)} 59 | /> 60 | 61 | ) 62 | } 63 | 64 | Comments.propTypes = { 65 | data: PropTypes.array, 66 | } 67 | 68 | export default Comments 69 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/comments.less: -------------------------------------------------------------------------------- 1 | @import '~themes/vars'; 2 | 3 | .comments { 4 | :global .ant-table-thead > tr > th { 5 | background: #fff; 6 | border-bottom: solid 1px @border-color-base; 7 | } 8 | 9 | .avatar { 10 | width: 48px; 11 | height: 48px; 12 | background-position: center; 13 | background-size: cover; 14 | border-radius: 50%; 15 | background: #f8f8f8; 16 | display: inline-block; 17 | } 18 | 19 | .content { 20 | text-align: left; 21 | color: #757575; 22 | } 23 | 24 | .date { 25 | color: #a3a3a3; 26 | line-height: 30px; 27 | } 28 | 29 | .daterow { 30 | display: flex; 31 | justify-content: space-between; 32 | } 33 | 34 | .name { 35 | font-size: 14px; 36 | color: #474747; 37 | text-align: left; 38 | } 39 | 40 | .avatarcolumn { 41 | vertical-align: top; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/completed.less: -------------------------------------------------------------------------------- 1 | @import '~themes/vars'; 2 | 3 | .sales { 4 | .title { 5 | margin-left: 32px; 6 | font-size: 16px; 7 | } 8 | } 9 | 10 | .radiusdot { 11 | width: 12px; 12 | height: 12px; 13 | margin-right: 8px; 14 | border-radius: 50%; 15 | display: inline-block; 16 | } 17 | 18 | .legend { 19 | text-align: right; 20 | color: #999; 21 | font-size: 14px; 22 | 23 | li { 24 | height: 48px; 25 | line-height: 48px; 26 | display: inline-block; 27 | 28 | & + li { 29 | margin-left: 24px; 30 | } 31 | } 32 | } 33 | 34 | .tooltip { 35 | background: #fff; 36 | padding: 20px; 37 | font-size: 14px; 38 | 39 | .tiptitle { 40 | font-weight: 700; 41 | font-size: 16px; 42 | margin-bottom: 8px; 43 | } 44 | 45 | .tipitem { 46 | height: 32px; 47 | line-height: 32px; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/cpu.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Color } from 'utils' 4 | import CountUp from 'react-countup' 5 | import { 6 | LineChart, 7 | Line, 8 | XAxis, 9 | YAxis, 10 | CartesianGrid, 11 | ResponsiveContainer, 12 | } from 'recharts' 13 | import styles from './cpu.less' 14 | 15 | const countUpProps = { 16 | start: 0, 17 | duration: 2.75, 18 | useEasing: true, 19 | useGrouping: true, 20 | separator: ',', 21 | } 22 | 23 | function Cpu({ usage = 0, space = 0, cpu = 0, data }) { 24 | return ( 25 |
26 |
27 |
28 |

usage

29 |

30 | 31 |

32 |
33 |
34 |

space

35 |

36 | 37 |

38 |
39 |
40 |

cpu

41 |

42 | 43 |

44 |
45 |
46 | 47 | 48 | 53 | 54 | 59 | 66 | 67 | 68 |
69 | ) 70 | } 71 | 72 | Cpu.propTypes = { 73 | data: PropTypes.array, 74 | usage: PropTypes.number, 75 | space: PropTypes.number, 76 | cpu: PropTypes.number, 77 | } 78 | 79 | export default Cpu 80 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/cpu.less: -------------------------------------------------------------------------------- 1 | .cpu { 2 | .number { 3 | display: flex; 4 | height: 64px; 5 | justify-content: space-between; 6 | margin-bottom: 32px; 7 | 8 | .item { 9 | text-align: center; 10 | height: 64px; 11 | width: 100%; 12 | position: relative; 13 | 14 | & + .item { 15 | &::before { 16 | content: ''; 17 | display: block; 18 | width: 1px; 19 | height: 40px; 20 | position: absolute; 21 | background: #f5f5f5; 22 | top: 12px; 23 | } 24 | } 25 | 26 | p { 27 | color: #757575; 28 | 29 | &:first-child { 30 | font-size: 16px; 31 | } 32 | 33 | &:last-child { 34 | font-size: 20px; 35 | font-weight: 700; 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/index.js: -------------------------------------------------------------------------------- 1 | import NumberCard from './numberCard' 2 | import Quote from './quote' 3 | import Sales from './sales' 4 | import Weather from './weather' 5 | import RecentSales from './recentSales' 6 | import Comments from './comments' 7 | import Completed from './completed' 8 | import Browser from './browser' 9 | import Cpu from './cpu' 10 | import User from './user' 11 | 12 | export { 13 | NumberCard, 14 | Quote, 15 | Sales, 16 | Weather, 17 | RecentSales, 18 | Comments, 19 | Completed, 20 | Browser, 21 | Cpu, 22 | User, 23 | } 24 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/numberCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Card } from 'antd' 4 | import CountUp from 'react-countup' 5 | import iconMap from 'utils/iconMap' 6 | import styles from './numberCard.less' 7 | 8 | 9 | function NumberCard({ icon, color, title, number, countUp }) { 10 | return ( 11 | 16 | 17 | {iconMap[icon]} 18 | 19 |
20 |

{title || 'No Title'}

21 |

22 | 31 |

32 |
33 |
34 | ) 35 | } 36 | 37 | NumberCard.propTypes = { 38 | icon: PropTypes.string, 39 | color: PropTypes.string, 40 | title: PropTypes.string, 41 | number: PropTypes.number, 42 | countUp: PropTypes.object, 43 | } 44 | 45 | export default NumberCard 46 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/numberCard.less: -------------------------------------------------------------------------------- 1 | @import '~themes/vars'; 2 | 3 | .numberCard { 4 | padding: 32px; 5 | margin-bottom: 24px; 6 | cursor: pointer; 7 | 8 | .iconWarp { 9 | font-size: 54px; 10 | float: left; 11 | } 12 | 13 | .content { 14 | width: 100%; 15 | padding-left: 78px; 16 | 17 | .title { 18 | line-height: 16px; 19 | font-size: 16px; 20 | margin-bottom: 8px; 21 | height: 16px; 22 | .text-overflow(); 23 | } 24 | 25 | .number { 26 | line-height: 32px; 27 | font-size: 24px; 28 | height: 32px; 29 | .text-overflow(); 30 | margin-bottom: 0; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/quote.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import styles from './quote.less' 4 | 5 | function Quote({ name, content, title, avatar }) { 6 | return ( 7 |
8 |
{content}
9 |
10 |
11 |

-{name}-

12 |

{title}

13 |
14 |
18 |
19 |
20 | ) 21 | } 22 | 23 | Quote.propTypes = { 24 | name: PropTypes.string, 25 | content: PropTypes.string, 26 | title: PropTypes.string, 27 | avatar: PropTypes.string, 28 | } 29 | 30 | export default Quote 31 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/quote.less: -------------------------------------------------------------------------------- 1 | @import '~themes/vars'; 2 | 3 | .quote { 4 | color: #fff; 5 | height: 100%; 6 | width: 100%; 7 | padding: 24px; 8 | font-size: 16px; 9 | font-weight: 700; 10 | 11 | .inner { 12 | text-overflow: ellipsis; 13 | word-wrap: normal; 14 | display: -webkit-box; 15 | -webkit-box-orient: vertical; 16 | -webkit-line-clamp: 4; 17 | overflow: hidden; 18 | text-indent: 24px; 19 | } 20 | 21 | .footer { 22 | position: relative; 23 | margin-top: 14px; 24 | 25 | .description { 26 | width: 100%; 27 | 28 | p { 29 | overflow: hidden; 30 | text-overflow: ellipsis; 31 | white-space: nowrap; 32 | margin-right: 64px; 33 | text-align: right; 34 | 35 | &:last-child { 36 | font-weight: 100; 37 | } 38 | } 39 | } 40 | 41 | .avatar { 42 | width: 48px; 43 | height: 48px; 44 | background-position: center; 45 | background-size: cover; 46 | border-radius: 50%; 47 | position: absolute; 48 | right: 0; 49 | top: 0; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/recentSales.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import dayjs from 'dayjs' 3 | import PropTypes from 'prop-types' 4 | import { Table, Tag } from 'antd' 5 | import { Color } from 'utils' 6 | import styles from './recentSales.less' 7 | 8 | const status = { 9 | 1: { 10 | color: Color.green, 11 | text: 'SALE', 12 | }, 13 | 2: { 14 | color: Color.yellow, 15 | text: 'REJECT', 16 | }, 17 | 3: { 18 | color: Color.red, 19 | text: 'TAX', 20 | }, 21 | 4: { 22 | color: Color.blue, 23 | text: 'EXTENDED', 24 | }, 25 | } 26 | 27 | function RecentSales({ data }) { 28 | const columns = [ 29 | { 30 | title: 'NAME', 31 | dataIndex: 'name', 32 | }, 33 | { 34 | title: 'STATUS', 35 | dataIndex: 'status', 36 | render: text => {status[text].text}, 37 | }, 38 | { 39 | title: 'DATE', 40 | dataIndex: 'date', 41 | render: text => dayjs(text).format('YYYY-MM-DD'), 42 | }, 43 | { 44 | title: 'PRICE', 45 | dataIndex: 'price', 46 | render: (text, it) => ( 47 | ${text} 48 | ), 49 | }, 50 | ] 51 | return ( 52 |
53 |
key < 5)} 58 | /> 59 | 60 | ) 61 | } 62 | 63 | RecentSales.propTypes = { 64 | data: PropTypes.array, 65 | } 66 | 67 | export default RecentSales 68 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/recentSales.less: -------------------------------------------------------------------------------- 1 | @import '~themes/vars'; 2 | 3 | .recentsales { 4 | :global .ant-table-thead > tr > th { 5 | background: #fff; 6 | border-bottom: solid 1px @border-color-base; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/sales.less: -------------------------------------------------------------------------------- 1 | @import '~themes/vars'; 2 | 3 | .sales { 4 | overflow: hidden; 5 | .title { 6 | margin-left: 32px; 7 | font-size: 16px; 8 | } 9 | } 10 | 11 | .radiusdot { 12 | width: 12px; 13 | height: 12px; 14 | margin-right: 8px; 15 | border-radius: 50%; 16 | display: inline-block; 17 | } 18 | 19 | .legend { 20 | text-align: right; 21 | color: #999; 22 | font-size: 14px; 23 | 24 | li { 25 | height: 48px; 26 | line-height: 48px; 27 | display: inline-block; 28 | 29 | & + li { 30 | margin-left: 24px; 31 | } 32 | } 33 | } 34 | 35 | .tooltip { 36 | background: #fff; 37 | padding: 20px; 38 | font-size: 14px; 39 | 40 | .tiptitle { 41 | font-weight: 700; 42 | font-size: 16px; 43 | margin-bottom: 8px; 44 | } 45 | 46 | .tipitem { 47 | height: 32px; 48 | line-height: 32px; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/user-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zuiidea/antd-admin/67fc31a00892215e2d9971a91aa300e33ba48321/src/pages/dashboard/components/user-background.png -------------------------------------------------------------------------------- /src/pages/dashboard/components/user.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Button, Avatar } from 'antd' 4 | import CountUp from 'react-countup' 5 | import { Color } from 'utils' 6 | import styles from './user.less' 7 | 8 | const countUpProps = { 9 | start: 0, 10 | duration: 2.75, 11 | useEasing: true, 12 | useGrouping: true, 13 | separator: ',', 14 | } 15 | 16 | function User({ avatar, username, sales = 0, sold = 0 }) { 17 | return ( 18 |
19 |
20 |
21 | 22 |
{username}
23 |
24 |
25 |
26 |
27 |

EARNING SALES

28 |

29 | 30 |

31 |
32 |
33 |

ITEM SOLD

34 |

35 | 36 |

37 |
38 |
39 |
40 | 43 |
44 |
45 | ) 46 | } 47 | 48 | User.propTypes = { 49 | avatar: PropTypes.string, 50 | username: PropTypes.string, 51 | sales: PropTypes.number, 52 | sold: PropTypes.number, 53 | } 54 | 55 | export default User 56 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/user.less: -------------------------------------------------------------------------------- 1 | @import '~themes/vars'; 2 | 3 | .user { 4 | .header { 5 | display: flex; 6 | justify-content: center; 7 | text-align: center; 8 | color: #fff; 9 | height: 200px; 10 | background-size: cover; 11 | align-items: center; 12 | 13 | .headerinner { 14 | z-index: 2; 15 | } 16 | 17 | &::after { 18 | content: ''; 19 | background-image: url('./user-background.png'); 20 | background-size: cover; 21 | position: absolute; 22 | width: 100%; 23 | height: 200px; 24 | left: 0; 25 | top: 0; 26 | opacity: 0.4; 27 | z-index: 1; 28 | } 29 | 30 | .name { 31 | font-size: 16px; 32 | margin-top: 8px; 33 | } 34 | } 35 | 36 | .number { 37 | display: flex; 38 | height: 116px; 39 | justify-content: space-between; 40 | border-bottom: solid 1px #f5f5f5; 41 | 42 | .item { 43 | text-align: center; 44 | height: 116px; 45 | width: 100%; 46 | position: relative; 47 | padding: 30px 0; 48 | 49 | & + .item { 50 | &::before { 51 | content: ''; 52 | display: block; 53 | width: 1px; 54 | height: 116px; 55 | position: absolute; 56 | background: #f5f5f5; 57 | top: 0; 58 | } 59 | } 60 | 61 | p { 62 | color: #757575; 63 | 64 | &:first-child { 65 | font-size: 16px; 66 | } 67 | 68 | &:last-child { 69 | font-size: 20px; 70 | font-weight: 700; 71 | } 72 | } 73 | } 74 | } 75 | 76 | .footer { 77 | height: 116px; 78 | display: flex; 79 | justify-content: center; 80 | align-items: center; 81 | 82 | :global .ant-btn { 83 | color: @purple; 84 | border-color: @purple; 85 | padding: 6px 16px; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/weather.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Spin } from 'antd' 4 | import styles from './weather.less' 5 | 6 | function Weather({ city, icon, dateTime, temperature, name, loading }) { 7 | return ( 8 | 9 |
10 |
11 |
17 |

{name}

18 |
19 |
20 |

{`${temperature}°`}

21 |

22 | {city},{dateTime} 23 |

24 |
25 |
26 | 27 | ) 28 | } 29 | 30 | Weather.propTypes = { 31 | city: PropTypes.string, 32 | icon: PropTypes.string, 33 | dateTime: PropTypes.string, 34 | temperature: PropTypes.string, 35 | name: PropTypes.string, 36 | loading: PropTypes.bool, 37 | } 38 | 39 | export default Weather 40 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/weather.less: -------------------------------------------------------------------------------- 1 | @import '~themes/vars'; 2 | 3 | .weather { 4 | color: #fff; 5 | height: 204px; 6 | padding: 24px; 7 | justify-content: space-between; 8 | display: flex; 9 | font-size: 14px; 10 | 11 | .left { 12 | display: flex; 13 | flex-direction: column; 14 | width: 64px; 15 | padding-top: 55px; 16 | 17 | .icon { 18 | width: 64px; 19 | height: 64px; 20 | background-position: center; 21 | background-size: contain; 22 | } 23 | 24 | p { 25 | margin-top: 16px; 26 | } 27 | } 28 | 29 | .right { 30 | display: flex; 31 | flex-direction: column; 32 | width: 50%; 33 | 34 | .temperature { 35 | font-size: 36px; 36 | text-align: right; 37 | height: 64px; 38 | color: #fff; 39 | } 40 | 41 | .description { 42 | overflow: hidden; 43 | text-overflow: ellipsis; 44 | white-space: nowrap; 45 | text-align: right; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/pages/dashboard/index.less: -------------------------------------------------------------------------------- 1 | .dashboard { 2 | position: relative; 3 | :global { 4 | .ant-card { 5 | border-radius: 0; 6 | margin-bottom: 24px; 7 | &:hover { 8 | box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05); 9 | } 10 | } 11 | .ant-card-body { 12 | overflow-x: hidden; 13 | } 14 | } 15 | 16 | .weather { 17 | &:hover { 18 | box-shadow: 4px 4px 40px rgba(143, 201, 251, 0.6); 19 | } 20 | } 21 | 22 | .quote { 23 | &:hover { 24 | box-shadow: 4px 4px 40px rgba(246, 152, 153, 0.6); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/pages/dashboard/model.js: -------------------------------------------------------------------------------- 1 | import { parse } from 'qs' 2 | import modelExtend from 'dva-model-extend' 3 | import api from 'api' 4 | const { pathToRegexp } = require("path-to-regexp") 5 | import { model } from 'utils/model' 6 | 7 | const { queryDashboard, queryWeather } = api 8 | const avatar = '//cdn.antd-admin.zuiidea.com/bc442cf0cc6f7940dcc567e465048d1a8d634493198c4-sPx5BR_fw236.jpeg' 9 | 10 | export default modelExtend(model, { 11 | namespace: 'dashboard', 12 | state: { 13 | weather: { 14 | city: '深圳', 15 | temperature: '30', 16 | name: '晴', 17 | icon: '//cdn.antd-admin.zuiidea.com/sun.png', 18 | }, 19 | sales: [], 20 | quote: { 21 | avatar, 22 | }, 23 | numbers: [], 24 | recentSales: [], 25 | comments: [], 26 | completed: [], 27 | browser: [], 28 | cpu: {}, 29 | user: { 30 | avatar, 31 | }, 32 | }, 33 | subscriptions: { 34 | setup({ dispatch, history }) { 35 | history.listen(({ pathname }) => { 36 | if ( 37 | pathToRegexp('/dashboard').exec(pathname) || 38 | pathToRegexp('/').exec(pathname) 39 | ) { 40 | dispatch({ type: 'query' }) 41 | dispatch({ type: 'queryWeather' }) 42 | } 43 | }) 44 | }, 45 | }, 46 | effects: { 47 | *query({ payload }, { call, put }) { 48 | const data = yield call(queryDashboard, parse(payload)) 49 | yield put({ 50 | type: 'updateState', 51 | payload: data, 52 | }) 53 | }, 54 | *queryWeather({ payload = {} }, { call, put }) { 55 | payload.location = 'shenzhen' 56 | const result = yield call(queryWeather, payload) 57 | const { success } = result 58 | if (success) { 59 | const data = result.results[0] 60 | const weather = { 61 | city: data.location.name, 62 | temperature: data.now.temperature, 63 | name: data.now.text, 64 | icon: `//cdn.antd-admin.zuiidea.com/web/icons/3d_50/${data.now.code}.png`, 65 | } 66 | yield put({ 67 | type: 'updateState', 68 | payload: { 69 | weather, 70 | }, 71 | }) 72 | } 73 | }, 74 | }, 75 | }) 76 | -------------------------------------------------------------------------------- /src/pages/dashboard/services/dashboard.js: -------------------------------------------------------------------------------- 1 | import { request, config } from 'utils' 2 | 3 | const { api } = config 4 | const { dashboard } = api 5 | 6 | export function query(params) { 7 | return request({ 8 | url: dashboard, 9 | method: 'get', 10 | data: params, 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /src/pages/dashboard/services/weather.js: -------------------------------------------------------------------------------- 1 | import { request, config } from 'utils' 2 | 3 | const { APIV1 } = config 4 | 5 | export function query(params) { 6 | params.key = 'i7sau1babuzwhycn' 7 | return request({ 8 | url: `${APIV1}/weather/now.json`, 9 | method: 'get', 10 | data: params, 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /src/pages/editor/index.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react' 2 | import { Editor, Page } from 'components' 3 | import { convertToRaw } from 'draft-js' 4 | import { Row, Col, Card } from 'antd' 5 | import draftToHtml from 'draftjs-to-html' 6 | import draftToMarkdown from 'draftjs-to-markdown' 7 | 8 | export default class EditorPage extends Component { 9 | constructor(props) { 10 | super(props) 11 | this.state = { 12 | editorContent: null, 13 | } 14 | } 15 | 16 | onEditorStateChange = editorContent => { 17 | this.setState({ 18 | editorContent, 19 | }) 20 | } 21 | 22 | render() { 23 | const { editorContent } = this.state 24 | const colProps = { 25 | lg: 12, 26 | md: 24, 27 | style: { 28 | marginBottom: 32, 29 | } 30 | } 31 | const textareaStyle = { 32 | minHeight: 496, 33 | width: '100%', 34 | background: '#f7f7f7', 35 | borderColor: '#F1F1F1', 36 | padding: '16px 8px' 37 | } 38 | 39 | return ( 40 | 41 | 42 |
43 | 44 | 54 | 55 | 56 | 57 | 58 |