The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .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 | <svg width="169px" height="141px" viewBox="0 0 169 141" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
 3 |     <!-- Generator: Sketch 51.3 (57544) - http://www.bohemiancoding.com/sketch -->
 4 |     <desc>Created with Sketch.</desc>
 5 |     <defs>
 6 |         <linearGradient x1="54.0428975%" y1="4.39752391%" x2="54.0428975%" y2="108.456714%" id="linearGradient-1">
 7 |             <stop stop-color="#29CDFF" offset="0%"></stop>
 8 |             <stop stop-color="#148EFF" offset="62.3089445%"></stop>
 9 |             <stop stop-color="#0A60FF" offset="100%"></stop>
10 |         </linearGradient>
11 |         <linearGradient x1="50%" y1="14.2201464%" x2="50%" y2="113.263844%" id="linearGradient-2">
12 |             <stop stop-color="#FA816E" offset="0%"></stop>
13 |             <stop stop-color="#F74A5C" offset="65.9092442%"></stop>
14 |             <stop stop-color="#F51D2C" offset="100%"></stop>
15 |         </linearGradient>
16 |     </defs>
17 |     <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
18 |         <g id="Group" transform="translate(0.000000, -5.000000)">
19 |             <rect id="Rectangle" fill="url(#linearGradient-1)" transform="translate(83.718923, 75.312358) rotate(-24.000000) translate(-83.718923, -75.312358) " x="68.7189234" y="0.312357954" width="30" height="150" rx="15"></rect>
20 |             <rect id="Rectangle" fill="url(#linearGradient-1)" transform="translate(129.009910, 75.580213) rotate(-24.000000) translate(-129.009910, -75.580213) " x="114.00991" y="0.580212739" width="30" height="150" rx="15"></rect>
21 |             <circle id="Oval" fill="url(#linearGradient-2)" cx="25" cy="120" r="25"></circle>
22 |         </g>
23 |     </g>
24 | </svg>


--------------------------------------------------------------------------------
/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 `<Trans>?message</Trans>`, `` 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 | <!DOCTYPE html>
 2 | <html lang="en">
 3 | 
 4 | <head>
 5 |   <meta charset="UTF-8">
 6 |   <title>antd-admin - An admin dashboard application demo built upon Ant Design and UmiJS</title>
 7 |   <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
 8 |   <meta name="description" content="An admin dashboard application demo built upon Ant Design and UmiJS">
 9 |   <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
10 |   <link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
11 |   <link rel="icon" href="/_media/favicon.ico" />
12 | </head>
13 | 
14 | <body>
15 |   <nav data-cloak class="app-nav">
16 |     <a href="/">En</a>
17 |     <a href="#/zh-cn/">中文</a>
18 |   </nav>
19 |   <div id="app">Loading...</div>
20 |   <script>
21 |     window.$docsify = {
22 |       name: 'AntD Admin',
23 |       loadSidebar: true,
24 |       maxLevel: 3,
25 |       subMaxLevel: 3,
26 |       auto2top: true,
27 |       autoHeader: true,
28 |       repo: 'zuiidea/antd-admin',
29 |       themeColor: '#1890ff',
30 |       search: {
31 |         paths: 'auto',
32 |         placeholder: {
33 |           '/zh-cn/': '搜索',
34 |           '/': 'Type to search'
35 |         },
36 | 
37 |         noData: {
38 |           '/zh-cn/': '找不到结果',
39 |           '/': 'No Results'
40 |         }
41 |       }
42 |     }
43 | 
44 |     // if (typeof navigator.serviceWorker !== 'undefined') {
45 |     //   navigator.serviceWorker.register('sw.js')
46 |     // }
47 |   </script>
48 |   <script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
49 |   <script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script>
50 |   <script src="//unpkg.com/docsify/lib/plugins/emoji.min.js"></script>
51 |   <script src="//unpkg.com/docsify/lib/plugins/zoom-image.min.js"></script>
52 |   <script src="//unpkg.com/docsify-copy-code"></script>
53 | </body>
54 | 
55 | </html>


--------------------------------------------------------------------------------
/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 |        <div>
43 |          <h1>Secondary</h1>
44 |          {children}
45 |        </div>
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 <div>Secondary page Content</div>
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<Response> 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. 提取代码中需要翻译的字段,即 `<Trans>message</Trans>`、`` 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 |        <div>
43 |          <h1>Secondary</h1>
44 |          {children}
45 |        </div>
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 <div>Secondary page Content</div>
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 | <?xml version="1.0" encoding="iso-8859-1"?>
 2 | <!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
 3 | <svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
 4 | 	 viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
 5 | <rect style="fill:#F0F0F0;" width="512" height="512"/>
 6 | <g>
 7 | 	<rect y="64" style="fill:#D80027;" width="512" height="64"/>
 8 | 	<rect y="192" style="fill:#D80027;" width="512" height="64"/>
 9 | 	<rect y="320" style="fill:#D80027;" width="512" height="64"/>
10 | 	<rect y="448" style="fill:#D80027;" width="512" height="64"/>
11 | </g>
12 | <rect style="fill:#2E52B2;" width="256" height="275.69"/>
13 | <g>
14 | 	<polygon style="fill:#F0F0F0;" points="51.518,115.318 45.924,132.529 27.826,132.529 42.469,143.163 36.875,160.375 
15 | 		51.518,149.741 66.155,160.375 60.56,143.163 75.203,132.529 57.106,132.529 	"/>
16 | 	<polygon style="fill:#F0F0F0;" points="57.106,194.645 51.518,177.434 45.924,194.645 27.826,194.645 42.469,205.279 
17 | 		36.875,222.49 51.518,211.857 66.155,222.49 60.56,205.279 75.203,194.645 	"/>
18 | 	<polygon style="fill:#F0F0F0;" points="51.518,53.202 45.924,70.414 27.826,70.414 42.469,81.047 36.875,98.259 51.518,87.625 
19 | 		66.155,98.259 60.56,81.047 75.203,70.414 57.106,70.414 	"/>
20 | 	<polygon style="fill:#F0F0F0;" points="128.003,115.318 122.409,132.529 104.311,132.529 118.954,143.163 113.36,160.375 
21 | 		128.003,149.741 142.64,160.375 137.045,143.163 151.689,132.529 133.591,132.529 	"/>
22 | 	<polygon style="fill:#F0F0F0;" points="133.591,194.645 128.003,177.434 122.409,194.645 104.311,194.645 118.954,205.279 
23 | 		113.36,222.49 128.003,211.857 142.64,222.49 137.045,205.279 151.689,194.645 	"/>
24 | 	<polygon style="fill:#F0F0F0;" points="210.076,194.645 204.489,177.434 198.894,194.645 180.797,194.645 195.44,205.279 
25 | 		189.845,222.49 204.489,211.857 219.125,222.49 213.531,205.279 228.174,194.645 	"/>
26 | 	<polygon style="fill:#F0F0F0;" points="204.489,115.318 198.894,132.529 180.797,132.529 195.44,143.163 189.845,160.375 
27 | 		204.489,149.741 219.125,160.375 213.531,143.163 228.174,132.529 210.076,132.529 	"/>
28 | 	<polygon style="fill:#F0F0F0;" points="128.003,53.202 122.409,70.414 104.311,70.414 118.954,81.047 113.36,98.259 
29 | 		128.003,87.625 142.64,98.259 137.045,81.047 151.689,70.414 133.591,70.414 	"/>
30 | 	<polygon style="fill:#F0F0F0;" points="204.489,53.202 198.894,70.414 180.797,70.414 195.44,81.047 189.845,98.259 
31 | 		204.489,87.625 219.125,98.259 213.531,81.047 228.174,70.414 210.076,70.414 	"/>
32 | </g>
33 | <g>
34 | </g>
35 | <g>
36 | </g>
37 | <g>
38 | </g>
39 | <g>
40 | </g>
41 | <g>
42 | </g>
43 | <g>
44 | </g>
45 | <g>
46 | </g>
47 | <g>
48 | </g>
49 | <g>
50 | </g>
51 | <g>
52 | </g>
53 | <g>
54 | </g>
55 | <g>
56 | </g>
57 | <g>
58 | </g>
59 | <g>
60 | </g>
61 | <g>
62 | </g>
63 | </svg>
64 | 


--------------------------------------------------------------------------------
/public/china.svg:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
 3 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
 4 | 	 viewBox="-49 141 512 512" style="enable-background:new -49 141 512 512;" xml:space="preserve">
 5 | <style type="text/css">
 6 | 	.st0{fill:#D80027;}
 7 | 	.st1{fill:#FFDA44;}
 8 | </style>
 9 | <rect x="-49" y="141" class="st0" width="512" height="512"/>
10 | 
11 | <g>
12 | 	<polygon class="st1" points="91.1,296.8 113.2,364.8 184.7,364.8 126.9,406.9 149,474.9 91.1,432.9 33.2,474.9 55.4,406.9 
13 | 		-2.5,364.8 69,364.8 	"/>
14 | 	<polygon class="st1" points="254.5,537.5 237.6,516.7 212.6,526.4 227.1,503.9 210.2,483 236.1,489.9 250.7,467.4 252.1,494.2 
15 | 		278.1,501.1 253,510.7 	"/>
16 | 	<polygon class="st1" points="288.1,476.5 296.1,450.9 274.2,435.4 301,435 308.9,409.4 317.6,434.8 344.4,434.5 322.9,450.5 
17 | 		331.5,475.9 309.6,460.4 	"/>
18 | 	<polygon class="st1" points="333.4,328.9 321.6,353 340.8,371.7 314.3,367.9 302.5,391.9 297.9,365.5 271.3,361.7 295.1,349.2 
19 | 		290.5,322.7 309.7,341.4 	"/>
20 | 	<polygon class="st1" points="255.2,255.9 253.2,282.6 278.1,292.7 252,299.1 250.1,325.9 236,303.1 209.9,309.5 227.2,289 
21 | 		213,266.3 237.9,276.4 	"/>
22 | </g>
23 | </svg>
24 | 


--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zuiidea/antd-admin/67fc31a00892215e2d9971a91aa300e33ba48321/public/favicon.ico


--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
 1 | 
 2 | <svg width="169px" height="141px" viewBox="0 0 169 141" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
 3 |     <!-- Generator: Sketch 51.3 (57544) - http://www.bohemiancoding.com/sketch -->
 4 |     <desc>Created with Sketch.</desc>
 5 |     <defs>
 6 |         <linearGradient x1="54.0428975%" y1="4.39752391%" x2="54.0428975%" y2="108.456714%" id="linearGradient-1">
 7 |             <stop stop-color="#29CDFF" offset="0%"></stop>
 8 |             <stop stop-color="#148EFF" offset="62.3089445%"></stop>
 9 |             <stop stop-color="#0A60FF" offset="100%"></stop>
10 |         </linearGradient>
11 |         <linearGradient x1="50%" y1="14.2201464%" x2="50%" y2="113.263844%" id="linearGradient-2">
12 |             <stop stop-color="#FA816E" offset="0%"></stop>
13 |             <stop stop-color="#F74A5C" offset="65.9092442%"></stop>
14 |             <stop stop-color="#F51D2C" offset="100%"></stop>
15 |         </linearGradient>
16 |     </defs>
17 |     <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
18 |         <g id="Group" transform="translate(0.000000, -5.000000)">
19 |             <rect id="Rectangle" fill="url(#linearGradient-1)" transform="translate(83.718923, 75.312358) rotate(-24.000000) translate(-83.718923, -75.312358) " x="68.7189234" y="0.312357954" width="30" height="150" rx="15"></rect>
20 |             <rect id="Rectangle" fill="url(#linearGradient-1)" transform="translate(129.009910, 75.580213) rotate(-24.000000) translate(-129.009910, -75.580213) " x="114.00991" y="0.580212739" width="30" height="150" rx="15"></rect>
21 |             <circle id="Oval" fill="url(#linearGradient-2)" cx="25" cy="120" r="25"></circle>
22 |         </g>
23 |     </g>
24 | </svg>


--------------------------------------------------------------------------------
/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 | <?xml version="1.0" encoding="iso-8859-1"?>
 2 | <!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
 3 | <svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
 4 | 	 viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
 5 | <rect style="fill:#D80027;" width="512" height="512"/>
 6 | <polygon style="fill:#6DA544;" points="196.641,0 196.641,264.348 196.641,512 0,512 0,0 "/>
 7 | <circle style="fill:#FFDA44;" cx="196.641" cy="256" r="96"/>
 8 | <path style="fill:#D80027;" d="M142.638,208v60c0,29.823,24.178,54,54,54s54-24.178,54-54v-60H142.638z"/>
 9 | <path style="fill:#F0F0F0;" d="M196.638,286c-9.925,0-18-8.075-18-18v-24.001h36V268C214.638,277.925,206.563,286,196.638,286z"/>
10 | <g>
11 | </g>
12 | <g>
13 | </g>
14 | <g>
15 | </g>
16 | <g>
17 | </g>
18 | <g>
19 | </g>
20 | <g>
21 | </g>
22 | <g>
23 | </g>
24 | <g>
25 | </g>
26 | <g>
27 | </g>
28 | <g>
29 | </g>
30 | <g>
31 | </g>
32 | <g>
33 | </g>
34 | <g>
35 | </g>
36 | <g>
37 | </g>
38 | <g>
39 | </g>
40 | </svg>
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 |     <Menu.Item key={item.key}>{item.name}</Menu.Item>
14 |   ))
15 |   return (
16 |     <Dropdown
17 |       overlay={<Menu onClick={onMenuClick}>{menu}</Menu>}
18 |       {...dropdownProps}
19 |     >
20 |       <Button style={{ border: 'none', ...buttonStyle }}>
21 |         <BarsOutlined style={{ marginRight: 2 }} />
22 |         <DownOutlined />
23 |       </Button>
24 |     </Dropdown>
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 |     <Editor
 9 |       toolbarClassName={styles.toolbar}
10 |       wrapperClassName={styles.wrapper}
11 |       editorClassName={styles.editor}
12 |       {...props}
13 |     />
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<EllipsisProps, any> {}
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 |     <div className={styles.filterItem}>
 9 |       {labelArray.length > 0 && (
10 |         <div className={styles.labelWrap}>
11 |           {labelArray.map((item, index) => (
12 |             <span className="labelText" key={index}>
13 |               {item}
14 |             </span>
15 |           ))}
16 |         </div>
17 |       )}
18 |       <div className={styles.item}>{children}</div>
19 |     </div>
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<GlobalFooterProps, any> {}
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 |     <footer className={clsString}>
 9 |       {links && (
10 |         <div className={styles.links}>
11 |           {links.map(link => (
12 |             <a
13 |               key={link.key}
14 |               title={link.key}
15 |               target={link.blankTarget ? '_blank' : '_self'}
16 |               href={link.href}
17 |             >
18 |               {link.title}
19 |             </a>
20 |           ))}
21 |         </div>
22 |       )}
23 |       {copyright && <div className={styles.copyright}>{copyright}</div>}
24 |     </footer>
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 |         <Fragment>
17 |           {item.icon && (
18 |             <span style={{ marginRight: 4 }}>{iconMap[item.icon]}</span>
19 |           )}
20 |           {item.name}
21 |         </Fragment>
22 |       )
23 | 
24 |       return (
25 |         item && (
26 |           <Breadcrumb.Item key={key}>
27 |             {paths.length - 1 !== key ? (
28 |               <Link to={item.route || '#'}>{content}</Link>
29 |             ) : (
30 |               content
31 |             )}
32 |           </Breadcrumb.Item>
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 |       <Breadcrumb className={styles.bread}>
58 |         {this.generateBreadcrumbs(paths)}
59 |       </Breadcrumb>
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 |           <SubMenu
 42 |             key={item.id}
 43 |             title={
 44 |               <Fragment>
 45 |                 {item.icon && iconMap[item.icon]}
 46 |                 <span>{item.name}</span>
 47 |               </Fragment>
 48 |             }
 49 |           >
 50 |             {this.generateMenus(item.children)}
 51 |           </SubMenu>
 52 |         )
 53 |       }
 54 |       return (
 55 |         <Menu.Item key={item.id}>
 56 |           <NavLink to={item.route || '#'}>
 57 |             {item.icon && iconMap[item.icon]}
 58 |             <span>{item.name}</span>
 59 |           </NavLink>
 60 |         </Menu.Item>
 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 |       <Menu
 96 |         mode="inline"
 97 |         theme={theme}
 98 |         onOpenChange={this.onOpenChange}
 99 |         selectedKeys={selectedKeys}
100 |         onClick={
101 |           isMobile
102 |             ? () => {
103 |                 onCollapseChange(true)
104 |               }
105 |             : undefined
106 |         }
107 |         {...menuProps}
108 |       >
109 |         {this.generateMenus(menuTree)}
110 |       </Menu>
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 |       <Layout.Sider
25 |         width={256}
26 |         theme={theme}
27 |         breakpoint="lg"
28 |         trigger={null}
29 |         collapsible
30 |         collapsed={collapsed}
31 |         onBreakpoint={!isMobile ? onCollapseChange : (broken) => {}}
32 |         className={styles.sider}
33 |       >
34 |         <div className={styles.brand}>
35 |           <div className={styles.logo}>
36 |             <img alt="logo" src={config.logoPath} />
37 |             {!collapsed && <h1>{config.siteName}</h1>}
38 |           </div>
39 |         </div>
40 | 
41 |         <div className={styles.menuContainer}>
42 |           <ScrollBar
43 |             options={{
44 |               // Disabled horizontal scrolling, https://github.com/utatti/perfect-scrollbar#options
45 |               suppressScrollX: true,
46 |             }}
47 |           >
48 |             <SiderMenu
49 |               menus={menus}
50 |               theme={theme}
51 |               isMobile={isMobile}
52 |               collapsed={collapsed}
53 |               onCollapseChange={onCollapseChange}
54 |             />
55 |           </ScrollBar>
56 |         </div>
57 |         {!collapsed && (
58 |           <div className={styles.switchTheme}>
59 |             <span>
60 |               <BulbOutlined />
61 |               <Trans>Switch Theme</Trans>
62 |             </span>
63 |             <Switch
64 |               onChange={onThemeChange.bind(
65 |                 this,
66 |                 theme === 'dark' ? 'light' : 'dark'
67 |               )}
68 |               defaultChecked={theme === 'dark'}
69 |               checkedChildren={t`Dark`}
70 |               unCheckedChildren={t`Light`}
71 |             />
72 |           </div>
73 |         )}
74 |       </Layout.Sider>
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 |     <div
 9 |       className={classNames(styles.loader, {
10 |         [styles.hidden]: !spinning,
11 |         [styles.fullScreen]: fullScreen,
12 |       })}
13 |     >
14 |       <div className={styles.warpper}>
15 |         <div className={styles.inner} />
16 |         <div className={styles.text}>LOADING</div>
17 |       </div>
18 |     </div>
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 |       <div
16 |         className={classnames(className, {
17 |           [styles.contentInner]: inner,
18 |         })}
19 |         style={loading ? loadingStyle : null}
20 |       >
21 |         {loading ? <Loader spinning /> : ''}
22 |         {children}
23 |       </div>
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 |       <Fragment>
41 |         <Helmet>
42 |           <title>{config.siteName}</title>
43 |         </Helmet>
44 |         <Loader fullScreen spinning={loading.effects['app/query']} />
45 |         <Container>{children}</Container>
46 |       </Fragment>
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 |       <ConfigProvider locale={languages[language]}>
57 |         <I18nProvider i18n={i18n}>
58 |           <BaseLayout>{children}</BaseLayout>
59 |         </I18nProvider>
60 |       </ConfigProvider>
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 |   <Page inner>
 8 |     <div className={styles.error}>
 9 |       <FrownOutlined />
10 |       <h1>404 Not Found</h1>
11 |     </div>
12 |   </Page>
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 |       '<ReactEcharts \n' +
 75 |       '    option={this.getOtion()} \n' +
 76 |       '    onChartReady={this.onChartReady} \n' +
 77 |       '    loadingOption={this.getLoadingOption()} \n' +
 78 |       '    showLoading={true} />'
 79 | 
 80 |     return (
 81 |       <div className="examples">
 82 |         <div className="parent">
 83 |           <label>
 84 |             {' '}
 85 |             Chart loading With <strong> showLoading </strong>: (when chart
 86 |             ready, hide the loading mask.)
 87 |           </label>
 88 |           <ReactEcharts
 89 |             option={getOtion()}
 90 |             onChartReady={this.onChartReady}
 91 |             loadingOption={getLoadingOption()}
 92 |             showLoading
 93 |           />
 94 |           <label> code below: </label>
 95 |           <pre>
 96 |             <code>{code}</code>
 97 |           </pre>
 98 |         </div>
 99 |       </div>
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} <br/>{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 |     '<ReactEcharts \n' +
67 |     '    option={getOtion()} \n' +
68 |     '    style={{height: 300}} \n' +
69 |     '    onChartReady={onChartReady} \n' +
70 |     '    onEvents={onEvents} />'
71 | 
72 |   return (
73 |     <div className="examples">
74 |       <div className="parent">
75 |         <label>
76 |           {' '}
77 |           Chart With event <strong> onEvents </strong>: (Click the chart, and
78 |           watch the console)
79 |         </label>
80 |         <ReactEcharts
81 |           option={getOtion()}
82 |           style={{ height: 300 }}
83 |           onChartReady={onChartReady}
84 |           onEvents={onEvents}
85 |         />
86 |         <label> code below: </label>
87 |         <pre>
88 |           <code>{code}</code>
89 |         </pre>
90 |       </div>
91 |     </div>
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 <SimpleChartComponent />
26 |   if (type === 'loading') return <ChartShowLoadingComponent />
27 |   if (type === 'api') return <ChartAPIComponent />
28 |   if (type === 'events') return <ChartWithEventComponent />
29 |   if (type === 'theme') return <ThemeChartComponent />
30 |   if (type === 'dynamic') return <DynamicChartComponent />
31 |   if (type === 'map') return <MapChartComponent />
32 |   if (type === 'airport') return <AirportCoordComponent />
33 |   if (type === 'graph') return <GraphComponent />
34 |   if (type === 'calendar') return <CalendarComponent />
35 |   if (type === 'treemap') return <TreemapComponent />
36 |   if (type === 'gauge') return <GaugeComponent />
37 |   if (type === 'gcalendar') return <GCalendarComponent />
38 |   if (type === 'lunar') return <LunarCalendarComponent />
39 |   if (type === 'liquid') return <LiquidfillComponent />
40 |   if (type === 'BubbleGradientComponent') return <BubbleGradientComponent />
41 |   if (type === 'TransparentBar3DComPonent') return <TransparentBar3DComPonent />
42 |   return <DynamicChartComponent />
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 |     <div className="examples">
64 |       <div className="parent">
65 |         <label> render a calendar like github commit history. </label>
66 |         <ReactEcharts
67 |           option={option}
68 |           style={{ height: '500px', width: '100%' }}
69 |           className="react_for_echarts"
70 |         />
71 |       </div>
72 |     </div>
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 |     <div className="examples">
17 |       <div className="parent">
18 |         <label>render a Liquidfill chart:</label>
19 |         <ReactEcharts
20 |           option={option}
21 |           style={{
22 |             height: '400px',
23 |             width: '100%',
24 |           }}
25 |           className="react_for_echarts"
26 |         />
27 |       </div>
28 |     </div>
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 |     <div>
 9 |       <h1> echarts-for-react {this.props.params.type} </h1>
10 |       <h3>
11 |         {' '}
12 |         A very simple echarts(v3.0) wrapper for React.{' '}
13 |         <a href="https://github.com/hustcc/echarts-for-react">
14 |           hustcc/echarts-for-react
15 |         </a>
16 |       </h3>
17 | 
18 |       <AdSense.Google client="ca-pub-7292810486004926" slot="7806394673" />
19 | 
20 |       <h4>
21 |         <Link to="/echarts/simple">Simple demo</Link> |
22 |         <Link to="/echarts/loading">Echarts loading</Link> |
23 |         <Link to="/echarts/api">Echarts API</Link> |
24 |         <Link to="/echarts/events">Echarts events</Link> |
25 |         <Link to="/echarts/theme">Echarts theme</Link> |
26 |         <Link to="/echarts/dynamic">Dynamic chart</Link> |
27 |         <Link to="/echarts/map">Map chart</Link>
28 |       </h4>
29 |       <h4>
30 |         <span style={{ color: 'red' }}>New</span>
31 |         :&nbsp;&nbsp;
32 |         <Link to="/echarts/airport">Airport</Link> |
33 |         <Link to="/echarts/graph">Graph</Link> |
34 |         <Link to="/echarts/calendar">Calendar</Link> |
35 |         <Link to="/echarts/treemap">Treemap</Link> |
36 |         <Link to="/echarts/gauge">Gauge</Link> |
37 |         <Link to="/echarts/gcalendar">GCalendar</Link> |
38 |         <Link to="/echarts/lunar">Lunar</Link> |
39 |         <Link to="/echarts/liquid">Liquidfill</Link>
40 |       </h4>
41 |       {this.props.children || <DynamicChartComponent />}
42 | 
43 |       <h3>
44 |         Get it on GitHub!{' '}
45 |         <a href="https://github.com/hustcc/echarts-for-react">
46 |           hustcc/echarts-for-react
47 |         </a>
48 |       </h3>
49 |     </div>
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 |     '<ReactEcharts \n' +
23 |     '    option={this.getOtion()} \n' +
24 |     "    style={{height: '350px', width: '100%'}}  \n" +
25 |     "    modules={['echarts/lib/chart/bar', 'echarts/lib/component/tooltip', 'echarts/lib/component/title']} \n" +
26 |     "    className='react_for_echarts' />"
27 |   return (
28 |     <div className="examples">
29 |       <div className="parent">
30 |         <label>
31 |           {' '}
32 |           load echarts module as you wish <strong>
33 |             reduce the file size
34 |           </strong>:{' '}
35 |         </label>
36 |         <ReactEcharts
37 |           option={option}
38 |           style={{ height: '350px', width: '100%' }}
39 |           modules={[
40 |             'echarts/lib/chart/bar',
41 |             'echarts/lib/component/tooltip',
42 |             'echarts/lib/component/title',
43 |           ]}
44 |           className="react_for_echarts"
45 |         />
46 |         <label> code below: </label>
47 |         <pre>
48 |           <code>{code}</code>
49 |         </pre>
50 |       </div>
51 |     </div>
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 |     '<ReactEcharts \n' +
65 |     '    option={this.getOtion()} \n' +
66 |     "    style={{height: '350px', width: '100%'}}  \n" +
67 |     "    className='react_for_echarts' />"
68 |   return (
69 |     <div className="examples">
70 |       <div className="parent">
71 |         <label>
72 |           {' '}
73 |           render a Simple echart With <strong>option and height</strong>:{' '}
74 |         </label>
75 |         <ReactEcharts
76 |           option={option}
77 |           style={{ height: '350px', width: '100%' }}
78 |           className="react_for_echarts"
79 |           theme="macarons"
80 |         />
81 |         <label> code below: </label>
82 |         <pre>
83 |           <code>{code}</code>
84 |         </pre>
85 |       </div>
86 |     </div>
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 |     '<ReactEcharts \n' +
102 |     '    option={this.getOtion()} \n' +
103 |     "    theme='my_theme' />"
104 |   return (
105 |     <div className="examples">
106 |       <div className="parent">
107 |         <label>
108 |           {' '}
109 |           render a echart With <strong>theme</strong>, should{' '}
110 |           <strong>echarts.registerTheme(themeName, themeObj)</strong> before
111 |           use.
112 |         </label>
113 |         <ReactEcharts option={option} theme="my_theme" />
114 |         <label>
115 |           {' '}
116 |           the theme object format:
117 |           https://github.com/ecomfe/echarts/blob/master/theme/dark.js
118 |         </label>
119 |         <pre>
120 |           <code>{code}</code>
121 |         </pre>
122 |       </div>
123 |     </div>
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 |       <Page inner id="EChartsMain">
 96 |         <RadioGroup
 97 |           options={chartList}
 98 |           defaultValue="dynamic"
 99 |           onChange={this.handleRadioGroupChange}
100 |         />
101 |         <div className={styles.chart}>
102 |           <EchartsComponent type={this.state.type} />
103 |         </div>
104 |         <div style={{ padding: 24, marginTop: 24 }}>
105 |           All demos from{' '}
106 |           <a href="https://github.com/hustcc/echarts-for-react">
107 |             https://github.com/hustcc/echarts-for-react
108 |           </a>
109 |         </div>
110 |       </Page>
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 |   <div className={styles.container} style={{ minHeight, maxHeight }}>
13 |     <div style={{ marginTop: `${100 / ratio}%` || '100%' }} />
14 |     <div className={styles.content} style={{ minHeight, maxHeight }}>
15 |       <ResponsiveContainer>{children}</ResponsiveContainer>
16 |     </div>
17 |   </div>
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 <AreaChartComponent />
10 |   if (type === 'barChart') return <BarChartComponent />
11 |   return <LineChartComponent />
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 |       <Page inner>
40 |         <RadioGroup
41 |           options={chartList}
42 |           defaultValue="lineChart"
43 |           onChange={this.handleRadioGroupChange}
44 |         />
45 |         <div className={styles.chart}>
46 |           <ReChartsComponent type={this.state.type} />
47 |         </div>
48 |       </Page>
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 <HighmapsComponent />
10 |   if (type === 'HighMore') return <HighMoreComponent />
11 |   return <HighstockComponent />
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 <ReactHighcharts config={config} />
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}: <b>{series.name}</b>',
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 <ReactHighmaps config={config} />
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 <ReactHighstock config={config} />
 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 |       <Page inner>
40 |         <RadioGroup
41 |           options={chartList}
42 |           defaultValue="Highstock"
43 |           onChange={this.handleRadioGroupChange}
44 |         />
45 |         <div className={styles.chart}>
46 |           <HighChartsComponent type={this.state.type} />
47 |         </div>
48 |       </Page>
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) => <Tag color={status[it.status].color}>{text}%</Tag>,
34 |     },
35 |   ]
36 |   return (
37 |     <Table
38 |       pagination={false}
39 |       showHeader={false}
40 |       columns={columns}
41 |       rowKey='name'
42 |       dataSource={data}
43 |     />
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 |         <span
31 |           style={{ backgroundImage: `url(${text})` }}
32 |           className={styles.avatar}
33 |         />
34 |       ),
35 |     },
36 |     {
37 |       title: 'content',
38 |       dataIndex: 'content',
39 |       render: (text, it) => (
40 |         <div>
41 |           <h5 className={styles.name}>{it.name}</h5>
42 |           <p className={styles.content}>{it.content}</p>
43 |           <div className={styles.daterow}>
44 |             <Tag color={status[it.status].color}>{status[it.status].text}</Tag>
45 |             <span className={styles.date}>{it.date}</span>
46 |           </div>
47 |         </div>
48 |       ),
49 |     },
50 |   ]
51 |   return (
52 |     <div className={styles.comments}>
53 |       <Table
54 |         pagination={false}
55 |         showHeader={false}
56 |         columns={columns}
57 |         rowKey='avatar'
58 |         dataSource={data.filter((item, key) => key < 3)}
59 |       />
60 |     </div>
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 |     <div className={styles.cpu}>
26 |       <div className={styles.number}>
27 |         <div className={styles.item}>
28 |           <p>usage</p>
29 |           <p>
30 |             <CountUp end={usage} suffix="GB" {...countUpProps} />
31 |           </p>
32 |         </div>
33 |         <div className={styles.item}>
34 |           <p>space</p>
35 |           <p>
36 |             <CountUp end={space} suffix="GB" {...countUpProps} />
37 |           </p>
38 |         </div>
39 |         <div className={styles.item}>
40 |           <p>cpu</p>
41 |           <p>
42 |             <CountUp end={cpu} suffix="%" {...countUpProps} />
43 |           </p>
44 |         </div>
45 |       </div>
46 |       <ResponsiveContainer minHeight={300}>
47 |         <LineChart data={data} margin={{ left: -40 }}>
48 |           <XAxis
49 |             dataKey="name"
50 |             axisLine={{ stroke: Color.borderBase, strokeWidth: 1 }}
51 |             tickLine={false}
52 |           />
53 |           <YAxis axisLine={false} tickLine={false} />
54 |           <CartesianGrid
55 |             vertical={false}
56 |             stroke={Color.borderBase}
57 |             strokeDasharray="3 3"
58 |           />
59 |           <Line
60 |             type="monotone"
61 |             connectNulls
62 |             dataKey="cpu"
63 |             stroke={Color.blue}
64 |             fill={Color.blue}
65 |           />
66 |         </LineChart>
67 |       </ResponsiveContainer>
68 |     </div>
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 |     <Card
12 |       className={styles.numberCard}
13 |       bordered={false}
14 |       bodyStyle={{ padding: 10 }}
15 |     >
16 |       <span className={styles.iconWarp} style={{ color }}>
17 |         {iconMap[icon]}
18 |       </span>
19 |       <div className={styles.content}>
20 |         <p className={styles.title}>{title || 'No Title'}</p>
21 |         <p className={styles.number}>
22 |           <CountUp
23 |             start={0}
24 |             end={number}
25 |             duration={2.75}
26 |             useEasing
27 |             useGrouping
28 |             separator=","
29 |             {...(countUp || {})}
30 |           />
31 |         </p>
32 |       </div>
33 |     </Card>
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 |     <div className={styles.quote}>
 8 |       <div className={styles.inner}>{content}</div>
 9 |       <div className={styles.footer}>
10 |         <div className={styles.description}>
11 |           <p>-{name}-</p>
12 |           <p>{title}</p>
13 |         </div>
14 |         <div
15 |           className={styles.avatar}
16 |           style={{ backgroundImage: `url(${avatar})` }}
17 |         />
18 |       </div>
19 |     </div>
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 => <Tag color={status[text].color}>{status[text].text}</Tag>,
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 |         <span style={{ color: status[it.status].color }}>${text}</span>
48 |       ),
49 |     },
50 |   ]
51 |   return (
52 |     <div className={styles.recentsales}>
53 |       <Table
54 |         pagination={false}
55 |         columns={columns}
56 |         rowKey='id'
57 |         dataSource={data.filter((item, key) => key < 5)}
58 |       />
59 |     </div>
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 |     <div className={styles.user}>
19 |       <div className={styles.header}>
20 |         <div className={styles.headerinner}>
21 |           <Avatar size="large" src={avatar} />
22 |           <h5 className={styles.name}>{username}</h5>
23 |         </div>
24 |       </div>
25 |       <div className={styles.number}>
26 |         <div className={styles.item}>
27 |           <p>EARNING SALES</p>
28 |           <p style={{ color: Color.green }}>
29 |             <CountUp end={sales} prefix="
quot; {...countUpProps} />
30 |           </p>
31 |         </div>
32 |         <div className={styles.item}>
33 |           <p>ITEM SOLD</p>
34 |           <p style={{ color: Color.blue }}>
35 |             <CountUp end={sold} {...countUpProps} />
36 |           </p>
37 |         </div>
38 |       </div>
39 |       <div className={styles.footer}>
40 |         <Button type="ghost" size="large">
41 |           View Profile
42 |         </Button>
43 |       </div>
44 |     </div>
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 |     <Spin spinning={loading}>
 9 |       <div className={styles.weather}>
10 |         <div className={styles.left}>
11 |           <div
12 |             className={styles.icon}
13 |             style={{
14 |               backgroundImage: `url(${icon})`,
15 |             }}
16 |           />
17 |           <p>{name}</p>
18 |         </div>
19 |         <div className={styles.right}>
20 |           <h1 className={styles.temperature}>{`${temperature}°`}</h1>
21 |           <p className={styles.description}>
22 |             {city},{dateTime}
23 |           </p>
24 |         </div>
25 |       </div>
26 |     </Spin>
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 |       <Page inner>
 41 |         <Row gutter={32}>
 42 |           <Col {...colProps}>
 43 |             <Card title="Editor" style={{ overflow: 'visible' }}>
 44 |               <Editor
 45 |                 wrapperStyle={{
 46 |                   minHeight: 500,
 47 |                 }}
 48 |                 editorStyle={{
 49 |                   minHeight: 376,
 50 |                 }}
 51 |                 editorState={editorContent}
 52 |                 onEditorStateChange={this.onEditorStateChange}
 53 |               />
 54 |             </Card>
 55 |           </Col>
 56 |           <Col {...colProps}>
 57 |             <Card title="HTML">
 58 |               <textarea
 59 |                 style={textareaStyle}
 60 |                 disabled
 61 |                 value={
 62 |                   editorContent
 63 |                     ? draftToHtml(
 64 |                         convertToRaw(editorContent.getCurrentContent())
 65 |                       )
 66 |                     : ''
 67 |                 }
 68 |               />
 69 |             </Card>
 70 |           </Col>
 71 |           <Col {...colProps}>
 72 |             <Card title="Markdown">
 73 |               <textarea
 74 |                 style={textareaStyle}
 75 |                 disabled
 76 |                 value={
 77 |                   editorContent
 78 |                     ? draftToMarkdown(
 79 |                         convertToRaw(editorContent.getCurrentContent())
 80 |                       )
 81 |                     : ''
 82 |                 }
 83 |               />
 84 |             </Card>
 85 |           </Col>
 86 |           <Col {...colProps}>
 87 |             <Card title="JSON">
 88 |               <textarea
 89 |                 style={textareaStyle}
 90 |                 disabled
 91 |                 value={
 92 |                   editorContent
 93 |                     ? JSON.stringify(
 94 |                         convertToRaw(editorContent.getCurrentContent())
 95 |                       )
 96 |                     : ''
 97 |                 }
 98 |               />
 99 |             </Card>
100 |           </Col>
101 |         </Row>
102 |       </Page>
103 |     )
104 |   }
105 | }
106 | 


--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
 1 | import React, { PureComponent } from 'react'
 2 | import { Redirect } from 'umi'
 3 | import { t } from "@lingui/macro"
 4 | 
 5 | class Index extends PureComponent {
 6 |   render() {
 7 |     return <Redirect to={t`/dashboard`} />
 8 |   }
 9 | }
10 | 
11 | export default Index
12 | 


--------------------------------------------------------------------------------
/src/pages/login/index.js:
--------------------------------------------------------------------------------
  1 | import React, { PureComponent, Fragment } from 'react'
  2 | import PropTypes from 'prop-types'
  3 | import { connect } from 'umi'
  4 | import { Button, Row, Input, Form } from 'antd'
  5 | import { GlobalFooter } from 'components'
  6 | import { GithubOutlined } from '@ant-design/icons'
  7 | import { t, Trans } from "@lingui/macro"
  8 | import { setLocale } from 'utils'
  9 | import config from 'utils/config'
 10 | 
 11 | import styles from './index.less'
 12 | 
 13 | const FormItem = Form.Item
 14 | 
 15 | @connect(({ loading, dispatch }) => ({ loading, dispatch }))
 16 | class Login extends PureComponent {
 17 | 
 18 |   render() {
 19 |     const { dispatch, loading } = this.props
 20 |     
 21 |     const handleOk = values => {
 22 |       dispatch({ type: 'login/login', payload: values })
 23 |     }
 24 |     let footerLinks = [
 25 |       {
 26 |         key: 'github',
 27 |         title: <GithubOutlined />,
 28 |         href: 'https://github.com/zuiidea/antd-admin',
 29 |         blankTarget: true,
 30 |       },
 31 |     ]
 32 | 
 33 |     if (config.i18n) {
 34 |       footerLinks = footerLinks.concat(
 35 |         config.i18n.languages.map(item => ({
 36 |           key: item.key,
 37 |           title: (
 38 |             <span onClick={setLocale.bind(null, item.key)}>{item.title}</span>
 39 |           ),
 40 |         }))
 41 |       )
 42 |     }
 43 | 
 44 |     return (
 45 |       <Fragment>
 46 |         <div className={styles.form}>
 47 |           <div className={styles.logo}>
 48 |             <img alt="logo" src={config.logoPath} />
 49 |             <span>{config.siteName}</span>
 50 |           </div>
 51 |           <Form
 52 |             onFinish={handleOk}
 53 |             >
 54 |             <FormItem name="username" 
 55 |               rules={[{ required: true }]} hasFeedback>
 56 |                 <Input
 57 |                   placeholder={t`Username`}
 58 |                 />
 59 |             </FormItem>
 60 |             <Trans id="Password" render={({translation}) => (
 61 |               <FormItem name="password" rules={[{ required: true }]} hasFeedback>
 62 |               <Input type='password' placeholder={translation} required />
 63 |               </FormItem>)} 
 64 |             />
 65 |             <Row>
 66 |               <Button
 67 |                 type="primary"
 68 |                 htmlType="submit"
 69 |                 loading={loading.effects.login}
 70 |               >
 71 |                 <Trans>Sign in</Trans>
 72 |               </Button>
 73 |               <p>
 74 |                 <span className="margin-right">
 75 |                   <Trans>Username</Trans>
 76 |                   :guest
 77 |                 </span>
 78 |                 <span>
 79 |                   <Trans>Password</Trans>
 80 |                   :guest
 81 |                 </span>
 82 |               </p>
 83 |             </Row>
 84 |           </Form>
 85 |         </div>
 86 |         <div className={styles.footer}>
 87 |           <GlobalFooter links={footerLinks} copyright={config.copyright} />
 88 |         </div>
 89 |       </Fragment>
 90 |     )
 91 |   }
 92 | }
 93 | 
 94 | Login.propTypes = {
 95 |   form: PropTypes.object,
 96 |   dispatch: PropTypes.func,
 97 |   loading: PropTypes.object,
 98 | }
 99 | 
100 | export default Login
101 | 


--------------------------------------------------------------------------------
/src/pages/login/index.less:
--------------------------------------------------------------------------------
 1 | @import '~themes/vars';
 2 | 
 3 | .form {
 4 |   position: absolute;
 5 |   top: 45%;
 6 |   left: 50%;
 7 |   margin: -160px 0 0 -160px;
 8 |   width: 320px;
 9 |   height: 320px;
10 |   padding: 36px;
11 |   box-shadow: 0 0 100px rgba(0, 0, 0, 0.08);
12 | 
13 |   button {
14 |     width: 100%;
15 |   }
16 | 
17 |   p {
18 |     color: rgb(204, 204, 204);
19 |     text-align: center;
20 |     margin-top: 16px;
21 |     font-size: 12px;
22 |     display: flex;
23 |     justify-content: space-between;
24 |   }
25 | }
26 | 
27 | .logo {
28 |   text-align: center;
29 |   cursor: pointer;
30 |   margin-bottom: 24px;
31 |   display: flex;
32 |   justify-content: center;
33 |   align-items: center;
34 | 
35 |   img {
36 |     width: 40px;
37 |     margin-right: 8px;
38 |   }
39 | 
40 |   span {
41 |     vertical-align: text-bottom;
42 |     font-size: 16px;
43 |     text-transform: uppercase;
44 |     display: inline-block;
45 |     font-weight: 700;
46 |     color: @primary-color;
47 |     .text-gradient();
48 |   }
49 | }
50 | 
51 | .ant-spin-container,
52 | .ant-spin-nested-loading {
53 |   height: 100%;
54 | }
55 | 
56 | .footer {
57 |   position: absolute;
58 |   width: 100%;
59 |   bottom: 0;
60 | }
61 | 


--------------------------------------------------------------------------------
/src/pages/login/model.js:
--------------------------------------------------------------------------------
 1 | import { history } from 'umi'
 2 | const { pathToRegexp } = require("path-to-regexp")
 3 | import api from 'api'
 4 | 
 5 | const { loginUser } = api
 6 | 
 7 | export default {
 8 |   namespace: 'login',
 9 | 
10 |   state: {},
11 |   // subscriptions: {
12 |   //   setup({ dispatch, history }) {
13 |   //     history.listen(location => {
14 |   //       if (pathToRegexp('/login').exec(location.pathname)) {
15 |   //       }
16 |   //     })
17 |   //   },
18 |   // },
19 |   effects: {
20 |     *login({ payload }, { put, call, select }) {
21 |       const data = yield call(loginUser, payload)
22 |       const { locationQuery } = yield select(_ => _.app)
23 |       if (data.success) {
24 |         const { from } = locationQuery
25 |         yield put({ type: 'app/query' })
26 |         if (!pathToRegexp('/login').exec(from)) {
27 |           if (['', '/'].includes(from)) history.push('/dashboard')
28 |           else history.push(from)
29 |         } else {
30 |           history.push('/dashboard')
31 |         }
32 |       } else {
33 |         throw data
34 |       }
35 |     },
36 |   },
37 | }
38 | 


--------------------------------------------------------------------------------
/src/pages/post/components/List.js:
--------------------------------------------------------------------------------
 1 | import React, { PureComponent } from 'react'
 2 | import { Table, Avatar } from 'antd'
 3 | import { t } from "@lingui/macro"
 4 | import { Ellipsis } from 'components'
 5 | import styles from './List.less'
 6 | 
 7 | class List extends PureComponent {
 8 |   render() {
 9 |     const { ...tableProps } = this.props
10 |     const columns = [
11 |       {
12 |         title: t`Image`,
13 |         dataIndex: 'image',
14 |         render: text => <Avatar shape="square" src={text} />,
15 |       },
16 |       {
17 |         title: t`Title`,
18 |         dataIndex: 'title',
19 |         render: text => (
20 |           <Ellipsis tooltip length={30}>
21 |             {text}
22 |           </Ellipsis>
23 |         ),
24 |       },
25 |       {
26 |         title: t`Author`,
27 |         dataIndex: 'author',
28 |       },
29 |       {
30 |         title: t`Categories`,
31 |         dataIndex: 'categories',
32 |       },
33 |       {
34 |         title: t`Tags`,
35 |         dataIndex: 'tags',
36 |       },
37 |       {
38 |         title: t`Visibility`,
39 |         dataIndex: 'visibility',
40 |       },
41 |       {
42 |         title: t`Comments`,
43 |         dataIndex: 'comments',
44 |       },
45 |       {
46 |         title: t`Views`,
47 |         dataIndex: 'views',
48 |       },
49 |       {
50 |         title: t`Publish Date`,
51 |         dataIndex: 'date',
52 |       },
53 |     ]
54 | 
55 |     return (
56 |       <Table
57 |         {...tableProps}
58 |         pagination={{
59 |           ...tableProps.pagination,
60 |           showTotal: total => t`Total ${total} Items`,
61 |         }}
62 |         bordered
63 |         scroll={{ x: 1200 }}
64 |         className={styles.table}
65 |         columns={columns}
66 |         simple
67 |         rowKey={record => record.id}
68 |       />
69 |     )
70 |   }
71 | }
72 | 
73 | export default List
74 | 


--------------------------------------------------------------------------------
/src/pages/post/components/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 |   :global {
3 |     .ant-table td {
4 |       white-space: nowrap;
5 |     }
6 |   }
7 | }
8 | 


--------------------------------------------------------------------------------
/src/pages/post/index.js:
--------------------------------------------------------------------------------
 1 | import React, { PureComponent } from 'react'
 2 | import PropTypes from 'prop-types'
 3 | import { connect } from 'umi'
 4 | import { Tabs } from 'antd'
 5 | import { history } from 'umi'
 6 | import { stringify } from 'qs'
 7 | import { t } from "@lingui/macro"
 8 | import { Page } from 'components'
 9 | import List from './components/List'
10 | 
11 | const { TabPane } = Tabs
12 | 
13 | const EnumPostStatus = {
14 |   UNPUBLISH: 1,
15 |   PUBLISHED: 2,
16 | }
17 | 
18 | @connect(({ post, loading }) => ({ post, loading }))
19 | class Post extends PureComponent {
20 |   handleTabClick = key => {
21 |     const { pathname } = this.props.location
22 | 
23 |     history.push({
24 |       pathname,
25 |       search: stringify({
26 |         status: key,
27 |       }),
28 |     })
29 |   }
30 | 
31 |   get listProps() {
32 |     const { post, loading, location } = this.props
33 |     const { list, pagination } = post
34 |     const { query, pathname } = location
35 | 
36 |     return {
37 |       pagination,
38 |       dataSource: list,
39 |       loading: loading.effects['post/query'],
40 |       onChange(page) {
41 |         history.push({
42 |           pathname,
43 |           search: stringify({
44 |             ...query,
45 |             page: page.current,
46 |             pageSize: page.pageSize,
47 |           }),
48 |         })
49 |       },
50 |     }
51 |   }
52 | 
53 |   render() {
54 |     const { location } = this.props
55 |     const { query } = location
56 | 
57 |     return (
58 |       <Page inner>
59 |         <Tabs
60 |           activeKey={
61 |             query.status === String(EnumPostStatus.UNPUBLISH)
62 |               ? String(EnumPostStatus.UNPUBLISH)
63 |               : String(EnumPostStatus.PUBLISHED)
64 |           }
65 |           onTabClick={this.handleTabClick}
66 |         >
67 |           <TabPane
68 |             tab={t`Publised`}
69 |             key={String(EnumPostStatus.PUBLISHED)}
70 |           >
71 |             <List {...this.listProps} />
72 |           </TabPane>
73 |           <TabPane
74 |             tab={t`Unpublished`}
75 |             key={String(EnumPostStatus.UNPUBLISH)}
76 |           >
77 |             <List {...this.listProps} />
78 |           </TabPane>
79 |         </Tabs>
80 |       </Page>
81 |     )
82 |   }
83 | }
84 | 
85 | Post.propTypes = {
86 |   post: PropTypes.object,
87 |   loading: PropTypes.object,
88 |   location: PropTypes.object,
89 |   dispatch: PropTypes.func,
90 | }
91 | 
92 | export default Post
93 | 


--------------------------------------------------------------------------------
/src/pages/post/model.js:
--------------------------------------------------------------------------------
 1 | import modelExtend from 'dva-model-extend'
 2 | import api from 'api'
 3 | const { pathToRegexp } = require("path-to-regexp")
 4 | import { pageModel } from 'utils/model'
 5 | 
 6 | const { queryPostList } = api
 7 | 
 8 | export default modelExtend(pageModel, {
 9 |   namespace: 'post',
10 | 
11 |   subscriptions: {
12 |     setup({ dispatch, history }) {
13 |       history.listen(location => {
14 |         if (pathToRegexp('/post').exec(location.pathname)) {
15 |           dispatch({
16 |             type: 'query',
17 |             payload: {
18 |               status: 2,
19 |               ...location.query,
20 |             },
21 |           })
22 |         }
23 |       })
24 |     },
25 |   },
26 | 
27 |   effects: {
28 |     *query({ payload }, { call, put }) {
29 |       const data = yield call(queryPostList, payload)
30 |       if (data.success) {
31 |         yield put({
32 |           type: 'querySuccess',
33 |           payload: {
34 |             list: data.data,
35 |             pagination: {
36 |               current: Number(payload.page) || 1,
37 |               pageSize: Number(payload.pageSize) || 10,
38 |               total: data.total,
39 |             },
40 |           },
41 |         })
42 |       } else {
43 |         throw data
44 |       }
45 |     },
46 |   },
47 | })
48 | 


--------------------------------------------------------------------------------
/src/pages/request/index.less:
--------------------------------------------------------------------------------
 1 | @import '~themes/vars';
 2 | 
 3 | .result {
 4 |   height: 600px;
 5 |   width: 100%;
 6 |   background: @hover-color;
 7 |   border-color: #ddd;
 8 |   padding: 16px;
 9 |   margin-top: 16px;
10 |   word-break: break-word;
11 |   line-height: 2;
12 |   overflow: scroll;
13 | }
14 | 
15 | .requestList {
16 |   padding-right: 24px;
17 |   margin-bottom: 24px;
18 |   .listItem {
19 |     cursor: pointer;
20 |     padding-left: 8px;
21 |     &.lstItemActive {
22 |       background-color: @hover-color;
23 |     }
24 |     .background-hover();
25 |   }
26 | }
27 | 
28 | .paramsBlock {
29 |   overflow: visible;
30 |   opacity: 1;
31 |   height: auto;
32 |   transition: opacity 0.3s;
33 |   &.hideParams {
34 |     width: 0;
35 |     height: 0;
36 |     opacity: 0;
37 |     overflow: hidden;
38 |   }
39 | }
40 | 


--------------------------------------------------------------------------------
/src/pages/user/[id]/index.js:
--------------------------------------------------------------------------------
 1 | import React, { PureComponent } from 'react'
 2 | import PropTypes from 'prop-types'
 3 | import { connect } from 'umi'
 4 | import { Page } from 'components'
 5 | import styles from './index.less'
 6 | 
 7 | @connect(({ userDetail }) => ({ userDetail }))
 8 | class UserDetail extends PureComponent {
 9 |   render() {
10 |     const { userDetail } = this.props
11 |     const { data } = userDetail
12 |     const content = []
13 |     for (let key in data) {
14 |       if ({}.hasOwnProperty.call(data, key)) {
15 |         content.push(
16 |           <div key={key} className={styles.item}>
17 |             <div>{key}</div>
18 |             <div>{String(data[key])}</div>
19 |           </div>
20 |         )
21 |       }
22 |     }
23 |     return (
24 |       <Page inner>
25 |         <div className={styles.content}>{content}</div>
26 |       </Page>
27 |     )
28 |   }
29 | }
30 | 
31 | UserDetail.propTypes = {
32 |   userDetail: PropTypes.object,
33 | }
34 | 
35 | export default UserDetail
36 | 


--------------------------------------------------------------------------------
/src/pages/user/[id]/index.less:
--------------------------------------------------------------------------------
 1 | .content {
 2 |   line-height: 2.4;
 3 |   font-size: 13px;
 4 | 
 5 |   .item {
 6 |     display: flex;
 7 | 
 8 |     & > div {
 9 |       &:first-child {
10 |         width: 100px;
11 |       }
12 |     }
13 |   }
14 | }
15 | 


--------------------------------------------------------------------------------
/src/pages/user/[id]/models/detail.js:
--------------------------------------------------------------------------------
 1 | const { pathToRegexp } = require("path-to-regexp")
 2 | import api from 'api'
 3 | 
 4 | const { queryUser } = api
 5 | 
 6 | export default {
 7 |   namespace: 'userDetail',
 8 | 
 9 |   state: {
10 |     data: {},
11 |   },
12 | 
13 |   subscriptions: {
14 |     setup({ dispatch, history }) {
15 |       history.listen(({ pathname }) => {
16 |         const match = pathToRegexp('/user/:id').exec(pathname)
17 |         if (match) {
18 |           dispatch({ type: 'query', payload: { id: match[1] } })
19 |         }
20 |       })
21 |     },
22 |   },
23 | 
24 |   effects: {
25 |     *query({ payload }, { call, put }) {
26 |       const data = yield call(queryUser, payload)
27 |       const { success, message, status, ...other } = data
28 |       if (success) {
29 |         yield put({
30 |           type: 'querySuccess',
31 |           payload: {
32 |             data: other,
33 |           },
34 |         })
35 |       } else {
36 |         throw data
37 |       }
38 |     },
39 |   },
40 | 
41 |   reducers: {
42 |     querySuccess(state, { payload }) {
43 |       const { data } = payload
44 |       return {
45 |         ...state,
46 |         data,
47 |       }
48 |     },
49 |   },
50 | }
51 | 


--------------------------------------------------------------------------------
/src/pages/user/components/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 |   :global {
3 |     .ant-table td {
4 |       white-space: nowrap;
5 |     }
6 |   }
7 | }
8 | 


--------------------------------------------------------------------------------
/src/pages/user/components/Modal.js:
--------------------------------------------------------------------------------
 1 | import React, { PureComponent } from 'react'
 2 | import PropTypes from 'prop-types'
 3 | import { Form, Input, InputNumber, Radio, Modal, Cascader } from 'antd'
 4 | import { Trans } from "@lingui/macro"
 5 | import city from 'utils/city'
 6 | import { t } from "@lingui/macro"
 7 | 
 8 | const FormItem = Form.Item
 9 | 
10 | const formItemLayout = {
11 |   labelCol: {
12 |     span: 6,
13 |   },
14 |   wrapperCol: {
15 |     span: 14,
16 |   },
17 | }
18 | 
19 | class UserModal extends PureComponent {
20 |   formRef = React.createRef()
21 | 
22 |   handleOk = () => {
23 |     const { item = {}, onOk } = this.props
24 | 
25 |     this.formRef.current.validateFields()
26 |       .then(values => {
27 |         const data = {
28 |           ...values,
29 |           key: item.key,
30 |         }
31 |         data.address = data.address.join(' ')
32 |         onOk(data)
33 |       })
34 |       .catch(errorInfo => {
35 |         console.log(errorInfo)
36 |       })
37 |   }
38 | 
39 |   render() {
40 |     const { item = {}, onOk, form, ...modalProps } = this.props
41 | 
42 |     return (
43 |       (<Modal {...modalProps} onOk={this.handleOk}>
44 |         <Form ref={this.formRef} name="control-ref" initialValues={{ ...item, address: item.address && item.address.split(' ') }} layout="horizontal">
45 |           <FormItem name='name' rules={[{ required: true }]}
46 |             label={t`Name`} hasFeedback {...formItemLayout}>
47 |             <Input />
48 |           </FormItem>
49 |           <FormItem name='nickName' rules={[{ required: true }]}
50 |             label={t`NickName`} hasFeedback {...formItemLayout}>
51 |             <Input />
52 |           </FormItem>
53 |           <FormItem name='isMale' rules={[{ required: true }]}
54 |             label={t`Gender`} hasFeedback {...formItemLayout}>
55 |             <Radio.Group>
56 |               <Radio value>
57 |                 <Trans>Male</Trans>
58 |               </Radio>
59 |               <Radio value={false}>
60 |                 <Trans>Female</Trans>
61 |               </Radio>
62 |             </Radio.Group>
63 |           </FormItem>
64 |           <FormItem name='age' label={t`Age`} hasFeedback {...formItemLayout}>
65 |             <InputNumber min={18} max={100} />
66 |           </FormItem>
67 |           <FormItem name='phone' rules={[{ required: true, pattern: /^1[34578]\d{9}$/, message: t`The input is not valid phone!`, }]}
68 |             label={t`Phone`} hasFeedback {...formItemLayout}>
69 |             <Input />
70 |           </FormItem>
71 |           <FormItem name='email' rules={[{ required: true, pattern: /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/, message: t`The input is not valid E-mail!`, }]}
72 |             label={t`Email`} hasFeedback {...formItemLayout}>
73 |             <Input />
74 |           </FormItem>
75 |           <FormItem name='address' rules={[{ required: true, }]}
76 |             label={t`Address`} hasFeedback {...formItemLayout}>
77 |             <Cascader
78 |               style={{ width: '100%' }}
79 |               options={city}
80 |               placeholder={t`Pick an address`}
81 |             />
82 |           </FormItem>
83 |         </Form>
84 |       </Modal>)
85 |     );
86 |   }
87 | }
88 | 
89 | UserModal.propTypes = {
90 |   type: PropTypes.string,
91 |   item: PropTypes.object,
92 |   onOk: PropTypes.func,
93 | }
94 | 
95 | export default UserModal
96 | 


--------------------------------------------------------------------------------
/src/pages/user/model.js:
--------------------------------------------------------------------------------
  1 | import modelExtend from 'dva-model-extend'
  2 | const { pathToRegexp } = require("path-to-regexp")
  3 | import api from 'api'
  4 | import { pageModel } from 'utils/model'
  5 | 
  6 | const {
  7 |   queryUserList,
  8 |   createUser,
  9 |   removeUser,
 10 |   updateUser,
 11 |   removeUserList,
 12 | } = api
 13 | 
 14 | export default modelExtend(pageModel, {
 15 |   namespace: 'user',
 16 | 
 17 |   state: {
 18 |     currentItem: {},
 19 |     modalOpen: false,
 20 |     modalType: 'create',
 21 |     selectedRowKeys: [],
 22 |   },
 23 | 
 24 |   subscriptions: {
 25 |     setup({ dispatch, history }) {
 26 |       history.listen(location => {
 27 |         if (pathToRegexp('/user').exec(location.pathname)) {
 28 |           const payload = location.query || { page: 1, pageSize: 10 }
 29 |           dispatch({
 30 |             type: 'query',
 31 |             payload,
 32 |           })
 33 |         }
 34 |       })
 35 |     },
 36 |   },
 37 | 
 38 |   effects: {
 39 |     *query({ payload = {} }, { call, put }) {
 40 |       const data = yield call(queryUserList, payload)
 41 |       if (data) {
 42 |         yield put({
 43 |           type: 'querySuccess',
 44 |           payload: {
 45 |             list: data.data,
 46 |             pagination: {
 47 |               current: Number(payload.page) || 1,
 48 |               pageSize: Number(payload.pageSize) || 10,
 49 |               total: data.total,
 50 |             },
 51 |           },
 52 |         })
 53 |       }
 54 |     },
 55 | 
 56 |     *delete({ payload }, { call, put, select }) {
 57 |       const data = yield call(removeUser, { id: payload })
 58 |       const { selectedRowKeys } = yield select(_ => _.user)
 59 |       if (data.success) {
 60 |         yield put({
 61 |           type: 'updateState',
 62 |           payload: {
 63 |             selectedRowKeys: selectedRowKeys.filter(_ => _ !== payload),
 64 |           },
 65 |         })
 66 |       } else {
 67 |         throw data
 68 |       }
 69 |     },
 70 | 
 71 |     *multiDelete({ payload }, { call, put }) {
 72 |       const data = yield call(removeUserList, payload)
 73 |       if (data.success) {
 74 |         yield put({ type: 'updateState', payload: { selectedRowKeys: [] } })
 75 |       } else {
 76 |         throw data
 77 |       }
 78 |     },
 79 | 
 80 |     *create({ payload }, { call, put }) {
 81 |       const data = yield call(createUser, payload)
 82 |       if (data.success) {
 83 |         yield put({ type: 'hideModal' })
 84 |       } else {
 85 |         throw data
 86 |       }
 87 |     },
 88 | 
 89 |     *update({ payload }, { select, call, put }) {
 90 |       const id = yield select(({ user }) => user.currentItem.id)
 91 |       const newUser = { ...payload, id }
 92 |       const data = yield call(updateUser, newUser)
 93 |       if (data.success) {
 94 |         yield put({ type: 'hideModal' })
 95 |       } else {
 96 |         throw data
 97 |       }
 98 |     },
 99 |   },
100 | 
101 |   reducers: {
102 |     showModal(state, { payload }) {
103 |       return { ...state, ...payload, modalOpen: true }
104 |     },
105 | 
106 |     hideModal(state) {
107 |       return { ...state, modalOpen: false }
108 |     },
109 |   },
110 | })
111 | 


--------------------------------------------------------------------------------
/src/plugins/onError.js:
--------------------------------------------------------------------------------
 1 | import { message } from 'antd'
 2 | 
 3 | export default {
 4 |   onError(e, a) {
 5 |     e.preventDefault()
 6 |     if (e.message) {
 7 |       message.error(e.message)
 8 |     } else {
 9 |       /* eslint-disable */
10 |       console.error(e)
11 |     }
12 |   },
13 | }
14 | 


--------------------------------------------------------------------------------
/src/services/api.js:
--------------------------------------------------------------------------------
 1 | export default {
 2 |   queryRouteList: '/routes',
 3 | 
 4 |   queryUserInfo: '/user',
 5 |   logoutUser: '/user/logout',
 6 |   loginUser: 'POST /user/login',
 7 | 
 8 |   queryUser: '/user/:id',
 9 |   queryUserList: '/users',
10 |   updateUser: 'Patch /user/:id',
11 |   createUser: 'POST /user',
12 |   removeUser: 'DELETE /user/:id',
13 |   removeUserList: 'POST /users/delete',
14 | 
15 |   queryPostList: '/posts',
16 | 
17 |   queryDashboard: '/dashboard',
18 | }
19 | 


--------------------------------------------------------------------------------
/src/services/index.js:
--------------------------------------------------------------------------------
 1 | import request from 'utils/request'
 2 | import { apiPrefix } from 'utils/config'
 3 | 
 4 | import api from './api'
 5 | 
 6 | const gen = params => {
 7 |   let url = apiPrefix + params
 8 |   let method = 'GET'
 9 | 
10 |   const paramsArray = params.split(' ')
11 |   if (paramsArray.length === 2) {
12 |     method = paramsArray[0]
13 |     url = apiPrefix + paramsArray[1]
14 |   }
15 | 
16 |   return function(data) {
17 |     return request({
18 |       url,
19 |       data,
20 |       method,
21 |     })
22 |   }
23 | }
24 | 
25 | const APIFunction = {}
26 | for (const key in api) {
27 |   APIFunction[key] = gen(api[key])
28 | }
29 | 
30 | APIFunction.queryWeather = params => {
31 |   params.key = 'i7sau1babuzwhycn'
32 |   return request({
33 |     url: `${apiPrefix}/weather/now.json`,
34 |     data: params,
35 |   })
36 | }
37 | 
38 | export default APIFunction
39 | 


--------------------------------------------------------------------------------
/src/themes/default.less:
--------------------------------------------------------------------------------
 1 | // 本文件是对 ant-design:
 2 | // https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less
 3 | // 相应变量值的覆盖
 4 | // 注意:只需写出要覆盖的变量即可(不需要覆盖的变量不要写)
 5 | 
 6 | /* @import '../../node_modules/antd/lib/style/themes/default.less';*/
 7 | 
 8 | @border-radius-base: 3px;
 9 | @border-radius-sm: 2px;
10 | @shadow-color: rgba(0, 0, 0, 0.05);
11 | @shadow-1-down: 4px 4px 40px @shadow-color;
12 | @border-color-split: #f4f4f4;
13 | @border-color-base: #e5e5e5;
14 | @font-size-base: 13px;
15 | @text-color: #666;
16 | @hover-color: #f9f9fc;
17 | 


--------------------------------------------------------------------------------
/src/themes/index.less:
--------------------------------------------------------------------------------
  1 | @import '~antd/dist/reset.css';
  2 | @import '~themes/vars.less';
  3 | 
  4 | body {
  5 |   height: 100%;
  6 |   overflow-y: auto;
  7 |   background-color: #f8f8f8;
  8 | }
  9 | 
 10 | ::-webkit-scrollbar-thumb {
 11 |   background-color: #e6e6e6;
 12 | }
 13 | 
 14 | ::-webkit-scrollbar {
 15 |   width: 8px;
 16 |   height: 8px;
 17 | }
 18 | 
 19 | .margin-right {
 20 |   margin-right: 16px;
 21 | }
 22 | 
 23 | :global {
 24 |   .ant-breadcrumb {
 25 |     & > span {
 26 |       &:last-child {
 27 |         color: #999;
 28 |         font-weight: normal;
 29 |       }
 30 |     }
 31 |   }
 32 | 
 33 |   .ant-breadcrumb-link {
 34 |     .anticon + span {
 35 |       margin-left: 4px;
 36 |     }
 37 |   }
 38 | 
 39 |   .ant-table {
 40 |     .ant-table-thead > tr > th {
 41 |       text-align: center;
 42 |     }
 43 | 
 44 |     .ant-table-tbody > tr > td {
 45 |       text-align: center;
 46 |     }
 47 | 
 48 |     &.ant-table-small {
 49 |       .ant-table-thead > tr > th {
 50 |         background: #f7f7f7;
 51 |       }
 52 | 
 53 |       .ant-table-body > table {
 54 |         padding: 0;
 55 |       }
 56 |     }
 57 |   }
 58 | 
 59 |   .ant-table-pagination {
 60 |     float: none !important;
 61 |     display: table;
 62 |     margin: 16px auto !important;
 63 |   }
 64 | 
 65 |   .ant-popover-inner {
 66 |     border: none;
 67 |     border-radius: 0;
 68 |     box-shadow: 0 0 20px rgba(100, 100, 100, 0.2);
 69 |   }
 70 | 
 71 |   .ant-form-item-control {
 72 |     vertical-align: middle;
 73 |   }
 74 | 
 75 |   .ant-modal-mask {
 76 |     background-color: rgba(55, 55, 55, 0.2);
 77 |   }
 78 | 
 79 |   .ant-modal-content {
 80 |     box-shadow: none;
 81 |   }
 82 | 
 83 |   .ant-select-dropdown-menu-item {
 84 |     padding: 12px 16px !important;
 85 |   }
 86 | 
 87 |   a:focus {
 88 |     text-decoration: none;
 89 |   }
 90 | 
 91 |   .ant-table-layout-fixed table {
 92 |     table-layout: auto;
 93 |   }
 94 | }
 95 | @media (min-width: 1600px) {
 96 |   :global {
 97 |     .ant-col-xl-48 {
 98 |       width: 20%;
 99 |     }
100 | 
101 |     .ant-col-xl-96 {
102 |       width: 40%;
103 |     }
104 |   }
105 | }
106 | @media (max-width: 767px) {
107 |   :global {
108 |     .ant-pagination-item,
109 |     .ant-pagination-next,
110 |     .ant-pagination-options,
111 |     .ant-pagination-prev {
112 |       margin-bottom: 8px;
113 |     }
114 | 
115 |     .ant-card {
116 |       .ant-card-head {
117 |         padding: 0 12px;
118 |       }
119 | 
120 |       .ant-card-body {
121 |         padding: 12px;
122 |       }
123 |     }
124 |   }
125 | }
126 | 


