├── .circleci └── config.yml ├── .dockerignore ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .firebaserc ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .stylelintrc.json ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── Dockerfile.dev ├── LICENSE ├── README.md ├── README.ru-RU.md ├── README.zh-CN.md ├── appveyor.yml ├── config ├── config.js ├── plugin.config.js └── router.config.js ├── docker ├── docker-compose.dev.yml ├── docker-compose.yml └── nginx.conf ├── firebase.json ├── functions ├── index.js ├── matchMock.js └── package.json ├── jest.config.js ├── jsconfig.json ├── mock ├── api.js ├── blog.js ├── chart.js ├── geographic.js ├── geographic │ ├── city.json │ └── province.json ├── notices.js ├── profile.js ├── rule.js └── user.js ├── package.json ├── public └── favicon.png ├── scripts └── generateMock.js ├── src ├── assets │ ├── all.png │ ├── logo.svg │ └── userLogo.jpeg ├── components │ ├── Authorized │ │ ├── Authorized.js │ │ ├── AuthorizedRoute.js │ │ ├── CheckPermissions.js │ │ ├── CheckPermissions.test.js │ │ ├── PromiseRender.js │ │ ├── Secured.js │ │ ├── demo │ │ │ ├── AuthorizedArray.md │ │ │ ├── AuthorizedFunction.md │ │ │ ├── basic.md │ │ │ └── secured.md │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.md │ │ └── renderAuthorize.js │ ├── Charts │ │ ├── Bar │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── ChartCard │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── Field │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── Gauge │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── MiniArea │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── MiniBar │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── MiniProgress │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── Pie │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── Radar │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── TagCloud │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── TimelineChart │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── WaterWave │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── autoHeight.js │ │ ├── bizcharts.d.ts │ │ ├── bizcharts.js │ │ ├── demo │ │ │ ├── bar.md │ │ │ ├── chart-card.md │ │ │ ├── gauge.md │ │ │ ├── mini-area.md │ │ │ ├── mini-bar.md │ │ │ ├── mini-pie.md │ │ │ ├── mini-progress.md │ │ │ ├── mix.md │ │ │ ├── pie.md │ │ │ ├── radar.md │ │ │ ├── tag-cloud.md │ │ │ ├── timeline-chart.md │ │ │ └── waterwave.md │ │ ├── g2.js │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── Exception │ │ ├── demo │ │ │ ├── 403.md │ │ │ ├── 404.md │ │ │ └── 500.md │ │ ├── index.d.ts │ │ ├── index.en-US.md │ │ ├── index.js │ │ ├── index.less │ │ ├── index.zh-CN.md │ │ └── typeConfig.js │ ├── FooterToolbar │ │ ├── demo │ │ │ └── basic.md │ │ ├── index.d.ts │ │ ├── index.en-US.md │ │ ├── index.js │ │ ├── index.less │ │ └── index.zh-CN.md │ ├── GlobalFooter │ │ ├── demo │ │ │ └── basic.md │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── GlobalHeader │ │ ├── RightContent.js │ │ ├── index.js │ │ └── index.less │ ├── Login │ │ ├── LoginItem.js │ │ ├── LoginSubmit.js │ │ ├── LoginTab.js │ │ ├── demo │ │ │ └── basic.md │ │ ├── index.d.ts │ │ ├── index.en-US.md │ │ ├── index.js │ │ ├── index.less │ │ ├── index.zh-CN.md │ │ ├── loginContext.js │ │ └── map.js │ ├── NoticeIcon │ │ ├── NoticeIconTab.d.ts │ │ ├── NoticeList.js │ │ ├── NoticeList.less │ │ ├── demo │ │ │ ├── basic.md │ │ │ └── popover.md │ │ ├── index.d.ts │ │ ├── index.en-US.md │ │ ├── index.js │ │ ├── index.less │ │ └── index.zh-CN.md │ ├── PageHeader │ │ ├── breadcrumb.d.ts │ │ ├── breadcrumb.js │ │ ├── demo │ │ │ ├── image.md │ │ │ ├── simple.md │ │ │ ├── standard.md │ │ │ └── structure.md │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.less │ │ ├── index.md │ │ └── index.test.js │ ├── PageHeaderWrapper │ │ ├── GridContent.js │ │ ├── GridContent.less │ │ ├── index.js │ │ └── index.less │ ├── PageLoading │ │ └── index.js │ ├── Result │ │ ├── demo │ │ │ ├── classic.md │ │ │ ├── error.md │ │ │ └── structure.md │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── SelectLang │ │ ├── index.js │ │ └── index.less │ ├── SettingDrawer │ │ ├── BlockChecbox.js │ │ ├── ThemeColor.js │ │ ├── ThemeColor.less │ │ ├── index.js │ │ └── index.less │ ├── SiderMenu │ │ ├── BaseMenu.js │ │ ├── SiderMenu.js │ │ ├── SiderMenu.test.js │ │ ├── index.js │ │ └── index.less │ ├── StandardTable │ │ ├── index.js │ │ └── index.less │ ├── TopNavHeader │ │ ├── index.js │ │ └── index.less │ └── _utils │ │ ├── pathTools.js │ │ └── pathTools.test.js ├── defaultSettings.js ├── e2e │ ├── home.e2e.js │ └── login.e2e.js ├── global.less ├── layouts │ ├── BasicLayout.js │ ├── BlankLayout.js │ ├── Footer.js │ ├── Header.js │ ├── Header.less │ ├── MenuContext.js │ ├── UserLayout.js │ └── UserLayout.less ├── locales │ ├── en-US.js │ ├── pt-BR.js │ ├── zh-CN.js │ └── zh-TW.js ├── models │ ├── article.js │ ├── category.js │ ├── global.js │ ├── link.js │ ├── list.js │ ├── login.js │ ├── message.js │ ├── otherUser.js │ ├── project.js │ ├── setting.js │ ├── tag.js │ ├── timeAxis.js │ └── user.js ├── pages │ ├── 404.js │ ├── Account │ │ └── Settings │ │ │ ├── BaseView.js │ │ │ ├── BaseView.less │ │ │ ├── Info.js │ │ │ ├── Info.less │ │ │ └── PersonalLinkView.js │ ├── Article │ │ ├── ArticleComponent.js │ │ ├── ArticleCreate.js │ │ ├── CommentsComponent.js │ │ ├── List.js │ │ └── style.less │ ├── Authorized.js │ ├── Category │ │ ├── CategoryComponent.js │ │ └── List.js │ ├── Dashboard │ │ ├── Workplace.js │ │ ├── Workplace.less │ │ └── models │ │ │ └── activities.js │ ├── Exception │ │ ├── 403.js │ │ ├── 404.js │ │ ├── 500.js │ │ ├── TriggerException.js │ │ ├── models │ │ │ └── error.js │ │ └── style.less │ ├── Link │ │ ├── LinkComponent.js │ │ └── List.js │ ├── Message │ │ ├── List.js │ │ └── MessageComponent.js │ ├── OtherUser │ │ ├── List.js │ │ ├── OtherUserComponent.js │ │ └── style.less │ ├── Project │ │ ├── List.js │ │ └── ProjectComponent.js │ ├── Tag │ │ ├── List.js │ │ └── TagComponent.js │ ├── TimeAxis │ │ ├── List.js │ │ └── TimeAxisComponent.js │ ├── User │ │ ├── Login.js │ │ ├── Login.less │ │ ├── Register.js │ │ ├── Register.less │ │ ├── RegisterResult.js │ │ ├── RegisterResult.less │ │ └── models │ │ │ └── register.js │ └── document.ejs ├── services │ ├── api.js │ ├── error.js │ ├── geographic.js │ └── user.js └── utils │ ├── Authorized.js │ ├── Yuan.js │ ├── authority.js │ ├── authority.test.js │ ├── domain.js │ ├── request.js │ ├── utils.js │ └── utils.less └── tests ├── fix_puppeteer.sh └── run-tests.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:8.11.4 6 | steps: 7 | - checkout 8 | - run: npm install 9 | - run: npm run build 10 | test: 11 | docker: 12 | - image: circleci/node:8.11.4 13 | steps: 14 | - checkout 15 | - run: sh ./tests/fix_puppeteer.sh 16 | - run: npm install 17 | - run: 18 | command : npm run test:all 19 | no_output_timeout : 30m 20 | workflows: 21 | version: 2 22 | build_and_test: 23 | jobs: 24 | - build 25 | - test -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | **/node_modules 5 | # roadhog-api-doc ignore 6 | /src/utils/request-temp.js 7 | _roadhog-api-doc 8 | 9 | # production 10 | /dist 11 | /.vscode 12 | 13 | # misc 14 | .DS_Store 15 | npm-debug.log* 16 | yarn-error.log 17 | 18 | /coverage 19 | .idea 20 | yarn.lock 21 | package-lock.json 22 | *bak 23 | .vscode 24 | 25 | # visual studio code 26 | .history 27 | *.log 28 | 29 | functions/mock 30 | .temp/** 31 | 32 | # umi 33 | .umi 34 | .umi-production 35 | 36 | # screenshot 37 | screenshot 38 | .firebase -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /functions/mock 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'babel-eslint', 3 | extends: ['airbnb', 'prettier', 'plugin:compat/recommended'], 4 | env: { 5 | browser: true, 6 | node: true, 7 | es6: true, 8 | mocha: true, 9 | jest: true, 10 | jasmine: true, 11 | }, 12 | globals: { 13 | APP_TYPE: true, 14 | }, 15 | rules: { 16 | 'react/jsx-filename-extension': [1, { extensions: ['.js'] }], 17 | 'react/jsx-wrap-multilines': 0, 18 | 'react/prop-types': 0, 19 | 'react/forbid-prop-types': 0, 20 | 'react/jsx-one-expression-per-line': 0, 21 | 'import/no-unresolved': [2, { ignore: ['^@/', '^umi/'] }], 22 | 'import/no-extraneous-dependencies': [2, { optionalDependencies: true }], 23 | 'jsx-a11y/no-noninteractive-element-interactions': 0, 24 | 'jsx-a11y/click-events-have-key-events': 0, 25 | 'jsx-a11y/no-static-element-interactions': 0, 26 | 'jsx-a11y/anchor-is-valid': 0, 27 | 'linebreak-style': 0, 28 | }, 29 | settings: { 30 | polyfills: ['fetch', 'promises', 'url'], 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "antd-pro" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | **/node_modules 5 | /dist 6 | # roadhog-api-doc ignore 7 | /src/utils/request-temp.js 8 | _roadhog-api-doc 9 | 10 | # production 11 | 12 | /.vscode 13 | 14 | # misc 15 | .DS_Store 16 | npm-debug.log* 17 | yarn-error.log 18 | 19 | /coverage 20 | .idea 21 | yarn.lock 22 | package-lock.json 23 | *bak 24 | .vscode 25 | 26 | # visual studio code 27 | .history 28 | *.log 29 | 30 | functions/mock 31 | .temp/** 32 | 33 | # umi 34 | .umi 35 | .umi-production 36 | 37 | # screenshot 38 | screenshot 39 | .firebase -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg 3 | **/*.ejs 4 | **/*.html 5 | package.json 6 | .umi 7 | .umi-production 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "printWidth": 100, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard", "stylelint-config-prettier"], 3 | "rules": { 4 | "declaration-empty-line-before": null, 5 | "no-descending-specificity": null, 6 | "selector-pseudo-class-no-unknown": null, 7 | "selector-pseudo-element-colon-notation": null 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | WORKDIR /usr/src/app/ 4 | 5 | COPY package.json ./ 6 | RUN npm install --silent --no-cache 7 | 8 | COPY ./ ./ 9 | 10 | RUN apt-get update 11 | RUN apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \ 12 | libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \ 13 | libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \ 14 | libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \ 15 | ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget 16 | 17 | CMD ["npm", "run", "build"] 18 | -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | WORKDIR /usr/src/app/ 4 | 5 | COPY package.json ./ 6 | RUN npm install --silent --no-cache 7 | 8 | COPY ./ ./ 9 | 10 | 11 | CMD ["npm", "run", "start"] 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Alipay.inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Test against the latest version of this Node.js version 2 | environment: 3 | nodejs_version: "8" 4 | 5 | # this is how to allow failing jobs in the matrix 6 | matrix: 7 | fast_finish: true # set this flag to immediately finish build once one of the jobs fails. 8 | 9 | # Install scripts. (runs after repo cloning) 10 | install: 11 | # Get the latest stable version of Node.js or io.js 12 | - ps: Install-Product node $env:nodejs_version 13 | # install modules 14 | - npm install 15 | # Output useful info for debugging. 16 | - node --version 17 | - npm --version 18 | 19 | # Post-install test scripts. 20 | test_script: 21 | - npm run lint 22 | - npm run test:all 23 | - npm run build 24 | 25 | # Don't actually build. 26 | build: off 27 | -------------------------------------------------------------------------------- /config/plugin.config.js: -------------------------------------------------------------------------------- 1 | // Change theme plugin 2 | 3 | import MergeLessPlugin from 'antd-pro-merge-less'; 4 | import AntDesignThemePlugin from 'antd-pro-theme-webpack-plugin'; 5 | import path from 'path'; 6 | 7 | export default config => { 8 | // 将所有 less 合并为一个供 themePlugin使用 9 | const outFile = path.join(__dirname, '../.temp/ant-design-pro.less'); 10 | const stylesDir = path.join(__dirname, '../src/'); 11 | 12 | config.plugin('merge-less').use(MergeLessPlugin, [ 13 | { 14 | stylesDir, 15 | outFile, 16 | }, 17 | ]); 18 | 19 | config.plugin('ant-design-theme').use(AntDesignThemePlugin, [ 20 | { 21 | antDir: path.join(__dirname, '../node_modules/antd'), 22 | stylesDir, 23 | varFile: path.join(__dirname, '../node_modules/antd/lib/style/themes/default.less'), 24 | mainLessFile: outFile, // themeVariables: ['@primary-color'], 25 | indexFileName: 'index.html', 26 | }, 27 | ]); 28 | }; 29 | -------------------------------------------------------------------------------- /docker/docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | 3 | services: 4 | ant-design-pro_dev: 5 | ports: 6 | - 8000:8000 7 | build: 8 | context: ../ 9 | dockerfile: Dockerfile.dev 10 | container_name: "ant-design-pro_dev" 11 | volumes: 12 | - ../src:/usr/src/app/src 13 | - ../config:/usr/src/app/config 14 | - ../mock:/usr/src/app/mock 15 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | 3 | services: 4 | ant-design-pro_build: 5 | build: ../ 6 | container_name: "ant-design-pro_build" 7 | volumes: 8 | - dist:/usr/src/app/dist 9 | 10 | ant-design-pro_web: 11 | image: nginx 12 | ports: 13 | - 80:80 14 | container_name: "ant-design-pro_web" 15 | restart: unless-stopped 16 | volumes: 17 | - dist:/usr/share/nginx/html:ro 18 | - ./nginx.conf:/etc/nginx/conf.d/default.conf 19 | 20 | volumes: 21 | dist: 22 | -------------------------------------------------------------------------------- /docker/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | # gzip config 4 | gzip on; 5 | gzip_min_length 1k; 6 | gzip_comp_level 9 7 | gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; 8 | gzip_vary on; 9 | gzip_disable "MSIE [1-6]\."; 10 | 11 | root /usr/share/nginx/html; 12 | 13 | location / { 14 | try_files $uri $uri/ /index.html; 15 | } 16 | location /api { 17 | proxy_pass https://preview.pro.ant.design; 18 | proxy_set_header X-Forwarded-Proto $scheme; 19 | proxy_set_header Host $http_host; 20 | proxy_set_header X-Real-IP $remote_addr; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "dist", 4 | "rewrites": [ 5 | { "source": "/api/**", "function": "api" }, 6 | { 7 | "source": "**", 8 | "destination": "/index.html" 9 | } 10 | ], 11 | "ignore": ["firebase.json", "**/.*", "**/node_modules/**"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /functions/index.js: -------------------------------------------------------------------------------- 1 | // [START functionsimport] 2 | const functions = require('firebase-functions'); 3 | const express = require('express'); 4 | const matchMock = require('./matchMock'); 5 | const app = express(); 6 | 7 | app.use(matchMock); 8 | 9 | exports.api = functions.https.onRequest(app); 10 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "description": "Cloud Functions for Firebase", 4 | "scripts": { 5 | "serve": "npm run mock && firebase serve --only functions", 6 | "shell": "firebase functions:shell", 7 | "start": "npm run shell", 8 | "deploy": "firebase deploy --only functions", 9 | "logs": "firebase functions:log", 10 | "mock": "node ../scripts/generateMock.js" 11 | }, 12 | "dependencies": { 13 | "@babel/runtime": "^7.0.0", 14 | "body-parser": "^1.18.3", 15 | "express": "^4.16.3", 16 | "firebase-admin": "^5.12.1", 17 | "firebase-functions": "^2.0.5", 18 | "mockjs": "^1.0.1-beta3", 19 | "moment": "^2.22.2", 20 | "path-to-regexp": "^2.2.1" 21 | }, 22 | "private": true 23 | } 24 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testURL: 'http://localhost:8000', 3 | }; 4 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "emitDecoratorMetadata": true, 4 | "experimentalDecorators": true, 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /mock/geographic.js: -------------------------------------------------------------------------------- 1 | import city from './geographic/city.json'; 2 | import province from './geographic/province.json'; 3 | 4 | function getProvince(req, res) { 5 | return res.json(province); 6 | } 7 | 8 | function getCity(req, res) { 9 | return res.json(city[req.params.province]); 10 | } 11 | 12 | export default { 13 | 'GET /api/geographic/province': getProvince, 14 | 'GET /api/geographic/city/:province': getCity, 15 | }; 16 | -------------------------------------------------------------------------------- /mock/geographic/province.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "北京市", 4 | "id": "110000" 5 | }, 6 | { 7 | "name": "天津市", 8 | "id": "120000" 9 | }, 10 | { 11 | "name": "河北省", 12 | "id": "130000" 13 | }, 14 | { 15 | "name": "山西省", 16 | "id": "140000" 17 | }, 18 | { 19 | "name": "内蒙古自治区", 20 | "id": "150000" 21 | }, 22 | { 23 | "name": "辽宁省", 24 | "id": "210000" 25 | }, 26 | { 27 | "name": "吉林省", 28 | "id": "220000" 29 | }, 30 | { 31 | "name": "黑龙江省", 32 | "id": "230000" 33 | }, 34 | { 35 | "name": "上海市", 36 | "id": "310000" 37 | }, 38 | { 39 | "name": "江苏省", 40 | "id": "320000" 41 | }, 42 | { 43 | "name": "浙江省", 44 | "id": "330000" 45 | }, 46 | { 47 | "name": "安徽省", 48 | "id": "340000" 49 | }, 50 | { 51 | "name": "福建省", 52 | "id": "350000" 53 | }, 54 | { 55 | "name": "江西省", 56 | "id": "360000" 57 | }, 58 | { 59 | "name": "山东省", 60 | "id": "370000" 61 | }, 62 | { 63 | "name": "河南省", 64 | "id": "410000" 65 | }, 66 | { 67 | "name": "湖北省", 68 | "id": "420000" 69 | }, 70 | { 71 | "name": "湖南省", 72 | "id": "430000" 73 | }, 74 | { 75 | "name": "广东省", 76 | "id": "440000" 77 | }, 78 | { 79 | "name": "广西壮族自治区", 80 | "id": "450000" 81 | }, 82 | { 83 | "name": "海南省", 84 | "id": "460000" 85 | }, 86 | { 87 | "name": "重庆市", 88 | "id": "500000" 89 | }, 90 | { 91 | "name": "四川省", 92 | "id": "510000" 93 | }, 94 | { 95 | "name": "贵州省", 96 | "id": "520000" 97 | }, 98 | { 99 | "name": "云南省", 100 | "id": "530000" 101 | }, 102 | { 103 | "name": "西藏自治区", 104 | "id": "540000" 105 | }, 106 | { 107 | "name": "陕西省", 108 | "id": "610000" 109 | }, 110 | { 111 | "name": "甘肃省", 112 | "id": "620000" 113 | }, 114 | { 115 | "name": "青海省", 116 | "id": "630000" 117 | }, 118 | { 119 | "name": "宁夏回族自治区", 120 | "id": "640000" 121 | }, 122 | { 123 | "name": "新疆维吾尔自治区", 124 | "id": "650000" 125 | }, 126 | { 127 | "name": "台湾省", 128 | "id": "710000" 129 | }, 130 | { 131 | "name": "香港特别行政区", 132 | "id": "810000" 133 | }, 134 | { 135 | "name": "澳门特别行政区", 136 | "id": "820000" 137 | } 138 | ] 139 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biaochenxuying/blog-react-admin/220b0aee82506c4daa2d67228f319ad214d0795e/public/favicon.png -------------------------------------------------------------------------------- /scripts/generateMock.js: -------------------------------------------------------------------------------- 1 | const generateMock = require('merge-umi-mock-data'); 2 | const path = require('path'); 3 | generateMock(path.join(__dirname, '../mock'), path.join(__dirname, '../functions/mock/index.js')); 4 | -------------------------------------------------------------------------------- /src/assets/all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biaochenxuying/blog-react-admin/220b0aee82506c4daa2d67228f319ad214d0795e/src/assets/all.png -------------------------------------------------------------------------------- /src/assets/userLogo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biaochenxuying/blog-react-admin/220b0aee82506c4daa2d67228f319ad214d0795e/src/assets/userLogo.jpeg -------------------------------------------------------------------------------- /src/components/Authorized/Authorized.js: -------------------------------------------------------------------------------- 1 | import CheckPermissions from './CheckPermissions'; 2 | 3 | const Authorized = ({ children, authority, noMatch = null }) => { 4 | const childrenRender = typeof children === 'undefined' ? null : children; 5 | return CheckPermissions(authority, childrenRender, noMatch); 6 | }; 7 | 8 | export default Authorized; 9 | -------------------------------------------------------------------------------- /src/components/Authorized/AuthorizedRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Redirect } from 'react-router-dom'; 3 | import Authorized from './Authorized'; 4 | 5 | // TODO: umi只会返回render和rest 6 | const AuthorizedRoute = ({ component: Component, render, authority, redirectPath, ...rest }) => ( 7 | } />} 10 | > 11 | (Component ? : render(props))} /> 12 | 13 | ); 14 | 15 | export default AuthorizedRoute; 16 | -------------------------------------------------------------------------------- /src/components/Authorized/CheckPermissions.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PromiseRender from './PromiseRender'; 3 | import { CURRENT } from './renderAuthorize'; 4 | 5 | function isPromise(obj) { 6 | return ( 7 | !!obj && 8 | (typeof obj === 'object' || typeof obj === 'function') && 9 | typeof obj.then === 'function' 10 | ); 11 | } 12 | 13 | /** 14 | * 通用权限检查方法 15 | * Common check permissions method 16 | * @param { 权限判定 Permission judgment type string |array | Promise | Function } authority 17 | * @param { 你的权限 Your permission description type:string} currentAuthority 18 | * @param { 通过的组件 Passing components } target 19 | * @param { 未通过的组件 no pass components } Exception 20 | */ 21 | const checkPermissions = (authority, currentAuthority, target, Exception) => { 22 | // 没有判定权限.默认查看所有 23 | // Retirement authority, return target; 24 | if (!authority) { 25 | return target; 26 | } 27 | // 数组处理 28 | if (Array.isArray(authority)) { 29 | if (authority.indexOf(currentAuthority) >= 0) { 30 | return target; 31 | } 32 | if (Array.isArray(currentAuthority)) { 33 | for (let i = 0; i < currentAuthority.length; i += 1) { 34 | const element = currentAuthority[i]; 35 | if (authority.indexOf(element) >= 0) { 36 | return target; 37 | } 38 | } 39 | } 40 | return Exception; 41 | } 42 | 43 | // string 处理 44 | if (typeof authority === 'string') { 45 | if (authority === currentAuthority) { 46 | return target; 47 | } 48 | if (Array.isArray(currentAuthority)) { 49 | for (let i = 0; i < currentAuthority.length; i += 1) { 50 | const element = currentAuthority[i]; 51 | if (authority.indexOf(element) >= 0) { 52 | return target; 53 | } 54 | } 55 | } 56 | return Exception; 57 | } 58 | 59 | // Promise 处理 60 | if (isPromise(authority)) { 61 | return ; 62 | } 63 | 64 | // Function 处理 65 | if (typeof authority === 'function') { 66 | try { 67 | const bool = authority(currentAuthority); 68 | // 函数执行后返回值是 Promise 69 | if (isPromise(bool)) { 70 | return ; 71 | } 72 | if (bool) { 73 | return target; 74 | } 75 | return Exception; 76 | } catch (error) { 77 | throw error; 78 | } 79 | } 80 | throw new Error('unsupported parameters'); 81 | }; 82 | 83 | export { checkPermissions }; 84 | 85 | const check = (authority, target, Exception) => 86 | checkPermissions(authority, CURRENT, target, Exception); 87 | 88 | export default check; 89 | -------------------------------------------------------------------------------- /src/components/Authorized/CheckPermissions.test.js: -------------------------------------------------------------------------------- 1 | import { checkPermissions } from './CheckPermissions'; 2 | 3 | const target = 'ok'; 4 | const error = 'error'; 5 | 6 | describe('test CheckPermissions', () => { 7 | it('Correct string permission authentication', () => { 8 | expect(checkPermissions('user', 'user', target, error)).toEqual('ok'); 9 | }); 10 | it('Correct string permission authentication', () => { 11 | expect(checkPermissions('user', 'NULL', target, error)).toEqual('error'); 12 | }); 13 | it('authority is undefined , return ok', () => { 14 | expect(checkPermissions(null, 'NULL', target, error)).toEqual('ok'); 15 | }); 16 | it('currentAuthority is undefined , return error', () => { 17 | expect(checkPermissions('admin', null, target, error)).toEqual('error'); 18 | }); 19 | it('Wrong string permission authentication', () => { 20 | expect(checkPermissions('admin', 'user', target, error)).toEqual('error'); 21 | }); 22 | it('Correct Array permission authentication', () => { 23 | expect(checkPermissions(['user', 'admin'], 'user', target, error)).toEqual('ok'); 24 | }); 25 | it('Wrong Array permission authentication,currentAuthority error', () => { 26 | expect(checkPermissions(['user', 'admin'], 'user,admin', target, error)).toEqual('error'); 27 | }); 28 | it('Wrong Array permission authentication', () => { 29 | expect(checkPermissions(['user', 'admin'], 'guest', target, error)).toEqual('error'); 30 | }); 31 | it('Wrong Function permission authentication', () => { 32 | expect(checkPermissions(() => false, 'guest', target, error)).toEqual('error'); 33 | }); 34 | it('Correct Function permission authentication', () => { 35 | expect(checkPermissions(() => true, 'guest', target, error)).toEqual('ok'); 36 | }); 37 | it('authority is string, currentAuthority is array, return ok', () => { 38 | expect(checkPermissions('user', ['user'], target, error)).toEqual('ok'); 39 | }); 40 | it('authority is string, currentAuthority is array, return ok', () => { 41 | expect(checkPermissions('user', ['user', 'admin'], target, error)).toEqual('ok'); 42 | }); 43 | it('authority is array, currentAuthority is array, return ok', () => { 44 | expect(checkPermissions(['user', 'admin'], ['user', 'admin'], target, error)).toEqual('ok'); 45 | }); 46 | it('Wrong Function permission authentication', () => { 47 | expect(checkPermissions(() => false, ['user'], target, error)).toEqual('error'); 48 | }); 49 | it('Correct Function permission authentication', () => { 50 | expect(checkPermissions(() => true, ['user'], target, error)).toEqual('ok'); 51 | }); 52 | it('authority is undefined , return ok', () => { 53 | expect(checkPermissions(null, ['user'], target, error)).toEqual('ok'); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /src/components/Authorized/PromiseRender.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Spin } from 'antd'; 3 | 4 | export default class PromiseRender extends React.PureComponent { 5 | state = { 6 | component: null, 7 | }; 8 | 9 | componentDidMount() { 10 | this.setRenderComponent(this.props); 11 | } 12 | 13 | componentDidUpdate(nextProps) { 14 | // new Props enter 15 | this.setRenderComponent(nextProps); 16 | } 17 | 18 | // set render Component : ok or error 19 | setRenderComponent(props) { 20 | const ok = this.checkIsInstantiation(props.ok); 21 | const error = this.checkIsInstantiation(props.error); 22 | props.promise 23 | .then(() => { 24 | this.setState({ 25 | component: ok, 26 | }); 27 | }) 28 | .catch(() => { 29 | this.setState({ 30 | component: error, 31 | }); 32 | }); 33 | } 34 | 35 | // Determine whether the incoming component has been instantiated 36 | // AuthorizedRoute is already instantiated 37 | // Authorized render is already instantiated, children is no instantiated 38 | // Secured is not instantiated 39 | checkIsInstantiation = target => { 40 | if (!React.isValidElement(target)) { 41 | return target; 42 | } 43 | return () => target; 44 | }; 45 | 46 | render() { 47 | const { component: Component } = this.state; 48 | const { ok, error, promise, ...rest } = this.props; 49 | return Component ? ( 50 | 51 | ) : ( 52 |
61 | 62 |
63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/components/Authorized/Secured.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Exception from '../Exception'; 3 | import CheckPermissions from './CheckPermissions'; 4 | /** 5 | * 默认不能访问任何页面 6 | * default is "NULL" 7 | */ 8 | const Exception403 = () => ; 9 | 10 | // Determine whether the incoming component has been instantiated 11 | // AuthorizedRoute is already instantiated 12 | // Authorized render is already instantiated, children is no instantiated 13 | // Secured is not instantiated 14 | const checkIsInstantiation = target => { 15 | if (!React.isValidElement(target)) { 16 | return target; 17 | } 18 | return () => target; 19 | }; 20 | 21 | /** 22 | * 用于判断是否拥有权限访问此view权限 23 | * authority 支持传入 string, function:()=>boolean|Promise 24 | * e.g. 'user' 只有user用户能访问 25 | * e.g. 'user,admin' user和 admin 都能访问 26 | * e.g. ()=>boolean 返回true能访问,返回false不能访问 27 | * e.g. Promise then 能访问 catch不能访问 28 | * e.g. authority support incoming string, function: () => boolean | Promise 29 | * e.g. 'user' only user user can access 30 | * e.g. 'user, admin' user and admin can access 31 | * e.g. () => boolean true to be able to visit, return false can not be accessed 32 | * e.g. Promise then can not access the visit to catch 33 | * @param {string | function | Promise} authority 34 | * @param {ReactNode} error 非必需参数 35 | */ 36 | const authorize = (authority, error) => { 37 | /** 38 | * conversion into a class 39 | * 防止传入字符串时找不到staticContext造成报错 40 | * String parameters can cause staticContext not found error 41 | */ 42 | let classError = false; 43 | if (error) { 44 | classError = () => error; 45 | } 46 | if (!authority) { 47 | throw new Error('authority is required'); 48 | } 49 | return function decideAuthority(target) { 50 | const component = CheckPermissions(authority, target, classError || Exception403); 51 | return checkIsInstantiation(component); 52 | }; 53 | }; 54 | 55 | export default authorize; 56 | -------------------------------------------------------------------------------- /src/components/Authorized/demo/AuthorizedArray.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: 4 | zh-CN: 使用数组作为参数 5 | en-US: Use Array as a parameter 6 | --- 7 | 8 | Use Array as a parameter 9 | 10 | ```jsx 11 | import RenderAuthorized from 'ant-design-pro/lib/Authorized'; 12 | import { Alert } from 'antd'; 13 | 14 | const Authorized = RenderAuthorized('user'); 15 | const noMatch = ; 16 | 17 | ReactDOM.render( 18 | 19 | 20 | , 21 | mountNode, 22 | ); 23 | ``` 24 | -------------------------------------------------------------------------------- /src/components/Authorized/demo/AuthorizedFunction.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | title: 4 | zh-CN: 使用方法作为参数 5 | en-US: Use function as a parameter 6 | --- 7 | 8 | Use Function as a parameter 9 | 10 | ```jsx 11 | import RenderAuthorized from 'ant-design-pro/lib/Authorized'; 12 | import { Alert } from 'antd'; 13 | 14 | const Authorized = RenderAuthorized('user'); 15 | const noMatch = ; 16 | 17 | const havePermission = () => { 18 | return false; 19 | }; 20 | 21 | ReactDOM.render( 22 | 23 | 28 | , 29 | mountNode, 30 | ); 31 | ``` 32 | -------------------------------------------------------------------------------- /src/components/Authorized/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 4 | zh-CN: 基本使用 5 | en-US: Basic use 6 | --- 7 | 8 | Basic use 9 | 10 | ```jsx 11 | import RenderAuthorized from 'ant-design-pro/lib/Authorized'; 12 | import { Alert } from 'antd'; 13 | 14 | const Authorized = RenderAuthorized('user'); 15 | const noMatch = ; 16 | 17 | ReactDOM.render( 18 |
19 | 20 | 21 | 22 |
, 23 | mountNode, 24 | ); 25 | ``` 26 | -------------------------------------------------------------------------------- /src/components/Authorized/demo/secured.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 3 3 | title: 4 | zh-CN: 注解基本使用 5 | en-US: Basic use secured 6 | --- 7 | 8 | secured demo used 9 | 10 | ```jsx 11 | import RenderAuthorized from 'ant-design-pro/lib/Authorized'; 12 | import { Alert } from 'antd'; 13 | 14 | const { Secured } = RenderAuthorized('user'); 15 | 16 | @Secured('admin') 17 | class TestSecuredString extends React.Component { 18 | render() { 19 | ; 20 | } 21 | } 22 | ReactDOM.render( 23 |
24 | 25 |
, 26 | mountNode, 27 | ); 28 | ``` 29 | -------------------------------------------------------------------------------- /src/components/Authorized/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RouteProps } from 'react-router'; 3 | 4 | type authorityFN = (currentAuthority?: string) => boolean; 5 | 6 | type authority = string | Array | authorityFN | Promise; 7 | 8 | export type IReactComponent

= 9 | | React.StatelessComponent

10 | | React.ComponentClass

11 | | React.ClassicComponentClass

; 12 | 13 | interface Secured { 14 | (authority: authority, error?: React.ReactNode): (target: T) => T; 15 | } 16 | 17 | export interface AuthorizedRouteProps extends RouteProps { 18 | authority: authority; 19 | } 20 | export class AuthorizedRoute extends React.Component {} 21 | 22 | interface check { 23 | ( 24 | authority: authority, 25 | target: T, 26 | Exception: S 27 | ): T | S; 28 | } 29 | 30 | export interface AuthorizedProps { 31 | authority: authority; 32 | noMatch?: React.ReactNode; 33 | } 34 | 35 | export class Authorized extends React.Component { 36 | static Secured: Secured; 37 | static AuthorizedRoute: typeof AuthorizedRoute; 38 | static check: check; 39 | } 40 | 41 | declare function renderAuthorize(currentAuthority: string): typeof Authorized; 42 | 43 | export default renderAuthorize; 44 | -------------------------------------------------------------------------------- /src/components/Authorized/index.js: -------------------------------------------------------------------------------- 1 | import Authorized from './Authorized'; 2 | import AuthorizedRoute from './AuthorizedRoute'; 3 | import Secured from './Secured'; 4 | import check from './CheckPermissions'; 5 | import renderAuthorize from './renderAuthorize'; 6 | 7 | Authorized.Secured = Secured; 8 | Authorized.AuthorizedRoute = AuthorizedRoute; 9 | Authorized.check = check; 10 | 11 | export default renderAuthorize(Authorized); 12 | -------------------------------------------------------------------------------- /src/components/Authorized/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: Authorized 4 | zh-CN: Authorized 5 | subtitle: 权限 6 | cols: 1 7 | order: 15 8 | --- 9 | 10 | 权限组件,通过比对现有权限与准入权限,决定相关元素的展示。 11 | 12 | ## API 13 | 14 | ### RenderAuthorized 15 | 16 | `RenderAuthorized: (currentAuthority: string | () => string) => Authorized` 17 | 18 | 权限组件默认 export RenderAuthorized 函数,它接收当前权限作为参数,返回一个权限对象,该对象提供以下几种使用方式。 19 | 20 | 21 | ### Authorized 22 | 23 | 最基础的权限控制。 24 | 25 | | 参数 | 说明 | 类型 | 默认值 | 26 | |----------|------------------------------------------|-------------|-------| 27 | | children | 正常渲染的元素,权限判断通过时展示 | ReactNode | - | 28 | | authority | 准入权限/权限判断 | `string | array | Promise | (currentAuthority) => boolean | Promise` | - | 29 | | noMatch | 权限异常渲染元素,权限判断不通过时展示 | ReactNode | - | 30 | 31 | ### Authorized.AuthorizedRoute 32 | 33 | | 参数 | 说明 | 类型 | 默认值 | 34 | |----------|------------------------------------------|-------------|-------| 35 | | authority | 准入权限/权限判断 | `string | array | Promise | (currentAuthority) => boolean | Promise` | - | 36 | | redirectPath | 权限异常时重定向的页面路由 | string | - | 37 | 38 | 其余参数与 `Route` 相同。 39 | 40 | ### Authorized.Secured 41 | 42 | 注解方式,`@Authorized.Secured(authority, error)` 43 | 44 | | 参数 | 说明 | 类型 | 默认值 | 45 | |----------|------------------------------------------|-------------|-------| 46 | | authority | 准入权限/权限判断 | `string | Promise | (currentAuthority) => boolean | Promise` | - | 47 | | error | 权限异常时渲染元素 | ReactNode | | 48 | 49 | ### Authorized.check 50 | 51 | 函数形式的 Authorized,用于某些不能被 HOC 包裹的组件。 `Authorized.check(authority, target, Exception)` 52 | 注意:传入一个 Promise 时,无论正确还是错误返回的都是一个 ReactClass。 53 | 54 | | 参数 | 说明 | 类型 | 默认值 | 55 | |----------|------------------------------------------|-------------|-------| 56 | | authority | 准入权限/权限判断 | `string | Promise | (currentAuthority) => boolean | Promise` | - | 57 | | target | 权限判断通过时渲染的元素 | ReactNode | - | 58 | | Exception | 权限异常时渲染元素 | ReactNode | - | 59 | -------------------------------------------------------------------------------- /src/components/Authorized/renderAuthorize.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-mutable-exports */ 2 | let CURRENT = 'NULL'; 3 | /** 4 | * use authority or getAuthority 5 | * @param {string|()=>String} currentAuthority 6 | */ 7 | const renderAuthorize = Authorized => currentAuthority => { 8 | if (currentAuthority) { 9 | if (typeof currentAuthority === 'function') { 10 | CURRENT = currentAuthority(); 11 | } 12 | if ( 13 | Object.prototype.toString.call(currentAuthority) === '[object String]' || 14 | Array.isArray(currentAuthority) 15 | ) { 16 | CURRENT = currentAuthority; 17 | } 18 | } else { 19 | CURRENT = 'NULL'; 20 | } 21 | return Authorized; 22 | }; 23 | 24 | export { CURRENT }; 25 | export default Authorized => renderAuthorize(Authorized); 26 | -------------------------------------------------------------------------------- /src/components/Charts/Bar/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IBarProps { 3 | title: React.ReactNode; 4 | color?: string; 5 | padding?: [number, number, number, number]; 6 | height: number; 7 | data: Array<{ 8 | x: string; 9 | y: number; 10 | }>; 11 | autoLabel?: boolean; 12 | style?: React.CSSProperties; 13 | } 14 | 15 | export default class Bar extends React.Component {} 16 | -------------------------------------------------------------------------------- /src/components/Charts/ChartCard/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { CardProps } from 'antd/lib/card'; 3 | 4 | export interface IChartCardProps extends CardProps { 5 | title: React.ReactNode; 6 | action?: React.ReactNode; 7 | total?: React.ReactNode | number | (() => React.ReactNode | number); 8 | footer?: React.ReactNode; 9 | contentHeight?: number; 10 | avatar?: React.ReactNode; 11 | style?: React.CSSProperties; 12 | } 13 | 14 | export default class ChartCard extends React.Component {} 15 | -------------------------------------------------------------------------------- /src/components/Charts/ChartCard/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card } from 'antd'; 3 | import classNames from 'classnames'; 4 | 5 | import styles from './index.less'; 6 | 7 | const renderTotal = total => { 8 | let totalDom; 9 | switch (typeof total) { 10 | case 'undefined': 11 | totalDom = null; 12 | break; 13 | case 'function': 14 | totalDom =

{total()}
; 15 | break; 16 | default: 17 | totalDom =
{total}
; 18 | } 19 | return totalDom; 20 | }; 21 | 22 | class ChartCard extends React.PureComponent { 23 | renderConnet = () => { 24 | const { contentHeight, title, avatar, action, total, footer, children, loading } = this.props; 25 | if (loading) { 26 | return false; 27 | } 28 | return ( 29 |
30 |
35 |
{avatar}
36 |
37 |
38 | {title} 39 | {action} 40 |
41 | {renderTotal(total)} 42 |
43 |
44 | {children && ( 45 |
46 |
{children}
47 |
48 | )} 49 | {footer && ( 50 |
55 | {footer} 56 |
57 | )} 58 |
59 | ); 60 | }; 61 | 62 | render() { 63 | const { 64 | loading = false, 65 | contentHeight, 66 | title, 67 | avatar, 68 | action, 69 | total, 70 | footer, 71 | children, 72 | ...rest 73 | } = this.props; 74 | return ( 75 | 76 | {this.renderConnet()} 77 | 78 | ); 79 | } 80 | } 81 | 82 | export default ChartCard; 83 | -------------------------------------------------------------------------------- /src/components/Charts/ChartCard/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .chartCard { 4 | position: relative; 5 | .chartTop { 6 | position: relative; 7 | overflow: hidden; 8 | width: 100%; 9 | } 10 | .chartTopMargin { 11 | margin-bottom: 12px; 12 | } 13 | .chartTopHasMargin { 14 | margin-bottom: 20px; 15 | } 16 | .metaWrap { 17 | float: left; 18 | } 19 | .avatar { 20 | position: relative; 21 | top: 4px; 22 | float: left; 23 | margin-right: 20px; 24 | img { 25 | border-radius: 100%; 26 | } 27 | } 28 | .meta { 29 | color: @text-color-secondary; 30 | font-size: @font-size-base; 31 | line-height: 22px; 32 | height: 22px; 33 | } 34 | .action { 35 | cursor: pointer; 36 | position: absolute; 37 | top: 0; 38 | right: 0; 39 | } 40 | .total { 41 | overflow: hidden; 42 | text-overflow: ellipsis; 43 | word-break: break-all; 44 | white-space: nowrap; 45 | color: @heading-color; 46 | margin-top: 4px; 47 | margin-bottom: 0; 48 | font-size: 30px; 49 | line-height: 38px; 50 | height: 38px; 51 | } 52 | .content { 53 | margin-bottom: 12px; 54 | position: relative; 55 | width: 100%; 56 | } 57 | .contentFixed { 58 | position: absolute; 59 | left: 0; 60 | bottom: 0; 61 | width: 100%; 62 | } 63 | .footer { 64 | border-top: 1px solid @border-color-split; 65 | padding-top: 9px; 66 | margin-top: 8px; 67 | & > * { 68 | position: relative; 69 | } 70 | } 71 | .footerMargin { 72 | margin-top: 20px; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/components/Charts/Field/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IFieldProps { 3 | label: React.ReactNode; 4 | value: React.ReactNode; 5 | style?: React.CSSProperties; 6 | } 7 | 8 | export default class Field extends React.Component {} 9 | -------------------------------------------------------------------------------- /src/components/Charts/Field/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import styles from './index.less'; 4 | 5 | const Field = ({ label, value, ...rest }) => ( 6 |
7 | {label} 8 | {value} 9 |
10 | ); 11 | 12 | export default Field; 13 | -------------------------------------------------------------------------------- /src/components/Charts/Field/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .field { 4 | white-space: nowrap; 5 | overflow: hidden; 6 | text-overflow: ellipsis; 7 | margin: 0; 8 | span { 9 | font-size: @font-size-base; 10 | line-height: 22px; 11 | } 12 | span:last-child { 13 | margin-left: 8px; 14 | color: @heading-color; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Charts/Gauge/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IGaugeProps { 3 | title: React.ReactNode; 4 | color?: string; 5 | height: number; 6 | bgColor?: number; 7 | percent: number; 8 | style?: React.CSSProperties; 9 | } 10 | 11 | export default class Gauge extends React.Component {} 12 | -------------------------------------------------------------------------------- /src/components/Charts/MiniArea/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | // g2已经更新到3.0 4 | // 不带的写了 5 | 6 | export interface IAxis { 7 | title: any; 8 | line: any; 9 | gridAlign: any; 10 | labels: any; 11 | tickLine: any; 12 | grid: any; 13 | } 14 | 15 | export interface IMiniAreaProps { 16 | color?: string; 17 | height: number; 18 | borderColor?: string; 19 | line?: boolean; 20 | animate?: boolean; 21 | xAxis?: IAxis; 22 | yAxis?: IAxis; 23 | data: Array<{ 24 | x: number | string; 25 | y: number; 26 | }>; 27 | } 28 | 29 | export default class MiniArea extends React.Component {} 30 | -------------------------------------------------------------------------------- /src/components/Charts/MiniBar/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IMiniBarProps { 3 | color?: string; 4 | height: number; 5 | data: Array<{ 6 | x: number | string; 7 | y: number; 8 | }>; 9 | style?: React.CSSProperties; 10 | } 11 | 12 | export default class MiniBar extends React.Component {} 13 | -------------------------------------------------------------------------------- /src/components/Charts/MiniBar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chart, Tooltip, Geom } from 'bizcharts'; 3 | import autoHeight from '../autoHeight'; 4 | import styles from '../index.less'; 5 | 6 | @autoHeight() 7 | class MiniBar extends React.Component { 8 | render() { 9 | const { height, forceFit = true, color = '#1890FF', data = [] } = this.props; 10 | 11 | const scale = { 12 | x: { 13 | type: 'cat', 14 | }, 15 | y: { 16 | min: 0, 17 | }, 18 | }; 19 | 20 | const padding = [36, 5, 30, 5]; 21 | 22 | const tooltip = [ 23 | 'x*y', 24 | (x, y) => ({ 25 | name: x, 26 | value: y, 27 | }), 28 | ]; 29 | 30 | // for tooltip not to be hide 31 | const chartHeight = height + 54; 32 | 33 | return ( 34 |
35 |
36 | 43 | 44 | 45 | 46 |
47 |
48 | ); 49 | } 50 | } 51 | export default MiniBar; 52 | -------------------------------------------------------------------------------- /src/components/Charts/MiniProgress/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IMiniProgressProps { 3 | target: number; 4 | color?: string; 5 | strokeWidth?: number; 6 | percent?: number; 7 | style?: React.CSSProperties; 8 | } 9 | 10 | export default class MiniProgress extends React.Component {} 11 | -------------------------------------------------------------------------------- /src/components/Charts/MiniProgress/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tooltip } from 'antd'; 3 | 4 | import styles from './index.less'; 5 | 6 | const MiniProgress = ({ target, color = 'rgb(19, 194, 194)', strokeWidth, percent }) => ( 7 |
8 | 9 |
10 | 11 | 12 |
13 |
14 |
15 |
23 |
24 |
25 | ); 26 | 27 | export default MiniProgress; 28 | -------------------------------------------------------------------------------- /src/components/Charts/MiniProgress/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .miniProgress { 4 | padding: 5px 0; 5 | position: relative; 6 | width: 100%; 7 | .progressWrap { 8 | background-color: @background-color-base; 9 | position: relative; 10 | } 11 | .progress { 12 | transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s; 13 | border-radius: 1px 0 0 1px; 14 | background-color: @primary-color; 15 | width: 0; 16 | height: 100%; 17 | } 18 | .target { 19 | position: absolute; 20 | top: 0; 21 | bottom: 0; 22 | span { 23 | border-radius: 100px; 24 | position: absolute; 25 | top: 0; 26 | left: 0; 27 | height: 4px; 28 | width: 2px; 29 | } 30 | span:last-child { 31 | top: auto; 32 | bottom: 0; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/Charts/Pie/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IPieProps { 3 | animate?: boolean; 4 | color?: string; 5 | colors?: string[]; 6 | height: number; 7 | hasLegend?: boolean; 8 | padding?: [number, number, number, number]; 9 | percent?: number; 10 | data?: Array<{ 11 | x: string | string; 12 | y: number; 13 | }>; 14 | total?: React.ReactNode | number | (() => React.ReactNode | number); 15 | title?: React.ReactNode; 16 | tooltip?: boolean; 17 | valueFormat?: (value: string) => string | React.ReactNode; 18 | subTitle?: React.ReactNode; 19 | } 20 | 21 | export default class Pie extends React.Component {} 22 | -------------------------------------------------------------------------------- /src/components/Charts/Pie/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .pie { 4 | position: relative; 5 | .chart { 6 | position: relative; 7 | } 8 | &.hasLegend .chart { 9 | width: ~'calc(100% - 240px)'; 10 | } 11 | .legend { 12 | position: absolute; 13 | right: 0; 14 | min-width: 200px; 15 | top: 50%; 16 | transform: translateY(-50%); 17 | margin: 0 20px; 18 | list-style: none; 19 | padding: 0; 20 | li { 21 | cursor: pointer; 22 | margin-bottom: 16px; 23 | height: 22px; 24 | line-height: 22px; 25 | &:last-child { 26 | margin-bottom: 0; 27 | } 28 | } 29 | } 30 | .dot { 31 | border-radius: 8px; 32 | display: inline-block; 33 | margin-right: 8px; 34 | position: relative; 35 | top: -1px; 36 | height: 8px; 37 | width: 8px; 38 | } 39 | .line { 40 | background-color: @border-color-split; 41 | display: inline-block; 42 | margin-right: 8px; 43 | width: 1px; 44 | height: 16px; 45 | } 46 | .legendTitle { 47 | color: @text-color; 48 | } 49 | .percent { 50 | color: @text-color-secondary; 51 | } 52 | .value { 53 | position: absolute; 54 | right: 0; 55 | } 56 | .title { 57 | margin-bottom: 8px; 58 | } 59 | .total { 60 | position: absolute; 61 | left: 50%; 62 | top: 50%; 63 | text-align: center; 64 | max-height: 62px; 65 | transform: translate(-50%, -50%); 66 | & > h4 { 67 | color: @text-color-secondary; 68 | font-size: 14px; 69 | line-height: 22px; 70 | height: 22px; 71 | margin-bottom: 8px; 72 | font-weight: normal; 73 | } 74 | & > p { 75 | color: @heading-color; 76 | display: block; 77 | font-size: 1.2em; 78 | height: 32px; 79 | line-height: 32px; 80 | white-space: nowrap; 81 | } 82 | } 83 | } 84 | 85 | .legendBlock { 86 | &.hasLegend .chart { 87 | width: 100%; 88 | margin: 0 0 32px 0; 89 | } 90 | .legend { 91 | position: relative; 92 | transform: none; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/components/Charts/Radar/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IRadarProps { 3 | title?: React.ReactNode; 4 | height: number; 5 | padding?: [number, number, number, number]; 6 | hasLegend?: boolean; 7 | data: Array<{ 8 | name: string; 9 | label: string; 10 | value: string; 11 | }>; 12 | style?: React.CSSProperties; 13 | } 14 | 15 | export default class Radar extends React.Component {} 16 | -------------------------------------------------------------------------------- /src/components/Charts/Radar/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .radar { 4 | .legend { 5 | margin-top: 16px; 6 | .legendItem { 7 | position: relative; 8 | text-align: center; 9 | cursor: pointer; 10 | color: @text-color-secondary; 11 | line-height: 22px; 12 | p { 13 | margin: 0; 14 | } 15 | h6 { 16 | color: @heading-color; 17 | padding-left: 16px; 18 | font-size: 24px; 19 | line-height: 32px; 20 | margin-top: 4px; 21 | margin-bottom: 0; 22 | } 23 | &:after { 24 | background-color: @border-color-split; 25 | position: absolute; 26 | top: 8px; 27 | right: 0; 28 | height: 40px; 29 | width: 1px; 30 | content: ''; 31 | } 32 | } 33 | > :last-child .legendItem:after { 34 | display: none; 35 | } 36 | .dot { 37 | border-radius: 6px; 38 | display: inline-block; 39 | margin-right: 6px; 40 | position: relative; 41 | top: -1px; 42 | height: 6px; 43 | width: 6px; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/components/Charts/TagCloud/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface ITagCloudProps { 3 | data: Array<{ 4 | name: string; 5 | value: number; 6 | }>; 7 | height: number; 8 | style?: React.CSSProperties; 9 | } 10 | 11 | export default class TagCloud extends React.Component {} 12 | -------------------------------------------------------------------------------- /src/components/Charts/TagCloud/index.less: -------------------------------------------------------------------------------- 1 | .tagCloud { 2 | overflow: hidden; 3 | canvas { 4 | transform: scale(0.25); 5 | transform-origin: 0 0; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/Charts/TimelineChart/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface ITimelineChartProps { 3 | data: Array<{ 4 | x: number; 5 | y1: number; 6 | y2?: number; 7 | }>; 8 | titleMap: { y1: string; y2?: string }; 9 | padding?: [number, number, number, number]; 10 | height?: number; 11 | style?: React.CSSProperties; 12 | } 13 | 14 | export default class TimelineChart extends React.Component {} 15 | -------------------------------------------------------------------------------- /src/components/Charts/TimelineChart/index.less: -------------------------------------------------------------------------------- 1 | .timelineChart { 2 | background: #fff; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/Charts/WaterWave/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IWaterWaveProps { 3 | title: React.ReactNode; 4 | color?: string; 5 | height: number; 6 | percent: number; 7 | style?: React.CSSProperties; 8 | } 9 | 10 | export default class WaterWave extends React.Component {} 11 | -------------------------------------------------------------------------------- /src/components/Charts/WaterWave/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .waterWave { 4 | display: inline-block; 5 | position: relative; 6 | transform-origin: left; 7 | .text { 8 | position: absolute; 9 | left: 0; 10 | top: 32px; 11 | text-align: center; 12 | width: 100%; 13 | span { 14 | color: @text-color-secondary; 15 | font-size: 14px; 16 | line-height: 22px; 17 | } 18 | h4 { 19 | color: @heading-color; 20 | line-height: 32px; 21 | font-size: 24px; 22 | } 23 | } 24 | .waterWaveCanvasWrapper { 25 | transform: scale(0.5); 26 | transform-origin: 0 0; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/Charts/autoHeight.js: -------------------------------------------------------------------------------- 1 | /* eslint eqeqeq: 0 */ 2 | import React from 'react'; 3 | 4 | function computeHeight(node) { 5 | const totalHeight = parseInt(getComputedStyle(node).height, 10); 6 | const padding = 7 | parseInt(getComputedStyle(node).paddingTop, 10) + 8 | parseInt(getComputedStyle(node).paddingBottom, 10); 9 | return totalHeight - padding; 10 | } 11 | 12 | function getAutoHeight(n) { 13 | if (!n) { 14 | return 0; 15 | } 16 | 17 | let node = n; 18 | 19 | let height = computeHeight(node); 20 | 21 | while (!height) { 22 | node = node.parentNode; 23 | if (node) { 24 | height = computeHeight(node); 25 | } else { 26 | break; 27 | } 28 | } 29 | 30 | return height; 31 | } 32 | 33 | const autoHeight = () => WrappedComponent => 34 | class extends React.Component { 35 | state = { 36 | computedHeight: 0, 37 | }; 38 | 39 | componentDidMount() { 40 | const { height } = this.props; 41 | if (!height) { 42 | const h = getAutoHeight(this.root); 43 | // eslint-disable-next-line 44 | this.setState({ computedHeight: h }); 45 | } 46 | } 47 | 48 | handleRoot = node => { 49 | this.root = node; 50 | }; 51 | 52 | render() { 53 | const { height } = this.props; 54 | const { computedHeight } = this.state; 55 | const h = height || computedHeight; 56 | return ( 57 |
{h > 0 && }
58 | ); 59 | } 60 | }; 61 | 62 | export default autoHeight; 63 | -------------------------------------------------------------------------------- /src/components/Charts/bizcharts.d.ts: -------------------------------------------------------------------------------- 1 | import * as BizChart from 'bizcharts'; 2 | 3 | export = BizChart; 4 | -------------------------------------------------------------------------------- /src/components/Charts/bizcharts.js: -------------------------------------------------------------------------------- 1 | import * as BizChart from 'bizcharts'; 2 | 3 | export default BizChart; 4 | -------------------------------------------------------------------------------- /src/components/Charts/demo/bar.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 4 3 | title: 柱状图 4 | --- 5 | 6 | 通过设置 `x`,`y` 属性,可以快速的构建出一个漂亮的柱状图,各种纬度的关系则是通过自定义的数据展现。 7 | 8 | ````jsx 9 | import { Bar } from 'ant-design-pro/lib/Charts'; 10 | 11 | const salesData = []; 12 | for (let i = 0; i < 12; i += 1) { 13 | salesData.push({ 14 | x: `${i + 1}月`, 15 | y: Math.floor(Math.random() * 1000) + 200, 16 | }); 17 | } 18 | 19 | ReactDOM.render( 20 | 25 | , mountNode); 26 | ```` 27 | -------------------------------------------------------------------------------- /src/components/Charts/demo/chart-card.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: 图表卡片 4 | --- 5 | 6 | 用于展示图表的卡片容器,可以方便的配合其它图表套件展示丰富信息。 7 | 8 | ```jsx 9 | import { ChartCard, yuan, Field } from 'ant-design-pro/lib/Charts'; 10 | import Trend from 'ant-design-pro/lib/Trend'; 11 | import { Row, Col, Icon, Tooltip } from 'antd'; 12 | import numeral from 'numeral'; 13 | 14 | ReactDOM.render( 15 | 16 | 17 | 21 | 22 | 23 | } 24 | total={() => ( 25 | 26 | )} 27 | footer={ 28 | 29 | } 30 | contentHeight={46} 31 | > 32 | 33 | 周同比 34 | 35 | 12% 36 | 37 | 38 | 39 | 日环比 40 | 44 | 11% 45 | 46 | 47 | 48 | 49 | 50 | 58 | } 59 | action={ 60 | 61 | 62 | 63 | } 64 | total={() => ( 65 | 66 | )} 67 | footer={ 68 | 69 | } 70 | /> 71 | 72 | 73 | 81 | } 82 | action={ 83 | 84 | 85 | 86 | } 87 | total={() => ( 88 | 89 | )} 90 | /> 91 | 92 | , 93 | mountNode, 94 | ); 95 | ``` 96 | -------------------------------------------------------------------------------- /src/components/Charts/demo/gauge.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 7 3 | title: 仪表盘 4 | --- 5 | 6 | 仪表盘是一种进度展示方式,可以更直观的展示当前的进展情况,通常也可表示占比。 7 | 8 | ````jsx 9 | import { Gauge } from 'ant-design-pro/lib/Charts'; 10 | 11 | ReactDOM.render( 12 | 17 | , mountNode); 18 | ```` 19 | -------------------------------------------------------------------------------- /src/components/Charts/demo/mini-area.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | col: 2 4 | title: 迷你区域图 5 | --- 6 | 7 | ````jsx 8 | import { MiniArea } from 'ant-design-pro/lib/Charts'; 9 | import moment from 'moment'; 10 | 11 | const visitData = []; 12 | const beginDay = new Date().getTime(); 13 | for (let i = 0; i < 20; i += 1) { 14 | visitData.push({ 15 | x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'), 16 | y: Math.floor(Math.random() * 100) + 10, 17 | }); 18 | } 19 | 20 | ReactDOM.render( 21 | 27 | , mountNode); 28 | ```` 29 | -------------------------------------------------------------------------------- /src/components/Charts/demo/mini-bar.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | col: 2 4 | title: 迷你柱状图 5 | --- 6 | 7 | 迷你柱状图更适合展示简单的区间数据,简洁的表现方式可以很好的减少大数据量的视觉展现压力。 8 | 9 | ````jsx 10 | import { MiniBar } from 'ant-design-pro/lib/Charts'; 11 | import moment from 'moment'; 12 | 13 | const visitData = []; 14 | const beginDay = new Date().getTime(); 15 | for (let i = 0; i < 20; i += 1) { 16 | visitData.push({ 17 | x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'), 18 | y: Math.floor(Math.random() * 100) + 10, 19 | }); 20 | } 21 | 22 | ReactDOM.render( 23 | 27 | , mountNode); 28 | ```` 29 | -------------------------------------------------------------------------------- /src/components/Charts/demo/mini-pie.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 6 3 | title: 迷你饼状图 4 | --- 5 | 6 | 通过简化 `Pie` 属性的设置,可以快速的实现极简的饼状图,可配合 `ChartCard` 组合展 7 | 现更多业务场景。 8 | 9 | ```jsx 10 | import { Pie } from 'ant-design-pro/lib/Charts'; 11 | 12 | ReactDOM.render( 13 | , 14 | mountNode 15 | ); 16 | ``` 17 | -------------------------------------------------------------------------------- /src/components/Charts/demo/mini-progress.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 3 3 | title: 迷你进度条 4 | --- 5 | 6 | ````jsx 7 | import { MiniProgress } from 'ant-design-pro/lib/Charts'; 8 | 9 | ReactDOM.render( 10 | 11 | , mountNode); 12 | ```` 13 | -------------------------------------------------------------------------------- /src/components/Charts/demo/mix.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 图表套件组合展示 4 | --- 5 | 6 | 利用 Ant Design Pro 提供的图表套件,可以灵活组合符合设计规范的图表来满足复杂的业务需求。 7 | 8 | ````jsx 9 | import { ChartCard, Field, MiniArea, MiniBar, MiniProgress } from 'ant-design-pro/lib/Charts'; 10 | import Trend from 'ant-design-pro/lib/Trend'; 11 | import NumberInfo from 'ant-design-pro/lib/NumberInfo'; 12 | import { Row, Col, Icon, Tooltip } from 'antd'; 13 | import numeral from 'numeral'; 14 | import moment from 'moment'; 15 | 16 | const visitData = []; 17 | const beginDay = new Date().getTime(); 18 | for (let i = 0; i < 20; i += 1) { 19 | visitData.push({ 20 | x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'), 21 | y: Math.floor(Math.random() * 100) + 10, 22 | }); 23 | } 24 | 25 | ReactDOM.render( 26 | 27 | 28 | 33 | 本周访问} 35 | total={numeral(12321).format('0,0')} 36 | status="up" 37 | subTotal={17.1} 38 | /> 39 | 44 | 45 | 46 | 47 | } 50 | total={numeral(8846).format('0,0')} 51 | footer={} 52 | contentHeight={46} 53 | > 54 | 58 | 59 | 60 | 61 | } 64 | total="78%" 65 | footer={ 66 |
67 | 68 | 周同比 69 | 12% 70 | 71 | 72 | 日环比 73 | 11% 74 | 75 |
76 | } 77 | contentHeight={46} 78 | > 79 | 80 |
81 | 82 |
83 | , mountNode); 84 | ```` 85 | -------------------------------------------------------------------------------- /src/components/Charts/demo/pie.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 5 3 | title: 饼状图 4 | --- 5 | 6 | ```jsx 7 | import { Pie, yuan } from 'ant-design-pro/lib/Charts'; 8 | 9 | const salesPieData = [ 10 | { 11 | x: '家用电器', 12 | y: 4544, 13 | }, 14 | { 15 | x: '食用酒水', 16 | y: 3321, 17 | }, 18 | { 19 | x: '个护健康', 20 | y: 3113, 21 | }, 22 | { 23 | x: '服饰箱包', 24 | y: 2341, 25 | }, 26 | { 27 | x: '母婴产品', 28 | y: 1231, 29 | }, 30 | { 31 | x: '其他', 32 | y: 1231, 33 | }, 34 | ]; 35 | 36 | ReactDOM.render( 37 | ( 42 | now.y + pre, 0)) 45 | }} 46 | /> 47 | )} 48 | data={salesPieData} 49 | valueFormat={val => } 50 | height={294} 51 | />, 52 | mountNode, 53 | ); 54 | ``` 55 | -------------------------------------------------------------------------------- /src/components/Charts/demo/radar.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 7 3 | title: 雷达图 4 | --- 5 | 6 | ````jsx 7 | import { Radar, ChartCard } from 'ant-design-pro/lib/Charts'; 8 | 9 | const radarOriginData = [ 10 | { 11 | name: '个人', 12 | ref: 10, 13 | koubei: 8, 14 | output: 4, 15 | contribute: 5, 16 | hot: 7, 17 | }, 18 | { 19 | name: '团队', 20 | ref: 3, 21 | koubei: 9, 22 | output: 6, 23 | contribute: 3, 24 | hot: 1, 25 | }, 26 | { 27 | name: '部门', 28 | ref: 4, 29 | koubei: 1, 30 | output: 6, 31 | contribute: 5, 32 | hot: 7, 33 | }, 34 | ]; 35 | const radarData = []; 36 | const radarTitleMap = { 37 | ref: '引用', 38 | koubei: '口碑', 39 | output: '产量', 40 | contribute: '贡献', 41 | hot: '热度', 42 | }; 43 | radarOriginData.forEach((item) => { 44 | Object.keys(item).forEach((key) => { 45 | if (key !== 'name') { 46 | radarData.push({ 47 | name: item.name, 48 | label: radarTitleMap[key], 49 | value: item[key], 50 | }); 51 | } 52 | }); 53 | }); 54 | 55 | ReactDOM.render( 56 | 57 | 62 | 63 | , mountNode); 64 | ```` 65 | -------------------------------------------------------------------------------- /src/components/Charts/demo/tag-cloud.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 9 3 | title: 标签云 4 | --- 5 | 6 | 标签云是一套相关的标签以及与此相应的权重展示方式,一般典型的标签云有 30 至 150 个标签,而权重影响使用的字体大小或其他视觉效果。 7 | 8 | ````jsx 9 | import { TagCloud } from 'ant-design-pro/lib/Charts'; 10 | 11 | const tags = []; 12 | for (let i = 0; i < 50; i += 1) { 13 | tags.push({ 14 | name: `TagClout-Title-${i}`, 15 | value: Math.floor((Math.random() * 50)) + 20, 16 | }); 17 | } 18 | 19 | ReactDOM.render( 20 | 24 | , mountNode); 25 | ```` 26 | -------------------------------------------------------------------------------- /src/components/Charts/demo/timeline-chart.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 9 3 | title: 带有时间轴的图表 4 | --- 5 | 6 | 使用 `TimelineChart` 组件可以实现带有时间轴的柱状图展现,而其中的 `x` 属性,则是时间值的指向,默认最多支持同时展现两个指标,分别是 `y1` 和 `y2`。 7 | 8 | ````jsx 9 | import { TimelineChart } from 'ant-design-pro/lib/Charts'; 10 | 11 | const chartData = []; 12 | for (let i = 0; i < 20; i += 1) { 13 | chartData.push({ 14 | x: (new Date().getTime()) + (1000 * 60 * 30 * i), 15 | y1: Math.floor(Math.random() * 100) + 1000, 16 | y2: Math.floor(Math.random() * 100) + 10, 17 | }); 18 | } 19 | 20 | ReactDOM.render( 21 | 26 | , mountNode); 27 | ```` 28 | -------------------------------------------------------------------------------- /src/components/Charts/demo/waterwave.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 8 3 | title: 水波图 4 | --- 5 | 6 | 水波图是一种比例的展示方式,可以更直观的展示关键值的占比。 7 | 8 | ````jsx 9 | import { WaterWave } from 'ant-design-pro/lib/Charts'; 10 | 11 | ReactDOM.render( 12 |
13 | 18 |
19 | , mountNode); 20 | ```` 21 | -------------------------------------------------------------------------------- /src/components/Charts/g2.js: -------------------------------------------------------------------------------- 1 | // 全局 G2 设置 2 | import { track, setTheme } from 'bizcharts'; 3 | 4 | track(false); 5 | 6 | const config = { 7 | defaultColor: '#1089ff', 8 | shape: { 9 | interval: { 10 | fillOpacity: 1, 11 | }, 12 | }, 13 | }; 14 | 15 | setTheme(config); 16 | -------------------------------------------------------------------------------- /src/components/Charts/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as numeral from 'numeral'; 2 | export { default as ChartCard } from './ChartCard'; 3 | export { default as Bar } from './Bar'; 4 | export { default as Pie } from './Pie'; 5 | export { default as Radar } from './Radar'; 6 | export { default as Gauge } from './Gauge'; 7 | export { default as MiniArea } from './MiniArea'; 8 | export { default as MiniBar } from './MiniBar'; 9 | export { default as MiniProgress } from './MiniProgress'; 10 | export { default as Field } from './Field'; 11 | export { default as WaterWave } from './WaterWave'; 12 | export { default as TagCloud } from './TagCloud'; 13 | export { default as TimelineChart } from './TimelineChart'; 14 | 15 | declare const yuan: (value: number | string) => string; 16 | 17 | export { yuan }; 18 | -------------------------------------------------------------------------------- /src/components/Charts/index.js: -------------------------------------------------------------------------------- 1 | import numeral from 'numeral'; 2 | import './g2'; 3 | import ChartCard from './ChartCard'; 4 | import Bar from './Bar'; 5 | import Pie from './Pie'; 6 | import Radar from './Radar'; 7 | import Gauge from './Gauge'; 8 | import MiniArea from './MiniArea'; 9 | import MiniBar from './MiniBar'; 10 | import MiniProgress from './MiniProgress'; 11 | import Field from './Field'; 12 | import WaterWave from './WaterWave'; 13 | import TagCloud from './TagCloud'; 14 | import TimelineChart from './TimelineChart'; 15 | 16 | const yuan = val => `¥ ${numeral(val).format('0,0')}`; 17 | 18 | const Charts = { 19 | yuan, 20 | Bar, 21 | Pie, 22 | Gauge, 23 | Radar, 24 | MiniBar, 25 | MiniArea, 26 | MiniProgress, 27 | ChartCard, 28 | Field, 29 | WaterWave, 30 | TagCloud, 31 | TimelineChart, 32 | }; 33 | 34 | export { 35 | Charts as default, 36 | yuan, 37 | Bar, 38 | Pie, 39 | Gauge, 40 | Radar, 41 | MiniBar, 42 | MiniArea, 43 | MiniProgress, 44 | ChartCard, 45 | Field, 46 | WaterWave, 47 | TagCloud, 48 | TimelineChart, 49 | }; 50 | -------------------------------------------------------------------------------- /src/components/Charts/index.less: -------------------------------------------------------------------------------- 1 | .miniChart { 2 | position: relative; 3 | width: 100%; 4 | .chartContent { 5 | position: absolute; 6 | bottom: -28px; 7 | width: 100%; 8 | > div { 9 | margin: 0 -5px; 10 | overflow: hidden; 11 | } 12 | } 13 | .chartLoading { 14 | position: absolute; 15 | top: 16px; 16 | left: 50%; 17 | margin-left: -7px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Exception/demo/403.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | title: 4 | zh-CN: 403 5 | en-US: 403 6 | --- 7 | 8 | ## zh-CN 9 | 10 | 403 页面,配合自定义操作。 11 | 12 | ## en-US 13 | 14 | 403 page with custom operations. 15 | 16 | ````jsx 17 | import Exception from 'ant-design-pro/lib/Exception'; 18 | import { Button } from 'antd'; 19 | 20 | const actions = ( 21 |
22 | 23 | 24 |
25 | ); 26 | ReactDOM.render( 27 | 28 | , mountNode); 29 | ```` 30 | -------------------------------------------------------------------------------- /src/components/Exception/demo/404.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 4 | zh-CN: 404 5 | en-US: 404 6 | --- 7 | 8 | ## zh-CN 9 | 10 | 404 页面。 11 | 12 | ## en-US 13 | 14 | 404 page. 15 | 16 | ````jsx 17 | import Exception from 'ant-design-pro/lib/Exception'; 18 | 19 | ReactDOM.render( 20 | 21 | , mountNode); 22 | ```` 23 | -------------------------------------------------------------------------------- /src/components/Exception/demo/500.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: 4 | zh-CN: 500 5 | en-US: 500 6 | --- 7 | 8 | ## zh-CN 9 | 10 | 500 页面。 11 | 12 | ## en-US 13 | 14 | 500 page. 15 | 16 | ````jsx 17 | import Exception from 'ant-design-pro/lib/Exception'; 18 | 19 | ReactDOM.render( 20 | 21 | , mountNode); 22 | ```` 23 | -------------------------------------------------------------------------------- /src/components/Exception/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IExceptionProps { 3 | type?: '403' | '404' | '500'; 4 | title?: React.ReactNode; 5 | desc?: React.ReactNode; 6 | img?: string; 7 | actions?: React.ReactNode; 8 | linkElement?: React.ReactNode; 9 | style?: React.CSSProperties; 10 | className?: string; 11 | backText?: React.ReactNode; 12 | redirect?: string; 13 | } 14 | 15 | export default class Exception extends React.Component {} 16 | -------------------------------------------------------------------------------- /src/components/Exception/index.en-US.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Exception 3 | cols: 1 4 | order: 5 5 | --- 6 | 7 | Exceptions page is used to provide feedback on specific abnormal state. Usually, it contains an explanation of the error status, and provides users with suggestions or operations, to prevent users from feeling lost and confused. 8 | 9 | ## API 10 | 11 | Property | Description | Type | Default 12 | ---------|-------------|------|-------- 13 | | backText | default return button text | ReactNode | back to home | 14 | type | type of exception, the corresponding default `title`, `desc`, `img` will be given if set, which can be overridden by explicit setting of `title`, `desc`, `img` | Enum {'403', '404', '500'} | - 15 | title | title | ReactNode | - 16 | desc | supplementary description | ReactNode | - 17 | img | the url of background image | string | - 18 | actions | suggested operations, a default 'Home' link will show if not set | ReactNode | - 19 | linkElement | to specify the element of link | string\|ReactElement | 'a' 20 | redirect | redirect path | string | '/' -------------------------------------------------------------------------------- /src/components/Exception/index.js: -------------------------------------------------------------------------------- 1 | import React, { createElement } from 'react'; 2 | import classNames from 'classnames'; 3 | import { Button } from 'antd'; 4 | import config from './typeConfig'; 5 | import styles from './index.less'; 6 | 7 | class Exception extends React.PureComponent { 8 | static defaultProps = { 9 | backText: 'back to home', 10 | redirect: '/', 11 | }; 12 | 13 | constructor(props) { 14 | super(props); 15 | this.state = {}; 16 | } 17 | 18 | render() { 19 | const { 20 | className, 21 | backText, 22 | linkElement = 'a', 23 | type, 24 | title, 25 | desc, 26 | img, 27 | actions, 28 | redirect, 29 | ...rest 30 | } = this.props; 31 | const pageType = type in config ? type : '404'; 32 | const clsString = classNames(styles.exception, className); 33 | return ( 34 |
35 |
36 |
40 |
41 |
42 |

{title || config[pageType].title}

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

Content Content Content Content

24 |

Content Content Content Content

25 |

Content Content Content Content

26 |

Content Content Content Content

27 |

Content Content Content Content

28 |

Content Content Content Content

29 |

Content Content Content Content

30 |

Content Content Content Content

31 |

Content Content Content Content

32 |

Content Content Content Content

33 |

Content Content Content Content

34 |

Content Content Content Content

35 |

Content Content Content Content

36 |

Content Content Content Content

37 |

Content Content Content Content

38 | 39 | 40 | 41 | 42 |
43 | , mountNode); 44 | ```` -------------------------------------------------------------------------------- /src/components/FooterToolbar/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IFooterToolbarProps { 3 | extra: React.ReactNode; 4 | style?: React.CSSProperties; 5 | } 6 | 7 | export default class FooterToolbar extends React.Component {} 8 | -------------------------------------------------------------------------------- /src/components/FooterToolbar/index.en-US.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: FooterToolbar 3 | cols: 1 4 | order: 6 5 | --- 6 | 7 | A toolbar fixed at the bottom. 8 | 9 | ## Usage 10 | 11 | It is fixed at the bottom of the content area and does not move along with the scroll bar, which is usually used for data collection and submission for long pages. 12 | 13 | ## API 14 | 15 | Property | Description | Type | Default 16 | ---------|-------------|------|-------- 17 | children | toolbar content, align to the right | ReactNode | - 18 | extra | extra information, align to the left | ReactNode | - -------------------------------------------------------------------------------- /src/components/FooterToolbar/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import styles from './index.less'; 5 | 6 | export default class FooterToolbar extends Component { 7 | static contextTypes = { 8 | isMobile: PropTypes.bool, 9 | }; 10 | 11 | state = { 12 | width: undefined, 13 | }; 14 | 15 | componentDidMount() { 16 | window.addEventListener('resize', this.resizeFooterToolbar); 17 | this.resizeFooterToolbar(); 18 | } 19 | 20 | componentWillUnmount() { 21 | window.removeEventListener('resize', this.resizeFooterToolbar); 22 | } 23 | 24 | resizeFooterToolbar = () => { 25 | const sider = document.querySelector('.ant-layout-sider'); 26 | if (sider == null) { 27 | return; 28 | } 29 | const { isMobile } = this.context; 30 | const width = isMobile ? null : `calc(100% - ${sider.style.width})`; 31 | const { width: stateWidth } = this.state; 32 | if (stateWidth !== width) { 33 | this.setState({ width }); 34 | } 35 | }; 36 | 37 | render() { 38 | const { children, className, extra, ...restProps } = this.props; 39 | const { width } = this.state; 40 | return ( 41 |
42 |
{extra}
43 |
{children}
44 |
45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/FooterToolbar/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .toolbar { 4 | position: fixed; 5 | width: 100%; 6 | bottom: 0; 7 | right: 0; 8 | height: 56px; 9 | line-height: 56px; 10 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.03); 11 | background: #fff; 12 | border-top: 1px solid @border-color-split; 13 | padding: 0 24px; 14 | z-index: 9; 15 | 16 | &:after { 17 | content: ''; 18 | display: block; 19 | clear: both; 20 | } 21 | 22 | .left { 23 | float: left; 24 | } 25 | 26 | .right { 27 | float: right; 28 | } 29 | 30 | button + button { 31 | margin-left: 8px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/FooterToolbar/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: FooterToolbar 3 | subtitle: 底部工具栏 4 | cols: 1 5 | order: 6 6 | --- 7 | 8 | 固定在底部的工具栏。 9 | 10 | ## 何时使用 11 | 12 | 固定在内容区域的底部,不随滚动条移动,常用于长页面的数据搜集和提交工作。 13 | 14 | ## API 15 | 16 | 参数 | 说明 | 类型 | 默认值 17 | ----|------|-----|------ 18 | children | 工具栏内容,向右对齐 | ReactNode | - 19 | extra | 额外信息,向左对齐 | ReactNode | - 20 | -------------------------------------------------------------------------------- /src/components/GlobalFooter/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 演示 4 | iframe: 400 5 | --- 6 | 7 | 基本页脚。 8 | 9 | ````jsx 10 | import GlobalFooter from 'ant-design-pro/lib/GlobalFooter'; 11 | import { Icon } from 'antd'; 12 | 13 | const links = [{ 14 | key: '帮助', 15 | title: '帮助', 16 | href: '', 17 | }, { 18 | key: 'github', 19 | title: , 20 | href: 'https://github.com/ant-design/ant-design-pro', 21 | blankTarget: true, 22 | }, { 23 | key: '条款', 24 | title: '条款', 25 | href: '', 26 | blankTarget: true, 27 | }]; 28 | 29 | const copyright =
Copyright 2017 蚂蚁金服体验技术部出品
; 30 | 31 | ReactDOM.render( 32 |
33 |
34 | 35 |
36 | , mountNode); 37 | ```` 38 | -------------------------------------------------------------------------------- /src/components/GlobalFooter/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IGlobalFooterProps { 3 | links?: Array<{ 4 | key?: string; 5 | title: React.ReactNode; 6 | href: string; 7 | blankTarget?: boolean; 8 | }>; 9 | copyright?: React.ReactNode; 10 | style?: React.CSSProperties; 11 | } 12 | 13 | export default class GlobalFooter extends React.Component {} 14 | -------------------------------------------------------------------------------- /src/components/GlobalFooter/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import styles from './index.less'; 4 | 5 | const GlobalFooter = ({ className, links, copyright }) => { 6 | const clsString = classNames(styles.globalFooter, className); 7 | return ( 8 |
9 | {links && ( 10 |
11 | {links.map(link => ( 12 | 18 | {link.title} 19 | 20 | ))} 21 |
22 | )} 23 | {copyright &&
{copyright}
} 24 |
25 | ); 26 | }; 27 | 28 | export default GlobalFooter; 29 | -------------------------------------------------------------------------------- /src/components/GlobalFooter/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .globalFooter { 4 | padding: 0 16px; 5 | margin: 48px 0 24px 0; 6 | text-align: center; 7 | 8 | .links { 9 | margin-bottom: 8px; 10 | 11 | a { 12 | color: @text-color-secondary; 13 | transition: all 0.3s; 14 | 15 | &:not(:last-child) { 16 | margin-right: 40px; 17 | } 18 | 19 | &:hover { 20 | color: @text-color; 21 | } 22 | } 23 | } 24 | 25 | .copyright { 26 | color: @text-color-secondary; 27 | font-size: @font-size-base; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/GlobalFooter/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: GlobalFooter 4 | zh-CN: GlobalFooter 5 | subtitle: 全局页脚 6 | cols: 1 7 | order: 7 8 | --- 9 | 10 | 页脚属于全局导航的一部分,作为对顶部导航的补充,通过传递数据控制展示内容。 11 | 12 | ## API 13 | 14 | 参数 | 说明 | 类型 | 默认值 15 | ----|------|-----|------ 16 | links | 链接数据 | array<{ title: ReactNode, href: string, blankTarget?: boolean }> | - 17 | copyright | 版权信息 | ReactNode | - 18 | -------------------------------------------------------------------------------- /src/components/GlobalHeader/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Icon } from 'antd'; 3 | import Link from 'umi/link'; 4 | import Debounce from 'lodash-decorators/debounce'; 5 | import styles from './index.less'; 6 | import RightContent from './RightContent'; 7 | 8 | export default class GlobalHeader extends PureComponent { 9 | componentWillUnmount() { 10 | this.triggerResizeEvent.cancel(); 11 | } 12 | /* eslint-disable*/ 13 | @Debounce(600) 14 | triggerResizeEvent() { 15 | // eslint-disable-line 16 | const event = document.createEvent('HTMLEvents'); 17 | event.initEvent('resize', true, false); 18 | window.dispatchEvent(event); 19 | } 20 | toggle = () => { 21 | const { collapsed, onCollapse } = this.props; 22 | onCollapse(!collapsed); 23 | this.triggerResizeEvent(); 24 | }; 25 | render() { 26 | const { collapsed, isMobile, logo } = this.props; 27 | return ( 28 |
29 | {isMobile && ( 30 | 31 | logo 32 | 33 | )} 34 | 39 | 40 | 41 |
42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/Login/LoginSubmit.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import { Button, Form } from 'antd'; 4 | import styles from './index.less'; 5 | 6 | const FormItem = Form.Item; 7 | 8 | const LoginSubmit = ({ className, ...rest }) => { 9 | const clsString = classNames(styles.submit, className); 10 | return ( 11 | 12 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | ); 45 | 46 | const extra = ( 47 | 48 | 49 |
状态
50 |
待审批
51 | 52 | 53 |
订单金额
54 |
¥ 568.08
55 | 56 |
57 | ); 58 | 59 | const breadcrumbList = [{ 60 | title: '一级菜单', 61 | href: '/', 62 | }, { 63 | title: '二级菜单', 64 | href: '/', 65 | }, { 66 | title: '三级菜单', 67 | }]; 68 | 69 | const tabList = [{ 70 | key: 'detail', 71 | tab: '详情', 72 | }, { 73 | key: 'rule', 74 | tab: '规则', 75 | }]; 76 | 77 | function onTabChange(key) { 78 | console.log(key); 79 | } 80 | 81 | ReactDOM.render( 82 |
83 | } 86 | action={action} 87 | content={description} 88 | extraContent={extra} 89 | breadcrumbList={breadcrumbList} 90 | tabList={tabList} 91 | tabActiveKey="detail" 92 | onTabChange={onTabChange} 93 | /> 94 |
95 | , mountNode); 96 | ```` 97 | 98 | 103 | -------------------------------------------------------------------------------- /src/components/PageHeader/demo/structure.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: Structure 4 | --- 5 | 6 | 基本结构,具备响应式布局功能,主要断点为 768px 和 576px,拖动窗口改变大小试试看。 7 | 8 | ````jsx 9 | import PageHeader from 'ant-design-pro/lib/PageHeader'; 10 | 11 | const breadcrumbList = [{ 12 | title: '面包屑', 13 | }]; 14 | 15 | const tabList = [{ 16 | key: '1', 17 | tab: '页签一', 18 | }, { 19 | key: '2', 20 | tab: '页签二', 21 | }, { 22 | key: '3', 23 | tab: '页签三', 24 | }]; 25 | 26 | ReactDOM.render( 27 |
28 | Title
} 31 | logo={
logo
} 32 | action={
action
} 33 | content={
content
} 34 | extraContent={
extraContent
} 35 | breadcrumbList={breadcrumbList} 36 | tabList={tabList} 37 | tabActiveKey="1" 38 | /> 39 |
40 | , mountNode); 41 | ```` 42 | 43 | 69 | -------------------------------------------------------------------------------- /src/components/PageHeader/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IPageHeaderProps { 3 | title?: React.ReactNode | string; 4 | logo?: React.ReactNode | string; 5 | action?: React.ReactNode | string; 6 | content?: React.ReactNode; 7 | extraContent?: React.ReactNode; 8 | routes?: any[]; 9 | params?: any; 10 | breadcrumbList?: Array<{ title: React.ReactNode; href?: string }>; 11 | tabList?: Array<{ key: string; tab: React.ReactNode }>; 12 | tabActiveKey?: string; 13 | tabDefaultActiveKey?: string; 14 | onTabChange?: (key: string) => void; 15 | tabBarExtraContent?: React.ReactNode; 16 | linkElement?: React.ReactNode; 17 | style?: React.CSSProperties; 18 | home?: React.ReactNode; 19 | wide?: boolean; 20 | hiddenBreadcrumb?:boolean; 21 | } 22 | 23 | export default class PageHeader extends React.Component {} 24 | -------------------------------------------------------------------------------- /src/components/PageHeader/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Tabs, Skeleton } from 'antd'; 3 | import classNames from 'classnames'; 4 | import styles from './index.less'; 5 | import BreadcrumbView from './breadcrumb'; 6 | 7 | const { TabPane } = Tabs; 8 | export default class PageHeader extends PureComponent { 9 | onChange = key => { 10 | const { onTabChange } = this.props; 11 | if (onTabChange) { 12 | onTabChange(key); 13 | } 14 | }; 15 | 16 | render() { 17 | const { 18 | title, 19 | logo, 20 | action, 21 | content, 22 | extraContent, 23 | tabList, 24 | className, 25 | tabActiveKey, 26 | tabDefaultActiveKey, 27 | tabBarExtraContent, 28 | loading = false, 29 | wide = false, 30 | hiddenBreadcrumb = false, 31 | } = this.props; 32 | 33 | const clsString = classNames(styles.pageHeader, className); 34 | const activeKeyProps = {}; 35 | if (tabDefaultActiveKey !== undefined) { 36 | activeKeyProps.defaultActiveKey = tabDefaultActiveKey; 37 | } 38 | if (tabActiveKey !== undefined) { 39 | activeKeyProps.activeKey = tabActiveKey; 40 | } 41 | return ( 42 |
43 |
44 | 51 | {hiddenBreadcrumb ? null : } 52 |
53 | {logo &&
{logo}
} 54 |
55 |
56 | {title &&

{title}

} 57 | {action &&
{action}
} 58 |
59 |
60 | {content &&
{content}
} 61 | {extraContent &&
{extraContent}
} 62 |
63 |
64 |
65 | {tabList && tabList.length ? ( 66 | 72 | {tabList.map(item => ( 73 | 74 | ))} 75 | 76 | ) : null} 77 |
78 |
79 |
80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/components/PageHeader/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: PageHeader 4 | zh-CN: PageHeader 5 | subtitle: 页头 6 | cols: 1 7 | order: 11 8 | --- 9 | 10 | 页头用来声明页面的主题,包含了用户所关注的最重要的信息,使用户可以快速理解当前页面是什么以及它的功能。 11 | 12 | ## API 13 | 14 | | 参数 | 说明 | 类型 | 默认值 | 15 | |----------|------------------------------------------|-------------|-------| 16 | | title | title 区域 | ReactNode | - | 17 | | logo | logo区域 | ReactNode | - | 18 | | action | 操作区,位于 title 行的行尾 | ReactNode | - | 19 | | home | 默认的主页说明文字 | ReactNode | - | 20 | | content | 内容区 | ReactNode | - | 21 | | extraContent | 额外内容区,位于content的右侧 | ReactNode | - | 22 | | breadcrumbList | 面包屑数据,配置了此属性时 `routes` `params` `location` `breadcrumbNameMap` 无效 | array<{title: ReactNode, href?: string}> | - | 23 | | hiddenBreadcrumb |隐藏面包屑 | boolean | false | 24 | | routes | 面包屑相关属性,router 的路由栈信息 | object[] | - | 25 | | params | 面包屑相关属性,路由的参数 | object | - | 26 | | location | 面包屑相关属性,当前的路由信息 | object | - | 27 | | breadcrumbNameMap | 面包屑相关属性,路由的地址-名称映射表 | object | - | 28 | | tabList | tab 标题列表 | array<{key: string, tab: ReactNode}> | - | 29 | | tabActiveKey | 当前高亮的 tab 项 | string | - | 30 | | tabDefaultActiveKey | 默认高亮的 tab 项 | string | 第一项 | 31 | | wide | 是否定宽 | boolean | false | 32 | | onTabChange | 切换面板的回调 | (key) => void | - | 33 | | itemRender | 自定义节点方法 | (menuItem) => ReactNode | - | 34 | | linkElement | 定义链接的元素,默认为 `a`,可传入 react-router 的 Link | string\|ReactElement | - | 35 | 36 | > 面包屑的配置方式有三种,一是直接配置 `breadcrumbList`,二是结合 `react-router@2` `react-router@3`,配置 `routes` 及 `params` 实现,类似 [面包屑 Demo](https://ant.design/components/breadcrumb-cn/#components-breadcrumb-demo-router),三是结合 `react-router@4`,配置 `location` `breadcrumbNameMap`,优先级依次递减,脚手架中使用最后一种。 对于后两种用法,你也可以将 `routes` `params` 及 `location` `breadcrumbNameMap` 放到 context 中,组件会自动获取。 37 | -------------------------------------------------------------------------------- /src/components/PageHeader/index.test.js: -------------------------------------------------------------------------------- 1 | import { getBreadcrumb } from './breadcrumb'; 2 | import { urlToList } from '../_utils/pathTools'; 3 | 4 | const routerData = { 5 | '/dashboard/analysis': { 6 | name: '分析页', 7 | }, 8 | '/userinfo': { 9 | name: '用户列表', 10 | }, 11 | '/userinfo/:id': { 12 | name: '用户信息', 13 | }, 14 | '/userinfo/:id/addr': { 15 | name: '收货订单', 16 | }, 17 | }; 18 | describe('test getBreadcrumb', () => { 19 | it('Simple url', () => { 20 | expect(getBreadcrumb(routerData, '/dashboard/analysis').name).toEqual('分析页'); 21 | }); 22 | it('Parameters url', () => { 23 | expect(getBreadcrumb(routerData, '/userinfo/2144').name).toEqual('用户信息'); 24 | }); 25 | it('The middle parameter url', () => { 26 | expect(getBreadcrumb(routerData, '/userinfo/2144/addr').name).toEqual('收货订单'); 27 | }); 28 | it('Loop through the parameters', () => { 29 | const urlNameList = urlToList('/userinfo/2144/addr').map( 30 | url => getBreadcrumb(routerData, url).name 31 | ); 32 | expect(urlNameList).toEqual(['用户列表', '用户信息', '收货订单']); 33 | }); 34 | 35 | it('a path', () => { 36 | const urlNameList = urlToList('/userinfo').map(url => getBreadcrumb(routerData, url).name); 37 | expect(urlNameList).toEqual(['用户列表']); 38 | }); 39 | it('Secondary path', () => { 40 | const urlNameList = urlToList('/userinfo/2144').map(url => getBreadcrumb(routerData, url).name); 41 | expect(urlNameList).toEqual(['用户列表', '用户信息']); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/components/PageHeaderWrapper/GridContent.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { connect } from 'dva'; 3 | import styles from './GridContent.less'; 4 | 5 | class GridContent extends PureComponent { 6 | render() { 7 | const { contentWidth, children } = this.props; 8 | let className = `${styles.main}`; 9 | if (contentWidth === 'Fixed') { 10 | className = `${styles.main} ${styles.wide}`; 11 | } 12 | return
{children}
; 13 | } 14 | } 15 | 16 | export default connect(({ setting }) => ({ 17 | contentWidth: setting.contentWidth, 18 | }))(GridContent); 19 | -------------------------------------------------------------------------------- /src/components/PageHeaderWrapper/GridContent.less: -------------------------------------------------------------------------------- 1 | .main { 2 | width: 100%; 3 | height: 100%; 4 | min-height: 100%; 5 | transition: 0.3s; 6 | &.wide { 7 | max-width: 1200px; 8 | margin: 0 auto; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/PageHeaderWrapper/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormattedMessage } from 'umi/locale'; 3 | import Link from 'umi/link'; 4 | import PageHeader from '@/components/PageHeader'; 5 | import { connect } from 'dva'; 6 | import GridContent from './GridContent'; 7 | import styles from './index.less'; 8 | import MenuContext from '@/layouts/MenuContext'; 9 | 10 | const PageHeaderWrapper = ({ children, contentWidth, wrapperClassName, top, ...restProps }) => ( 11 |
12 | {top} 13 | 14 | {value => ( 15 | } 18 | {...value} 19 | key="pageheader" 20 | {...restProps} 21 | linkElement={Link} 22 | itemRender={item => { 23 | if (item.locale) { 24 | return ; 25 | } 26 | return item.name; 27 | }} 28 | /> 29 | )} 30 | 31 | {children ? ( 32 |
33 | {children} 34 |
35 | ) : null} 36 |
37 | ); 38 | 39 | export default connect(({ setting }) => ({ 40 | contentWidth: setting.contentWidth, 41 | }))(PageHeaderWrapper); 42 | -------------------------------------------------------------------------------- /src/components/PageHeaderWrapper/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .content { 4 | margin: 24px 24px 0; 5 | } 6 | 7 | @media screen and (max-width: @screen-sm) { 8 | .content { 9 | margin: 24px 0 0; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/PageLoading/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Spin } from 'antd'; 3 | 4 | // loading components from code split 5 | // https://umijs.org/plugin/umi-plugin-react.html#dynamicimport 6 | export default () => ( 7 |
8 | 9 |
10 | ); 11 | -------------------------------------------------------------------------------- /src/components/Result/demo/classic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: Classic 4 | --- 5 | 6 | 典型结果页面。 7 | 8 | ````jsx 9 | import Result from 'ant-design-pro/lib/Result'; 10 | import { Button, Row, Col, Icon, Steps } from 'antd'; 11 | 12 | const { Step } = Steps; 13 | 14 | const desc1 = ( 15 |
16 |
17 | 曲丽丽 18 | 19 |
20 |
2016-12-12 12:32
21 |
22 | ); 23 | 24 | const desc2 = ( 25 |
26 |
27 | 周毛毛 28 | 29 |
30 | 31 |
32 | ); 33 | 34 | const extra = ( 35 |
36 |
37 | 项目名称 38 |
39 | 40 | 41 | 项目 ID: 42 | 23421 43 | 44 | 45 | 负责人: 46 | 曲丽丽 47 | 48 | 49 | 生效时间: 50 | 2016-12-12 ~ 2017-12-12 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
60 | ); 61 | 62 | const actions = ( 63 |
64 | 65 | 66 | 67 |
68 | ); 69 | 70 | ReactDOM.render( 71 | 79 | , mountNode); 80 | ```` 81 | -------------------------------------------------------------------------------- /src/components/Result/demo/error.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | title: Failed 4 | --- 5 | 6 | 提交失败。 7 | 8 | ````jsx 9 | import Result from 'ant-design-pro/lib/Result'; 10 | import { Button, Icon } from 'antd'; 11 | 12 | const extra = ( 13 |
14 |
15 | 您提交的内容有如下错误: 16 |
17 |
18 | 您的账户已被冻结 19 | 立即解冻 20 |
21 |
22 | 您的账户还不具备申请资格 23 | 立即升级 24 |
25 |
26 | ); 27 | 28 | const actions = ; 29 | 30 | ReactDOM.render( 31 | 38 | , mountNode); 39 | ```` 40 | -------------------------------------------------------------------------------- /src/components/Result/demo/structure.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: Structure 4 | --- 5 | 6 | 结构包含 `处理结果`,`补充信息` 以及 `操作建议` 三个部分,其中 `处理结果` 由 `提示图标`,`标题` 和 `结果描述` 组成。 7 | 8 | ````jsx 9 | import Result from 'ant-design-pro/lib/Result'; 10 | 11 | ReactDOM.render( 12 | 标题
} 15 | description={
结果描述
} 16 | extra="其他补充信息,自带灰底效果" 17 | actions={
操作建议,一般放置按钮组
} 18 | /> 19 | , mountNode); 20 | ```` 21 | -------------------------------------------------------------------------------- /src/components/Result/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IResultProps { 3 | type: 'success' | 'error'; 4 | title: React.ReactNode; 5 | description?: React.ReactNode; 6 | extra?: React.ReactNode; 7 | actions?: React.ReactNode; 8 | style?: React.CSSProperties; 9 | } 10 | 11 | export default class Result extends React.Component {} 12 | -------------------------------------------------------------------------------- /src/components/Result/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import { Icon } from 'antd'; 4 | import styles from './index.less'; 5 | 6 | export default function Result({ 7 | className, 8 | type, 9 | title, 10 | description, 11 | extra, 12 | actions, 13 | ...restProps 14 | }) { 15 | const iconMap = { 16 | error: , 17 | success: , 18 | }; 19 | const clsString = classNames(styles.result, className); 20 | return ( 21 |
22 |
{iconMap[type]}
23 |
{title}
24 | {description &&
{description}
} 25 | {extra &&
{extra}
} 26 | {actions &&
{actions}
} 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/Result/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .result { 4 | text-align: center; 5 | width: 72%; 6 | margin: 0 auto; 7 | @media screen and (max-width: @screen-xs) { 8 | width: 100%; 9 | } 10 | 11 | .icon { 12 | font-size: 72px; 13 | line-height: 72px; 14 | margin-bottom: 24px; 15 | 16 | & > .success { 17 | color: @success-color; 18 | } 19 | 20 | & > .error { 21 | color: @error-color; 22 | } 23 | } 24 | 25 | .title { 26 | font-size: 24px; 27 | color: @heading-color; 28 | font-weight: 500; 29 | line-height: 32px; 30 | margin-bottom: 16px; 31 | } 32 | 33 | .description { 34 | font-size: 14px; 35 | line-height: 22px; 36 | color: @text-color-secondary; 37 | margin-bottom: 24px; 38 | } 39 | 40 | .extra { 41 | background: #fafafa; 42 | padding: 24px 40px; 43 | border-radius: @border-radius-sm; 44 | text-align: left; 45 | 46 | @media screen and (max-width: @screen-xs) { 47 | padding: 18px 20px; 48 | } 49 | } 50 | 51 | .actions { 52 | margin-top: 32px; 53 | 54 | button:not(:last-child) { 55 | margin-right: 8px; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/components/Result/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: Result 4 | zh-CN: Result 5 | subtitle: 处理结果 6 | cols: 1 7 | order: 12 8 | --- 9 | 10 | 结果页用于对用户进行的一系列任务处理结果进行反馈。 11 | 12 | ## API 13 | 14 | | 参数 | 说明 | 类型 | 默认值 | 15 | |----------|------------------------------------------|-------------|-------| 16 | | type | 类型,不同类型自带对应的图标 | Enum {'success', 'error'} | - | 17 | | title | 标题 | ReactNode | - | 18 | | description | 结果描述 | ReactNode | - | 19 | | extra | 补充信息,有默认的灰色背景 | ReactNode | - | 20 | | actions | 操作建议,推荐放置跳转链接,按钮组等 | ReactNode | - | 21 | -------------------------------------------------------------------------------- /src/components/SelectLang/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { FormattedMessage, setLocale, getLocale } from 'umi/locale'; 3 | import { Menu, Icon, Dropdown } from 'antd'; 4 | import classNames from 'classnames'; 5 | import styles from './index.less'; 6 | 7 | export default class SelectLang extends PureComponent { 8 | changLang = ({ key }) => { 9 | setLocale(key); 10 | }; 11 | 12 | render() { 13 | const { className } = this.props; 14 | const selectedLang = getLocale(); 15 | const langMenu = ( 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | return ( 32 | 33 | 34 | 35 | 36 | 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/SelectLang/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .menu { 4 | :global(.anticon) { 5 | margin-right: 8px; 6 | } 7 | :global(.ant-dropdown-menu-item) { 8 | width: 160px; 9 | } 10 | } 11 | 12 | .dropDown { 13 | cursor: pointer; 14 | } 15 | -------------------------------------------------------------------------------- /src/components/SettingDrawer/BlockChecbox.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tooltip, Icon } from 'antd'; 3 | import style from './index.less'; 4 | 5 | const BlockChecbox = ({ value, onChange, list }) => ( 6 |
7 | {list.map(item => ( 8 | 9 |
onChange(item.key)}> 10 | {item.key} 11 |
17 | 18 |
19 |
20 |
21 | ))} 22 |
23 | ); 24 | 25 | export default BlockChecbox; 26 | -------------------------------------------------------------------------------- /src/components/SettingDrawer/ThemeColor.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tooltip, Icon } from 'antd'; 3 | import { formatMessage } from 'umi/locale'; 4 | import styles from './ThemeColor.less'; 5 | 6 | const Tag = ({ color, check, ...rest }) => ( 7 |
13 | {check ? : ''} 14 |
15 | ); 16 | 17 | const ThemeColor = ({ colors, title, value, onChange }) => { 18 | let colorList = colors; 19 | if (!colors) { 20 | colorList = [ 21 | { 22 | key: 'dust', 23 | color: '#F5222D', 24 | }, 25 | { 26 | key: 'volcano', 27 | color: '#FA541C', 28 | }, 29 | { 30 | key: 'sunset', 31 | color: '#FAAD14', 32 | }, 33 | { 34 | key: 'cyan', 35 | color: '#13C2C2', 36 | }, 37 | { 38 | key: 'green', 39 | color: '#52C41A', 40 | }, 41 | { 42 | key: 'daybreak', 43 | color: '#1890FF', 44 | }, 45 | { 46 | key: 'geekblue', 47 | color: '#2F54EB', 48 | }, 49 | { 50 | key: 'purple', 51 | color: '#722ED1', 52 | }, 53 | ]; 54 | } 55 | return ( 56 |
57 |

{title}

58 |
59 | {colorList.map(({ key, color }) => ( 60 | 61 | onChange && onChange(color)} 66 | /> 67 | 68 | ))} 69 |
70 |
71 | ); 72 | }; 73 | 74 | export default ThemeColor; 75 | -------------------------------------------------------------------------------- /src/components/SettingDrawer/ThemeColor.less: -------------------------------------------------------------------------------- 1 | .themeColor { 2 | overflow: hidden; 3 | margin-top: 24px; 4 | .title { 5 | font-size: 14px; 6 | color: rgba(0, 0, 0, 0.65); 7 | line-height: 22px; 8 | margin-bottom: 12px; 9 | } 10 | .colorBlock { 11 | width: 20px; 12 | height: 20px; 13 | border-radius: 2px; 14 | float: left; 15 | cursor: pointer; 16 | margin-right: 8px; 17 | text-align: center; 18 | color: #fff; 19 | font-weight: bold; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/SettingDrawer/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .content { 4 | min-height: 100%; 5 | background: #fff; 6 | position: relative; 7 | } 8 | 9 | .blockChecbox { 10 | display: flex; 11 | .item { 12 | margin-right: 16px; 13 | position: relative; 14 | // box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); 15 | border-radius: @border-radius-base; 16 | cursor: pointer; 17 | img { 18 | width: 48px; 19 | } 20 | } 21 | .selectIcon { 22 | position: absolute; 23 | top: 0; 24 | right: 0; 25 | width: 100%; 26 | padding-top: 15px; 27 | padding-left: 24px; 28 | height: 100%; 29 | color: @primary-color; 30 | font-size: 14px; 31 | font-weight: bold; 32 | } 33 | } 34 | 35 | .color_block { 36 | width: 38px; 37 | height: 22px; 38 | margin: 4px; 39 | border-radius: 4px; 40 | cursor: pointer; 41 | margin-right: 12px; 42 | display: inline-block; 43 | vertical-align: middle; 44 | } 45 | 46 | .title { 47 | font-size: 14px; 48 | color: @heading-color; 49 | line-height: 22px; 50 | margin-bottom: 12px; 51 | } 52 | 53 | .handle { 54 | position: absolute; 55 | top: 240px; 56 | background: @primary-color; 57 | width: 48px; 58 | height: 48px; 59 | right: 300px; 60 | display: flex; 61 | justify-content: center; 62 | align-items: center; 63 | cursor: pointer; 64 | pointer-events: auto; 65 | z-index: 0; 66 | text-align: center; 67 | font-size: 16px; 68 | border-radius: 4px 0 0 4px; 69 | } 70 | 71 | .productionHint { 72 | font-size: 12px; 73 | margin-top: 16px; 74 | } 75 | -------------------------------------------------------------------------------- /src/components/SiderMenu/SiderMenu.test.js: -------------------------------------------------------------------------------- 1 | import { urlToList } from '../_utils/pathTools'; 2 | import { getFlatMenuKeys, getMenuMatchKeys } from './SiderMenu'; 3 | 4 | const menu = [ 5 | { 6 | path: '/dashboard', 7 | children: [ 8 | { 9 | path: '/dashboard/name', 10 | }, 11 | ], 12 | }, 13 | { 14 | path: '/userinfo', 15 | children: [ 16 | { 17 | path: '/userinfo/:id', 18 | children: [ 19 | { 20 | path: '/userinfo/:id/info', 21 | }, 22 | ], 23 | }, 24 | ], 25 | }, 26 | ]; 27 | 28 | const flatMenuKeys = getFlatMenuKeys(menu); 29 | 30 | describe('test convert nested menu to flat menu', () => { 31 | it('simple menu', () => { 32 | expect(flatMenuKeys).toEqual([ 33 | '/dashboard', 34 | '/dashboard/name', 35 | '/userinfo', 36 | '/userinfo/:id', 37 | '/userinfo/:id/info', 38 | ]); 39 | }); 40 | }); 41 | 42 | describe('test menu match', () => { 43 | it('simple path', () => { 44 | expect(getMenuMatchKeys(flatMenuKeys, urlToList('/dashboard'))).toEqual(['/dashboard']); 45 | }); 46 | 47 | it('error path', () => { 48 | expect(getMenuMatchKeys(flatMenuKeys, urlToList('/dashboardname'))).toEqual([]); 49 | }); 50 | 51 | it('Secondary path', () => { 52 | expect(getMenuMatchKeys(flatMenuKeys, urlToList('/dashboard/name'))).toEqual([ 53 | '/dashboard', 54 | '/dashboard/name', 55 | ]); 56 | }); 57 | 58 | it('Parameter path', () => { 59 | expect(getMenuMatchKeys(flatMenuKeys, urlToList('/userinfo/2144'))).toEqual([ 60 | '/userinfo', 61 | '/userinfo/:id', 62 | ]); 63 | }); 64 | 65 | it('three parameter path', () => { 66 | expect(getMenuMatchKeys(flatMenuKeys, urlToList('/userinfo/2144/info'))).toEqual([ 67 | '/userinfo', 68 | '/userinfo/:id', 69 | '/userinfo/:id/info', 70 | ]); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /src/components/SiderMenu/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Drawer } from 'antd'; 3 | import SiderMenu from './SiderMenu'; 4 | 5 | /** 6 | * Recursively flatten the data 7 | * [{path:string},{path:string}] => {path,path2} 8 | * @param menus 9 | */ 10 | const getFlatMenuKeys = menuData => { 11 | let keys = []; 12 | menuData.forEach(item => { 13 | if (item.children) { 14 | keys = keys.concat(getFlatMenuKeys(item.children)); 15 | } 16 | keys.push(item.path); 17 | }); 18 | return keys; 19 | }; 20 | 21 | const SiderMenuWrapper = props => { 22 | const { isMobile, menuData, collapsed, onCollapse } = props; 23 | return isMobile ? ( 24 | onCollapse(true)} 28 | style={{ 29 | padding: 0, 30 | height: '100vh', 31 | }} 32 | > 33 | 38 | 39 | ) : ( 40 | 41 | ); 42 | }; 43 | 44 | export default SiderMenuWrapper; 45 | -------------------------------------------------------------------------------- /src/components/SiderMenu/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | @nav-header-height: 64px; 4 | 5 | .logo { 6 | height: @nav-header-height; 7 | position: relative; 8 | line-height: @nav-header-height; 9 | padding-left: (@menu-collapsed-width - 32px) / 2; 10 | transition: all 0.3s; 11 | background: #002140; 12 | overflow: hidden; 13 | img { 14 | display: inline-block; 15 | vertical-align: middle; 16 | height: 32px; 17 | } 18 | h1 { 19 | color: white; 20 | display: inline-block; 21 | vertical-align: middle; 22 | font-size: 20px; 23 | margin: 0 0 0 12px; 24 | font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif; 25 | font-weight: 600; 26 | } 27 | } 28 | 29 | .sider { 30 | min-height: 100vh; 31 | box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); 32 | position: relative; 33 | z-index: 9; 34 | &.fixSiderbar { 35 | position: fixed; 36 | top: 0; 37 | left: 0; 38 | :global(.ant-menu-root) { 39 | overflow-y: auto; 40 | height: ~'calc(100vh - @{nav-header-height})'; 41 | } 42 | } 43 | &.light { 44 | box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05); 45 | background-color: white; 46 | .logo { 47 | background: white; 48 | box-shadow: 1px 1px 0 0 @border-color-split; 49 | h1 { 50 | color: @primary-color; 51 | } 52 | } 53 | :global(.ant-menu-light) { 54 | border-right-color: transparent; 55 | } 56 | } 57 | } 58 | 59 | .icon { 60 | width: 14px; 61 | margin-right: 10px; 62 | } 63 | 64 | :global { 65 | .top-nav-menu li.ant-menu-item { 66 | height: @nav-header-height; 67 | line-height: @nav-header-height; 68 | } 69 | .drawer .drawer-content { 70 | background: #001529; 71 | } 72 | .ant-menu-inline-collapsed { 73 | & > .ant-menu-item .sider-menu-item-img + span, 74 | & 75 | > .ant-menu-item-group 76 | > .ant-menu-item-group-list 77 | > .ant-menu-item 78 | .sider-menu-item-img 79 | + span, 80 | & > .ant-menu-submenu > .ant-menu-submenu-title .sider-menu-item-img + span { 81 | max-width: 0; 82 | display: inline-block; 83 | opacity: 0; 84 | } 85 | } 86 | .ant-menu-item .sider-menu-item-img + span, 87 | .ant-menu-submenu-title .sider-menu-item-img + span { 88 | transition: opacity 0.3s @ease-in-out, width 0.3s @ease-in-out; 89 | opacity: 1; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/components/StandardTable/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .standardTable { 4 | :global { 5 | .ant-table-pagination { 6 | margin-top: 24px; 7 | } 8 | } 9 | 10 | .tableAlert { 11 | margin-bottom: 16px; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/TopNavHeader/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import Link from 'umi/link'; 3 | import RightContent from '../GlobalHeader/RightContent'; 4 | import BaseMenu from '../SiderMenu/BaseMenu'; 5 | import styles from './index.less'; 6 | 7 | export default class TopNavHeader extends PureComponent { 8 | constructor(props) { 9 | super(props); 10 | 11 | this.state = { 12 | maxWidth: (props.contentWidth === 'Fixed' ? 1200 : window.innerWidth) - 330 - 165 - 4 - 36, 13 | }; 14 | } 15 | 16 | static getDerivedStateFromProps(props) { 17 | return { 18 | maxWidth: (props.contentWidth === 'Fixed' ? 1200 : window.innerWidth) - 330 - 165 - 4 - 36, 19 | }; 20 | } 21 | 22 | render() { 23 | const { theme, contentWidth, logo } = this.props; 24 | const { maxWidth } = this.state; 25 | return ( 26 |
27 |
{ 29 | this.maim = ref; 30 | }} 31 | className={`${styles.main} ${contentWidth === 'Fixed' ? styles.wide : ''}`} 32 | > 33 |
34 | 40 |
45 | 46 |
47 |
48 | 49 |
50 |
51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/components/TopNavHeader/index.less: -------------------------------------------------------------------------------- 1 | .head { 2 | width: 100%; 3 | transition: background 0.3s, width 0.2s; 4 | height: 64px; 5 | padding: 0 12px 0 0; 6 | box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); 7 | position: relative; 8 | :global { 9 | .ant-menu-submenu.ant-menu-submenu-horizontal { 10 | height: 100%; 11 | padding-top: 9px; 12 | .ant-menu-submenu-title { 13 | height: 100%; 14 | } 15 | } 16 | } 17 | &.light { 18 | background-color: #fff; 19 | } 20 | .main { 21 | display: flex; 22 | height: 64px; 23 | padding-left: 24px; 24 | &.wide { 25 | max-width: 1200px; 26 | margin: auto; 27 | padding-left: 4px; 28 | } 29 | .left { 30 | flex: 1; 31 | display: flex; 32 | } 33 | .right { 34 | width: 324px; 35 | } 36 | } 37 | } 38 | 39 | .logo { 40 | width: 165px; 41 | height: 64px; 42 | position: relative; 43 | line-height: 64px; 44 | transition: all 0.3s; 45 | overflow: hidden; 46 | img { 47 | display: inline-block; 48 | vertical-align: middle; 49 | height: 32px; 50 | } 51 | h1 { 52 | color: #fff; 53 | display: inline-block; 54 | vertical-align: middle; 55 | font-size: 16px; 56 | margin: 0 0 0 12px; 57 | font-weight: 400; 58 | } 59 | } 60 | 61 | .light { 62 | h1 { 63 | color: #002140; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/components/_utils/pathTools.js: -------------------------------------------------------------------------------- 1 | // /userinfo/2144/id => ['/userinfo','/useinfo/2144,'/userindo/2144/id'] 2 | // eslint-disable-next-line import/prefer-default-export 3 | export function urlToList(url) { 4 | const urllist = url.split('/').filter(i => i); 5 | return urllist.map((urlItem, index) => `/${urllist.slice(0, index + 1).join('/')}`); 6 | } 7 | -------------------------------------------------------------------------------- /src/components/_utils/pathTools.test.js: -------------------------------------------------------------------------------- 1 | import { urlToList } from './pathTools'; 2 | 3 | describe('test urlToList', () => { 4 | it('A path', () => { 5 | expect(urlToList('/userinfo')).toEqual(['/userinfo']); 6 | }); 7 | it('Secondary path', () => { 8 | expect(urlToList('/userinfo/2144')).toEqual(['/userinfo', '/userinfo/2144']); 9 | }); 10 | it('Three paths', () => { 11 | expect(urlToList('/userinfo/2144/addr')).toEqual([ 12 | '/userinfo', 13 | '/userinfo/2144', 14 | '/userinfo/2144/addr', 15 | ]); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/defaultSettings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | navTheme: 'dark', // theme for nav menu 3 | primaryColor: '#1890FF', // primary color of ant design 4 | layout: 'sidemenu', // nav menu position: sidemenu or topmenu 5 | contentWidth: 'Fluid', // layout of content: Fluid or Fixed, only works when layout is topmenu 6 | fixedHeader: false, // sticky header 7 | autoHideHeader: false, // auto hide header 8 | fixSiderbar: false, // sticky siderbar 9 | }; 10 | -------------------------------------------------------------------------------- /src/e2e/home.e2e.js: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | 3 | describe('Homepage', () => { 4 | it('it should have logo text', async () => { 5 | const browser = await puppeteer.launch({ args: ['--no-sandbox'] }); 6 | const page = await browser.newPage(); 7 | await page.goto('http://localhost:8000', { waitUntil: 'networkidle2' }); 8 | await page.waitForSelector('#logo h1'); 9 | const text = await page.evaluate(() => document.body.innerHTML); 10 | expect(text).toContain('

Ant Design Pro

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

Ant Design Pro

'); 39 | }); 40 | 41 | afterAll(() => browser.close()); 42 | }); 43 | -------------------------------------------------------------------------------- /src/global.less: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #root { 4 | height: 100%; 5 | } 6 | 7 | .colorWeak { 8 | filter: invert(80%); 9 | } 10 | 11 | .ant-layout { 12 | min-height: 100vh; 13 | } 14 | 15 | canvas { 16 | display: block; 17 | } 18 | 19 | body { 20 | text-rendering: optimizeLegibility; 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | } 24 | 25 | .globalSpin { 26 | width: 100%; 27 | margin: 40px 0 !important; 28 | } 29 | 30 | ul, 31 | ol { 32 | list-style: none; 33 | } 34 | .ant-modal-title { 35 | margin: 0; 36 | font-size: 16px; 37 | line-height: 22px; 38 | font-weight: 500; 39 | text-align: center; 40 | color: rgba(0, 0, 0, 0.85); 41 | } 42 | -------------------------------------------------------------------------------- /src/layouts/BlankLayout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default props =>
; 4 | -------------------------------------------------------------------------------- /src/layouts/Footer.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import { Layout, Icon } from 'antd'; 3 | import GlobalFooter from '@/components/GlobalFooter'; 4 | 5 | const { Footer } = Layout; 6 | const FooterView = () => ( 7 |
8 | , 19 | href: 'https://github.com/biaochenxuying/blog-react-admin', 20 | blankTarget: true, 21 | }, 22 | { 23 | key: 'Ant Design', 24 | title: 'Ant Design', 25 | href: 'https://ant.design', 26 | blankTarget: true, 27 | }, 28 | ]} 29 | copyright={ 30 | 31 | Copyright BiaoChenXuYing 32 | 33 | } 34 | /> 35 |
36 | ); 37 | export default FooterView; 38 | -------------------------------------------------------------------------------- /src/layouts/Header.less: -------------------------------------------------------------------------------- 1 | .fixedHeader { 2 | position: fixed; 3 | top: 0; 4 | right: 0; 5 | width: 100%; 6 | z-index: 9; 7 | transition: width 0.2s; 8 | } 9 | -------------------------------------------------------------------------------- /src/layouts/MenuContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export default createContext(); 4 | -------------------------------------------------------------------------------- /src/layouts/UserLayout.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import { formatMessage } from 'umi/locale'; 3 | import Link from 'umi/link'; 4 | import { Icon } from 'antd'; 5 | import GlobalFooter from '@/components/GlobalFooter'; 6 | import SelectLang from '@/components/SelectLang'; 7 | import styles from './UserLayout.less'; 8 | import logo from '../assets/logo.svg'; 9 | 10 | const links = [ 11 | { 12 | key: 'help', 13 | title: formatMessage({ id: 'layout.user.link.help' }), 14 | href: '', 15 | }, 16 | { 17 | key: 'privacy', 18 | title: formatMessage({ id: 'layout.user.link.privacy' }), 19 | href: '', 20 | }, 21 | { 22 | key: 'terms', 23 | title: formatMessage({ id: 'layout.user.link.terms' }), 24 | href: '', 25 | }, 26 | ]; 27 | 28 | const copyright = ( 29 | 30 | Copyright 2018 蚂蚁金服体验技术部出品 31 | 32 | ); 33 | 34 | class UserLayout extends React.PureComponent { 35 | // @TODO title 36 | // getPageTitle() { 37 | // const { routerData, location } = this.props; 38 | // const { pathname } = location; 39 | // let title = 'Ant Design Pro'; 40 | // if (routerData[pathname] && routerData[pathname].name) { 41 | // title = `${routerData[pathname].name} - Ant Design Pro`; 42 | // } 43 | // return title; 44 | // } 45 | 46 | render() { 47 | const { children } = this.props; 48 | return ( 49 | // @TODO 50 |
51 |
52 | 53 |
54 |
55 |
56 |
57 | 58 | logo 59 | Ant Design 60 | 61 |
62 |
Ant Design 是西湖区最具影响力的 Web 设计规范
63 |
64 | {children} 65 |
66 | 67 |
68 | ); 69 | } 70 | } 71 | 72 | export default UserLayout; 73 | -------------------------------------------------------------------------------- /src/layouts/UserLayout.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .container { 4 | display: flex; 5 | flex-direction: column; 6 | height: 100vh; 7 | overflow: auto; 8 | background: @layout-body-background; 9 | } 10 | 11 | .lang { 12 | text-align: right; 13 | width: 100%; 14 | height: 40px; 15 | line-height: 44px; 16 | :global(.ant-dropdown-trigger) { 17 | margin-right: 24px; 18 | } 19 | } 20 | 21 | .content { 22 | padding: 32px 0; 23 | flex: 1; 24 | } 25 | 26 | @media (min-width: @screen-md-min) { 27 | .container { 28 | background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg'); 29 | background-repeat: no-repeat; 30 | background-position: center 110px; 31 | background-size: 100%; 32 | } 33 | 34 | .content { 35 | padding: 72px 0 24px 0; 36 | } 37 | } 38 | 39 | .top { 40 | text-align: center; 41 | } 42 | 43 | .header { 44 | height: 44px; 45 | line-height: 44px; 46 | a { 47 | text-decoration: none; 48 | } 49 | } 50 | 51 | .logo { 52 | height: 44px; 53 | vertical-align: top; 54 | margin-right: 16px; 55 | } 56 | 57 | .title { 58 | font-size: 33px; 59 | color: @heading-color; 60 | font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif; 61 | font-weight: 600; 62 | position: relative; 63 | top: 2px; 64 | } 65 | 66 | .desc { 67 | font-size: @font-size-base; 68 | color: @text-color-secondary; 69 | margin-top: 12px; 70 | margin-bottom: 40px; 71 | } 72 | -------------------------------------------------------------------------------- /src/models/category.js: -------------------------------------------------------------------------------- 1 | import { queryCategory, addCategory, delCategory } from '@/services/api'; 2 | 3 | export default { 4 | namespace: 'category', 5 | 6 | state: { 7 | categoryList: [], 8 | total: 0, 9 | }, 10 | 11 | effects: { 12 | *queryCategory({ payload }, { call, put }) { 13 | const { resolve, params } = payload; 14 | const response = yield call(queryCategory, params); 15 | !!resolve && resolve(response); // 返回数据 16 | // console.log('response :', response) 17 | if (response.code === 0) { 18 | yield put({ 19 | type: 'saveCategoryList', 20 | payload: response.data.list, 21 | }); 22 | yield put({ 23 | type: 'saveCategoryListTotal', 24 | payload: response.data.count, 25 | }); 26 | } else { 27 | // 28 | } 29 | }, 30 | *addCategory({ payload }, { call, put }) { 31 | const { resolve, params } = payload; 32 | const response = yield call(addCategory, params); 33 | !!resolve && resolve(response); 34 | }, 35 | *delCategory({ payload }, { call, put }) { 36 | const { resolve, params } = payload; 37 | const response = yield call(delCategory, params); 38 | !!resolve && resolve(response); 39 | }, 40 | }, 41 | 42 | reducers: { 43 | saveCategoryList(state, { payload }) { 44 | return { 45 | ...state, 46 | categoryList: payload, 47 | }; 48 | }, 49 | saveCategoryListTotal(state, { payload }) { 50 | return { 51 | ...state, 52 | total: payload, 53 | }; 54 | }, 55 | }, 56 | }; 57 | -------------------------------------------------------------------------------- /src/models/global.js: -------------------------------------------------------------------------------- 1 | import { queryNotices } from '@/services/api'; 2 | 3 | export default { 4 | namespace: 'global', 5 | 6 | state: { 7 | collapsed: false, 8 | notices: [], 9 | }, 10 | 11 | effects: { 12 | *fetchNotices(_, { call, put }) { 13 | const data = yield call(queryNotices); 14 | yield put({ 15 | type: 'saveNotices', 16 | payload: data, 17 | }); 18 | yield put({ 19 | type: 'user/changeNotifyCount', 20 | payload: data.length, 21 | }); 22 | }, 23 | *clearNotices({ payload }, { put, select }) { 24 | yield put({ 25 | type: 'saveClearedNotices', 26 | payload, 27 | }); 28 | const count = yield select(state => state.global.notices.length); 29 | yield put({ 30 | type: 'user/changeNotifyCount', 31 | payload: count, 32 | }); 33 | }, 34 | }, 35 | 36 | reducers: { 37 | changeLayoutCollapsed(state, { payload }) { 38 | return { 39 | ...state, 40 | collapsed: payload, 41 | }; 42 | }, 43 | saveNotices(state, { payload }) { 44 | return { 45 | ...state, 46 | notices: payload, 47 | }; 48 | }, 49 | saveClearedNotices(state, { payload }) { 50 | return { 51 | ...state, 52 | notices: state.notices.filter(item => item.type !== payload), 53 | }; 54 | }, 55 | }, 56 | 57 | subscriptions: { 58 | setup({ history }) { 59 | // Subscribe history(url) change, trigger `load` action if pathname is `/` 60 | return history.listen(({ pathname, search }) => { 61 | if (typeof window.ga !== 'undefined') { 62 | window.ga('send', 'pageview', pathname + search); 63 | } 64 | }); 65 | }, 66 | }, 67 | }; 68 | -------------------------------------------------------------------------------- /src/models/link.js: -------------------------------------------------------------------------------- 1 | import { queryLink, addLink, updateLink,delLink } from '@/services/api'; 2 | 3 | export default { 4 | namespace: 'link', 5 | 6 | state: { 7 | linkList: [], 8 | total: 0, 9 | }, 10 | 11 | effects: { 12 | *queryLink({ payload }, { call, put }) { 13 | const { resolve, params } = payload; 14 | const response = yield call(queryLink, params); 15 | !!resolve && resolve(response); // 返回数据 16 | // console.log('response :', response) 17 | if (response.code === 0) { 18 | yield put({ 19 | type: 'saveLinkList', 20 | payload: response.data.list, 21 | }); 22 | yield put({ 23 | type: 'saveLinkListTotal', 24 | payload: response.data.count, 25 | }); 26 | } else { 27 | // 28 | } 29 | }, 30 | *addLink({ payload }, { call, put }) { 31 | const { resolve, params } = payload; 32 | const response = yield call(addLink, params); 33 | !!resolve && resolve(response); 34 | }, 35 | *updateLink({ payload }, { call, put }) { 36 | const { resolve, params } = payload; 37 | const response = yield call(updateLink, params); 38 | !!resolve && resolve(response); 39 | }, 40 | *delLink({ payload }, { call, put }) { 41 | const { resolve, params } = payload; 42 | const response = yield call(delLink, params); 43 | !!resolve && resolve(response); 44 | }, 45 | }, 46 | 47 | reducers: { 48 | saveLinkList(state, { payload }) { 49 | return { 50 | ...state, 51 | linkList: payload, 52 | }; 53 | }, 54 | saveLinkListTotal(state, { payload }) { 55 | return { 56 | ...state, 57 | total: payload, 58 | }; 59 | }, 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /src/models/list.js: -------------------------------------------------------------------------------- 1 | import { queryFakeList, removeFakeList, addFakeList, updateFakeList } from '@/services/api'; 2 | 3 | export default { 4 | namespace: 'list', 5 | 6 | state: { 7 | list: [], 8 | }, 9 | 10 | effects: { 11 | *fetch({ payload }, { call, put }) { 12 | const response = yield call(queryFakeList, payload); 13 | yield put({ 14 | type: 'queryList', 15 | payload: Array.isArray(response) ? response : [], 16 | }); 17 | }, 18 | *appendFetch({ payload }, { call, put }) { 19 | const response = yield call(queryFakeList, payload); 20 | yield put({ 21 | type: 'appendList', 22 | payload: Array.isArray(response) ? response : [], 23 | }); 24 | }, 25 | *submit({ payload }, { call, put }) { 26 | let callback; 27 | if (payload.id) { 28 | callback = Object.keys(payload).length === 1 ? removeFakeList : updateFakeList; 29 | } else { 30 | callback = addFakeList; 31 | } 32 | const response = yield call(callback, payload); // post 33 | yield put({ 34 | type: 'queryList', 35 | payload: response, 36 | }); 37 | }, 38 | }, 39 | 40 | reducers: { 41 | queryList(state, action) { 42 | return { 43 | ...state, 44 | list: action.payload, 45 | }; 46 | }, 47 | appendList(state, action) { 48 | return { 49 | ...state, 50 | list: state.list.concat(action.payload), 51 | }; 52 | }, 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /src/models/message.js: -------------------------------------------------------------------------------- 1 | import { queryMessage, delMessage, getMessageDetail, addReplyMessage } from '@/services/api'; 2 | 3 | export default { 4 | namespace: 'message', 5 | 6 | state: { 7 | messageList: [], 8 | total: 0, 9 | messageDetail: { 10 | avatar: 'user', 11 | content: '.....留言', 12 | reply_list: [], 13 | create_time: '2018-11-04T12:05:10.761Z', 14 | email: '13800138000', 15 | id: 15, 16 | introduce: 'introduce', 17 | name: '虚影', 18 | phone: '1380013800', 19 | state: 0, 20 | update_time: '2018-11-04T12:05:10.761Z', 21 | user_id: '5bd9a84c2758be723f5ef2cb', 22 | __v: 0, 23 | _id: '5bdee076bc454f49bba03ab0', 24 | }, 25 | }, 26 | 27 | effects: { 28 | *queryMessage({ payload }, { call, put }) { 29 | const { resolve, params } = payload; 30 | const response = yield call(queryMessage, params); 31 | !!resolve && resolve(response); // 返回数据 32 | // console.log('response :', response) 33 | if (response.code === 0) { 34 | yield put({ 35 | type: 'saveMessageList', 36 | payload: response.data.list, 37 | }); 38 | yield put({ 39 | type: 'saveMessageListTotal', 40 | payload: response.data.count, 41 | }); 42 | } 43 | }, 44 | *delMessage({ payload }, { call, put }) { 45 | const { resolve, params } = payload; 46 | const response = yield call(delMessage, params); 47 | !!resolve && resolve(response); 48 | }, 49 | *addReplyMessage({ payload }, { call, put }) { 50 | const { resolve, params } = payload; 51 | const response = yield call(addReplyMessage, params); 52 | !!resolve && resolve(response); 53 | }, 54 | *getMessageDetail({ payload }, { call, put }) { 55 | const { resolve, params } = payload; 56 | const response = yield call(getMessageDetail, params); 57 | !!resolve && resolve(response); 58 | // console.log('response :', response) 59 | if (response.code === 0) { 60 | yield put({ 61 | type: 'saveMessageDetail', 62 | payload: response.data, 63 | }); 64 | } 65 | }, 66 | }, 67 | 68 | reducers: { 69 | saveMessageList(state, { payload }) { 70 | return { 71 | ...state, 72 | messageList: payload, 73 | }; 74 | }, 75 | saveMessageListTotal(state, { payload }) { 76 | return { 77 | ...state, 78 | total: payload, 79 | }; 80 | }, 81 | saveMessageDetail(state, { payload }) { 82 | return { 83 | ...state, 84 | messageDetail: payload, 85 | }; 86 | }, 87 | }, 88 | }; 89 | -------------------------------------------------------------------------------- /src/models/otherUser.js: -------------------------------------------------------------------------------- 1 | import { queryUser, addUser, updateUser,delUser } from '@/services/api'; 2 | 3 | export default { 4 | namespace: 'otherUser', 5 | 6 | state: { 7 | userList: [], 8 | total: 0, 9 | }, 10 | 11 | effects: { 12 | *queryUser({ payload }, { call, put }) { 13 | const { resolve, params } = payload; 14 | const response = yield call(queryUser, params); 15 | !!resolve && resolve(response); // 返回数据 16 | // console.log('response :', response) 17 | if (response.code === 0) { 18 | yield put({ 19 | type: 'saveUserList', 20 | payload: response.data.list, 21 | }); 22 | yield put({ 23 | type: 'saveUserListTotal', 24 | payload: response.data.count, 25 | }); 26 | } else { 27 | // 28 | } 29 | }, 30 | *addUser({ payload }, { call, put }) { 31 | const { resolve, params } = payload; 32 | const response = yield call(addUser, params); 33 | !!resolve && resolve(response); 34 | }, 35 | *updateUser({ payload }, { call, put }) { 36 | const { resolve, params } = payload; 37 | const response = yield call(updateUser, params); 38 | !!resolve && resolve(response); 39 | }, 40 | *delUser({ payload }, { call, put }) { 41 | const { resolve, params } = payload; 42 | const response = yield call(delUser, params); 43 | !!resolve && resolve(response); 44 | }, 45 | }, 46 | 47 | reducers: { 48 | saveUserList(state, { payload }) { 49 | return { 50 | ...state, 51 | userList: payload, 52 | }; 53 | }, 54 | saveUserListTotal(state, { payload }) { 55 | return { 56 | ...state, 57 | total: payload, 58 | }; 59 | }, 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /src/models/project.js: -------------------------------------------------------------------------------- 1 | import { queryProjectNotice,queryProject, delProject, updateProject, addProject,getProjectDetail } from '@/services/api'; 2 | 3 | export default { 4 | namespace: 'project', 5 | 6 | state: { 7 | notice: [], 8 | projectList: [], 9 | total: 0, 10 | projectDetail: { 11 | title: '', 12 | state: '', 13 | content: '', 14 | _id: '', 15 | }, 16 | }, 17 | 18 | effects: { 19 | *fetchNotice(_, { call, put }) { 20 | const response = yield call(queryProjectNotice); 21 | yield put({ 22 | type: 'saveNotice', 23 | payload: Array.isArray(response) ? response : [], 24 | }); 25 | }, 26 | *queryProject({ payload }, { call, put }) { 27 | const { resolve, params } = payload; 28 | const response = yield call(queryProject, params); 29 | !!resolve && resolve(response); // 返回数据 30 | // console.log('response :', response) 31 | if (response.code === 0) { 32 | yield put({ 33 | type: 'saveProjectList', 34 | payload: response.data.list, 35 | }); 36 | yield put({ 37 | type: 'saveProjectListTotal', 38 | payload: response.data.count, 39 | }); 40 | } 41 | }, 42 | *delProject({ payload }, { call, put }) { 43 | const { resolve, params } = payload; 44 | const response = yield call(delProject, params); 45 | !!resolve && resolve(response); 46 | }, 47 | *addProject({ payload }, { call, put }) { 48 | const { resolve, params } = payload; 49 | const response = yield call(addProject, params); 50 | !!resolve && resolve(response); 51 | }, 52 | *updateProject({ payload }, { call, put }) { 53 | const { resolve, params } = payload; 54 | const response = yield call(updateProject, params); 55 | !!resolve && resolve(response); 56 | }, 57 | *getProjectDetail({ payload }, { call, put }) { 58 | const { resolve, params } = payload; 59 | const response = yield call(getProjectDetail, params); 60 | !!resolve && resolve(response); 61 | if (response.code === 0) { 62 | yield put({ 63 | type: 'saveProjectDetail', 64 | payload: response.data, 65 | }); 66 | } 67 | }, 68 | }, 69 | 70 | reducers: { 71 | saveNotice(state, action) { 72 | return { 73 | ...state, 74 | notice: action.payload, 75 | }; 76 | }, 77 | saveProjectList(state, { payload }) { 78 | return { 79 | ...state, 80 | projectList: payload, 81 | }; 82 | }, 83 | saveProjectListTotal(state, { payload }) { 84 | return { 85 | ...state, 86 | total: payload, 87 | }; 88 | }, 89 | saveProjectDetail(state, { payload }) { 90 | return { 91 | ...state, 92 | projectDetail: payload, 93 | }; 94 | }, 95 | }, 96 | }; 97 | -------------------------------------------------------------------------------- /src/models/tag.js: -------------------------------------------------------------------------------- 1 | import { queryTag, addTag, delTag } from '@/services/api'; 2 | 3 | export default { 4 | namespace: 'tag', 5 | 6 | state: { 7 | tagList: [], 8 | total: 0, 9 | }, 10 | 11 | effects: { 12 | *queryTag({ payload }, { call, put }) { 13 | const { resolve, params } = payload; 14 | const response = yield call(queryTag, params); 15 | !!resolve && resolve(response); // 返回数据 16 | // console.log('response :', response) 17 | if (response.code === 0) { 18 | yield put({ 19 | type: 'saveTagList', 20 | payload: response.data.list, 21 | }); 22 | yield put({ 23 | type: 'saveTagListTotal', 24 | payload: response.data.count, 25 | }); 26 | } else { 27 | // 28 | } 29 | }, 30 | *addTag({ payload }, { call, put }) { 31 | const { resolve, params } = payload; 32 | const response = yield call(addTag, params); 33 | !!resolve && resolve(response); 34 | }, 35 | *delTag({ payload }, { call, put }) { 36 | const { resolve, params } = payload; 37 | const response = yield call(delTag, params); 38 | !!resolve && resolve(response); 39 | }, 40 | }, 41 | 42 | reducers: { 43 | saveTagList(state, { payload }) { 44 | return { 45 | ...state, 46 | tagList: payload, 47 | }; 48 | }, 49 | saveTagListTotal(state, { payload }) { 50 | return { 51 | ...state, 52 | total: payload, 53 | }; 54 | }, 55 | }, 56 | }; 57 | -------------------------------------------------------------------------------- /src/models/timeAxis.js: -------------------------------------------------------------------------------- 1 | import { queryTimeAxis, delTimeAxis, updateTimeAxis, addTimeAxis,getTimeAxisDetail } from '@/services/api'; 2 | 3 | export default { 4 | namespace: 'timeAxis', 5 | 6 | state: { 7 | timeAxisList: [], 8 | total: 0, 9 | timeAxisDetail: { 10 | title: '', 11 | state: '', 12 | content: '', 13 | _id: '', 14 | }, 15 | }, 16 | 17 | effects: { 18 | *queryTimeAxis({ payload }, { call, put }) { 19 | const { resolve, params } = payload; 20 | const response = yield call(queryTimeAxis, params); 21 | !!resolve && resolve(response); // 返回数据 22 | // console.log('response :', response) 23 | if (response.code === 0) { 24 | yield put({ 25 | type: 'saveTimeAxisList', 26 | payload: response.data.list, 27 | }); 28 | yield put({ 29 | type: 'saveTimeAxisListTotal', 30 | payload: response.data.count, 31 | }); 32 | } 33 | }, 34 | *delTimeAxis({ payload }, { call, put }) { 35 | const { resolve, params } = payload; 36 | const response = yield call(delTimeAxis, params); 37 | !!resolve && resolve(response); 38 | }, 39 | *addTimeAxis({ payload }, { call, put }) { 40 | const { resolve, params } = payload; 41 | const response = yield call(addTimeAxis, params); 42 | !!resolve && resolve(response); 43 | }, 44 | *updateTimeAxis({ payload }, { call, put }) { 45 | const { resolve, params } = payload; 46 | const response = yield call(updateTimeAxis, params); 47 | !!resolve && resolve(response); 48 | }, 49 | *getTimeAxisDetail({ payload }, { call, put }) { 50 | const { resolve, params } = payload; 51 | const response = yield call(getTimeAxisDetail, params); 52 | !!resolve && resolve(response); 53 | console.log('response :', response) 54 | if (response.code === 0) { 55 | yield put({ 56 | type: 'saveTimeAxisDetail', 57 | payload: response.data, 58 | }); 59 | } 60 | }, 61 | }, 62 | 63 | reducers: { 64 | saveTimeAxisList(state, { payload }) { 65 | return { 66 | ...state, 67 | timeAxisList: payload, 68 | }; 69 | }, 70 | saveTimeAxisListTotal(state, { payload }) { 71 | return { 72 | ...state, 73 | total: payload, 74 | }; 75 | }, 76 | saveTimeAxisDetail(state, { payload }) { 77 | return { 78 | ...state, 79 | timeAxisDetail: payload, 80 | }; 81 | }, 82 | }, 83 | }; 84 | -------------------------------------------------------------------------------- /src/models/user.js: -------------------------------------------------------------------------------- 1 | import { query as queryAdmin, queryCurrent } from '@/services/user'; 2 | 3 | export default { 4 | namespace: 'user', 5 | 6 | state: { 7 | list: [], 8 | currentUser: {}, 9 | }, 10 | 11 | effects: { 12 | *fetch(_, { call, put }) { 13 | const response = yield call(queryAdmin); 14 | yield put({ 15 | type: 'save', 16 | payload: response, 17 | }); 18 | }, 19 | *fetchCurrent(_, { call, put }) { 20 | const response = yield call(queryCurrent); 21 | yield put({ 22 | type: 'saveCurrentUser', 23 | payload: response.data, 24 | }); 25 | }, 26 | *delUser({ payload }, { call, put }) { 27 | const { resolve, params } = payload; 28 | const response = yield call(delUser, params); 29 | !!resolve && resolve(response); 30 | }, 31 | }, 32 | 33 | reducers: { 34 | save(state, action) { 35 | return { 36 | ...state, 37 | list: action.payload, 38 | }; 39 | }, 40 | saveCurrentUser(state, action) { 41 | return { 42 | ...state, 43 | currentUser: action.payload || {}, 44 | }; 45 | }, 46 | changeNotifyCount(state, action) { 47 | return { 48 | ...state, 49 | currentUser: { 50 | ...state.currentUser, 51 | notifyCount: action.payload, 52 | }, 53 | }; 54 | }, 55 | saveUserList(state, { payload }) { 56 | return { 57 | ...state, 58 | userList: payload, 59 | }; 60 | }, 61 | saveUserListTotal(state, { payload }) { 62 | return { 63 | ...state, 64 | total: payload, 65 | }; 66 | }, 67 | }, 68 | }; 69 | -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from 'umi/link'; 3 | import Exception from '@/components/Exception'; 4 | 5 | export default () => ( 6 | 7 | ); 8 | -------------------------------------------------------------------------------- /src/pages/Account/Settings/BaseView.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .baseView { 4 | display: flex; 5 | padding-top: 12px; 6 | 7 | .left { 8 | max-width: 448px; 9 | min-width: 224px; 10 | } 11 | .right { 12 | flex: 1; 13 | padding-left: 104px; 14 | .avatar_title { 15 | height: 22px; 16 | font-size: @font-size-base; 17 | color: @heading-color; 18 | line-height: 22px; 19 | margin-bottom: 8px; 20 | } 21 | .avatar { 22 | width: 144px; 23 | height: 144px; 24 | margin-bottom: 12px; 25 | overflow: hidden; 26 | img { 27 | width: 100%; 28 | } 29 | } 30 | .button_view { 31 | width: 144px; 32 | text-align: center; 33 | } 34 | } 35 | } 36 | 37 | @media screen and (max-width: @screen-xl) { 38 | .baseView { 39 | flex-direction: column-reverse; 40 | 41 | .right { 42 | padding: 20px; 43 | display: flex; 44 | flex-direction: column; 45 | align-items: center; 46 | max-width: 448px; 47 | .avatar_title { 48 | display: none; 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/pages/Account/Settings/Info.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .main { 4 | width: 100%; 5 | height: 100%; 6 | background-color: @body-background; 7 | display: flex; 8 | padding-top: 16px; 9 | padding-bottom: 16px; 10 | overflow: auto; 11 | .leftmenu { 12 | width: 224px; 13 | border-right: @border-width-base @border-style-base @border-color-split; 14 | :global { 15 | .ant-menu-inline { 16 | border: none; 17 | } 18 | .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected { 19 | font-weight: bold; 20 | } 21 | } 22 | } 23 | .right { 24 | flex: 1; 25 | padding-left: 40px; 26 | padding-right: 40px; 27 | padding-top: 8px; 28 | padding-bottom: 8px; 29 | .title { 30 | font-size: 20px; 31 | color: @heading-color; 32 | line-height: 28px; 33 | font-weight: 500; 34 | margin-bottom: 12px; 35 | } 36 | } 37 | :global { 38 | .ant-list-split .ant-list-item:last-child { 39 | border-bottom: 1px solid #e8e8e8; 40 | } 41 | .ant-list-item { 42 | padding-top: 14px; 43 | padding-bottom: 14px; 44 | } 45 | } 46 | } 47 | :global { 48 | .ant-list-item-meta { 49 | // 账号绑定图标 50 | .taobao { 51 | color: #ff4000; 52 | display: block; 53 | font-size: 48px; 54 | line-height: 48px; 55 | border-radius: @border-radius-base; 56 | } 57 | .dingding { 58 | background-color: #2eabff; 59 | color: #fff; 60 | font-size: 32px; 61 | line-height: 32px; 62 | padding: 6px; 63 | margin: 2px; 64 | border-radius: @border-radius-base; 65 | } 66 | .alipay { 67 | color: #2eabff; 68 | font-size: 48px; 69 | line-height: 48px; 70 | border-radius: @border-radius-base; 71 | } 72 | } 73 | 74 | // 密码强度 75 | font.strong { 76 | color: @success-color; 77 | } 78 | font.medium { 79 | color: @warning-color; 80 | } 81 | font.weak { 82 | color: @error-color; 83 | } 84 | } 85 | 86 | @media screen and (max-width: @screen-md) { 87 | .main { 88 | flex-direction: column; 89 | .leftmenu { 90 | width: 100%; 91 | border: none; 92 | } 93 | .right { 94 | padding: 40px; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/pages/Article/style.less: -------------------------------------------------------------------------------- 1 | 2 | 3 | // .editor-toolbar.fullscreen { 4 | // z-index: 99; 5 | // } 6 | // .CodeMirror-fullscreen { 7 | // z-index: 90; 8 | // } 9 | // .editor-preview-side { 10 | // z-index: 99; 11 | // } 12 | 13 | /*对 markdown 样式的补充*/ 14 | pre { 15 | display: block; 16 | padding: 10px; 17 | margin: 0 0 10px; 18 | font-size: 14px; 19 | line-height: 1.42857143; 20 | color: #abb2bf; 21 | background: #282c34; 22 | word-break: break-all; 23 | word-wrap: break-word; 24 | overflow: auto; 25 | } 26 | .editor-preview pre, .editor-preview-side pre{ 27 | background: #282c34; 28 | } 29 | h1,h2,h3,h4,h5,h6{ 30 | margin-top: 1em; 31 | /* margin-bottom: 1em; */ 32 | } 33 | strong { 34 | font-weight: bold; 35 | } 36 | 37 | p > code:not([class]) { 38 | padding: 2px 4px; 39 | font-size: 90%; 40 | color: #c7254e; 41 | background-color: #f9f2f4; 42 | border-radius: 4px; 43 | } 44 | p img{ 45 | /* 图片居中 */ 46 | margin: 0 auto; 47 | display: flex; 48 | } 49 | 50 | .editor-preview-side { 51 | font-family: "Microsoft YaHei", 'sans-serif'; 52 | font-size: 16px; 53 | line-height: 30px; 54 | } 55 | 56 | .editor-preview-side .desc ul,.editor-preview-side .desc ol { 57 | color: #333333; 58 | margin: 1.5em 0 0 25px; 59 | } 60 | 61 | .editor-preview-side .desc h1, .editor-preview-side .desc h2 { 62 | border-bottom: 1px solid #eee; 63 | padding-bottom: 10px; 64 | } 65 | 66 | .editor-preview-side .desc a { 67 | color: #009a61; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/pages/Authorized.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import RenderAuthorized from '@/components/Authorized'; 3 | import { getAuthority } from '@/utils/authority'; 4 | import Redirect from 'umi/redirect'; 5 | 6 | const Authority = getAuthority(); 7 | const Authorized = RenderAuthorized(Authority); 8 | 9 | export default ({ children }) => ( 10 | }> 11 | {children} 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /src/pages/Category/CategoryComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input, Modal } from 'antd'; 3 | 4 | class LinkComponent extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = {}; 8 | } 9 | 10 | componentDidMount() {} 11 | 12 | render() { 13 | const normalCenter = { 14 | textAlign: 'center', 15 | marginBottom: 20, 16 | }; 17 | return ( 18 |
19 | 26 | 35 | 44 | 45 |
46 | ); 47 | } 48 | } 49 | 50 | export default LinkComponent; 51 | -------------------------------------------------------------------------------- /src/pages/Dashboard/Workplace.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @import '~@/utils/utils.less'; 3 | 4 | 5 | .pageHeaderContent { 6 | display: flex; 7 | .avatar { 8 | flex: 0 1 72px; 9 | margin-bottom: 8px; 10 | & > span { 11 | border-radius: 72px; 12 | display: block; 13 | width: 72px; 14 | height: 72px; 15 | } 16 | } 17 | .content { 18 | position: relative; 19 | top: 4px; 20 | margin-left: 24px; 21 | flex: 1 1 auto; 22 | color: @text-color-secondary; 23 | line-height: 22px; 24 | .contentTitle { 25 | font-size: 20px; 26 | line-height: 28px; 27 | font-weight: 500; 28 | color: @heading-color; 29 | margin-bottom: 12px; 30 | } 31 | } 32 | } 33 | 34 | .extraContent { 35 | .clearfix(); 36 | float: right; 37 | white-space: nowrap; 38 | .statItem { 39 | padding: 0 32px; 40 | position: relative; 41 | display: inline-block; 42 | > p:first-child { 43 | color: @text-color-secondary; 44 | font-size: @font-size-base; 45 | line-height: 22px; 46 | margin-bottom: 4px; 47 | } 48 | > p { 49 | color: @heading-color; 50 | font-size: 30px; 51 | line-height: 38px; 52 | margin: 0; 53 | > span { 54 | color: @text-color-secondary; 55 | font-size: 20px; 56 | } 57 | } 58 | &:after { 59 | background-color: @border-color-split; 60 | position: absolute; 61 | top: 8px; 62 | right: 0; 63 | width: 1px; 64 | height: 40px; 65 | content: ''; 66 | } 67 | &:last-child { 68 | padding-right: 0; 69 | &:after { 70 | display: none; 71 | } 72 | } 73 | } 74 | } 75 | 76 | 77 | .datetime { 78 | color: @disabled-color; 79 | } 80 | 81 | @media screen and (max-width: @screen-xl) and (min-width: @screen-lg) { 82 | .activeCard { 83 | margin-bottom: 24px; 84 | } 85 | .extraContent { 86 | margin-left: -44px; 87 | .statItem { 88 | padding: 0 16px; 89 | } 90 | } 91 | } 92 | 93 | @media screen and (max-width: @screen-lg) { 94 | .activeCard { 95 | margin-bottom: 24px; 96 | } 97 | .extraContent { 98 | float: none; 99 | margin-right: 0; 100 | .statItem { 101 | padding: 0 16px; 102 | text-align: left; 103 | &:after { 104 | display: none; 105 | } 106 | } 107 | } 108 | } 109 | 110 | @media screen and (max-width: @screen-md) { 111 | .extraContent { 112 | margin-left: -16px; 113 | } 114 | } 115 | 116 | @media screen and (max-width: @screen-sm) { 117 | .pageHeaderContent { 118 | display: block; 119 | .content { 120 | margin-left: 0; 121 | } 122 | } 123 | .extraContent { 124 | .statItem { 125 | float: none; 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /src/pages/Dashboard/models/activities.js: -------------------------------------------------------------------------------- 1 | import { queryActivities } from '@/services/api'; 2 | 3 | export default { 4 | namespace: 'activities', 5 | 6 | state: { 7 | list: [], 8 | }, 9 | 10 | effects: { 11 | *fetchList(_, { call, put }) { 12 | const response = yield call(queryActivities); 13 | yield put({ 14 | type: 'saveList', 15 | payload: Array.isArray(response) ? response : [], 16 | }); 17 | }, 18 | }, 19 | 20 | reducers: { 21 | saveList(state, action) { 22 | return { 23 | ...state, 24 | list: action.payload, 25 | }; 26 | }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/pages/Exception/403.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { formatMessage } from 'umi/locale'; 3 | import Link from 'umi/link'; 4 | import Exception from '@/components/Exception'; 5 | 6 | const Exception403 = () => ( 7 | 13 | ); 14 | 15 | export default Exception403; 16 | -------------------------------------------------------------------------------- /src/pages/Exception/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { formatMessage } from 'umi/locale'; 3 | import Link from 'umi/link'; 4 | import Exception from '@/components/Exception'; 5 | 6 | const Exception404 = () => ( 7 | 13 | ); 14 | 15 | export default Exception404; 16 | -------------------------------------------------------------------------------- /src/pages/Exception/500.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { formatMessage } from 'umi/locale'; 3 | import Link from 'umi/link'; 4 | import Exception from '@/components/Exception'; 5 | 6 | const Exception500 = () => ( 7 | 13 | ); 14 | 15 | export default Exception500; 16 | -------------------------------------------------------------------------------- /src/pages/Exception/TriggerException.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Button, Spin, Card } from 'antd'; 3 | import { connect } from 'dva'; 4 | import styles from './style.less'; 5 | 6 | @connect(state => ({ 7 | isloading: state.error.isloading, 8 | })) 9 | class TriggerException extends PureComponent { 10 | state = { 11 | isloading: false, 12 | }; 13 | 14 | triggerError = code => { 15 | this.setState({ 16 | isloading: true, 17 | }); 18 | const { dispatch } = this.props; 19 | dispatch({ 20 | type: 'error/query', 21 | payload: { 22 | code, 23 | }, 24 | }); 25 | }; 26 | 27 | render() { 28 | const { isloading } = this.state; 29 | return ( 30 | 31 | 32 | 35 | 38 | 41 | 44 | 45 | 46 | ); 47 | } 48 | } 49 | 50 | export default TriggerException; 51 | -------------------------------------------------------------------------------- /src/pages/Exception/models/error.js: -------------------------------------------------------------------------------- 1 | import queryError from '@/services/error'; 2 | 3 | export default { 4 | namespace: 'error', 5 | 6 | state: { 7 | error: '', 8 | isloading: false, 9 | }, 10 | 11 | effects: { 12 | *query({ payload }, { call, put }) { 13 | yield call(queryError, payload.code); 14 | yield put({ 15 | type: 'trigger', 16 | payload: payload.code, 17 | }); 18 | }, 19 | }, 20 | 21 | reducers: { 22 | trigger(state, action) { 23 | return { 24 | error: action.payload, 25 | }; 26 | }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/pages/Exception/style.less: -------------------------------------------------------------------------------- 1 | .trigger { 2 | background: 'red'; 3 | :global(.ant-btn) { 4 | margin-right: 8px; 5 | margin-bottom: 12px; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/pages/Link/LinkComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input, Modal } from 'antd'; 3 | 4 | class LinkComponent extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = {}; 8 | } 9 | 10 | componentDidMount() {} 11 | 12 | render() { 13 | const normalCenter = { 14 | textAlign: 'center', 15 | marginBottom: 20, 16 | }; 17 | return ( 18 |
19 | 26 | 35 | 44 | 53 | 62 | 71 | 72 |
73 | ); 74 | } 75 | } 76 | 77 | export default LinkComponent; 78 | -------------------------------------------------------------------------------- /src/pages/OtherUser/OtherUserComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input, Modal } from 'antd'; 3 | 4 | class OtherUserComponent extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = {}; 8 | } 9 | 10 | componentDidMount() {} 11 | 12 | render() { 13 | const normalCenter = { 14 | textAlign: 'center', 15 | marginBottom: 20, 16 | }; 17 | return ( 18 |
19 | 26 | 35 | 44 | 53 | 62 | 71 | 72 |
73 | ); 74 | } 75 | } 76 | 77 | export default OtherUserComponent; 78 | -------------------------------------------------------------------------------- /src/pages/OtherUser/style.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @import '~@/utils/utils.less'; 3 | 4 | .tableList { 5 | .tableListOperator { 6 | margin-bottom: 16px; 7 | button { 8 | margin-right: 8px; 9 | } 10 | } 11 | } 12 | 13 | .tableListForm { 14 | :global { 15 | .ant-form-item { 16 | margin-bottom: 24px; 17 | margin-right: 0; 18 | display: flex; 19 | > .ant-form-item-label { 20 | width: auto; 21 | line-height: 32px; 22 | padding-right: 8px; 23 | } 24 | .ant-form-item-control { 25 | line-height: 32px; 26 | } 27 | } 28 | .ant-form-item-control-wrapper { 29 | flex: 1; 30 | } 31 | } 32 | .submitButtons { 33 | display: block; 34 | white-space: nowrap; 35 | margin-bottom: 24px; 36 | } 37 | } 38 | 39 | @media screen and (max-width: @screen-lg) { 40 | .tableListForm :global(.ant-form-item) { 41 | margin-right: 24px; 42 | } 43 | } 44 | 45 | @media screen and (max-width: @screen-md) { 46 | .tableListForm :global(.ant-form-item) { 47 | margin-right: 8px; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/pages/Tag/TagComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input, Modal } from 'antd'; 3 | 4 | class TagComponent extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = {}; 8 | } 9 | 10 | componentDidMount() {} 11 | 12 | render() { 13 | const normalCenter = { 14 | textAlign: 'center', 15 | marginBottom: 20, 16 | }; 17 | return ( 18 |
19 | 26 | 35 | 44 | 45 |
46 | ); 47 | } 48 | } 49 | 50 | export default TagComponent; 51 | -------------------------------------------------------------------------------- /src/pages/TimeAxis/TimeAxisComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input, Modal, Select, DatePicker } from 'antd'; 3 | import { connect } from 'dva'; 4 | 5 | const { RangePicker } = DatePicker; 6 | 7 | @connect(({ timeAxis }) => ({ 8 | timeAxis, 9 | })) 10 | class TimeAxisComponent extends React.Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | 15 | }; 16 | } 17 | 18 | checkUpdate(){ 19 | const { changeType } = this.props; 20 | const { timeAxisDetail } = this.props.timeAxis; 21 | if (changeType) { 22 | this.setState({ 23 | title: timeAxisDetail.title, 24 | state: timeAxisDetail.state, 25 | content: timeAxisDetail.content, 26 | }); 27 | } 28 | } 29 | 30 | 31 | 32 | render() { 33 | 34 | const { TextArea } = Input; 35 | const normalCenter = { 36 | textAlign: 'center', 37 | marginBottom: 20, 38 | }; 39 | return ( 40 |
41 | 48 | 57 |