Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
23a821b236
47
.gitignore
vendored
47
.gitignore
vendored
@ -1,3 +1,6 @@
|
||||
|
||||
# 查看更多 .gitignore 配置 -> https://help.github.com/articles/ignoring-files/
|
||||
|
||||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
@ -23,4 +26,46 @@ target/*
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
/build/
|
||||
/build/
|
||||
|
||||
|
||||
|
||||
### admin-web ###
|
||||
|
||||
# dependencies
|
||||
**/node_modules
|
||||
|
||||
# roadhog-api-doc ignore
|
||||
/src/utils/request-temp.js
|
||||
_roadhog-api-doc
|
||||
|
||||
# production
|
||||
/dist
|
||||
/.vscode
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
npm-debug.log*
|
||||
yarn-error.log
|
||||
|
||||
/coverage
|
||||
.idea
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
*bak
|
||||
.vscode
|
||||
|
||||
# visual studio code
|
||||
.history
|
||||
*.log
|
||||
|
||||
functions/mock
|
||||
.temp/**
|
||||
|
||||
# umi
|
||||
.umi
|
||||
.umi-production
|
||||
|
||||
# screenshot
|
||||
screenshot
|
||||
.firebase
|
35
admin-web/.dockerignore
Normal file
35
admin-web/.dockerignore
Normal file
@ -0,0 +1,35 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
**/node_modules
|
||||
/src/utils/request-temp.js
|
||||
|
||||
# production
|
||||
/.vscode
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
npm-debug.log*
|
||||
yarn-error.log
|
||||
|
||||
/coverage
|
||||
.idea
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
*bak
|
||||
.vscode
|
||||
|
||||
# visual studio code
|
||||
.history
|
||||
*.log
|
||||
|
||||
functions/mock
|
||||
.temp/**
|
||||
|
||||
# umi
|
||||
.umi
|
||||
.umi-production
|
||||
|
||||
# screenshot
|
||||
screenshot
|
||||
.firebase
|
16
admin-web/.editorconfig
Normal file
16
admin-web/.editorconfig
Normal file
@ -0,0 +1,16 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
3
admin-web/.eslintignore
Normal file
3
admin-web/.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
/functions/mock/**
|
||||
/scripts
|
||||
/config
|
39
admin-web/.eslintrc.js
Normal file
39
admin-web/.eslintrc.js
Normal file
@ -0,0 +1,39 @@
|
||||
module.exports = {
|
||||
parser: 'babel-eslint',
|
||||
extends: ['airbnb', 'prettier', 'plugin:compat/recommended'],
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true,
|
||||
mocha: true,
|
||||
jest: true,
|
||||
jasmine: true,
|
||||
},
|
||||
globals: {
|
||||
APP_TYPE: true,
|
||||
page: true,
|
||||
},
|
||||
rules: {
|
||||
'react/jsx-filename-extension': [1, { extensions: ['.js'] }],
|
||||
'react/jsx-wrap-multilines': 0,
|
||||
'react/prop-types': 0,
|
||||
'react/forbid-prop-types': 0,
|
||||
'react/jsx-one-expression-per-line': 0,
|
||||
'import/no-unresolved': [2, { ignore: ['^@/', '^umi/'] }],
|
||||
'import/no-extraneous-dependencies': [
|
||||
2,
|
||||
{
|
||||
optionalDependencies: true,
|
||||
devDependencies: ['**/tests/**.js', '/mock/**/**.js', '**/**.test.js'],
|
||||
},
|
||||
],
|
||||
'jsx-a11y/no-noninteractive-element-interactions': 0,
|
||||
'jsx-a11y/click-events-have-key-events': 0,
|
||||
'jsx-a11y/no-static-element-interactions': 0,
|
||||
'jsx-a11y/anchor-is-valid': 0,
|
||||
'linebreak-style': 0,
|
||||
},
|
||||
settings: {
|
||||
polyfills: ['fetch', 'promises', 'url'],
|
||||
},
|
||||
};
|
5
admin-web/.firebaserc
Normal file
5
admin-web/.firebaserc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"projects": {
|
||||
"default": "antd-pro"
|
||||
}
|
||||
}
|
38
admin-web/.gitignore
vendored
Normal file
38
admin-web/.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
**/node_modules
|
||||
# roadhog-api-doc ignore
|
||||
/src/utils/request-temp.js
|
||||
_roadhog-api-doc
|
||||
|
||||
# production
|
||||
/dist
|
||||
/.vscode
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
npm-debug.log*
|
||||
yarn-error.log
|
||||
|
||||
/coverage
|
||||
.idea
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
*bak
|
||||
.vscode
|
||||
|
||||
# visual studio code
|
||||
.history
|
||||
*.log
|
||||
|
||||
functions/mock
|
||||
.temp/**
|
||||
|
||||
# umi
|
||||
.umi
|
||||
.umi-production
|
||||
|
||||
# screenshot
|
||||
screenshot
|
||||
.firebase
|
6
admin-web/.gitpod.yml
Normal file
6
admin-web/.gitpod.yml
Normal file
@ -0,0 +1,6 @@
|
||||
ports:
|
||||
- port: 8000
|
||||
onOpen: open-preview
|
||||
tasks:
|
||||
- init: npm install
|
||||
command: npm start
|
5
admin-web/.prettierignore
Normal file
5
admin-web/.prettierignore
Normal file
@ -0,0 +1,5 @@
|
||||
**/*.md
|
||||
**/*.svg
|
||||
package.json
|
||||
.umi
|
||||
.umi-production
|
11
admin-web/.prettierrc
Normal file
11
admin-web/.prettierrc
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"overrides": [
|
||||
{
|
||||
"files": ".prettierrc",
|
||||
"options": { "parser": "json" }
|
||||
}
|
||||
]
|
||||
}
|
13
admin-web/.stylelintrc.json
Normal file
13
admin-web/.stylelintrc.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": [
|
||||
"stylelint-config-standard",
|
||||
"stylelint-config-css-modules",
|
||||
"stylelint-config-rational-order",
|
||||
"stylelint-config-prettier"
|
||||
],
|
||||
"plugins": ["stylelint-order", "stylelint-declaration-block-no-ignored-properties"],
|
||||
"rules": {
|
||||
"no-descending-specificity": null,
|
||||
"plugin/declaration-block-no-ignored-properties": true
|
||||
}
|
||||
}
|
46
admin-web/CODE_OF_CONDUCT.md
Normal file
46
admin-web/CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at afc163@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
12
admin-web/Dockerfile
Normal file
12
admin-web/Dockerfile
Normal file
@ -0,0 +1,12 @@
|
||||
FROM circleci/node:latest-browsers
|
||||
|
||||
WORKDIR /usr/src/app/
|
||||
USER root
|
||||
COPY package.json ./
|
||||
RUN yarn
|
||||
|
||||
COPY ./ ./
|
||||
|
||||
RUN npm run test:all
|
||||
|
||||
CMD ["npm", "run", "build"]
|
11
admin-web/Dockerfile.dev
Normal file
11
admin-web/Dockerfile.dev
Normal file
@ -0,0 +1,11 @@
|
||||
FROM node:latest
|
||||
|
||||
WORKDIR /usr/src/app/
|
||||
|
||||
COPY package.json ./
|
||||
RUN npm install --silent --no-cache
|
||||
|
||||
COPY ./ ./
|
||||
|
||||
|
||||
CMD ["npm", "run", "start"]
|
11
admin-web/Dockerfile.hub
Normal file
11
admin-web/Dockerfile.hub
Normal file
@ -0,0 +1,11 @@
|
||||
FROM nginx
|
||||
|
||||
WORKDIR /usr/src/app/
|
||||
|
||||
COPY ./docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
COPY ./dist /usr/share/nginx/html/
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
21
admin-web/LICENSE
Normal file
21
admin-web/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Alipay.inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
136
admin-web/README-antd.md
Normal file
136
admin-web/README-antd.md
Normal file
@ -0,0 +1,136 @@
|
||||
English | [简体中文](./README.zh-CN.md) | [Русский](./README.ru-RU.md)
|
||||
|
||||
<h1 align="center">Ant Design Pro</h1>
|
||||
|
||||
<div align="center">
|
||||
|
||||
An out-of-box UI solution for enterprise applications as a React boilerplate.
|
||||
|
||||
[![Build With Umi](https://img.shields.io/badge/build%20with-umi-028fe4.svg?style=flat-square)](http://umijs.org/)
|
||||
[![Build Status](https://dev.azure.com/ant-design/ant-design-pro/_apis/build/status/ant-design.ant-design-pro?branchName=master)](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master)
|
||||
[![Dependencies](https://img.shields.io/david/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro)
|
||||
[![DevDependencies](https://img.shields.io/david/dev/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro?type=dev)
|
||||
[![Gitter](https://img.shields.io/gitter/room/ant-design/pro-english.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjEyMzUiIGhlaWdodD0iNjUwIiB2aWV3Qm94PSIwIDAgNzQxMCAzOTAwIj4NCjxyZWN0IHdpZHRoPSI3NDEwIiBoZWlnaHQ9IjM5MDAiIGZpbGw9IiNiMjIyMzQiLz4NCjxwYXRoIGQ9Ik0wLDQ1MEg3NDEwbTAsNjAwSDBtMCw2MDBINzQxMG0wLDYwMEgwbTAsNjAwSDc0MTBtMCw2MDBIMCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjMwMCIvPg0KPHJlY3Qgd2lkdGg9IjI5NjQiIGhlaWdodD0iMjEwMCIgZmlsbD0iIzNjM2I2ZSIvPg0KPGcgZmlsbD0iI2ZmZiI%2BDQo8ZyBpZD0iczE4Ij4NCjxnIGlkPSJzOSI%2BDQo8ZyBpZD0iczUiPg0KPGcgaWQ9InM0Ij4NCjxwYXRoIGlkPSJzIiBkPSJNMjQ3LDkwIDMxNy41MzQyMzAsMzA3LjA4MjAzOSAxMzIuODczMjE4LDE3Mi45MTc5NjFIMzYxLjEyNjc4MkwxNzYuNDY1NzcwLDMwNy4wODIwMzl6Ii8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB5PSI0MjAiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHk9Ijg0MCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTI2MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTY4MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjczQiIHg9IjI0NyIgeT0iMjEwIi8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzOSIgeD0iNDk0Ii8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzMTgiIHg9Ijk4OCIvPg0KPHVzZSB4bGluazpocmVmPSIjczkiIHg9IjE5NzYiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3M1IiB4PSIyNDcwIi8%2BDQo8L2c%2BDQo8L3N2Zz4%3D)](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
[![Join the chat at https://gitter.im/ant-design/ant-design-pro](https://img.shields.io/gitter/room/ant-design/ant-design-pro.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png)
|
||||
|
||||
</div>
|
||||
|
||||
- Preview: http://preview.pro.ant.design
|
||||
- Home Page: http://pro.ant.design
|
||||
- Documentation: http://pro.ant.design/docs/getting-started
|
||||
- ChangeLog: http://pro.ant.design/docs/changelog
|
||||
- FAQ: http://pro.ant.design/docs/faq
|
||||
- Mirror Site in China: http://ant-design-pro.gitee.io
|
||||
|
||||
## 2.0 Released Now! 🎉🎉🎉
|
||||
[Announcing Ant Design Pro 2.0.0](https://medium.com/ant-design/beautiful-and-powerful-ant-design-pro-2-0-release-51358da5af95)
|
||||
|
||||
## Translation Recruitment :loudspeaker:
|
||||
|
||||
We need your help: https://github.com/ant-design/ant-design-pro/issues/120
|
||||
|
||||
## Features
|
||||
|
||||
- :gem: **Neat Design**: Follow [Ant Design specification](http://ant.design/)
|
||||
- :triangular_ruler: **Common Templates**: Typical templates for enterprise applications
|
||||
- :rocket: **State of The Art Development**: Newest development stack of React/umi/dva/antd
|
||||
- :iphone: **Responsive**: Designed for variable screen sizes
|
||||
- :art: **Theming**: Customizable theme with simple config
|
||||
- :globe_with_meridians: **International**: Built-in i18n solution
|
||||
- :gear: **Best Practices**: Solid workflow to make your code healthy
|
||||
- :1234: **Mock development**: Easy to use mock development solution
|
||||
- :white_check_mark: **UI Test**: Fly safely with unit and e2e tests
|
||||
|
||||
## Templates
|
||||
|
||||
```
|
||||
- Dashboard
|
||||
- Analytic
|
||||
- Monitor
|
||||
- Workspace
|
||||
- Form
|
||||
- Basic Form
|
||||
- Step Form
|
||||
- Advanced From
|
||||
- List
|
||||
- Standard Table
|
||||
- Standard List
|
||||
- Card List
|
||||
- Search List (Project/Applications/Article)
|
||||
- Profile
|
||||
- Simple Profile
|
||||
- Advanced Profile
|
||||
- Account
|
||||
- Account Center
|
||||
- Account Settings
|
||||
- Result
|
||||
- Success
|
||||
- Failed
|
||||
- Exception
|
||||
- 403
|
||||
- 404
|
||||
- 500
|
||||
- User
|
||||
- Login
|
||||
- Register
|
||||
- Register Result
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Use bash
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/ant-design/ant-design-pro.git --depth=1
|
||||
$ cd ant-design-pro
|
||||
$ npm install
|
||||
$ npm start # visit http://localhost:8000
|
||||
```
|
||||
|
||||
### Use by docker
|
||||
|
||||
```bash
|
||||
# preview
|
||||
$ docker pull antdesign/ant-design-pro
|
||||
$ docker run -p 80:80 antdesign/ant-design-pro
|
||||
# open http://localhost
|
||||
|
||||
# dev
|
||||
$ npm run docker:dev
|
||||
|
||||
# build
|
||||
$ npm run docker:build
|
||||
|
||||
|
||||
# production dev
|
||||
$ npm run docker-prod:dev
|
||||
|
||||
# production build
|
||||
$ npm run docker-prod:build
|
||||
```
|
||||
|
||||
### Use Gitpod
|
||||
|
||||
Open the project in Gitpod (free online dev environment for GitHub) and start coding immediately.
|
||||
|
||||
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/ant-design/ant-design-pro)
|
||||
|
||||
More instructions at [documentation](http://pro.ant.design/docs/getting-started).
|
||||
|
||||
## Browsers support
|
||||
|
||||
Modern browsers and IE11.
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
|
||||
| --------- | --------- | --------- | --------- | --------- |
|
||||
| IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions
|
||||
|
||||
## Contributing
|
||||
|
||||
Any type of contribution is welcome, here are some examples of how you may contribute to this project:
|
||||
|
||||
- Use Ant Design Pro in your daily work.
|
||||
- Submit [issues](http://github.com/ant-design/ant-design-pro/issues) to report bugs or ask questions.
|
||||
- Propose [pull requests](http://github.com/ant-design/ant-design-pro/pulls) to improve our code.
|
5
admin-web/README.md
Normal file
5
admin-web/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# 后台管理页面
|
||||
|
||||
> 采用 antd pro 快速开发
|
||||
|
||||
> TODO
|
103
admin-web/README.ru-RU.md
Normal file
103
admin-web/README.ru-RU.md
Normal file
@ -0,0 +1,103 @@
|
||||
[English](./README.md) | [简体中文](./README.zh-CN.md) | Русский
|
||||
|
||||
<h1 align="center">Ant Design Pro</h1>
|
||||
|
||||
<div align="center">
|
||||
|
||||
UI-решение "из коробки" для корпоративных приложений как React boilerplate
|
||||
|
||||
[![Build With Umi](https://img.shields.io/badge/build%20with-umi-028fe4.svg?style=flat-square)](http://umijs.org/)
|
||||
[![Build Status](https://dev.azure.com/ant-design/ant-design-pro/_apis/build/status/ant-design.ant-design-pro?branchName=master)](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master)
|
||||
[![Dependencies](https://img.shields.io/david/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro)
|
||||
[![DevDependencies](https://img.shields.io/david/dev/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro?type=dev)
|
||||
[![Gitter](https://img.shields.io/gitter/room/ant-design/pro-english.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjEyMzUiIGhlaWdodD0iNjUwIiB2aWV3Qm94PSIwIDAgNzQxMCAzOTAwIj4NCjxyZWN0IHdpZHRoPSI3NDEwIiBoZWlnaHQ9IjM5MDAiIGZpbGw9IiNiMjIyMzQiLz4NCjxwYXRoIGQ9Ik0wLDQ1MEg3NDEwbTAsNjAwSDBtMCw2MDBINzQxMG0wLDYwMEgwbTAsNjAwSDc0MTBtMCw2MDBIMCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjMwMCIvPg0KPHJlY3Qgd2lkdGg9IjI5NjQiIGhlaWdodD0iMjEwMCIgZmlsbD0iIzNjM2I2ZSIvPg0KPGcgZmlsbD0iI2ZmZiI%2BDQo8ZyBpZD0iczE4Ij4NCjxnIGlkPSJzOSI%2BDQo8ZyBpZD0iczUiPg0KPGcgaWQ9InM0Ij4NCjxwYXRoIGlkPSJzIiBkPSJNMjQ3LDkwIDMxNy41MzQyMzAsMzA3LjA4MjAzOSAxMzIuODczMjE4LDE3Mi45MTc5NjFIMzYxLjEyNjc4MkwxNzYuNDY1NzcwLDMwNy4wODIwMzl6Ii8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB5PSI0MjAiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHk9Ijg0MCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTI2MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTY4MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjczQiIHg9IjI0NyIgeT0iMjEwIi8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzOSIgeD0iNDk0Ii8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzMTgiIHg9Ijk4OCIvPg0KPHVzZSB4bGluazpocmVmPSIjczkiIHg9IjE5NzYiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3M1IiB4PSIyNDcwIi8%2BDQo8L2c%2BDQo8L3N2Zz4%3D)](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
[![Join the chat at https://gitter.im/ant-design/ant-design-pro](https://img.shields.io/gitter/room/ant-design/ant-design-pro.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png)
|
||||
|
||||
</div>
|
||||
|
||||
- Демо: http://preview.pro.ant.design
|
||||
- Домашняя страница: http://pro.ant.design
|
||||
- Документация: http://pro.ant.design/docs/getting-started
|
||||
- История изменений: http://pro.ant.design/docs/changelog
|
||||
- FAQ: http://pro.ant.design/docs/faq
|
||||
- Китайское зеркало сайта: http://ant-design-pro.gitee.io
|
||||
|
||||
## Поиск переводчиков :loudspeaker:
|
||||
|
||||
Нам нужна ваша помощь: https://github.com/ant-design/ant-design-pro/issues/120
|
||||
|
||||
## Возможности
|
||||
|
||||
- :gem: **Аккуратный дизайн**: Посмотрите [спецификацию Ant Design](http://ant.design/)
|
||||
- :triangular_ruler: **Общие шаблоны**: Стандартные шаблоны для корпоративных приложений
|
||||
- :rocket: **Разработка, как искусство**: Новейший стек технологий React/umi/dva/antd
|
||||
- :iphone: **Отзывчивая верстка**: Создан для экранов разных размеров
|
||||
- :art: **Темизация**: Возможность изменения темы с помощью конфигурации
|
||||
- :globe_with_meridians: **Мультиязычность**: Встроенное i18n решение
|
||||
- :gear: **Лучшие практики**: Надежные процессы для хорошего кода
|
||||
- :1234: **Разработка по шаблону**: Простое в использовании решение для разработки
|
||||
- :white_check_mark: **UI тесты**: Разрабатывайте безопасно с юнит и e2e тестами
|
||||
|
||||
## Шаблоны
|
||||
|
||||
```
|
||||
- Dashboard
|
||||
- Analytic
|
||||
- Monitor
|
||||
- Workspace
|
||||
- Form
|
||||
- Basic Form
|
||||
- Step Form
|
||||
- Advanced From
|
||||
- List
|
||||
- Standard Table
|
||||
- Standard List
|
||||
- Card List
|
||||
- Search List (Project/Applications/Article)
|
||||
- Profile
|
||||
- Simple Profile
|
||||
- Advanced Profile
|
||||
- Account
|
||||
- Account Center
|
||||
- Account Settings
|
||||
- Result
|
||||
- Success
|
||||
- Failed
|
||||
- Exception
|
||||
- 403
|
||||
- 404
|
||||
- 500
|
||||
- User
|
||||
- Login
|
||||
- Register
|
||||
- Register Result
|
||||
```
|
||||
|
||||
## Использование
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/ant-design/ant-design-pro.git --depth=1
|
||||
$ cd ant-design-pro
|
||||
$ npm install
|
||||
$ npm start # visit http://localhost:8000
|
||||
```
|
||||
|
||||
Больше информации в [документации](http://pro.ant.design/docs/getting-started).
|
||||
|
||||
## Совместимость
|
||||
|
||||
Современные браузеры и IE11.
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
|
||||
| --------- | --------- | --------- | --------- | --------- |
|
||||
| IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions
|
||||
|
||||
## Распространение
|
||||
|
||||
Любые варианты распространения приветствуются! Вот несколько примеров того, как вы можете помочь распространению проекта:
|
||||
|
||||
- Использовать Ant Design Pro в ежедневной работе.
|
||||
- Создавать [задачи](http://github.com/ant-design/ant-design-pro/issues) заводить баги или отвечать на вопросы.
|
||||
- Делать [pull-реквесты](http://github.com/ant-design/ant-design-pro/pulls) для совершенствования нашего кода.
|
121
admin-web/README.zh-CN.md
Normal file
121
admin-web/README.zh-CN.md
Normal file
@ -0,0 +1,121 @@
|
||||
[English](./README.md) | 简体中文 | [Русский](./README.ru-RU.md)
|
||||
|
||||
<h1 align="center">Ant Design Pro</h1>
|
||||
|
||||
<div align="center">
|
||||
|
||||
开箱即用的中台前端/设计解决方案。
|
||||
|
||||
[![Build With Umi](https://img.shields.io/badge/build%20with-umi-028fe4.svg?style=flat-square)](http://umijs.org/)
|
||||
[![Build Status](https://dev.azure.com/ant-design/ant-design-pro/_apis/build/status/ant-design.ant-design-pro?branchName=master)](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master)
|
||||
[![Dependencies](https://img.shields.io/david/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro)
|
||||
[![DevDependencies](https://img.shields.io/david/dev/ant-design/ant-design-pro.svg)](https://david-dm.org/ant-design/ant-design-pro?type=dev)
|
||||
[![Join the chat at https://gitter.im/ant-design/ant-design-pro](https://img.shields.io/gitter/room/ant-design/ant-design-pro.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png)
|
||||
|
||||
</div>
|
||||
|
||||
- 预览:http://preview.pro.ant.design
|
||||
- 首页:http://pro.ant.design/index-cn
|
||||
- 使用文档:http://pro.ant.design/docs/getting-started-cn
|
||||
- 更新日志: http://pro.ant.design/docs/changelog-cn
|
||||
- 常见问题:http://pro.ant.design/docs/faq-cn
|
||||
- 国内镜像:http://ant-design-pro.gitee.io
|
||||
|
||||
## 特性
|
||||
|
||||
- :gem: **优雅美观**:基于 Ant Design 体系精心设计
|
||||
- :triangular_ruler: **常见设计模式**:提炼自中后台应用的典型页面和场景
|
||||
- :rocket: **最新技术栈**:使用 React/umi/dva/antd 等前端前沿技术开发
|
||||
- :iphone: **响应式**:针对不同屏幕大小设计
|
||||
- :art: **主题**:可配置的主题满足多样化的品牌诉求
|
||||
- :globe_with_meridians: **国际化**:内建业界通用的国际化方案
|
||||
- :gear: **最佳实践**:良好的工程实践助您持续产出高质量代码
|
||||
- :1234: **Mock 数据**:实用的本地数据调试方案
|
||||
- :white_check_mark: **UI 测试**:自动化测试保障前端产品质量
|
||||
|
||||
## 模板
|
||||
|
||||
```
|
||||
- Dashboard
|
||||
- 分析页
|
||||
- 监控页
|
||||
- 工作台
|
||||
- 表单页
|
||||
- 基础表单页
|
||||
- 分步表单页
|
||||
- 高级表单页
|
||||
- 列表页
|
||||
- 查询表格
|
||||
- 标准列表
|
||||
- 卡片列表
|
||||
- 搜索列表(项目/应用/文章)
|
||||
- 详情页
|
||||
- 基础详情页
|
||||
- 高级详情页
|
||||
- 用户
|
||||
- 用户中心页
|
||||
- 用户设置页
|
||||
- 结果
|
||||
- 成功页
|
||||
- 失败页
|
||||
- 异常
|
||||
- 403 无权限
|
||||
- 404 找不到
|
||||
- 500 服务器出错
|
||||
- 帐户
|
||||
- 登录
|
||||
- 注册
|
||||
- 注册成功
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
### 使用命令行
|
||||
```bash
|
||||
$ git clone https://github.com/ant-design/ant-design-pro.git --depth=1
|
||||
$ cd ant-design-pro
|
||||
$ npm install
|
||||
$ npm start # 访问 http://localhost:8000
|
||||
```
|
||||
|
||||
### 使用 docker
|
||||
|
||||
```bash
|
||||
# preview
|
||||
$ docker pull antdesign/ant-design-pro
|
||||
$ docker run -p 80:80 antdesign/ant-design-pro
|
||||
# open http://localhost
|
||||
|
||||
# dev
|
||||
$ npm run docker:dev
|
||||
|
||||
# build
|
||||
$ npm run docker:build
|
||||
|
||||
|
||||
# production dev
|
||||
$ npm run docker-prod:dev
|
||||
|
||||
// production build
|
||||
$ npm run docker-prod:build
|
||||
```
|
||||
|
||||
更多信息请参考 [使用文档](http://pro.ant.design/docs/getting-started)。
|
||||
|
||||
## 支持环境
|
||||
|
||||
现代浏览器及 IE11。
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
|
||||
| --------- | --------- | --------- | --------- | --------- |
|
||||
| IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions
|
||||
|
||||
## 参与贡献
|
||||
|
||||
我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :smiley::
|
||||
|
||||
- 在你的公司或个人项目中使用 Ant Design Pro。
|
||||
- 通过 [Issue](http://github.com/ant-design/ant-design-pro/issues) 报告 bug 或进行咨询。
|
||||
- 提交 [Pull Request](http://github.com/ant-design/ant-design-pro/pulls) 改进 Pro 的代码。
|
74
admin-web/azure-pipelines.yml
Normal file
74
admin-web/azure-pipelines.yml
Normal file
@ -0,0 +1,74 @@
|
||||
# Node.js
|
||||
# Build a general Node.js project with npm.
|
||||
# Add steps that analyze code, save build artifacts, deploy, and more:
|
||||
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
|
||||
name: ant design pro
|
||||
|
||||
trigger:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
- job: lintAndBuild
|
||||
|
||||
pool:
|
||||
vmImage: 'Ubuntu-16.04'
|
||||
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: false
|
||||
- script: yarn install
|
||||
displayName: install
|
||||
- script: npm run lint
|
||||
displayName: lint
|
||||
- script: npm run build
|
||||
env:
|
||||
PROGRESS: none
|
||||
displayName: build
|
||||
|
||||
- job: test
|
||||
pool:
|
||||
vmImage: 'Ubuntu-16.04'
|
||||
|
||||
container:
|
||||
image: circleci/node:latest-browsers
|
||||
options: '-u root'
|
||||
|
||||
steps:
|
||||
- script: yarn install
|
||||
displayName: install
|
||||
- script: npm run test:all
|
||||
env:
|
||||
PROGRESS: none
|
||||
displayName: test
|
||||
|
||||
- job: Windows
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '11.x'
|
||||
- script: yarn install
|
||||
displayName: install
|
||||
- script: npm run lint
|
||||
displayName: lint
|
||||
- script: npm run build
|
||||
env:
|
||||
PROGRESS: none
|
||||
displayName: build
|
||||
|
||||
- job: MacOS
|
||||
pool:
|
||||
vmImage: 'macOS-10.13'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '11.x'
|
||||
- script: yarn install
|
||||
displayName: install
|
||||
- script: npm run lint
|
||||
displayName: lint
|
||||
- script: npm run
|
||||
env:
|
||||
PROGRESS: none
|
||||
displayName: build
|
121
admin-web/config/config.js
Normal file
121
admin-web/config/config.js
Normal file
@ -0,0 +1,121 @@
|
||||
// https://umijs.org/config/
|
||||
import os from 'os';
|
||||
import pageRoutes from './router.config';
|
||||
import webpackPlugin from './plugin.config';
|
||||
import defaultSettings from '../src/defaultSettings';
|
||||
import slash from 'slash2';
|
||||
|
||||
const { pwa, primaryColor } = defaultSettings;
|
||||
const { NODE_ENV, APP_TYPE, TEST } = process.env;
|
||||
|
||||
const plugins = [
|
||||
[
|
||||
'umi-plugin-react',
|
||||
{
|
||||
antd: true,
|
||||
dva: {
|
||||
hmr: true,
|
||||
},
|
||||
locale: {
|
||||
enable: true, // default false
|
||||
default: 'zh-CN', // default zh-CN
|
||||
baseNavigator: true, // default true, when it is true, will use `navigator.language` overwrite default
|
||||
},
|
||||
dynamicImport: {
|
||||
loadingComponent: './components/PageLoading/index',
|
||||
webpackChunkName: true,
|
||||
level: 3,
|
||||
},
|
||||
pwa: pwa
|
||||
? {
|
||||
workboxPluginMode: 'InjectManifest',
|
||||
workboxOptions: {
|
||||
importWorkboxFrom: 'local',
|
||||
},
|
||||
}
|
||||
: {},
|
||||
...(!TEST && os.platform() === 'darwin'
|
||||
? {
|
||||
dll: {
|
||||
include: ['dva', 'dva/router', 'dva/saga', 'dva/fetch'],
|
||||
exclude: ['@babel/runtime'],
|
||||
},
|
||||
hardSource: false,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
// 针对 preview.pro.ant.design 的 GA 统计代码
|
||||
// 业务上不需要这个
|
||||
if (APP_TYPE === 'site') {
|
||||
plugins.push([
|
||||
'umi-plugin-ga',
|
||||
{
|
||||
code: 'UA-72788897-6',
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
export default {
|
||||
// add for transfer to umi
|
||||
plugins,
|
||||
define: {
|
||||
APP_TYPE: APP_TYPE || '',
|
||||
},
|
||||
treeShaking: true,
|
||||
targets: {
|
||||
ie: 11,
|
||||
},
|
||||
// 路由配置
|
||||
routes: pageRoutes,
|
||||
// Theme for antd
|
||||
// https://ant.design/docs/react/customize-theme-cn
|
||||
theme: {
|
||||
'primary-color': primaryColor,
|
||||
},
|
||||
externals: {
|
||||
'@antv/data-set': 'DataSet',
|
||||
bizcharts: 'BizCharts',
|
||||
},
|
||||
// proxy: {
|
||||
// '/server/api/': {
|
||||
// target: 'https://preview.pro.ant.design/',
|
||||
// changeOrigin: true,
|
||||
// pathRewrite: { '^/server': '' },
|
||||
// },
|
||||
// },
|
||||
ignoreMomentLocale: true,
|
||||
lessLoaderOptions: {
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
disableRedirectHoist: true,
|
||||
cssLoaderOptions: {
|
||||
modules: true,
|
||||
getLocalIdent: (context, localIdentName, localName) => {
|
||||
if (
|
||||
context.resourcePath.includes('node_modules') ||
|
||||
context.resourcePath.includes('ant.design.pro.less') ||
|
||||
context.resourcePath.includes('global.less')
|
||||
) {
|
||||
return localName;
|
||||
}
|
||||
const match = context.resourcePath.match(/src(.*)/);
|
||||
if (match && match[1]) {
|
||||
const antdProPath = match[1].replace('.less', '');
|
||||
const arr = slash(antdProPath)
|
||||
.split('/')
|
||||
.map(a => a.replace(/([A-Z])/g, '-$1'))
|
||||
.map(a => a.toLowerCase());
|
||||
return `antd-pro${arr.join('-')}-${localName}`.replace(/--/g, '-');
|
||||
}
|
||||
return localName;
|
||||
},
|
||||
},
|
||||
manifest: {
|
||||
basePath: '/',
|
||||
},
|
||||
|
||||
chainWebpack: webpackPlugin,
|
||||
};
|
33
admin-web/config/plugin.config.js
Normal file
33
admin-web/config/plugin.config.js
Normal file
@ -0,0 +1,33 @@
|
||||
// Change theme plugin
|
||||
|
||||
import MergeLessPlugin from 'antd-pro-merge-less';
|
||||
import AntDesignThemePlugin from 'antd-theme-webpack-plugin';
|
||||
import path from 'path';
|
||||
|
||||
export default config => {
|
||||
// pro 和 开发环境再添加这个插件
|
||||
if (process.env.APP_TYPE === 'site' || process.env.NODE_ENV !== 'production') {
|
||||
// 将所有 less 合并为一个供 themePlugin使用
|
||||
const outFile = path.join(__dirname, '../.temp/ant-design-pro.less');
|
||||
const stylesDir = path.join(__dirname, '../src/');
|
||||
|
||||
config.plugin('merge-less').use(MergeLessPlugin, [
|
||||
{
|
||||
stylesDir,
|
||||
outFile,
|
||||
},
|
||||
]);
|
||||
|
||||
config.plugin('ant-design-theme').use(AntDesignThemePlugin, [
|
||||
{
|
||||
antDir: path.join(__dirname, '../node_modules/antd'),
|
||||
stylesDir,
|
||||
varFile: path.join(__dirname, '../node_modules/antd/lib/style/themes/default.less'),
|
||||
mainLessFile: outFile, // themeVariables: ['@primary-color'],
|
||||
indexFileName: 'index.html',
|
||||
generateOne: true,
|
||||
lessUrl: 'https://gw.alipayobjects.com/os/lib/less.js/3.8.1/less.min.js',
|
||||
},
|
||||
]);
|
||||
}
|
||||
};
|
273
admin-web/config/router.config.js
Normal file
273
admin-web/config/router.config.js
Normal file
@ -0,0 +1,273 @@
|
||||
export default [
|
||||
// user
|
||||
{
|
||||
path: '/user',
|
||||
component: '../layouts/UserLayout',
|
||||
routes: [
|
||||
{ path: '/user', redirect: '/user/login' },
|
||||
{ path: '/user/login', name: 'login', component: './User/Login' },
|
||||
{ path: '/user/register', name: 'register', component: './User/Register' },
|
||||
{
|
||||
path: '/user/register-result',
|
||||
name: 'register.result',
|
||||
component: './User/RegisterResult',
|
||||
},
|
||||
],
|
||||
},
|
||||
// app
|
||||
{
|
||||
path: '/',
|
||||
component: '../layouts/BasicLayout',
|
||||
Routes: ['src/pages/Authorized'],
|
||||
authority: ['admin', 'user'],
|
||||
routes: [
|
||||
// dashboard
|
||||
{ path: '/', redirect: '/dashboard/analysis' },
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'dashboard',
|
||||
icon: 'dashboard',
|
||||
routes: [
|
||||
{
|
||||
path: '/dashboard/analysis',
|
||||
name: 'analysis',
|
||||
component: './Dashboard/Analysis',
|
||||
},
|
||||
{
|
||||
path: '/dashboard/monitor',
|
||||
name: 'monitor',
|
||||
component: './Dashboard/Monitor',
|
||||
},
|
||||
{
|
||||
path: '/dashboard/workplace',
|
||||
name: 'workplace',
|
||||
component: './Dashboard/Workplace',
|
||||
},
|
||||
],
|
||||
},
|
||||
// forms
|
||||
{
|
||||
path: '/form',
|
||||
icon: 'form',
|
||||
name: 'form',
|
||||
routes: [
|
||||
{
|
||||
path: '/form/basic-form',
|
||||
name: 'basicform',
|
||||
component: './Forms/BasicForm',
|
||||
},
|
||||
{
|
||||
path: '/form/step-form',
|
||||
name: 'stepform',
|
||||
component: './Forms/StepForm',
|
||||
hideChildrenInMenu: true,
|
||||
routes: [
|
||||
{
|
||||
path: '/form/step-form',
|
||||
redirect: '/form/step-form/info',
|
||||
},
|
||||
{
|
||||
path: '/form/step-form/info',
|
||||
name: 'info',
|
||||
component: './Forms/StepForm/Step1',
|
||||
},
|
||||
{
|
||||
path: '/form/step-form/confirm',
|
||||
name: 'confirm',
|
||||
component: './Forms/StepForm/Step2',
|
||||
},
|
||||
{
|
||||
path: '/form/step-form/result',
|
||||
name: 'result',
|
||||
component: './Forms/StepForm/Step3',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/form/advanced-form',
|
||||
name: 'advancedform',
|
||||
authority: ['admin'],
|
||||
component: './Forms/AdvancedForm',
|
||||
},
|
||||
],
|
||||
},
|
||||
// list
|
||||
{
|
||||
path: '/list',
|
||||
icon: 'table',
|
||||
name: 'list',
|
||||
routes: [
|
||||
{
|
||||
path: '/list/table-list',
|
||||
name: 'searchtable',
|
||||
component: './List/TableList',
|
||||
},
|
||||
{
|
||||
path: '/list/basic-list',
|
||||
name: 'basiclist',
|
||||
component: './List/BasicList',
|
||||
},
|
||||
{
|
||||
path: '/list/card-list',
|
||||
name: 'cardlist',
|
||||
component: './List/CardList',
|
||||
},
|
||||
{
|
||||
path: '/list/search',
|
||||
name: 'searchlist',
|
||||
component: './List/List',
|
||||
routes: [
|
||||
{
|
||||
path: '/list/search',
|
||||
redirect: '/list/search/articles',
|
||||
},
|
||||
{
|
||||
path: '/list/search/articles',
|
||||
name: 'articles',
|
||||
component: './List/Articles',
|
||||
},
|
||||
{
|
||||
path: '/list/search/projects',
|
||||
name: 'projects',
|
||||
component: './List/Projects',
|
||||
},
|
||||
{
|
||||
path: '/list/search/applications',
|
||||
name: 'applications',
|
||||
component: './List/Applications',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
name: 'profile',
|
||||
icon: 'profile',
|
||||
routes: [
|
||||
// profile
|
||||
{
|
||||
path: '/profile/basic',
|
||||
name: 'basic',
|
||||
component: './Profile/BasicProfile',
|
||||
},
|
||||
{
|
||||
path: '/profile/basic/:id',
|
||||
name: 'basic',
|
||||
hideInMenu: true,
|
||||
component: './Profile/BasicProfile',
|
||||
},
|
||||
{
|
||||
path: '/profile/advanced',
|
||||
name: 'advanced',
|
||||
authority: ['admin'],
|
||||
component: './Profile/AdvancedProfile',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'result',
|
||||
icon: 'check-circle-o',
|
||||
path: '/result',
|
||||
routes: [
|
||||
// result
|
||||
{
|
||||
path: '/result/success',
|
||||
name: 'success',
|
||||
component: './Result/Success',
|
||||
},
|
||||
{ path: '/result/fail', name: 'fail', component: './Result/Error' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'exception',
|
||||
icon: 'warning',
|
||||
path: '/exception',
|
||||
routes: [
|
||||
// exception
|
||||
{
|
||||
path: '/exception/403',
|
||||
name: 'not-permission',
|
||||
component: './Exception/403',
|
||||
},
|
||||
{
|
||||
path: '/exception/404',
|
||||
name: 'not-find',
|
||||
component: './Exception/404',
|
||||
},
|
||||
{
|
||||
path: '/exception/500',
|
||||
name: 'server-error',
|
||||
component: './Exception/500',
|
||||
},
|
||||
{
|
||||
path: '/exception/trigger',
|
||||
name: 'trigger',
|
||||
hideInMenu: true,
|
||||
component: './Exception/TriggerException',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'account',
|
||||
icon: 'user',
|
||||
path: '/account',
|
||||
routes: [
|
||||
{
|
||||
path: '/account/center',
|
||||
name: 'center',
|
||||
component: './Account/Center/Center',
|
||||
routes: [
|
||||
{
|
||||
path: '/account/center',
|
||||
redirect: '/account/center/articles',
|
||||
},
|
||||
{
|
||||
path: '/account/center/articles',
|
||||
component: './Account/Center/Articles',
|
||||
},
|
||||
{
|
||||
path: '/account/center/applications',
|
||||
component: './Account/Center/Applications',
|
||||
},
|
||||
{
|
||||
path: '/account/center/projects',
|
||||
component: './Account/Center/Projects',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/account/settings',
|
||||
name: 'settings',
|
||||
component: './Account/Settings/Info',
|
||||
routes: [
|
||||
{
|
||||
path: '/account/settings',
|
||||
redirect: '/account/settings/base',
|
||||
},
|
||||
{
|
||||
path: '/account/settings/base',
|
||||
component: './Account/Settings/BaseView',
|
||||
},
|
||||
{
|
||||
path: '/account/settings/security',
|
||||
component: './Account/Settings/SecurityView',
|
||||
},
|
||||
{
|
||||
path: '/account/settings/binding',
|
||||
component: './Account/Settings/BindingView',
|
||||
},
|
||||
{
|
||||
path: '/account/settings/notification',
|
||||
component: './Account/Settings/NotificationView',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
component: '404',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
14
admin-web/docker/docker-compose.dev.yml
Normal file
14
admin-web/docker/docker-compose.dev.yml
Normal file
@ -0,0 +1,14 @@
|
||||
version: "3.5"
|
||||
|
||||
services:
|
||||
ant-design-pro_dev:
|
||||
ports:
|
||||
- 8000:8000
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: Dockerfile.dev
|
||||
container_name: "ant-design-pro_dev"
|
||||
volumes:
|
||||
- ../src:/usr/src/app/src
|
||||
- ../config:/usr/src/app/config
|
||||
- ../mock:/usr/src/app/mock
|
21
admin-web/docker/docker-compose.yml
Normal file
21
admin-web/docker/docker-compose.yml
Normal file
@ -0,0 +1,21 @@
|
||||
version: "3.5"
|
||||
|
||||
services:
|
||||
ant-design-pro_build:
|
||||
build: ../
|
||||
container_name: "ant-design-pro_build"
|
||||
volumes:
|
||||
- dist:/usr/src/app/dist
|
||||
|
||||
ant-design-pro_web:
|
||||
image: nginx
|
||||
ports:
|
||||
- 80:80
|
||||
container_name: "ant-design-pro_web"
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- dist:/usr/share/nginx/html:ro
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
volumes:
|
||||
dist:
|
22
admin-web/docker/nginx.conf
Normal file
22
admin-web/docker/nginx.conf
Normal file
@ -0,0 +1,22 @@
|
||||
server {
|
||||
listen 80;
|
||||
# gzip config
|
||||
gzip on;
|
||||
gzip_min_length 1k;
|
||||
gzip_comp_level 9;
|
||||
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
|
||||
gzip_vary on;
|
||||
gzip_disable "MSIE [1-6]\.";
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
location /api {
|
||||
proxy_pass https://preview.pro.ant.design;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
}
|
13
admin-web/firebase.json
Normal file
13
admin-web/firebase.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"hosting": {
|
||||
"public": "dist",
|
||||
"rewrites": [
|
||||
{ "source": "/api/**", "function": "api" },
|
||||
{
|
||||
"source": "**",
|
||||
"destination": "/index.html"
|
||||
}
|
||||
],
|
||||
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"]
|
||||
}
|
||||
}
|
10
admin-web/functions/index.js
Normal file
10
admin-web/functions/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
// [START functionsimport]
|
||||
const functions = require('firebase-functions');
|
||||
const express = require('express');
|
||||
|
||||
const matchMock = require('./matchMock');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(matchMock);
|
||||
exports.api = functions.https.onRequest(app);
|
115
admin-web/functions/matchMock.js
Normal file
115
admin-web/functions/matchMock.js
Normal file
@ -0,0 +1,115 @@
|
||||
const pathToRegexp = require('path-to-regexp');
|
||||
const bodyParser = require('body-parser');
|
||||
|
||||
const mockFile = require('./mock/index');
|
||||
|
||||
const BODY_PARSED_METHODS = ['post', 'put', 'patch'];
|
||||
|
||||
const debug = console.log;
|
||||
function parseKey(key) {
|
||||
let method = 'get';
|
||||
let path = key;
|
||||
if (key.indexOf(' ') > -1) {
|
||||
const splited = key.split(' ');
|
||||
method = splited[0].toLowerCase();
|
||||
path = splited[1]; // eslint-disable-line
|
||||
}
|
||||
return {
|
||||
method,
|
||||
path,
|
||||
};
|
||||
}
|
||||
|
||||
function createHandler(method, path, handler) {
|
||||
return (req, res, next) => {
|
||||
function sendData() {
|
||||
if (typeof handler === 'function') {
|
||||
handler(req, res, next);
|
||||
} else {
|
||||
res.json(handler);
|
||||
}
|
||||
}
|
||||
if (BODY_PARSED_METHODS.includes(method)) {
|
||||
bodyParser.json({ limit: '5mb', strict: false })(req, res, () => {
|
||||
bodyParser.urlencoded({ limit: '5mb', extended: true })(req, res, () => {
|
||||
sendData();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
sendData();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeConfig(config) {
|
||||
return Object.keys(config).reduce((memo, key) => {
|
||||
const handler = config[key];
|
||||
const { method, path } = parseKey(key);
|
||||
const keys = [];
|
||||
const re = pathToRegexp(path, keys);
|
||||
memo.push({
|
||||
method,
|
||||
path,
|
||||
re,
|
||||
keys,
|
||||
handler: createHandler(method, path, handler),
|
||||
});
|
||||
return memo;
|
||||
}, []);
|
||||
}
|
||||
|
||||
const mockData = normalizeConfig(mockFile);
|
||||
|
||||
function matchMock(req) {
|
||||
const { path: exceptPath } = req;
|
||||
const exceptMethod = req.method.toLowerCase();
|
||||
function decodeParam(val) {
|
||||
if (typeof val !== 'string' || val.length === 0) {
|
||||
return val;
|
||||
}
|
||||
|
||||
try {
|
||||
return decodeURIComponent(val);
|
||||
} catch (err) {
|
||||
if (err instanceof URIError) {
|
||||
err.message = `Failed to decode param ' ${val} '`;
|
||||
err.statusCode = 400;
|
||||
err.status = 400;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const mock of mockData) {
|
||||
const { method, re, keys } = mock;
|
||||
if (method === exceptMethod) {
|
||||
const match = re.exec(req.path);
|
||||
if (match) {
|
||||
const params = {};
|
||||
|
||||
for (let i = 1; i < match.length; i += 1) {
|
||||
const key = keys[i - 1];
|
||||
const prop = key.name;
|
||||
const val = decodeParam(match[i]);
|
||||
|
||||
if (val !== undefined || !hasOwnProperty.call(params, prop)) {
|
||||
params[prop] = val;
|
||||
}
|
||||
}
|
||||
req.params = params;
|
||||
return mock;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mockData.filter(({ method, re }) => method === exceptMethod && re.test(exceptPath))[0];
|
||||
}
|
||||
module.exports = (req, res, next) => {
|
||||
const match = matchMock(req);
|
||||
if (match) {
|
||||
debug(`mock matched: [${match.method}] ${match.path}`);
|
||||
return match.handler(req, res, next);
|
||||
}
|
||||
return next();
|
||||
};
|
23
admin-web/functions/package.json
Normal file
23
admin-web/functions/package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "functions",
|
||||
"description": "Cloud Functions for Firebase",
|
||||
"scripts": {
|
||||
"serve": "firebase serve --only functions",
|
||||
"shell": "firebase functions:shell",
|
||||
"start": "npm run shell",
|
||||
"deploy": "npm run mock && firebase deploy --only functions",
|
||||
"logs": "firebase functions:log",
|
||||
"mock": "node ../scripts/generateMock.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"body-parser": "^1.18.3",
|
||||
"express": "^4.16.4",
|
||||
"firebase-admin": "^6.4.0",
|
||||
"firebase-functions": "^2.1.0",
|
||||
"mockjs": "^1.0.1-beta3",
|
||||
"moment": "^2.22.2",
|
||||
"path-to-regexp": "^3.0.0"
|
||||
},
|
||||
"private": true
|
||||
}
|
11
admin-web/jest-puppeteer.config.js
Normal file
11
admin-web/jest-puppeteer.config.js
Normal file
@ -0,0 +1,11 @@
|
||||
// ps https://github.com/GoogleChrome/puppeteer/issues/3120
|
||||
module.exports = {
|
||||
launch: {
|
||||
args: [
|
||||
'--disable-gpu',
|
||||
'--disable-dev-shm-usage',
|
||||
'--no-first-run',
|
||||
'--no-zygote',
|
||||
],
|
||||
},
|
||||
};
|
4
admin-web/jest.config.js
Normal file
4
admin-web/jest.config.js
Normal file
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
testURL: 'http://localhost:8000',
|
||||
preset: 'jest-puppeteer',
|
||||
};
|
10
admin-web/jsconfig.json
Normal file
10
admin-web/jsconfig.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
336
admin-web/mock/api.js
Normal file
336
admin-web/mock/api.js
Normal file
@ -0,0 +1,336 @@
|
||||
import mockjs from 'mockjs';
|
||||
|
||||
const titles = [
|
||||
'Alipay',
|
||||
'Angular',
|
||||
'Ant Design',
|
||||
'Ant Design Pro',
|
||||
'Bootstrap',
|
||||
'React',
|
||||
'Vue',
|
||||
'Webpack',
|
||||
];
|
||||
const avatars = [
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', // Alipay
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', // Angular
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', // Ant Design
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', // Ant Design Pro
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', // Bootstrap
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png', // React
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png', // Vue
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png', // Webpack
|
||||
];
|
||||
|
||||
const avatars2 = [
|
||||
'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/cnrhVkzwxjPwAaCfPbdc.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/gaOngJwsRYRaVAuXXcmB.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/ubnKSIfAJTxIgXOKlciN.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/WhxKECPNujWoWEFNdnJE.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/jZUIxmJycoymBprLOUbT.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/psOgztMplJMGpVEqfcgF.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/ZpBqSxLxVEXfcUNoPKrz.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/laiEnJdGHVOhJrUShBaJ.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/UrQsqscbKEpNuJcvBZBu.png',
|
||||
];
|
||||
|
||||
const covers = [
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/uMfMFlvUuceEyPpotzlq.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/iZBVOIhGJiAnhplqjvZW.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/iXjVmWVHbCJAyqvDxdtx.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png',
|
||||
];
|
||||
const desc = [
|
||||
'那是一种内在的东西, 他们到达不了,也无法触及的',
|
||||
'希望是一个好东西,也许是最好的,好东西是不会消亡的',
|
||||
'生命就像一盒巧克力,结果往往出人意料',
|
||||
'城镇中有那么多的酒馆,她却偏偏走进了我的酒馆',
|
||||
'那时候我只会想自己想要什么,从不想自己拥有什么',
|
||||
];
|
||||
|
||||
const user = [
|
||||
'付小小',
|
||||
'曲丽丽',
|
||||
'林东东',
|
||||
'周星星',
|
||||
'吴加好',
|
||||
'朱偏右',
|
||||
'鱼酱',
|
||||
'乐哥',
|
||||
'谭小仪',
|
||||
'仲尼',
|
||||
];
|
||||
|
||||
function fakeList(count) {
|
||||
const list = [];
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
list.push({
|
||||
id: `fake-list-${i}`,
|
||||
owner: user[i % 10],
|
||||
title: titles[i % 8],
|
||||
avatar: avatars[i % 8],
|
||||
cover: parseInt(i / 4, 10) % 2 === 0 ? covers[i % 4] : covers[3 - (i % 4)],
|
||||
status: ['active', 'exception', 'normal'][i % 3],
|
||||
percent: Math.ceil(Math.random() * 50) + 50,
|
||||
logo: avatars[i % 8],
|
||||
href: 'https://ant.design',
|
||||
updatedAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i),
|
||||
createdAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i),
|
||||
subDescription: desc[i % 5],
|
||||
description:
|
||||
'在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。',
|
||||
activeUser: Math.ceil(Math.random() * 100000) + 100000,
|
||||
newUser: Math.ceil(Math.random() * 1000) + 1000,
|
||||
star: Math.ceil(Math.random() * 100) + 100,
|
||||
like: Math.ceil(Math.random() * 100) + 100,
|
||||
message: Math.ceil(Math.random() * 10) + 10,
|
||||
content:
|
||||
'段落示意:蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。',
|
||||
members: [
|
||||
{
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png',
|
||||
name: '曲丽丽',
|
||||
id: 'member1',
|
||||
},
|
||||
{
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png',
|
||||
name: '王昭君',
|
||||
id: 'member2',
|
||||
},
|
||||
{
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png',
|
||||
name: '董娜娜',
|
||||
id: 'member3',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
let sourceData;
|
||||
|
||||
function getFakeList(req, res) {
|
||||
const params = req.query;
|
||||
|
||||
const count = params.count * 1 || 20;
|
||||
|
||||
const result = fakeList(count);
|
||||
sourceData = result;
|
||||
return res.json(result);
|
||||
}
|
||||
|
||||
function postFakeList(req, res) {
|
||||
const { /* url = '', */ body } = req;
|
||||
// const params = getUrlParams(url);
|
||||
const { method, id } = body;
|
||||
// const count = (params.count * 1) || 20;
|
||||
let result = sourceData;
|
||||
|
||||
switch (method) {
|
||||
case 'delete':
|
||||
result = result.filter(item => item.id !== id);
|
||||
break;
|
||||
case 'update':
|
||||
result.forEach((item, i) => {
|
||||
if (item.id === id) {
|
||||
result[i] = Object.assign(item, body);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'post':
|
||||
result.unshift({
|
||||
body,
|
||||
id: `fake-list-${result.length}`,
|
||||
createdAt: new Date().getTime(),
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return res.json(result);
|
||||
}
|
||||
|
||||
const getNotice = [
|
||||
{
|
||||
id: 'xxx1',
|
||||
title: titles[0],
|
||||
logo: avatars[0],
|
||||
description: '那是一种内在的东西,他们到达不了,也无法触及的',
|
||||
updatedAt: new Date(),
|
||||
member: '科学搬砖组',
|
||||
href: '',
|
||||
memberLink: '',
|
||||
},
|
||||
{
|
||||
id: 'xxx2',
|
||||
title: titles[1],
|
||||
logo: avatars[1],
|
||||
description: '希望是一个好东西,也许是最好的,好东西是不会消亡的',
|
||||
updatedAt: new Date('2017-07-24'),
|
||||
member: '全组都是吴彦祖',
|
||||
href: '',
|
||||
memberLink: '',
|
||||
},
|
||||
{
|
||||
id: 'xxx3',
|
||||
title: titles[2],
|
||||
logo: avatars[2],
|
||||
description: '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆',
|
||||
updatedAt: new Date(),
|
||||
member: '中二少女团',
|
||||
href: '',
|
||||
memberLink: '',
|
||||
},
|
||||
{
|
||||
id: 'xxx4',
|
||||
title: titles[3],
|
||||
logo: avatars[3],
|
||||
description: '那时候我只会想自己想要什么,从不想自己拥有什么',
|
||||
updatedAt: new Date('2017-07-23'),
|
||||
member: '程序员日常',
|
||||
href: '',
|
||||
memberLink: '',
|
||||
},
|
||||
{
|
||||
id: 'xxx5',
|
||||
title: titles[4],
|
||||
logo: avatars[4],
|
||||
description: '凛冬将至',
|
||||
updatedAt: new Date('2017-07-23'),
|
||||
member: '高逼格设计天团',
|
||||
href: '',
|
||||
memberLink: '',
|
||||
},
|
||||
{
|
||||
id: 'xxx6',
|
||||
title: titles[5],
|
||||
logo: avatars[5],
|
||||
description: '生命就像一盒巧克力,结果往往出人意料',
|
||||
updatedAt: new Date('2017-07-23'),
|
||||
member: '骗你来学计算机',
|
||||
href: '',
|
||||
memberLink: '',
|
||||
},
|
||||
];
|
||||
|
||||
const getActivities = [
|
||||
{
|
||||
id: 'trend-1',
|
||||
updatedAt: new Date(),
|
||||
user: {
|
||||
name: '曲丽丽',
|
||||
avatar: avatars2[0],
|
||||
},
|
||||
group: {
|
||||
name: '高逼格设计天团',
|
||||
link: 'http://github.com/',
|
||||
},
|
||||
project: {
|
||||
name: '六月迭代',
|
||||
link: 'http://github.com/',
|
||||
},
|
||||
template: '在 @{group} 新建项目 @{project}',
|
||||
},
|
||||
{
|
||||
id: 'trend-2',
|
||||
updatedAt: new Date(),
|
||||
user: {
|
||||
name: '付小小',
|
||||
avatar: avatars2[1],
|
||||
},
|
||||
group: {
|
||||
name: '高逼格设计天团',
|
||||
link: 'http://github.com/',
|
||||
},
|
||||
project: {
|
||||
name: '六月迭代',
|
||||
link: 'http://github.com/',
|
||||
},
|
||||
template: '在 @{group} 新建项目 @{project}',
|
||||
},
|
||||
{
|
||||
id: 'trend-3',
|
||||
updatedAt: new Date(),
|
||||
user: {
|
||||
name: '林东东',
|
||||
avatar: avatars2[2],
|
||||
},
|
||||
group: {
|
||||
name: '中二少女团',
|
||||
link: 'http://github.com/',
|
||||
},
|
||||
project: {
|
||||
name: '六月迭代',
|
||||
link: 'http://github.com/',
|
||||
},
|
||||
template: '在 @{group} 新建项目 @{project}',
|
||||
},
|
||||
{
|
||||
id: 'trend-4',
|
||||
updatedAt: new Date(),
|
||||
user: {
|
||||
name: '周星星',
|
||||
avatar: avatars2[4],
|
||||
},
|
||||
project: {
|
||||
name: '5 月日常迭代',
|
||||
link: 'http://github.com/',
|
||||
},
|
||||
template: '将 @{project} 更新至已发布状态',
|
||||
},
|
||||
{
|
||||
id: 'trend-5',
|
||||
updatedAt: new Date(),
|
||||
user: {
|
||||
name: '朱偏右',
|
||||
avatar: avatars2[3],
|
||||
},
|
||||
project: {
|
||||
name: '工程效能',
|
||||
link: 'http://github.com/',
|
||||
},
|
||||
comment: {
|
||||
name: '留言',
|
||||
link: 'http://github.com/',
|
||||
},
|
||||
template: '在 @{project} 发布了 @{comment}',
|
||||
},
|
||||
{
|
||||
id: 'trend-6',
|
||||
updatedAt: new Date(),
|
||||
user: {
|
||||
name: '乐哥',
|
||||
avatar: avatars2[5],
|
||||
},
|
||||
group: {
|
||||
name: '程序员日常',
|
||||
link: 'http://github.com/',
|
||||
},
|
||||
project: {
|
||||
name: '品牌迭代',
|
||||
link: 'http://github.com/',
|
||||
},
|
||||
template: '在 @{group} 新建项目 @{project}',
|
||||
},
|
||||
];
|
||||
|
||||
function getFakeCaptcha(req, res) {
|
||||
return res.json('captcha-xxx');
|
||||
}
|
||||
|
||||
export default {
|
||||
'GET /api/project/notice': getNotice,
|
||||
'GET /api/activities': getActivities,
|
||||
'POST /api/forms': (req, res) => {
|
||||
res.send({ message: 'Ok' });
|
||||
},
|
||||
'GET /api/tags': mockjs.mock({
|
||||
'list|100': [{ name: '@city', 'value|1-100': 150, 'type|0-2': 1 }],
|
||||
}),
|
||||
'GET /api/fake_list': getFakeList,
|
||||
'POST /api/fake_list': postFakeList,
|
||||
'GET /api/captcha': getFakeCaptcha,
|
||||
};
|
196
admin-web/mock/chart.js
Normal file
196
admin-web/mock/chart.js
Normal file
@ -0,0 +1,196 @@
|
||||
import moment from 'moment';
|
||||
|
||||
// mock data
|
||||
const visitData = [];
|
||||
const beginDay = new Date().getTime();
|
||||
|
||||
const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5];
|
||||
for (let i = 0; i < fakeY.length; i += 1) {
|
||||
visitData.push({
|
||||
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
|
||||
y: fakeY[i],
|
||||
});
|
||||
}
|
||||
|
||||
const visitData2 = [];
|
||||
const fakeY2 = [1, 6, 4, 8, 3, 7, 2];
|
||||
for (let i = 0; i < fakeY2.length; i += 1) {
|
||||
visitData2.push({
|
||||
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
|
||||
y: fakeY2[i],
|
||||
});
|
||||
}
|
||||
|
||||
const salesData = [];
|
||||
for (let i = 0; i < 12; i += 1) {
|
||||
salesData.push({
|
||||
x: `${i + 1}月`,
|
||||
y: Math.floor(Math.random() * 1000) + 200,
|
||||
});
|
||||
}
|
||||
const searchData = [];
|
||||
for (let i = 0; i < 50; i += 1) {
|
||||
searchData.push({
|
||||
index: i + 1,
|
||||
keyword: `搜索关键词-${i}`,
|
||||
count: Math.floor(Math.random() * 1000),
|
||||
range: Math.floor(Math.random() * 100),
|
||||
status: Math.floor((Math.random() * 10) % 2),
|
||||
});
|
||||
}
|
||||
const salesTypeData = [
|
||||
{
|
||||
x: '家用电器',
|
||||
y: 4544,
|
||||
},
|
||||
{
|
||||
x: '食用酒水',
|
||||
y: 3321,
|
||||
},
|
||||
{
|
||||
x: '个护健康',
|
||||
y: 3113,
|
||||
},
|
||||
{
|
||||
x: '服饰箱包',
|
||||
y: 2341,
|
||||
},
|
||||
{
|
||||
x: '母婴产品',
|
||||
y: 1231,
|
||||
},
|
||||
{
|
||||
x: '其他',
|
||||
y: 1231,
|
||||
},
|
||||
];
|
||||
|
||||
const salesTypeDataOnline = [
|
||||
{
|
||||
x: '家用电器',
|
||||
y: 244,
|
||||
},
|
||||
{
|
||||
x: '食用酒水',
|
||||
y: 321,
|
||||
},
|
||||
{
|
||||
x: '个护健康',
|
||||
y: 311,
|
||||
},
|
||||
{
|
||||
x: '服饰箱包',
|
||||
y: 41,
|
||||
},
|
||||
{
|
||||
x: '母婴产品',
|
||||
y: 121,
|
||||
},
|
||||
{
|
||||
x: '其他',
|
||||
y: 111,
|
||||
},
|
||||
];
|
||||
|
||||
const salesTypeDataOffline = [
|
||||
{
|
||||
x: '家用电器',
|
||||
y: 99,
|
||||
},
|
||||
{
|
||||
x: '食用酒水',
|
||||
y: 188,
|
||||
},
|
||||
{
|
||||
x: '个护健康',
|
||||
y: 344,
|
||||
},
|
||||
{
|
||||
x: '服饰箱包',
|
||||
y: 255,
|
||||
},
|
||||
{
|
||||
x: '其他',
|
||||
y: 65,
|
||||
},
|
||||
];
|
||||
|
||||
const offlineData = [];
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
offlineData.push({
|
||||
name: `Stores ${i}`,
|
||||
cvr: Math.ceil(Math.random() * 9) / 10,
|
||||
});
|
||||
}
|
||||
const offlineChartData = [];
|
||||
for (let i = 0; i < 20; i += 1) {
|
||||
offlineChartData.push({
|
||||
x: new Date().getTime() + 1000 * 60 * 30 * i,
|
||||
y1: Math.floor(Math.random() * 100) + 10,
|
||||
y2: Math.floor(Math.random() * 100) + 10,
|
||||
});
|
||||
}
|
||||
|
||||
const radarOriginData = [
|
||||
{
|
||||
name: '个人',
|
||||
ref: 10,
|
||||
koubei: 8,
|
||||
output: 4,
|
||||
contribute: 5,
|
||||
hot: 7,
|
||||
},
|
||||
{
|
||||
name: '团队',
|
||||
ref: 3,
|
||||
koubei: 9,
|
||||
output: 6,
|
||||
contribute: 3,
|
||||
hot: 1,
|
||||
},
|
||||
{
|
||||
name: '部门',
|
||||
ref: 4,
|
||||
koubei: 1,
|
||||
output: 6,
|
||||
contribute: 5,
|
||||
hot: 7,
|
||||
},
|
||||
];
|
||||
|
||||
const radarData = [];
|
||||
const radarTitleMap = {
|
||||
ref: '引用',
|
||||
koubei: '口碑',
|
||||
output: '产量',
|
||||
contribute: '贡献',
|
||||
hot: '热度',
|
||||
};
|
||||
radarOriginData.forEach(item => {
|
||||
Object.keys(item).forEach(key => {
|
||||
if (key !== 'name') {
|
||||
radarData.push({
|
||||
name: item.name,
|
||||
label: radarTitleMap[key],
|
||||
value: item[key],
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const getFakeChartData = {
|
||||
visitData,
|
||||
visitData2,
|
||||
salesData,
|
||||
searchData,
|
||||
offlineData,
|
||||
offlineChartData,
|
||||
salesTypeData,
|
||||
salesTypeDataOnline,
|
||||
salesTypeDataOffline,
|
||||
radarData,
|
||||
};
|
||||
|
||||
export default {
|
||||
'GET /api/fake_chart_data': getFakeChartData,
|
||||
};
|
15
admin-web/mock/geographic.js
Normal file
15
admin-web/mock/geographic.js
Normal file
@ -0,0 +1,15 @@
|
||||
import city from './geographic/city.json';
|
||||
import province from './geographic/province.json';
|
||||
|
||||
function getProvince(req, res) {
|
||||
return res.json(province);
|
||||
}
|
||||
|
||||
function getCity(req, res) {
|
||||
return res.json(city[req.params.province]);
|
||||
}
|
||||
|
||||
export default {
|
||||
'GET /api/geographic/province': getProvince,
|
||||
'GET /api/geographic/city/:province': getCity,
|
||||
};
|
1784
admin-web/mock/geographic/city.json
Normal file
1784
admin-web/mock/geographic/city.json
Normal file
File diff suppressed because it is too large
Load Diff
138
admin-web/mock/geographic/province.json
Normal file
138
admin-web/mock/geographic/province.json
Normal file
@ -0,0 +1,138 @@
|
||||
[
|
||||
{
|
||||
"name": "北京市",
|
||||
"id": "110000"
|
||||
},
|
||||
{
|
||||
"name": "天津市",
|
||||
"id": "120000"
|
||||
},
|
||||
{
|
||||
"name": "河北省",
|
||||
"id": "130000"
|
||||
},
|
||||
{
|
||||
"name": "山西省",
|
||||
"id": "140000"
|
||||
},
|
||||
{
|
||||
"name": "内蒙古自治区",
|
||||
"id": "150000"
|
||||
},
|
||||
{
|
||||
"name": "辽宁省",
|
||||
"id": "210000"
|
||||
},
|
||||
{
|
||||
"name": "吉林省",
|
||||
"id": "220000"
|
||||
},
|
||||
{
|
||||
"name": "黑龙江省",
|
||||
"id": "230000"
|
||||
},
|
||||
{
|
||||
"name": "上海市",
|
||||
"id": "310000"
|
||||
},
|
||||
{
|
||||
"name": "江苏省",
|
||||
"id": "320000"
|
||||
},
|
||||
{
|
||||
"name": "浙江省",
|
||||
"id": "330000"
|
||||
},
|
||||
{
|
||||
"name": "安徽省",
|
||||
"id": "340000"
|
||||
},
|
||||
{
|
||||
"name": "福建省",
|
||||
"id": "350000"
|
||||
},
|
||||
{
|
||||
"name": "江西省",
|
||||
"id": "360000"
|
||||
},
|
||||
{
|
||||
"name": "山东省",
|
||||
"id": "370000"
|
||||
},
|
||||
{
|
||||
"name": "河南省",
|
||||
"id": "410000"
|
||||
},
|
||||
{
|
||||
"name": "湖北省",
|
||||
"id": "420000"
|
||||
},
|
||||
{
|
||||
"name": "湖南省",
|
||||
"id": "430000"
|
||||
},
|
||||
{
|
||||
"name": "广东省",
|
||||
"id": "440000"
|
||||
},
|
||||
{
|
||||
"name": "广西壮族自治区",
|
||||
"id": "450000"
|
||||
},
|
||||
{
|
||||
"name": "海南省",
|
||||
"id": "460000"
|
||||
},
|
||||
{
|
||||
"name": "重庆市",
|
||||
"id": "500000"
|
||||
},
|
||||
{
|
||||
"name": "四川省",
|
||||
"id": "510000"
|
||||
},
|
||||
{
|
||||
"name": "贵州省",
|
||||
"id": "520000"
|
||||
},
|
||||
{
|
||||
"name": "云南省",
|
||||
"id": "530000"
|
||||
},
|
||||
{
|
||||
"name": "西藏自治区",
|
||||
"id": "540000"
|
||||
},
|
||||
{
|
||||
"name": "陕西省",
|
||||
"id": "610000"
|
||||
},
|
||||
{
|
||||
"name": "甘肃省",
|
||||
"id": "620000"
|
||||
},
|
||||
{
|
||||
"name": "青海省",
|
||||
"id": "630000"
|
||||
},
|
||||
{
|
||||
"name": "宁夏回族自治区",
|
||||
"id": "640000"
|
||||
},
|
||||
{
|
||||
"name": "新疆维吾尔自治区",
|
||||
"id": "650000"
|
||||
},
|
||||
{
|
||||
"name": "台湾省",
|
||||
"id": "710000"
|
||||
},
|
||||
{
|
||||
"name": "香港特别行政区",
|
||||
"id": "810000"
|
||||
},
|
||||
{
|
||||
"name": "澳门特别行政区",
|
||||
"id": "820000"
|
||||
}
|
||||
]
|
115
admin-web/mock/notices.js
Normal file
115
admin-web/mock/notices.js
Normal file
@ -0,0 +1,115 @@
|
||||
const fakeNotices = [
|
||||
{
|
||||
id: '000000001',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
|
||||
title: '你收到了 14 份新周报',
|
||||
datetime: '2017-08-09',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000002',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
|
||||
title: '你推荐的 曲妮妮 已通过第三轮面试',
|
||||
datetime: '2017-08-08',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000003',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
|
||||
title: '这种模板可以区分多种通知类型',
|
||||
datetime: '2017-08-07',
|
||||
read: true,
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000004',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
|
||||
title: '左侧图标用于区分不同的类型',
|
||||
datetime: '2017-08-07',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000005',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
|
||||
title: '内容不要超过两行字,超出时自动截断',
|
||||
datetime: '2017-08-07',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000006',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '曲丽丽 评论了你',
|
||||
description: '描述信息描述信息描述信息',
|
||||
datetime: '2017-08-07',
|
||||
type: 'message',
|
||||
clickClose: true,
|
||||
},
|
||||
{
|
||||
id: '000000007',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '朱偏右 回复了你',
|
||||
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
|
||||
datetime: '2017-08-07',
|
||||
type: 'message',
|
||||
clickClose: true,
|
||||
},
|
||||
{
|
||||
id: '000000008',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '标题',
|
||||
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
|
||||
datetime: '2017-08-07',
|
||||
type: 'message',
|
||||
clickClose: true,
|
||||
},
|
||||
{
|
||||
id: '000000009',
|
||||
title: '任务名称',
|
||||
description: '任务需要在 2017-01-12 20:00 前启动',
|
||||
extra: '未开始',
|
||||
status: 'todo',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
id: '000000010',
|
||||
title: '第三方紧急代码变更',
|
||||
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
|
||||
extra: '马上到期',
|
||||
status: 'urgent',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
id: '000000011',
|
||||
title: '信息安全考试',
|
||||
description: '指派竹尔于 2017-01-09 前完成更新并发布',
|
||||
extra: '已耗时 8 天',
|
||||
status: 'doing',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
id: '000000012',
|
||||
title: 'ABCD 版本发布',
|
||||
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
|
||||
extra: '进行中',
|
||||
status: 'processing',
|
||||
type: 'event',
|
||||
},
|
||||
];
|
||||
|
||||
const getNotices = (req, res) => {
|
||||
if (req.query && req.query.type) {
|
||||
const startFrom = parseInt(req.query.lastItemId, 10) + 1;
|
||||
const result = fakeNotices
|
||||
.filter(({ type }) => type === req.query.type)
|
||||
.map((notice, index) => ({
|
||||
...notice,
|
||||
id: `0000000${startFrom + index}`,
|
||||
}));
|
||||
return res.json(startFrom > 24 ? result.concat(null) : result);
|
||||
}
|
||||
return res.json(fakeNotices);
|
||||
};
|
||||
|
||||
export default {
|
||||
'GET /api/notices': getNotices,
|
||||
};
|
177
admin-web/mock/profile.js
Normal file
177
admin-web/mock/profile.js
Normal file
@ -0,0 +1,177 @@
|
||||
import mockjs from 'mockjs';
|
||||
|
||||
const basicGoods = [
|
||||
{
|
||||
id: '1234561',
|
||||
name: '矿泉水 550ml',
|
||||
barcode: '12421432143214321',
|
||||
price: '2.00',
|
||||
num: '1',
|
||||
amount: '2.00',
|
||||
},
|
||||
{
|
||||
id: '1234562',
|
||||
name: '凉茶 300ml',
|
||||
barcode: '12421432143214322',
|
||||
price: '3.00',
|
||||
num: '2',
|
||||
amount: '6.00',
|
||||
},
|
||||
{
|
||||
id: '1234563',
|
||||
name: '好吃的薯片',
|
||||
barcode: '12421432143214323',
|
||||
price: '7.00',
|
||||
num: '4',
|
||||
amount: '28.00',
|
||||
},
|
||||
{
|
||||
id: '1234564',
|
||||
name: '特别好吃的蛋卷',
|
||||
barcode: '12421432143214324',
|
||||
price: '8.50',
|
||||
num: '3',
|
||||
amount: '25.50',
|
||||
},
|
||||
];
|
||||
|
||||
const basicProgress = [
|
||||
{
|
||||
key: '1',
|
||||
time: '2017-10-01 14:10',
|
||||
rate: '联系客户',
|
||||
status: 'processing',
|
||||
operator: '取货员 ID1234',
|
||||
cost: '5mins',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
time: '2017-10-01 14:05',
|
||||
rate: '取货员出发',
|
||||
status: 'success',
|
||||
operator: '取货员 ID1234',
|
||||
cost: '1h',
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
time: '2017-10-01 13:05',
|
||||
rate: '取货员接单',
|
||||
status: 'success',
|
||||
operator: '取货员 ID1234',
|
||||
cost: '5mins',
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
time: '2017-10-01 13:00',
|
||||
rate: '申请审批通过',
|
||||
status: 'success',
|
||||
operator: '系统',
|
||||
cost: '1h',
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
time: '2017-10-01 12:00',
|
||||
rate: '发起退货申请',
|
||||
status: 'success',
|
||||
operator: '用户',
|
||||
cost: '5mins',
|
||||
},
|
||||
];
|
||||
|
||||
const advancedOperation1 = [
|
||||
{
|
||||
key: 'op1',
|
||||
type: '订购关系生效',
|
||||
name: '曲丽丽',
|
||||
status: 'agree',
|
||||
updatedAt: '2017-10-03 19:23:12',
|
||||
memo: '-',
|
||||
},
|
||||
{
|
||||
key: 'op2',
|
||||
type: '财务复审',
|
||||
name: '付小小',
|
||||
status: 'reject',
|
||||
updatedAt: '2017-10-03 19:23:12',
|
||||
memo: '不通过原因',
|
||||
},
|
||||
{
|
||||
key: 'op3',
|
||||
type: '部门初审',
|
||||
name: '周毛毛',
|
||||
status: 'agree',
|
||||
updatedAt: '2017-10-03 19:23:12',
|
||||
memo: '-',
|
||||
},
|
||||
{
|
||||
key: 'op4',
|
||||
type: '提交订单',
|
||||
name: '林东东',
|
||||
status: 'agree',
|
||||
updatedAt: '2017-10-03 19:23:12',
|
||||
memo: '很棒',
|
||||
},
|
||||
{
|
||||
key: 'op5',
|
||||
type: '创建订单',
|
||||
name: '汗牙牙',
|
||||
status: 'agree',
|
||||
updatedAt: '2017-10-03 19:23:12',
|
||||
memo: '-',
|
||||
},
|
||||
];
|
||||
|
||||
const advancedOperation2 = [
|
||||
{
|
||||
key: 'op1',
|
||||
type: '订购关系生效',
|
||||
name: '曲丽丽',
|
||||
status: 'agree',
|
||||
updatedAt: '2017-10-03 19:23:12',
|
||||
memo: '-',
|
||||
},
|
||||
];
|
||||
|
||||
const advancedOperation3 = [
|
||||
{
|
||||
key: 'op1',
|
||||
type: '创建订单',
|
||||
name: '汗牙牙',
|
||||
status: 'agree',
|
||||
updatedAt: '2017-10-03 19:23:12',
|
||||
memo: '-',
|
||||
},
|
||||
];
|
||||
const getProfileAdvancedData = {
|
||||
advancedOperation1,
|
||||
advancedOperation2,
|
||||
advancedOperation3,
|
||||
};
|
||||
|
||||
const { Random } = mockjs;
|
||||
|
||||
export default {
|
||||
'GET /api/profile/advanced': getProfileAdvancedData,
|
||||
'GET /api/profile/basic': (req, res) => {
|
||||
const { id } = req.query;
|
||||
const application = {
|
||||
id,
|
||||
status: '已取货',
|
||||
orderNo: Random.id(),
|
||||
childOrderNo: Random.id(),
|
||||
};
|
||||
const userInfo = {
|
||||
name: Random.cname(),
|
||||
tel: '18100000000',
|
||||
delivery: '菜鸟物流',
|
||||
addr: '浙江省杭州市西湖区万塘路18号',
|
||||
remark: '备注',
|
||||
};
|
||||
res.json({
|
||||
userInfo,
|
||||
application,
|
||||
basicGoods,
|
||||
basicProgress,
|
||||
});
|
||||
},
|
||||
};
|
5
admin-web/mock/route.js
Normal file
5
admin-web/mock/route.js
Normal file
@ -0,0 +1,5 @@
|
||||
export default {
|
||||
'/api/auth_routes': {
|
||||
'/form/advanced-form': { authority: ['admin', 'user'] },
|
||||
},
|
||||
};
|
131
admin-web/mock/rule.js
Normal file
131
admin-web/mock/rule.js
Normal file
@ -0,0 +1,131 @@
|
||||
import { parse } from 'url';
|
||||
|
||||
// mock tableListDataSource
|
||||
let tableListDataSource = [];
|
||||
for (let i = 0; i < 46; i += 1) {
|
||||
tableListDataSource.push({
|
||||
key: i,
|
||||
disabled: i % 6 === 0,
|
||||
href: 'https://ant.design',
|
||||
avatar: [
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
|
||||
][i % 2],
|
||||
name: `TradeCode ${i}`,
|
||||
title: `一个任务名称 ${i}`,
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: Math.floor(Math.random() * 1000),
|
||||
status: Math.floor(Math.random() * 10) % 4,
|
||||
updatedAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`),
|
||||
createdAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`),
|
||||
progress: Math.ceil(Math.random() * 100),
|
||||
});
|
||||
}
|
||||
|
||||
function getRule(req, res, u) {
|
||||
let url = u;
|
||||
if (!url || Object.prototype.toString.call(url) !== '[object String]') {
|
||||
url = req.url; // eslint-disable-line
|
||||
}
|
||||
|
||||
const params = parse(url, true).query;
|
||||
|
||||
let dataSource = tableListDataSource;
|
||||
|
||||
if (params.sorter) {
|
||||
const s = params.sorter.split('_');
|
||||
dataSource = dataSource.sort((prev, next) => {
|
||||
if (s[1] === 'descend') {
|
||||
return next[s[0]] - prev[s[0]];
|
||||
}
|
||||
return prev[s[0]] - next[s[0]];
|
||||
});
|
||||
}
|
||||
|
||||
if (params.status) {
|
||||
const status = params.status.split(',');
|
||||
let filterDataSource = [];
|
||||
status.forEach(s => {
|
||||
filterDataSource = filterDataSource.concat(
|
||||
dataSource.filter(data => parseInt(data.status, 10) === parseInt(s[0], 10))
|
||||
);
|
||||
});
|
||||
dataSource = filterDataSource;
|
||||
}
|
||||
|
||||
if (params.name) {
|
||||
dataSource = dataSource.filter(data => data.name.indexOf(params.name) > -1);
|
||||
}
|
||||
|
||||
let pageSize = 10;
|
||||
if (params.pageSize) {
|
||||
pageSize = params.pageSize * 1;
|
||||
}
|
||||
|
||||
const result = {
|
||||
list: dataSource,
|
||||
pagination: {
|
||||
total: dataSource.length,
|
||||
pageSize,
|
||||
current: parseInt(params.currentPage, 10) || 1,
|
||||
},
|
||||
};
|
||||
|
||||
return res.json(result);
|
||||
}
|
||||
|
||||
function postRule(req, res, u, b) {
|
||||
let url = u;
|
||||
if (!url || Object.prototype.toString.call(url) !== '[object String]') {
|
||||
url = req.url; // eslint-disable-line
|
||||
}
|
||||
|
||||
const body = (b && b.body) || req.body;
|
||||
const { method, name, desc, key } = body;
|
||||
|
||||
switch (method) {
|
||||
/* eslint no-case-declarations:0 */
|
||||
case 'delete':
|
||||
tableListDataSource = tableListDataSource.filter(item => key.indexOf(item.key) === -1);
|
||||
break;
|
||||
case 'post':
|
||||
const i = Math.ceil(Math.random() * 10000);
|
||||
tableListDataSource.unshift({
|
||||
key: i,
|
||||
href: 'https://ant.design',
|
||||
avatar: [
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
|
||||
][i % 2],
|
||||
name: `TradeCode ${i}`,
|
||||
title: `一个任务名称 ${i}`,
|
||||
owner: '曲丽丽',
|
||||
desc,
|
||||
callNo: Math.floor(Math.random() * 1000),
|
||||
status: Math.floor(Math.random() * 10) % 2,
|
||||
updatedAt: new Date(),
|
||||
createdAt: new Date(),
|
||||
progress: Math.ceil(Math.random() * 100),
|
||||
});
|
||||
break;
|
||||
case 'update':
|
||||
tableListDataSource = tableListDataSource.map(item => {
|
||||
if (item.key === key) {
|
||||
Object.assign(item, { desc, name });
|
||||
return item;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return getRule(req, res, u);
|
||||
}
|
||||
|
||||
export default {
|
||||
'GET /api/rule': getRule,
|
||||
'POST /api/rule': postRule,
|
||||
};
|
138
admin-web/mock/user.js
Normal file
138
admin-web/mock/user.js
Normal file
@ -0,0 +1,138 @@
|
||||
// 代码中会兼容本地 service mock 以及部署站点的静态数据
|
||||
export default {
|
||||
// 支持值为 Object 和 Array
|
||||
'GET /api/currentUser': {
|
||||
name: 'Serati Ma',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
|
||||
userid: '00000001',
|
||||
email: 'antdesign@alipay.com',
|
||||
signature: '海纳百川,有容乃大',
|
||||
title: '交互专家',
|
||||
group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
|
||||
tags: [
|
||||
{
|
||||
key: '0',
|
||||
label: '很有想法的',
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
label: '专注设计',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: '辣~',
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: '大长腿',
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
label: '川妹子',
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
label: '海纳百川',
|
||||
},
|
||||
],
|
||||
notifyCount: 12,
|
||||
unreadCount: 11,
|
||||
country: 'China',
|
||||
geographic: {
|
||||
province: {
|
||||
label: '浙江省',
|
||||
key: '330000',
|
||||
},
|
||||
city: {
|
||||
label: '杭州市',
|
||||
key: '330100',
|
||||
},
|
||||
},
|
||||
address: '西湖区工专路 77 号',
|
||||
phone: '0752-268888888',
|
||||
},
|
||||
// GET POST 可省略
|
||||
'GET /api/users': [
|
||||
{
|
||||
key: '1',
|
||||
name: 'John Brown',
|
||||
age: 32,
|
||||
address: 'New York No. 1 Lake Park',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
name: 'Jim Green',
|
||||
age: 42,
|
||||
address: 'London No. 1 Lake Park',
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
name: 'Joe Black',
|
||||
age: 32,
|
||||
address: 'Sidney No. 1 Lake Park',
|
||||
},
|
||||
],
|
||||
'POST /api/login/account': (req, res) => {
|
||||
const { password, userName, type } = req.body;
|
||||
if (password === 'ant.design' && userName === 'admin') {
|
||||
res.send({
|
||||
status: 'ok',
|
||||
type,
|
||||
currentAuthority: 'admin',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (password === 'ant.design' && userName === 'user') {
|
||||
res.send({
|
||||
status: 'ok',
|
||||
type,
|
||||
currentAuthority: 'user',
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.send({
|
||||
status: 'error',
|
||||
type,
|
||||
currentAuthority: 'guest',
|
||||
});
|
||||
},
|
||||
'POST /api/register': (req, res) => {
|
||||
res.send({ status: 'ok', currentAuthority: 'user' });
|
||||
},
|
||||
'GET /api/500': (req, res) => {
|
||||
res.status(500).send({
|
||||
timestamp: 1513932555104,
|
||||
status: 500,
|
||||
error: 'error',
|
||||
message: 'error',
|
||||
path: '/base/category/list',
|
||||
});
|
||||
},
|
||||
'GET /api/404': (req, res) => {
|
||||
res.status(404).send({
|
||||
timestamp: 1513932643431,
|
||||
status: 404,
|
||||
error: 'Not Found',
|
||||
message: 'No message available',
|
||||
path: '/base/category/list/2121212',
|
||||
});
|
||||
},
|
||||
'GET /api/403': (req, res) => {
|
||||
res.status(403).send({
|
||||
timestamp: 1513932555104,
|
||||
status: 403,
|
||||
error: 'Unauthorized',
|
||||
message: 'Unauthorized',
|
||||
path: '/base/category/list',
|
||||
});
|
||||
},
|
||||
'GET /api/401': (req, res) => {
|
||||
res.status(401).send({
|
||||
timestamp: 1513932555104,
|
||||
status: 401,
|
||||
error: 'Unauthorized',
|
||||
message: 'Unauthorized',
|
||||
path: '/base/category/list',
|
||||
});
|
||||
},
|
||||
};
|
13
admin-web/netlify.toml
Normal file
13
admin-web/netlify.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[[redirects]]
|
||||
from = "/api/*"
|
||||
to = "https://us-central1-antd-pro.cloudfunctions.net/api/api/:splat"
|
||||
status = 200
|
||||
force = true
|
||||
[redirects.headers]
|
||||
X-From = "Netlify"
|
||||
X-Api-Key = "some-api-key-string"
|
||||
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
status = 200
|
137
admin-web/package.json
Normal file
137
admin-web/package.json
Normal file
@ -0,0 +1,137 @@
|
||||
{
|
||||
"name": "ant-design-pro",
|
||||
"version": "2.2.1",
|
||||
"description": "An out-of-box UI solution for enterprise applications",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"presite": "cd functions && npm install",
|
||||
"start": "cross-env APP_TYPE=site umi dev",
|
||||
"start:no-mock": "cross-env MOCK=none umi dev",
|
||||
"build": "umi build",
|
||||
"site": "npm run presite && cross-env APP_TYPE=site npm run build && firebase deploy && npm run docker:push",
|
||||
"analyze": "cross-env ANALYZE=1 umi build",
|
||||
"lint:style": "stylelint 'src/**/*.less' --syntax less",
|
||||
"lint:prettier": "check-prettier lint",
|
||||
"lint": "eslint --ext .js src mock tests && npm run lint:style && npm run lint:prettier",
|
||||
"lint:fix": "eslint --fix --ext .js src mock tests && stylelint --fix 'src/**/*.less' --syntax less",
|
||||
"lint-staged": "lint-staged",
|
||||
"lint-staged:js": "eslint --ext .js",
|
||||
"tslint": "npm run tslint:fix",
|
||||
"tslint:fix": "tslint --fix 'src/**/*.ts*'",
|
||||
"test": "umi test",
|
||||
"test:component": "umi test ./src/components",
|
||||
"test:all": "node ./tests/run-tests.js",
|
||||
"prettier": "node ./scripts/prettier.js",
|
||||
"docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up",
|
||||
"docker:build": "docker-compose -f ./docker/docker-compose.dev.yml build",
|
||||
"docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up",
|
||||
"docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build",
|
||||
"docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./",
|
||||
"docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro",
|
||||
"docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/data-set": "^0.10.1",
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"antd": "^3.13.0",
|
||||
"bizcharts": "^3.4.3",
|
||||
"bizcharts-plugin-slider": "^2.1.1-beta.1",
|
||||
"classnames": "^2.2.6",
|
||||
"dva": "^2.4.1",
|
||||
"enquire-js": "^0.2.1",
|
||||
"hash.js": "^1.1.7",
|
||||
"lodash": "^4.17.11",
|
||||
"lodash-decorators": "^6.0.1",
|
||||
"memoize-one": "^5.0.0",
|
||||
"moment": "^2.24.0",
|
||||
"numeral": "^2.0.6",
|
||||
"nzh": "^1.0.4",
|
||||
"omit.js": "^1.0.0",
|
||||
"path-to-regexp": "^3.0.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"qs": "^6.6.0",
|
||||
"rc-animate": "^2.6.0",
|
||||
"react": "^16.7.0",
|
||||
"react-container-query": "^0.11.0",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"react-document-title": "^2.0.3",
|
||||
"react-dom": "^16.7.0",
|
||||
"react-fittext": "^1.0.0",
|
||||
"react-media": "^1.9.2",
|
||||
"react-router-dom": "^4.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^16.8.1",
|
||||
"@types/react-dom": "^16.0.11",
|
||||
"antd-pro-merge-less": "^1.0.0",
|
||||
"antd-theme-webpack-plugin": "^1.2.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"chalk": "^2.4.2",
|
||||
"check-prettier": "^1.0.1",
|
||||
"cross-env": "^5.2.0",
|
||||
"cross-port-killer": "^1.0.1",
|
||||
"enzyme": "3.8.0",
|
||||
"eslint": "^5.13.0",
|
||||
"eslint-config-airbnb": "^17.1.0",
|
||||
"eslint-config-prettier": "^4.0.0",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
"eslint-plugin-compat": "^2.6.3",
|
||||
"eslint-plugin-import": "^2.16.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.0",
|
||||
"eslint-plugin-markdown": "^1.0.0",
|
||||
"eslint-plugin-react": "^7.12.4",
|
||||
"gh-pages": "^2.0.1",
|
||||
"husky": "^1.3.1",
|
||||
"jest-puppeteer": "^3.9.0",
|
||||
"less": "^3.9.0",
|
||||
"lint-staged": "^8.1.1",
|
||||
"merge-umi-mock-data": "^1.0.4",
|
||||
"mockjs": "^1.0.1-beta3",
|
||||
"prettier": "1.16.3",
|
||||
"slash2": "^2.0.0",
|
||||
"stylelint": "^9.10.1",
|
||||
"stylelint-config-css-modules": "^1.3.0",
|
||||
"stylelint-config-prettier": "^4.0.0",
|
||||
"stylelint-config-rational-order": "^0.0.4",
|
||||
"stylelint-config-standard": "^18.2.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "^1.1.0",
|
||||
"stylelint-order": "^2.0.0",
|
||||
"tslint": "^5.12.1",
|
||||
"tslint-config-prettier": "^1.17.0",
|
||||
"tslint-react": "^3.6.0",
|
||||
"umi": "^2.4.4",
|
||||
"umi-plugin-ga": "^1.1.3",
|
||||
"umi-plugin-react": "^1.4.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"puppeteer": "^1.12.1"
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*.{js,ts,tsx,json,jsx,less}": [
|
||||
"node ./scripts/lint-prettier.js",
|
||||
"git add"
|
||||
],
|
||||
"**/*.{js,jsx}": "npm run lint-staged:js",
|
||||
"**/*.less": "stylelint --syntax less"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 10"
|
||||
],
|
||||
"checkFiles": [
|
||||
"src/**/*.js*",
|
||||
"src/**/*.ts*",
|
||||
"src/**/*.less",
|
||||
"config/**/*.js*",
|
||||
"scripts/**/*.js"
|
||||
],
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "npm run lint-staged"
|
||||
}
|
||||
}
|
||||
}
|
BIN
admin-web/public/favicon.png
Normal file
BIN
admin-web/public/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
BIN
admin-web/public/icons/icon-128x128.png
Normal file
BIN
admin-web/public/icons/icon-128x128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
admin-web/public/icons/icon-192x192.png
Normal file
BIN
admin-web/public/icons/icon-192x192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
admin-web/public/icons/icon-512x512.png
Normal file
BIN
admin-web/public/icons/icon-512x512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
3
admin-web/scripts/generateMock.js
Normal file
3
admin-web/scripts/generateMock.js
Normal file
@ -0,0 +1,3 @@
|
||||
const generateMock = require('merge-umi-mock-data');
|
||||
const path = require('path');
|
||||
generateMock(path.join(__dirname, '../mock'), path.join(__dirname, '../functions/mock/index.js'));
|
21
admin-web/scripts/getPrettierFiles.js
Normal file
21
admin-web/scripts/getPrettierFiles.js
Normal file
@ -0,0 +1,21 @@
|
||||
const glob = require('glob');
|
||||
|
||||
const getPrettierFiles = () => {
|
||||
let files = [];
|
||||
const jsFiles = glob.sync('src/**/*.js*', { ignore: ['**/node_modules/**', 'build/**'] });
|
||||
const tsFiles = glob.sync('src/**/*.ts*', { ignore: ['**/node_modules/**', 'build/**'] });
|
||||
const configFiles = glob.sync('config/**/*.js*', { ignore: ['**/node_modules/**', 'build/**'] });
|
||||
const scriptFiles = glob.sync('scripts/**/*.js');
|
||||
const lessFiles = glob.sync('src/**/*.less*', { ignore: ['**/node_modules/**', 'build/**'] });
|
||||
files = files.concat(jsFiles);
|
||||
files = files.concat(tsFiles);
|
||||
files = files.concat(configFiles);
|
||||
files = files.concat(scriptFiles);
|
||||
files = files.concat(lessFiles);
|
||||
if (!files.length) {
|
||||
return;
|
||||
}
|
||||
return files;
|
||||
};
|
||||
|
||||
module.exports = getPrettierFiles;
|
50
admin-web/scripts/lint-prettier.js
Normal file
50
admin-web/scripts/lint-prettier.js
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* copy to https://github.com/facebook/react/blob/master/scripts/prettier/index.js
|
||||
* prettier api doc https://prettier.io/docs/en/api.html
|
||||
*----------*****--------------
|
||||
* lint file is prettier
|
||||
*----------*****--------------
|
||||
*/
|
||||
|
||||
const prettier = require('prettier');
|
||||
const fs = require('fs');
|
||||
const chalk = require('chalk');
|
||||
const prettierConfigPath = require.resolve('../.prettierrc');
|
||||
|
||||
const files = process.argv.slice(2);
|
||||
|
||||
let didError = false;
|
||||
|
||||
files.forEach(file => {
|
||||
Promise.all([
|
||||
prettier.resolveConfig(file, {
|
||||
config: prettierConfigPath,
|
||||
}),
|
||||
prettier.getFileInfo(file),
|
||||
])
|
||||
.then(resolves => {
|
||||
const [options, fileInfo] = resolves;
|
||||
if (fileInfo.ignored) {
|
||||
return;
|
||||
}
|
||||
const input = fs.readFileSync(file, 'utf8');
|
||||
const withParserOptions = {
|
||||
...options,
|
||||
parser: fileInfo.inferredParser,
|
||||
};
|
||||
const output = prettier.format(input, withParserOptions);
|
||||
if (output !== input) {
|
||||
fs.writeFileSync(file, output, 'utf8');
|
||||
console.log(chalk.green(`${file} is prettier`));
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
didError = true;
|
||||
})
|
||||
.finally(() => {
|
||||
if (didError) {
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(chalk.hex('#1890FF')('prettier success!'));
|
||||
});
|
||||
});
|
46
admin-web/scripts/prettier.js
Normal file
46
admin-web/scripts/prettier.js
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* copy to https://github.com/facebook/react/blob/master/scripts/prettier/index.js
|
||||
* prettier api doc https://prettier.io/docs/en/api.html
|
||||
*----------*****--------------
|
||||
* prettier all js and all ts.
|
||||
*----------*****--------------
|
||||
*/
|
||||
|
||||
const prettier = require('prettier');
|
||||
const fs = require('fs');
|
||||
const getPrettierFiles = require('./getPrettierFiles');
|
||||
const prettierConfigPath = require.resolve('../.prettierrc');
|
||||
const chalk = require('chalk');
|
||||
|
||||
let didError = false;
|
||||
|
||||
const files = getPrettierFiles();
|
||||
|
||||
files.forEach(file => {
|
||||
const options = prettier.resolveConfig.sync(file, {
|
||||
config: prettierConfigPath,
|
||||
});
|
||||
const fileInfo = prettier.getFileInfo.sync(file);
|
||||
if (fileInfo.ignored) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const input = fs.readFileSync(file, 'utf8');
|
||||
const withParserOptions = {
|
||||
...options,
|
||||
parser: fileInfo.inferredParser,
|
||||
};
|
||||
const output = prettier.format(input, withParserOptions);
|
||||
if (output !== input) {
|
||||
fs.writeFileSync(file, output, 'utf8');
|
||||
console.log(chalk.green(`${file} is prettier`));
|
||||
}
|
||||
} catch (e) {
|
||||
didError = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (didError) {
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(chalk.hex('#1890FF')('prettier success!'));
|
44
admin-web/src/app.js
Normal file
44
admin-web/src/app.js
Normal file
@ -0,0 +1,44 @@
|
||||
import fetch from 'dva/fetch';
|
||||
|
||||
export const dva = {
|
||||
config: {
|
||||
onError(err) {
|
||||
err.preventDefault();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let authRoutes = {};
|
||||
|
||||
function ergodicRoutes(routes, authKey, authority) {
|
||||
routes.forEach(element => {
|
||||
if (element.path === authKey) {
|
||||
if (!element.authority) element.authority = []; // eslint-disable-line
|
||||
Object.assign(element.authority, authority || []);
|
||||
} else if (element.routes) {
|
||||
ergodicRoutes(element.routes, authKey, authority);
|
||||
}
|
||||
return element;
|
||||
});
|
||||
}
|
||||
|
||||
export function patchRoutes(routes) {
|
||||
Object.keys(authRoutes).map(authKey =>
|
||||
ergodicRoutes(routes, authKey, authRoutes[authKey].authority)
|
||||
);
|
||||
window.g_routes = routes;
|
||||
}
|
||||
|
||||
export function render(oldRender) {
|
||||
fetch('/api/auth_routes')
|
||||
.then(res => res.json())
|
||||
.then(
|
||||
ret => {
|
||||
authRoutes = ret;
|
||||
oldRender();
|
||||
},
|
||||
() => {
|
||||
oldRender();
|
||||
}
|
||||
);
|
||||
}
|
43
admin-web/src/assets/logo.svg
Normal file
43
admin-web/src/assets/logo.svg
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="200px" height="200px" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Group 28 Copy 5</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<linearGradient x1="62.1023273%" y1="0%" x2="108.19718%" y2="37.8635764%" id="linearGradient-1">
|
||||
<stop stop-color="#4285EB" offset="0%"></stop>
|
||||
<stop stop-color="#2EC7FF" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient x1="69.644116%" y1="0%" x2="54.0428975%" y2="108.456714%" id="linearGradient-2">
|
||||
<stop stop-color="#29CDFF" offset="0%"></stop>
|
||||
<stop stop-color="#148EFF" offset="37.8600687%"></stop>
|
||||
<stop stop-color="#0A60FF" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient x1="69.6908165%" y1="-12.9743587%" x2="16.7228981%" y2="117.391248%" id="linearGradient-3">
|
||||
<stop stop-color="#FA816E" offset="0%"></stop>
|
||||
<stop stop-color="#F74A5C" offset="41.472606%"></stop>
|
||||
<stop stop-color="#F51D2C" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient x1="68.1279872%" y1="-35.6905737%" x2="30.4400914%" y2="114.942679%" id="linearGradient-4">
|
||||
<stop stop-color="#FA8E7D" offset="0%"></stop>
|
||||
<stop stop-color="#F74A5C" offset="51.2635191%"></stop>
|
||||
<stop stop-color="#F51D2C" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="logo" transform="translate(-20.000000, -20.000000)">
|
||||
<g id="Group-28-Copy-5" transform="translate(20.000000, 20.000000)">
|
||||
<g id="Group-27-Copy-3">
|
||||
<g id="Group-25" fill-rule="nonzero">
|
||||
<g id="2">
|
||||
<path d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C99.2571609,26.9692191 101.032305,26.9692191 102.20193,28.1378823 L129.985225,55.8983314 C134.193707,60.1033528 141.017005,60.1033528 145.225487,55.8983314 C149.433969,51.69331 149.433969,44.8756232 145.225487,40.6706018 L108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z" id="Shape" fill="url(#linearGradient-1)"></path>
|
||||
<path d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C100.999864,25.6271836 105.751642,20.541824 112.729652,19.3524487 C117.915585,18.4685261 123.585219,20.4140239 129.738554,25.1889424 C125.624663,21.0784292 118.571995,14.0340304 108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z" id="Shape" fill="url(#linearGradient-2)"></path>
|
||||
</g>
|
||||
<path d="M153.685633,135.854579 C157.894115,140.0596 164.717412,140.0596 168.925894,135.854579 L195.959977,108.842726 C200.659183,104.147384 200.659183,96.5636133 195.960527,91.8688194 L168.690777,64.7181159 C164.472332,60.5180858 157.646868,60.5241425 153.435895,64.7316526 C149.227413,68.936674 149.227413,75.7543607 153.435895,79.9593821 L171.854035,98.3623765 C173.02366,99.5310396 173.02366,101.304724 171.854035,102.473387 L153.685633,120.626849 C149.47715,124.83187 149.47715,131.649557 153.685633,135.854579 Z" id="Shape" fill="url(#linearGradient-3)"></path>
|
||||
</g>
|
||||
<ellipse id="Combined-Shape" fill="url(#linearGradient-4)" cx="100.519339" cy="100.436681" rx="23.6001926" ry="23.580786"></ellipse>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.6 KiB |
98
admin-web/src/components/ActiveChart/index.js
Normal file
98
admin-web/src/components/ActiveChart/index.js
Normal file
@ -0,0 +1,98 @@
|
||||
import React, { Component } from 'react';
|
||||
import { MiniArea } from '../Charts';
|
||||
import NumberInfo from '../NumberInfo';
|
||||
import styles from './index.less';
|
||||
|
||||
function fixedZero(val) {
|
||||
return val * 1 < 10 ? `0${val}` : val;
|
||||
}
|
||||
|
||||
function getActiveData() {
|
||||
const activeData = [];
|
||||
for (let i = 0; i < 24; i += 1) {
|
||||
activeData.push({
|
||||
x: `${fixedZero(i)}:00`,
|
||||
y: Math.floor(Math.random() * 200) + i * 50,
|
||||
});
|
||||
}
|
||||
return activeData;
|
||||
}
|
||||
|
||||
export default class ActiveChart extends Component {
|
||||
state = {
|
||||
activeData: getActiveData(),
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.loopData();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.timer);
|
||||
cancelAnimationFrame(this.requestRef);
|
||||
}
|
||||
|
||||
loopData = () => {
|
||||
this.timer = setTimeout(() => {
|
||||
this.setState(
|
||||
{
|
||||
activeData: getActiveData(),
|
||||
},
|
||||
() => {
|
||||
this.loopData();
|
||||
}
|
||||
);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { activeData = [] } = this.state;
|
||||
|
||||
return (
|
||||
<div className={styles.activeChart}>
|
||||
<NumberInfo subTitle="目标评估" total="有望达到预期" />
|
||||
<div style={{ marginTop: 32 }}>
|
||||
<MiniArea
|
||||
animate={false}
|
||||
line
|
||||
borderWidth={2}
|
||||
height={84}
|
||||
scale={{
|
||||
y: {
|
||||
tickCount: 3,
|
||||
},
|
||||
}}
|
||||
yAxis={{
|
||||
tickLine: false,
|
||||
label: false,
|
||||
title: false,
|
||||
line: false,
|
||||
}}
|
||||
data={activeData}
|
||||
/>
|
||||
</div>
|
||||
{activeData && (
|
||||
<div>
|
||||
<div className={styles.activeChartGrid}>
|
||||
<p>{[...activeData].sort()[activeData.length - 1].y + 200} 亿元</p>
|
||||
<p>{[...activeData].sort()[Math.floor(activeData.length / 2)].y} 亿元</p>
|
||||
</div>
|
||||
<div className={styles.dashedLine}>
|
||||
<div className={styles.line} />
|
||||
</div>
|
||||
<div className={styles.dashedLine}>
|
||||
<div className={styles.line} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{activeData && (
|
||||
<div className={styles.activeChartLegend}>
|
||||
<span>00:00</span>
|
||||
<span>{activeData[Math.floor(activeData.length / 2)].x}</span>
|
||||
<span>{activeData[activeData.length - 1].x}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
51
admin-web/src/components/ActiveChart/index.less
Normal file
51
admin-web/src/components/ActiveChart/index.less
Normal file
@ -0,0 +1,51 @@
|
||||
.activeChart {
|
||||
position: relative;
|
||||
}
|
||||
.activeChartGrid {
|
||||
p {
|
||||
position: absolute;
|
||||
top: 80px;
|
||||
}
|
||||
p:last-child {
|
||||
top: 115px;
|
||||
}
|
||||
}
|
||||
.activeChartLegend {
|
||||
position: relative;
|
||||
height: 20px;
|
||||
margin-top: 8px;
|
||||
font-size: 0;
|
||||
line-height: 20px;
|
||||
span {
|
||||
display: inline-block;
|
||||
width: 33.33%;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
span:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
span:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
.dashedLine {
|
||||
position: relative;
|
||||
top: -70px;
|
||||
left: -3px;
|
||||
height: 1px;
|
||||
|
||||
.line {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: linear-gradient(to right, transparent 50%, #e9e9e9 50%);
|
||||
background-size: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.dashedLine:last-child {
|
||||
top: -36px;
|
||||
}
|
17
admin-web/src/components/ArticleListContent/index.js
Normal file
17
admin-web/src/components/ArticleListContent/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { Avatar } from 'antd';
|
||||
import styles from './index.less';
|
||||
|
||||
const ArticleListContent = ({ data: { content, updatedAt, avatar, owner, href } }) => (
|
||||
<div className={styles.listContent}>
|
||||
<div className={styles.description}>{content}</div>
|
||||
<div className={styles.extra}>
|
||||
<Avatar src={avatar} size="small" />
|
||||
<a href={href}>{owner}</a> 发布在 <a href={href}>{href}</a>
|
||||
<em>{moment(updatedAt).format('YYYY-MM-DD HH:mm')}</em>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ArticleListContent;
|
38
admin-web/src/components/ArticleListContent/index.less
Normal file
38
admin-web/src/components/ArticleListContent/index.less
Normal file
@ -0,0 +1,38 @@
|
||||
@import '~antd/lib/style/themes/default.less';
|
||||
|
||||
.listContent {
|
||||
.description {
|
||||
max-width: 720px;
|
||||
line-height: 22px;
|
||||
}
|
||||
.extra {
|
||||
margin-top: 16px;
|
||||
color: @text-color-secondary;
|
||||
line-height: 22px;
|
||||
& > :global(.ant-avatar) {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 8px;
|
||||
vertical-align: top;
|
||||
}
|
||||
& > em {
|
||||
margin-left: 16px;
|
||||
color: @disabled-color;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: @screen-xs) {
|
||||
.listContent {
|
||||
.extra {
|
||||
& > em {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
admin-web/src/components/Authorized/Authorized.js
Normal file
8
admin-web/src/components/Authorized/Authorized.js
Normal file
@ -0,0 +1,8 @@
|
||||
import CheckPermissions from './CheckPermissions';
|
||||
|
||||
const Authorized = ({ children, authority, noMatch = null }) => {
|
||||
const childrenRender = typeof children === 'undefined' ? null : children;
|
||||
return CheckPermissions(authority, childrenRender, noMatch);
|
||||
};
|
||||
|
||||
export default Authorized;
|
13
admin-web/src/components/Authorized/AuthorizedRoute.d.ts
vendored
Normal file
13
admin-web/src/components/Authorized/AuthorizedRoute.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import { RouteProps } from 'react-router';
|
||||
|
||||
type authorityFN = (currentAuthority?: string) => boolean;
|
||||
|
||||
type authority = string | string[] | authorityFN | Promise<any>;
|
||||
|
||||
export interface IAuthorizedRouteProps extends RouteProps {
|
||||
authority: authority;
|
||||
}
|
||||
export { authority };
|
||||
|
||||
export default class AuthorizedRoute extends React.Component<IAuthorizedRouteProps, any> {}
|
15
admin-web/src/components/Authorized/AuthorizedRoute.js
Normal file
15
admin-web/src/components/Authorized/AuthorizedRoute.js
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Route, Redirect } from 'react-router-dom';
|
||||
import Authorized from './Authorized';
|
||||
|
||||
// TODO: umi只会返回render和rest
|
||||
const AuthorizedRoute = ({ component: Component, render, authority, redirectPath, ...rest }) => (
|
||||
<Authorized
|
||||
authority={authority}
|
||||
noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />}
|
||||
>
|
||||
<Route {...rest} render={props => (Component ? <Component {...props} /> : render(props))} />
|
||||
</Authorized>
|
||||
);
|
||||
|
||||
export default AuthorizedRoute;
|
88
admin-web/src/components/Authorized/CheckPermissions.js
Normal file
88
admin-web/src/components/Authorized/CheckPermissions.js
Normal file
@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
import PromiseRender from './PromiseRender';
|
||||
import { CURRENT } from './renderAuthorize';
|
||||
|
||||
function isPromise(obj) {
|
||||
return (
|
||||
!!obj &&
|
||||
(typeof obj === 'object' || typeof obj === 'function') &&
|
||||
typeof obj.then === 'function'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用权限检查方法
|
||||
* Common check permissions method
|
||||
* @param { 权限判定 Permission judgment type string |array | Promise | Function } authority
|
||||
* @param { 你的权限 Your permission description type:string} currentAuthority
|
||||
* @param { 通过的组件 Passing components } target
|
||||
* @param { 未通过的组件 no pass components } Exception
|
||||
*/
|
||||
const checkPermissions = (authority, currentAuthority, target, Exception) => {
|
||||
// 没有判定权限.默认查看所有
|
||||
// Retirement authority, return target;
|
||||
if (!authority) {
|
||||
return target;
|
||||
}
|
||||
// 数组处理
|
||||
if (Array.isArray(authority)) {
|
||||
if (authority.indexOf(currentAuthority) >= 0) {
|
||||
return target;
|
||||
}
|
||||
if (Array.isArray(currentAuthority)) {
|
||||
for (let i = 0; i < currentAuthority.length; i += 1) {
|
||||
const element = currentAuthority[i];
|
||||
if (authority.indexOf(element) >= 0) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Exception;
|
||||
}
|
||||
|
||||
// string 处理
|
||||
if (typeof authority === 'string') {
|
||||
if (authority === currentAuthority) {
|
||||
return target;
|
||||
}
|
||||
if (Array.isArray(currentAuthority)) {
|
||||
for (let i = 0; i < currentAuthority.length; i += 1) {
|
||||
const element = currentAuthority[i];
|
||||
if (authority === element) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Exception;
|
||||
}
|
||||
|
||||
// Promise 处理
|
||||
if (isPromise(authority)) {
|
||||
return <PromiseRender ok={target} error={Exception} promise={authority} />;
|
||||
}
|
||||
|
||||
// Function 处理
|
||||
if (typeof authority === 'function') {
|
||||
try {
|
||||
const bool = authority(currentAuthority);
|
||||
// 函数执行后返回值是 Promise
|
||||
if (isPromise(bool)) {
|
||||
return <PromiseRender ok={target} error={Exception} promise={bool} />;
|
||||
}
|
||||
if (bool) {
|
||||
return target;
|
||||
}
|
||||
return Exception;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
throw new Error('unsupported parameters');
|
||||
};
|
||||
|
||||
export { checkPermissions };
|
||||
|
||||
const check = (authority, target, Exception) =>
|
||||
checkPermissions(authority, CURRENT, target, Exception);
|
||||
|
||||
export default check;
|
55
admin-web/src/components/Authorized/CheckPermissions.test.js
Normal file
55
admin-web/src/components/Authorized/CheckPermissions.test.js
Normal file
@ -0,0 +1,55 @@
|
||||
import { checkPermissions } from './CheckPermissions';
|
||||
|
||||
const target = 'ok';
|
||||
const error = 'error';
|
||||
|
||||
describe('test CheckPermissions', () => {
|
||||
it('Correct string permission authentication', () => {
|
||||
expect(checkPermissions('user', 'user', target, error)).toEqual('ok');
|
||||
});
|
||||
it('Correct string permission authentication', () => {
|
||||
expect(checkPermissions('user', 'NULL', target, error)).toEqual('error');
|
||||
});
|
||||
it('authority is undefined , return ok', () => {
|
||||
expect(checkPermissions(null, 'NULL', target, error)).toEqual('ok');
|
||||
});
|
||||
it('currentAuthority is undefined , return error', () => {
|
||||
expect(checkPermissions('admin', null, target, error)).toEqual('error');
|
||||
});
|
||||
it('Wrong string permission authentication', () => {
|
||||
expect(checkPermissions('admin', 'user', target, error)).toEqual('error');
|
||||
});
|
||||
it('Correct Array permission authentication', () => {
|
||||
expect(checkPermissions(['user', 'admin'], 'user', target, error)).toEqual('ok');
|
||||
});
|
||||
it('Wrong Array permission authentication,currentAuthority error', () => {
|
||||
expect(checkPermissions(['user', 'admin'], 'user,admin', target, error)).toEqual('error');
|
||||
});
|
||||
it('Wrong Array permission authentication', () => {
|
||||
expect(checkPermissions(['user', 'admin'], 'guest', target, error)).toEqual('error');
|
||||
});
|
||||
it('Wrong Function permission authentication', () => {
|
||||
expect(checkPermissions(() => false, 'guest', target, error)).toEqual('error');
|
||||
});
|
||||
it('Correct Function permission authentication', () => {
|
||||
expect(checkPermissions(() => true, 'guest', target, error)).toEqual('ok');
|
||||
});
|
||||
it('authority is string, currentAuthority is array, return ok', () => {
|
||||
expect(checkPermissions('user', ['user'], target, error)).toEqual('ok');
|
||||
});
|
||||
it('authority is string, currentAuthority is array, return ok', () => {
|
||||
expect(checkPermissions('user', ['user', 'admin'], target, error)).toEqual('ok');
|
||||
});
|
||||
it('authority is array, currentAuthority is array, return ok', () => {
|
||||
expect(checkPermissions(['user', 'admin'], ['user', 'admin'], target, error)).toEqual('ok');
|
||||
});
|
||||
it('Wrong Function permission authentication', () => {
|
||||
expect(checkPermissions(() => false, ['user'], target, error)).toEqual('error');
|
||||
});
|
||||
it('Correct Function permission authentication', () => {
|
||||
expect(checkPermissions(() => true, ['user'], target, error)).toEqual('ok');
|
||||
});
|
||||
it('authority is undefined , return ok', () => {
|
||||
expect(checkPermissions(null, ['user'], target, error)).toEqual('ok');
|
||||
});
|
||||
});
|
65
admin-web/src/components/Authorized/PromiseRender.js
Normal file
65
admin-web/src/components/Authorized/PromiseRender.js
Normal file
@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import { Spin } from 'antd';
|
||||
|
||||
export default class PromiseRender extends React.PureComponent {
|
||||
state = {
|
||||
component: null,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.setRenderComponent(this.props);
|
||||
}
|
||||
|
||||
componentDidUpdate(nextProps) {
|
||||
// new Props enter
|
||||
this.setRenderComponent(nextProps);
|
||||
}
|
||||
|
||||
// set render Component : ok or error
|
||||
setRenderComponent(props) {
|
||||
const ok = this.checkIsInstantiation(props.ok);
|
||||
const error = this.checkIsInstantiation(props.error);
|
||||
props.promise
|
||||
.then(() => {
|
||||
this.setState({
|
||||
component: ok,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({
|
||||
component: error,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Determine whether the incoming component has been instantiated
|
||||
// AuthorizedRoute is already instantiated
|
||||
// Authorized render is already instantiated, children is no instantiated
|
||||
// Secured is not instantiated
|
||||
checkIsInstantiation = target => {
|
||||
if (!React.isValidElement(target)) {
|
||||
return target;
|
||||
}
|
||||
return () => target;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { component: Component } = this.state;
|
||||
const { ok, error, promise, ...rest } = this.props;
|
||||
return Component ? (
|
||||
<Component {...rest} />
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
margin: 'auto',
|
||||
paddingTop: 50,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
55
admin-web/src/components/Authorized/Secured.js
Normal file
55
admin-web/src/components/Authorized/Secured.js
Normal file
@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import Exception from '../Exception';
|
||||
import CheckPermissions from './CheckPermissions';
|
||||
/**
|
||||
* 默认不能访问任何页面
|
||||
* default is "NULL"
|
||||
*/
|
||||
const Exception403 = () => <Exception type="403" />;
|
||||
|
||||
// Determine whether the incoming component has been instantiated
|
||||
// AuthorizedRoute is already instantiated
|
||||
// Authorized render is already instantiated, children is no instantiated
|
||||
// Secured is not instantiated
|
||||
const checkIsInstantiation = target => {
|
||||
if (!React.isValidElement(target)) {
|
||||
return target;
|
||||
}
|
||||
return () => target;
|
||||
};
|
||||
|
||||
/**
|
||||
* 用于判断是否拥有权限访问此view权限
|
||||
* authority 支持传入 string, function:()=>boolean|Promise
|
||||
* e.g. 'user' 只有user用户能访问
|
||||
* e.g. 'user,admin' user和 admin 都能访问
|
||||
* e.g. ()=>boolean 返回true能访问,返回false不能访问
|
||||
* e.g. Promise then 能访问 catch不能访问
|
||||
* e.g. authority support incoming string, function: () => boolean | Promise
|
||||
* e.g. 'user' only user user can access
|
||||
* e.g. 'user, admin' user and admin can access
|
||||
* e.g. () => boolean true to be able to visit, return false can not be accessed
|
||||
* e.g. Promise then can not access the visit to catch
|
||||
* @param {string | function | Promise} authority
|
||||
* @param {ReactNode} error 非必需参数
|
||||
*/
|
||||
const authorize = (authority, error) => {
|
||||
/**
|
||||
* conversion into a class
|
||||
* 防止传入字符串时找不到staticContext造成报错
|
||||
* String parameters can cause staticContext not found error
|
||||
*/
|
||||
let classError = false;
|
||||
if (error) {
|
||||
classError = () => error;
|
||||
}
|
||||
if (!authority) {
|
||||
throw new Error('authority is required');
|
||||
}
|
||||
return function decideAuthority(target) {
|
||||
const component = CheckPermissions(authority, target, classError || Exception403);
|
||||
return checkIsInstantiation(component);
|
||||
};
|
||||
};
|
||||
|
||||
export default authorize;
|
23
admin-web/src/components/Authorized/demo/AuthorizedArray.md
Normal file
23
admin-web/src/components/Authorized/demo/AuthorizedArray.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
order: 1
|
||||
title:
|
||||
zh-CN: 使用数组作为参数
|
||||
en-US: Use Array as a parameter
|
||||
---
|
||||
|
||||
Use Array as a parameter
|
||||
|
||||
```jsx
|
||||
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
|
||||
import { Alert } from 'antd';
|
||||
|
||||
const Authorized = RenderAuthorized('user');
|
||||
const noMatch = <Alert message="No permission." type="error" showIcon />;
|
||||
|
||||
ReactDOM.render(
|
||||
<Authorized authority={['user', 'admin']} noMatch={noMatch}>
|
||||
<Alert message="Use Array as a parameter passed!" type="success" showIcon />
|
||||
</Authorized>,
|
||||
mountNode,
|
||||
);
|
||||
```
|
@ -0,0 +1,31 @@
|
||||
---
|
||||
order: 2
|
||||
title:
|
||||
zh-CN: 使用方法作为参数
|
||||
en-US: Use function as a parameter
|
||||
---
|
||||
|
||||
Use Function as a parameter
|
||||
|
||||
```jsx
|
||||
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
|
||||
import { Alert } from 'antd';
|
||||
|
||||
const Authorized = RenderAuthorized('user');
|
||||
const noMatch = <Alert message="No permission." type="error" showIcon />;
|
||||
|
||||
const havePermission = () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
<Authorized authority={havePermission} noMatch={noMatch}>
|
||||
<Alert
|
||||
message="Use Function as a parameter passed!"
|
||||
type="success"
|
||||
showIcon
|
||||
/>
|
||||
</Authorized>,
|
||||
mountNode,
|
||||
);
|
||||
```
|
25
admin-web/src/components/Authorized/demo/basic.md
Normal file
25
admin-web/src/components/Authorized/demo/basic.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
order: 0
|
||||
title:
|
||||
zh-CN: 基本使用
|
||||
en-US: Basic use
|
||||
---
|
||||
|
||||
Basic use
|
||||
|
||||
```jsx
|
||||
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
|
||||
import { Alert } from 'antd';
|
||||
|
||||
const Authorized = RenderAuthorized('user');
|
||||
const noMatch = <Alert message="No permission." type="error" showIcon />;
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<Authorized authority="admin" noMatch={noMatch}>
|
||||
<Alert message="user Passed!" type="success" showIcon />
|
||||
</Authorized>
|
||||
</div>,
|
||||
mountNode,
|
||||
);
|
||||
```
|
28
admin-web/src/components/Authorized/demo/secured.md
Normal file
28
admin-web/src/components/Authorized/demo/secured.md
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
order: 3
|
||||
title:
|
||||
zh-CN: 注解基本使用
|
||||
en-US: Basic use secured
|
||||
---
|
||||
|
||||
secured demo used
|
||||
|
||||
```jsx
|
||||
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
|
||||
import { Alert } from 'antd';
|
||||
|
||||
const { Secured } = RenderAuthorized('user');
|
||||
|
||||
@Secured('admin')
|
||||
class TestSecuredString extends React.Component {
|
||||
render() {
|
||||
<Alert message="user Passed!" type="success" showIcon />;
|
||||
}
|
||||
}
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<TestSecuredString />
|
||||
</div>,
|
||||
mountNode,
|
||||
);
|
||||
```
|
32
admin-web/src/components/Authorized/index.d.ts
vendored
Normal file
32
admin-web/src/components/Authorized/index.d.ts
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
import * as React from 'react';
|
||||
import AuthorizedRoute, { authority } from './AuthorizedRoute';
|
||||
export type IReactComponent<P = any> =
|
||||
| React.StatelessComponent<P>
|
||||
| React.ComponentClass<P>
|
||||
| React.ClassicComponentClass<P>;
|
||||
|
||||
type Secured = (
|
||||
authority: authority,
|
||||
error?: React.ReactNode
|
||||
) => <T extends IReactComponent>(target: T) => T;
|
||||
|
||||
type check = <T extends IReactComponent, S extends IReactComponent>(
|
||||
authority: authority,
|
||||
target: T,
|
||||
Exception: S
|
||||
) => T | S;
|
||||
|
||||
export interface IAuthorizedProps {
|
||||
authority: authority;
|
||||
noMatch?: React.ReactNode;
|
||||
}
|
||||
|
||||
export class Authorized extends React.Component<IAuthorizedProps, any> {
|
||||
public static Secured: Secured;
|
||||
public static AuthorizedRoute: typeof AuthorizedRoute;
|
||||
public static check: check;
|
||||
}
|
||||
|
||||
declare function renderAuthorize(currentAuthority: string): typeof Authorized;
|
||||
|
||||
export default renderAuthorize;
|
11
admin-web/src/components/Authorized/index.js
Normal file
11
admin-web/src/components/Authorized/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
import Authorized from './Authorized';
|
||||
import AuthorizedRoute from './AuthorizedRoute';
|
||||
import Secured from './Secured';
|
||||
import check from './CheckPermissions';
|
||||
import renderAuthorize from './renderAuthorize';
|
||||
|
||||
Authorized.Secured = Secured;
|
||||
Authorized.AuthorizedRoute = AuthorizedRoute;
|
||||
Authorized.check = check;
|
||||
|
||||
export default renderAuthorize(Authorized);
|
56
admin-web/src/components/Authorized/index.md
Normal file
56
admin-web/src/components/Authorized/index.md
Normal file
@ -0,0 +1,56 @@
|
||||
---
|
||||
title: Authorized
|
||||
subtitle: 权限
|
||||
cols: 1
|
||||
order: 15
|
||||
---
|
||||
|
||||
权限组件,通过比对现有权限与准入权限,决定相关元素的展示。
|
||||
|
||||
## API
|
||||
|
||||
### RenderAuthorized
|
||||
|
||||
`RenderAuthorized: (currentAuthority: string | () => string) => Authorized`
|
||||
|
||||
权限组件默认 export RenderAuthorized 函数,它接收当前权限作为参数,返回一个权限对象,该对象提供以下几种使用方式。
|
||||
|
||||
|
||||
### Authorized
|
||||
|
||||
最基础的权限控制。
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
|----------|------------------------------------------|-------------|-------|
|
||||
| children | 正常渲染的元素,权限判断通过时展示 | ReactNode | - |
|
||||
| authority | 准入权限/权限判断 | `string | array | Promise | (currentAuthority) => boolean | Promise` | - |
|
||||
| noMatch | 权限异常渲染元素,权限判断不通过时展示 | ReactNode | - |
|
||||
|
||||
### Authorized.AuthorizedRoute
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
|----------|------------------------------------------|-------------|-------|
|
||||
| authority | 准入权限/权限判断 | `string | array | Promise | (currentAuthority) => boolean | Promise` | - |
|
||||
| redirectPath | 权限异常时重定向的页面路由 | string | - |
|
||||
|
||||
其余参数与 `Route` 相同。
|
||||
|
||||
### Authorized.Secured
|
||||
|
||||
注解方式,`@Authorized.Secured(authority, error)`
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
|----------|------------------------------------------|-------------|-------|
|
||||
| authority | 准入权限/权限判断 | `string | Promise | (currentAuthority) => boolean | Promise` | - |
|
||||
| error | 权限异常时渲染元素 | ReactNode | <Exception type="403" /> |
|
||||
|
||||
### Authorized.check
|
||||
|
||||
函数形式的 Authorized,用于某些不能被 HOC 包裹的组件。 `Authorized.check(authority, target, Exception)`
|
||||
注意:传入一个 Promise 时,无论正确还是错误返回的都是一个 ReactClass。
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
|----------|------------------------------------------|-------------|-------|
|
||||
| authority | 准入权限/权限判断 | `string | Promise | (currentAuthority) => boolean | Promise` | - |
|
||||
| target | 权限判断通过时渲染的元素 | ReactNode | - |
|
||||
| Exception | 权限异常时渲染元素 | ReactNode | - |
|
25
admin-web/src/components/Authorized/renderAuthorize.js
Normal file
25
admin-web/src/components/Authorized/renderAuthorize.js
Normal file
@ -0,0 +1,25 @@
|
||||
/* eslint-disable import/no-mutable-exports */
|
||||
let CURRENT = 'NULL';
|
||||
/**
|
||||
* use authority or getAuthority
|
||||
* @param {string|()=>String} currentAuthority
|
||||
*/
|
||||
const renderAuthorize = Authorized => currentAuthority => {
|
||||
if (currentAuthority) {
|
||||
if (typeof currentAuthority === 'function') {
|
||||
CURRENT = currentAuthority();
|
||||
}
|
||||
if (
|
||||
Object.prototype.toString.call(currentAuthority) === '[object String]' ||
|
||||
Array.isArray(currentAuthority)
|
||||
) {
|
||||
CURRENT = currentAuthority;
|
||||
}
|
||||
} else {
|
||||
CURRENT = 'NULL';
|
||||
}
|
||||
return Authorized;
|
||||
};
|
||||
|
||||
export { CURRENT };
|
||||
export default Authorized => renderAuthorize(Authorized);
|
10
admin-web/src/components/AvatarList/AvatarItem.d.ts
vendored
Normal file
10
admin-web/src/components/AvatarList/AvatarItem.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
import * as React from 'react';
|
||||
export interface IAvatarItemProps {
|
||||
tips: React.ReactNode;
|
||||
src: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default class AvatarItem extends React.Component<IAvatarItemProps, any> {
|
||||
constructor(props: IAvatarItemProps);
|
||||
}
|
24
admin-web/src/components/AvatarList/demo/maxLength.md
Normal file
24
admin-web/src/components/AvatarList/demo/maxLength.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
order: 0
|
||||
title:
|
||||
zh-CN: 要显示的最大项目
|
||||
en-US: Max Items to Show
|
||||
---
|
||||
|
||||
`maxLength` attribute specifies the maximum number of items to show while `excessItemsStyle` style the excess
|
||||
item component.
|
||||
|
||||
````jsx
|
||||
import AvatarList from 'ant-design-pro/lib/AvatarList';
|
||||
|
||||
ReactDOM.render(
|
||||
<AvatarList size="mini" maxLength={3} excessItemsStyle={{ color: '#f56a00', backgroundColor: '#fde3cf' }}>
|
||||
<AvatarList.Item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
|
||||
<AvatarList.Item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
|
||||
<AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||
<AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||
<AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||
<AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||
</AvatarList>
|
||||
, mountNode);
|
||||
````
|
20
admin-web/src/components/AvatarList/demo/simple.md
Normal file
20
admin-web/src/components/AvatarList/demo/simple.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
order: 0
|
||||
title:
|
||||
zh-CN: 基础样例
|
||||
en-US: Basic Usage
|
||||
---
|
||||
|
||||
Simplest of usage.
|
||||
|
||||
````jsx
|
||||
import AvatarList from 'ant-design-pro/lib/AvatarList';
|
||||
|
||||
ReactDOM.render(
|
||||
<AvatarList size="mini">
|
||||
<AvatarList.Item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
|
||||
<AvatarList.Item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
|
||||
<AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||
</AvatarList>
|
||||
, mountNode);
|
||||
````
|
14
admin-web/src/components/AvatarList/index.d.ts
vendored
Normal file
14
admin-web/src/components/AvatarList/index.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import AvatarItem from './AvatarItem';
|
||||
|
||||
export interface IAvatarListProps {
|
||||
size?: 'large' | 'small' | 'mini' | 'default';
|
||||
maxLength?: number;
|
||||
excessItemsStyle?: React.CSSProperties;
|
||||
style?: React.CSSProperties;
|
||||
children: React.ReactElement<AvatarItem> | Array<React.ReactElement<AvatarItem>>;
|
||||
}
|
||||
|
||||
export default class AvatarList extends React.Component<IAvatarListProps, any> {
|
||||
public static Item: typeof AvatarItem;
|
||||
}
|
24
admin-web/src/components/AvatarList/index.en-US.md
Normal file
24
admin-web/src/components/AvatarList/index.en-US.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
title: AvatarList
|
||||
order: 1
|
||||
cols: 1
|
||||
---
|
||||
|
||||
A list of user's avatar for project or group member list frequently. If a large or small AvatarList is desired, set the `size` property to either `large` or `small` and `mini` respectively. Omit the `size` property for a AvatarList with the default size.
|
||||
|
||||
## API
|
||||
|
||||
### AvatarList
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
| ---------------- | --------------------- | ---------------------------------- | --------- |
|
||||
| size | size of list | `large`、`small` 、`mini`, `default` | `default` |
|
||||
| maxLength | max items to show | number | - |
|
||||
| excessItemsStyle | the excess item style | CSSProperties | - |
|
||||
|
||||
### AvatarList.Item
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
| -------- | -------------------------------------------- | --------- | ------- |
|
||||
| tips | title tips for avatar item | ReactNode | - |
|
||||
| src | the address of the image for an image avatar | string | - |
|
61
admin-web/src/components/AvatarList/index.js
Normal file
61
admin-web/src/components/AvatarList/index.js
Normal file
@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import { Tooltip, Avatar } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import styles from './index.less';
|
||||
|
||||
const avatarSizeToClassName = size =>
|
||||
classNames(styles.avatarItem, {
|
||||
[styles.avatarItemLarge]: size === 'large',
|
||||
[styles.avatarItemSmall]: size === 'small',
|
||||
[styles.avatarItemMini]: size === 'mini',
|
||||
});
|
||||
|
||||
const AvatarList = ({ children, size, maxLength, excessItemsStyle, ...other }) => {
|
||||
const numOfChildren = React.Children.count(children);
|
||||
const numToShow = maxLength >= numOfChildren ? numOfChildren : maxLength;
|
||||
|
||||
const childrenWithProps = React.Children.toArray(children)
|
||||
.slice(0, numToShow)
|
||||
.map(child =>
|
||||
React.cloneElement(child, {
|
||||
size,
|
||||
})
|
||||
);
|
||||
|
||||
if (numToShow < numOfChildren) {
|
||||
const cls = avatarSizeToClassName(size);
|
||||
|
||||
childrenWithProps.push(
|
||||
<li key="exceed" className={cls}>
|
||||
<Avatar size={size} style={excessItemsStyle}>{`+${numOfChildren - maxLength}`}</Avatar>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...other} className={styles.avatarList}>
|
||||
<ul> {childrenWithProps} </ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Item = ({ src, size, tips, onClick = () => {} }) => {
|
||||
const cls = avatarSizeToClassName(size);
|
||||
|
||||
return (
|
||||
<li className={cls} onClick={onClick}>
|
||||
{tips ? (
|
||||
<Tooltip title={tips}>
|
||||
<Avatar src={src} size={size} style={{ cursor: 'pointer' }} />
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Avatar src={src} size={size} />
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
AvatarList.Item = Item;
|
||||
|
||||
export default AvatarList;
|
50
admin-web/src/components/AvatarList/index.less
Normal file
50
admin-web/src/components/AvatarList/index.less
Normal file
@ -0,0 +1,50 @@
|
||||
@import '~antd/lib/style/themes/default.less';
|
||||
|
||||
.avatarList {
|
||||
display: inline-block;
|
||||
ul {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
font-size: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.avatarItem {
|
||||
display: inline-block;
|
||||
width: @avatar-size-base;
|
||||
height: @avatar-size-base;
|
||||
margin-left: -8px;
|
||||
font-size: @font-size-base;
|
||||
:global {
|
||||
.ant-avatar {
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.avatarItemLarge {
|
||||
width: @avatar-size-lg;
|
||||
height: @avatar-size-lg;
|
||||
}
|
||||
|
||||
.avatarItemSmall {
|
||||
width: @avatar-size-sm;
|
||||
height: @avatar-size-sm;
|
||||
}
|
||||
|
||||
.avatarItemMini {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
:global {
|
||||
.ant-avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
|
||||
.ant-avatar-string {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
admin-web/src/components/AvatarList/index.test.js
Normal file
29
admin-web/src/components/AvatarList/index.test.js
Normal file
@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import range from 'lodash/range';
|
||||
import { mount } from 'enzyme';
|
||||
import AvatarList from './index';
|
||||
|
||||
const renderItems = numItems =>
|
||||
range(numItems).map(i => (
|
||||
<AvatarList.Item
|
||||
key={i}
|
||||
tips="Jake"
|
||||
src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png"
|
||||
/>
|
||||
));
|
||||
|
||||
describe('AvatarList', () => {
|
||||
it('renders all items', () => {
|
||||
const wrapper = mount(<AvatarList>{renderItems(4)}</AvatarList>);
|
||||
expect(wrapper.find('AvatarList').length).toBe(1);
|
||||
expect(wrapper.find('Item').length).toBe(4);
|
||||
expect(wrapper.findWhere(node => node.key() === 'exceed').length).toBe(0);
|
||||
});
|
||||
|
||||
it('renders max of 3 items', () => {
|
||||
const wrapper = mount(<AvatarList maxLength={3}>{renderItems(4)}</AvatarList>);
|
||||
expect(wrapper.find('AvatarList').length).toBe(1);
|
||||
expect(wrapper.find('Item').length).toBe(3);
|
||||
expect(wrapper.findWhere(node => node.key() === 'exceed').length).toBe(1);
|
||||
});
|
||||
});
|
25
admin-web/src/components/AvatarList/index.zh-CN.md
Normal file
25
admin-web/src/components/AvatarList/index.zh-CN.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
title: AvatarList
|
||||
subtitle: 用户头像列表
|
||||
order: 1
|
||||
cols: 1
|
||||
---
|
||||
|
||||
一组用户头像,常用在项目/团队成员列表。可通过设置 `size` 属性来指定头像大小。
|
||||
|
||||
## API
|
||||
|
||||
### AvatarList
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| ---------------- | -------- | ---------------------------------- | --------- |
|
||||
| size | 头像大小 | `large`、`small` 、`mini`, `default` | `default` |
|
||||
| maxLength | 要显示的最大项目 | number | - |
|
||||
| excessItemsStyle | 多余的项目风格 | CSSProperties | - |
|
||||
|
||||
### AvatarList.Item
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| ---- | ------ | --------- | --- |
|
||||
| tips | 头像展示文案 | ReactNode | - |
|
||||
| src | 头像图片连接 | string | - |
|
44
admin-web/src/components/Charts/AsyncLoadBizCharts.js
Normal file
44
admin-web/src/components/Charts/AsyncLoadBizCharts.js
Normal file
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import PageLoading from '../PageLoading';
|
||||
import { importCDN } from '@/utils/utils';
|
||||
|
||||
let isLoaderBizChart = false;
|
||||
const loadBizCharts = async () => {
|
||||
if (isLoaderBizChart) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
await Promise.all([
|
||||
importCDN('//gw.alipayobjects.com/os/lib/bizcharts/3.4.3/umd/BizCharts.min.js'),
|
||||
importCDN('//gw.alipayobjects.com/os/lib/antv/data-set/0.10.1/dist/data-set.min.js'),
|
||||
]);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('bizCharts load success');
|
||||
isLoaderBizChart = true;
|
||||
return Promise.resolve(true);
|
||||
};
|
||||
|
||||
class AsyncLoadBizCharts extends React.Component {
|
||||
state = {
|
||||
loading: !isLoaderBizChart,
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
await loadBizCharts();
|
||||
requestAnimationFrame(() => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
const { loading } = this.state;
|
||||
if (!loading) {
|
||||
return children;
|
||||
}
|
||||
return <PageLoading />;
|
||||
}
|
||||
}
|
||||
|
||||
export { loadBizCharts, AsyncLoadBizCharts };
|
15
admin-web/src/components/Charts/Bar/index.d.ts
vendored
Normal file
15
admin-web/src/components/Charts/Bar/index.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
import * as React from 'react';
|
||||
export interface IBarProps {
|
||||
title: React.ReactNode;
|
||||
color?: string;
|
||||
padding?: [number, number, number, number];
|
||||
height: number;
|
||||
data: Array<{
|
||||
x: string;
|
||||
y: number;
|
||||
}>;
|
||||
autoLabel?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default class Bar extends React.Component<IBarProps, any> {}
|
113
admin-web/src/components/Charts/Bar/index.js
Normal file
113
admin-web/src/components/Charts/Bar/index.js
Normal file
@ -0,0 +1,113 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Chart, Axis, Tooltip, Geom } from 'bizcharts';
|
||||
import Debounce from 'lodash-decorators/debounce';
|
||||
import Bind from 'lodash-decorators/bind';
|
||||
import autoHeight from '../autoHeight';
|
||||
import styles from '../index.less';
|
||||
|
||||
@autoHeight()
|
||||
class Bar extends Component {
|
||||
state = {
|
||||
autoHideXLabels: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('resize', this.resize, { passive: true });
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.resize);
|
||||
}
|
||||
|
||||
handleRoot = n => {
|
||||
this.root = n;
|
||||
};
|
||||
|
||||
handleRef = n => {
|
||||
this.node = n;
|
||||
};
|
||||
|
||||
@Bind()
|
||||
@Debounce(400)
|
||||
resize() {
|
||||
if (!this.node) {
|
||||
return;
|
||||
}
|
||||
const canvasWidth = this.node.parentNode.clientWidth;
|
||||
const { data = [], autoLabel = true } = this.props;
|
||||
if (!autoLabel) {
|
||||
return;
|
||||
}
|
||||
const minWidth = data.length * 30;
|
||||
const { autoHideXLabels } = this.state;
|
||||
|
||||
if (canvasWidth <= minWidth) {
|
||||
if (!autoHideXLabels) {
|
||||
this.setState({
|
||||
autoHideXLabels: true,
|
||||
});
|
||||
}
|
||||
} else if (autoHideXLabels) {
|
||||
this.setState({
|
||||
autoHideXLabels: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
height,
|
||||
title,
|
||||
forceFit = true,
|
||||
data,
|
||||
color = 'rgba(24, 144, 255, 0.85)',
|
||||
padding,
|
||||
} = this.props;
|
||||
|
||||
const { autoHideXLabels } = this.state;
|
||||
|
||||
const scale = {
|
||||
x: {
|
||||
type: 'cat',
|
||||
},
|
||||
y: {
|
||||
min: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const tooltip = [
|
||||
'x*y',
|
||||
(x, y) => ({
|
||||
name: x,
|
||||
value: y,
|
||||
}),
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles.chart} style={{ height }} ref={this.handleRoot}>
|
||||
<div ref={this.handleRef}>
|
||||
{title && <h4 style={{ marginBottom: 20 }}>{title}</h4>}
|
||||
<Chart
|
||||
scale={scale}
|
||||
height={title ? height - 41 : height}
|
||||
forceFit={forceFit}
|
||||
data={data}
|
||||
padding={padding || 'auto'}
|
||||
>
|
||||
<Axis
|
||||
name="x"
|
||||
title={false}
|
||||
label={autoHideXLabels ? false : {}}
|
||||
tickLine={autoHideXLabels ? false : {}}
|
||||
/>
|
||||
<Axis name="y" min={0} />
|
||||
<Tooltip showTitle={false} crosshairs={false} />
|
||||
<Geom type="interval" position="x*y" color={color} tooltip={tooltip} />
|
||||
</Chart>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Bar;
|
14
admin-web/src/components/Charts/ChartCard/index.d.ts
vendored
Normal file
14
admin-web/src/components/Charts/ChartCard/index.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
import { CardProps } from 'antd/lib/card';
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IChartCardProps extends CardProps {
|
||||
title: React.ReactNode;
|
||||
action?: React.ReactNode;
|
||||
total?: React.ReactNode | number | (() => React.ReactNode | number);
|
||||
footer?: React.ReactNode;
|
||||
contentHeight?: number;
|
||||
avatar?: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default class ChartCard extends React.Component<IChartCardProps, any> {}
|
82
admin-web/src/components/Charts/ChartCard/index.js
Normal file
82
admin-web/src/components/Charts/ChartCard/index.js
Normal file
@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import { Card } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import styles from './index.less';
|
||||
|
||||
const renderTotal = total => {
|
||||
let totalDom;
|
||||
switch (typeof total) {
|
||||
case 'undefined':
|
||||
totalDom = null;
|
||||
break;
|
||||
case 'function':
|
||||
totalDom = <div className={styles.total}>{total()}</div>;
|
||||
break;
|
||||
default:
|
||||
totalDom = <div className={styles.total}>{total}</div>;
|
||||
}
|
||||
return totalDom;
|
||||
};
|
||||
|
||||
class ChartCard extends React.PureComponent {
|
||||
renderConnet = () => {
|
||||
const { contentHeight, title, avatar, action, total, footer, children, loading } = this.props;
|
||||
if (loading) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
<div className={styles.chartCard}>
|
||||
<div
|
||||
className={classNames(styles.chartTop, {
|
||||
[styles.chartTopMargin]: !children && !footer,
|
||||
})}
|
||||
>
|
||||
<div className={styles.avatar}>{avatar}</div>
|
||||
<div className={styles.metaWrap}>
|
||||
<div className={styles.meta}>
|
||||
<span className={styles.title}>{title}</span>
|
||||
<span className={styles.action}>{action}</span>
|
||||
</div>
|
||||
{renderTotal(total)}
|
||||
</div>
|
||||
</div>
|
||||
{children && (
|
||||
<div className={styles.content} style={{ height: contentHeight || 'auto' }}>
|
||||
<div className={contentHeight && styles.contentFixed}>{children}</div>
|
||||
</div>
|
||||
)}
|
||||
{footer && (
|
||||
<div
|
||||
className={classNames(styles.footer, {
|
||||
[styles.footerMargin]: !children,
|
||||
})}
|
||||
>
|
||||
{footer}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
loading = false,
|
||||
contentHeight,
|
||||
title,
|
||||
avatar,
|
||||
action,
|
||||
total,
|
||||
footer,
|
||||
children,
|
||||
...rest
|
||||
} = this.props;
|
||||
return (
|
||||
<Card loading={loading} bodyStyle={{ padding: '20px 24px 8px 24px' }} {...rest}>
|
||||
{this.renderConnet()}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChartCard;
|
75
admin-web/src/components/Charts/ChartCard/index.less
Normal file
75
admin-web/src/components/Charts/ChartCard/index.less
Normal file
@ -0,0 +1,75 @@
|
||||
@import '~antd/lib/style/themes/default.less';
|
||||
|
||||
.chartCard {
|
||||
position: relative;
|
||||
.chartTop {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.chartTopMargin {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.chartTopHasMargin {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.metaWrap {
|
||||
float: left;
|
||||
}
|
||||
.avatar {
|
||||
position: relative;
|
||||
top: 4px;
|
||||
float: left;
|
||||
margin-right: 20px;
|
||||
img {
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
.meta {
|
||||
height: 22px;
|
||||
color: @text-color-secondary;
|
||||
font-size: @font-size-base;
|
||||
line-height: 22px;
|
||||
}
|
||||
.action {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 0;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
.total {
|
||||
height: 38px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 0;
|
||||
overflow: hidden;
|
||||
color: @heading-color;
|
||||
font-size: 30px;
|
||||
line-height: 38px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
}
|
||||
.content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.contentFixed {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 8px;
|
||||
padding-top: 9px;
|
||||
border-top: 1px solid @border-color-split;
|
||||
& > * {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
.footerMargin {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
8
admin-web/src/components/Charts/Field/index.d.ts
vendored
Normal file
8
admin-web/src/components/Charts/Field/index.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import * as React from 'react';
|
||||
export interface IFieldProps {
|
||||
label: React.ReactNode;
|
||||
value: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default class Field extends React.Component<IFieldProps, any> {}
|
12
admin-web/src/components/Charts/Field/index.js
Normal file
12
admin-web/src/components/Charts/Field/index.js
Normal file
@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
import styles from './index.less';
|
||||
|
||||
const Field = ({ label, value, ...rest }) => (
|
||||
<div className={styles.field} {...rest}>
|
||||
<span className={styles.label}>{label}</span>
|
||||
<span className={styles.number}>{value}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Field;
|
17
admin-web/src/components/Charts/Field/index.less
Normal file
17
admin-web/src/components/Charts/Field/index.less
Normal file
@ -0,0 +1,17 @@
|
||||
@import '~antd/lib/style/themes/default.less';
|
||||
|
||||
.field {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
.label,
|
||||
.number {
|
||||
font-size: @font-size-base;
|
||||
line-height: 22px;
|
||||
}
|
||||
.number {
|
||||
margin-left: 8px;
|
||||
color: @heading-color;
|
||||
}
|
||||
}
|
11
admin-web/src/components/Charts/Gauge/index.d.ts
vendored
Normal file
11
admin-web/src/components/Charts/Gauge/index.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
import * as React from 'react';
|
||||
export interface IGaugeProps {
|
||||
title: React.ReactNode;
|
||||
color?: string;
|
||||
height: number;
|
||||
bgColor?: number;
|
||||
percent: number;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default class Gauge extends React.Component<IGaugeProps, any> {}
|
167
admin-web/src/components/Charts/Gauge/index.js
Normal file
167
admin-web/src/components/Charts/Gauge/index.js
Normal file
@ -0,0 +1,167 @@
|
||||
import React from 'react';
|
||||
import { Chart, Geom, Axis, Coord, Guide, Shape } from 'bizcharts';
|
||||
import autoHeight from '../autoHeight';
|
||||
|
||||
const { Arc, Html, Line } = Guide;
|
||||
|
||||
const defaultFormatter = val => {
|
||||
switch (val) {
|
||||
case '2':
|
||||
return '差';
|
||||
case '4':
|
||||
return '中';
|
||||
case '6':
|
||||
return '良';
|
||||
case '8':
|
||||
return '优';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
Shape.registerShape('point', 'pointer', {
|
||||
drawShape(cfg, group) {
|
||||
let point = cfg.points[0];
|
||||
point = this.parsePoint(point);
|
||||
const center = this.parsePoint({
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
group.addShape('line', {
|
||||
attrs: {
|
||||
x1: center.x,
|
||||
y1: center.y,
|
||||
x2: point.x,
|
||||
y2: point.y,
|
||||
stroke: cfg.color,
|
||||
lineWidth: 2,
|
||||
lineCap: 'round',
|
||||
},
|
||||
});
|
||||
return group.addShape('circle', {
|
||||
attrs: {
|
||||
x: center.x,
|
||||
y: center.y,
|
||||
r: 6,
|
||||
stroke: cfg.color,
|
||||
lineWidth: 3,
|
||||
fill: '#fff',
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@autoHeight()
|
||||
class Gauge extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
height,
|
||||
percent,
|
||||
forceFit = true,
|
||||
formatter = defaultFormatter,
|
||||
color = '#2F9CFF',
|
||||
bgColor = '#F0F2F5',
|
||||
} = this.props;
|
||||
const cols = {
|
||||
value: {
|
||||
type: 'linear',
|
||||
min: 0,
|
||||
max: 10,
|
||||
tickCount: 6,
|
||||
nice: true,
|
||||
},
|
||||
};
|
||||
const data = [{ value: percent / 10 }];
|
||||
return (
|
||||
<Chart height={height} data={data} scale={cols} padding={[-16, 0, 16, 0]} forceFit={forceFit}>
|
||||
<Coord type="polar" startAngle={-1.25 * Math.PI} endAngle={0.25 * Math.PI} radius={0.8} />
|
||||
<Axis name="1" line={null} />
|
||||
<Axis
|
||||
line={null}
|
||||
tickLine={null}
|
||||
subTickLine={null}
|
||||
name="value"
|
||||
zIndex={2}
|
||||
gird={null}
|
||||
label={{
|
||||
offset: -12,
|
||||
formatter,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
fill: 'rgba(0, 0, 0, 0.65)',
|
||||
textAlign: 'center',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Guide>
|
||||
<Line
|
||||
start={[3, 0.905]}
|
||||
end={[3, 0.85]}
|
||||
lineStyle={{
|
||||
stroke: color,
|
||||
lineDash: null,
|
||||
lineWidth: 2,
|
||||
}}
|
||||
/>
|
||||
<Line
|
||||
start={[5, 0.905]}
|
||||
end={[5, 0.85]}
|
||||
lineStyle={{
|
||||
stroke: color,
|
||||
lineDash: null,
|
||||
lineWidth: 3,
|
||||
}}
|
||||
/>
|
||||
<Line
|
||||
start={[7, 0.905]}
|
||||
end={[7, 0.85]}
|
||||
lineStyle={{
|
||||
stroke: color,
|
||||
lineDash: null,
|
||||
lineWidth: 3,
|
||||
}}
|
||||
/>
|
||||
<Arc
|
||||
zIndex={0}
|
||||
start={[0, 0.965]}
|
||||
end={[10, 0.965]}
|
||||
style={{
|
||||
stroke: bgColor,
|
||||
lineWidth: 10,
|
||||
}}
|
||||
/>
|
||||
<Arc
|
||||
zIndex={1}
|
||||
start={[0, 0.965]}
|
||||
end={[data[0].value, 0.965]}
|
||||
style={{
|
||||
stroke: color,
|
||||
lineWidth: 10,
|
||||
}}
|
||||
/>
|
||||
<Html
|
||||
position={['50%', '95%']}
|
||||
html={() => `
|
||||
<div style="width: 300px;text-align: center;font-size: 12px!important;">
|
||||
<p style="font-size: 14px; color: rgba(0,0,0,0.43);margin: 0;">${title}</p>
|
||||
<p style="font-size: 24px;color: rgba(0,0,0,0.85);margin: 0;">
|
||||
${data[0].value * 10}%
|
||||
</p>
|
||||
</div>`}
|
||||
/>
|
||||
</Guide>
|
||||
<Geom
|
||||
line={false}
|
||||
type="point"
|
||||
position="value*1"
|
||||
shape="pointer"
|
||||
color={color}
|
||||
active={false}
|
||||
/>
|
||||
</Chart>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Gauge;
|
29
admin-web/src/components/Charts/MiniArea/index.d.ts
vendored
Normal file
29
admin-web/src/components/Charts/MiniArea/index.d.ts
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
import * as React from 'react';
|
||||
|
||||
// g2已经更新到3.0
|
||||
// 不带的写了
|
||||
|
||||
export interface IAxis {
|
||||
title: any;
|
||||
line: any;
|
||||
gridAlign: any;
|
||||
labels: any;
|
||||
tickLine: any;
|
||||
grid: any;
|
||||
}
|
||||
|
||||
export interface IMiniAreaProps {
|
||||
color?: string;
|
||||
height: number;
|
||||
borderColor?: string;
|
||||
line?: boolean;
|
||||
animate?: boolean;
|
||||
xAxis?: IAxis;
|
||||
yAxis?: IAxis;
|
||||
data: Array<{
|
||||
x: number | string;
|
||||
y: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
export default class MiniArea extends React.Component<IMiniAreaProps, any> {}
|
108
admin-web/src/components/Charts/MiniArea/index.js
Normal file
108
admin-web/src/components/Charts/MiniArea/index.js
Normal file
@ -0,0 +1,108 @@
|
||||
import React from 'react';
|
||||
import { Chart, Axis, Tooltip, Geom } from 'bizcharts';
|
||||
import autoHeight from '../autoHeight';
|
||||
import styles from '../index.less';
|
||||
|
||||
@autoHeight()
|
||||
class MiniArea extends React.PureComponent {
|
||||
render() {
|
||||
const {
|
||||
height,
|
||||
data = [],
|
||||
forceFit = true,
|
||||
color = 'rgba(24, 144, 255, 0.2)',
|
||||
borderColor = '#1089ff',
|
||||
scale = {},
|
||||
borderWidth = 2,
|
||||
line,
|
||||
xAxis,
|
||||
yAxis,
|
||||
animate = true,
|
||||
} = this.props;
|
||||
|
||||
const padding = [36, 5, 30, 5];
|
||||
|
||||
const scaleProps = {
|
||||
x: {
|
||||
type: 'cat',
|
||||
range: [0, 1],
|
||||
...scale.x,
|
||||
},
|
||||
y: {
|
||||
min: 0,
|
||||
...scale.y,
|
||||
},
|
||||
};
|
||||
|
||||
const tooltip = [
|
||||
'x*y',
|
||||
(x, y) => ({
|
||||
name: x,
|
||||
value: y,
|
||||
}),
|
||||
];
|
||||
|
||||
const chartHeight = height + 54;
|
||||
|
||||
return (
|
||||
<div className={styles.miniChart} style={{ height }}>
|
||||
<div className={styles.chartContent}>
|
||||
{height > 0 && (
|
||||
<Chart
|
||||
animate={animate}
|
||||
scale={scaleProps}
|
||||
height={chartHeight}
|
||||
forceFit={forceFit}
|
||||
data={data}
|
||||
padding={padding}
|
||||
>
|
||||
<Axis
|
||||
key="axis-x"
|
||||
name="x"
|
||||
label={false}
|
||||
line={false}
|
||||
tickLine={false}
|
||||
grid={false}
|
||||
{...xAxis}
|
||||
/>
|
||||
<Axis
|
||||
key="axis-y"
|
||||
name="y"
|
||||
label={false}
|
||||
line={false}
|
||||
tickLine={false}
|
||||
grid={false}
|
||||
{...yAxis}
|
||||
/>
|
||||
<Tooltip showTitle={false} crosshairs={false} />
|
||||
<Geom
|
||||
type="area"
|
||||
position="x*y"
|
||||
color={color}
|
||||
tooltip={tooltip}
|
||||
shape="smooth"
|
||||
style={{
|
||||
fillOpacity: 1,
|
||||
}}
|
||||
/>
|
||||
{line ? (
|
||||
<Geom
|
||||
type="line"
|
||||
position="x*y"
|
||||
shape="smooth"
|
||||
color={borderColor}
|
||||
size={borderWidth}
|
||||
tooltip={false}
|
||||
/>
|
||||
) : (
|
||||
<span style={{ display: 'none' }} />
|
||||
)}
|
||||
</Chart>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MiniArea;
|
12
admin-web/src/components/Charts/MiniBar/index.d.ts
vendored
Normal file
12
admin-web/src/components/Charts/MiniBar/index.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
import * as React from 'react';
|
||||
export interface IMiniBarProps {
|
||||
color?: string;
|
||||
height: number;
|
||||
data: Array<{
|
||||
x: number | string;
|
||||
y: number;
|
||||
}>;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default class MiniBar extends React.Component<IMiniBarProps, any> {}
|
51
admin-web/src/components/Charts/MiniBar/index.js
Normal file
51
admin-web/src/components/Charts/MiniBar/index.js
Normal file
@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import { Chart, Tooltip, Geom } from 'bizcharts';
|
||||
import autoHeight from '../autoHeight';
|
||||
import styles from '../index.less';
|
||||
|
||||
@autoHeight()
|
||||
class MiniBar extends React.Component {
|
||||
render() {
|
||||
const { height, forceFit = true, color = '#1890FF', data = [] } = this.props;
|
||||
|
||||
const scale = {
|
||||
x: {
|
||||
type: 'cat',
|
||||
},
|
||||
y: {
|
||||
min: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const padding = [36, 5, 30, 5];
|
||||
|
||||
const tooltip = [
|
||||
'x*y',
|
||||
(x, y) => ({
|
||||
name: x,
|
||||
value: y,
|
||||
}),
|
||||
];
|
||||
|
||||
// for tooltip not to be hide
|
||||
const chartHeight = height + 54;
|
||||
|
||||
return (
|
||||
<div className={styles.miniChart} style={{ height }}>
|
||||
<div className={styles.chartContent}>
|
||||
<Chart
|
||||
scale={scale}
|
||||
height={chartHeight}
|
||||
forceFit={forceFit}
|
||||
data={data}
|
||||
padding={padding}
|
||||
>
|
||||
<Tooltip showTitle={false} crosshairs={false} />
|
||||
<Geom type="interval" position="x*y" color={color} tooltip={tooltip} />
|
||||
</Chart>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default MiniBar;
|
10
admin-web/src/components/Charts/MiniProgress/index.d.ts
vendored
Normal file
10
admin-web/src/components/Charts/MiniProgress/index.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
import * as React from 'react';
|
||||
export interface IMiniProgressProps {
|
||||
target: number;
|
||||
color?: string;
|
||||
strokeWidth?: number;
|
||||
percent?: number;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default class MiniProgress extends React.Component<IMiniProgressProps, any> {}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user