--------------------------------------------------------------------------------
/src/themes/mixin.less:
--------------------------------------------------------------------------------
 1 | @import '~themes/default';
 2 | 
 3 | @dark-half: #494949;
 4 | @purple: #d897eb;
 5 | @shadow-1: 4px 4px 20px 0 rgba(0, 0, 0, 0.01);
 6 | @shadow-2: 4px 4px 40px 0 rgba(0, 0, 0, 0.05);
 7 | @transition-ease-in: all 0.3s ease-out;
 8 | @transition-ease-out: all 0.3s ease-out;
 9 | @ease-in: ease-in;
10 | 
11 | .text-overflow {
12 |   white-space: nowrap;
13 |   text-overflow: ellipsis;
14 |   overflow: hidden;
15 | }
16 | 
17 | .text-gradient {
18 |   background-image: -webkit-gradient(
19 |     linear,
20 |     37.219838% 34.532506%,
21 |     36.425669% 93.178216%,
22 |     from(#29cdff),
23 |     to(#0a60ff),
24 |     color-stop(0.37, #148eff)
25 |   );
26 |   -webkit-background-clip: text;
27 |   -webkit-text-fill-color: transparent;
28 | }
29 | 
30 | .background-hover {
31 |   transition: @transition-ease-in;
32 |   &:hover {
33 |     background-color: @hover-color;
34 |   }
35 | }
36 | 


--------------------------------------------------------------------------------
/src/themes/vars.less:
--------------------------------------------------------------------------------
1 | @import '~themes/default.less';
2 | @import '~themes/mixin.less';
3 | 


--------------------------------------------------------------------------------
/src/utils/config.js:
--------------------------------------------------------------------------------
 1 | module.exports = {
 2 |   siteName: 'AntD Admin',
 3 |   copyright: 'Ant Design Admin  ©2020 zuiidea',
 4 |   logoPath: '/logo.svg',
 5 |   apiPrefix: '/api/v1',
 6 |   fixedHeader: true, // sticky primary layout header
 7 | 
 8 |   /* Layout configuration, specify which layout to use for route. */
 9 |   layouts: [
10 |     {
11 |       name: 'primary',
12 |       include: [/.*/],
13 |       exclude: [/(\/(en|zh))*\/login/],
14 |     },
15 |   ],
16 | 
17 |   /* I18n configuration, `languages` and `defaultLanguage` are required currently. */
18 |   i18n: {
19 |     /* Countrys flags: https://www.flaticon.com/packs/countrys-flags */
20 |     languages: [
21 |       {
22 |         key: 'pt-br',
23 |         title: 'Português',
24 |         flag: '/portugal.svg',
25 |       },
26 |       {
27 |         key: 'en',
28 |         title: 'English',
29 |         flag: '/america.svg',
30 |       },
31 |       {
32 |         key: 'zh',
33 |         title: '中文',
34 |         flag: '/china.svg',
35 |       },
36 |     ],
37 |     defaultLanguage: 'en',
38 |   },
39 | }
40 | 


--------------------------------------------------------------------------------
/src/utils/constant.js:
--------------------------------------------------------------------------------
1 | export const ROLE_TYPE = {
2 |   ADMIN: 'admin',
3 |   DEFAULT: 'admin',
4 |   DEVELOPER: 'developer',
5 | }
6 | 
7 | export const CANCEL_REQUEST_MESSAGE = 'cancel request'
8 | 


--------------------------------------------------------------------------------
/src/utils/iconMap.jsx:
--------------------------------------------------------------------------------
 1 | import {
 2 |   PayCircleOutlined,
 3 |   ShoppingCartOutlined,
 4 |   MessageOutlined,
 5 |   TeamOutlined,
 6 |   UserOutlined,
 7 |   DashboardOutlined,
 8 |   ApiOutlined,
 9 |   CameraOutlined,
10 |   EditOutlined,
11 |   CodeOutlined,
12 |   LineOutlined,
13 |   BarChartOutlined,
14 |   AreaChartOutlined,
15 | } from '@ant-design/icons'
16 | 
17 | export default {
18 |   'pay-circle-o': <PayCircleOutlined />,
19 |   'shopping-cart': <ShoppingCartOutlined />,
20 |   'camera-o': <CameraOutlined />,
21 |   'line-chart': <LineOutlined />,
22 |   'code-o': <CodeOutlined />,
23 |   'area-chart': <AreaChartOutlined />,
24 |   'bar-chart': <BarChartOutlined />,
25 |   message: <MessageOutlined />,
26 |   team: <TeamOutlined />,
27 |   dashboard: <DashboardOutlined />,
28 |   user: <UserOutlined />,
29 |   api: <ApiOutlined />,
30 |   edit: <EditOutlined />,
31 | }
32 | 


--------------------------------------------------------------------------------
/src/utils/index.test.js:
--------------------------------------------------------------------------------
 1 | const { pathToRegexp } = require("path-to-regexp")
 2 | 
 3 | describe('test pathToRegexp', () => {
 4 |   it('get right', () => {
 5 |     expect(pathToRegexp('/user').exec('/zh/user')).toEqual(
 6 |       pathToRegexp('/user').exec('/user')
 7 |     )
 8 |     expect(pathToRegexp('/user').exec('/user')).toEqual(
 9 |       pathToRegexp('/user').exec('/user')
10 |     )
11 | 
12 |     expect(pathToRegexp('/user/:id').exec('/zh/user/1')).toEqual(
13 |       pathToRegexp('/user/:id').exec('/user/1')
14 |     )
15 |     expect(pathToRegexp('/user/:id').exec('/user/1')).toEqual(
16 |       pathToRegexp('/user/:id').exec('/user/1')
17 |     )
18 |   })
19 | })
20 | 


--------------------------------------------------------------------------------
/src/utils/model.js:
--------------------------------------------------------------------------------
 1 | import modelExtend from 'dva-model-extend'
 2 | 
 3 | export const model = {
 4 |   reducers: {
 5 |     updateState(state, { payload }) {
 6 |       return {
 7 |         ...state,
 8 |         ...payload,
 9 |       }
10 |     },
11 |   },
12 | }
13 | 
14 | export const pageModel = modelExtend(model, {
15 |   state: {
16 |     list: [],
17 |     pagination: {
18 |       showSizeChanger: true,
19 |       showQuickJumper: true,
20 |       current: 1,
21 |       total: 0,
22 |       pageSize: 10,
23 |     },
24 |   },
25 | 
26 |   reducers: {
27 |     querySuccess(state, { payload }) {
28 |       const { list, pagination } = payload
29 |       return {
30 |         ...state,
31 |         list,
32 |         pagination: {
33 |           ...state.pagination,
34 |           ...pagination,
35 |         },
36 |       }
37 |     },
38 |   },
39 | })
40 | 


--------------------------------------------------------------------------------
/src/utils/request.js:
--------------------------------------------------------------------------------
 1 | import axios from 'axios'
 2 | import { cloneDeep } from 'lodash'
 3 | const { parse, compile } = require("path-to-regexp")
 4 | import { message } from 'antd'
 5 | import { CANCEL_REQUEST_MESSAGE } from 'utils/constant'
 6 | 
 7 | const { CancelToken } = axios
 8 | window.cancelRequest = new Map()
 9 | 
10 | export default function request(options) {
11 |   let { data, url } = options
12 |   const cloneData = cloneDeep(data)
13 | 
14 |   try {
15 |     let domain = ''
16 |     const urlMatch = url.match(/[a-zA-z]+:\/\/[^/]*/)
17 |     if (urlMatch) {
18 |       ;[domain] = urlMatch
19 |       url = url.slice(domain.length)
20 |     }
21 | 
22 |     const match = parse(url)
23 |     url = compile(url)(data)
24 | 
25 |     for (const item of match) {
26 |       if (item instanceof Object && item.name in cloneData) {
27 |         delete cloneData[item.name]
28 |       }
29 |     }
30 |     url = domain + url
31 |   } catch (e) {
32 |     message.error(e.message)
33 |   }
34 | 
35 |   options.url = url
36 |   options.cancelToken = new CancelToken(cancel => {
37 |     window.cancelRequest.set(Symbol(Date.now()), {
38 |       pathname: window.location.pathname,
39 |       cancel,
40 |     })
41 |   })
42 | 
43 |   return axios(options)
44 |     .then(response => {
45 |       const { statusText, status, data } = response
46 | 
47 |       let result = {}
48 |       if (typeof data === 'object') {
49 |         result = data
50 |         if (Array.isArray(data)) {
51 |           result.list = data
52 |         }
53 |       } else {
54 |         result.data = data
55 |       }
56 | 
57 |       return Promise.resolve({
58 |         success: true,
59 |         message: statusText,
60 |         statusCode: status,
61 |         ...result,
62 |       })
63 |     })
64 |     .catch(error => {
65 |       const { response, message } = error
66 | 
67 |       if (String(message) === CANCEL_REQUEST_MESSAGE) {
68 |         return {
69 |           success: false,
70 |         }
71 |       }
72 | 
73 |       let msg
74 |       let statusCode
75 | 
76 |       if (response && response instanceof Object) {
77 |         const { data, statusText } = response
78 |         statusCode = response.status
79 |         msg = data.message || statusText
80 |       } else {
81 |         statusCode = 600
82 |         msg = error.message || 'Network Error'
83 |       }
84 | 
85 |       /* eslint-disable */
86 |       return Promise.reject({
87 |         success: false,
88 |         statusCode,
89 |         message: msg,
90 |       })
91 |     })
92 | }
93 | 


--------------------------------------------------------------------------------
/src/utils/theme.js:
--------------------------------------------------------------------------------
 1 | module.exports = {
 2 |   Color: {
 3 |     green: '#64ea91',
 4 |     blue: '#8fc9fb',
 5 |     purple: '#d897eb',
 6 |     red: '#f69899',
 7 |     yellow: '#f8c82e',
 8 |     peach: '#f797d6',
 9 |     borderBase: '#e5e5e5',
10 |     borderSplit: '#f4f4f4',
11 |     grass: '#d6fbb5',
12 |     sky: '#c1e0fc',
13 |   },
14 | }
15 | 


--------------------------------------------------------------------------------