committed by
GitHub
491 changed files with 6880 additions and 25005 deletions
@ -1,3 +1,3 @@ |
|||
/lambda/ |
|||
/scripts |
|||
/config |
|||
/config |
|||
|
|||
@ -1,12 +1,14 @@ |
|||
{ |
|||
"singleQuote": true, |
|||
"trailingComma": "es5", |
|||
"trailingComma": "all", |
|||
"printWidth": 100, |
|||
"proseWrap": "never", |
|||
"overrides": [ |
|||
{ |
|||
"files": ".prettierrc", |
|||
"options": { "parser": "json" } |
|||
"options": { |
|||
"parser": "json" |
|||
} |
|||
} |
|||
] |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
import { MenuTheme } from 'antd/es/menu'; |
|||
|
|||
export type ContentWidth = 'Fluid' | 'Fixed'; |
|||
|
|||
export interface DefaultSettings { |
|||
/** |
|||
* theme for nav menu |
|||
*/ |
|||
navTheme: MenuTheme; |
|||
/** |
|||
* primary color of ant design |
|||
*/ |
|||
primaryColor: string; |
|||
/** |
|||
* nav menu position: `sidemenu` or `topmenu` |
|||
*/ |
|||
layout: 'sidemenu' | 'topmenu'; |
|||
/** |
|||
* layout of content: `Fluid` or `Fixed`, only works when layout is topmenu |
|||
*/ |
|||
contentWidth: ContentWidth; |
|||
/** |
|||
* sticky header |
|||
*/ |
|||
fixedHeader: boolean; |
|||
/** |
|||
* auto hide header |
|||
*/ |
|||
autoHideHeader: boolean; |
|||
/** |
|||
* sticky siderbar |
|||
*/ |
|||
fixSiderbar: boolean; |
|||
menu: { locale: boolean }; |
|||
title: string; |
|||
pwa: boolean; |
|||
// Your custom iconfont Symbol script Url
|
|||
// eg://at.alicdn.com/t/font_1039637_btcrd5co4w.js
|
|||
// 注意:如果需要图标多色,Iconfont 图标项目里要进行批量去色处理
|
|||
// Usage: https://github.com/ant-design/ant-design-pro/pull/3517
|
|||
iconfontUrl: string; |
|||
colorWeak: boolean; |
|||
} |
|||
|
|||
export default { |
|||
navTheme: 'dark', |
|||
primaryColor: '#1890FF', |
|||
layout: 'sidemenu', |
|||
contentWidth: 'Fluid', |
|||
fixedHeader: false, |
|||
autoHideHeader: false, |
|||
fixSiderbar: false, |
|||
colorWeak: false, |
|||
menu: { |
|||
locale: true, |
|||
}, |
|||
title: 'Ant Design Pro', |
|||
pwa: false, |
|||
iconfontUrl: '', |
|||
} as DefaultSettings; |
|||
@ -1,297 +0,0 @@ |
|||
export default [ |
|||
// user
|
|||
{ |
|||
path: '/user', |
|||
component: '../layouts/UserLayout', |
|||
routes: [ |
|||
{ path: '/user', redirect: '/user/login' }, |
|||
{ path: '/user/login', name: 'login', component: './User/Login' }, |
|||
{ path: '/user/register', name: 'register', component: './User/Register' }, |
|||
{ |
|||
path: '/user/register-result', |
|||
name: 'register.result', |
|||
component: './User/RegisterResult', |
|||
}, |
|||
{ |
|||
component: '404', |
|||
}, |
|||
], |
|||
}, |
|||
// app
|
|||
{ |
|||
path: '/', |
|||
component: '../layouts/BasicLayout', |
|||
Routes: ['src/pages/Authorized'], |
|||
routes: [ |
|||
// dashboard
|
|||
{ path: '/', redirect: '/dashboard/analysis', authority: ['admin', 'user'] }, |
|||
{ |
|||
path: '/dashboard', |
|||
name: 'dashboard', |
|||
icon: 'dashboard', |
|||
routes: [ |
|||
{ |
|||
path: '/dashboard/analysis', |
|||
name: 'analysis', |
|||
component: './Dashboard/Analysis', |
|||
}, |
|||
{ |
|||
path: '/dashboard/monitor', |
|||
name: 'monitor', |
|||
component: './Dashboard/Monitor', |
|||
}, |
|||
{ |
|||
path: '/dashboard/workplace', |
|||
name: 'workplace', |
|||
component: './Dashboard/Workplace', |
|||
}, |
|||
], |
|||
}, |
|||
// forms
|
|||
{ |
|||
path: '/form', |
|||
icon: 'form', |
|||
name: 'form', |
|||
routes: [ |
|||
{ |
|||
path: '/form/basic-form', |
|||
name: 'basicform', |
|||
component: './Forms/BasicForm', |
|||
}, |
|||
{ |
|||
path: '/form/step-form', |
|||
name: 'stepform', |
|||
component: './Forms/StepForm', |
|||
hideChildrenInMenu: true, |
|||
routes: [ |
|||
{ |
|||
path: '/form/step-form', |
|||
redirect: '/form/step-form/info', |
|||
}, |
|||
{ |
|||
path: '/form/step-form/info', |
|||
name: 'info', |
|||
component: './Forms/StepForm/Step1', |
|||
}, |
|||
{ |
|||
path: '/form/step-form/confirm', |
|||
name: 'confirm', |
|||
component: './Forms/StepForm/Step2', |
|||
}, |
|||
{ |
|||
path: '/form/step-form/result', |
|||
name: 'result', |
|||
component: './Forms/StepForm/Step3', |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
path: '/form/advanced-form', |
|||
name: 'advancedform', |
|||
authority: ['admin'], |
|||
component: './Forms/AdvancedForm', |
|||
}, |
|||
], |
|||
}, |
|||
// list
|
|||
{ |
|||
path: '/list', |
|||
icon: 'table', |
|||
name: 'list', |
|||
routes: [ |
|||
{ |
|||
path: '/list/table-list', |
|||
name: 'searchtable', |
|||
component: './List/TableList', |
|||
}, |
|||
{ |
|||
path: '/list/basic-list', |
|||
name: 'basiclist', |
|||
component: './List/BasicList', |
|||
}, |
|||
{ |
|||
path: '/list/card-list', |
|||
name: 'cardlist', |
|||
component: './List/CardList', |
|||
}, |
|||
{ |
|||
path: '/list/search', |
|||
name: 'searchlist', |
|||
component: './List/List', |
|||
routes: [ |
|||
{ |
|||
path: '/list/search', |
|||
redirect: '/list/search/articles', |
|||
}, |
|||
{ |
|||
path: '/list/search/articles', |
|||
name: 'articles', |
|||
component: './List/Articles', |
|||
}, |
|||
{ |
|||
path: '/list/search/projects', |
|||
name: 'projects', |
|||
component: './List/Projects', |
|||
}, |
|||
{ |
|||
path: '/list/search/applications', |
|||
name: 'applications', |
|||
component: './List/Applications', |
|||
}, |
|||
], |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
path: '/profile', |
|||
name: 'profile', |
|||
icon: 'profile', |
|||
routes: [ |
|||
// profile
|
|||
{ |
|||
path: '/profile/basic', |
|||
name: 'basic', |
|||
component: './Profile/BasicProfile', |
|||
}, |
|||
{ |
|||
path: '/profile/basic/:id', |
|||
hideInMenu: true, |
|||
component: './Profile/BasicProfile', |
|||
}, |
|||
{ |
|||
path: '/profile/advanced', |
|||
name: 'advanced', |
|||
authority: ['admin'], |
|||
component: './Profile/AdvancedProfile', |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
name: 'result', |
|||
icon: 'check-circle-o', |
|||
path: '/result', |
|||
routes: [ |
|||
// result
|
|||
{ |
|||
path: '/result/success', |
|||
name: 'success', |
|||
component: './Result/Success', |
|||
}, |
|||
{ path: '/result/fail', name: 'fail', component: './Result/Error' }, |
|||
], |
|||
}, |
|||
{ |
|||
name: 'exception', |
|||
icon: 'warning', |
|||
path: '/exception', |
|||
routes: [ |
|||
// exception
|
|||
{ |
|||
path: '/exception/403', |
|||
name: 'not-permission', |
|||
component: './Exception/403', |
|||
}, |
|||
{ |
|||
path: '/exception/404', |
|||
name: 'not-find', |
|||
component: './Exception/404', |
|||
}, |
|||
{ |
|||
path: '/exception/500', |
|||
name: 'server-error', |
|||
component: './Exception/500', |
|||
}, |
|||
{ |
|||
path: '/exception/trigger', |
|||
name: 'trigger', |
|||
hideInMenu: true, |
|||
component: './Exception/TriggerException', |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
name: 'account', |
|||
icon: 'user', |
|||
path: '/account', |
|||
routes: [ |
|||
{ |
|||
path: '/account/center', |
|||
name: 'center', |
|||
component: './Account/Center/Center', |
|||
routes: [ |
|||
{ |
|||
path: '/account/center', |
|||
redirect: '/account/center/articles', |
|||
}, |
|||
{ |
|||
path: '/account/center/articles', |
|||
component: './Account/Center/Articles', |
|||
}, |
|||
{ |
|||
path: '/account/center/applications', |
|||
component: './Account/Center/Applications', |
|||
}, |
|||
{ |
|||
path: '/account/center/projects', |
|||
component: './Account/Center/Projects', |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
path: '/account/settings', |
|||
name: 'settings', |
|||
component: './Account/Settings/Info', |
|||
routes: [ |
|||
{ |
|||
path: '/account/settings', |
|||
redirect: '/account/settings/base', |
|||
}, |
|||
{ |
|||
path: '/account/settings/base', |
|||
component: './Account/Settings/BaseView', |
|||
}, |
|||
{ |
|||
path: '/account/settings/security', |
|||
component: './Account/Settings/SecurityView', |
|||
}, |
|||
{ |
|||
path: '/account/settings/binding', |
|||
component: './Account/Settings/BindingView', |
|||
}, |
|||
{ |
|||
path: '/account/settings/notification', |
|||
component: './Account/Settings/NotificationView', |
|||
}, |
|||
], |
|||
}, |
|||
], |
|||
}, |
|||
// editor
|
|||
{ |
|||
name: 'editor', |
|||
icon: 'highlight', |
|||
path: '/editor', |
|||
routes: [ |
|||
{ |
|||
path: '/editor/flow', |
|||
name: 'flow', |
|||
component: './Editor/GGEditor/Flow', |
|||
}, |
|||
{ |
|||
path: '/editor/mind', |
|||
name: 'mind', |
|||
component: './Editor/GGEditor/Mind', |
|||
}, |
|||
{ |
|||
path: '/editor/koni', |
|||
name: 'koni', |
|||
component: './Editor/GGEditor/Koni', |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
component: '404', |
|||
}, |
|||
], |
|||
}, |
|||
]; |
|||
@ -0,0 +1,56 @@ |
|||
# Ant Design Pro |
|||
|
|||
This project is initialized with [Ant Design Pro](https://pro.ant.design). Follow is the quick guide for how to use. |
|||
|
|||
## Environment Prepare |
|||
|
|||
Install `node_modules`: |
|||
```bash |
|||
npm install |
|||
``` |
|||
|
|||
or |
|||
|
|||
```bash |
|||
yarn |
|||
``` |
|||
|
|||
## Provided Scripts |
|||
|
|||
Ant Design Pro provides some useful script to help you quick start and build with web project, code style check and test. |
|||
|
|||
Scripts provided in `package.json`. It's safe to modify or add additional script: |
|||
|
|||
### Start project |
|||
|
|||
```bash |
|||
npm start |
|||
``` |
|||
|
|||
### Build project |
|||
|
|||
```bash |
|||
npm run build |
|||
``` |
|||
|
|||
### Check code style |
|||
|
|||
```bash |
|||
npm run lint |
|||
``` |
|||
|
|||
You can also use script to auto fix some lint error: |
|||
|
|||
```bash |
|||
npm run lint:fix |
|||
``` |
|||
|
|||
### Test code |
|||
|
|||
```bash |
|||
npm test |
|||
``` |
|||
|
|||
## More |
|||
|
|||
You can view full document on our [official website](https://pro.ant.design). And welcome any feedback in our [github](https://github.com/ant-design/ant-design-pro). |
|||
@ -0,0 +1,131 @@ |
|||
{ |
|||
"name": "ant-design-pro", |
|||
"version": "4.0.0", |
|||
"private": true, |
|||
"description": "An out-of-box UI solution for enterprise applications", |
|||
"scripts": { |
|||
"analyze": "cross-env ANALYZE=1 umi build", |
|||
"build": "umi build", |
|||
"generateMock": "node ./scripts/generateMock", |
|||
"lint": "npm run lint:js && npm run lint:ts && npm run lint:style && npm run lint:prettier", |
|||
"lint-staged": "lint-staged", |
|||
"lint-staged:js": "eslint --ext .js", |
|||
"lint-staged:ts": "tslint", |
|||
"lint:fix": "eslint --fix --ext .js src tests && npm run lint:style && npm run tslint:fix", |
|||
"lint:js": "eslint --ext .js src tests", |
|||
"lint:prettier": "check-prettier lint", |
|||
"lint:style": "stylelint --fix 'src/**/*.less' --syntax less", |
|||
"lint:ts": "tslint -p . -c tslint.yml", |
|||
"prettier": " check-prettier write", |
|||
"start": "umi dev", |
|||
"start:no-mock": "cross-env MOCK=none umi dev", |
|||
"test": "umi test", |
|||
"test:all": "node ./tests/run-tests.js", |
|||
"test:component": "umi test ./src/components", |
|||
"tslint:fix": "tslint --fix 'src/**/*.ts*'" |
|||
}, |
|||
"husky": { |
|||
"hooks": { |
|||
"pre-commit": "npm run lint-staged" |
|||
} |
|||
}, |
|||
"lint-staged": { |
|||
"**/*.less": "stylelint --syntax less", |
|||
"**/*.{js,jsx}": "npm run lint-staged:js", |
|||
"**/*.{js,ts,tsx,md,json,jsx,less}": [ "npm run prettier", "git add" ], |
|||
"**/*.{ts,tsx}": "npm run lint-staged:ts" |
|||
}, |
|||
"browserslist": [ "> 1%", "last 2 versions", "not ie <= 10" ], |
|||
"dependencies": { |
|||
"@ant-design/pro-layout": "^4.2.0", |
|||
"@antv/data-set": "^0.10.1", |
|||
"antd": "^3.16.1", |
|||
"bizcharts": "^3.5.3-beta.0", |
|||
"bizcharts-plugin-slider": "^2.1.1-beta.1", |
|||
"classnames": "^2.2.6", |
|||
"dva": "^2.4.0", |
|||
"lodash": "^4.17.10", |
|||
"lodash-decorators": "^6.0.0", |
|||
"memoize-one": "^5.0.0", |
|||
"moment": "^2.22.2", |
|||
"numeral": "^2.0.6", |
|||
"omit.js": "^1.0.0", |
|||
"path-to-regexp": "^2.4.0", |
|||
"qs": "^6.7.0", |
|||
"rc-animate": "^2.4.4", |
|||
"react": "^16.8.5", |
|||
"react-container-query": "^0.11.0", |
|||
"react-copy-to-clipboard": "^5.0.1", |
|||
"react-document-title": "^2.0.3", |
|||
"react-dom": "^16.7.0", |
|||
"react-fittext": "^1.0.0", |
|||
"react-media": "^1.9.2", |
|||
"react-media-hook2": "^1.0.2" |
|||
}, |
|||
"devDependencies": { |
|||
"@types/classnames": "^2.2.7", |
|||
"@types/history": "^4.7.2", |
|||
"@types/lodash": "^4.14.123", |
|||
"@types/react": "^16.8.1", |
|||
"@types/react-document-title": "^2.0.3", |
|||
"@types/react-dom": "^16.0.11", |
|||
"antd-pro-merge-less": "^1.0.0", |
|||
"antd-theme-webpack-plugin": "^1.2.0", |
|||
"babel-eslint": "^10.0.1", |
|||
"chalk": "^2.4.2", |
|||
"check-prettier": "^1.0.1", |
|||
"cross-env": "^5.2.0", |
|||
"cross-port-killer": "^1.0.1", |
|||
"enzyme": "^3.9.0", |
|||
"eslint": "^5.13.0", |
|||
"eslint-config-airbnb": "^17.1.0", |
|||
"eslint-config-prettier": "^4.1.0", |
|||
"eslint-plugin-babel": "^5.3.0", |
|||
"eslint-plugin-compat": "^2.6.3", |
|||
"eslint-plugin-import": "^2.16.0", |
|||
"eslint-plugin-jsx-a11y": "^6.2.1", |
|||
"eslint-plugin-markdown": "^1.0.0", |
|||
"eslint-plugin-react": "^7.12.4", |
|||
"express": "^4.16.4", |
|||
"gh-pages": "^2.0.1", |
|||
"husky": "^2.2.0", |
|||
"jest-puppeteer": "^4.1.0", |
|||
"jsdom-global": "^3.0.2", |
|||
"less": "^3.9.0", |
|||
"lint-staged": "^8.1.1", |
|||
"merge-umi-mock-data": "^1.0.4", |
|||
"mockjs": "^1.0.1-beta3", |
|||
"prettier": "^1.17.0", |
|||
"serverless-http": "^2.0.1", |
|||
"slash2": "^2.0.0", |
|||
"stylelint": "^9.10.1", |
|||
"stylelint-config-css-modules": "^1.3.0", |
|||
"stylelint-config-prettier": "^5.0.0", |
|||
"stylelint-config-rational-order": "^0.1.0", |
|||
"stylelint-config-standard": "^18.2.0", |
|||
"stylelint-declaration-block-no-ignored-properties": "^2.1.0", |
|||
"stylelint-order": "^2.0.0", |
|||
"tslint": "^5.12.1", |
|||
"tslint-config-prettier": "^1.17.0", |
|||
"tslint-eslint-rules": "^5.4.0", |
|||
"tslint-react": "^3.6.0", |
|||
"umi": "^2.7.0-beta.2", |
|||
"umi-plugin-ga": "^1.1.3", |
|||
"umi-plugin-pro-block": "^1.3.0", |
|||
"umi-plugin-react": "^1.8.0-beta.1", |
|||
"umi-request": "^1.0.0" |
|||
}, |
|||
"optionalDependencies": { |
|||
"puppeteer": "^1.12.1" |
|||
}, |
|||
"engines": { |
|||
"node": ">=10.0.0" |
|||
}, |
|||
"checkFiles": [ |
|||
"src/**/*.js*", |
|||
"src/**/*.ts*", |
|||
"src/**/*.less", |
|||
"config/**/*.js*", |
|||
"scripts/**/*.js" |
|||
] |
|||
} |
|||
File diff suppressed because it is too large
@ -1,336 +0,0 @@ |
|||
import mockjs from 'mockjs'; |
|||
|
|||
const titles = [ |
|||
'Alipay', |
|||
'Angular', |
|||
'Ant Design', |
|||
'Ant Design Pro', |
|||
'Bootstrap', |
|||
'React', |
|||
'Vue', |
|||
'Webpack', |
|||
]; |
|||
const avatars = [ |
|||
'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', // Alipay
|
|||
'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', // Angular
|
|||
'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', // Ant Design
|
|||
'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', // Ant Design Pro
|
|||
'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', // Bootstrap
|
|||
'https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png', // React
|
|||
'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png', // Vue
|
|||
'https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png', // Webpack
|
|||
]; |
|||
|
|||
const avatars2 = [ |
|||
'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png', |
|||
'https://gw.alipayobjects.com/zos/rmsportal/cnrhVkzwxjPwAaCfPbdc.png', |
|||
'https://gw.alipayobjects.com/zos/rmsportal/gaOngJwsRYRaVAuXXcmB.png', |
|||
'https://gw.alipayobjects.com/zos/rmsportal/ubnKSIfAJTxIgXOKlciN.png', |
|||
'https://gw.alipayobjects.com/zos/rmsportal/WhxKECPNujWoWEFNdnJE.png', |
|||
'https://gw.alipayobjects.com/zos/rmsportal/jZUIxmJycoymBprLOUbT.png', |
|||
'https://gw.alipayobjects.com/zos/rmsportal/psOgztMplJMGpVEqfcgF.png', |
|||
'https://gw.alipayobjects.com/zos/rmsportal/ZpBqSxLxVEXfcUNoPKrz.png', |
|||
'https://gw.alipayobjects.com/zos/rmsportal/laiEnJdGHVOhJrUShBaJ.png', |
|||
'https://gw.alipayobjects.com/zos/rmsportal/UrQsqscbKEpNuJcvBZBu.png', |
|||
]; |
|||
|
|||
const covers = [ |
|||
'https://gw.alipayobjects.com/zos/rmsportal/uMfMFlvUuceEyPpotzlq.png', |
|||
'https://gw.alipayobjects.com/zos/rmsportal/iZBVOIhGJiAnhplqjvZW.png', |
|||
'https://gw.alipayobjects.com/zos/rmsportal/iXjVmWVHbCJAyqvDxdtx.png', |
|||
'https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png', |
|||
]; |
|||
const desc = [ |
|||
'那是一种内在的东西, 他们到达不了,也无法触及的', |
|||
'希望是一个好东西,也许是最好的,好东西是不会消亡的', |
|||
'生命就像一盒巧克力,结果往往出人意料', |
|||
'城镇中有那么多的酒馆,她却偏偏走进了我的酒馆', |
|||
'那时候我只会想自己想要什么,从不想自己拥有什么', |
|||
]; |
|||
|
|||
const user = [ |
|||
'付小小', |
|||
'曲丽丽', |
|||
'林东东', |
|||
'周星星', |
|||
'吴加好', |
|||
'朱偏右', |
|||
'鱼酱', |
|||
'乐哥', |
|||
'谭小仪', |
|||
'仲尼', |
|||
]; |
|||
|
|||
function fakeList(count) { |
|||
const list = []; |
|||
for (let i = 0; i < count; i += 1) { |
|||
list.push({ |
|||
id: `fake-list-${i}`, |
|||
owner: user[i % 10], |
|||
title: titles[i % 8], |
|||
avatar: avatars[i % 8], |
|||
cover: parseInt(i / 4, 10) % 2 === 0 ? covers[i % 4] : covers[3 - (i % 4)], |
|||
status: ['active', 'exception', 'normal'][i % 3], |
|||
percent: Math.ceil(Math.random() * 50) + 50, |
|||
logo: avatars[i % 8], |
|||
href: 'https://ant.design', |
|||
updatedAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i), |
|||
createdAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i), |
|||
subDescription: desc[i % 5], |
|||
description: |
|||
'在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。', |
|||
activeUser: Math.ceil(Math.random() * 100000) + 100000, |
|||
newUser: Math.ceil(Math.random() * 1000) + 1000, |
|||
star: Math.ceil(Math.random() * 100) + 100, |
|||
like: Math.ceil(Math.random() * 100) + 100, |
|||
message: Math.ceil(Math.random() * 10) + 10, |
|||
content: |
|||
'段落示意:蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。', |
|||
members: [ |
|||
{ |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png', |
|||
name: '曲丽丽', |
|||
id: 'member1', |
|||
}, |
|||
{ |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png', |
|||
name: '王昭君', |
|||
id: 'member2', |
|||
}, |
|||
{ |
|||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png', |
|||
name: '董娜娜', |
|||
id: 'member3', |
|||
}, |
|||
], |
|||
}); |
|||
} |
|||
|
|||
return list; |
|||
} |
|||
|
|||
let sourceData; |
|||
|
|||
function getFakeList(req, res) { |
|||
const params = req.query; |
|||
|
|||
const count = params.count * 1 || 20; |
|||
|
|||
const result = fakeList(count); |
|||
sourceData = result; |
|||
return res.json(result); |
|||
} |
|||
|
|||
function postFakeList(req, res) { |
|||
const { /* url = '', */ body } = req; |
|||
// const params = getUrlParams(url);
|
|||
const { method, id } = body; |
|||
// const count = (params.count * 1) || 20;
|
|||
let result = sourceData; |
|||
|
|||
switch (method) { |
|||
case 'delete': |
|||
result = result.filter(item => item.id !== id); |
|||
break; |
|||
case 'update': |
|||
result.forEach((item, i) => { |
|||
if (item.id === id) { |
|||
result[i] = Object.assign(item, body); |
|||
} |
|||
}); |
|||
break; |
|||
case 'post': |
|||
result.unshift({ |
|||
body, |
|||
id: `fake-list-${result.length}`, |
|||
createdAt: new Date().getTime(), |
|||
}); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
|
|||
return res.json(result); |
|||
} |
|||
|
|||
const getNotice = [ |
|||
{ |
|||
id: 'xxx1', |
|||
title: titles[0], |
|||
logo: avatars[0], |
|||
description: '那是一种内在的东西,他们到达不了,也无法触及的', |
|||
updatedAt: new Date(), |
|||
member: '科学搬砖组', |
|||
href: '', |
|||
memberLink: '', |
|||
}, |
|||
{ |
|||
id: 'xxx2', |
|||
title: titles[1], |
|||
logo: avatars[1], |
|||
description: '希望是一个好东西,也许是最好的,好东西是不会消亡的', |
|||
updatedAt: new Date('2017-07-24'), |
|||
member: '全组都是吴彦祖', |
|||
href: '', |
|||
memberLink: '', |
|||
}, |
|||
{ |
|||
id: 'xxx3', |
|||
title: titles[2], |
|||
logo: avatars[2], |
|||
description: '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆', |
|||
updatedAt: new Date(), |
|||
member: '中二少女团', |
|||
href: '', |
|||
memberLink: '', |
|||
}, |
|||
{ |
|||
id: 'xxx4', |
|||
title: titles[3], |
|||
logo: avatars[3], |
|||
description: '那时候我只会想自己想要什么,从不想自己拥有什么', |
|||
updatedAt: new Date('2017-07-23'), |
|||
member: '程序员日常', |
|||
href: '', |
|||
memberLink: '', |
|||
}, |
|||
{ |
|||
id: 'xxx5', |
|||
title: titles[4], |
|||
logo: avatars[4], |
|||
description: '凛冬将至', |
|||
updatedAt: new Date('2017-07-23'), |
|||
member: '高逼格设计天团', |
|||
href: '', |
|||
memberLink: '', |
|||
}, |
|||
{ |
|||
id: 'xxx6', |
|||
title: titles[5], |
|||
logo: avatars[5], |
|||
description: '生命就像一盒巧克力,结果往往出人意料', |
|||
updatedAt: new Date('2017-07-23'), |
|||
member: '骗你来学计算机', |
|||
href: '', |
|||
memberLink: '', |
|||
}, |
|||
]; |
|||
|
|||
const getActivities = [ |
|||
{ |
|||
id: 'trend-1', |
|||
updatedAt: new Date(), |
|||
user: { |
|||
name: '曲丽丽', |
|||
avatar: avatars2[0], |
|||
}, |
|||
group: { |
|||
name: '高逼格设计天团', |
|||
link: 'http://github.com/', |
|||
}, |
|||
project: { |
|||
name: '六月迭代', |
|||
link: 'http://github.com/', |
|||
}, |
|||
template: '在 @{group} 新建项目 @{project}', |
|||
}, |
|||
{ |
|||
id: 'trend-2', |
|||
updatedAt: new Date(), |
|||
user: { |
|||
name: '付小小', |
|||
avatar: avatars2[1], |
|||
}, |
|||
group: { |
|||
name: '高逼格设计天团', |
|||
link: 'http://github.com/', |
|||
}, |
|||
project: { |
|||
name: '六月迭代', |
|||
link: 'http://github.com/', |
|||
}, |
|||
template: '在 @{group} 新建项目 @{project}', |
|||
}, |
|||
{ |
|||
id: 'trend-3', |
|||
updatedAt: new Date(), |
|||
user: { |
|||
name: '林东东', |
|||
avatar: avatars2[2], |
|||
}, |
|||
group: { |
|||
name: '中二少女团', |
|||
link: 'http://github.com/', |
|||
}, |
|||
project: { |
|||
name: '六月迭代', |
|||
link: 'http://github.com/', |
|||
}, |
|||
template: '在 @{group} 新建项目 @{project}', |
|||
}, |
|||
{ |
|||
id: 'trend-4', |
|||
updatedAt: new Date(), |
|||
user: { |
|||
name: '周星星', |
|||
avatar: avatars2[4], |
|||
}, |
|||
project: { |
|||
name: '5 月日常迭代', |
|||
link: 'http://github.com/', |
|||
}, |
|||
template: '将 @{project} 更新至已发布状态', |
|||
}, |
|||
{ |
|||
id: 'trend-5', |
|||
updatedAt: new Date(), |
|||
user: { |
|||
name: '朱偏右', |
|||
avatar: avatars2[3], |
|||
}, |
|||
project: { |
|||
name: '工程效能', |
|||
link: 'http://github.com/', |
|||
}, |
|||
comment: { |
|||
name: '留言', |
|||
link: 'http://github.com/', |
|||
}, |
|||
template: '在 @{project} 发布了 @{comment}', |
|||
}, |
|||
{ |
|||
id: 'trend-6', |
|||
updatedAt: new Date(), |
|||
user: { |
|||
name: '乐哥', |
|||
avatar: avatars2[5], |
|||
}, |
|||
group: { |
|||
name: '程序员日常', |
|||
link: 'http://github.com/', |
|||
}, |
|||
project: { |
|||
name: '品牌迭代', |
|||
link: 'http://github.com/', |
|||
}, |
|||
template: '在 @{group} 新建项目 @{project}', |
|||
}, |
|||
]; |
|||
|
|||
function getFakeCaptcha(req, res) { |
|||
return res.json('captcha-xxx'); |
|||
} |
|||
|
|||
export default { |
|||
'GET /api/project/notice': getNotice, |
|||
'GET /api/activities': getActivities, |
|||
'POST /api/forms': (req, res) => { |
|||
res.send({ message: 'Ok' }); |
|||
}, |
|||
'GET /api/tags': mockjs.mock({ |
|||
'list|100': [{ name: '@city', 'value|1-100': 150, 'type|0-2': 1 }], |
|||
}), |
|||
'GET /api/fake_list': getFakeList, |
|||
'POST /api/fake_list': postFakeList, |
|||
'GET /api/captcha': getFakeCaptcha, |
|||
}; |
|||
@ -1,196 +0,0 @@ |
|||
import moment from 'moment'; |
|||
|
|||
// mock data
|
|||
const visitData = []; |
|||
const beginDay = new Date().getTime(); |
|||
|
|||
const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5]; |
|||
for (let i = 0; i < fakeY.length; i += 1) { |
|||
visitData.push({ |
|||
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'), |
|||
y: fakeY[i], |
|||
}); |
|||
} |
|||
|
|||
const visitData2 = []; |
|||
const fakeY2 = [1, 6, 4, 8, 3, 7, 2]; |
|||
for (let i = 0; i < fakeY2.length; i += 1) { |
|||
visitData2.push({ |
|||
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'), |
|||
y: fakeY2[i], |
|||
}); |
|||
} |
|||
|
|||
const salesData = []; |
|||
for (let i = 0; i < 12; i += 1) { |
|||
salesData.push({ |
|||
x: `${i + 1}月`, |
|||
y: Math.floor(Math.random() * 1000) + 200, |
|||
}); |
|||
} |
|||
const searchData = []; |
|||
for (let i = 0; i < 50; i += 1) { |
|||
searchData.push({ |
|||
index: i + 1, |
|||
keyword: `搜索关键词-${i}`, |
|||
count: Math.floor(Math.random() * 1000), |
|||
range: Math.floor(Math.random() * 100), |
|||
status: Math.floor((Math.random() * 10) % 2), |
|||
}); |
|||
} |
|||
const salesTypeData = [ |
|||
{ |
|||
x: '家用电器', |
|||
y: 4544, |
|||
}, |
|||
{ |
|||
x: '食用酒水', |
|||
y: 3321, |
|||
}, |
|||
{ |
|||
x: '个护健康', |
|||
y: 3113, |
|||
}, |
|||
{ |
|||
x: '服饰箱包', |
|||
y: 2341, |
|||
}, |
|||
{ |
|||
x: '母婴产品', |
|||
y: 1231, |
|||
}, |
|||
{ |
|||
x: '其他', |
|||
y: 1231, |
|||
}, |
|||
]; |
|||
|
|||
const salesTypeDataOnline = [ |
|||
{ |
|||
x: '家用电器', |
|||
y: 244, |
|||
}, |
|||
{ |
|||
x: '食用酒水', |
|||
y: 321, |
|||
}, |
|||
{ |
|||
x: '个护健康', |
|||
y: 311, |
|||
}, |
|||
{ |
|||
x: '服饰箱包', |
|||
y: 41, |
|||
}, |
|||
{ |
|||
x: '母婴产品', |
|||
y: 121, |
|||
}, |
|||
{ |
|||
x: '其他', |
|||
y: 111, |
|||
}, |
|||
]; |
|||
|
|||
const salesTypeDataOffline = [ |
|||
{ |
|||
x: '家用电器', |
|||
y: 99, |
|||
}, |
|||
{ |
|||
x: '食用酒水', |
|||
y: 188, |
|||
}, |
|||
{ |
|||
x: '个护健康', |
|||
y: 344, |
|||
}, |
|||
{ |
|||
x: '服饰箱包', |
|||
y: 255, |
|||
}, |
|||
{ |
|||
x: '其他', |
|||
y: 65, |
|||
}, |
|||
]; |
|||
|
|||
const offlineData = []; |
|||
for (let i = 0; i < 10; i += 1) { |
|||
offlineData.push({ |
|||
name: `Stores ${i}`, |
|||
cvr: Math.ceil(Math.random() * 9) / 10, |
|||
}); |
|||
} |
|||
const offlineChartData = []; |
|||
for (let i = 0; i < 20; i += 1) { |
|||
offlineChartData.push({ |
|||
x: new Date().getTime() + 1000 * 60 * 30 * i, |
|||
y1: Math.floor(Math.random() * 100) + 10, |
|||
y2: Math.floor(Math.random() * 100) + 10, |
|||
}); |
|||
} |
|||
|
|||
const radarOriginData = [ |
|||
{ |
|||
name: '个人', |
|||
ref: 10, |
|||
koubei: 8, |
|||
output: 4, |
|||
contribute: 5, |
|||
hot: 7, |
|||
}, |
|||
{ |
|||
name: '团队', |
|||
ref: 3, |
|||
koubei: 9, |
|||
output: 6, |
|||
contribute: 3, |
|||
hot: 1, |
|||
}, |
|||
{ |
|||
name: '部门', |
|||
ref: 4, |
|||
koubei: 1, |
|||
output: 6, |
|||
contribute: 5, |
|||
hot: 7, |
|||
}, |
|||
]; |
|||
|
|||
const radarData = []; |
|||
const radarTitleMap = { |
|||
ref: '引用', |
|||
koubei: '口碑', |
|||
output: '产量', |
|||
contribute: '贡献', |
|||
hot: '热度', |
|||
}; |
|||
radarOriginData.forEach(item => { |
|||
Object.keys(item).forEach(key => { |
|||
if (key !== 'name') { |
|||
radarData.push({ |
|||
name: item.name, |
|||
label: radarTitleMap[key], |
|||
value: item[key], |
|||
}); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
const getFakeChartData = { |
|||
visitData, |
|||
visitData2, |
|||
salesData, |
|||
searchData, |
|||
offlineData, |
|||
offlineChartData, |
|||
salesTypeData, |
|||
salesTypeDataOnline, |
|||
salesTypeDataOffline, |
|||
radarData, |
|||
}; |
|||
|
|||
export default { |
|||
'GET /api/fake_chart_data': getFakeChartData, |
|||
}; |
|||
@ -1,15 +0,0 @@ |
|||
import city from './geographic/city.json'; |
|||
import province from './geographic/province.json'; |
|||
|
|||
function getProvince(req, res) { |
|||
return res.json(province); |
|||
} |
|||
|
|||
function getCity(req, res) { |
|||
return res.json(city[req.params.province]); |
|||
} |
|||
|
|||
export default { |
|||
'GET /api/geographic/province': getProvince, |
|||
'GET /api/geographic/city/:province': getCity, |
|||
}; |
|||
File diff suppressed because it is too large
@ -1,138 +0,0 @@ |
|||
[ |
|||
{ |
|||
"name": "北京市", |
|||
"id": "110000" |
|||
}, |
|||
{ |
|||
"name": "天津市", |
|||
"id": "120000" |
|||
}, |
|||
{ |
|||
"name": "河北省", |
|||
"id": "130000" |
|||
}, |
|||
{ |
|||
"name": "山西省", |
|||
"id": "140000" |
|||
}, |
|||
{ |
|||
"name": "内蒙古自治区", |
|||
"id": "150000" |
|||
}, |
|||
{ |
|||
"name": "辽宁省", |
|||
"id": "210000" |
|||
}, |
|||
{ |
|||
"name": "吉林省", |
|||
"id": "220000" |
|||
}, |
|||
{ |
|||
"name": "黑龙江省", |
|||
"id": "230000" |
|||
}, |
|||
{ |
|||
"name": "上海市", |
|||
"id": "310000" |
|||
}, |
|||
{ |
|||
"name": "江苏省", |
|||
"id": "320000" |
|||
}, |
|||
{ |
|||
"name": "浙江省", |
|||
"id": "330000" |
|||
}, |
|||
{ |
|||
"name": "安徽省", |
|||
"id": "340000" |
|||
}, |
|||
{ |
|||
"name": "福建省", |
|||
"id": "350000" |
|||
}, |
|||
{ |
|||
"name": "江西省", |
|||
"id": "360000" |
|||
}, |
|||
{ |
|||
"name": "山东省", |
|||
"id": "370000" |
|||
}, |
|||
{ |
|||
"name": "河南省", |
|||
"id": "410000" |
|||
}, |
|||
{ |
|||
"name": "湖北省", |
|||
"id": "420000" |
|||
}, |
|||
{ |
|||
"name": "湖南省", |
|||
"id": "430000" |
|||
}, |
|||
{ |
|||
"name": "广东省", |
|||
"id": "440000" |
|||
}, |
|||
{ |
|||
"name": "广西壮族自治区", |
|||
"id": "450000" |
|||
}, |
|||
{ |
|||
"name": "海南省", |
|||
"id": "460000" |
|||
}, |
|||
{ |
|||
"name": "重庆市", |
|||
"id": "500000" |
|||
}, |
|||
{ |
|||
"name": "四川省", |
|||
"id": "510000" |
|||
}, |
|||
{ |
|||
"name": "贵州省", |
|||
"id": "520000" |
|||
}, |
|||
{ |
|||
"name": "云南省", |
|||
"id": "530000" |
|||
}, |
|||
{ |
|||
"name": "西藏自治区", |
|||
"id": "540000" |
|||
}, |
|||
{ |
|||
"name": "陕西省", |
|||
"id": "610000" |
|||
}, |
|||
{ |
|||
"name": "甘肃省", |
|||
"id": "620000" |
|||
}, |
|||
{ |
|||
"name": "青海省", |
|||
"id": "630000" |
|||
}, |
|||
{ |
|||
"name": "宁夏回族自治区", |
|||
"id": "640000" |
|||
}, |
|||
{ |
|||
"name": "新疆维吾尔自治区", |
|||
"id": "650000" |
|||
}, |
|||
{ |
|||
"name": "台湾省", |
|||
"id": "710000" |
|||
}, |
|||
{ |
|||
"name": "香港特别行政区", |
|||
"id": "810000" |
|||
}, |
|||
{ |
|||
"name": "澳门特别行政区", |
|||
"id": "820000" |
|||
} |
|||
] |
|||
@ -1,177 +0,0 @@ |
|||
import mockjs from 'mockjs'; |
|||
|
|||
const basicGoods = [ |
|||
{ |
|||
id: '1234561', |
|||
name: '矿泉水 550ml', |
|||
barcode: '12421432143214321', |
|||
price: '2.00', |
|||
num: '1', |
|||
amount: '2.00', |
|||
}, |
|||
{ |
|||
id: '1234562', |
|||
name: '凉茶 300ml', |
|||
barcode: '12421432143214322', |
|||
price: '3.00', |
|||
num: '2', |
|||
amount: '6.00', |
|||
}, |
|||
{ |
|||
id: '1234563', |
|||
name: '好吃的薯片', |
|||
barcode: '12421432143214323', |
|||
price: '7.00', |
|||
num: '4', |
|||
amount: '28.00', |
|||
}, |
|||
{ |
|||
id: '1234564', |
|||
name: '特别好吃的蛋卷', |
|||
barcode: '12421432143214324', |
|||
price: '8.50', |
|||
num: '3', |
|||
amount: '25.50', |
|||
}, |
|||
]; |
|||
|
|||
const basicProgress = [ |
|||
{ |
|||
key: '1', |
|||
time: '2017-10-01 14:10', |
|||
rate: '联系客户', |
|||
status: 'processing', |
|||
operator: '取货员 ID1234', |
|||
cost: '5mins', |
|||
}, |
|||
{ |
|||
key: '2', |
|||
time: '2017-10-01 14:05', |
|||
rate: '取货员出发', |
|||
status: 'success', |
|||
operator: '取货员 ID1234', |
|||
cost: '1h', |
|||
}, |
|||
{ |
|||
key: '3', |
|||
time: '2017-10-01 13:05', |
|||
rate: '取货员接单', |
|||
status: 'success', |
|||
operator: '取货员 ID1234', |
|||
cost: '5mins', |
|||
}, |
|||
{ |
|||
key: '4', |
|||
time: '2017-10-01 13:00', |
|||
rate: '申请审批通过', |
|||
status: 'success', |
|||
operator: '系统', |
|||
cost: '1h', |
|||
}, |
|||
{ |
|||
key: '5', |
|||
time: '2017-10-01 12:00', |
|||
rate: '发起退货申请', |
|||
status: 'success', |
|||
operator: '用户', |
|||
cost: '5mins', |
|||
}, |
|||
]; |
|||
|
|||
const advancedOperation1 = [ |
|||
{ |
|||
key: 'op1', |
|||
type: '订购关系生效', |
|||
name: '曲丽丽', |
|||
status: 'agree', |
|||
updatedAt: '2017-10-03 19:23:12', |
|||
memo: '-', |
|||
}, |
|||
{ |
|||
key: 'op2', |
|||
type: '财务复审', |
|||
name: '付小小', |
|||
status: 'reject', |
|||
updatedAt: '2017-10-03 19:23:12', |
|||
memo: '不通过原因', |
|||
}, |
|||
{ |
|||
key: 'op3', |
|||
type: '部门初审', |
|||
name: '周毛毛', |
|||
status: 'agree', |
|||
updatedAt: '2017-10-03 19:23:12', |
|||
memo: '-', |
|||
}, |
|||
{ |
|||
key: 'op4', |
|||
type: '提交订单', |
|||
name: '林东东', |
|||
status: 'agree', |
|||
updatedAt: '2017-10-03 19:23:12', |
|||
memo: '很棒', |
|||
}, |
|||
{ |
|||
key: 'op5', |
|||
type: '创建订单', |
|||
name: '汗牙牙', |
|||
status: 'agree', |
|||
updatedAt: '2017-10-03 19:23:12', |
|||
memo: '-', |
|||
}, |
|||
]; |
|||
|
|||
const advancedOperation2 = [ |
|||
{ |
|||
key: 'op1', |
|||
type: '订购关系生效', |
|||
name: '曲丽丽', |
|||
status: 'agree', |
|||
updatedAt: '2017-10-03 19:23:12', |
|||
memo: '-', |
|||
}, |
|||
]; |
|||
|
|||
const advancedOperation3 = [ |
|||
{ |
|||
key: 'op1', |
|||
type: '创建订单', |
|||
name: '汗牙牙', |
|||
status: 'agree', |
|||
updatedAt: '2017-10-03 19:23:12', |
|||
memo: '-', |
|||
}, |
|||
]; |
|||
const getProfileAdvancedData = { |
|||
advancedOperation1, |
|||
advancedOperation2, |
|||
advancedOperation3, |
|||
}; |
|||
|
|||
const { Random } = mockjs; |
|||
|
|||
export default { |
|||
'GET /api/profile/advanced': getProfileAdvancedData, |
|||
'GET /api/profile/basic': (req, res) => { |
|||
const { id } = req.query; |
|||
const application = { |
|||
id, |
|||
status: '已取货', |
|||
orderNo: Random.id(), |
|||
childOrderNo: Random.id(), |
|||
}; |
|||
const userInfo = { |
|||
name: Random.cname(), |
|||
tel: '18100000000', |
|||
delivery: '菜鸟物流', |
|||
addr: '浙江省杭州市西湖区万塘路18号', |
|||
remark: '备注', |
|||
}; |
|||
res.json({ |
|||
userInfo, |
|||
application, |
|||
basicGoods, |
|||
basicProgress, |
|||
}); |
|||
}, |
|||
}; |
|||
@ -0,0 +1,5 @@ |
|||
export default { |
|||
'/api/auth_routes': { |
|||
'/form/advanced-form': { authority: ['admin', 'user'] }, |
|||
}, |
|||
}; |
|||
@ -1,131 +0,0 @@ |
|||
import { parse } from 'url'; |
|||
|
|||
// mock tableListDataSource
|
|||
let tableListDataSource = []; |
|||
for (let i = 0; i < 46; i += 1) { |
|||
tableListDataSource.push({ |
|||
key: i, |
|||
disabled: i % 6 === 0, |
|||
href: 'https://ant.design', |
|||
avatar: [ |
|||
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
|||
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
|||
][i % 2], |
|||
name: `TradeCode ${i}`, |
|||
title: `一个任务名称 ${i}`, |
|||
owner: '曲丽丽', |
|||
desc: '这是一段描述', |
|||
callNo: Math.floor(Math.random() * 1000), |
|||
status: Math.floor(Math.random() * 10) % 4, |
|||
updatedAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`), |
|||
createdAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`), |
|||
progress: Math.ceil(Math.random() * 100), |
|||
}); |
|||
} |
|||
|
|||
function getRule(req, res, u) { |
|||
let url = u; |
|||
if (!url || Object.prototype.toString.call(url) !== '[object String]') { |
|||
url = req.url; // eslint-disable-line
|
|||
} |
|||
|
|||
const params = parse(url, true).query; |
|||
|
|||
let dataSource = tableListDataSource; |
|||
|
|||
if (params.sorter) { |
|||
const s = params.sorter.split('_'); |
|||
dataSource = dataSource.sort((prev, next) => { |
|||
if (s[1] === 'descend') { |
|||
return next[s[0]] - prev[s[0]]; |
|||
} |
|||
return prev[s[0]] - next[s[0]]; |
|||
}); |
|||
} |
|||
|
|||
if (params.status) { |
|||
const status = params.status.split(','); |
|||
let filterDataSource = []; |
|||
status.forEach(s => { |
|||
filterDataSource = filterDataSource.concat( |
|||
dataSource.filter(data => parseInt(data.status, 10) === parseInt(s[0], 10)) |
|||
); |
|||
}); |
|||
dataSource = filterDataSource; |
|||
} |
|||
|
|||
if (params.name) { |
|||
dataSource = dataSource.filter(data => data.name.indexOf(params.name) > -1); |
|||
} |
|||
|
|||
let pageSize = 10; |
|||
if (params.pageSize) { |
|||
pageSize = params.pageSize * 1; |
|||
} |
|||
|
|||
const result = { |
|||
list: dataSource, |
|||
pagination: { |
|||
total: dataSource.length, |
|||
pageSize, |
|||
current: parseInt(params.currentPage, 10) || 1, |
|||
}, |
|||
}; |
|||
|
|||
return res.json(result); |
|||
} |
|||
|
|||
function postRule(req, res, u, b) { |
|||
let url = u; |
|||
if (!url || Object.prototype.toString.call(url) !== '[object String]') { |
|||
url = req.url; // eslint-disable-line
|
|||
} |
|||
|
|||
const body = (b && b.body) || req.body; |
|||
const { method, name, desc, key } = body; |
|||
|
|||
switch (method) { |
|||
/* eslint no-case-declarations:0 */ |
|||
case 'delete': |
|||
tableListDataSource = tableListDataSource.filter(item => key.indexOf(item.key) === -1); |
|||
break; |
|||
case 'post': |
|||
const i = Math.ceil(Math.random() * 10000); |
|||
tableListDataSource.unshift({ |
|||
key: i, |
|||
href: 'https://ant.design', |
|||
avatar: [ |
|||
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', |
|||
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', |
|||
][i % 2], |
|||
name: `TradeCode ${i}`, |
|||
title: `一个任务名称 ${i}`, |
|||
owner: '曲丽丽', |
|||
desc, |
|||
callNo: Math.floor(Math.random() * 1000), |
|||
status: Math.floor(Math.random() * 10) % 2, |
|||
updatedAt: new Date(), |
|||
createdAt: new Date(), |
|||
progress: Math.ceil(Math.random() * 100), |
|||
}); |
|||
break; |
|||
case 'update': |
|||
tableListDataSource = tableListDataSource.map(item => { |
|||
if (item.key === key) { |
|||
Object.assign(item, { desc, name }); |
|||
return item; |
|||
} |
|||
return item; |
|||
}); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
|
|||
return getRule(req, res, u); |
|||
} |
|||
|
|||
export default { |
|||
'GET /api/rule': getRule, |
|||
'POST /api/rule': postRule, |
|||
}; |
|||
@ -0,0 +1,129 @@ |
|||
const path = require('path'); |
|||
const fs = require('fs'); |
|||
const fetch = require('node-fetch'); |
|||
const exec = require('child_process').exec; |
|||
const getNewRouteCode = require('./repalceRouter'); |
|||
const router = require('./router.config'); |
|||
const chalk = require('chalk'); |
|||
const insertCode = require('./insertCode'); |
|||
|
|||
const fetchGithubFiles = async () => { |
|||
const ignoreFile = ['_scripts']; |
|||
const data = await fetch(`https://api.github.com/repos/ant-design/pro-blocks/git/trees/master`); |
|||
if (data.status !== 200) { |
|||
return; |
|||
} |
|||
const { tree } = await data.json(); |
|||
const files = tree.filter(file => file.type === 'tree' && !ignoreFile.includes(file.path)); |
|||
return Promise.resolve(files); |
|||
}; |
|||
|
|||
const relativePath = path.join(__dirname, '../config/config.ts'); |
|||
|
|||
const findAllInstallRouter = router => { |
|||
let routers = []; |
|||
router.forEach(item => { |
|||
if (item.component && item.path) { |
|||
if (item.path !== '/user' || item.path !== '/') { |
|||
routers.push({ |
|||
...item, |
|||
routes: !!item.routes, |
|||
}); |
|||
} |
|||
} |
|||
if (item.routes) { |
|||
routers = routers.concat(findAllInstallRouter(item.routes)); |
|||
} |
|||
}); |
|||
return routers; |
|||
}; |
|||
|
|||
const filterParentRouter = (router, layout) => { |
|||
return [...router] |
|||
.map(item => { |
|||
if (item.routes && (!router.component || layout)) { |
|||
return { ...item, routes: filterParentRouter(item.routes, false) }; |
|||
} |
|||
if (item.redirect) { |
|||
return item; |
|||
} |
|||
return null; |
|||
}) |
|||
.filter(item => item); |
|||
}; |
|||
const firstUpperCase = pathString => { |
|||
return pathString |
|||
.replace('.', '') |
|||
.split(/\/|\-/) |
|||
.map(s => s.toLowerCase().replace(/( |^)[a-z]/g, L => L.toUpperCase())) |
|||
.filter(s => s) |
|||
.join(''); |
|||
}; |
|||
|
|||
const execCmd = shell => { |
|||
return new Promise((resolve, reject) => { |
|||
exec(shell, { encoding: 'utf8' }, (error, statusbar) => { |
|||
if (error) { |
|||
console.log(error); |
|||
return reject(error); |
|||
} |
|||
console.log(statusbar); |
|||
resolve(); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
// replace router config
|
|||
const parentRouter = filterParentRouter(router, true); |
|||
const { routesPath, code } = getNewRouteCode(relativePath, parentRouter); |
|||
// write ParentRouter
|
|||
fs.writeFileSync(routesPath, code); |
|||
|
|||
const installBlock = async () => { |
|||
let gitFiles = await fetchGithubFiles(); |
|||
const installRouters = findAllInstallRouter(router); |
|||
const installBlockIteration = async i => { |
|||
const item = installRouters[i]; |
|||
|
|||
if (!item || !item.path) { |
|||
return Promise.resolve(); |
|||
} |
|||
const gitPath = firstUpperCase(item.path); |
|||
// 如果这个区块在 git 上存在
|
|||
if (gitFiles.find(file => file.path === gitPath)) { |
|||
console.log('install ' + chalk.green(item.name) + ' to: ' + chalk.yellow(item.path)); |
|||
gitFiles = gitFiles.filter(file => file.path !== gitPath); |
|||
const skipModifyRouter = item.routes ? '--skip-modify-routes' : ''; |
|||
const cmd = `umi block add https://github.com/ant-design/pro-blocks/tree/master/${gitPath} --path=${ |
|||
item.path |
|||
} ${skipModifyRouter}`;
|
|||
try { |
|||
await execCmd(cmd); |
|||
console.log(`install ${chalk.hex('#1890ff')(item.name)} success`); |
|||
} catch (error) { |
|||
console.error(error); |
|||
} |
|||
} |
|||
return installBlockIteration(i + 1); |
|||
}; |
|||
// 安装路由中设置的区块
|
|||
await installBlockIteration(0); |
|||
|
|||
const installGitFile = async i => { |
|||
const item = gitFiles[i]; |
|||
if (!item || !item.path) { |
|||
return Promise.resolve(); |
|||
} |
|||
console.log('install ' + chalk.green(item.path)); |
|||
const cmd = `umi block add https://github.com/ant-design/pro-blocks/tree/master/${item.path}`; |
|||
await execCmd(cmd); |
|||
return installBlockIteration(1); |
|||
}; |
|||
|
|||
// 安装 router 中没有的剩余区块.
|
|||
installGitFile(0); |
|||
}; |
|||
installBlock().then(() => { |
|||
// 插入 pro 需要的演示代码
|
|||
insertCode(); |
|||
}); |
|||
@ -1,3 +0,0 @@ |
|||
const generateMock = require('merge-umi-mock-data'); |
|||
const path = require('path'); |
|||
generateMock(path.join(__dirname, '../mock'), path.join(__dirname, '../lambda/mock/index.js')); |
|||
@ -1,23 +0,0 @@ |
|||
const glob = require('glob'); |
|||
|
|||
const getPrettierFiles = () => { |
|||
let files = []; |
|||
const jsFiles = glob.sync('src/**/*.js*', { ignore: ['**/node_modules/**', 'build/**'] }); |
|||
const tsFiles = glob.sync('src/**/*.ts*', { ignore: ['**/node_modules/**', 'build/**'] }); |
|||
const configFiles = glob.sync('config/**/*.js*', { ignore: ['**/node_modules/**', 'build/**'] }); |
|||
const scriptFiles = glob.sync('scripts/**/*.js'); |
|||
const lessFiles = glob.sync('src/**/*.less*', { ignore: ['**/node_modules/**', 'build/**'] }); |
|||
const mdFiles = glob.sync('src/**/*.md*', { ignore: ['**/node_modules/**', 'build/**'] }); |
|||
files = files.concat(jsFiles); |
|||
files = files.concat(tsFiles); |
|||
files = files.concat(configFiles); |
|||
files = files.concat(scriptFiles); |
|||
files = files.concat(lessFiles); |
|||
files = files.concat(mdFiles); |
|||
if (!files.length) { |
|||
return; |
|||
} |
|||
return files; |
|||
}; |
|||
|
|||
module.exports = getPrettierFiles; |
|||
@ -0,0 +1,161 @@ |
|||
const parser = require('@babel/parser'); |
|||
const traverse = require('@babel/traverse'); |
|||
const generate = require('@babel/generator'); |
|||
const t = require('@babel/types'); |
|||
const fs = require('fs'); |
|||
const path = require('path'); |
|||
const prettier = require('prettier'); |
|||
const chalk = require('chalk'); |
|||
|
|||
const parseCode = code => { |
|||
return parser.parse(code, { |
|||
sourceType: 'module', |
|||
plugins: ['typescript', 'jsx'], |
|||
}).program.body[0]; |
|||
}; |
|||
|
|||
/** |
|||
* 生成代码 |
|||
* @param {*} ast |
|||
*/ |
|||
function generateCode(ast) { |
|||
const newCode = generate.default(ast, {}).code; |
|||
return prettier.format(newCode, { |
|||
// format same as ant-design-pro
|
|||
singleQuote: true, |
|||
trailingComma: 'es5', |
|||
printWidth: 100, |
|||
parser: 'typescript', |
|||
}); |
|||
} |
|||
|
|||
const SettingCodeString = ` |
|||
<SettingDrawer |
|||
settings={settings} |
|||
onSettingChange={config => |
|||
dispatch({ |
|||
type: 'settings/changeSetting', |
|||
payload: config, |
|||
}) |
|||
} |
|||
/> |
|||
`;
|
|||
|
|||
const mapAst = (configPath, callBack) => { |
|||
const ast = parser.parse(fs.readFileSync(configPath, 'utf-8'), { |
|||
sourceType: 'module', |
|||
plugins: ['typescript', 'jsx'], |
|||
}); |
|||
// 查询当前配置文件是否导出 routes 属性
|
|||
traverse.default(ast, { |
|||
Program({ node }) { |
|||
const { body } = node; |
|||
callBack(body); |
|||
}, |
|||
}); |
|||
return generateCode(ast); |
|||
}; |
|||
|
|||
const insertBasicLayout = configPath => { |
|||
return mapAst(configPath, body => { |
|||
const index = body.findIndex(item => { |
|||
return item.type !== 'ImportDeclaration'; |
|||
}); |
|||
|
|||
body.forEach(item => { |
|||
// 从包中导出 SettingDrawer
|
|||
if (item.type === 'ImportDeclaration') { |
|||
if (item.source.value === '@ant-design/pro-layout') { |
|||
item.specifiers.push(parseCode(`SettingDrawer`).expression); |
|||
} |
|||
} |
|||
if (item.type === 'VariableDeclaration') { |
|||
const { |
|||
id, |
|||
init: { body }, |
|||
} = item.declarations[0]; |
|||
// 给 BasicLayout 中插入 button 和 设置抽屉
|
|||
if (id.name === `BasicLayout`) { |
|||
body.body.forEach(node => { |
|||
if (node.type === 'ReturnStatement') { |
|||
const JSXFragment = parseCode(`<></>`).expression; |
|||
JSXFragment.children.push({ ...node.argument }); |
|||
JSXFragment.children.push(parseCode(SettingCodeString).expression); |
|||
node.argument = JSXFragment; |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
const insertBlankLayout = configPath => { |
|||
return mapAst(configPath, body => { |
|||
const index = body.findIndex(item => { |
|||
return item.type !== 'ImportDeclaration'; |
|||
}); |
|||
// 从组件中导入 CopyBlock
|
|||
body.splice( |
|||
index, |
|||
0, |
|||
parseCode(`import CopyBlock from '@/components/CopyBlock';
|
|||
`),
|
|||
); |
|||
body.forEach(item => { |
|||
if (item.type === 'VariableDeclaration') { |
|||
const { id, init } = item.declarations[0]; |
|||
// 给 BasicLayout 中插入 button 和 设置抽屉
|
|||
if (id.name === `Layout`) { |
|||
const JSXFragment = parseCode(`<></>`).expression; |
|||
JSXFragment.children.push({ ...init.body }); |
|||
JSXFragment.children.push(parseCode(` <CopyBlock id={Date.now()}/>`).expression); |
|||
init.body = JSXFragment; |
|||
} |
|||
} |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
const insertRightContent = configPath => { |
|||
return mapAst(configPath, body => { |
|||
const index = body.findIndex(item => { |
|||
return item.type !== 'ImportDeclaration'; |
|||
}); |
|||
// 从组件中导入 CopyBlock
|
|||
body.splice(index, 0, parseCode(`import NoticeIconView from './NoticeIconView';`)); |
|||
|
|||
body.forEach(item => { |
|||
if (item.type === 'ClassDeclaration') { |
|||
const classBody = item.body.body[0].body; |
|||
classBody.body.forEach(node => { |
|||
if (node.type === 'ReturnStatement') { |
|||
const index = node.argument.children.findIndex(item => { |
|||
if (item.type === 'JSXElement') { |
|||
if (item.openingElement.name.name === 'Avatar') { |
|||
return true; |
|||
} |
|||
} |
|||
}); |
|||
node.argument.children.splice(index, 1, parseCode(`<Avatar menu />`).expression); |
|||
node.argument.children.splice(index, 0, parseCode(`<NoticeIconView />`).expression); |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
module.exports = () => { |
|||
const basicLayoutPath = path.join(__dirname, '../src/layouts/BasicLayout.tsx'); |
|||
fs.writeFileSync(basicLayoutPath, insertBasicLayout(basicLayoutPath)); |
|||
console.log(`insert ${chalk.hex('#1890ff')('BasicLayout')} success`); |
|||
|
|||
const rightContentPath = path.join(__dirname, '../src/components/GlobalHeader/RightContent.tsx'); |
|||
fs.writeFileSync(rightContentPath, insertRightContent(rightContentPath)); |
|||
console.log(`insert ${chalk.hex('#1890ff')('RightContent')} success`); |
|||
|
|||
const blankLayoutPath = path.join(__dirname, '../src/layouts/BlankLayout.tsx'); |
|||
fs.writeFileSync(blankLayoutPath, insertBlankLayout(blankLayoutPath)); |
|||
console.log(`insert ${chalk.hex('#1890ff')('blankLayoutPath')} success`); |
|||
}; |
|||
@ -1,50 +0,0 @@ |
|||
/** |
|||
* copy to https://github.com/facebook/react/blob/master/scripts/prettier/index.js
|
|||
* prettier api doc https://prettier.io/docs/en/api.html
|
|||
*----------*****-------------- |
|||
* lint file is prettier |
|||
*----------*****-------------- |
|||
*/ |
|||
|
|||
const prettier = require('prettier'); |
|||
const fs = require('fs'); |
|||
const chalk = require('chalk'); |
|||
const prettierConfigPath = require.resolve('../.prettierrc'); |
|||
|
|||
const files = process.argv.slice(2); |
|||
|
|||
let didError = false; |
|||
|
|||
files.forEach(file => { |
|||
Promise.all([ |
|||
prettier.resolveConfig(file, { |
|||
config: prettierConfigPath, |
|||
}), |
|||
prettier.getFileInfo(file), |
|||
]) |
|||
.then(resolves => { |
|||
const [options, fileInfo] = resolves; |
|||
if (fileInfo.ignored) { |
|||
return; |
|||
} |
|||
const input = fs.readFileSync(file, 'utf8'); |
|||
const withParserOptions = { |
|||
...options, |
|||
parser: fileInfo.inferredParser, |
|||
}; |
|||
const output = prettier.format(input, withParserOptions); |
|||
if (output !== input) { |
|||
fs.writeFileSync(file, output, 'utf8'); |
|||
console.log(chalk.green(`${file} is prettier`)); |
|||
} |
|||
}) |
|||
.catch(e => { |
|||
didError = true; |
|||
}) |
|||
.finally(() => { |
|||
if (didError) { |
|||
process.exit(1); |
|||
} |
|||
console.log(chalk.hex('#1890FF')('prettier success!')); |
|||
}); |
|||
}); |
|||
@ -1,46 +0,0 @@ |
|||
/** |
|||
* copy to https://github.com/facebook/react/blob/master/scripts/prettier/index.js
|
|||
* prettier api doc https://prettier.io/docs/en/api.html
|
|||
*----------*****-------------- |
|||
* prettier all js and all ts. |
|||
*----------*****-------------- |
|||
*/ |
|||
|
|||
const prettier = require('prettier'); |
|||
const fs = require('fs'); |
|||
const getPrettierFiles = require('./getPrettierFiles'); |
|||
const prettierConfigPath = require.resolve('../.prettierrc'); |
|||
const chalk = require('chalk'); |
|||
|
|||
let didError = false; |
|||
|
|||
const files = getPrettierFiles(); |
|||
|
|||
files.forEach(file => { |
|||
const options = prettier.resolveConfig.sync(file, { |
|||
config: prettierConfigPath, |
|||
}); |
|||
const fileInfo = prettier.getFileInfo.sync(file); |
|||
if (fileInfo.ignored) { |
|||
return; |
|||
} |
|||
try { |
|||
const input = fs.readFileSync(file, 'utf8'); |
|||
const withParserOptions = { |
|||
...options, |
|||
parser: fileInfo.inferredParser, |
|||
}; |
|||
const output = prettier.format(input, withParserOptions); |
|||
if (output !== input) { |
|||
fs.writeFileSync(file, output, 'utf8'); |
|||
console.log(chalk.green(`${file} is prettier`)); |
|||
} |
|||
} catch (e) { |
|||
didError = true; |
|||
} |
|||
}); |
|||
|
|||
if (didError) { |
|||
process.exit(1); |
|||
} |
|||
console.log(chalk.hex('#1890FF')('prettier success!')); |
|||
@ -0,0 +1,77 @@ |
|||
const parser = require('@babel/parser'); |
|||
const traverse = require('@babel/traverse'); |
|||
const generate = require('@babel/generator'); |
|||
const t = require('@babel/types'); |
|||
const fs = require('fs'); |
|||
const prettier = require('prettier'); |
|||
|
|||
const getNewRouteCode = (configPath, newRoute) => { |
|||
const ast = parser.parse(fs.readFileSync(configPath, 'utf-8'), { |
|||
sourceType: 'module', |
|||
plugins: ['typescript'], |
|||
}); |
|||
let routesNode = null; |
|||
const importModules = []; |
|||
// 查询当前配置文件是否导出 routes 属性
|
|||
traverse.default(ast, { |
|||
Program({ node }) { |
|||
// find import
|
|||
const { body } = node; |
|||
body.forEach(item => { |
|||
if (t.isImportDeclaration(item)) { |
|||
const { specifiers } = item; |
|||
const defaultEpecifier = specifiers.find(s => { |
|||
return t.isImportDefaultSpecifier(s) && t.isIdentifier(s.local); |
|||
}); |
|||
if (defaultEpecifier && t.isStringLiteral(item.source)) { |
|||
importModules.push({ |
|||
identifierName: defaultEpecifier.local.name, |
|||
modulePath: item.source.value, |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
}, |
|||
ObjectExpression({ node, parent }) { |
|||
// find routes on object, like { routes: [] }
|
|||
if (t.isArrayExpression(parent)) { |
|||
// children routes
|
|||
return; |
|||
} |
|||
const { properties } = node; |
|||
properties.forEach(p => { |
|||
const { key, value } = p; |
|||
if (t.isObjectProperty(p) && t.isIdentifier(key) && key.name === 'routes') { |
|||
if (value) { |
|||
// find json file program expression
|
|||
(p.value = parser.parse(JSON.stringify(newRoute)).program.body[0].expression), |
|||
(routesNode = value); |
|||
} |
|||
} |
|||
}); |
|||
}, |
|||
}); |
|||
if (routesNode) { |
|||
const code = generateCode(ast); |
|||
return { code, routesPath: configPath }; |
|||
} else { |
|||
throw new Error('route array config not found.'); |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* 生成代码 |
|||
* @param {*} ast |
|||
*/ |
|||
function generateCode(ast) { |
|||
const newCode = generate.default(ast, {}).code; |
|||
return prettier.format(newCode, { |
|||
// format same as ant-design-pro
|
|||
singleQuote: true, |
|||
trailingComma: 'es5', |
|||
printWidth: 100, |
|||
parser: 'typescript', |
|||
}); |
|||
} |
|||
|
|||
module.exports = getNewRouteCode; |
|||
@ -0,0 +1,236 @@ |
|||
module.exports = [ |
|||
{ |
|||
path: '/', |
|||
component: '../layouts/BlankLayout', |
|||
routes: [ |
|||
// user
|
|||
{ |
|||
path: '/user', |
|||
component: '../layouts/UserLayout', |
|||
routes: [ |
|||
{ path: '/user/login', name: 'login', component: './User/Login' }, |
|||
{ path: '/user/register', name: 'register', component: './User/Register' }, |
|||
{ |
|||
path: '/user/register-result', |
|||
name: 'register.result', |
|||
component: './User/RegisterResult', |
|||
}, |
|||
{ path: '/user', redirect: '/user/login' }, |
|||
{ |
|||
component: '404', |
|||
}, |
|||
], |
|||
}, |
|||
// app
|
|||
{ |
|||
path: '/', |
|||
component: '../layouts/BasicLayout', |
|||
Routes: ['src/pages/Authorized'], |
|||
authority: ['admin', 'user'], |
|||
routes: [ |
|||
// dashboard
|
|||
{ |
|||
path: '/dashboard', |
|||
name: 'dashboard', |
|||
icon: 'dashboard', |
|||
routes: [ |
|||
{ |
|||
path: '/dashboard/analysis', |
|||
name: 'analysis', |
|||
component: './Dashboard/Analysis', |
|||
}, |
|||
{ |
|||
path: '/dashboard/monitor', |
|||
name: 'monitor', |
|||
component: './Dashboard/Monitor', |
|||
}, |
|||
{ |
|||
path: '/dashboard/workplace', |
|||
name: 'workplace', |
|||
component: './Dashboard/Workplace', |
|||
}, |
|||
], |
|||
}, |
|||
// forms
|
|||
{ |
|||
path: '/form', |
|||
icon: 'form', |
|||
name: 'form', |
|||
routes: [ |
|||
{ |
|||
path: '/form/basic-form', |
|||
name: 'basicform', |
|||
component: './Form/BasicForm', |
|||
}, |
|||
{ |
|||
path: '/form/step-form', |
|||
name: 'stepform', |
|||
component: './Form/StepForm', |
|||
}, |
|||
{ |
|||
path: '/form/advanced-form', |
|||
name: 'advancedform', |
|||
authority: ['admin'], |
|||
component: './Form/AdvancedForm', |
|||
}, |
|||
], |
|||
}, |
|||
// list
|
|||
{ |
|||
path: '/list', |
|||
icon: 'table', |
|||
name: 'list', |
|||
routes: [ |
|||
{ |
|||
path: '/list/table-list', |
|||
name: 'searchtable', |
|||
component: './list/Tablelist', |
|||
}, |
|||
{ |
|||
path: '/list/basic-list', |
|||
name: 'basiclist', |
|||
component: './list/Basiclist', |
|||
}, |
|||
{ |
|||
path: '/list/card-list', |
|||
name: 'cardlist', |
|||
component: './list/Cardlist', |
|||
}, |
|||
{ |
|||
path: '/list/search', |
|||
name: 'search-list', |
|||
component: './list/search', |
|||
routes: [ |
|||
{ |
|||
path: '/list/search/articles', |
|||
name: 'articles', |
|||
component: './list/Articles', |
|||
}, |
|||
{ |
|||
path: '/list/search/projects', |
|||
name: 'projects', |
|||
component: './list/Projects', |
|||
}, |
|||
{ |
|||
path: '/list/search/applications', |
|||
name: 'applications', |
|||
component: './list/Applications', |
|||
}, |
|||
{ |
|||
path: '/list/search', |
|||
redirect: '/list/search/articles', |
|||
}, |
|||
], |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
path: '/profile', |
|||
name: 'profile', |
|||
icon: 'profile', |
|||
routes: [ |
|||
// profile
|
|||
{ |
|||
path: '/profile/basic', |
|||
name: 'basic', |
|||
component: './Profile/BasicProfile', |
|||
}, |
|||
{ |
|||
path: '/profile/basic/:id', |
|||
hideInMenu: true, |
|||
component: './Profile/BasicProfile', |
|||
}, |
|||
{ |
|||
path: '/profile/advanced', |
|||
name: 'advanced', |
|||
authority: ['admin'], |
|||
component: './Profile/AdvancedProfile', |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
name: 'result', |
|||
icon: 'check-circle-o', |
|||
path: '/result', |
|||
routes: [ |
|||
// result
|
|||
{ |
|||
path: '/result/success', |
|||
name: 'success', |
|||
component: './Result/Success', |
|||
}, |
|||
{ path: '/result/fail', name: 'fail', component: './Result/Error' }, |
|||
], |
|||
}, |
|||
{ |
|||
name: 'exception', |
|||
icon: 'warning', |
|||
path: '/exception', |
|||
routes: [ |
|||
// exception
|
|||
{ |
|||
path: '/exception/403', |
|||
name: 'not-permission', |
|||
component: './Exception/403', |
|||
}, |
|||
{ |
|||
path: '/exception/404', |
|||
name: 'not-find', |
|||
component: './Exception/404', |
|||
}, |
|||
{ |
|||
path: '/exception/500', |
|||
name: 'server-error', |
|||
component: './Exception/500', |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
name: 'account', |
|||
icon: 'user', |
|||
path: '/account', |
|||
routes: [ |
|||
{ |
|||
path: '/account/center', |
|||
name: 'center', |
|||
component: './Account/Center/Center', |
|||
}, |
|||
{ |
|||
path: '/account/settings', |
|||
name: 'settings', |
|||
component: './Account/Settings/Info', |
|||
}, |
|||
], |
|||
}, |
|||
// editor
|
|||
{ |
|||
name: 'editor', |
|||
icon: 'highlight', |
|||
path: '/editor', |
|||
routes: [ |
|||
{ |
|||
path: '/editor/flow', |
|||
name: 'flow', |
|||
component: './Editor/GGEditor/Flow', |
|||
}, |
|||
{ |
|||
path: '/editor/mind', |
|||
name: 'mind', |
|||
component: './Editor/GGEditor/Mind', |
|||
}, |
|||
{ |
|||
path: '/editor/koni', |
|||
name: 'koni', |
|||
component: './Editor/GGEditor/Koni', |
|||
}, |
|||
], |
|||
}, |
|||
{ path: '/', redirect: '/dashboard/analysis', authority: ['admin', 'user'] }, |
|||
{ |
|||
component: '404', |
|||
}, |
|||
], |
|||
}, |
|||
], |
|||
}, |
|||
]; |
|||
@ -1,11 +0,0 @@ |
|||
export const dva = { |
|||
config: { |
|||
onError(err) { |
|||
err.preventDefault(); |
|||
}, |
|||
}, |
|||
}; |
|||
|
|||
export function render(oldRender) { |
|||
oldRender(); |
|||
} |
|||
@ -1,98 +0,0 @@ |
|||
import React, { Component } from 'react'; |
|||
import { MiniArea } from '../Charts'; |
|||
import NumberInfo from '../NumberInfo'; |
|||
import styles from './index.less'; |
|||
|
|||
function fixedZero(val) { |
|||
return val * 1 < 10 ? `0${val}` : val; |
|||
} |
|||
|
|||
function getActiveData() { |
|||
const activeData = []; |
|||
for (let i = 0; i < 24; i += 1) { |
|||
activeData.push({ |
|||
x: `${fixedZero(i)}:00`, |
|||
y: Math.floor(Math.random() * 200) + i * 50, |
|||
}); |
|||
} |
|||
return activeData; |
|||
} |
|||
|
|||
export default class ActiveChart extends Component { |
|||
state = { |
|||
activeData: getActiveData(), |
|||
}; |
|||
|
|||
componentDidMount() { |
|||
this.loopData(); |
|||
} |
|||
|
|||
componentWillUnmount() { |
|||
clearTimeout(this.timer); |
|||
cancelAnimationFrame(this.requestRef); |
|||
} |
|||
|
|||
loopData = () => { |
|||
this.timer = setTimeout(() => { |
|||
this.setState( |
|||
{ |
|||
activeData: getActiveData(), |
|||
}, |
|||
() => { |
|||
this.loopData(); |
|||
} |
|||
); |
|||
}, 500); |
|||
}; |
|||
|
|||
render() { |
|||
const { activeData = [] } = this.state; |
|||
|
|||
return ( |
|||
<div className={styles.activeChart}> |
|||
<NumberInfo subTitle="目标评估" total="有望达到预期" /> |
|||
<div style={{ marginTop: 32 }}> |
|||
<MiniArea |
|||
animate={false} |
|||
line |
|||
borderWidth={2} |
|||
height={84} |
|||
scale={{ |
|||
y: { |
|||
tickCount: 3, |
|||
}, |
|||
}} |
|||
yAxis={{ |
|||
tickLine: false, |
|||
label: false, |
|||
title: false, |
|||
line: false, |
|||
}} |
|||
data={activeData} |
|||
/> |
|||
</div> |
|||
{activeData && ( |
|||
<div> |
|||
<div className={styles.activeChartGrid}> |
|||
<p>{[...activeData].sort()[activeData.length - 1].y + 200} 亿元</p> |
|||
<p>{[...activeData].sort()[Math.floor(activeData.length / 2)].y} 亿元</p> |
|||
</div> |
|||
<div className={styles.dashedLine}> |
|||
<div className={styles.line} /> |
|||
</div> |
|||
<div className={styles.dashedLine}> |
|||
<div className={styles.line} /> |
|||
</div> |
|||
</div> |
|||
)} |
|||
{activeData && ( |
|||
<div className={styles.activeChartLegend}> |
|||
<span>00:00</span> |
|||
<span>{activeData[Math.floor(activeData.length / 2)].x}</span> |
|||
<span>{activeData[activeData.length - 1].x}</span> |
|||
</div> |
|||
)} |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
@ -1,51 +0,0 @@ |
|||
.activeChart { |
|||
position: relative; |
|||
} |
|||
.activeChartGrid { |
|||
p { |
|||
position: absolute; |
|||
top: 80px; |
|||
} |
|||
p:last-child { |
|||
top: 115px; |
|||
} |
|||
} |
|||
.activeChartLegend { |
|||
position: relative; |
|||
height: 20px; |
|||
margin-top: 8px; |
|||
font-size: 0; |
|||
line-height: 20px; |
|||
span { |
|||
display: inline-block; |
|||
width: 33.33%; |
|||
font-size: 12px; |
|||
text-align: center; |
|||
} |
|||
span:first-child { |
|||
text-align: left; |
|||
} |
|||
span:last-child { |
|||
text-align: right; |
|||
} |
|||
} |
|||
.dashedLine { |
|||
position: relative; |
|||
top: -70px; |
|||
left: -3px; |
|||
height: 1px; |
|||
|
|||
.line { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
background-image: linear-gradient(to right, transparent 50%, #e9e9e9 50%); |
|||
background-size: 6px; |
|||
} |
|||
} |
|||
|
|||
.dashedLine:last-child { |
|||
top: -36px; |
|||
} |
|||
@ -1,13 +0,0 @@ |
|||
import React from 'react'; |
|||
|
|||
export interface ApplicationsProps { |
|||
data: { |
|||
content?: string; |
|||
updatedAt?: any; |
|||
avatar?: string; |
|||
owner?: string; |
|||
href?: string; |
|||
}; |
|||
} |
|||
|
|||
export default class ArticleListContent extends React.Component<ApplicationsProps, any> {} |
|||
@ -1,17 +0,0 @@ |
|||
import React from 'react'; |
|||
import moment from 'moment'; |
|||
import { Avatar } from 'antd'; |
|||
import styles from './index.less'; |
|||
|
|||
const ArticleListContent = ({ data: { content, updatedAt, avatar, owner, href } }) => ( |
|||
<div className={styles.listContent}> |
|||
<div className={styles.description}>{content}</div> |
|||
<div className={styles.extra}> |
|||
<Avatar src={avatar} size="small" /> |
|||
<a href={href}>{owner}</a> 发布在 <a href={href}>{href}</a> |
|||
<em>{moment(updatedAt).format('YYYY-MM-DD HH:mm')}</em> |
|||
</div> |
|||
</div> |
|||
); |
|||
|
|||
export default ArticleListContent; |
|||
@ -1,38 +0,0 @@ |
|||
@import '~antd/lib/style/themes/default.less'; |
|||
|
|||
.listContent { |
|||
.description { |
|||
max-width: 720px; |
|||
line-height: 22px; |
|||
} |
|||
.extra { |
|||
margin-top: 16px; |
|||
color: @text-color-secondary; |
|||
line-height: 22px; |
|||
& > :global(.ant-avatar) { |
|||
position: relative; |
|||
top: 1px; |
|||
width: 20px; |
|||
height: 20px; |
|||
margin-right: 8px; |
|||
vertical-align: top; |
|||
} |
|||
& > em { |
|||
margin-left: 16px; |
|||
color: @disabled-color; |
|||
font-style: normal; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@media screen and (max-width: @screen-xs) { |
|||
.listContent { |
|||
.extra { |
|||
& > em { |
|||
display: block; |
|||
margin-top: 8px; |
|||
margin-left: 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,8 +0,0 @@ |
|||
import CheckPermissions from './CheckPermissions'; |
|||
|
|||
const Authorized = ({ children, authority, noMatch = null }) => { |
|||
const childrenRender = typeof children === 'undefined' ? null : children; |
|||
return CheckPermissions(authority, childrenRender, noMatch); |
|||
}; |
|||
|
|||
export default Authorized; |
|||
@ -0,0 +1,29 @@ |
|||
import CheckPermissions from './CheckPermissions'; |
|||
import { IAuthorityType } from './CheckPermissions'; |
|||
import Secured from './Secured'; |
|||
import check from './CheckPermissions'; |
|||
import AuthorizedRoute from './AuthorizedRoute'; |
|||
import React from 'react'; |
|||
|
|||
interface IAuthorizedProps { |
|||
authority: IAuthorityType; |
|||
noMatch?: React.ReactNode; |
|||
} |
|||
|
|||
type IAuthorizedType = React.FunctionComponent<IAuthorizedProps> & { |
|||
Secured: typeof Secured; |
|||
check: typeof check; |
|||
AuthorizedRoute: typeof AuthorizedRoute; |
|||
}; |
|||
|
|||
const Authorized: React.FunctionComponent<IAuthorizedProps> = ({ |
|||
children, |
|||
authority, |
|||
noMatch = null, |
|||
}) => { |
|||
const childrenRender: React.ReactNode = typeof children === 'undefined' ? null : children; |
|||
const dom = CheckPermissions(authority, childrenRender, noMatch); |
|||
return <>{dom}</>; |
|||
}; |
|||
|
|||
export default Authorized as IAuthorizedType; |
|||
@ -1,13 +0,0 @@ |
|||
import React from 'react'; |
|||
import { RouteProps } from 'react-router'; |
|||
|
|||
type authorityFN = (currentAuthority?: string) => boolean; |
|||
|
|||
type authority = string | string[] | authorityFN | Promise<any>; |
|||
|
|||
export interface IAuthorizedRouteProps extends RouteProps { |
|||
authority: authority; |
|||
} |
|||
export { authority }; |
|||
|
|||
export default class AuthorizedRoute extends React.Component<IAuthorizedRouteProps, any> {} |
|||
@ -1,15 +0,0 @@ |
|||
import React from 'react'; |
|||
import { Route, Redirect } from 'umi'; |
|||
import Authorized from './Authorized'; |
|||
|
|||
// TODO: umi只会返回render和rest
|
|||
const AuthorizedRoute = ({ component: Component, render, authority, redirectPath, ...rest }) => ( |
|||
<Authorized |
|||
authority={authority} |
|||
noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />} |
|||
> |
|||
<Route {...rest} render={props => (Component ? <Component {...props} /> : render(props))} /> |
|||
</Authorized> |
|||
); |
|||
|
|||
export default AuthorizedRoute; |
|||
@ -0,0 +1,32 @@ |
|||
import React from 'react'; |
|||
import { Route, Redirect } from 'umi'; |
|||
import Authorized from './Authorized'; |
|||
import { IAuthorityType } from './CheckPermissions'; |
|||
|
|||
interface IAuthorizedRoutePops { |
|||
currentAuthority: string; |
|||
component: React.ComponentClass<any, any>; |
|||
render: (props: any) => React.ReactNode; |
|||
redirectPath: string; |
|||
authority: IAuthorityType; |
|||
} |
|||
|
|||
const AuthorizedRoute: React.SFC<IAuthorizedRoutePops> = ({ |
|||
component: Component, |
|||
render, |
|||
authority, |
|||
redirectPath, |
|||
...rest |
|||
}) => ( |
|||
<Authorized |
|||
authority={authority} |
|||
noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />} |
|||
> |
|||
<Route |
|||
{...rest} |
|||
render={(props: any) => (Component ? <Component {...props} /> : render(props))} |
|||
/> |
|||
</Authorized> |
|||
); |
|||
|
|||
export default AuthorizedRoute; |
|||
@ -1,55 +0,0 @@ |
|||
import { checkPermissions } from './CheckPermissions'; |
|||
|
|||
const target = 'ok'; |
|||
const error = 'error'; |
|||
|
|||
describe('test CheckPermissions', () => { |
|||
it('Correct string permission authentication', () => { |
|||
expect(checkPermissions('user', 'user', target, error)).toEqual('ok'); |
|||
}); |
|||
it('Correct string permission authentication', () => { |
|||
expect(checkPermissions('user', 'NULL', target, error)).toEqual('error'); |
|||
}); |
|||
it('authority is undefined , return ok', () => { |
|||
expect(checkPermissions(null, 'NULL', target, error)).toEqual('ok'); |
|||
}); |
|||
it('currentAuthority is undefined , return error', () => { |
|||
expect(checkPermissions('admin', null, target, error)).toEqual('error'); |
|||
}); |
|||
it('Wrong string permission authentication', () => { |
|||
expect(checkPermissions('admin', 'user', target, error)).toEqual('error'); |
|||
}); |
|||
it('Correct Array permission authentication', () => { |
|||
expect(checkPermissions(['user', 'admin'], 'user', target, error)).toEqual('ok'); |
|||
}); |
|||
it('Wrong Array permission authentication,currentAuthority error', () => { |
|||
expect(checkPermissions(['user', 'admin'], 'user,admin', target, error)).toEqual('error'); |
|||
}); |
|||
it('Wrong Array permission authentication', () => { |
|||
expect(checkPermissions(['user', 'admin'], 'guest', target, error)).toEqual('error'); |
|||
}); |
|||
it('Wrong Function permission authentication', () => { |
|||
expect(checkPermissions(() => false, 'guest', target, error)).toEqual('error'); |
|||
}); |
|||
it('Correct Function permission authentication', () => { |
|||
expect(checkPermissions(() => true, 'guest', target, error)).toEqual('ok'); |
|||
}); |
|||
it('authority is string, currentAuthority is array, return ok', () => { |
|||
expect(checkPermissions('user', ['user'], target, error)).toEqual('ok'); |
|||
}); |
|||
it('authority is string, currentAuthority is array, return ok', () => { |
|||
expect(checkPermissions('user', ['user', 'admin'], target, error)).toEqual('ok'); |
|||
}); |
|||
it('authority is array, currentAuthority is array, return ok', () => { |
|||
expect(checkPermissions(['user', 'admin'], ['user', 'admin'], target, error)).toEqual('ok'); |
|||
}); |
|||
it('Wrong Function permission authentication', () => { |
|||
expect(checkPermissions(() => false, ['user'], target, error)).toEqual('error'); |
|||
}); |
|||
it('Correct Function permission authentication', () => { |
|||
expect(checkPermissions(() => true, ['user'], target, error)).toEqual('ok'); |
|||
}); |
|||
it('authority is undefined , return ok', () => { |
|||
expect(checkPermissions(null, ['user'], target, error)).toEqual('ok'); |
|||
}); |
|||
}); |
|||
@ -1,23 +0,0 @@ |
|||
--- |
|||
order: 1 |
|||
title: |
|||
zh-CN: 使用数组作为参数 |
|||
en-US: Use Array as a parameter |
|||
--- |
|||
|
|||
Use Array as a parameter |
|||
|
|||
```jsx |
|||
import RenderAuthorized from 'ant-design-pro/lib/Authorized'; |
|||
import { Alert } from 'antd'; |
|||
|
|||
const Authorized = RenderAuthorized('user'); |
|||
const noMatch = <Alert message="No permission." type="error" showIcon />; |
|||
|
|||
ReactDOM.render( |
|||
<Authorized authority={['user', 'admin']} noMatch={noMatch}> |
|||
<Alert message="Use Array as a parameter passed!" type="success" showIcon /> |
|||
</Authorized>, |
|||
mountNode |
|||
); |
|||
``` |
|||
@ -1,27 +0,0 @@ |
|||
--- |
|||
order: 2 |
|||
title: |
|||
zh-CN: 使用方法作为参数 |
|||
en-US: Use function as a parameter |
|||
--- |
|||
|
|||
Use Function as a parameter |
|||
|
|||
```jsx |
|||
import RenderAuthorized from 'ant-design-pro/lib/Authorized'; |
|||
import { Alert } from 'antd'; |
|||
|
|||
const Authorized = RenderAuthorized('user'); |
|||
const noMatch = <Alert message="No permission." type="error" showIcon />; |
|||
|
|||
const havePermission = () => { |
|||
return false; |
|||
}; |
|||
|
|||
ReactDOM.render( |
|||
<Authorized authority={havePermission} noMatch={noMatch}> |
|||
<Alert message="Use Function as a parameter passed!" type="success" showIcon /> |
|||
</Authorized>, |
|||
mountNode |
|||
); |
|||
``` |
|||
@ -1,25 +0,0 @@ |
|||
--- |
|||
order: 0 |
|||
title: |
|||
zh-CN: 基本使用 |
|||
en-US: Basic use |
|||
--- |
|||
|
|||
Basic use |
|||
|
|||
```jsx |
|||
import RenderAuthorized from 'ant-design-pro/lib/Authorized'; |
|||
import { Alert } from 'antd'; |
|||
|
|||
const Authorized = RenderAuthorized('user'); |
|||
const noMatch = <Alert message="No permission." type="error" showIcon />; |
|||
|
|||
ReactDOM.render( |
|||
<div> |
|||
<Authorized authority="admin" noMatch={noMatch}> |
|||
<Alert message="user Passed!" type="success" showIcon /> |
|||
</Authorized> |
|||
</div>, |
|||
mountNode |
|||
); |
|||
``` |
|||
@ -1,28 +0,0 @@ |
|||
--- |
|||
order: 3 |
|||
title: |
|||
zh-CN: 注解基本使用 |
|||
en-US: Basic use secured |
|||
--- |
|||
|
|||
secured demo used |
|||
|
|||
```jsx |
|||
import RenderAuthorized from 'ant-design-pro/lib/Authorized'; |
|||
import { Alert } from 'antd'; |
|||
|
|||
const { Secured } = RenderAuthorized('user'); |
|||
|
|||
@Secured('admin') |
|||
class TestSecuredString extends React.Component { |
|||
render() { |
|||
return <Alert message="user Passed!" type="success" showIcon />; |
|||
} |
|||
} |
|||
ReactDOM.render( |
|||
<div> |
|||
<TestSecuredString /> |
|||
</div>, |
|||
mountNode |
|||
); |
|||
``` |
|||
@ -1,32 +0,0 @@ |
|||
import React from 'react'; |
|||
import AuthorizedRoute, { authority } from './AuthorizedRoute'; |
|||
export type IReactComponent<P = any> = |
|||
| React.StatelessComponent<P> |
|||
| React.ComponentClass<P> |
|||
| React.ClassicComponentClass<P>; |
|||
|
|||
type Secured = ( |
|||
authority: authority, |
|||
error?: React.ReactNode |
|||
) => <T extends IReactComponent>(target: T) => T; |
|||
|
|||
type check = <T extends IReactComponent, S extends IReactComponent>( |
|||
authority: authority, |
|||
target: T, |
|||
Exception: S |
|||
) => T | S; |
|||
|
|||
export interface IAuthorizedProps { |
|||
authority: authority; |
|||
noMatch?: React.ReactNode; |
|||
} |
|||
|
|||
export class Authorized extends React.Component<IAuthorizedProps, any> { |
|||
public static Secured: Secured; |
|||
public static AuthorizedRoute: typeof AuthorizedRoute; |
|||
public static check: check; |
|||
} |
|||
|
|||
declare function renderAuthorize(currentAuthority: string): typeof Authorized; |
|||
|
|||
export default renderAuthorize; |
|||
@ -1,54 +0,0 @@ |
|||
--- |
|||
title: Authorized |
|||
subtitle: 权限 |
|||
cols: 1 |
|||
order: 15 |
|||
--- |
|||
|
|||
权限组件,通过比对现有权限与准入权限,决定相关元素的展示。 |
|||
|
|||
## API |
|||
|
|||
### RenderAuthorized |
|||
|
|||
`RenderAuthorized: (currentAuthority: string | () => string) => Authorized` |
|||
|
|||
权限组件默认 export RenderAuthorized 函数,它接收当前权限作为参数,返回一个权限对象,该对象提供以下几种使用方式。 |
|||
|
|||
### Authorized |
|||
|
|||
最基础的权限控制。 |
|||
|
|||
| 参数 | 说明 | 类型 | 默认值 | |
|||
| --- | --- | --- | --- | |
|||
| children | 正常渲染的元素,权限判断通过时展示 | ReactNode | - | |
|||
| authority | 准入权限/权限判断 | `string | array | Promise | (currentAuthority) => boolean | Promise` | - | |
|||
| noMatch | 权限异常渲染元素,权限判断不通过时展示 | ReactNode | - | |
|||
|
|||
### Authorized.AuthorizedRoute |
|||
|
|||
| 参数 | 说明 | 类型 | 默认值 | |
|||
| --- | --- | --- | --- | |
|||
| authority | 准入权限/权限判断 | `string | array | Promise | (currentAuthority) => boolean | Promise` | - | |
|||
| redirectPath | 权限异常时重定向的页面路由 | string | - | |
|||
|
|||
其余参数与 `Route` 相同。 |
|||
|
|||
### Authorized.Secured |
|||
|
|||
注解方式,`@Authorized.Secured(authority, error)` |
|||
|
|||
| 参数 | 说明 | 类型 | 默认值 | |
|||
| --- | --- | --- | --- | |
|||
| authority | 准入权限/权限判断 | `string | Promise | (currentAuthority) => boolean | Promise` | - | |
|||
| error | 权限异常时渲染元素 | ReactNode | <Exception type="403" /> | |
|||
|
|||
### Authorized.check |
|||
|
|||
函数形式的 Authorized,用于某些不能被 HOC 包裹的组件。 `Authorized.check(authority, target, Exception)` 注意:传入一个 Promise 时,无论正确还是错误返回的都是一个 ReactClass。 |
|||
|
|||
| 参数 | 说明 | 类型 | 默认值 | |
|||
| --- | --- | --- | --- | |
|||
| authority | 准入权限/权限判断 | `string | Promise | (currentAuthority) => boolean | Promise` | - | |
|||
| target | 权限判断通过时渲染的元素 | ReactNode | - | |
|||
| Exception | 权限异常时渲染元素 | ReactNode | - | |
|||
@ -1,15 +0,0 @@ |
|||
import React from 'react'; |
|||
|
|||
export declare type SizeType = number | 'small' | 'default' | 'large'; |
|||
|
|||
export interface AvatarItemProps { |
|||
tips: React.ReactNode; |
|||
src: string; |
|||
size?: SizeType; |
|||
style?: React.CSSProperties; |
|||
onClick?: () => void; |
|||
} |
|||
|
|||
export default class AvatarItem extends React.Component<AvatarItemProps, any> { |
|||
constructor(props: AvatarItemProps); |
|||
} |
|||
@ -1,46 +0,0 @@ |
|||
--- |
|||
order: 0 |
|||
title: |
|||
zh-CN: 要显示的最大项目 |
|||
en-US: Max Items to Show |
|||
--- |
|||
|
|||
`maxLength` attribute specifies the maximum number of items to show while `excessItemsStyle` style the excess item component. |
|||
|
|||
```jsx |
|||
import AvatarList from 'ant-design-pro/lib/AvatarList'; |
|||
|
|||
ReactDOM.render( |
|||
<AvatarList |
|||
size="mini" |
|||
maxLength={3} |
|||
excessItemsStyle={{ color: '#f56a00', backgroundColor: '#fde3cf' }} |
|||
> |
|||
<AvatarList.Item |
|||
tips="Jake" |
|||
src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" |
|||
/> |
|||
<AvatarList.Item |
|||
tips="Andy" |
|||
src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" |
|||
/> |
|||
<AvatarList.Item |
|||
tips="Niko" |
|||
src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" |
|||
/> |
|||
<AvatarList.Item |
|||
tips="Niko" |
|||
src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" |
|||
/> |
|||
<AvatarList.Item |
|||
tips="Niko" |
|||
src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" |
|||
/> |
|||
<AvatarList.Item |
|||
tips="Niko" |
|||
src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" |
|||
/> |
|||
</AvatarList>, |
|||
mountNode |
|||
); |
|||
``` |
|||
@ -1,30 +0,0 @@ |
|||
--- |
|||
order: 0 |
|||
title: |
|||
zh-CN: 基础样例 |
|||
en-US: Basic Usage |
|||
--- |
|||
|
|||
Simplest of usage. |
|||
|
|||
```jsx |
|||
import AvatarList from 'ant-design-pro/lib/AvatarList'; |
|||
|
|||
ReactDOM.render( |
|||
<AvatarList size="mini"> |
|||
<AvatarList.Item |
|||
tips="Jake" |
|||
src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" |
|||
/> |
|||
<AvatarList.Item |
|||
tips="Andy" |
|||
src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" |
|||
/> |
|||
<AvatarList.Item |
|||
tips="Niko" |
|||
src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" |
|||
/> |
|||
</AvatarList>, |
|||
mountNode |
|||
); |
|||
``` |
|||
@ -1,14 +0,0 @@ |
|||
import React from 'react'; |
|||
import AvatarItem, { AvatarItemProps, SizeType } from './AvatarItem'; |
|||
|
|||
export interface AvatarListProps { |
|||
Item?: React.ReactElement<AvatarItemProps>; |
|||
size?: SizeType; |
|||
maxLength?: number; |
|||
excessItemsStyle?: React.CSSProperties; |
|||
style?: React.CSSProperties; |
|||
children: React.ReactElement<AvatarItemProps> | Array<React.ReactElement<AvatarItemProps>>; |
|||
} |
|||
export default class AvatarList extends React.Component<AvatarListProps, any> { |
|||
public static Item: typeof AvatarItem; |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
--- |
|||
title: AvatarList |
|||
order: 1 |
|||
cols: 1 |
|||
--- |
|||
|
|||
A list of user's avatar for project or group member list frequently. If a large or small AvatarList is desired, set the `size` property to either `large` or `small` and `mini` respectively. Omit the `size` property for a AvatarList with the default size. |
|||
|
|||
## API |
|||
|
|||
### AvatarList |
|||
|
|||
| Property | Description | Type | Default | |
|||
| ---------------- | --------------------- | ------------------------------------ | --------- | |
|||
| size | size of list | `large`、`small` 、`mini`, `default` | `default` | |
|||
| maxLength | max items to show | number | - | |
|||
| excessItemsStyle | the excess item style | CSSProperties | - | |
|||
|
|||
### AvatarList.Item |
|||
|
|||
| Property | Description | Type | Default | |
|||
| -------- | -------------------------------------------- | --------- | ------- | |
|||
| tips | title tips for avatar item | ReactNode | - | |
|||
| src | the address of the image for an image avatar | string | - | |
|||
@ -1,61 +0,0 @@ |
|||
import React from 'react'; |
|||
import { Tooltip, Avatar } from 'antd'; |
|||
import classNames from 'classnames'; |
|||
|
|||
import styles from './index.less'; |
|||
|
|||
const avatarSizeToClassName = size => |
|||
classNames(styles.avatarItem, { |
|||
[styles.avatarItemLarge]: size === 'large', |
|||
[styles.avatarItemSmall]: size === 'small', |
|||
[styles.avatarItemMini]: size === 'mini', |
|||
}); |
|||
|
|||
const AvatarList = ({ children, size, maxLength, excessItemsStyle, ...other }) => { |
|||
const numOfChildren = React.Children.count(children); |
|||
const numToShow = maxLength >= numOfChildren ? numOfChildren : maxLength; |
|||
|
|||
const childrenWithProps = React.Children.toArray(children) |
|||
.slice(0, numToShow) |
|||
.map(child => |
|||
React.cloneElement(child, { |
|||
size, |
|||
}) |
|||
); |
|||
|
|||
if (numToShow < numOfChildren) { |
|||
const cls = avatarSizeToClassName(size); |
|||
|
|||
childrenWithProps.push( |
|||
<li key="exceed" className={cls}> |
|||
<Avatar size={size} style={excessItemsStyle}>{`+${numOfChildren - maxLength}`}</Avatar> |
|||
</li> |
|||
); |
|||
} |
|||
|
|||
return ( |
|||
<div {...other} className={styles.avatarList}> |
|||
<ul> {childrenWithProps} </ul> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
const Item = ({ src, size, tips, onClick = () => {} }) => { |
|||
const cls = avatarSizeToClassName(size); |
|||
|
|||
return ( |
|||
<li className={cls} onClick={onClick}> |
|||
{tips ? ( |
|||
<Tooltip title={tips}> |
|||
<Avatar src={src} size={size} style={{ cursor: 'pointer' }} /> |
|||
</Tooltip> |
|||
) : ( |
|||
<Avatar src={src} size={size} /> |
|||
)} |
|||
</li> |
|||
); |
|||
}; |
|||
|
|||
AvatarList.Item = Item; |
|||
|
|||
export default AvatarList; |
|||
@ -1,50 +0,0 @@ |
|||
@import '~antd/lib/style/themes/default.less'; |
|||
|
|||
.avatarList { |
|||
display: inline-block; |
|||
ul { |
|||
display: inline-block; |
|||
margin-left: 8px; |
|||
font-size: 0; |
|||
} |
|||
} |
|||
|
|||
.avatarItem { |
|||
display: inline-block; |
|||
width: @avatar-size-base; |
|||
height: @avatar-size-base; |
|||
margin-left: -8px; |
|||
font-size: @font-size-base; |
|||
:global { |
|||
.ant-avatar { |
|||
border: 1px solid #fff; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.avatarItemLarge { |
|||
width: @avatar-size-lg; |
|||
height: @avatar-size-lg; |
|||
} |
|||
|
|||
.avatarItemSmall { |
|||
width: @avatar-size-sm; |
|||
height: @avatar-size-sm; |
|||
} |
|||
|
|||
.avatarItemMini { |
|||
width: 20px; |
|||
height: 20px; |
|||
:global { |
|||
.ant-avatar { |
|||
width: 20px; |
|||
height: 20px; |
|||
line-height: 20px; |
|||
|
|||
.ant-avatar-string { |
|||
font-size: 12px; |
|||
line-height: 18px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,29 +0,0 @@ |
|||
import React from 'react'; |
|||
import range from 'lodash/range'; |
|||
import { mount } from 'enzyme'; |
|||
import AvatarList from './index'; |
|||
|
|||
const renderItems = numItems => |
|||
range(numItems).map(i => ( |
|||
<AvatarList.Item |
|||
key={i} |
|||
tips="Jake" |
|||
src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" |
|||
/> |
|||
)); |
|||
|
|||
describe('AvatarList', () => { |
|||
it('renders all items', () => { |
|||
const wrapper = mount(<AvatarList>{renderItems(4)}</AvatarList>); |
|||
expect(wrapper.find('AvatarList').length).toBe(1); |
|||
expect(wrapper.find('Item').length).toBe(4); |
|||
expect(wrapper.findWhere(node => node.key() === 'exceed').length).toBe(0); |
|||
}); |
|||
|
|||
it('renders max of 3 items', () => { |
|||
const wrapper = mount(<AvatarList maxLength={3}>{renderItems(4)}</AvatarList>); |
|||
expect(wrapper.find('AvatarList').length).toBe(1); |
|||
expect(wrapper.find('Item').length).toBe(3); |
|||
expect(wrapper.findWhere(node => node.key() === 'exceed').length).toBe(1); |
|||
}); |
|||
}); |
|||
@ -1,25 +0,0 @@ |
|||
--- |
|||
title: AvatarList |
|||
subtitle: 用户头像列表 |
|||
order: 1 |
|||
cols: 1 |
|||
--- |
|||
|
|||
一组用户头像,常用在项目/团队成员列表。可通过设置 `size` 属性来指定头像大小。 |
|||
|
|||
## API |
|||
|
|||
### AvatarList |
|||
|
|||
| 参数 | 说明 | 类型 | 默认值 | |
|||
| ---------------- | ---------------- | ------------------------------------ | --------- | |
|||
| size | 头像大小 | `large`、`small` 、`mini`, `default` | `default` | |
|||
| maxLength | 要显示的最大项目 | number | - | |
|||
| excessItemsStyle | 多余的项目风格 | CSSProperties | - | |
|||
|
|||
### AvatarList.Item |
|||
|
|||
| 参数 | 说明 | 类型 | 默认值 | |
|||
| ---- | ------------ | --------- | ------ | |
|||
| tips | 头像展示文案 | ReactNode | - | |
|||
| src | 头像图片连接 | string | - | |
|||
@ -1,15 +0,0 @@ |
|||
import React from 'react'; |
|||
export interface IBarProps { |
|||
title: React.ReactNode; |
|||
color?: string; |
|||
padding?: [number, number, number, number]; |
|||
height: number; |
|||
data: Array<{ |
|||
x: string; |
|||
y: number; |
|||
}>; |
|||
autoLabel?: boolean; |
|||
style?: React.CSSProperties; |
|||
} |
|||
|
|||
export default class Bar extends React.Component<IBarProps, any> {} |
|||
@ -1,124 +0,0 @@ |
|||
import React, { Component } from 'react'; |
|||
import { Chart, Axis, Tooltip, Geom } from 'bizcharts'; |
|||
import Debounce from 'lodash-decorators/debounce'; |
|||
import Bind from 'lodash-decorators/bind'; |
|||
import ResizeObserver from 'resize-observer-polyfill'; |
|||
import styles from '../index.less'; |
|||
|
|||
class Bar extends Component { |
|||
state = { |
|||
height: 0, |
|||
autoHideXLabels: false, |
|||
}; |
|||
|
|||
handleRoot = n => { |
|||
this.root = n; |
|||
}; |
|||
|
|||
handleRef = n => { |
|||
this.node = n; |
|||
}; |
|||
|
|||
resizeObserver() { |
|||
const ro = new ResizeObserver(entries => { |
|||
const { width, height } = entries[0].contentRect; |
|||
this.resize(); |
|||
this.setState(preState => { |
|||
if (preState.width !== width || preState.height !== height) { |
|||
return { |
|||
height, |
|||
}; |
|||
} |
|||
return null; |
|||
}); |
|||
}); |
|||
if (this.root) { |
|||
ro.observe(this.root); |
|||
} |
|||
} |
|||
|
|||
@Bind() |
|||
@Debounce(400) |
|||
resize() { |
|||
if (!this.node) { |
|||
return; |
|||
} |
|||
const canvasWidth = this.node.parentNode.clientWidth; |
|||
const { data = [], autoLabel = true } = this.props; |
|||
if (!autoLabel) { |
|||
return; |
|||
} |
|||
const minWidth = data.length * 30; |
|||
const { autoHideXLabels } = this.state; |
|||
|
|||
if (canvasWidth <= minWidth) { |
|||
if (!autoHideXLabels) { |
|||
this.setState({ |
|||
autoHideXLabels: true, |
|||
}); |
|||
} |
|||
} else if (autoHideXLabels) { |
|||
this.setState({ |
|||
autoHideXLabels: false, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
render() { |
|||
const { |
|||
height: propsHeight, |
|||
title, |
|||
forceFit = true, |
|||
data, |
|||
color = 'rgba(24, 144, 255, 0.85)', |
|||
padding, |
|||
} = this.props; |
|||
|
|||
const { autoHideXLabels } = this.state; |
|||
|
|||
const scale = { |
|||
x: { |
|||
type: 'cat', |
|||
}, |
|||
y: { |
|||
min: 0, |
|||
}, |
|||
}; |
|||
|
|||
const tooltip = [ |
|||
'x*y', |
|||
(x, y) => ({ |
|||
name: x, |
|||
value: y, |
|||
}), |
|||
]; |
|||
const { height: stateHeight } = this.state; |
|||
const height = propsHeight || stateHeight; |
|||
return ( |
|||
<div className={styles.chart} style={{ height }} ref={this.handleRoot}> |
|||
<div ref={this.handleRef}> |
|||
{title && <h4 style={{ marginBottom: 20 }}>{title}</h4>} |
|||
<Chart |
|||
scale={scale} |
|||
height={title ? height - 41 : height} |
|||
forceFit={forceFit} |
|||
data={data} |
|||
padding={padding || 'auto'} |
|||
> |
|||
<Axis |
|||
name="x" |
|||
title={false} |
|||
label={autoHideXLabels ? false : {}} |
|||
tickLine={autoHideXLabels ? false : {}} |
|||
/> |
|||
<Axis name="y" min={0} /> |
|||
<Tooltip showTitle={false} crosshairs={false} /> |
|||
<Geom type="interval" position="x*y" color={color} tooltip={tooltip} /> |
|||
</Chart> |
|||
</div> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default Bar; |
|||
@ -1,14 +0,0 @@ |
|||
import { CardProps } from 'antd/lib/card'; |
|||
import React from 'react'; |
|||
|
|||
export interface IChartCardProps extends CardProps { |
|||
title: React.ReactNode; |
|||
action?: React.ReactNode; |
|||
total?: React.ReactNode | number | (() => React.ReactNode | number); |
|||
footer?: React.ReactNode; |
|||
contentHeight?: number; |
|||
avatar?: React.ReactNode; |
|||
style?: React.CSSProperties; |
|||
} |
|||
|
|||
export default class ChartCard extends React.Component<IChartCardProps, any> {} |
|||
@ -1,82 +0,0 @@ |
|||
import React from 'react'; |
|||
import { Card } from 'antd'; |
|||
import classNames from 'classnames'; |
|||
|
|||
import styles from './index.less'; |
|||
|
|||
const renderTotal = total => { |
|||
let totalDom; |
|||
switch (typeof total) { |
|||
case 'undefined': |
|||
totalDom = null; |
|||
break; |
|||
case 'function': |
|||
totalDom = <div className={styles.total}>{total()}</div>; |
|||
break; |
|||
default: |
|||
totalDom = <div className={styles.total}>{total}</div>; |
|||
} |
|||
return totalDom; |
|||
}; |
|||
|
|||
class ChartCard extends React.PureComponent { |
|||
renderConnet = () => { |
|||
const { contentHeight, title, avatar, action, total, footer, children, loading } = this.props; |
|||
if (loading) { |
|||
return false; |
|||
} |
|||
return ( |
|||
<div className={styles.chartCard}> |
|||
<div |
|||
className={classNames(styles.chartTop, { |
|||
[styles.chartTopMargin]: !children && !footer, |
|||
})} |
|||
> |
|||
<div className={styles.avatar}>{avatar}</div> |
|||
<div className={styles.metaWrap}> |
|||
<div className={styles.meta}> |
|||
<span className={styles.title}>{title}</span> |
|||
<span className={styles.action}>{action}</span> |
|||
</div> |
|||
{renderTotal(total)} |
|||
</div> |
|||
</div> |
|||
{children && ( |
|||
<div className={styles.content} style={{ height: contentHeight || 'auto' }}> |
|||
<div className={contentHeight && styles.contentFixed}>{children}</div> |
|||
</div> |
|||
)} |
|||
{footer && ( |
|||
<div |
|||
className={classNames(styles.footer, { |
|||
[styles.footerMargin]: !children, |
|||
})} |
|||
> |
|||
{footer} |
|||
</div> |
|||
)} |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
render() { |
|||
const { |
|||
loading = false, |
|||
contentHeight, |
|||
title, |
|||
avatar, |
|||
action, |
|||
total, |
|||
footer, |
|||
children, |
|||
...rest |
|||
} = this.props; |
|||
return ( |
|||
<Card loading={loading} bodyStyle={{ padding: '20px 24px 8px 24px' }} {...rest}> |
|||
{this.renderConnet()} |
|||
</Card> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ChartCard; |
|||
@ -1,75 +0,0 @@ |
|||
@import '~antd/lib/style/themes/default.less'; |
|||
|
|||
.chartCard { |
|||
position: relative; |
|||
.chartTop { |
|||
position: relative; |
|||
width: 100%; |
|||
overflow: hidden; |
|||
} |
|||
.chartTopMargin { |
|||
margin-bottom: 12px; |
|||
} |
|||
.chartTopHasMargin { |
|||
margin-bottom: 20px; |
|||
} |
|||
.metaWrap { |
|||
float: left; |
|||
} |
|||
.avatar { |
|||
position: relative; |
|||
top: 4px; |
|||
float: left; |
|||
margin-right: 20px; |
|||
img { |
|||
border-radius: 100%; |
|||
} |
|||
} |
|||
.meta { |
|||
height: 22px; |
|||
color: @text-color-secondary; |
|||
font-size: @font-size-base; |
|||
line-height: 22px; |
|||
} |
|||
.action { |
|||
position: absolute; |
|||
top: 4px; |
|||
right: 0; |
|||
line-height: 1; |
|||
cursor: pointer; |
|||
} |
|||
.total { |
|||
height: 38px; |
|||
margin-top: 4px; |
|||
margin-bottom: 0; |
|||
overflow: hidden; |
|||
color: @heading-color; |
|||
font-size: 30px; |
|||
line-height: 38px; |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
word-break: break-all; |
|||
} |
|||
.content { |
|||
position: relative; |
|||
width: 100%; |
|||
margin-bottom: 12px; |
|||
} |
|||
.contentFixed { |
|||
position: absolute; |
|||
bottom: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
} |
|||
.footer { |
|||
margin-top: 8px; |
|||
padding-top: 9px; |
|||
border-top: 1px solid @border-color-split; |
|||
& > * { |
|||
position: relative; |
|||
} |
|||
} |
|||
.footerMargin { |
|||
margin-top: 20px; |
|||
} |
|||
} |
|||
@ -1,8 +0,0 @@ |
|||
import React from 'react'; |
|||
export interface IFieldProps { |
|||
label: React.ReactNode; |
|||
value: React.ReactNode; |
|||
style?: React.CSSProperties; |
|||
} |
|||
|
|||
export default class Field extends React.Component<IFieldProps, any> {} |
|||
@ -1,12 +0,0 @@ |
|||
import React from 'react'; |
|||
|
|||
import styles from './index.less'; |
|||
|
|||
const Field = ({ label, value, ...rest }) => ( |
|||
<div className={styles.field} {...rest}> |
|||
<span className={styles.label}>{label}</span> |
|||
<span className={styles.number}>{value}</span> |
|||
</div> |
|||
); |
|||
|
|||
export default Field; |
|||
@ -1,17 +0,0 @@ |
|||
@import '~antd/lib/style/themes/default.less'; |
|||
|
|||
.field { |
|||
margin: 0; |
|||
overflow: hidden; |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
.label, |
|||
.number { |
|||
font-size: @font-size-base; |
|||
line-height: 22px; |
|||
} |
|||
.number { |
|||
margin-left: 8px; |
|||
color: @heading-color; |
|||
} |
|||
} |
|||
@ -1,11 +0,0 @@ |
|||
import React from 'react'; |
|||
export interface IGaugeProps { |
|||
title: React.ReactNode; |
|||
color?: string; |
|||
height: number; |
|||
bgColor?: number; |
|||
percent: number; |
|||
style?: React.CSSProperties; |
|||
} |
|||
|
|||
export default class Gauge extends React.Component<IGaugeProps, any> {} |
|||
@ -1,167 +0,0 @@ |
|||
import React from 'react'; |
|||
import { Chart, Geom, Axis, Coord, Guide, Shape } from 'bizcharts'; |
|||
import autoHeight from '../autoHeight'; |
|||
|
|||
const { Arc, Html, Line } = Guide; |
|||
|
|||
const defaultFormatter = val => { |
|||
switch (val) { |
|||
case '2': |
|||
return '差'; |
|||
case '4': |
|||
return '中'; |
|||
case '6': |
|||
return '良'; |
|||
case '8': |
|||
return '优'; |
|||
default: |
|||
return ''; |
|||
} |
|||
}; |
|||
|
|||
Shape.registerShape('point', 'pointer', { |
|||
drawShape(cfg, group) { |
|||
let point = cfg.points[0]; |
|||
point = this.parsePoint(point); |
|||
const center = this.parsePoint({ |
|||
x: 0, |
|||
y: 0, |
|||
}); |
|||
group.addShape('line', { |
|||
attrs: { |
|||
x1: center.x, |
|||
y1: center.y, |
|||
x2: point.x, |
|||
y2: point.y, |
|||
stroke: cfg.color, |
|||
lineWidth: 2, |
|||
lineCap: 'round', |
|||
}, |
|||
}); |
|||
return group.addShape('circle', { |
|||
attrs: { |
|||
x: center.x, |
|||
y: center.y, |
|||
r: 6, |
|||
stroke: cfg.color, |
|||
lineWidth: 3, |
|||
fill: '#fff', |
|||
}, |
|||
}); |
|||
}, |
|||
}); |
|||
|
|||
@autoHeight() |
|||
class Gauge extends React.Component { |
|||
render() { |
|||
const { |
|||
title, |
|||
height, |
|||
percent, |
|||
forceFit = true, |
|||
formatter = defaultFormatter, |
|||
color = '#2F9CFF', |
|||
bgColor = '#F0F2F5', |
|||
} = this.props; |
|||
const cols = { |
|||
value: { |
|||
type: 'linear', |
|||
min: 0, |
|||
max: 10, |
|||
tickCount: 6, |
|||
nice: true, |
|||
}, |
|||
}; |
|||
const data = [{ value: percent / 10 }]; |
|||
return ( |
|||
<Chart height={height} data={data} scale={cols} padding={[-16, 0, 16, 0]} forceFit={forceFit}> |
|||
<Coord type="polar" startAngle={-1.25 * Math.PI} endAngle={0.25 * Math.PI} radius={0.8} /> |
|||
<Axis name="1" line={null} /> |
|||
<Axis |
|||
line={null} |
|||
tickLine={null} |
|||
subTickLine={null} |
|||
name="value" |
|||
zIndex={2} |
|||
gird={null} |
|||
label={{ |
|||
offset: -12, |
|||
formatter, |
|||
textStyle: { |
|||
fontSize: 12, |
|||
fill: 'rgba(0, 0, 0, 0.65)', |
|||
textAlign: 'center', |
|||
}, |
|||
}} |
|||
/> |
|||
<Guide> |
|||
<Line |
|||
start={[3, 0.905]} |
|||
end={[3, 0.85]} |
|||
lineStyle={{ |
|||
stroke: color, |
|||
lineDash: null, |
|||
lineWidth: 2, |
|||
}} |
|||
/> |
|||
<Line |
|||
start={[5, 0.905]} |
|||
end={[5, 0.85]} |
|||
lineStyle={{ |
|||
stroke: color, |
|||
lineDash: null, |
|||
lineWidth: 3, |
|||
}} |
|||
/> |
|||
<Line |
|||
start={[7, 0.905]} |
|||
end={[7, 0.85]} |
|||
lineStyle={{ |
|||
stroke: color, |
|||
lineDash: null, |
|||
lineWidth: 3, |
|||
}} |
|||
/> |
|||
<Arc |
|||
zIndex={0} |
|||
start={[0, 0.965]} |
|||
end={[10, 0.965]} |
|||
style={{ |
|||
stroke: bgColor, |
|||
lineWidth: 10, |
|||
}} |
|||
/> |
|||
<Arc |
|||
zIndex={1} |
|||
start={[0, 0.965]} |
|||
end={[data[0].value, 0.965]} |
|||
style={{ |
|||
stroke: color, |
|||
lineWidth: 10, |
|||
}} |
|||
/> |
|||
<Html |
|||
position={['50%', '95%']} |
|||
html={() => ` |
|||
<div style="width: 300px;text-align: center;font-size: 12px!important;"> |
|||
<p style="font-size: 14px; color: rgba(0,0,0,0.43);margin: 0;">${title}</p> |
|||
<p style="font-size: 24px;color: rgba(0,0,0,0.85);margin: 0;"> |
|||
${(data[0].value * 10).toFixed(2)}% |
|||
</p> |
|||
</div>`} |
|||
/> |
|||
</Guide> |
|||
<Geom |
|||
line={false} |
|||
type="point" |
|||
position="value*1" |
|||
shape="pointer" |
|||
color={color} |
|||
active={false} |
|||
/> |
|||
</Chart> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default Gauge; |
|||
@ -1,29 +0,0 @@ |
|||
import React from 'react'; |
|||
|
|||
// g2已经更新到3.0
|
|||
// 不带的写了
|
|||
|
|||
export interface IAxis { |
|||
title: any; |
|||
line: any; |
|||
gridAlign: any; |
|||
labels: any; |
|||
tickLine: any; |
|||
grid: any; |
|||
} |
|||
|
|||
export interface IMiniAreaProps { |
|||
color?: string; |
|||
height: number; |
|||
borderColor?: string; |
|||
line?: boolean; |
|||
animate?: boolean; |
|||
xAxis?: IAxis; |
|||
yAxis?: IAxis; |
|||
data: Array<{ |
|||
x: number | string; |
|||
y: number; |
|||
}>; |
|||
} |
|||
|
|||
export default class MiniArea extends React.Component<IMiniAreaProps, any> {} |
|||
@ -1,108 +0,0 @@ |
|||
import React from 'react'; |
|||
import { Chart, Axis, Tooltip, Geom } from 'bizcharts'; |
|||
import autoHeight from '../autoHeight'; |
|||
import styles from '../index.less'; |
|||
|
|||
@autoHeight() |
|||
class MiniArea extends React.PureComponent { |
|||
render() { |
|||
const { |
|||
height, |
|||
data = [], |
|||
forceFit = true, |
|||
color = 'rgba(24, 144, 255, 0.2)', |
|||
borderColor = '#1089ff', |
|||
scale = {}, |
|||
borderWidth = 2, |
|||
line, |
|||
xAxis, |
|||
yAxis, |
|||
animate = true, |
|||
} = this.props; |
|||
|
|||
const padding = [36, 5, 30, 5]; |
|||
|
|||
const scaleProps = { |
|||
x: { |
|||
type: 'cat', |
|||
range: [0, 1], |
|||
...scale.x, |
|||
}, |
|||
y: { |
|||
min: 0, |
|||
...scale.y, |
|||
}, |
|||
}; |
|||
|
|||
const tooltip = [ |
|||
'x*y', |
|||
(x, y) => ({ |
|||
name: x, |
|||
value: y, |
|||
}), |
|||
]; |
|||
|
|||
const chartHeight = height + 54; |
|||
|
|||
return ( |
|||
<div className={styles.miniChart} style={{ height }}> |
|||
<div className={styles.chartContent}> |
|||
{height > 0 && ( |
|||
<Chart |
|||
animate={animate} |
|||
scale={scaleProps} |
|||
height={chartHeight} |
|||
forceFit={forceFit} |
|||
data={data} |
|||
padding={padding} |
|||
> |
|||
<Axis |
|||
key="axis-x" |
|||
name="x" |
|||
label={false} |
|||
line={false} |
|||
tickLine={false} |
|||
grid={false} |
|||
{...xAxis} |
|||
/> |
|||
<Axis |
|||
key="axis-y" |
|||
name="y" |
|||
label={false} |
|||
line={false} |
|||
tickLine={false} |
|||
grid={false} |
|||
{...yAxis} |
|||
/> |
|||
<Tooltip showTitle={false} crosshairs={false} /> |
|||
<Geom |
|||
type="area" |
|||
position="x*y" |
|||
color={color} |
|||
tooltip={tooltip} |
|||
shape="smooth" |
|||
style={{ |
|||
fillOpacity: 1, |
|||
}} |
|||
/> |
|||
{line ? ( |
|||
<Geom |
|||
type="line" |
|||
position="x*y" |
|||
shape="smooth" |
|||
color={borderColor} |
|||
size={borderWidth} |
|||
tooltip={false} |
|||
/> |
|||
) : ( |
|||
<span style={{ display: 'none' }} /> |
|||
)} |
|||
</Chart> |
|||
)} |
|||
</div> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default MiniArea; |
|||
@ -1,12 +0,0 @@ |
|||
import React from 'react'; |
|||
export interface IMiniBarProps { |
|||
color?: string; |
|||
height: number; |
|||
data: Array<{ |
|||
x: number | string; |
|||
y: number; |
|||
}>; |
|||
style?: React.CSSProperties; |
|||
} |
|||
|
|||
export default class MiniBar extends React.Component<IMiniBarProps, any> {} |
|||
@ -1,51 +0,0 @@ |
|||
import React from 'react'; |
|||
import { Chart, Tooltip, Geom } from 'bizcharts'; |
|||
import autoHeight from '../autoHeight'; |
|||
import styles from '../index.less'; |
|||
|
|||
@autoHeight() |
|||
class MiniBar extends React.Component { |
|||
render() { |
|||
const { height, forceFit = true, color = '#1890FF', data = [] } = this.props; |
|||
|
|||
const scale = { |
|||
x: { |
|||
type: 'cat', |
|||
}, |
|||
y: { |
|||
min: 0, |
|||
}, |
|||
}; |
|||
|
|||
const padding = [36, 5, 30, 5]; |
|||
|
|||
const tooltip = [ |
|||
'x*y', |
|||
(x, y) => ({ |
|||
name: x, |
|||
value: y, |
|||
}), |
|||
]; |
|||
|
|||
// for tooltip not to be hide
|
|||
const chartHeight = height + 54; |
|||
|
|||
return ( |
|||
<div className={styles.miniChart} style={{ height }}> |
|||
<div className={styles.chartContent}> |
|||
<Chart |
|||
scale={scale} |
|||
height={chartHeight} |
|||
forceFit={forceFit} |
|||
data={data} |
|||
padding={padding} |
|||
> |
|||
<Tooltip showTitle={false} crosshairs={false} /> |
|||
<Geom type="interval" position="x*y" color={color} tooltip={tooltip} /> |
|||
</Chart> |
|||
</div> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
export default MiniBar; |
|||
@ -1,11 +0,0 @@ |
|||
import React from 'react'; |
|||
export interface IMiniProgressProps { |
|||
target: number; |
|||
targetLabel: string; |
|||
color?: string; |
|||
strokeWidth?: number; |
|||
percent?: number; |
|||
style?: React.CSSProperties; |
|||
} |
|||
|
|||
export default class MiniProgress extends React.Component<IMiniProgressProps, any> {} |
|||
@ -1,34 +0,0 @@ |
|||
import React from 'react'; |
|||
import { Tooltip } from 'antd'; |
|||
import styles from './index.less'; |
|||
|
|||
const MiniProgress = ({ |
|||
targetLabel, |
|||
target, |
|||
color = 'rgb(19, 194, 194)', |
|||
strokeWidth, |
|||
percent, |
|||
}) => { |
|||
return ( |
|||
<div className={styles.miniProgress}> |
|||
<Tooltip title={targetLabel}> |
|||
<div className={styles.target} style={{ left: target ? `${target}%` : null }}> |
|||
<span style={{ backgroundColor: color || null }} /> |
|||
<span style={{ backgroundColor: color || null }} /> |
|||
</div> |
|||
</Tooltip> |
|||
<div className={styles.progressWrap}> |
|||
<div |
|||
className={styles.progress} |
|||
style={{ |
|||
backgroundColor: color || null, |
|||
width: percent ? `${percent}%` : null, |
|||
height: strokeWidth || null, |
|||
}} |
|||
/> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default MiniProgress; |
|||
@ -1,37 +0,0 @@ |
|||
@import '~antd/lib/style/themes/default.less'; |
|||
|
|||
.miniProgress { |
|||
position: relative; |
|||
width: 100%; |
|||
padding: 5px 0; |
|||
.progressWrap { |
|||
position: relative; |
|||
background-color: @background-color-base; |
|||
} |
|||
.progress { |
|||
width: 0; |
|||
height: 100%; |
|||
background-color: @primary-color; |
|||
border-radius: 1px 0 0 1px; |
|||
transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s; |
|||
} |
|||
.target { |
|||
position: absolute; |
|||
top: 0; |
|||
bottom: 0; |
|||
z-index: 9; |
|||
width: 20px; |
|||
span { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 2px; |
|||
height: 4px; |
|||
border-radius: 100px; |
|||
} |
|||
span:last-child { |
|||
top: auto; |
|||
bottom: 0; |
|||
} |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
import React from 'react'; |
|||
export interface IPieProps { |
|||
animate?: boolean; |
|||
color?: string; |
|||
colors?: string[]; |
|||
height: number; |
|||
hasLegend?: boolean; |
|||
padding?: [number, number, number, number]; |
|||
percent?: number; |
|||
data?: Array<{ |
|||
x: string | string; |
|||
y: number; |
|||
}>; |
|||
total?: React.ReactNode | number | (() => React.ReactNode | number); |
|||
title?: React.ReactNode; |
|||
tooltip?: boolean; |
|||
valueFormat?: (value: string) => string | React.ReactNode; |
|||
subTitle?: React.ReactNode; |
|||
} |
|||
|
|||
export default class Pie extends React.Component<IPieProps, any> {} |
|||
@ -1,248 +0,0 @@ |
|||
import React, { Component } from 'react'; |
|||
import { Chart, Tooltip, Geom, Coord } from 'bizcharts'; |
|||
import { DataView } from '@antv/data-set'; |
|||
import { Divider } from 'antd'; |
|||
import classNames from 'classnames'; |
|||
import ReactFitText from 'react-fittext'; |
|||
import Debounce from 'lodash-decorators/debounce'; |
|||
import Bind from 'lodash-decorators/bind'; |
|||
import styles from './index.less'; |
|||
|
|||
/* eslint react/no-danger:0 */ |
|||
class Pie extends Component { |
|||
state = { |
|||
height: 0, |
|||
legendData: [], |
|||
legendBlock: false, |
|||
}; |
|||
|
|||
componentDidUpdate(preProps) { |
|||
const { data } = this.props; |
|||
if (data !== preProps.data) { |
|||
// because of charts data create when rendered
|
|||
// so there is a trick for get rendered time
|
|||
this.getLegendData(); |
|||
} |
|||
} |
|||
|
|||
getG2Instance = chart => { |
|||
this.chart = chart; |
|||
requestAnimationFrame(() => { |
|||
this.getLegendData(); |
|||
}); |
|||
}; |
|||
|
|||
// for custom lengend view
|
|||
getLegendData = () => { |
|||
if (!this.chart) return; |
|||
const geom = this.chart.getAllGeoms()[0]; // 获取所有的图形
|
|||
if (!geom) return; |
|||
const items = geom.get('dataArray') || []; // 获取图形对应的
|
|||
|
|||
const legendData = items.map(item => { |
|||
/* eslint no-underscore-dangle:0 */ |
|||
const origin = item[0]._origin; |
|||
origin.color = item[0].color; |
|||
origin.checked = true; |
|||
return origin; |
|||
}); |
|||
|
|||
this.setState({ |
|||
legendData, |
|||
}); |
|||
}; |
|||
|
|||
handleRoot = n => { |
|||
this.root = n; |
|||
}; |
|||
|
|||
handleLegendClick = (item, i) => { |
|||
const newItem = item; |
|||
newItem.checked = !newItem.checked; |
|||
|
|||
const { legendData } = this.state; |
|||
legendData[i] = newItem; |
|||
|
|||
const filteredLegendData = legendData.filter(l => l.checked).map(l => l.x); |
|||
|
|||
if (this.chart) { |
|||
this.chart.filter('x', val => filteredLegendData.indexOf(val) > -1); |
|||
} |
|||
|
|||
this.setState({ |
|||
legendData, |
|||
}); |
|||
}; |
|||
|
|||
// for window resize auto responsive legend
|
|||
@Bind() |
|||
@Debounce(300) |
|||
resize() { |
|||
const { hasLegend } = this.props; |
|||
const { legendBlock } = this.state; |
|||
if (!hasLegend || !this.root) { |
|||
window.removeEventListener('resize', this.resize); |
|||
return; |
|||
} |
|||
if (this.root.parentNode.clientWidth <= 380) { |
|||
if (!legendBlock) { |
|||
this.setState({ |
|||
legendBlock: true, |
|||
}); |
|||
} |
|||
} else if (legendBlock) { |
|||
this.setState({ |
|||
legendBlock: false, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
render() { |
|||
const { |
|||
valueFormat, |
|||
subTitle, |
|||
total, |
|||
hasLegend = false, |
|||
className, |
|||
style, |
|||
height, |
|||
percent, |
|||
color, |
|||
inner = 0.75, |
|||
animate = true, |
|||
colors, |
|||
lineWidth = 1, |
|||
} = this.props; |
|||
|
|||
const { legendData, height: stateHeight, legendBlock } = this.state; |
|||
const pieClassName = classNames(styles.pie, className, { |
|||
[styles.hasLegend]: !!hasLegend, |
|||
[styles.legendBlock]: legendBlock, |
|||
}); |
|||
|
|||
const { |
|||
data: propsData, |
|||
selected: propsSelected = true, |
|||
tooltip: propsTooltip = true, |
|||
} = this.props; |
|||
|
|||
let data = propsData || []; |
|||
let selected = propsSelected; |
|||
let tooltip = propsTooltip; |
|||
|
|||
const defaultColors = colors; |
|||
selected = selected || true; |
|||
tooltip = tooltip || true; |
|||
let formatColor; |
|||
|
|||
const scale = { |
|||
x: { |
|||
type: 'cat', |
|||
range: [0, 1], |
|||
}, |
|||
y: { |
|||
min: 0, |
|||
}, |
|||
}; |
|||
|
|||
if (percent || percent === 0) { |
|||
selected = false; |
|||
tooltip = false; |
|||
formatColor = value => { |
|||
if (value === '占比') { |
|||
return color || 'rgba(24, 144, 255, 0.85)'; |
|||
} |
|||
return '#F0F2F5'; |
|||
}; |
|||
|
|||
data = [ |
|||
{ |
|||
x: '占比', |
|||
y: parseFloat(percent), |
|||
}, |
|||
{ |
|||
x: '反比', |
|||
y: 100 - parseFloat(percent), |
|||
}, |
|||
]; |
|||
} |
|||
|
|||
const tooltipFormat = [ |
|||
'x*percent', |
|||
(x, p) => ({ |
|||
name: x, |
|||
value: `${(p * 100).toFixed(2)}%`, |
|||
}), |
|||
]; |
|||
|
|||
const padding = [12, 0, 12, 0]; |
|||
|
|||
const dv = new DataView(); |
|||
dv.source(data).transform({ |
|||
type: 'percent', |
|||
field: 'y', |
|||
dimension: 'x', |
|||
as: 'percent', |
|||
}); |
|||
|
|||
return ( |
|||
<div ref={this.handleRoot} className={pieClassName} style={style}> |
|||
<ReactFitText maxFontSize={25}> |
|||
<div className={styles.chart}> |
|||
<Chart |
|||
scale={scale} |
|||
height={height || stateHeight} |
|||
data={dv} |
|||
padding={padding} |
|||
animate={animate} |
|||
onGetG2Instance={this.getG2Instance} |
|||
> |
|||
{!!tooltip && <Tooltip showTitle={false} />} |
|||
<Coord type="theta" innerRadius={inner} /> |
|||
<Geom |
|||
style={{ lineWidth, stroke: '#fff' }} |
|||
tooltip={tooltip && tooltipFormat} |
|||
type="intervalStack" |
|||
position="percent" |
|||
color={['x', percent || percent === 0 ? formatColor : defaultColors]} |
|||
selected={selected} |
|||
/> |
|||
</Chart> |
|||
|
|||
{(subTitle || total) && ( |
|||
<div className={styles.total}> |
|||
{subTitle && <h4 className="pie-sub-title">{subTitle}</h4>} |
|||
{/* eslint-disable-next-line */} |
|||
{total && ( |
|||
<div className="pie-stat">{typeof total === 'function' ? total() : total}</div> |
|||
)} |
|||
</div> |
|||
)} |
|||
</div> |
|||
</ReactFitText> |
|||
{hasLegend && ( |
|||
<ul className={styles.legend}> |
|||
{legendData.map((item, i) => ( |
|||
<li key={item.x} onClick={() => this.handleLegendClick(item, i)}> |
|||
<span |
|||
className={styles.dot} |
|||
style={{ |
|||
backgroundColor: !item.checked ? '#aaa' : item.color, |
|||
}} |
|||
/> |
|||
<span className={styles.legendTitle}>{item.x}</span> |
|||
<Divider type="vertical" /> |
|||
<span className={styles.percent}> |
|||
{`${(Number.isNaN(item.percent) ? 0 : item.percent * 100).toFixed(2)}%`} |
|||
</span> |
|||
<span className={styles.value}>{valueFormat ? valueFormat(item.y) : item.y}</span> |
|||
</li> |
|||
))} |
|||
</ul> |
|||
)} |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default Pie; |
|||
@ -1,94 +0,0 @@ |
|||
@import '~antd/lib/style/themes/default.less'; |
|||
|
|||
.pie { |
|||
position: relative; |
|||
.chart { |
|||
position: relative; |
|||
} |
|||
&.hasLegend .chart { |
|||
width: ~'calc(100% - 240px)'; |
|||
} |
|||
.legend { |
|||
position: absolute; |
|||
top: 50%; |
|||
right: 0; |
|||
min-width: 200px; |
|||
margin: 0 20px; |
|||
padding: 0; |
|||
list-style: none; |
|||
transform: translateY(-50%); |
|||
li { |
|||
height: 22px; |
|||
margin-bottom: 16px; |
|||
line-height: 22px; |
|||
cursor: pointer; |
|||
&:last-child { |
|||
margin-bottom: 0; |
|||
} |
|||
} |
|||
} |
|||
.dot { |
|||
position: relative; |
|||
top: -1px; |
|||
display: inline-block; |
|||
width: 8px; |
|||
height: 8px; |
|||
margin-right: 8px; |
|||
border-radius: 8px; |
|||
} |
|||
.line { |
|||
display: inline-block; |
|||
width: 1px; |
|||
height: 16px; |
|||
margin-right: 8px; |
|||
background-color: @border-color-split; |
|||
} |
|||
.legendTitle { |
|||
color: @text-color; |
|||
} |
|||
.percent { |
|||
color: @text-color-secondary; |
|||
} |
|||
.value { |
|||
position: absolute; |
|||
right: 0; |
|||
} |
|||
.title { |
|||
margin-bottom: 8px; |
|||
} |
|||
.total { |
|||
position: absolute; |
|||
top: 50%; |
|||
left: 50%; |
|||
max-height: 62px; |
|||
text-align: center; |
|||
transform: translate(-50%, -50%); |
|||
& > h4 { |
|||
height: 22px; |
|||
margin-bottom: 8px; |
|||
color: @text-color-secondary; |
|||
font-weight: normal; |
|||
font-size: 14px; |
|||
line-height: 22px; |
|||
} |
|||
& > p { |
|||
display: block; |
|||
height: 32px; |
|||
color: @heading-color; |
|||
font-size: 1.2em; |
|||
line-height: 32px; |
|||
white-space: nowrap; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.legendBlock { |
|||
&.hasLegend .chart { |
|||
width: 100%; |
|||
margin: 0 0 32px 0; |
|||
} |
|||
.legend { |
|||
position: relative; |
|||
transform: none; |
|||
} |
|||
} |
|||
@ -1,15 +0,0 @@ |
|||
import React from 'react'; |
|||
export interface IRadarProps { |
|||
title?: React.ReactNode; |
|||
height: number; |
|||
padding?: [number, number, number, number]; |
|||
hasLegend?: boolean; |
|||
data: Array<{ |
|||
name: string; |
|||
label: string; |
|||
value: string; |
|||
}>; |
|||
style?: React.CSSProperties; |
|||
} |
|||
|
|||
export default class Radar extends React.Component<IRadarProps, any> {} |
|||
@ -1,184 +0,0 @@ |
|||
import React, { Component } from 'react'; |
|||
import { Chart, Tooltip, Geom, Coord, Axis } from 'bizcharts'; |
|||
import { Row, Col } from 'antd'; |
|||
import autoHeight from '../autoHeight'; |
|||
import styles from './index.less'; |
|||
|
|||
/* eslint react/no-danger:0 */ |
|||
@autoHeight() |
|||
class Radar extends Component { |
|||
state = { |
|||
legendData: [], |
|||
}; |
|||
|
|||
componentDidMount() { |
|||
this.getLegendData(); |
|||
} |
|||
|
|||
componentDidUpdate(preProps) { |
|||
const { data } = this.props; |
|||
if (data !== preProps.data) { |
|||
this.getLegendData(); |
|||
} |
|||
} |
|||
|
|||
getG2Instance = chart => { |
|||
this.chart = chart; |
|||
}; |
|||
|
|||
// for custom lengend view
|
|||
getLegendData = () => { |
|||
if (!this.chart) return; |
|||
const geom = this.chart.getAllGeoms()[0]; // 获取所有的图形
|
|||
if (!geom) return; |
|||
const items = geom.get('dataArray') || []; // 获取图形对应的
|
|||
|
|||
const legendData = items.map(item => { |
|||
// eslint-disable-next-line
|
|||
const origins = item.map(t => t._origin); |
|||
const result = { |
|||
name: origins[0].name, |
|||
color: item[0].color, |
|||
checked: true, |
|||
value: origins.reduce((p, n) => p + n.value, 0), |
|||
}; |
|||
|
|||
return result; |
|||
}); |
|||
|
|||
this.setState({ |
|||
legendData, |
|||
}); |
|||
}; |
|||
|
|||
handleRef = n => { |
|||
this.node = n; |
|||
}; |
|||
|
|||
handleLegendClick = (item, i) => { |
|||
const newItem = item; |
|||
newItem.checked = !newItem.checked; |
|||
|
|||
const { legendData } = this.state; |
|||
legendData[i] = newItem; |
|||
|
|||
const filteredLegendData = legendData.filter(l => l.checked).map(l => l.name); |
|||
|
|||
if (this.chart) { |
|||
this.chart.filter('name', val => filteredLegendData.indexOf(val) > -1); |
|||
this.chart.repaint(); |
|||
} |
|||
|
|||
this.setState({ |
|||
legendData, |
|||
}); |
|||
}; |
|||
|
|||
render() { |
|||
const defaultColors = [ |
|||
'#1890FF', |
|||
'#FACC14', |
|||
'#2FC25B', |
|||
'#8543E0', |
|||
'#F04864', |
|||
'#13C2C2', |
|||
'#fa8c16', |
|||
'#a0d911', |
|||
]; |
|||
|
|||
const { |
|||
data = [], |
|||
height = 0, |
|||
title, |
|||
hasLegend = false, |
|||
forceFit = true, |
|||
tickCount = 5, |
|||
padding = [35, 30, 16, 30], |
|||
animate = true, |
|||
colors = defaultColors, |
|||
} = this.props; |
|||
|
|||
const { legendData } = this.state; |
|||
|
|||
const scale = { |
|||
value: { |
|||
min: 0, |
|||
tickCount, |
|||
}, |
|||
}; |
|||
|
|||
const chartHeight = height - (hasLegend ? 80 : 22); |
|||
|
|||
return ( |
|||
<div className={styles.radar} style={{ height }}> |
|||
{title && <h4>{title}</h4>} |
|||
<Chart |
|||
scale={scale} |
|||
height={chartHeight} |
|||
forceFit={forceFit} |
|||
data={data} |
|||
padding={padding} |
|||
animate={animate} |
|||
onGetG2Instance={this.getG2Instance} |
|||
> |
|||
<Tooltip /> |
|||
<Coord type="polar" /> |
|||
<Axis |
|||
name="label" |
|||
line={null} |
|||
tickLine={null} |
|||
grid={{ |
|||
lineStyle: { |
|||
lineDash: null, |
|||
}, |
|||
hideFirstLine: false, |
|||
}} |
|||
/> |
|||
<Axis |
|||
name="value" |
|||
grid={{ |
|||
type: 'polygon', |
|||
lineStyle: { |
|||
lineDash: null, |
|||
}, |
|||
}} |
|||
/> |
|||
<Geom type="line" position="label*value" color={['name', colors]} size={1} /> |
|||
<Geom |
|||
type="point" |
|||
position="label*value" |
|||
color={['name', colors]} |
|||
shape="circle" |
|||
size={3} |
|||
/> |
|||
</Chart> |
|||
{hasLegend && ( |
|||
<Row className={styles.legend}> |
|||
{legendData.map((item, i) => ( |
|||
<Col |
|||
span={24 / legendData.length} |
|||
key={item.name} |
|||
onClick={() => this.handleLegendClick(item, i)} |
|||
> |
|||
<div className={styles.legendItem}> |
|||
<p> |
|||
<span |
|||
className={styles.dot} |
|||
style={{ |
|||
backgroundColor: !item.checked ? '#aaa' : item.color, |
|||
}} |
|||
/> |
|||
<span>{item.name}</span> |
|||
</p> |
|||
<h6>{item.value}</h6> |
|||
</div> |
|||
</Col> |
|||
))} |
|||
</Row> |
|||
)} |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default Radar; |
|||
@ -1,46 +0,0 @@ |
|||
@import '~antd/lib/style/themes/default.less'; |
|||
|
|||
.radar { |
|||
.legend { |
|||
margin-top: 16px; |
|||
.legendItem { |
|||
position: relative; |
|||
color: @text-color-secondary; |
|||
line-height: 22px; |
|||
text-align: center; |
|||
cursor: pointer; |
|||
p { |
|||
margin: 0; |
|||
} |
|||
h6 { |
|||
margin-top: 4px; |
|||
margin-bottom: 0; |
|||
padding-left: 16px; |
|||
color: @heading-color; |
|||
font-size: 24px; |
|||
line-height: 32px; |
|||
} |
|||
&::after { |
|||
position: absolute; |
|||
top: 8px; |
|||
right: 0; |
|||
width: 1px; |
|||
height: 40px; |
|||
background-color: @border-color-split; |
|||
content: ''; |
|||
} |
|||
} |
|||
> :last-child .legendItem::after { |
|||
display: none; |
|||
} |
|||
.dot { |
|||
position: relative; |
|||
top: -1px; |
|||
display: inline-block; |
|||
width: 6px; |
|||
height: 6px; |
|||
margin-right: 6px; |
|||
border-radius: 6px; |
|||
} |
|||
} |
|||
} |
|||
@ -1,11 +0,0 @@ |
|||
import React from 'react'; |
|||
export interface ITagCloudProps { |
|||
data: Array<{ |
|||
name: string; |
|||
value: number; |
|||
}>; |
|||
height: number; |
|||
style?: React.CSSProperties; |
|||
} |
|||
|
|||
export default class TagCloud extends React.Component<ITagCloudProps, any> {} |
|||
@ -1,167 +0,0 @@ |
|||
import React, { Component } from 'react'; |
|||
import { Chart, Geom, Coord, Shape, Tooltip } from 'bizcharts'; |
|||
import DataSet from '@antv/data-set'; |
|||
import Debounce from 'lodash-decorators/debounce'; |
|||
import Bind from 'lodash-decorators/bind'; |
|||
import classNames from 'classnames'; |
|||
import autoHeight from '../autoHeight'; |
|||
import styles from './index.less'; |
|||
|
|||
/* eslint no-underscore-dangle: 0 */ |
|||
/* eslint no-param-reassign: 0 */ |
|||
|
|||
const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png'; |
|||
|
|||
@autoHeight() |
|||
class TagCloud extends Component { |
|||
state = { |
|||
dv: null, |
|||
}; |
|||
|
|||
componentDidMount() { |
|||
this.initTagCloud(); |
|||
this.renderChart(); |
|||
} |
|||
|
|||
componentDidUpdate(preProps) { |
|||
const { data } = this.props; |
|||
if (JSON.stringify(preProps.data) !== JSON.stringify(data)) { |
|||
this.renderChart(this.props); |
|||
} |
|||
} |
|||
|
|||
componentWillUnmount() { |
|||
this.isUnmount = true; |
|||
} |
|||
|
|||
saveRootRef = node => { |
|||
this.root = node; |
|||
}; |
|||
|
|||
initTagCloud = () => { |
|||
function getTextAttrs(cfg) { |
|||
return Object.assign({}, cfg.style, { |
|||
fillOpacity: cfg.opacity, |
|||
fontSize: cfg.origin._origin.size, |
|||
rotate: cfg.origin._origin.rotate, |
|||
text: cfg.origin._origin.text, |
|||
textAlign: 'center', |
|||
fontFamily: cfg.origin._origin.font, |
|||
fill: cfg.color, |
|||
textBaseline: 'Alphabetic', |
|||
}); |
|||
} |
|||
|
|||
// 给point注册一个词云的shape
|
|||
Shape.registerShape('point', 'cloud', { |
|||
drawShape(cfg, container) { |
|||
const attrs = getTextAttrs(cfg); |
|||
return container.addShape('text', { |
|||
attrs: Object.assign(attrs, { |
|||
x: cfg.x, |
|||
y: cfg.y, |
|||
}), |
|||
}); |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
@Bind() |
|||
@Debounce(500) |
|||
renderChart(nextProps) { |
|||
// const colors = ['#1890FF', '#41D9C7', '#2FC25B', '#FACC14', '#9AE65C'];
|
|||
const { data, height } = nextProps || this.props; |
|||
|
|||
if (data.length < 1 || !this.root) { |
|||
return; |
|||
} |
|||
|
|||
const h = height; |
|||
const w = this.root.offsetWidth; |
|||
|
|||
const onload = () => { |
|||
const dv = new DataSet.View().source(data); |
|||
const range = dv.range('value'); |
|||
const [min, max] = range; |
|||
dv.transform({ |
|||
type: 'tag-cloud', |
|||
fields: ['name', 'value'], |
|||
imageMask: this.imageMask, |
|||
font: 'Verdana', |
|||
size: [w, h], // 宽高设置最好根据 imageMask 做调整
|
|||
padding: 0, |
|||
timeInterval: 5000, // max execute time
|
|||
rotate() { |
|||
return 0; |
|||
}, |
|||
fontSize(d) { |
|||
// eslint-disable-next-line
|
|||
return Math.pow((d.value - min) / (max - min), 2) * (17.5 - 5) + 5; |
|||
}, |
|||
}); |
|||
|
|||
if (this.isUnmount) { |
|||
return; |
|||
} |
|||
|
|||
this.setState({ |
|||
dv, |
|||
w, |
|||
h, |
|||
}); |
|||
}; |
|||
|
|||
if (!this.imageMask) { |
|||
this.imageMask = new Image(); |
|||
this.imageMask.crossOrigin = ''; |
|||
this.imageMask.src = imgUrl; |
|||
|
|||
this.imageMask.onload = onload; |
|||
} else { |
|||
onload(); |
|||
} |
|||
} |
|||
|
|||
render() { |
|||
const { className, height } = this.props; |
|||
const { dv, w, h } = this.state; |
|||
|
|||
return ( |
|||
<div |
|||
className={classNames(styles.tagCloud, className)} |
|||
style={{ width: '100%', height }} |
|||
ref={this.saveRootRef} |
|||
> |
|||
{dv && ( |
|||
<Chart |
|||
width={w} |
|||
height={h} |
|||
data={dv} |
|||
padding={0} |
|||
scale={{ |
|||
x: { nice: false }, |
|||
y: { nice: false }, |
|||
}} |
|||
> |
|||
<Tooltip showTitle={false} /> |
|||
<Coord reflect="y" /> |
|||
<Geom |
|||
type="point" |
|||
position="x*y" |
|||
color="text" |
|||
shape="cloud" |
|||
tooltip={[ |
|||
'text*value', |
|||
function trans(text, value) { |
|||
return { name: text, value }; |
|||
}, |
|||
]} |
|||
/> |
|||
</Chart> |
|||
)} |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default TagCloud; |
|||
@ -1,6 +0,0 @@ |
|||
.tagCloud { |
|||
overflow: hidden; |
|||
canvas { |
|||
transform-origin: 0 0; |
|||
} |
|||
} |
|||
@ -1,14 +0,0 @@ |
|||
import React from 'react'; |
|||
export interface ITimelineChartProps { |
|||
data: Array<{ |
|||
x: number; |
|||
y1: number; |
|||
y2?: number; |
|||
}>; |
|||
titleMap: { y1: string; y2?: string }; |
|||
padding?: [number, number, number, number]; |
|||
height?: number; |
|||
style?: React.CSSProperties; |
|||
} |
|||
|
|||
export default class TimelineChart extends React.Component<ITimelineChartProps, any> {} |
|||
@ -1,120 +0,0 @@ |
|||
import React from 'react'; |
|||
import { Chart, Tooltip, Geom, Legend, Axis } from 'bizcharts'; |
|||
import DataSet from '@antv/data-set'; |
|||
import Slider from 'bizcharts-plugin-slider'; |
|||
import autoHeight from '../autoHeight'; |
|||
import styles from './index.less'; |
|||
|
|||
@autoHeight() |
|||
class TimelineChart extends React.Component { |
|||
render() { |
|||
const { |
|||
title, |
|||
height = 400, |
|||
padding = [60, 20, 40, 40], |
|||
titleMap = { |
|||
y1: 'y1', |
|||
y2: 'y2', |
|||
}, |
|||
borderWidth = 2, |
|||
data: sourceData, |
|||
} = this.props; |
|||
|
|||
const data = Array.isArray(sourceData) ? sourceData : [{ x: 0, y1: 0, y2: 0 }]; |
|||
|
|||
data.sort((a, b) => a.x - b.x); |
|||
|
|||
let max; |
|||
if (data[0] && data[0].y1 && data[0].y2) { |
|||
max = Math.max( |
|||
[...data].sort((a, b) => b.y1 - a.y1)[0].y1, |
|||
[...data].sort((a, b) => b.y2 - a.y2)[0].y2 |
|||
); |
|||
} |
|||
|
|||
const ds = new DataSet({ |
|||
state: { |
|||
start: data[0].x, |
|||
end: data[data.length - 1].x, |
|||
}, |
|||
}); |
|||
|
|||
const dv = ds.createView(); |
|||
dv.source(data) |
|||
.transform({ |
|||
type: 'filter', |
|||
callback: obj => { |
|||
const date = obj.x; |
|||
return date <= ds.state.end && date >= ds.state.start; |
|||
}, |
|||
}) |
|||
.transform({ |
|||
type: 'map', |
|||
callback(row) { |
|||
const newRow = { ...row }; |
|||
newRow[titleMap.y1] = row.y1; |
|||
newRow[titleMap.y2] = row.y2; |
|||
return newRow; |
|||
}, |
|||
}) |
|||
.transform({ |
|||
type: 'fold', |
|||
fields: [titleMap.y1, titleMap.y2], // 展开字段集
|
|||
key: 'key', // key字段
|
|||
value: 'value', // value字段
|
|||
}); |
|||
|
|||
const timeScale = { |
|||
type: 'time', |
|||
tickInterval: 60 * 60 * 1000, |
|||
mask: 'HH:mm', |
|||
range: [0, 1], |
|||
}; |
|||
|
|||
const cols = { |
|||
x: timeScale, |
|||
value: { |
|||
max, |
|||
min: 0, |
|||
}, |
|||
}; |
|||
|
|||
const SliderGen = () => ( |
|||
<Slider |
|||
padding={[0, padding[1] + 20, 0, padding[3]]} |
|||
width="auto" |
|||
height={26} |
|||
xAxis="x" |
|||
yAxis="y1" |
|||
scales={{ x: timeScale }} |
|||
data={data} |
|||
start={ds.state.start} |
|||
end={ds.state.end} |
|||
backgroundChart={{ type: 'line' }} |
|||
onChange={({ startValue, endValue }) => { |
|||
ds.setState('start', startValue); |
|||
ds.setState('end', endValue); |
|||
}} |
|||
/> |
|||
); |
|||
|
|||
return ( |
|||
<div className={styles.timelineChart} style={{ height: height + 30 }}> |
|||
<div> |
|||
{title && <h4>{title}</h4>} |
|||
<Chart height={height} padding={padding} data={dv} scale={cols} forceFit> |
|||
<Axis name="x" /> |
|||
<Tooltip /> |
|||
<Legend name="key" position="top" /> |
|||
<Geom type="line" position="x*value" size={borderWidth} color="key" /> |
|||
</Chart> |
|||
<div style={{ marginRight: -20 }}> |
|||
<SliderGen /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default TimelineChart; |
|||
@ -1,3 +0,0 @@ |
|||
.timelineChart { |
|||
background: #fff; |
|||
} |
|||
@ -1,10 +0,0 @@ |
|||
import React from 'react'; |
|||
export interface IWaterWaveProps { |
|||
title: React.ReactNode; |
|||
color?: string; |
|||
height: number; |
|||
percent: number; |
|||
style?: React.CSSProperties; |
|||
} |
|||
|
|||
export default class WaterWave extends React.Component<IWaterWaveProps, any> {} |
|||
@ -1,213 +0,0 @@ |
|||
import React, { PureComponent } from 'react'; |
|||
import autoHeight from '../autoHeight'; |
|||
import styles from './index.less'; |
|||
|
|||
/* eslint no-return-assign: 0 */ |
|||
/* eslint no-mixed-operators: 0 */ |
|||
// riddle: https://riddle.alibaba-inc.com/riddles/2d9a4b90
|
|||
|
|||
@autoHeight() |
|||
class WaterWave extends PureComponent { |
|||
state = { |
|||
radio: 1, |
|||
}; |
|||
|
|||
componentDidMount() { |
|||
this.renderChart(); |
|||
this.resize(); |
|||
window.addEventListener( |
|||
'resize', |
|||
() => { |
|||
requestAnimationFrame(() => this.resize()); |
|||
}, |
|||
{ passive: true } |
|||
); |
|||
} |
|||
|
|||
componentDidUpdate(props) { |
|||
const { percent } = this.props; |
|||
if (props.percent !== percent) { |
|||
// 不加这个会造成绘制缓慢
|
|||
this.renderChart('update'); |
|||
} |
|||
} |
|||
|
|||
componentWillUnmount() { |
|||
cancelAnimationFrame(this.timer); |
|||
if (this.node) { |
|||
this.node.innerHTML = ''; |
|||
} |
|||
window.removeEventListener('resize', this.resize); |
|||
} |
|||
|
|||
resize = () => { |
|||
if (this.root) { |
|||
const { height } = this.props; |
|||
const { offsetWidth } = this.root.parentNode; |
|||
this.setState({ |
|||
radio: offsetWidth < height ? offsetWidth / height : 1, |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
renderChart(type) { |
|||
const { percent, color = '#1890FF' } = this.props; |
|||
const data = percent / 100; |
|||
const self = this; |
|||
cancelAnimationFrame(this.timer); |
|||
|
|||
if (!this.node || (data !== 0 && !data)) { |
|||
return; |
|||
} |
|||
|
|||
const canvas = this.node; |
|||
const ctx = canvas.getContext('2d'); |
|||
const canvasWidth = canvas.width; |
|||
const canvasHeight = canvas.height; |
|||
const radius = canvasWidth / 2; |
|||
const lineWidth = 2; |
|||
const cR = radius - lineWidth; |
|||
|
|||
ctx.beginPath(); |
|||
ctx.lineWidth = lineWidth * 2; |
|||
|
|||
const axisLength = canvasWidth - lineWidth; |
|||
const unit = axisLength / 8; |
|||
const range = 0.2; // 振幅
|
|||
let currRange = range; |
|||
const xOffset = lineWidth; |
|||
let sp = 0; // 周期偏移量
|
|||
let currData = 0; |
|||
const waveupsp = 0.005; // 水波上涨速度
|
|||
|
|||
let arcStack = []; |
|||
const bR = radius - lineWidth; |
|||
const circleOffset = -(Math.PI / 2); |
|||
let circleLock = true; |
|||
|
|||
for (let i = circleOffset; i < circleOffset + 2 * Math.PI; i += 1 / (8 * Math.PI)) { |
|||
arcStack.push([radius + bR * Math.cos(i), radius + bR * Math.sin(i)]); |
|||
} |
|||
|
|||
const cStartPoint = arcStack.shift(); |
|||
ctx.strokeStyle = color; |
|||
ctx.moveTo(cStartPoint[0], cStartPoint[1]); |
|||
|
|||
function drawSin() { |
|||
ctx.beginPath(); |
|||
ctx.save(); |
|||
|
|||
const sinStack = []; |
|||
for (let i = xOffset; i <= xOffset + axisLength; i += 20 / axisLength) { |
|||
const x = sp + (xOffset + i) / unit; |
|||
const y = Math.sin(x) * currRange; |
|||
const dx = i; |
|||
const dy = 2 * cR * (1 - currData) + (radius - cR) - unit * y; |
|||
|
|||
ctx.lineTo(dx, dy); |
|||
sinStack.push([dx, dy]); |
|||
} |
|||
|
|||
const startPoint = sinStack.shift(); |
|||
|
|||
ctx.lineTo(xOffset + axisLength, canvasHeight); |
|||
ctx.lineTo(xOffset, canvasHeight); |
|||
ctx.lineTo(startPoint[0], startPoint[1]); |
|||
|
|||
const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeight); |
|||
gradient.addColorStop(0, '#ffffff'); |
|||
gradient.addColorStop(1, color); |
|||
ctx.fillStyle = gradient; |
|||
ctx.fill(); |
|||
ctx.restore(); |
|||
} |
|||
|
|||
function render() { |
|||
ctx.clearRect(0, 0, canvasWidth, canvasHeight); |
|||
if (circleLock && type !== 'update') { |
|||
if (arcStack.length) { |
|||
const temp = arcStack.shift(); |
|||
ctx.lineTo(temp[0], temp[1]); |
|||
ctx.stroke(); |
|||
} else { |
|||
circleLock = false; |
|||
ctx.lineTo(cStartPoint[0], cStartPoint[1]); |
|||
ctx.stroke(); |
|||
arcStack = null; |
|||
|
|||
ctx.globalCompositeOperation = 'destination-over'; |
|||
ctx.beginPath(); |
|||
ctx.lineWidth = lineWidth; |
|||
ctx.arc(radius, radius, bR, 0, 2 * Math.PI, 1); |
|||
|
|||
ctx.beginPath(); |
|||
ctx.save(); |
|||
ctx.arc(radius, radius, radius - 3 * lineWidth, 0, 2 * Math.PI, 1); |
|||
|
|||
ctx.restore(); |
|||
ctx.clip(); |
|||
ctx.fillStyle = color; |
|||
} |
|||
} else { |
|||
if (data >= 0.85) { |
|||
if (currRange > range / 4) { |
|||
const t = range * 0.01; |
|||
currRange -= t; |
|||
} |
|||
} else if (data <= 0.1) { |
|||
if (currRange < range * 1.5) { |
|||
const t = range * 0.01; |
|||
currRange += t; |
|||
} |
|||
} else { |
|||
if (currRange <= range) { |
|||
const t = range * 0.01; |
|||
currRange += t; |
|||
} |
|||
if (currRange >= range) { |
|||
const t = range * 0.01; |
|||
currRange -= t; |
|||
} |
|||
} |
|||
if (data - currData > 0) { |
|||
currData += waveupsp; |
|||
} |
|||
if (data - currData < 0) { |
|||
currData -= waveupsp; |
|||
} |
|||
|
|||
sp += 0.07; |
|||
drawSin(); |
|||
} |
|||
self.timer = requestAnimationFrame(render); |
|||
} |
|||
render(); |
|||
} |
|||
|
|||
render() { |
|||
const { radio } = this.state; |
|||
const { percent, title, height } = this.props; |
|||
return ( |
|||
<div |
|||
className={styles.waterWave} |
|||
ref={n => (this.root = n)} |
|||
style={{ transform: `scale(${radio})` }} |
|||
> |
|||
<div style={{ width: height, height, overflow: 'hidden' }}> |
|||
<canvas |
|||
className={styles.waterWaveCanvasWrapper} |
|||
ref={n => (this.node = n)} |
|||
width={height * 2} |
|||
height={height * 2} |
|||
/> |
|||
</div> |
|||
<div className={styles.text} style={{ width: height }}> |
|||
{title && <span>{title}</span>} |
|||
<h4>{percent}%</h4> |
|||
</div> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default WaterWave; |
|||
@ -1,28 +0,0 @@ |
|||
@import '~antd/lib/style/themes/default.less'; |
|||
|
|||
.waterWave { |
|||
position: relative; |
|||
display: inline-block; |
|||
transform-origin: left; |
|||
.text { |
|||
position: absolute; |
|||
top: 32px; |
|||
left: 0; |
|||
width: 100%; |
|||
text-align: center; |
|||
span { |
|||
color: @text-color-secondary; |
|||
font-size: 14px; |
|||
line-height: 22px; |
|||
} |
|||
h4 { |
|||
color: @heading-color; |
|||
font-size: 24px; |
|||
line-height: 32px; |
|||
} |
|||
} |
|||
.waterWaveCanvasWrapper { |
|||
transform: scale(0.5); |
|||
transform-origin: 0 0; |
|||
} |
|||
} |
|||
@ -1,62 +0,0 @@ |
|||
/* eslint eqeqeq: 0 */ |
|||
import React from 'react'; |
|||
|
|||
function computeHeight(node) { |
|||
const totalHeight = parseInt(getComputedStyle(node).height, 10); |
|||
const padding = |
|||
parseInt(getComputedStyle(node).paddingTop, 10) + |
|||
parseInt(getComputedStyle(node).paddingBottom, 10); |
|||
return totalHeight - padding; |
|||
} |
|||
|
|||
function getAutoHeight(n) { |
|||
if (!n) { |
|||
return 0; |
|||
} |
|||
|
|||
let node = n; |
|||
|
|||
let height = computeHeight(node); |
|||
|
|||
while (!height) { |
|||
node = node.parentNode; |
|||
if (node) { |
|||
height = computeHeight(node); |
|||
} else { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return height; |
|||
} |
|||
|
|||
const autoHeight = () => WrappedComponent => |
|||
class extends React.Component { |
|||
state = { |
|||
computedHeight: 0, |
|||
}; |
|||
|
|||
componentDidMount() { |
|||
const { height } = this.props; |
|||
if (!height) { |
|||
const h = getAutoHeight(this.root); |
|||
// eslint-disable-next-line
|
|||
this.setState({ computedHeight: h }); |
|||
} |
|||
} |
|||
|
|||
handleRoot = node => { |
|||
this.root = node; |
|||
}; |
|||
|
|||
render() { |
|||
const { height } = this.props; |
|||
const { computedHeight } = this.state; |
|||
const h = height || computedHeight; |
|||
return ( |
|||
<div ref={this.handleRoot}>{h > 0 && <WrappedComponent {...this.props} height={h} />}</div> |
|||
); |
|||
} |
|||
}; |
|||
|
|||
export default autoHeight; |
|||
@ -1,3 +0,0 @@ |
|||
import * as BizChart from 'bizcharts'; |
|||
|
|||
export = BizChart; |
|||
@ -1,3 +0,0 @@ |
|||
import * as BizChart from 'bizcharts'; |
|||
|
|||
export default BizChart; |
|||
@ -1,20 +0,0 @@ |
|||
--- |
|||
order: 4 |
|||
title: 柱状图 |
|||
--- |
|||
|
|||
通过设置 `x`,`y` 属性,可以快速的构建出一个漂亮的柱状图,各种纬度的关系则是通过自定义的数据展现。 |
|||
|
|||
```jsx |
|||
import { Bar } from 'ant-design-pro/lib/Charts'; |
|||
|
|||
const salesData = []; |
|||
for (let i = 0; i < 12; i += 1) { |
|||
salesData.push({ |
|||
x: `${i + 1}月`, |
|||
y: Math.floor(Math.random() * 1000) + 200, |
|||
}); |
|||
} |
|||
|
|||
ReactDOM.render(<Bar height={200} title="销售额趋势" data={salesData} />, mountNode); |
|||
``` |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue