re-init
This commit is contained in:
parent
a360f6a627
commit
7ed52851e5
|
@ -0,0 +1,33 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
write: true,
|
||||||
|
prefix: '^',
|
||||||
|
plugin: 'autod-egg',
|
||||||
|
test: [
|
||||||
|
'test',
|
||||||
|
'benchmark',
|
||||||
|
'script',
|
||||||
|
'.roadhogrc.mock.js',
|
||||||
|
],
|
||||||
|
dep: [
|
||||||
|
'egg',
|
||||||
|
'egg-scripts',
|
||||||
|
],
|
||||||
|
devdep: [
|
||||||
|
'egg-ci',
|
||||||
|
'egg-bin',
|
||||||
|
'egg-mock',
|
||||||
|
'autod',
|
||||||
|
'autod-egg',
|
||||||
|
'eslint',
|
||||||
|
'eslint-config-egg',
|
||||||
|
'webstorm-disable-index',
|
||||||
|
],
|
||||||
|
exclude: [
|
||||||
|
'./test/fixtures',
|
||||||
|
'./dist',
|
||||||
|
'**/*.test.js',
|
||||||
|
'**/*.e2e.js',
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules/
|
||||||
|
.git/
|
|
@ -0,0 +1,3 @@
|
||||||
|
coverage
|
||||||
|
node_modules
|
||||||
|
app/public
|
|
@ -0,0 +1,18 @@
|
||||||
|
logs/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
node_modules
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
coverage/
|
||||||
|
.idea/
|
||||||
|
run/
|
||||||
|
.DS_Store
|
||||||
|
*.sw*
|
||||||
|
*.un~
|
||||||
|
|
||||||
|
app/public
|
||||||
|
config/manifest.json
|
||||||
|
app/web/.temp
|
||||||
|
.umi
|
||||||
|
docker/.node_modules/*
|
|
@ -0,0 +1,24 @@
|
||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
environment {
|
||||||
|
CI = 'true'
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Build') {
|
||||||
|
steps {
|
||||||
|
sh 'cnpm install'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Test') {
|
||||||
|
steps {
|
||||||
|
sh '/bin/true'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Deliver') {
|
||||||
|
steps {
|
||||||
|
sh '/bin/true'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
# 极限日志分析中心
|
||||||
|
|
||||||
|
INFINI Logging Center
|
||||||
|
|
||||||
|
|
||||||
|
## 开发说明
|
||||||
|
|
||||||
|
### 开发环境准备
|
||||||
|
|
||||||
|
确保已经安装好`nodejs`(版本大于等于 8.5.0)环境:
|
||||||
|
```sh
|
||||||
|
node -v
|
||||||
|
npm -v
|
||||||
|
```
|
||||||
|
|
||||||
|
在国内,你可以安装 `cnpm` 获得更快速、更安全的包管理体验。使用如下命令安装:
|
||||||
|
```sh
|
||||||
|
npm install -g cnpm --registry=https://registry.npm.taobao.org
|
||||||
|
```
|
||||||
|
|
||||||
|
### 下载项目依赖包
|
||||||
|
```
|
||||||
|
cnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 启动开发模式
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cnpm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
在浏览器中访问:[http://localhost:8000](http://localhost:8000)
|
||||||
|
|
||||||
|
|
||||||
|
### 构建和部署
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cnpm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
执行该命令后会生成最终的 HTML、CSS 和 JS 到 `dist` 目录下。它们是浏览器可以直接识别并运行的代码,这样你就可以将它们部署到你想要的服务器上了。
|
||||||
|
|
||||||
|
### 新增项目依赖包
|
||||||
|
```
|
||||||
|
cnpm install --save md5
|
||||||
|
```
|
|
@ -0,0 +1,23 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Controller = require('egg').Controller;
|
||||||
|
|
||||||
|
class HomeController extends Controller {
|
||||||
|
async index() {
|
||||||
|
await this.ctx.render('index.html');
|
||||||
|
}
|
||||||
|
|
||||||
|
async proxy() {
|
||||||
|
const ctx = this.ctx;
|
||||||
|
// use roadhog mock api first
|
||||||
|
const url = this.app.config.assets.url + ctx.path + '?' + ctx.querystring;
|
||||||
|
|
||||||
|
const res = await this.ctx.curl(url, {
|
||||||
|
method: this.ctx.method,
|
||||||
|
});
|
||||||
|
ctx.body = res.data;
|
||||||
|
ctx.status = res.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = HomeController;
|
|
@ -0,0 +1,10 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Egg.Application} app - egg application
|
||||||
|
*/
|
||||||
|
module.exports = app => {
|
||||||
|
const { router, controller } = app;
|
||||||
|
router.all('/api/*', controller.home.proxy);
|
||||||
|
router.get('*', controller.home.index);
|
||||||
|
};
|
|
@ -0,0 +1,27 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes"/>
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
|
||||||
|
<meta name="format-detection" content="telephone=no"/>
|
||||||
|
<meta name="format-detection" content="email=no"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
|
||||||
|
<title></title>
|
||||||
|
{{ helper.assets.getStyle('umi.css') | safe }}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="root"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.routerBase = '/';
|
||||||
|
window.resourceBaseUrl = '{{ helper.assets.resourceBase }}';
|
||||||
|
</script>
|
||||||
|
<script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.data-set-0.9.6/dist/data-set.min.js"></script>
|
||||||
|
{% if ctx.app.config.env === 'local' -%}
|
||||||
|
{{ helper.assets.getScript('umi.dll.js') | safe }}
|
||||||
|
{%- endif %}
|
||||||
|
{{ helper.assets.getScript('umi.js') | safe }}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,32 @@
|
||||||
|
module.exports = {
|
||||||
|
parser: 'babel-eslint',
|
||||||
|
extends: ['airbnb', 'prettier', 'plugin:compat/recommended'],
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
node: true,
|
||||||
|
es6: true,
|
||||||
|
mocha: true,
|
||||||
|
jest: true,
|
||||||
|
jasmine: true,
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
APP_TYPE: true,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'react/jsx-filename-extension': [1, { extensions: ['.js'] }],
|
||||||
|
'react/jsx-wrap-multilines': 0,
|
||||||
|
'react/prop-types': 0,
|
||||||
|
'react/forbid-prop-types': 0,
|
||||||
|
'react/jsx-one-expression-per-line': 0,
|
||||||
|
'import/no-unresolved': [2, { ignore: ['^@/', '^umi/'] }],
|
||||||
|
'import/no-extraneous-dependencies': [2, { optionalDependencies: true }],
|
||||||
|
'jsx-a11y/no-noninteractive-element-interactions': 0,
|
||||||
|
'jsx-a11y/click-events-have-key-events': 0,
|
||||||
|
'jsx-a11y/no-static-element-interactions': 0,
|
||||||
|
'jsx-a11y/anchor-is-valid': 0,
|
||||||
|
'linebreak-style': 0,
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
polyfills: ['fetch', 'promises', 'url'],
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,111 @@
|
||||||
|
// https://umijs.org/config/
|
||||||
|
import os from 'os';
|
||||||
|
import pageRoutes from './router.config';
|
||||||
|
import webpackPlugin from './plugin.config';
|
||||||
|
import defaultSettings from '../src/defaultSettings';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
// add for transfer to umi
|
||||||
|
plugins: [
|
||||||
|
[
|
||||||
|
'umi-plugin-react',
|
||||||
|
{
|
||||||
|
antd: true,
|
||||||
|
dva: {
|
||||||
|
hmr: true,
|
||||||
|
},
|
||||||
|
targets: {
|
||||||
|
ie: 11,
|
||||||
|
},
|
||||||
|
locale: {
|
||||||
|
enable: true, // default false
|
||||||
|
default: 'zh-CN', // default zh-CN
|
||||||
|
baseNavigator: true, // default true, when it is true, will use `navigator.language` overwrite default
|
||||||
|
},
|
||||||
|
dynamicImport: {
|
||||||
|
loadingComponent: './components/PageLoading/index',
|
||||||
|
},
|
||||||
|
...(!process.env.TEST && os.platform() === 'darwin'
|
||||||
|
? {
|
||||||
|
dll: {
|
||||||
|
include: ['dva', 'dva/router', 'dva/saga', 'dva/fetch'],
|
||||||
|
exclude: ['@babel/runtime'],
|
||||||
|
},
|
||||||
|
hardSource: true,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'umi-plugin-ga',
|
||||||
|
{
|
||||||
|
code: 'UA-72788897-6',
|
||||||
|
judge: () => process.env.APP_TYPE === 'site',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
targets: {
|
||||||
|
ie: 11,
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
APP_TYPE: process.env.APP_TYPE || '',
|
||||||
|
},
|
||||||
|
// 路由配置
|
||||||
|
routes: pageRoutes,
|
||||||
|
// Theme for antd
|
||||||
|
// https://ant.design/docs/react/customize-theme-cn
|
||||||
|
theme: {
|
||||||
|
'primary-color': defaultSettings.primaryColor,
|
||||||
|
},
|
||||||
|
externals: {
|
||||||
|
'@antv/data-set': 'DataSet',
|
||||||
|
},
|
||||||
|
// proxy: {
|
||||||
|
// '/server/api/': {
|
||||||
|
// target: 'https://preview.pro.ant.design/',
|
||||||
|
// changeOrigin: true,
|
||||||
|
// pathRewrite: { '^/server': '' },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
ignoreMomentLocale: true,
|
||||||
|
lessLoaderOptions: {
|
||||||
|
javascriptEnabled: true,
|
||||||
|
},
|
||||||
|
disableRedirectHoist: true,
|
||||||
|
cssLoaderOptions: {
|
||||||
|
modules: true,
|
||||||
|
getLocalIdent: (context, localIdentName, localName) => {
|
||||||
|
if (
|
||||||
|
context.resourcePath.includes('node_modules') ||
|
||||||
|
context.resourcePath.includes('ant.design.pro.less') ||
|
||||||
|
context.resourcePath.includes('global.less')
|
||||||
|
) {
|
||||||
|
return localName;
|
||||||
|
}
|
||||||
|
const match = context.resourcePath.match(/src(.*)/);
|
||||||
|
if (match && match[1]) {
|
||||||
|
const antdProPath = match[1].replace('.less', '');
|
||||||
|
const arr = antdProPath
|
||||||
|
.split('/')
|
||||||
|
.map(a => a.replace(/([A-Z])/g, '-$1'))
|
||||||
|
.map(a => a.toLowerCase());
|
||||||
|
return `antd-pro${arr.join('-')}-${localName}`.replace(/--/g, '-');
|
||||||
|
}
|
||||||
|
return localName;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
chainWebpack: webpackPlugin,
|
||||||
|
cssnano: {
|
||||||
|
mergeRules: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// extra configuration for egg
|
||||||
|
runtimePublicPath: true,
|
||||||
|
hash: true,
|
||||||
|
outputPath: '../public',
|
||||||
|
manifest: {
|
||||||
|
fileName: '../../config/manifest.json',
|
||||||
|
publicPath: '',
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Change theme plugin
|
||||||
|
|
||||||
|
import MergeLessPlugin from 'antd-pro-merge-less';
|
||||||
|
import AntDesignThemePlugin from 'antd-theme-webpack-plugin';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export default config => {
|
||||||
|
// 将所有 less 合并为一个供 themePlugin使用
|
||||||
|
const outFile = path.join(__dirname, '../.temp/ant-design-pro.less');
|
||||||
|
const stylesDir = path.join(__dirname, '../src/');
|
||||||
|
|
||||||
|
config.plugin('merge-less').use(MergeLessPlugin, [
|
||||||
|
{
|
||||||
|
stylesDir,
|
||||||
|
outFile,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
config.plugin('ant-design-theme').use(AntDesignThemePlugin, [
|
||||||
|
{
|
||||||
|
antDir: require.resolve('antd').replace('lib/index.js', ''),
|
||||||
|
stylesDir,
|
||||||
|
varFile: require.resolve('antd/lib/style/themes/default.less'),
|
||||||
|
mainLessFile: outFile, // themeVariables: ['@primary-color'],
|
||||||
|
indexFileName: 'index.html',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
|
@ -0,0 +1,264 @@
|
||||||
|
export default [
|
||||||
|
// user
|
||||||
|
{
|
||||||
|
path: '/user',
|
||||||
|
component: '../layouts/UserLayout',
|
||||||
|
routes: [
|
||||||
|
{ path: '/user', redirect: '/user/login' },
|
||||||
|
{ path: '/user/login', component: './User/Login' },
|
||||||
|
{ path: '/user/register', component: './User/Register' },
|
||||||
|
{ path: '/user/register-result', component: './User/RegisterResult' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// app
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: '../layouts/BasicLayout',
|
||||||
|
Routes: ['src/pages/Authorized'],
|
||||||
|
authority: ['admin', 'user'],
|
||||||
|
routes: [
|
||||||
|
// dashboard
|
||||||
|
{ path: '/', redirect: '/dashboard/analysis' },
|
||||||
|
{
|
||||||
|
path: '/dashboard',
|
||||||
|
name: 'dashboard',
|
||||||
|
icon: 'dashboard',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/dashboard/analysis',
|
||||||
|
name: 'analysis',
|
||||||
|
component: './Dashboard/Analysis',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/dashboard/monitor',
|
||||||
|
name: 'monitor',
|
||||||
|
component: './Dashboard/Monitor',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/dashboard/workplace',
|
||||||
|
name: 'workplace',
|
||||||
|
component: './Dashboard/Workplace',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// forms
|
||||||
|
{
|
||||||
|
path: '/form',
|
||||||
|
icon: 'form',
|
||||||
|
name: 'form',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/form/basic-form',
|
||||||
|
name: 'basicform',
|
||||||
|
component: './Forms/BasicForm',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/form/step-form',
|
||||||
|
name: 'stepform',
|
||||||
|
component: './Forms/StepForm',
|
||||||
|
hideChildrenInMenu: true,
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/form/step-form',
|
||||||
|
name: 'stepform',
|
||||||
|
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/advanced',
|
||||||
|
name: 'advanced',
|
||||||
|
authority: ['admin'],
|
||||||
|
component: './Profile/AdvancedProfile',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'result',
|
||||||
|
icon: 'check-circle-o',
|
||||||
|
path: '/result',
|
||||||
|
routes: [
|
||||||
|
// result
|
||||||
|
{
|
||||||
|
path: '/result/success',
|
||||||
|
name: 'success',
|
||||||
|
component: './Result/Success',
|
||||||
|
},
|
||||||
|
{ path: '/result/fail', name: 'fail', component: './Result/Error' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'exception',
|
||||||
|
icon: 'warning',
|
||||||
|
path: '/exception',
|
||||||
|
routes: [
|
||||||
|
// exception
|
||||||
|
{
|
||||||
|
path: '/exception/403',
|
||||||
|
name: 'not-permission',
|
||||||
|
component: './Exception/403',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/exception/404',
|
||||||
|
name: 'not-find',
|
||||||
|
component: './Exception/404',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/exception/500',
|
||||||
|
name: 'server-error',
|
||||||
|
component: './Exception/500',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/exception/trigger',
|
||||||
|
name: 'trigger',
|
||||||
|
hideInMenu: true,
|
||||||
|
component: './Exception/TriggerException',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'account',
|
||||||
|
icon: 'user',
|
||||||
|
path: '/account',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/account/center',
|
||||||
|
name: 'center',
|
||||||
|
component: './Account/Center/Center',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/account/center',
|
||||||
|
redirect: '/account/center/articles',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/account/center/articles',
|
||||||
|
component: './Account/Center/Articles',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/account/center/applications',
|
||||||
|
component: './Account/Center/Applications',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/account/center/projects',
|
||||||
|
component: './Account/Center/Projects',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/account/settings',
|
||||||
|
name: 'settings',
|
||||||
|
component: './Account/Settings/Info',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/account/settings',
|
||||||
|
redirect: '/account/settings/base',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/account/settings/base',
|
||||||
|
component: './Account/Settings/BaseView',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/account/settings/security',
|
||||||
|
component: './Account/Settings/SecurityView',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/account/settings/binding',
|
||||||
|
component: './Account/Settings/BindingView',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/account/settings/notification',
|
||||||
|
component: './Account/Settings/NotificationView',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: '404',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,336 @@
|
||||||
|
import mockjs from 'mockjs';
|
||||||
|
|
||||||
|
const titles = [
|
||||||
|
'Alipay',
|
||||||
|
'Angular',
|
||||||
|
'Ant Design',
|
||||||
|
'Ant Design Pro',
|
||||||
|
'Bootstrap',
|
||||||
|
'React',
|
||||||
|
'Vue',
|
||||||
|
'Webpack',
|
||||||
|
];
|
||||||
|
const avatars = [
|
||||||
|
'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', // Alipay
|
||||||
|
'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', // Angular
|
||||||
|
'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', // Ant Design
|
||||||
|
'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', // Ant Design Pro
|
||||||
|
'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', // Bootstrap
|
||||||
|
'https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png', // React
|
||||||
|
'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png', // Vue
|
||||||
|
'https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png', // Webpack
|
||||||
|
];
|
||||||
|
|
||||||
|
const avatars2 = [
|
||||||
|
'https://gw.alipayobjects.com/zos/rmsportal/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,
|
||||||
|
};
|
|
@ -0,0 +1,196 @@
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
// mock data
|
||||||
|
const visitData = [];
|
||||||
|
const beginDay = new Date().getTime();
|
||||||
|
|
||||||
|
const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5];
|
||||||
|
for (let i = 0; i < fakeY.length; i += 1) {
|
||||||
|
visitData.push({
|
||||||
|
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
|
||||||
|
y: fakeY[i],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const visitData2 = [];
|
||||||
|
const fakeY2 = [1, 6, 4, 8, 3, 7, 2];
|
||||||
|
for (let i = 0; i < fakeY2.length; i += 1) {
|
||||||
|
visitData2.push({
|
||||||
|
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
|
||||||
|
y: fakeY2[i],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const salesData = [];
|
||||||
|
for (let i = 0; i < 12; i += 1) {
|
||||||
|
salesData.push({
|
||||||
|
x: `${i + 1}月`,
|
||||||
|
y: Math.floor(Math.random() * 1000) + 200,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const searchData = [];
|
||||||
|
for (let i = 0; i < 50; i += 1) {
|
||||||
|
searchData.push({
|
||||||
|
index: i + 1,
|
||||||
|
keyword: `搜索关键词-${i}`,
|
||||||
|
count: Math.floor(Math.random() * 1000),
|
||||||
|
range: Math.floor(Math.random() * 100),
|
||||||
|
status: Math.floor((Math.random() * 10) % 2),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const salesTypeData = [
|
||||||
|
{
|
||||||
|
x: '家用电器',
|
||||||
|
y: 4544,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '食用酒水',
|
||||||
|
y: 3321,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '个护健康',
|
||||||
|
y: 3113,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '服饰箱包',
|
||||||
|
y: 2341,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '母婴产品',
|
||||||
|
y: 1231,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '其他',
|
||||||
|
y: 1231,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const salesTypeDataOnline = [
|
||||||
|
{
|
||||||
|
x: '家用电器',
|
||||||
|
y: 244,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '食用酒水',
|
||||||
|
y: 321,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '个护健康',
|
||||||
|
y: 311,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '服饰箱包',
|
||||||
|
y: 41,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '母婴产品',
|
||||||
|
y: 121,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '其他',
|
||||||
|
y: 111,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const salesTypeDataOffline = [
|
||||||
|
{
|
||||||
|
x: '家用电器',
|
||||||
|
y: 99,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '食用酒水',
|
||||||
|
y: 188,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '个护健康',
|
||||||
|
y: 344,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '服饰箱包',
|
||||||
|
y: 255,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '其他',
|
||||||
|
y: 65,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const offlineData = [];
|
||||||
|
for (let i = 0; i < 10; i += 1) {
|
||||||
|
offlineData.push({
|
||||||
|
name: `Stores ${i}`,
|
||||||
|
cvr: Math.ceil(Math.random() * 9) / 10,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const offlineChartData = [];
|
||||||
|
for (let i = 0; i < 20; i += 1) {
|
||||||
|
offlineChartData.push({
|
||||||
|
x: new Date().getTime() + 1000 * 60 * 30 * i,
|
||||||
|
y1: Math.floor(Math.random() * 100) + 10,
|
||||||
|
y2: Math.floor(Math.random() * 100) + 10,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const radarOriginData = [
|
||||||
|
{
|
||||||
|
name: '个人',
|
||||||
|
ref: 10,
|
||||||
|
koubei: 8,
|
||||||
|
output: 4,
|
||||||
|
contribute: 5,
|
||||||
|
hot: 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '团队',
|
||||||
|
ref: 3,
|
||||||
|
koubei: 9,
|
||||||
|
output: 6,
|
||||||
|
contribute: 3,
|
||||||
|
hot: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '部门',
|
||||||
|
ref: 4,
|
||||||
|
koubei: 1,
|
||||||
|
output: 6,
|
||||||
|
contribute: 5,
|
||||||
|
hot: 7,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const radarData = [];
|
||||||
|
const radarTitleMap = {
|
||||||
|
ref: '引用',
|
||||||
|
koubei: '口碑',
|
||||||
|
output: '产量',
|
||||||
|
contribute: '贡献',
|
||||||
|
hot: '热度',
|
||||||
|
};
|
||||||
|
radarOriginData.forEach(item => {
|
||||||
|
Object.keys(item).forEach(key => {
|
||||||
|
if (key !== 'name') {
|
||||||
|
radarData.push({
|
||||||
|
name: item.name,
|
||||||
|
label: radarTitleMap[key],
|
||||||
|
value: item[key],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const getFakeChartData = {
|
||||||
|
visitData,
|
||||||
|
visitData2,
|
||||||
|
salesData,
|
||||||
|
searchData,
|
||||||
|
offlineData,
|
||||||
|
offlineChartData,
|
||||||
|
salesTypeData,
|
||||||
|
salesTypeDataOnline,
|
||||||
|
salesTypeDataOffline,
|
||||||
|
radarData,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
'GET /api/fake_chart_data': getFakeChartData,
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import city from './geographic/city.json';
|
||||||
|
import province from './geographic/province.json';
|
||||||
|
|
||||||
|
function getProvince(req, res) {
|
||||||
|
return res.json(province);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCity(req, res) {
|
||||||
|
return res.json(city[req.params.province]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
'GET /api/geographic/province': getProvince,
|
||||||
|
'GET /api/geographic/city/:province': getCity,
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,138 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "北京市",
|
||||||
|
"id": "110000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "天津市",
|
||||||
|
"id": "120000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "河北省",
|
||||||
|
"id": "130000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "山西省",
|
||||||
|
"id": "140000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "内蒙古自治区",
|
||||||
|
"id": "150000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "辽宁省",
|
||||||
|
"id": "210000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "吉林省",
|
||||||
|
"id": "220000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "黑龙江省",
|
||||||
|
"id": "230000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "上海市",
|
||||||
|
"id": "310000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "江苏省",
|
||||||
|
"id": "320000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "浙江省",
|
||||||
|
"id": "330000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "安徽省",
|
||||||
|
"id": "340000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "福建省",
|
||||||
|
"id": "350000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "江西省",
|
||||||
|
"id": "360000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "山东省",
|
||||||
|
"id": "370000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "河南省",
|
||||||
|
"id": "410000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "湖北省",
|
||||||
|
"id": "420000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "湖南省",
|
||||||
|
"id": "430000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "广东省",
|
||||||
|
"id": "440000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "广西壮族自治区",
|
||||||
|
"id": "450000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "海南省",
|
||||||
|
"id": "460000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "重庆市",
|
||||||
|
"id": "500000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "四川省",
|
||||||
|
"id": "510000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "贵州省",
|
||||||
|
"id": "520000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "云南省",
|
||||||
|
"id": "530000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "西藏自治区",
|
||||||
|
"id": "540000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "陕西省",
|
||||||
|
"id": "610000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "甘肃省",
|
||||||
|
"id": "620000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "青海省",
|
||||||
|
"id": "630000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "宁夏回族自治区",
|
||||||
|
"id": "640000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "新疆维吾尔自治区",
|
||||||
|
"id": "650000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "台湾省",
|
||||||
|
"id": "710000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "香港特别行政区",
|
||||||
|
"id": "810000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "澳门特别行政区",
|
||||||
|
"id": "820000"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,99 @@
|
||||||
|
const getNotices = (req, res) =>
|
||||||
|
res.json([
|
||||||
|
{
|
||||||
|
id: '000000001',
|
||||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
|
||||||
|
title: '你收到了 14 份新周报',
|
||||||
|
datetime: '2017-08-09',
|
||||||
|
type: 'notification',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '000000002',
|
||||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
|
||||||
|
title: '你推荐的 曲妮妮 已通过第三轮面试',
|
||||||
|
datetime: '2017-08-08',
|
||||||
|
type: 'notification',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '000000003',
|
||||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
|
||||||
|
title: '这种模板可以区分多种通知类型',
|
||||||
|
datetime: '2017-08-07',
|
||||||
|
read: true,
|
||||||
|
type: 'notification',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '000000004',
|
||||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
|
||||||
|
title: '左侧图标用于区分不同的类型',
|
||||||
|
datetime: '2017-08-07',
|
||||||
|
type: 'notification',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '000000005',
|
||||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
|
||||||
|
title: '内容不要超过两行字,超出时自动截断',
|
||||||
|
datetime: '2017-08-07',
|
||||||
|
type: 'notification',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '000000006',
|
||||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||||
|
title: '曲丽丽 评论了你',
|
||||||
|
description: '描述信息描述信息描述信息',
|
||||||
|
datetime: '2017-08-07',
|
||||||
|
type: 'message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '000000007',
|
||||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||||
|
title: '朱偏右 回复了你',
|
||||||
|
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
|
||||||
|
datetime: '2017-08-07',
|
||||||
|
type: 'message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '000000008',
|
||||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||||
|
title: '标题',
|
||||||
|
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
|
||||||
|
datetime: '2017-08-07',
|
||||||
|
type: 'message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '000000009',
|
||||||
|
title: '任务名称',
|
||||||
|
description: '任务需要在 2017-01-12 20:00 前启动',
|
||||||
|
extra: '未开始',
|
||||||
|
status: 'todo',
|
||||||
|
type: 'event',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '000000010',
|
||||||
|
title: '第三方紧急代码变更',
|
||||||
|
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
|
||||||
|
extra: '马上到期',
|
||||||
|
status: 'urgent',
|
||||||
|
type: 'event',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '000000011',
|
||||||
|
title: '信息安全考试',
|
||||||
|
description: '指派竹尔于 2017-01-09 前完成更新并发布',
|
||||||
|
extra: '已耗时 8 天',
|
||||||
|
status: 'doing',
|
||||||
|
type: 'event',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '000000012',
|
||||||
|
title: 'ABCD 版本发布',
|
||||||
|
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
|
||||||
|
extra: '进行中',
|
||||||
|
status: 'processing',
|
||||||
|
type: 'event',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
'GET /api/notices': getNotices,
|
||||||
|
};
|
|
@ -0,0 +1,158 @@
|
||||||
|
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 getProfileBasicData = {
|
||||||
|
basicGoods,
|
||||||
|
basicProgress,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getProfileAdvancedData = {
|
||||||
|
advancedOperation1,
|
||||||
|
advancedOperation2,
|
||||||
|
advancedOperation3,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
'GET /api/profile/advanced': getProfileAdvancedData,
|
||||||
|
'GET /api/profile/basic': getProfileBasicData,
|
||||||
|
};
|
|
@ -0,0 +1,138 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
list: tableListDataSource,
|
||||||
|
pagination: {
|
||||||
|
total: tableListDataSource.length,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return res.json(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
'GET /api/rule': getRule,
|
||||||
|
'POST /api/rule': postRule,
|
||||||
|
};
|
|
@ -0,0 +1,137 @@
|
||||||
|
// 代码中会兼容本地 service mock 以及部署站点的静态数据
|
||||||
|
export default {
|
||||||
|
// 支持值为 Object 和 Array
|
||||||
|
'GET /api/currentUser': {
|
||||||
|
name: 'Serati Ma',
|
||||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
|
||||||
|
userid: '00000001',
|
||||||
|
email: 'antdesign@alipay.com',
|
||||||
|
signature: '海纳百川,有容乃大',
|
||||||
|
title: '交互专家',
|
||||||
|
group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
key: '0',
|
||||||
|
label: '很有想法的',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
label: '专注设计',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
label: '辣~',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '3',
|
||||||
|
label: '大长腿',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '4',
|
||||||
|
label: '川妹子',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '5',
|
||||||
|
label: '海纳百川',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
notifyCount: 12,
|
||||||
|
country: 'China',
|
||||||
|
geographic: {
|
||||||
|
province: {
|
||||||
|
label: '浙江省',
|
||||||
|
key: '330000',
|
||||||
|
},
|
||||||
|
city: {
|
||||||
|
label: '杭州市',
|
||||||
|
key: '330100',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
address: '西湖区工专路 77 号',
|
||||||
|
phone: '0752-268888888',
|
||||||
|
},
|
||||||
|
// GET POST 可省略
|
||||||
|
'GET /api/users': [
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
name: 'John Brown',
|
||||||
|
age: 32,
|
||||||
|
address: 'New York No. 1 Lake Park',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
name: 'Jim Green',
|
||||||
|
age: 42,
|
||||||
|
address: 'London No. 1 Lake Park',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '3',
|
||||||
|
name: 'Joe Black',
|
||||||
|
age: 32,
|
||||||
|
address: 'Sidney No. 1 Lake Park',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'POST /api/login/account': (req, res) => {
|
||||||
|
const { password, userName, type } = req.body;
|
||||||
|
if (password === '888888' && userName === 'admin') {
|
||||||
|
res.send({
|
||||||
|
status: 'ok',
|
||||||
|
type,
|
||||||
|
currentAuthority: 'admin',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (password === '123456' && userName === 'user') {
|
||||||
|
res.send({
|
||||||
|
status: 'ok',
|
||||||
|
type,
|
||||||
|
currentAuthority: 'user',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.send({
|
||||||
|
status: 'error',
|
||||||
|
type,
|
||||||
|
currentAuthority: 'guest',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'POST /api/register': (req, res) => {
|
||||||
|
res.send({ status: 'ok', currentAuthority: 'user' });
|
||||||
|
},
|
||||||
|
'GET /api/500': (req, res) => {
|
||||||
|
res.status(500).send({
|
||||||
|
timestamp: 1513932555104,
|
||||||
|
status: 500,
|
||||||
|
error: 'error',
|
||||||
|
message: 'error',
|
||||||
|
path: '/base/category/list',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'GET /api/404': (req, res) => {
|
||||||
|
res.status(404).send({
|
||||||
|
timestamp: 1513932643431,
|
||||||
|
status: 404,
|
||||||
|
error: 'Not Found',
|
||||||
|
message: 'No message available',
|
||||||
|
path: '/base/category/list/2121212',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'GET /api/403': (req, res) => {
|
||||||
|
res.status(403).send({
|
||||||
|
timestamp: 1513932555104,
|
||||||
|
status: 403,
|
||||||
|
error: 'Unauthorized',
|
||||||
|
message: 'Unauthorized',
|
||||||
|
path: '/base/category/list',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'GET /api/401': (req, res) => {
|
||||||
|
res.status(401).send({
|
||||||
|
timestamp: 1513932555104,
|
||||||
|
status: 401,
|
||||||
|
error: 'Unauthorized',
|
||||||
|
message: 'Unauthorized',
|
||||||
|
path: '/base/category/list',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 90 KiB |
|
@ -0,0 +1,93 @@
|
||||||
|
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.requestRef = requestAnimationFrame(() => {
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
activeData: getActiveData(),
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.loopData();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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 className={styles.activeChartGrid}>
|
||||||
|
<p>{[...activeData].sort()[activeData.length - 1].y + 200} 亿元</p>
|
||||||
|
<p>{[...activeData].sort()[Math.floor(activeData.length / 2)].y} 亿元</p>
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
.activeChart {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.activeChartGrid {
|
||||||
|
p {
|
||||||
|
position: absolute;
|
||||||
|
top: 80px;
|
||||||
|
}
|
||||||
|
p:last-child {
|
||||||
|
top: 115px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.activeChartLegend {
|
||||||
|
position: relative;
|
||||||
|
font-size: 0;
|
||||||
|
margin-top: 8px;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
width: 33.33%;
|
||||||
|
}
|
||||||
|
span:first-child {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
span:last-child {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import React from 'react';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Avatar } from 'antd';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const ArticleListContent = ({ data: { content, updatedAt, avatar, owner, href } }) => (
|
||||||
|
<div className={styles.listContent}>
|
||||||
|
<div className={styles.description}>{content}</div>
|
||||||
|
<div className={styles.extra}>
|
||||||
|
<Avatar src={avatar} size="small" />
|
||||||
|
<a href={href}>{owner}</a> 发布在 <a href={href}>{href}</a>
|
||||||
|
<em>{moment(updatedAt).format('YYYY-MM-DD HH:mm')}</em>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ArticleListContent;
|
|
@ -0,0 +1,38 @@
|
||||||
|
@import '~antd/lib/style/themes/default.less';
|
||||||
|
|
||||||
|
.listContent {
|
||||||
|
.description {
|
||||||
|
line-height: 22px;
|
||||||
|
max-width: 720px;
|
||||||
|
}
|
||||||
|
.extra {
|
||||||
|
color: @text-color-secondary;
|
||||||
|
margin-top: 16px;
|
||||||
|
line-height: 22px;
|
||||||
|
& > :global(.ant-avatar) {
|
||||||
|
vertical-align: top;
|
||||||
|
margin-right: 8px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
& > em {
|
||||||
|
color: @disabled-color;
|
||||||
|
font-style: normal;
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: @screen-xs) {
|
||||||
|
.listContent {
|
||||||
|
.extra {
|
||||||
|
& > em {
|
||||||
|
display: block;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import CheckPermissions from './CheckPermissions';
|
||||||
|
|
||||||
|
const Authorized = ({ children, authority, noMatch = null }) => {
|
||||||
|
const childrenRender = typeof children === 'undefined' ? null : children;
|
||||||
|
return CheckPermissions(authority, childrenRender, noMatch);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Authorized;
|
|
@ -0,0 +1,15 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Route, Redirect } from 'react-router-dom';
|
||||||
|
import Authorized from './Authorized';
|
||||||
|
|
||||||
|
// TODO: umi只会返回render和rest
|
||||||
|
const AuthorizedRoute = ({ component: Component, render, authority, redirectPath, ...rest }) => (
|
||||||
|
<Authorized
|
||||||
|
authority={authority}
|
||||||
|
noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />}
|
||||||
|
>
|
||||||
|
<Route {...rest} render={props => (Component ? <Component {...props} /> : render(props))} />
|
||||||
|
</Authorized>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default AuthorizedRoute;
|
|
@ -0,0 +1,88 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PromiseRender from './PromiseRender';
|
||||||
|
import { CURRENT } from './renderAuthorize';
|
||||||
|
|
||||||
|
function isPromise(obj) {
|
||||||
|
return (
|
||||||
|
!!obj &&
|
||||||
|
(typeof obj === 'object' || typeof obj === 'function') &&
|
||||||
|
typeof obj.then === 'function'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用权限检查方法
|
||||||
|
* Common check permissions method
|
||||||
|
* @param { 权限判定 Permission judgment type string |array | Promise | Function } authority
|
||||||
|
* @param { 你的权限 Your permission description type:string} currentAuthority
|
||||||
|
* @param { 通过的组件 Passing components } target
|
||||||
|
* @param { 未通过的组件 no pass components } Exception
|
||||||
|
*/
|
||||||
|
const checkPermissions = (authority, currentAuthority, target, Exception) => {
|
||||||
|
// 没有判定权限.默认查看所有
|
||||||
|
// Retirement authority, return target;
|
||||||
|
if (!authority) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
// 数组处理
|
||||||
|
if (Array.isArray(authority)) {
|
||||||
|
if (authority.indexOf(currentAuthority) >= 0) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
if (Array.isArray(currentAuthority)) {
|
||||||
|
for (let i = 0; i < currentAuthority.length; i += 1) {
|
||||||
|
const element = currentAuthority[i];
|
||||||
|
if (authority.indexOf(element) >= 0) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
// string 处理
|
||||||
|
if (typeof authority === 'string') {
|
||||||
|
if (authority === currentAuthority) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
if (Array.isArray(currentAuthority)) {
|
||||||
|
for (let i = 0; i < currentAuthority.length; i += 1) {
|
||||||
|
const element = currentAuthority[i];
|
||||||
|
if (authority === element) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Promise 处理
|
||||||
|
if (isPromise(authority)) {
|
||||||
|
return <PromiseRender ok={target} error={Exception} promise={authority} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function 处理
|
||||||
|
if (typeof authority === 'function') {
|
||||||
|
try {
|
||||||
|
const bool = authority(currentAuthority);
|
||||||
|
// 函数执行后返回值是 Promise
|
||||||
|
if (isPromise(bool)) {
|
||||||
|
return <PromiseRender ok={target} error={Exception} promise={bool} />;
|
||||||
|
}
|
||||||
|
if (bool) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
return Exception;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('unsupported parameters');
|
||||||
|
};
|
||||||
|
|
||||||
|
export { checkPermissions };
|
||||||
|
|
||||||
|
const check = (authority, target, Exception) =>
|
||||||
|
checkPermissions(authority, CURRENT, target, Exception);
|
||||||
|
|
||||||
|
export default check;
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { checkPermissions } from './CheckPermissions';
|
||||||
|
|
||||||
|
const target = 'ok';
|
||||||
|
const error = 'error';
|
||||||
|
|
||||||
|
describe('test CheckPermissions', () => {
|
||||||
|
it('Correct string permission authentication', () => {
|
||||||
|
expect(checkPermissions('user', 'user', target, error)).toEqual('ok');
|
||||||
|
});
|
||||||
|
it('Correct string permission authentication', () => {
|
||||||
|
expect(checkPermissions('user', 'NULL', target, error)).toEqual('error');
|
||||||
|
});
|
||||||
|
it('authority is undefined , return ok', () => {
|
||||||
|
expect(checkPermissions(null, 'NULL', target, error)).toEqual('ok');
|
||||||
|
});
|
||||||
|
it('currentAuthority is undefined , return error', () => {
|
||||||
|
expect(checkPermissions('admin', null, target, error)).toEqual('error');
|
||||||
|
});
|
||||||
|
it('Wrong string permission authentication', () => {
|
||||||
|
expect(checkPermissions('admin', 'user', target, error)).toEqual('error');
|
||||||
|
});
|
||||||
|
it('Correct Array permission authentication', () => {
|
||||||
|
expect(checkPermissions(['user', 'admin'], 'user', target, error)).toEqual('ok');
|
||||||
|
});
|
||||||
|
it('Wrong Array permission authentication,currentAuthority error', () => {
|
||||||
|
expect(checkPermissions(['user', 'admin'], 'user,admin', target, error)).toEqual('error');
|
||||||
|
});
|
||||||
|
it('Wrong Array permission authentication', () => {
|
||||||
|
expect(checkPermissions(['user', 'admin'], 'guest', target, error)).toEqual('error');
|
||||||
|
});
|
||||||
|
it('Wrong Function permission authentication', () => {
|
||||||
|
expect(checkPermissions(() => false, 'guest', target, error)).toEqual('error');
|
||||||
|
});
|
||||||
|
it('Correct Function permission authentication', () => {
|
||||||
|
expect(checkPermissions(() => true, 'guest', target, error)).toEqual('ok');
|
||||||
|
});
|
||||||
|
it('authority is string, currentAuthority is array, return ok', () => {
|
||||||
|
expect(checkPermissions('user', ['user'], target, error)).toEqual('ok');
|
||||||
|
});
|
||||||
|
it('authority is string, currentAuthority is array, return ok', () => {
|
||||||
|
expect(checkPermissions('user', ['user', 'admin'], target, error)).toEqual('ok');
|
||||||
|
});
|
||||||
|
it('authority is array, currentAuthority is array, return ok', () => {
|
||||||
|
expect(checkPermissions(['user', 'admin'], ['user', 'admin'], target, error)).toEqual('ok');
|
||||||
|
});
|
||||||
|
it('Wrong Function permission authentication', () => {
|
||||||
|
expect(checkPermissions(() => false, ['user'], target, error)).toEqual('error');
|
||||||
|
});
|
||||||
|
it('Correct Function permission authentication', () => {
|
||||||
|
expect(checkPermissions(() => true, ['user'], target, error)).toEqual('ok');
|
||||||
|
});
|
||||||
|
it('authority is undefined , return ok', () => {
|
||||||
|
expect(checkPermissions(null, ['user'], target, error)).toEqual('ok');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,65 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Spin } from 'antd';
|
||||||
|
|
||||||
|
export default class PromiseRender extends React.PureComponent {
|
||||||
|
state = {
|
||||||
|
component: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setRenderComponent(this.props);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(nextProps) {
|
||||||
|
// new Props enter
|
||||||
|
this.setRenderComponent(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set render Component : ok or error
|
||||||
|
setRenderComponent(props) {
|
||||||
|
const ok = this.checkIsInstantiation(props.ok);
|
||||||
|
const error = this.checkIsInstantiation(props.error);
|
||||||
|
props.promise
|
||||||
|
.then(() => {
|
||||||
|
this.setState({
|
||||||
|
component: ok,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.setState({
|
||||||
|
component: error,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine whether the incoming component has been instantiated
|
||||||
|
// AuthorizedRoute is already instantiated
|
||||||
|
// Authorized render is already instantiated, children is no instantiated
|
||||||
|
// Secured is not instantiated
|
||||||
|
checkIsInstantiation = target => {
|
||||||
|
if (!React.isValidElement(target)) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
return () => target;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { component: Component } = this.state;
|
||||||
|
const { ok, error, promise, ...rest } = this.props;
|
||||||
|
return Component ? (
|
||||||
|
<Component {...rest} />
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
margin: 'auto',
|
||||||
|
paddingTop: 50,
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Spin size="large" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Exception from '../Exception';
|
||||||
|
import CheckPermissions from './CheckPermissions';
|
||||||
|
/**
|
||||||
|
* 默认不能访问任何页面
|
||||||
|
* default is "NULL"
|
||||||
|
*/
|
||||||
|
const Exception403 = () => <Exception type="403" />;
|
||||||
|
|
||||||
|
// Determine whether the incoming component has been instantiated
|
||||||
|
// AuthorizedRoute is already instantiated
|
||||||
|
// Authorized render is already instantiated, children is no instantiated
|
||||||
|
// Secured is not instantiated
|
||||||
|
const checkIsInstantiation = target => {
|
||||||
|
if (!React.isValidElement(target)) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
return () => target;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于判断是否拥有权限访问此view权限
|
||||||
|
* authority 支持传入 string, function:()=>boolean|Promise
|
||||||
|
* e.g. 'user' 只有user用户能访问
|
||||||
|
* e.g. 'user,admin' user和 admin 都能访问
|
||||||
|
* e.g. ()=>boolean 返回true能访问,返回false不能访问
|
||||||
|
* e.g. Promise then 能访问 catch不能访问
|
||||||
|
* e.g. authority support incoming string, function: () => boolean | Promise
|
||||||
|
* e.g. 'user' only user user can access
|
||||||
|
* e.g. 'user, admin' user and admin can access
|
||||||
|
* e.g. () => boolean true to be able to visit, return false can not be accessed
|
||||||
|
* e.g. Promise then can not access the visit to catch
|
||||||
|
* @param {string | function | Promise} authority
|
||||||
|
* @param {ReactNode} error 非必需参数
|
||||||
|
*/
|
||||||
|
const authorize = (authority, error) => {
|
||||||
|
/**
|
||||||
|
* conversion into a class
|
||||||
|
* 防止传入字符串时找不到staticContext造成报错
|
||||||
|
* String parameters can cause staticContext not found error
|
||||||
|
*/
|
||||||
|
let classError = false;
|
||||||
|
if (error) {
|
||||||
|
classError = () => error;
|
||||||
|
}
|
||||||
|
if (!authority) {
|
||||||
|
throw new Error('authority is required');
|
||||||
|
}
|
||||||
|
return function decideAuthority(target) {
|
||||||
|
const component = CheckPermissions(authority, target, classError || Exception403);
|
||||||
|
return checkIsInstantiation(component);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default authorize;
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
order: 1
|
||||||
|
title:
|
||||||
|
zh-CN: 使用数组作为参数
|
||||||
|
en-US: Use Array as a parameter
|
||||||
|
---
|
||||||
|
|
||||||
|
Use Array as a parameter
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
|
||||||
|
import { Alert } from 'antd';
|
||||||
|
|
||||||
|
const Authorized = RenderAuthorized('user');
|
||||||
|
const noMatch = <Alert message="No permission." type="error" showIcon />;
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Authorized authority={['user', 'admin']} noMatch={noMatch}>
|
||||||
|
<Alert message="Use Array as a parameter passed!" type="success" showIcon />
|
||||||
|
</Authorized>,
|
||||||
|
mountNode,
|
||||||
|
);
|
||||||
|
```
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
order: 2
|
||||||
|
title:
|
||||||
|
zh-CN: 使用方法作为参数
|
||||||
|
en-US: Use function as a parameter
|
||||||
|
---
|
||||||
|
|
||||||
|
Use Function as a parameter
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
|
||||||
|
import { Alert } from 'antd';
|
||||||
|
|
||||||
|
const Authorized = RenderAuthorized('user');
|
||||||
|
const noMatch = <Alert message="No permission." type="error" showIcon />;
|
||||||
|
|
||||||
|
const havePermission = () => {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Authorized authority={havePermission} noMatch={noMatch}>
|
||||||
|
<Alert
|
||||||
|
message="Use Function as a parameter passed!"
|
||||||
|
type="success"
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
</Authorized>,
|
||||||
|
mountNode,
|
||||||
|
);
|
||||||
|
```
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
order: 0
|
||||||
|
title:
|
||||||
|
zh-CN: 基本使用
|
||||||
|
en-US: Basic use
|
||||||
|
---
|
||||||
|
|
||||||
|
Basic use
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
|
||||||
|
import { Alert } from 'antd';
|
||||||
|
|
||||||
|
const Authorized = RenderAuthorized('user');
|
||||||
|
const noMatch = <Alert message="No permission." type="error" showIcon />;
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<div>
|
||||||
|
<Authorized authority="admin" noMatch={noMatch}>
|
||||||
|
<Alert message="user Passed!" type="success" showIcon />
|
||||||
|
</Authorized>
|
||||||
|
</div>,
|
||||||
|
mountNode,
|
||||||
|
);
|
||||||
|
```
|
|
@ -0,0 +1,28 @@
|
||||||
|
---
|
||||||
|
order: 3
|
||||||
|
title:
|
||||||
|
zh-CN: 注解基本使用
|
||||||
|
en-US: Basic use secured
|
||||||
|
---
|
||||||
|
|
||||||
|
secured demo used
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
|
||||||
|
import { Alert } from 'antd';
|
||||||
|
|
||||||
|
const { Secured } = RenderAuthorized('user');
|
||||||
|
|
||||||
|
@Secured('admin')
|
||||||
|
class TestSecuredString extends React.Component {
|
||||||
|
render() {
|
||||||
|
<Alert message="user Passed!" type="success" showIcon />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReactDOM.render(
|
||||||
|
<div>
|
||||||
|
<TestSecuredString />
|
||||||
|
</div>,
|
||||||
|
mountNode,
|
||||||
|
);
|
||||||
|
```
|
|
@ -0,0 +1,43 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { RouteProps } from 'react-router';
|
||||||
|
|
||||||
|
type authorityFN = (currentAuthority?: string) => boolean;
|
||||||
|
|
||||||
|
type authority = string | Array<string> | authorityFN | Promise<any>;
|
||||||
|
|
||||||
|
export type IReactComponent<P = any> =
|
||||||
|
| React.StatelessComponent<P>
|
||||||
|
| React.ComponentClass<P>
|
||||||
|
| React.ClassicComponentClass<P>;
|
||||||
|
|
||||||
|
interface Secured {
|
||||||
|
(authority: authority, error?: React.ReactNode): <T extends IReactComponent>(target: T) => T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthorizedRouteProps extends RouteProps {
|
||||||
|
authority: authority;
|
||||||
|
}
|
||||||
|
export class AuthorizedRoute extends React.Component<AuthorizedRouteProps, any> {}
|
||||||
|
|
||||||
|
interface check {
|
||||||
|
<T extends IReactComponent, S extends IReactComponent>(
|
||||||
|
authority: authority,
|
||||||
|
target: T,
|
||||||
|
Exception: S
|
||||||
|
): T | S;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthorizedProps {
|
||||||
|
authority: authority;
|
||||||
|
noMatch?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Authorized extends React.Component<AuthorizedProps, any> {
|
||||||
|
static Secured: Secured;
|
||||||
|
static AuthorizedRoute: typeof AuthorizedRoute;
|
||||||
|
static check: check;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare function renderAuthorize(currentAuthority: string): typeof Authorized;
|
||||||
|
|
||||||
|
export default renderAuthorize;
|
|
@ -0,0 +1,11 @@
|
||||||
|
import Authorized from './Authorized';
|
||||||
|
import AuthorizedRoute from './AuthorizedRoute';
|
||||||
|
import Secured from './Secured';
|
||||||
|
import check from './CheckPermissions';
|
||||||
|
import renderAuthorize from './renderAuthorize';
|
||||||
|
|
||||||
|
Authorized.Secured = Secured;
|
||||||
|
Authorized.AuthorizedRoute = AuthorizedRoute;
|
||||||
|
Authorized.check = check;
|
||||||
|
|
||||||
|
export default renderAuthorize(Authorized);
|
|
@ -0,0 +1,58 @@
|
||||||
|
---
|
||||||
|
title:
|
||||||
|
en-US: Authorized
|
||||||
|
zh-CN: 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 | - |
|
|
@ -0,0 +1,25 @@
|
||||||
|
/* eslint-disable import/no-mutable-exports */
|
||||||
|
let CURRENT = 'NULL';
|
||||||
|
/**
|
||||||
|
* use authority or getAuthority
|
||||||
|
* @param {string|()=>String} currentAuthority
|
||||||
|
*/
|
||||||
|
const renderAuthorize = Authorized => currentAuthority => {
|
||||||
|
if (currentAuthority) {
|
||||||
|
if (typeof currentAuthority === 'function') {
|
||||||
|
CURRENT = currentAuthority();
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
Object.prototype.toString.call(currentAuthority) === '[object String]' ||
|
||||||
|
Array.isArray(currentAuthority)
|
||||||
|
) {
|
||||||
|
CURRENT = currentAuthority;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CURRENT = 'NULL';
|
||||||
|
}
|
||||||
|
return Authorized;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { CURRENT };
|
||||||
|
export default Authorized => renderAuthorize(Authorized);
|
|
@ -0,0 +1,10 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
export interface IAvatarItemProps {
|
||||||
|
tips: React.ReactNode;
|
||||||
|
src: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AvatarItem extends React.Component<IAvatarItemProps, any> {
|
||||||
|
constructor(props: IAvatarItemProps);
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
order: 0
|
||||||
|
title:
|
||||||
|
zh-CN: 基础样例
|
||||||
|
en-US: Basic Usage
|
||||||
|
---
|
||||||
|
|
||||||
|
Simplest of usage.
|
||||||
|
|
||||||
|
````jsx
|
||||||
|
import AvatarList from 'ant-design-pro/lib/AvatarList';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<AvatarList size="mini">
|
||||||
|
<AvatarList.Item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
|
||||||
|
<AvatarList.Item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
|
||||||
|
<AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||||
|
</AvatarList>
|
||||||
|
, mountNode);
|
||||||
|
````
|
|
@ -0,0 +1,12 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import AvatarItem from './AvatarItem';
|
||||||
|
|
||||||
|
export interface IAvatarListProps {
|
||||||
|
size?: 'large' | 'small' | 'mini' | 'default';
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
children: React.ReactElement<AvatarItem> | Array<React.ReactElement<AvatarItem>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AvatarList extends React.Component<IAvatarListProps, any> {
|
||||||
|
public static Item: typeof AvatarItem;
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
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` |
|
||||||
|
|
||||||
|
### AvatarList.Item
|
||||||
|
|
||||||
|
| Property | Description | Type | Default |
|
||||||
|
|----------|------------------------------------------|-------------|-------|
|
||||||
|
| tips | title tips for avatar item | ReactNode | - |
|
||||||
|
| src | the address of the image for an image avatar | string | - |
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Tooltip, Avatar } from 'antd';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const AvatarList = ({ children, size, ...other }) => {
|
||||||
|
const childrenWithProps = React.Children.map(children, child =>
|
||||||
|
React.cloneElement(child, {
|
||||||
|
size,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...other} className={styles.avatarList}>
|
||||||
|
<ul> {childrenWithProps} </ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Item = ({ src, size, tips, onClick = () => {} }) => {
|
||||||
|
const cls = classNames(styles.avatarItem, {
|
||||||
|
[styles.avatarItemLarge]: size === 'large',
|
||||||
|
[styles.avatarItemSmall]: size === 'small',
|
||||||
|
[styles.avatarItemMini]: size === 'mini',
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
|
@ -0,0 +1,45 @@
|
||||||
|
@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;
|
||||||
|
font-size: @font-size-base;
|
||||||
|
margin-left: -8px;
|
||||||
|
width: @avatar-size-base;
|
||||||
|
height: @avatar-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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
title: AvatarList
|
||||||
|
subtitle: 用户头像列表
|
||||||
|
order: 1
|
||||||
|
cols: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
一组用户头像,常用在项目/团队成员列表。可通过设置 `size` 属性来指定头像大小。
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### AvatarList
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
|
|----------|------------------------------------------|-------------|-------|
|
||||||
|
| size | 头像大小 | `large`、`small` 、`mini`, `default` | `default` |
|
||||||
|
|
||||||
|
### AvatarList.Item
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
|
|----------|------------------------------------------|-------------|-------|
|
||||||
|
| tips | 头像展示文案 | ReactNode | - |
|
||||||
|
| src | 头像图片连接 | string | - |
|
|
@ -0,0 +1,15 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
export interface IBarProps {
|
||||||
|
title: React.ReactNode;
|
||||||
|
color?: string;
|
||||||
|
padding?: [number, number, number, number];
|
||||||
|
height: number;
|
||||||
|
data: Array<{
|
||||||
|
x: string;
|
||||||
|
y: number;
|
||||||
|
}>;
|
||||||
|
autoLabel?: boolean;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Bar extends React.Component<IBarProps, any> {}
|
|
@ -0,0 +1,113 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Chart, Axis, Tooltip, Geom } from 'bizcharts';
|
||||||
|
import Debounce from 'lodash-decorators/debounce';
|
||||||
|
import Bind from 'lodash-decorators/bind';
|
||||||
|
import autoHeight from '../autoHeight';
|
||||||
|
import styles from '../index.less';
|
||||||
|
|
||||||
|
@autoHeight()
|
||||||
|
class Bar extends Component {
|
||||||
|
state = {
|
||||||
|
autoHideXLabels: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
window.addEventListener('resize', this.resize, { passive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
window.removeEventListener('resize', this.resize);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRoot = n => {
|
||||||
|
this.root = n;
|
||||||
|
};
|
||||||
|
|
||||||
|
handleRef = n => {
|
||||||
|
this.node = n;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Bind()
|
||||||
|
@Debounce(400)
|
||||||
|
resize() {
|
||||||
|
if (!this.node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const canvasWidth = this.node.parentNode.clientWidth;
|
||||||
|
const { data = [], autoLabel = true } = this.props;
|
||||||
|
if (!autoLabel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const minWidth = data.length * 30;
|
||||||
|
const { autoHideXLabels } = this.state;
|
||||||
|
|
||||||
|
if (canvasWidth <= minWidth) {
|
||||||
|
if (!autoHideXLabels) {
|
||||||
|
this.setState({
|
||||||
|
autoHideXLabels: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (autoHideXLabels) {
|
||||||
|
this.setState({
|
||||||
|
autoHideXLabels: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
height,
|
||||||
|
title,
|
||||||
|
forceFit = true,
|
||||||
|
data,
|
||||||
|
color = 'rgba(24, 144, 255, 0.85)',
|
||||||
|
padding,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { autoHideXLabels } = this.state;
|
||||||
|
|
||||||
|
const scale = {
|
||||||
|
x: {
|
||||||
|
type: 'cat',
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const tooltip = [
|
||||||
|
'x*y',
|
||||||
|
(x, y) => ({
|
||||||
|
name: x,
|
||||||
|
value: y,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.chart} style={{ height }} ref={this.handleRoot}>
|
||||||
|
<div ref={this.handleRef}>
|
||||||
|
{title && <h4 style={{ marginBottom: 20 }}>{title}</h4>}
|
||||||
|
<Chart
|
||||||
|
scale={scale}
|
||||||
|
height={title ? height - 41 : height}
|
||||||
|
forceFit={forceFit}
|
||||||
|
data={data}
|
||||||
|
padding={padding || 'auto'}
|
||||||
|
>
|
||||||
|
<Axis
|
||||||
|
name="x"
|
||||||
|
title={false}
|
||||||
|
label={autoHideXLabels ? false : {}}
|
||||||
|
tickLine={autoHideXLabels ? false : {}}
|
||||||
|
/>
|
||||||
|
<Axis name="y" min={0} />
|
||||||
|
<Tooltip showTitle={false} crosshairs={false} />
|
||||||
|
<Geom type="interval" position="x*y" color={color} tooltip={tooltip} />
|
||||||
|
</Chart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Bar;
|
|
@ -0,0 +1,14 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { CardProps } from 'antd/lib/card';
|
||||||
|
|
||||||
|
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> {}
|
|
@ -0,0 +1,82 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Card } from 'antd';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const renderTotal = total => {
|
||||||
|
let totalDom;
|
||||||
|
switch (typeof total) {
|
||||||
|
case 'undefined':
|
||||||
|
totalDom = null;
|
||||||
|
break;
|
||||||
|
case 'function':
|
||||||
|
totalDom = <div className={styles.total}>{total()}</div>;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
totalDom = <div className={styles.total}>{total}</div>;
|
||||||
|
}
|
||||||
|
return totalDom;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ChartCard extends React.PureComponent {
|
||||||
|
renderConnet = () => {
|
||||||
|
const { contentHeight, title, avatar, action, total, footer, children, loading } = this.props;
|
||||||
|
if (loading) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={styles.chartCard}>
|
||||||
|
<div
|
||||||
|
className={classNames(styles.chartTop, {
|
||||||
|
[styles.chartTopMargin]: !children && !footer,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className={styles.avatar}>{avatar}</div>
|
||||||
|
<div className={styles.metaWrap}>
|
||||||
|
<div className={styles.meta}>
|
||||||
|
<span className={styles.title}>{title}</span>
|
||||||
|
<span className={styles.action}>{action}</span>
|
||||||
|
</div>
|
||||||
|
{renderTotal(total)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{children && (
|
||||||
|
<div className={styles.content} style={{ height: contentHeight || 'auto' }}>
|
||||||
|
<div className={contentHeight && styles.contentFixed}>{children}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{footer && (
|
||||||
|
<div
|
||||||
|
className={classNames(styles.footer, {
|
||||||
|
[styles.footerMargin]: !children,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{footer}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
loading = false,
|
||||||
|
contentHeight,
|
||||||
|
title,
|
||||||
|
avatar,
|
||||||
|
action,
|
||||||
|
total,
|
||||||
|
footer,
|
||||||
|
children,
|
||||||
|
...rest
|
||||||
|
} = this.props;
|
||||||
|
return (
|
||||||
|
<Card loading={loading} bodyStyle={{ padding: '20px 24px 8px 24px' }} {...rest}>
|
||||||
|
{this.renderConnet()}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChartCard;
|
|
@ -0,0 +1,74 @@
|
||||||
|
@import '~antd/lib/style/themes/default.less';
|
||||||
|
|
||||||
|
.chartCard {
|
||||||
|
position: relative;
|
||||||
|
.chartTop {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.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 {
|
||||||
|
color: @text-color-secondary;
|
||||||
|
font-size: @font-size-base;
|
||||||
|
line-height: 22px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
.action {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.total {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: @heading-color;
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 30px;
|
||||||
|
line-height: 38px;
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.contentFixed {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
border-top: 1px solid @border-color-split;
|
||||||
|
padding-top: 9px;
|
||||||
|
margin-top: 8px;
|
||||||
|
& > * {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.footerMargin {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
export interface IFieldProps {
|
||||||
|
label: React.ReactNode;
|
||||||
|
value: React.ReactNode;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Field extends React.Component<IFieldProps, any> {}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const Field = ({ label, value, ...rest }) => (
|
||||||
|
<div className={styles.field} {...rest}>
|
||||||
|
<span>{label}</span>
|
||||||
|
<span>{value}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Field;
|
|
@ -0,0 +1,16 @@
|
||||||
|
@import '~antd/lib/style/themes/default.less';
|
||||||
|
|
||||||
|
.field {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin: 0;
|
||||||
|
span {
|
||||||
|
font-size: @font-size-base;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
span:last-child {
|
||||||
|
margin-left: 8px;
|
||||||
|
color: @heading-color;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
export interface IGaugeProps {
|
||||||
|
title: React.ReactNode;
|
||||||
|
color?: string;
|
||||||
|
height: number;
|
||||||
|
bgColor?: number;
|
||||||
|
percent: number;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Gauge extends React.Component<IGaugeProps, any> {}
|
|
@ -0,0 +1,167 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Chart, Geom, Axis, Coord, Guide, Shape } from 'bizcharts';
|
||||||
|
import autoHeight from '../autoHeight';
|
||||||
|
|
||||||
|
const { Arc, Html, Line } = Guide;
|
||||||
|
|
||||||
|
const defaultFormatter = val => {
|
||||||
|
switch (val) {
|
||||||
|
case '2':
|
||||||
|
return '差';
|
||||||
|
case '4':
|
||||||
|
return '中';
|
||||||
|
case '6':
|
||||||
|
return '良';
|
||||||
|
case '8':
|
||||||
|
return '优';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Shape.registerShape('point', 'pointer', {
|
||||||
|
drawShape(cfg, group) {
|
||||||
|
let point = cfg.points[0];
|
||||||
|
point = this.parsePoint(point);
|
||||||
|
const center = this.parsePoint({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
});
|
||||||
|
group.addShape('line', {
|
||||||
|
attrs: {
|
||||||
|
x1: center.x,
|
||||||
|
y1: center.y,
|
||||||
|
x2: point.x,
|
||||||
|
y2: point.y,
|
||||||
|
stroke: cfg.color,
|
||||||
|
lineWidth: 2,
|
||||||
|
lineCap: 'round',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return group.addShape('circle', {
|
||||||
|
attrs: {
|
||||||
|
x: center.x,
|
||||||
|
y: center.y,
|
||||||
|
r: 6,
|
||||||
|
stroke: cfg.color,
|
||||||
|
lineWidth: 3,
|
||||||
|
fill: '#fff',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
@autoHeight()
|
||||||
|
class Gauge extends React.Component {
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
height,
|
||||||
|
percent,
|
||||||
|
forceFit = true,
|
||||||
|
formatter = defaultFormatter,
|
||||||
|
color = '#2F9CFF',
|
||||||
|
bgColor = '#F0F2F5',
|
||||||
|
} = this.props;
|
||||||
|
const cols = {
|
||||||
|
value: {
|
||||||
|
type: 'linear',
|
||||||
|
min: 0,
|
||||||
|
max: 10,
|
||||||
|
tickCount: 6,
|
||||||
|
nice: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const data = [{ value: percent / 10 }];
|
||||||
|
return (
|
||||||
|
<Chart height={height} data={data} scale={cols} padding={[-16, 0, 16, 0]} forceFit={forceFit}>
|
||||||
|
<Coord type="polar" startAngle={-1.25 * Math.PI} endAngle={0.25 * Math.PI} radius={0.8} />
|
||||||
|
<Axis name="1" line={null} />
|
||||||
|
<Axis
|
||||||
|
line={null}
|
||||||
|
tickLine={null}
|
||||||
|
subTickLine={null}
|
||||||
|
name="value"
|
||||||
|
zIndex={2}
|
||||||
|
gird={null}
|
||||||
|
label={{
|
||||||
|
offset: -12,
|
||||||
|
formatter,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
fill: 'rgba(0, 0, 0, 0.65)',
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Guide>
|
||||||
|
<Line
|
||||||
|
start={[3, 0.905]}
|
||||||
|
end={[3, 0.85]}
|
||||||
|
lineStyle={{
|
||||||
|
stroke: color,
|
||||||
|
lineDash: null,
|
||||||
|
lineWidth: 2,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
start={[5, 0.905]}
|
||||||
|
end={[5, 0.85]}
|
||||||
|
lineStyle={{
|
||||||
|
stroke: color,
|
||||||
|
lineDash: null,
|
||||||
|
lineWidth: 3,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
start={[7, 0.905]}
|
||||||
|
end={[7, 0.85]}
|
||||||
|
lineStyle={{
|
||||||
|
stroke: color,
|
||||||
|
lineDash: null,
|
||||||
|
lineWidth: 3,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Arc
|
||||||
|
zIndex={0}
|
||||||
|
start={[0, 0.965]}
|
||||||
|
end={[10, 0.965]}
|
||||||
|
style={{
|
||||||
|
stroke: bgColor,
|
||||||
|
lineWidth: 10,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Arc
|
||||||
|
zIndex={1}
|
||||||
|
start={[0, 0.965]}
|
||||||
|
end={[data[0].value, 0.965]}
|
||||||
|
style={{
|
||||||
|
stroke: color,
|
||||||
|
lineWidth: 10,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Html
|
||||||
|
position={['50%', '95%']}
|
||||||
|
html={() => `
|
||||||
|
<div style="width: 300px;text-align: center;font-size: 12px!important;">
|
||||||
|
<p style="font-size: 14px; color: rgba(0,0,0,0.43);margin: 0;">${title}</p>
|
||||||
|
<p style="font-size: 24px;color: rgba(0,0,0,0.85);margin: 0;">
|
||||||
|
${data[0].value * 10}%
|
||||||
|
</p>
|
||||||
|
</div>`}
|
||||||
|
/>
|
||||||
|
</Guide>
|
||||||
|
<Geom
|
||||||
|
line={false}
|
||||||
|
type="point"
|
||||||
|
position="value*1"
|
||||||
|
shape="pointer"
|
||||||
|
color={color}
|
||||||
|
active={false}
|
||||||
|
/>
|
||||||
|
</Chart>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Gauge;
|
|
@ -0,0 +1,29 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
// g2已经更新到3.0
|
||||||
|
// 不带的写了
|
||||||
|
|
||||||
|
export interface IAxis {
|
||||||
|
title: any;
|
||||||
|
line: any;
|
||||||
|
gridAlign: any;
|
||||||
|
labels: any;
|
||||||
|
tickLine: any;
|
||||||
|
grid: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMiniAreaProps {
|
||||||
|
color?: string;
|
||||||
|
height: number;
|
||||||
|
borderColor?: string;
|
||||||
|
line?: boolean;
|
||||||
|
animate?: boolean;
|
||||||
|
xAxis?: IAxis;
|
||||||
|
yAxis?: IAxis;
|
||||||
|
data: Array<{
|
||||||
|
x: number | string;
|
||||||
|
y: number;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class MiniArea extends React.Component<IMiniAreaProps, any> {}
|
|
@ -0,0 +1,108 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Chart, Axis, Tooltip, Geom } from 'bizcharts';
|
||||||
|
import autoHeight from '../autoHeight';
|
||||||
|
import styles from '../index.less';
|
||||||
|
|
||||||
|
@autoHeight()
|
||||||
|
class MiniArea extends React.PureComponent {
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
height,
|
||||||
|
data = [],
|
||||||
|
forceFit = true,
|
||||||
|
color = 'rgba(24, 144, 255, 0.2)',
|
||||||
|
borderColor = '#1089ff',
|
||||||
|
scale = {},
|
||||||
|
borderWidth = 2,
|
||||||
|
line,
|
||||||
|
xAxis,
|
||||||
|
yAxis,
|
||||||
|
animate = true,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const padding = [36, 5, 30, 5];
|
||||||
|
|
||||||
|
const scaleProps = {
|
||||||
|
x: {
|
||||||
|
type: 'cat',
|
||||||
|
range: [0, 1],
|
||||||
|
...scale.x,
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
min: 0,
|
||||||
|
...scale.y,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const tooltip = [
|
||||||
|
'x*y',
|
||||||
|
(x, y) => ({
|
||||||
|
name: x,
|
||||||
|
value: y,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const chartHeight = height + 54;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.miniChart} style={{ height }}>
|
||||||
|
<div className={styles.chartContent}>
|
||||||
|
{height > 0 && (
|
||||||
|
<Chart
|
||||||
|
animate={animate}
|
||||||
|
scale={scaleProps}
|
||||||
|
height={chartHeight}
|
||||||
|
forceFit={forceFit}
|
||||||
|
data={data}
|
||||||
|
padding={padding}
|
||||||
|
>
|
||||||
|
<Axis
|
||||||
|
key="axis-x"
|
||||||
|
name="x"
|
||||||
|
label={false}
|
||||||
|
line={false}
|
||||||
|
tickLine={false}
|
||||||
|
grid={false}
|
||||||
|
{...xAxis}
|
||||||
|
/>
|
||||||
|
<Axis
|
||||||
|
key="axis-y"
|
||||||
|
name="y"
|
||||||
|
label={false}
|
||||||
|
line={false}
|
||||||
|
tickLine={false}
|
||||||
|
grid={false}
|
||||||
|
{...yAxis}
|
||||||
|
/>
|
||||||
|
<Tooltip showTitle={false} crosshairs={false} />
|
||||||
|
<Geom
|
||||||
|
type="area"
|
||||||
|
position="x*y"
|
||||||
|
color={color}
|
||||||
|
tooltip={tooltip}
|
||||||
|
shape="smooth"
|
||||||
|
style={{
|
||||||
|
fillOpacity: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{line ? (
|
||||||
|
<Geom
|
||||||
|
type="line"
|
||||||
|
position="x*y"
|
||||||
|
shape="smooth"
|
||||||
|
color={borderColor}
|
||||||
|
size={borderWidth}
|
||||||
|
tooltip={false}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span style={{ display: 'none' }} />
|
||||||
|
)}
|
||||||
|
</Chart>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MiniArea;
|
|
@ -0,0 +1,12 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
export interface IMiniBarProps {
|
||||||
|
color?: string;
|
||||||
|
height: number;
|
||||||
|
data: Array<{
|
||||||
|
x: number | string;
|
||||||
|
y: number;
|
||||||
|
}>;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class MiniBar extends React.Component<IMiniBarProps, any> {}
|
|
@ -0,0 +1,51 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Chart, Tooltip, Geom } from 'bizcharts';
|
||||||
|
import autoHeight from '../autoHeight';
|
||||||
|
import styles from '../index.less';
|
||||||
|
|
||||||
|
@autoHeight()
|
||||||
|
class MiniBar extends React.Component {
|
||||||
|
render() {
|
||||||
|
const { height, forceFit = true, color = '#1890FF', data = [] } = this.props;
|
||||||
|
|
||||||
|
const scale = {
|
||||||
|
x: {
|
||||||
|
type: 'cat',
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const padding = [36, 5, 30, 5];
|
||||||
|
|
||||||
|
const tooltip = [
|
||||||
|
'x*y',
|
||||||
|
(x, y) => ({
|
||||||
|
name: x,
|
||||||
|
value: y,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
// for tooltip not to be hide
|
||||||
|
const chartHeight = height + 54;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.miniChart} style={{ height }}>
|
||||||
|
<div className={styles.chartContent}>
|
||||||
|
<Chart
|
||||||
|
scale={scale}
|
||||||
|
height={chartHeight}
|
||||||
|
forceFit={forceFit}
|
||||||
|
data={data}
|
||||||
|
padding={padding}
|
||||||
|
>
|
||||||
|
<Tooltip showTitle={false} crosshairs={false} />
|
||||||
|
<Geom type="interval" position="x*y" color={color} tooltip={tooltip} />
|
||||||
|
</Chart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default MiniBar;
|
|
@ -0,0 +1,10 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
export interface IMiniProgressProps {
|
||||||
|
target: number;
|
||||||
|
color?: string;
|
||||||
|
strokeWidth?: number;
|
||||||
|
percent?: number;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class MiniProgress extends React.Component<IMiniProgressProps, any> {}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Tooltip } from 'antd';
|
||||||
|
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const MiniProgress = ({ target, color = 'rgb(19, 194, 194)', strokeWidth, percent }) => (
|
||||||
|
<div className={styles.miniProgress}>
|
||||||
|
<Tooltip title={`目标值: ${target}%`}>
|
||||||
|
<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;
|
|
@ -0,0 +1,35 @@
|
||||||
|
@import '~antd/lib/style/themes/default.less';
|
||||||
|
|
||||||
|
.miniProgress {
|
||||||
|
padding: 5px 0;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
.progressWrap {
|
||||||
|
background-color: @background-color-base;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.progress {
|
||||||
|
transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s;
|
||||||
|
border-radius: 1px 0 0 1px;
|
||||||
|
background-color: @primary-color;
|
||||||
|
width: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.target {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
span {
|
||||||
|
border-radius: 100px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 4px;
|
||||||
|
width: 2px;
|
||||||
|
}
|
||||||
|
span:last-child {
|
||||||
|
top: auto;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import * as 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> {}
|
|
@ -0,0 +1,271 @@
|
||||||
|
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 autoHeight from '../autoHeight';
|
||||||
|
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
/* eslint react/no-danger:0 */
|
||||||
|
@autoHeight()
|
||||||
|
class Pie extends Component {
|
||||||
|
state = {
|
||||||
|
legendData: [],
|
||||||
|
legendBlock: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
window.addEventListener(
|
||||||
|
'resize',
|
||||||
|
() => {
|
||||||
|
this.requestRef = requestAnimationFrame(() => this.resize());
|
||||||
|
},
|
||||||
|
{ passive: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
window.cancelAnimationFrame(this.requestRef);
|
||||||
|
window.removeEventListener('resize', this.resize);
|
||||||
|
this.resize.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
getG2Instance = chart => {
|
||||||
|
this.chart = chart;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.getLegendData();
|
||||||
|
this.resize();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
forceFit = true,
|
||||||
|
percent,
|
||||||
|
color,
|
||||||
|
inner = 0.75,
|
||||||
|
animate = true,
|
||||||
|
colors,
|
||||||
|
lineWidth = 1,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { legendData, 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;
|
||||||
|
data = data || [];
|
||||||
|
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}
|
||||||
|
forceFit={forceFit}
|
||||||
|
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;
|
|
@ -0,0 +1,94 @@
|
||||||
|
@import '~antd/lib/style/themes/default.less';
|
||||||
|
|
||||||
|
.pie {
|
||||||
|
position: relative;
|
||||||
|
.chart {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
&.hasLegend .chart {
|
||||||
|
width: ~'calc(100% - 240px)';
|
||||||
|
}
|
||||||
|
.legend {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
min-width: 200px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
margin: 0 20px;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
li {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
height: 22px;
|
||||||
|
line-height: 22px;
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dot {
|
||||||
|
border-radius: 8px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 8px;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
height: 8px;
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
.line {
|
||||||
|
background-color: @border-color-split;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 8px;
|
||||||
|
width: 1px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
.legendTitle {
|
||||||
|
color: @text-color;
|
||||||
|
}
|
||||||
|
.percent {
|
||||||
|
color: @text-color-secondary;
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.total {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
text-align: center;
|
||||||
|
max-height: 62px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
& > h4 {
|
||||||
|
color: @text-color-secondary;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
height: 22px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
& > p {
|
||||||
|
color: @heading-color;
|
||||||
|
display: block;
|
||||||
|
font-size: 1.2em;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legendBlock {
|
||||||
|
&.hasLegend .chart {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0 32px 0;
|
||||||
|
}
|
||||||
|
.legend {
|
||||||
|
position: relative;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import * as 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> {}
|
|
@ -0,0 +1,184 @@
|
||||||
|
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 = 4,
|
||||||
|
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;
|
|
@ -0,0 +1,46 @@
|
||||||
|
@import '~antd/lib/style/themes/default.less';
|
||||||
|
|
||||||
|
.radar {
|
||||||
|
.legend {
|
||||||
|
margin-top: 16px;
|
||||||
|
.legendItem {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
color: @text-color-secondary;
|
||||||
|
line-height: 22px;
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
h6 {
|
||||||
|
color: @heading-color;
|
||||||
|
padding-left: 16px;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 32px;
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
background-color: @border-color-split;
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 0;
|
||||||
|
height: 40px;
|
||||||
|
width: 1px;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> :last-child .legendItem:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.dot {
|
||||||
|
border-radius: 6px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 6px;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
height: 6px;
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import * as 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> {}
|
|
@ -0,0 +1,170 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Chart, Geom, Coord, Shape } 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() {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.initTagCloud();
|
||||||
|
this.renderChart();
|
||||||
|
});
|
||||||
|
window.addEventListener('resize', this.resize, { passive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(preProps) {
|
||||||
|
const { data } = this.props;
|
||||||
|
if (JSON.stringify(preProps.data) !== JSON.stringify(data)) {
|
||||||
|
this.renderChart(this.props);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.isUnmount = true;
|
||||||
|
window.cancelAnimationFrame(this.requestRef);
|
||||||
|
window.removeEventListener('resize', this.resize);
|
||||||
|
}
|
||||||
|
|
||||||
|
resize = () => {
|
||||||
|
this.requestRef = requestAnimationFrame(() => {
|
||||||
|
this.renderChart();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
saveRootRef = node => {
|
||||||
|
this.root = node;
|
||||||
|
};
|
||||||
|
|
||||||
|
initTagCloud = () => {
|
||||||
|
function getTextAttrs(cfg) {
|
||||||
|
return Object.assign(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
cfg.style
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 给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 * 4;
|
||||||
|
const w = this.root.offsetWidth * 4;
|
||||||
|
|
||||||
|
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: 5,
|
||||||
|
timeInterval: 5000, // max execute time
|
||||||
|
rotate() {
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
fontSize(d) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
return Math.pow((d.value - min) / (max - min), 2) * (70 - 20) + 20;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Coord reflect="y" />
|
||||||
|
<Geom type="point" position="x*y" color="text" shape="cloud" />
|
||||||
|
</Chart>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TagCloud;
|
|
@ -0,0 +1,7 @@
|
||||||
|
.tagCloud {
|
||||||
|
overflow: hidden;
|
||||||
|
canvas {
|
||||||
|
transform: scale(0.25);
|
||||||
|
transform-origin: 0 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import * as 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> {}
|
|
@ -0,0 +1,124 @@
|
||||||
|
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 = [
|
||||||
|
{
|
||||||
|
x: 0,
|
||||||
|
y1: 0,
|
||||||
|
y2: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
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;
|
|
@ -0,0 +1,3 @@
|
||||||
|
.timelineChart {
|
||||||
|
background: #fff;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import * as 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> {}
|
|
@ -0,0 +1,213 @@
|
||||||
|
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;
|
|
@ -0,0 +1,28 @@
|
||||||
|
@import '~antd/lib/style/themes/default.less';
|
||||||
|
|
||||||
|
.waterWave {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
transform-origin: left;
|
||||||
|
.text {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 32px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
span {
|
||||||
|
color: @text-color-secondary;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
color: @heading-color;
|
||||||
|
line-height: 32px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.waterWaveCanvasWrapper {
|
||||||
|
transform: scale(0.5);
|
||||||
|
transform-origin: 0 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/* 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;
|
|
@ -0,0 +1,3 @@
|
||||||
|
import * as BizChart from 'bizcharts';
|
||||||
|
|
||||||
|
export = BizChart;
|
|
@ -0,0 +1,3 @@
|
||||||
|
import * as BizChart from 'bizcharts';
|
||||||
|
|
||||||
|
export default BizChart;
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
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);
|
||||||
|
````
|
|
@ -0,0 +1,95 @@
|
||||||
|
---
|
||||||
|
order: 1
|
||||||
|
title: 图表卡片
|
||||||
|
---
|
||||||
|
|
||||||
|
用于展示图表的卡片容器,可以方便的配合其它图表套件展示丰富信息。
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { ChartCard, yuan, Field } from 'ant-design-pro/lib/Charts';
|
||||||
|
import Trend from 'ant-design-pro/lib/Trend';
|
||||||
|
import { Row, Col, Icon, Tooltip } from 'antd';
|
||||||
|
import numeral from 'numeral';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Row>
|
||||||
|
<Col span={24}>
|
||||||
|
<ChartCard
|
||||||
|
title="销售额"
|
||||||
|
action={
|
||||||
|
<Tooltip title="指标说明">
|
||||||
|
<Icon type="info-circle-o" />
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
total={() => (
|
||||||
|
<span dangerouslySetInnerHTML={{ __html: yuan(126560) }} />
|
||||||
|
)}
|
||||||
|
footer={
|
||||||
|
<Field label="日均销售额" value={numeral(12423).format("0,0")} />
|
||||||
|
}
|
||||||
|
contentHeight={46}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
周同比
|
||||||
|
<Trend flag="up" style={{ marginLeft: 8, color: "rgba(0,0,0,.85)" }}>
|
||||||
|
12%
|
||||||
|
</Trend>
|
||||||
|
</span>
|
||||||
|
<span style={{ marginLeft: 16 }}>
|
||||||
|
日环比
|
||||||
|
<Trend
|
||||||
|
flag="down"
|
||||||
|
style={{ marginLeft: 8, color: "rgba(0,0,0,.85)" }}
|
||||||
|
>
|
||||||
|
11%
|
||||||
|
</Trend>
|
||||||
|
</span>
|
||||||
|
</ChartCard>
|
||||||
|
</Col>
|
||||||
|
<Col span={24} style={{ marginTop: 24 }}>
|
||||||
|
<ChartCard
|
||||||
|
title="移动指标"
|
||||||
|
avatar={
|
||||||
|
<img
|
||||||
|
style={{ width: 56, height: 56 }}
|
||||||
|
src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png"
|
||||||
|
alt="indicator"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
action={
|
||||||
|
<Tooltip title="指标说明">
|
||||||
|
<Icon type="info-circle-o" />
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
total={() => (
|
||||||
|
<span dangerouslySetInnerHTML={{ __html: yuan(126560) }} />
|
||||||
|
)}
|
||||||
|
footer={
|
||||||
|
<Field label="日均销售额" value={numeral(12423).format("0,0")} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={24} style={{ marginTop: 24 }}>
|
||||||
|
<ChartCard
|
||||||
|
title="移动指标"
|
||||||
|
avatar={
|
||||||
|
<img
|
||||||
|
alt="indicator"
|
||||||
|
style={{ width: 56, height: 56 }}
|
||||||
|
src="https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
action={
|
||||||
|
<Tooltip title="指标说明">
|
||||||
|
<Icon type="info-circle-o" />
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
total={() => (
|
||||||
|
<span dangerouslySetInnerHTML={{ __html: yuan(126560) }} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>,
|
||||||
|
mountNode,
|
||||||
|
);
|
||||||
|
```
|
|
@ -0,0 +1,18 @@
|
||||||
|
---
|
||||||
|
order: 7
|
||||||
|
title: 仪表盘
|
||||||
|
---
|
||||||
|
|
||||||
|
仪表盘是一种进度展示方式,可以更直观的展示当前的进展情况,通常也可表示占比。
|
||||||
|
|
||||||
|
````jsx
|
||||||
|
import { Gauge } from 'ant-design-pro/lib/Charts';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Gauge
|
||||||
|
title="核销率"
|
||||||
|
height={164}
|
||||||
|
percent={87}
|
||||||
|
/>
|
||||||
|
, mountNode);
|
||||||
|
````
|
|
@ -0,0 +1,28 @@
|
||||||
|
---
|
||||||
|
order: 2
|
||||||
|
col: 2
|
||||||
|
title: 迷你区域图
|
||||||
|
---
|
||||||
|
|
||||||
|
````jsx
|
||||||
|
import { MiniArea } from 'ant-design-pro/lib/Charts';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
const visitData = [];
|
||||||
|
const beginDay = new Date().getTime();
|
||||||
|
for (let i = 0; i < 20; i += 1) {
|
||||||
|
visitData.push({
|
||||||
|
x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'),
|
||||||
|
y: Math.floor(Math.random() * 100) + 10,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<MiniArea
|
||||||
|
line
|
||||||
|
color="#cceafe"
|
||||||
|
height={45}
|
||||||
|
data={visitData}
|
||||||
|
/>
|
||||||
|
, mountNode);
|
||||||
|
````
|
|
@ -0,0 +1,28 @@
|
||||||
|
---
|
||||||
|
order: 2
|
||||||
|
col: 2
|
||||||
|
title: 迷你柱状图
|
||||||
|
---
|
||||||
|
|
||||||
|
迷你柱状图更适合展示简单的区间数据,简洁的表现方式可以很好的减少大数据量的视觉展现压力。
|
||||||
|
|
||||||
|
````jsx
|
||||||
|
import { MiniBar } from 'ant-design-pro/lib/Charts';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
const visitData = [];
|
||||||
|
const beginDay = new Date().getTime();
|
||||||
|
for (let i = 0; i < 20; i += 1) {
|
||||||
|
visitData.push({
|
||||||
|
x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'),
|
||||||
|
y: Math.floor(Math.random() * 100) + 10,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<MiniBar
|
||||||
|
height={45}
|
||||||
|
data={visitData}
|
||||||
|
/>
|
||||||
|
, mountNode);
|
||||||
|
````
|
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
order: 6
|
||||||
|
title: 迷你饼状图
|
||||||
|
---
|
||||||
|
|
||||||
|
通过简化 `Pie` 属性的设置,可以快速的实现极简的饼状图,可配合 `ChartCard` 组合展
|
||||||
|
现更多业务场景。
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { Pie } from 'ant-design-pro/lib/Charts';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Pie percent={28} subTitle="中式快餐" total="28%" height={140} />,
|
||||||
|
mountNode
|
||||||
|
);
|
||||||
|
```
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
order: 3
|
||||||
|
title: 迷你进度条
|
||||||
|
---
|
||||||
|
|
||||||
|
````jsx
|
||||||
|
import { MiniProgress } from 'ant-design-pro/lib/Charts';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<MiniProgress percent={78} strokeWidth={8} target={80} />
|
||||||
|
, mountNode);
|
||||||
|
````
|
|
@ -0,0 +1,84 @@
|
||||||
|
---
|
||||||
|
order: 0
|
||||||
|
title: 图表套件组合展示
|
||||||
|
---
|
||||||
|
|
||||||
|
利用 Ant Design Pro 提供的图表套件,可以灵活组合符合设计规范的图表来满足复杂的业务需求。
|
||||||
|
|
||||||
|
````jsx
|
||||||
|
import { ChartCard, Field, MiniArea, MiniBar, MiniProgress } from 'ant-design-pro/lib/Charts';
|
||||||
|
import Trend from 'ant-design-pro/lib/Trend';
|
||||||
|
import NumberInfo from 'ant-design-pro/lib/NumberInfo';
|
||||||
|
import { Row, Col, Icon, Tooltip } from 'antd';
|
||||||
|
import numeral from 'numeral';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
const visitData = [];
|
||||||
|
const beginDay = new Date().getTime();
|
||||||
|
for (let i = 0; i < 20; i += 1) {
|
||||||
|
visitData.push({
|
||||||
|
x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'),
|
||||||
|
y: Math.floor(Math.random() * 100) + 10,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Row>
|
||||||
|
<Col span={24}>
|
||||||
|
<ChartCard
|
||||||
|
title="搜索用户数量"
|
||||||
|
total={numeral(8846).format('0,0')}
|
||||||
|
contentHeight={134}
|
||||||
|
>
|
||||||
|
<NumberInfo
|
||||||
|
subTitle={<span>本周访问</span>}
|
||||||
|
total={numeral(12321).format('0,0')}
|
||||||
|
status="up"
|
||||||
|
subTotal={17.1}
|
||||||
|
/>
|
||||||
|
<MiniArea
|
||||||
|
line
|
||||||
|
height={45}
|
||||||
|
data={visitData}
|
||||||
|
/>
|
||||||
|
</ChartCard>
|
||||||
|
</Col>
|
||||||
|
<Col span={24} style={{ marginTop: 24 }}>
|
||||||
|
<ChartCard
|
||||||
|
title="访问量"
|
||||||
|
action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>}
|
||||||
|
total={numeral(8846).format('0,0')}
|
||||||
|
footer={<Field label="日访问量" value={numeral(1234).format('0,0')} />}
|
||||||
|
contentHeight={46}
|
||||||
|
>
|
||||||
|
<MiniBar
|
||||||
|
height={46}
|
||||||
|
data={visitData}
|
||||||
|
/>
|
||||||
|
</ChartCard>
|
||||||
|
</Col>
|
||||||
|
<Col span={24} style={{ marginTop: 24 }}>
|
||||||
|
<ChartCard
|
||||||
|
title="线上购物转化率"
|
||||||
|
action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>}
|
||||||
|
total="78%"
|
||||||
|
footer={
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
周同比
|
||||||
|
<Trend flag="up" style={{ marginLeft: 8, color: 'rgba(0,0,0,.85)' }}>12%</Trend>
|
||||||
|
</span>
|
||||||
|
<span style={{ marginLeft: 16 }}>
|
||||||
|
日环比
|
||||||
|
<Trend flag="down" style={{ marginLeft: 8, color: 'rgba(0,0,0,.85)' }}>11%</Trend>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
contentHeight={46}
|
||||||
|
>
|
||||||
|
<MiniProgress percent={78} strokeWidth={8} target={80} />
|
||||||
|
</ChartCard>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
, mountNode);
|
||||||
|
````
|
|
@ -0,0 +1,54 @@
|
||||||
|
---
|
||||||
|
order: 5
|
||||||
|
title: 饼状图
|
||||||
|
---
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { Pie, yuan } from 'ant-design-pro/lib/Charts';
|
||||||
|
|
||||||
|
const salesPieData = [
|
||||||
|
{
|
||||||
|
x: '家用电器',
|
||||||
|
y: 4544,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '食用酒水',
|
||||||
|
y: 3321,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '个护健康',
|
||||||
|
y: 3113,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '服饰箱包',
|
||||||
|
y: 2341,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '母婴产品',
|
||||||
|
y: 1231,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: '其他',
|
||||||
|
y: 1231,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Pie
|
||||||
|
hasLegend
|
||||||
|
title="销售额"
|
||||||
|
subTitle="销售额"
|
||||||
|
total={() => (
|
||||||
|
<span
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: yuan(salesPieData.reduce((pre, now) => now.y + pre, 0))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
data={salesPieData}
|
||||||
|
valueFormat={val => <span dangerouslySetInnerHTML={{ __html: yuan(val) }} />}
|
||||||
|
height={294}
|
||||||
|
/>,
|
||||||
|
mountNode,
|
||||||
|
);
|
||||||
|
```
|
|
@ -0,0 +1,64 @@
|
||||||
|
---
|
||||||
|
order: 7
|
||||||
|
title: 雷达图
|
||||||
|
---
|
||||||
|
|
||||||
|
````jsx
|
||||||
|
import { Radar, ChartCard } from 'ant-design-pro/lib/Charts';
|
||||||
|
|
||||||
|
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],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<ChartCard title="数据比例">
|
||||||
|
<Radar
|
||||||
|
hasLegend
|
||||||
|
height={286}
|
||||||
|
data={radarData}
|
||||||
|
/>
|
||||||
|
</ChartCard>
|
||||||
|
, mountNode);
|
||||||
|
````
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
order: 9
|
||||||
|
title: 标签云
|
||||||
|
---
|
||||||
|
|
||||||
|
标签云是一套相关的标签以及与此相应的权重展示方式,一般典型的标签云有 30 至 150 个标签,而权重影响使用的字体大小或其他视觉效果。
|
||||||
|
|
||||||
|
````jsx
|
||||||
|
import { TagCloud } from 'ant-design-pro/lib/Charts';
|
||||||
|
|
||||||
|
const tags = [];
|
||||||
|
for (let i = 0; i < 50; i += 1) {
|
||||||
|
tags.push({
|
||||||
|
name: `TagClout-Title-${i}`,
|
||||||
|
value: Math.floor((Math.random() * 50)) + 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<TagCloud
|
||||||
|
data={tags}
|
||||||
|
height={200}
|
||||||
|
/>
|
||||||
|
, mountNode);
|
||||||
|
````
|
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
order: 9
|
||||||
|
title: 带有时间轴的图表
|
||||||
|
---
|
||||||
|
|
||||||
|
使用 `TimelineChart` 组件可以实现带有时间轴的柱状图展现,而其中的 `x` 属性,则是时间值的指向,默认最多支持同时展现两个指标,分别是 `y1` 和 `y2`。
|
||||||
|
|
||||||
|
````jsx
|
||||||
|
import { TimelineChart } from 'ant-design-pro/lib/Charts';
|
||||||
|
|
||||||
|
const chartData = [];
|
||||||
|
for (let i = 0; i < 20; i += 1) {
|
||||||
|
chartData.push({
|
||||||
|
x: (new Date().getTime()) + (1000 * 60 * 30 * i),
|
||||||
|
y1: Math.floor(Math.random() * 100) + 1000,
|
||||||
|
y2: Math.floor(Math.random() * 100) + 10,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<TimelineChart
|
||||||
|
height={200}
|
||||||
|
data={chartData}
|
||||||
|
titleMap={{ y1: '客流量', y2: '支付笔数' }}
|
||||||
|
/>
|
||||||
|
, mountNode);
|
||||||
|
````
|
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
order: 8
|
||||||
|
title: 水波图
|
||||||
|
---
|
||||||
|
|
||||||
|
水波图是一种比例的展示方式,可以更直观的展示关键值的占比。
|
||||||
|
|
||||||
|
````jsx
|
||||||
|
import { WaterWave } from 'ant-design-pro/lib/Charts';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<WaterWave
|
||||||
|
height={161}
|
||||||
|
title="补贴资金剩余"
|
||||||
|
percent={34}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
, mountNode);
|
||||||
|
````
|
|
@ -0,0 +1,15 @@
|
||||||
|
// 全局 G2 设置
|
||||||
|
import { track, setTheme } from 'bizcharts';
|
||||||
|
|
||||||
|
track(false);
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
defaultColor: '#1089ff',
|
||||||
|
shape: {
|
||||||
|
interval: {
|
||||||
|
fillOpacity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
setTheme(config);
|
|
@ -0,0 +1,17 @@
|
||||||
|
import * as numeral from 'numeral';
|
||||||
|
export { default as ChartCard } from './ChartCard';
|
||||||
|
export { default as Bar } from './Bar';
|
||||||
|
export { default as Pie } from './Pie';
|
||||||
|
export { default as Radar } from './Radar';
|
||||||
|
export { default as Gauge } from './Gauge';
|
||||||
|
export { default as MiniArea } from './MiniArea';
|
||||||
|
export { default as MiniBar } from './MiniBar';
|
||||||
|
export { default as MiniProgress } from './MiniProgress';
|
||||||
|
export { default as Field } from './Field';
|
||||||
|
export { default as WaterWave } from './WaterWave';
|
||||||
|
export { default as TagCloud } from './TagCloud';
|
||||||
|
export { default as TimelineChart } from './TimelineChart';
|
||||||
|
|
||||||
|
declare const yuan: (value: number | string) => string;
|
||||||
|
|
||||||
|
export { yuan };
|
|
@ -0,0 +1,49 @@
|
||||||
|
import numeral from 'numeral';
|
||||||
|
import './g2';
|
||||||
|
import ChartCard from './ChartCard';
|
||||||
|
import Bar from './Bar';
|
||||||
|
import Pie from './Pie';
|
||||||
|
import Radar from './Radar';
|
||||||
|
import Gauge from './Gauge';
|
||||||
|
import MiniArea from './MiniArea';
|
||||||
|
import MiniBar from './MiniBar';
|
||||||
|
import MiniProgress from './MiniProgress';
|
||||||
|
import Field from './Field';
|
||||||
|
import WaterWave from './WaterWave';
|
||||||
|
import TagCloud from './TagCloud';
|
||||||
|
import TimelineChart from './TimelineChart';
|
||||||
|
|
||||||
|
const yuan = val => `¥ ${numeral(val).format('0,0')}`;
|
||||||
|
|
||||||
|
const Charts = {
|
||||||
|
yuan,
|
||||||
|
Bar,
|
||||||
|
Pie,
|
||||||
|
Gauge,
|
||||||
|
Radar,
|
||||||
|
MiniBar,
|
||||||
|
MiniArea,
|
||||||
|
MiniProgress,
|
||||||
|
ChartCard,
|
||||||
|
Field,
|
||||||
|
WaterWave,
|
||||||
|
TagCloud,
|
||||||
|
TimelineChart,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
Charts as default,
|
||||||
|
yuan,
|
||||||
|
Bar,
|
||||||
|
Pie,
|
||||||
|
Gauge,
|
||||||
|
Radar,
|
||||||
|
MiniBar,
|
||||||
|
MiniArea,
|
||||||
|
MiniProgress,
|
||||||
|
ChartCard,
|
||||||
|
Field,
|
||||||
|
WaterWave,
|
||||||
|
TagCloud,
|
||||||
|
TimelineChart,
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue