mirror of
https://github.com/chillzhuang/Sword
synced 2024-12-03 23:59:26 +08:00
🎉 2.6.1发布,增加登陆验证码,支持Seata1.0
This commit is contained in:
parent
2bae7753bd
commit
e380417953
BIN
public/favicon.png
Normal file
BIN
public/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
public/icons/icon-128x128.png
Normal file
BIN
public/icons/icon-128x128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
public/icons/icon-192x192.png
Normal file
BIN
public/icons/icon-192x192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
public/icons/icon-512x512.png
Normal file
BIN
public/icons/icon-512x512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
3
scripts/generateMock.js
Normal file
3
scripts/generateMock.js
Normal file
@ -0,0 +1,3 @@
|
||||
const generateMock = require('merge-umi-mock-data');
|
||||
const path = require('path');
|
||||
generateMock(path.join(__dirname, '../mock'), path.join(__dirname, '../functions/mock/index.js'));
|
23
scripts/getPrettierFiles.js
Normal file
23
scripts/getPrettierFiles.js
Normal file
@ -0,0 +1,23 @@
|
||||
const glob = require('glob');
|
||||
|
||||
const getPrettierFiles = () => {
|
||||
let files = [];
|
||||
const configFiles = glob.sync('config/**/*.js*', { ignore: ['**/node_modules/**', 'build/**'] });
|
||||
const mockFiles = glob.sync('mock/**/*.js*', { ignore: ['**/node_modules/**', 'build/**'] });
|
||||
const jsFiles = glob.sync('src/**/*.js*', { ignore: ['**/node_modules/**', 'build/**'] });
|
||||
const scriptFiles = glob.sync('scripts/**/*.js');
|
||||
const tsFiles = glob.sync('src/**/*.ts*', { ignore: ['**/node_modules/**', 'build/**'] });
|
||||
const lessFiles = glob.sync('src/**/*.less*', { ignore: ['**/node_modules/**', 'build/**'] });
|
||||
files = files.concat(configFiles);
|
||||
files = files.concat(mockFiles);
|
||||
files = files.concat(jsFiles);
|
||||
files = files.concat(scriptFiles);
|
||||
files = files.concat(tsFiles);
|
||||
files = files.concat(lessFiles);
|
||||
if (!files.length) {
|
||||
return;
|
||||
}
|
||||
return files;
|
||||
};
|
||||
|
||||
module.exports = getPrettierFiles;
|
50
scripts/lint-prettier.js
Normal file
50
scripts/lint-prettier.js
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* copy to https://github.com/facebook/react/blob/master/scripts/prettier/index.js
|
||||
* prettier api doc https://prettier.io/docs/en/api.html
|
||||
*----------*****--------------
|
||||
* lint file is prettier
|
||||
*----------*****--------------
|
||||
*/
|
||||
|
||||
const prettier = require('prettier');
|
||||
const fs = require('fs');
|
||||
const chalk = require('chalk');
|
||||
const prettierConfigPath = require.resolve('../.prettierrc');
|
||||
|
||||
const files = process.argv.slice(2);
|
||||
|
||||
let didError = false;
|
||||
|
||||
files.forEach(file => {
|
||||
Promise.all([
|
||||
prettier.resolveConfig(file, {
|
||||
config: prettierConfigPath,
|
||||
}),
|
||||
prettier.getFileInfo(file),
|
||||
])
|
||||
.then(resolves => {
|
||||
const [options, fileInfo] = resolves;
|
||||
if (fileInfo.ignored) {
|
||||
return;
|
||||
}
|
||||
const input = fs.readFileSync(file, 'utf8');
|
||||
const withParserOptions = {
|
||||
...options,
|
||||
parser: fileInfo.inferredParser,
|
||||
};
|
||||
const output = prettier.format(input, withParserOptions);
|
||||
if (output !== input) {
|
||||
fs.writeFileSync(file, output, 'utf8');
|
||||
console.log(chalk.green(`${file} is prettier`));
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
didError = true;
|
||||
})
|
||||
.finally(() => {
|
||||
if (didError) {
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(chalk.hex('#1890FF')('prettier success!'));
|
||||
});
|
||||
});
|
46
scripts/prettier.js
Normal file
46
scripts/prettier.js
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* copy to https://github.com/facebook/react/blob/master/scripts/prettier/index.js
|
||||
* prettier api doc https://prettier.io/docs/en/api.html
|
||||
*----------*****--------------
|
||||
* prettier all js and all ts.
|
||||
*----------*****--------------
|
||||
*/
|
||||
|
||||
const prettier = require('prettier');
|
||||
const fs = require('fs');
|
||||
const getPrettierFiles = require('./getPrettierFiles');
|
||||
const prettierConfigPath = require.resolve('../.prettierrc');
|
||||
const chalk = require('chalk');
|
||||
|
||||
let didError = false;
|
||||
|
||||
const files = getPrettierFiles();
|
||||
|
||||
files.forEach(file => {
|
||||
const options = prettier.resolveConfig.sync(file, {
|
||||
config: prettierConfigPath,
|
||||
});
|
||||
const fileInfo = prettier.getFileInfo.sync(file);
|
||||
if (fileInfo.ignored) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const input = fs.readFileSync(file, 'utf8');
|
||||
const withParserOptions = {
|
||||
...options,
|
||||
parser: fileInfo.inferredParser,
|
||||
};
|
||||
const output = prettier.format(input, withParserOptions);
|
||||
if (output !== input) {
|
||||
fs.writeFileSync(file, output, 'utf8');
|
||||
console.log(chalk.green(`${file} is prettier`));
|
||||
}
|
||||
} catch (e) {
|
||||
didError = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (didError) {
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(chalk.hex('#1890FF')('prettier success!'));
|
36
src/actions/client.js
Normal file
36
src/actions/client.js
Normal file
@ -0,0 +1,36 @@
|
||||
export const CLIENT_NAMESPACE = 'client';
|
||||
|
||||
export function CLIENT_LIST(payload) {
|
||||
return {
|
||||
type: `${CLIENT_NAMESPACE}/fetchList`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function CLIENT_DETAIL(id) {
|
||||
return {
|
||||
type: `${CLIENT_NAMESPACE}/fetchDetail`,
|
||||
payload: { id },
|
||||
};
|
||||
}
|
||||
|
||||
export function CLIENT_CLEAR_DETAIL() {
|
||||
return {
|
||||
type: `${CLIENT_NAMESPACE}/clearDetail`,
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function CLIENT_SUBMIT(payload) {
|
||||
return {
|
||||
type: `${CLIENT_NAMESPACE}/submit`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function CLIENT_REMOVE(payload) {
|
||||
return {
|
||||
type: `${CLIENT_NAMESPACE}/remove`,
|
||||
payload,
|
||||
};
|
||||
}
|
43
src/actions/code.js
Normal file
43
src/actions/code.js
Normal file
@ -0,0 +1,43 @@
|
||||
export const CODE_NAMESPACE = 'code';
|
||||
|
||||
export function CODE_LIST(payload) {
|
||||
return {
|
||||
type: `${CODE_NAMESPACE}/fetchList`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function CODE_INIT() {
|
||||
return {
|
||||
type: `${CODE_NAMESPACE}/fetchInit`,
|
||||
payload: { code: 'yes_no' },
|
||||
};
|
||||
}
|
||||
|
||||
export function CODE_DETAIL(id) {
|
||||
return {
|
||||
type: `${CODE_NAMESPACE}/fetchDetail`,
|
||||
payload: { id },
|
||||
};
|
||||
}
|
||||
|
||||
export function CODE_CLEAR_DETAIL() {
|
||||
return {
|
||||
type: `${CODE_NAMESPACE}/clearDetail`,
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function CODE_SUBMIT(payload) {
|
||||
return {
|
||||
type: `${CODE_NAMESPACE}/submit`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function CODE_REMOVE(payload) {
|
||||
return {
|
||||
type: `${CODE_NAMESPACE}/remove`,
|
||||
payload,
|
||||
};
|
||||
}
|
36
src/actions/datasource.js
Normal file
36
src/actions/datasource.js
Normal file
@ -0,0 +1,36 @@
|
||||
export const DATASOURCE_NAMESPACE = 'datasource';
|
||||
|
||||
export function DATASOURCE_LIST(payload) {
|
||||
return {
|
||||
type: `${DATASOURCE_NAMESPACE}/fetchList`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function DATASOURCE_DETAIL(id) {
|
||||
return {
|
||||
type: `${DATASOURCE_NAMESPACE}/fetchDetail`,
|
||||
payload: { id },
|
||||
};
|
||||
}
|
||||
|
||||
export function DATASOURCE_CLEAR_DETAIL() {
|
||||
return {
|
||||
type: `${DATASOURCE_NAMESPACE}/clearDetail`,
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function DATASOURCE_SUBMIT(payload) {
|
||||
return {
|
||||
type: `${DATASOURCE_NAMESPACE}/submit`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function DATASOURCE_REMOVE(payload) {
|
||||
return {
|
||||
type: `${DATASOURCE_NAMESPACE}/remove`,
|
||||
payload,
|
||||
};
|
||||
}
|
43
src/actions/dept.js
Normal file
43
src/actions/dept.js
Normal file
@ -0,0 +1,43 @@
|
||||
export const DEPT_NAMESPACE = 'dept';
|
||||
|
||||
export function DEPT_LIST(payload) {
|
||||
return {
|
||||
type: `${DEPT_NAMESPACE}/fetchList`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function DEPT_INIT() {
|
||||
return {
|
||||
type: `${DEPT_NAMESPACE}/fetchInit`,
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function DEPT_DETAIL(id) {
|
||||
return {
|
||||
type: `${DEPT_NAMESPACE}/fetchDetail`,
|
||||
payload: { id },
|
||||
};
|
||||
}
|
||||
|
||||
export function DEPT_CLEAR_DETAIL() {
|
||||
return {
|
||||
type: `${DEPT_NAMESPACE}/clearDetail`,
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function DEPT_SUBMIT(payload) {
|
||||
return {
|
||||
type: `${DEPT_NAMESPACE}/submit`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function DEPT_REMOVE(payload) {
|
||||
return {
|
||||
type: `${DEPT_NAMESPACE}/remove`,
|
||||
payload,
|
||||
};
|
||||
}
|
43
src/actions/dict.js
Normal file
43
src/actions/dict.js
Normal file
@ -0,0 +1,43 @@
|
||||
export const DICT_NAMESPACE = 'dict';
|
||||
|
||||
export function DICT_LIST(payload) {
|
||||
return {
|
||||
type: `${DICT_NAMESPACE}/fetchList`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function DICT_INIT() {
|
||||
return {
|
||||
type: `${DICT_NAMESPACE}/fetchInit`,
|
||||
payload: { code: 'DICT' },
|
||||
};
|
||||
}
|
||||
|
||||
export function DICT_DETAIL(id) {
|
||||
return {
|
||||
type: `${DICT_NAMESPACE}/fetchDetail`,
|
||||
payload: { id },
|
||||
};
|
||||
}
|
||||
|
||||
export function DICT_CLEAR_DETAIL() {
|
||||
return {
|
||||
type: `${DICT_NAMESPACE}/clearDetail`,
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function DICT_SUBMIT(payload) {
|
||||
return {
|
||||
type: `${DICT_NAMESPACE}/submit`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function DICT_REMOVE(payload) {
|
||||
return {
|
||||
type: `${DICT_NAMESPACE}/remove`,
|
||||
payload,
|
||||
};
|
||||
}
|
43
src/actions/log.js
Normal file
43
src/actions/log.js
Normal file
@ -0,0 +1,43 @@
|
||||
export const LOG_NAMESPACE = 'log';
|
||||
|
||||
export function LOG_USUAL_LIST(payload) {
|
||||
return {
|
||||
type: `${LOG_NAMESPACE}/fetchUsualList`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function LOG_USUAL_DETAIL(id) {
|
||||
return {
|
||||
type: `${LOG_NAMESPACE}/fetchUsualDetail`,
|
||||
payload: { id },
|
||||
};
|
||||
}
|
||||
|
||||
export function LOG_API_LIST(payload) {
|
||||
return {
|
||||
type: `${LOG_NAMESPACE}/fetchApiList`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function LOG_API_DETAIL(id) {
|
||||
return {
|
||||
type: `${LOG_NAMESPACE}/fetchApiDetail`,
|
||||
payload: { id },
|
||||
};
|
||||
}
|
||||
|
||||
export function LOG_ERROR_LIST(payload) {
|
||||
return {
|
||||
type: `${LOG_NAMESPACE}/fetchErrorList`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function LOG_ERROR_DETAIL(id) {
|
||||
return {
|
||||
type: `${LOG_NAMESPACE}/fetchErrorDetail`,
|
||||
payload: { id },
|
||||
};
|
||||
}
|
61
src/actions/menu.js
Normal file
61
src/actions/menu.js
Normal file
@ -0,0 +1,61 @@
|
||||
import { getAuthority } from '../utils/authority';
|
||||
|
||||
export const MENU_NAMESPACE = 'menu';
|
||||
|
||||
export function MENU_REFRESH_DATA() {
|
||||
return {
|
||||
type: `${MENU_NAMESPACE}/fetchMenuData`,
|
||||
payload: { authority: getAuthority() },
|
||||
};
|
||||
}
|
||||
|
||||
export function MENU_LIST(payload) {
|
||||
return {
|
||||
type: `${MENU_NAMESPACE}/fetchList`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function MENU_INIT() {
|
||||
return {
|
||||
type: `${MENU_NAMESPACE}/fetchInit`,
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function MENU_DETAIL(id) {
|
||||
return {
|
||||
type: `${MENU_NAMESPACE}/fetchDetail`,
|
||||
payload: { id },
|
||||
};
|
||||
}
|
||||
|
||||
export function MENU_CLEAR_DETAIL() {
|
||||
return {
|
||||
type: `${MENU_NAMESPACE}/clearDetail`,
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function MENU_SUBMIT(payload) {
|
||||
return {
|
||||
type: `${MENU_NAMESPACE}/submit`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function MENU_REMOVE(payload) {
|
||||
return {
|
||||
type: `${MENU_NAMESPACE}/remove`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function MENU_SELECT_ICON(icon) {
|
||||
return {
|
||||
type: `${MENU_NAMESPACE}/selectIcon`,
|
||||
payload: {
|
||||
source: icon,
|
||||
},
|
||||
};
|
||||
}
|
36
src/actions/notice.js
Normal file
36
src/actions/notice.js
Normal file
@ -0,0 +1,36 @@
|
||||
export const NOTICE_NAMESPACE = 'notice';
|
||||
|
||||
export function NOTICE_LIST(payload) {
|
||||
return {
|
||||
type: `${NOTICE_NAMESPACE}/fetchList`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function NOTICE_INIT() {
|
||||
return {
|
||||
type: `${NOTICE_NAMESPACE}/fetchInit`,
|
||||
payload: { code: 'notice' },
|
||||
};
|
||||
}
|
||||
|
||||
export function NOTICE_DETAIL(id) {
|
||||
return {
|
||||
type: `${NOTICE_NAMESPACE}/fetchDetail`,
|
||||
payload: { id },
|
||||
};
|
||||
}
|
||||
|
||||
export function NOTICE_SUBMIT(payload) {
|
||||
return {
|
||||
type: `${NOTICE_NAMESPACE}/submit`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function NOTICE_REMOVE(payload) {
|
||||
return {
|
||||
type: `${NOTICE_NAMESPACE}/remove`,
|
||||
payload,
|
||||
};
|
||||
}
|
36
src/actions/param.js
Normal file
36
src/actions/param.js
Normal file
@ -0,0 +1,36 @@
|
||||
export const PARAM_NAMESPACE = 'param';
|
||||
|
||||
export function PARAM_LIST(payload) {
|
||||
return {
|
||||
type: `${PARAM_NAMESPACE}/fetchList`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function PARAM_DETAIL(id) {
|
||||
return {
|
||||
type: `${PARAM_NAMESPACE}/fetchDetail`,
|
||||
payload: { id },
|
||||
};
|
||||
}
|
||||
|
||||
export function PARAM_CLEAR_DETAIL() {
|
||||
return {
|
||||
type: `${PARAM_NAMESPACE}/clearDetail`,
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function PARAM_SUBMIT(payload) {
|
||||
return {
|
||||
type: `${PARAM_NAMESPACE}/submit`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function PARAM_REMOVE(payload) {
|
||||
return {
|
||||
type: `${PARAM_NAMESPACE}/remove`,
|
||||
payload,
|
||||
};
|
||||
}
|
72
src/actions/role.js
Normal file
72
src/actions/role.js
Normal file
@ -0,0 +1,72 @@
|
||||
export const ROLE_NAMESPACE = 'role';
|
||||
|
||||
export function ROLE_LIST(payload) {
|
||||
return {
|
||||
type: `${ROLE_NAMESPACE}/fetchList`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function ROLE_INIT() {
|
||||
return {
|
||||
type: `${ROLE_NAMESPACE}/fetchInit`,
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function ROLE_DETAIL(id) {
|
||||
return {
|
||||
type: `${ROLE_NAMESPACE}/fetchDetail`,
|
||||
payload: { id },
|
||||
};
|
||||
}
|
||||
|
||||
export function ROLE_CLEAR_DETAIL() {
|
||||
return {
|
||||
type: `${ROLE_NAMESPACE}/clearDetail`,
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function ROLE_GRANT_TREE(payload) {
|
||||
return {
|
||||
type: `${ROLE_NAMESPACE}/grantTree`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function ROLE_TREE_KEYS(payload) {
|
||||
return {
|
||||
type: `${ROLE_NAMESPACE}/roleTreeKeys`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function ROLE_SET_TREE_KEYS(payload) {
|
||||
return {
|
||||
type: `${ROLE_NAMESPACE}/setRoleTreeKeys`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function ROLE_GRANT(payload, callback) {
|
||||
return {
|
||||
type: `${ROLE_NAMESPACE}/grant`,
|
||||
payload,
|
||||
callback,
|
||||
};
|
||||
}
|
||||
|
||||
export function ROLE_SUBMIT(payload) {
|
||||
return {
|
||||
type: `${ROLE_NAMESPACE}/submit`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function ROLE_REMOVE(payload) {
|
||||
return {
|
||||
type: `${ROLE_NAMESPACE}/remove`,
|
||||
payload,
|
||||
};
|
||||
}
|
36
src/actions/tenant.js
Normal file
36
src/actions/tenant.js
Normal file
@ -0,0 +1,36 @@
|
||||
export const TENANT_NAMESPACE = 'tenant';
|
||||
|
||||
export function TENANT_LIST(payload) {
|
||||
return {
|
||||
type: `${TENANT_NAMESPACE}/fetchList`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function TENANT_DETAIL(id) {
|
||||
return {
|
||||
type: `${TENANT_NAMESPACE}/fetchDetail`,
|
||||
payload: { id },
|
||||
};
|
||||
}
|
||||
|
||||
export function TENANT_CLEAR_DETAIL() {
|
||||
return {
|
||||
type: `${TENANT_NAMESPACE}/clearDetail`,
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function TENANT_SUBMIT(payload) {
|
||||
return {
|
||||
type: `${TENANT_NAMESPACE}/submit`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function TENANT_REMOVE(payload) {
|
||||
return {
|
||||
type: `${TENANT_NAMESPACE}/remove`,
|
||||
payload,
|
||||
};
|
||||
}
|
58
src/actions/user.js
Normal file
58
src/actions/user.js
Normal file
@ -0,0 +1,58 @@
|
||||
export const USER_NAMESPACE = 'user';
|
||||
|
||||
export function USER_LIST(payload) {
|
||||
return {
|
||||
type: `${USER_NAMESPACE}/fetchList`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function USER_INIT() {
|
||||
return {
|
||||
type: `${USER_NAMESPACE}/fetchInit`,
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function USER_CHANGE_INIT(payload) {
|
||||
return {
|
||||
type: `${USER_NAMESPACE}/fetchChangeInit`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function USER_DETAIL(id) {
|
||||
return {
|
||||
type: `${USER_NAMESPACE}/fetchDetail`,
|
||||
payload: { id },
|
||||
};
|
||||
}
|
||||
|
||||
export function USER_ROLE_GRANT(payload, callback) {
|
||||
return {
|
||||
type: `${USER_NAMESPACE}/grant`,
|
||||
payload,
|
||||
callback,
|
||||
};
|
||||
}
|
||||
|
||||
export function USER_SUBMIT(payload) {
|
||||
return {
|
||||
type: `${USER_NAMESPACE}/submit`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function USER_UPDATE(payload) {
|
||||
return {
|
||||
type: `${USER_NAMESPACE}/update`,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
export function USER_REMOVE(payload) {
|
||||
return {
|
||||
type: `${USER_NAMESPACE}/remove`,
|
||||
payload,
|
||||
};
|
||||
}
|
43
src/app.js
Normal file
43
src/app.js
Normal file
@ -0,0 +1,43 @@
|
||||
import { routesAuthority } from './services/menu';
|
||||
|
||||
export const dva = {
|
||||
config: {
|
||||
onError(err) {
|
||||
err.preventDefault();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let authRoutes = {
|
||||
'/form/advanced-form': { authority: ['admin', 'user'] },
|
||||
};
|
||||
|
||||
function ergodicRoutes(routes, authKey, authority) {
|
||||
routes.forEach(element => {
|
||||
if (element.path === authKey) {
|
||||
if (!element.authority) element.authority = []; // eslint-disable-line
|
||||
Object.assign(element.authority, authority || []);
|
||||
} else if (element.routes) {
|
||||
ergodicRoutes(element.routes, authKey, authority);
|
||||
}
|
||||
return element;
|
||||
});
|
||||
}
|
||||
|
||||
export function patchRoutes(routes) {
|
||||
if (authRoutes !== null && authRoutes !== undefined) {
|
||||
Object.keys(authRoutes).map(authKey =>
|
||||
ergodicRoutes(routes, authKey, authRoutes[authKey].authority)
|
||||
);
|
||||
window.g_routes = routes;
|
||||
}
|
||||
}
|
||||
|
||||
export function render(oldRender) {
|
||||
routesAuthority().then(response => {
|
||||
if (response && response.success) {
|
||||
authRoutes = response.data;
|
||||
}
|
||||
oldRender();
|
||||
});
|
||||
}
|
160
src/assets/logo.svg
Normal file
160
src/assets/logo.svg
Normal file
@ -0,0 +1,160 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="87px" height="86px" viewBox="0 0 87 86" enable-background="new 0 0 87 86" xml:space="preserve"> <image id="image0" width="87" height="86" x="0" y="0"
|
||||
xlink:href="data:image/png;base64,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
|
||||
MzErMDg6MDC8c1XdAAAAAElFTkSuQmCC" />
|
||||
</svg>
|
101
src/components/ActiveChart/index.js
Normal file
101
src/components/ActiveChart/index.js
Normal file
@ -0,0 +1,101 @@
|
||||
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>
|
||||
<div className={styles.activeChartGrid}>
|
||||
<p>{[...activeData].sort()[activeData.length - 1].y + 200} 亿元</p>
|
||||
<p>{[...activeData].sort()[Math.floor(activeData.length / 2)].y} 亿元</p>
|
||||
</div>
|
||||
<div className={styles.dashedLine}>
|
||||
<div className={styles.line} />
|
||||
</div>
|
||||
<div className={styles.dashedLine}>
|
||||
<div className={styles.line} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{activeData && (
|
||||
<div className={styles.activeChartLegend}>
|
||||
<span>00:00</span>
|
||||
<span>{activeData[Math.floor(activeData.length / 2)].x}</span>
|
||||
<span>{activeData[activeData.length - 1].x}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
51
src/components/ActiveChart/index.less
Normal file
51
src/components/ActiveChart/index.less
Normal file
@ -0,0 +1,51 @@
|
||||
.activeChart {
|
||||
position: relative;
|
||||
}
|
||||
.activeChartGrid {
|
||||
p {
|
||||
position: absolute;
|
||||
top: 80px;
|
||||
}
|
||||
p:last-child {
|
||||
top: 115px;
|
||||
}
|
||||
}
|
||||
.activeChartLegend {
|
||||
position: relative;
|
||||
height: 20px;
|
||||
margin-top: 8px;
|
||||
font-size: 0;
|
||||
line-height: 20px;
|
||||
span {
|
||||
display: inline-block;
|
||||
width: 33.33%;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
span:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
span:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
.dashedLine {
|
||||
position: relative;
|
||||
top: -70px;
|
||||
left: -3px;
|
||||
height: 1px;
|
||||
|
||||
.line {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: linear-gradient(to right, transparent 50%, #e9e9e9 50%);
|
||||
background-size: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.dashedLine:last-child {
|
||||
top: -36px;
|
||||
}
|
54
src/components/AdvancedTable/withExpandRow.js
Normal file
54
src/components/AdvancedTable/withExpandRow.js
Normal file
@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
|
||||
export default ({
|
||||
expendAll = false,
|
||||
expandedRowRender,
|
||||
updateExpandRowKeys,
|
||||
initExpandedRowKeys,
|
||||
}) => {
|
||||
return WrappedComponent => {
|
||||
return class extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
expandedRowKeys: initExpandedRowKeys || [],
|
||||
expandRowByClick: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (initExpandedRowKeys) {
|
||||
this.onExpandedRowsChange(initExpandedRowKeys);
|
||||
}
|
||||
}
|
||||
|
||||
onExpandedRowsChange = rows => {
|
||||
if (updateExpandRowKeys) updateExpandRowKeys(rows);
|
||||
this.setState({
|
||||
expandedRowKeys: rows,
|
||||
});
|
||||
};
|
||||
|
||||
expandRow = row => {
|
||||
this.setState({
|
||||
expandedRowKeys: row ? [row] : [],
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { expandedRowKeys } = this.state;
|
||||
// const expandRowByClick = true;
|
||||
return (
|
||||
<WrappedComponent
|
||||
expandedRowRender={expandedRowRender}
|
||||
defaultExpandAllRows={expendAll}
|
||||
expandedRowKeys={expandedRowKeys}
|
||||
onExpandedRowsChange={this.onExpandedRowsChange}
|
||||
expandRow={this.expandRow}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
17
src/components/ArticleListContent/index.js
Normal file
17
src/components/ArticleListContent/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { Avatar } from 'antd';
|
||||
import styles from './index.less';
|
||||
|
||||
const ArticleListContent = ({ data: { content, updatedAt, avatar, owner, href } }) => (
|
||||
<div className={styles.listContent}>
|
||||
<div className={styles.description}>{content}</div>
|
||||
<div className={styles.extra}>
|
||||
<Avatar src={avatar} size="small" />
|
||||
<a href={href}>{owner}</a> 发布在 <a href={href}>{href}</a>
|
||||
<em>{moment(updatedAt).format('YYYY-MM-DD HH:mm')}</em>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ArticleListContent;
|
38
src/components/ArticleListContent/index.less
Normal file
38
src/components/ArticleListContent/index.less
Normal file
@ -0,0 +1,38 @@
|
||||
@import '~antd/lib/style/themes/default.less';
|
||||
|
||||
.listContent {
|
||||
.description {
|
||||
max-width: 720px;
|
||||
line-height: 22px;
|
||||
}
|
||||
.extra {
|
||||
margin-top: 16px;
|
||||
color: @text-color-secondary;
|
||||
line-height: 22px;
|
||||
& > :global(.ant-avatar) {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 8px;
|
||||
vertical-align: top;
|
||||
}
|
||||
& > em {
|
||||
margin-left: 16px;
|
||||
color: @disabled-color;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: @screen-xs) {
|
||||
.listContent {
|
||||
.extra {
|
||||
& > em {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
src/components/Authorized/Authorized.js
Normal file
8
src/components/Authorized/Authorized.js
Normal file
@ -0,0 +1,8 @@
|
||||
import CheckPermissions from './CheckPermissions';
|
||||
|
||||
const Authorized = ({ children, authority, noMatch = null }) => {
|
||||
const childrenRender = typeof children === 'undefined' ? null : children;
|
||||
return CheckPermissions(authority, childrenRender, noMatch);
|
||||
};
|
||||
|
||||
export default Authorized;
|
13
src/components/Authorized/AuthorizedRoute.d.ts
vendored
Normal file
13
src/components/Authorized/AuthorizedRoute.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import { RouteProps } from 'react-router';
|
||||
|
||||
type authorityFN = (currentAuthority?: string) => boolean;
|
||||
|
||||
type authority = string | string[] | authorityFN | Promise<any>;
|
||||
|
||||
export interface IAuthorizedRouteProps extends RouteProps {
|
||||
authority: authority;
|
||||
}
|
||||
export { authority };
|
||||
|
||||
export class AuthorizedRoute extends React.Component<IAuthorizedRouteProps, any> {}
|
15
src/components/Authorized/AuthorizedRoute.js
Normal file
15
src/components/Authorized/AuthorizedRoute.js
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Route, Redirect } from 'react-router-dom';
|
||||
import Authorized from './Authorized';
|
||||
|
||||
// TODO: umi只会返回render和rest
|
||||
const AuthorizedRoute = ({ component: Component, render, authority, redirectPath, ...rest }) => (
|
||||
<Authorized
|
||||
authority={authority}
|
||||
noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />}
|
||||
>
|
||||
<Route {...rest} render={props => (Component ? <Component {...props} /> : render(props))} />
|
||||
</Authorized>
|
||||
);
|
||||
|
||||
export default AuthorizedRoute;
|
88
src/components/Authorized/CheckPermissions.js
Normal file
88
src/components/Authorized/CheckPermissions.js
Normal file
@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
import PromiseRender from './PromiseRender';
|
||||
import { CURRENT } from './renderAuthorize';
|
||||
|
||||
function isPromise(obj) {
|
||||
return (
|
||||
!!obj &&
|
||||
(typeof obj === 'object' || typeof obj === 'function') &&
|
||||
typeof obj.then === 'function'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用权限检查方法
|
||||
* Common check permissions method
|
||||
* @param { 权限判定 Permission judgment type string |array | Promise | Function } authority
|
||||
* @param { 你的权限 Your permission description type:string} currentAuthority
|
||||
* @param { 通过的组件 Passing components } target
|
||||
* @param { 未通过的组件 no pass components } Exception
|
||||
*/
|
||||
const checkPermissions = (authority, currentAuthority, target, Exception) => {
|
||||
// 没有判定权限.默认查看所有
|
||||
// Retirement authority, return target;
|
||||
if (!authority) {
|
||||
return target;
|
||||
}
|
||||
// 数组处理
|
||||
if (Array.isArray(authority)) {
|
||||
if (authority.indexOf(currentAuthority) >= 0) {
|
||||
return target;
|
||||
}
|
||||
if (Array.isArray(currentAuthority)) {
|
||||
for (let i = 0; i < currentAuthority.length; i += 1) {
|
||||
const element = currentAuthority[i];
|
||||
if (authority.indexOf(element) >= 0) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Exception;
|
||||
}
|
||||
|
||||
// string 处理
|
||||
if (typeof authority === 'string') {
|
||||
if (authority === currentAuthority) {
|
||||
return target;
|
||||
}
|
||||
if (Array.isArray(currentAuthority)) {
|
||||
for (let i = 0; i < currentAuthority.length; i += 1) {
|
||||
const element = currentAuthority[i];
|
||||
if (authority === element) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Exception;
|
||||
}
|
||||
|
||||
// Promise 处理
|
||||
if (isPromise(authority)) {
|
||||
return <PromiseRender ok={target} error={Exception} promise={authority} />;
|
||||
}
|
||||
|
||||
// Function 处理
|
||||
if (typeof authority === 'function') {
|
||||
try {
|
||||
const bool = authority(currentAuthority);
|
||||
// 函数执行后返回值是 Promise
|
||||
if (isPromise(bool)) {
|
||||
return <PromiseRender ok={target} error={Exception} promise={bool} />;
|
||||
}
|
||||
if (bool) {
|
||||
return target;
|
||||
}
|
||||
return Exception;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
throw new Error('unsupported parameters');
|
||||
};
|
||||
|
||||
export { checkPermissions };
|
||||
|
||||
const check = (authority, target, Exception) =>
|
||||
checkPermissions(authority, CURRENT, target, Exception);
|
||||
|
||||
export default check;
|
55
src/components/Authorized/CheckPermissions.test.js
Normal file
55
src/components/Authorized/CheckPermissions.test.js
Normal file
@ -0,0 +1,55 @@
|
||||
import { checkPermissions } from './CheckPermissions';
|
||||
|
||||
const target = 'ok';
|
||||
const error = 'error';
|
||||
|
||||
describe('test CheckPermissions', () => {
|
||||
it('Correct string permission authentication', () => {
|
||||
expect(checkPermissions('user', 'user', target, error)).toEqual('ok');
|
||||
});
|
||||
it('Correct string permission authentication', () => {
|
||||
expect(checkPermissions('user', 'NULL', target, error)).toEqual('error');
|
||||
});
|
||||
it('authority is undefined , return ok', () => {
|
||||
expect(checkPermissions(null, 'NULL', target, error)).toEqual('ok');
|
||||
});
|
||||
it('currentAuthority is undefined , return error', () => {
|
||||
expect(checkPermissions('admin', null, target, error)).toEqual('error');
|
||||
});
|
||||
it('Wrong string permission authentication', () => {
|
||||
expect(checkPermissions('admin', 'user', target, error)).toEqual('error');
|
||||
});
|
||||
it('Correct Array permission authentication', () => {
|
||||
expect(checkPermissions(['user', 'admin'], 'user', target, error)).toEqual('ok');
|
||||
});
|
||||
it('Wrong Array permission authentication,currentAuthority error', () => {
|
||||
expect(checkPermissions(['user', 'admin'], 'user,admin', target, error)).toEqual('error');
|
||||
});
|
||||
it('Wrong Array permission authentication', () => {
|
||||
expect(checkPermissions(['user', 'admin'], 'guest', target, error)).toEqual('error');
|
||||
});
|
||||
it('Wrong Function permission authentication', () => {
|
||||
expect(checkPermissions(() => false, 'guest', target, error)).toEqual('error');
|
||||
});
|
||||
it('Correct Function permission authentication', () => {
|
||||
expect(checkPermissions(() => true, 'guest', target, error)).toEqual('ok');
|
||||
});
|
||||
it('authority is string, currentAuthority is array, return ok', () => {
|
||||
expect(checkPermissions('user', ['user'], target, error)).toEqual('ok');
|
||||
});
|
||||
it('authority is string, currentAuthority is array, return ok', () => {
|
||||
expect(checkPermissions('user', ['user', 'admin'], target, error)).toEqual('ok');
|
||||
});
|
||||
it('authority is array, currentAuthority is array, return ok', () => {
|
||||
expect(checkPermissions(['user', 'admin'], ['user', 'admin'], target, error)).toEqual('ok');
|
||||
});
|
||||
it('Wrong Function permission authentication', () => {
|
||||
expect(checkPermissions(() => false, ['user'], target, error)).toEqual('error');
|
||||
});
|
||||
it('Correct Function permission authentication', () => {
|
||||
expect(checkPermissions(() => true, ['user'], target, error)).toEqual('ok');
|
||||
});
|
||||
it('authority is undefined , return ok', () => {
|
||||
expect(checkPermissions(null, ['user'], target, error)).toEqual('ok');
|
||||
});
|
||||
});
|
65
src/components/Authorized/PromiseRender.js
Normal file
65
src/components/Authorized/PromiseRender.js
Normal file
@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import { Spin } from 'antd';
|
||||
|
||||
export default class PromiseRender extends React.PureComponent {
|
||||
state = {
|
||||
component: null,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.setRenderComponent(this.props);
|
||||
}
|
||||
|
||||
componentDidUpdate(nextProps) {
|
||||
// new Props enter
|
||||
this.setRenderComponent(nextProps);
|
||||
}
|
||||
|
||||
// set render Component : ok or error
|
||||
setRenderComponent(props) {
|
||||
const ok = this.checkIsInstantiation(props.ok);
|
||||
const error = this.checkIsInstantiation(props.error);
|
||||
props.promise
|
||||
.then(() => {
|
||||
this.setState({
|
||||
component: ok,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({
|
||||
component: error,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Determine whether the incoming component has been instantiated
|
||||
// AuthorizedRoute is already instantiated
|
||||
// Authorized render is already instantiated, children is no instantiated
|
||||
// Secured is not instantiated
|
||||
checkIsInstantiation = target => {
|
||||
if (!React.isValidElement(target)) {
|
||||
return target;
|
||||
}
|
||||
return () => target;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { component: Component } = this.state;
|
||||
const { ok, error, promise, ...rest } = this.props;
|
||||
return Component ? (
|
||||
<Component {...rest} />
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
margin: 'auto',
|
||||
paddingTop: 50,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
55
src/components/Authorized/Secured.js
Normal file
55
src/components/Authorized/Secured.js
Normal file
@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import Exception from '../Exception';
|
||||
import CheckPermissions from './CheckPermissions';
|
||||
/**
|
||||
* 默认不能访问任何页面
|
||||
* default is "NULL"
|
||||
*/
|
||||
const Exception403 = () => <Exception type="403" />;
|
||||
|
||||
// Determine whether the incoming component has been instantiated
|
||||
// AuthorizedRoute is already instantiated
|
||||
// Authorized render is already instantiated, children is no instantiated
|
||||
// Secured is not instantiated
|
||||
const checkIsInstantiation = target => {
|
||||
if (!React.isValidElement(target)) {
|
||||
return target;
|
||||
}
|
||||
return () => target;
|
||||
};
|
||||
|
||||
/**
|
||||
* 用于判断是否拥有权限访问此view权限
|
||||
* authority 支持传入 string, function:()=>boolean|Promise
|
||||
* e.g. 'user' 只有user用户能访问
|
||||
* e.g. 'user,admin' user和 admin 都能访问
|
||||
* e.g. ()=>boolean 返回true能访问,返回false不能访问
|
||||
* e.g. Promise then 能访问 catch不能访问
|
||||
* e.g. authority support incoming string, function: () => boolean | Promise
|
||||
* e.g. 'user' only user user can access
|
||||
* e.g. 'user, admin' user and admin can access
|
||||
* e.g. () => boolean true to be able to visit, return false can not be accessed
|
||||
* e.g. Promise then can not access the visit to catch
|
||||
* @param {string | function | Promise} authority
|
||||
* @param {ReactNode} error 非必需参数
|
||||
*/
|
||||
const authorize = (authority, error) => {
|
||||
/**
|
||||
* conversion into a class
|
||||
* 防止传入字符串时找不到staticContext造成报错
|
||||
* String parameters can cause staticContext not found error
|
||||
*/
|
||||
let classError = false;
|
||||
if (error) {
|
||||
classError = () => error;
|
||||
}
|
||||
if (!authority) {
|
||||
throw new Error('authority is required');
|
||||
}
|
||||
return function decideAuthority(target) {
|
||||
const component = CheckPermissions(authority, target, classError || Exception403);
|
||||
return checkIsInstantiation(component);
|
||||
};
|
||||
};
|
||||
|
||||
export default authorize;
|
23
src/components/Authorized/demo/AuthorizedArray.md
Normal file
23
src/components/Authorized/demo/AuthorizedArray.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
order: 1
|
||||
title:
|
||||
zh-CN: 使用数组作为参数
|
||||
en-US: Use Array as a parameter
|
||||
---
|
||||
|
||||
Use Array as a parameter
|
||||
|
||||
```jsx
|
||||
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
|
||||
import { Alert } from 'antd';
|
||||
|
||||
const Authorized = RenderAuthorized('user');
|
||||
const noMatch = <Alert message="No permission." type="error" showIcon />;
|
||||
|
||||
ReactDOM.render(
|
||||
<Authorized authority={['user', 'admin']} noMatch={noMatch}>
|
||||
<Alert message="Use Array as a parameter passed!" type="success" showIcon />
|
||||
</Authorized>,
|
||||
mountNode,
|
||||
);
|
||||
```
|
31
src/components/Authorized/demo/AuthorizedFunction.md
Normal file
31
src/components/Authorized/demo/AuthorizedFunction.md
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
order: 2
|
||||
title:
|
||||
zh-CN: 使用方法作为参数
|
||||
en-US: Use function as a parameter
|
||||
---
|
||||
|
||||
Use Function as a parameter
|
||||
|
||||
```jsx
|
||||
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
|
||||
import { Alert } from 'antd';
|
||||
|
||||
const Authorized = RenderAuthorized('user');
|
||||
const noMatch = <Alert message="No permission." type="error" showIcon />;
|
||||
|
||||
const havePermission = () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
<Authorized authority={havePermission} noMatch={noMatch}>
|
||||
<Alert
|
||||
message="Use Function as a parameter passed!"
|
||||
type="success"
|
||||
showIcon
|
||||
/>
|
||||
</Authorized>,
|
||||
mountNode,
|
||||
);
|
||||
```
|
25
src/components/Authorized/demo/basic.md
Normal file
25
src/components/Authorized/demo/basic.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
order: 0
|
||||
title:
|
||||
zh-CN: 基本使用
|
||||
en-US: Basic use
|
||||
---
|
||||
|
||||
Basic use
|
||||
|
||||
```jsx
|
||||
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
|
||||
import { Alert } from 'antd';
|
||||
|
||||
const Authorized = RenderAuthorized('user');
|
||||
const noMatch = <Alert message="No permission." type="error" showIcon />;
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<Authorized authority="admin" noMatch={noMatch}>
|
||||
<Alert message="user Passed!" type="success" showIcon />
|
||||
</Authorized>
|
||||
</div>,
|
||||
mountNode,
|
||||
);
|
||||
```
|
28
src/components/Authorized/demo/secured.md
Normal file
28
src/components/Authorized/demo/secured.md
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
order: 3
|
||||
title:
|
||||
zh-CN: 注解基本使用
|
||||
en-US: Basic use secured
|
||||
---
|
||||
|
||||
secured demo used
|
||||
|
||||
```jsx
|
||||
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
|
||||
import { Alert } from 'antd';
|
||||
|
||||
const { Secured } = RenderAuthorized('user');
|
||||
|
||||
@Secured('admin')
|
||||
class TestSecuredString extends React.Component {
|
||||
render() {
|
||||
<Alert message="user Passed!" type="success" showIcon />;
|
||||
}
|
||||
}
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<TestSecuredString />
|
||||
</div>,
|
||||
mountNode,
|
||||
);
|
||||
```
|
32
src/components/Authorized/index.d.ts
vendored
Normal file
32
src/components/Authorized/index.d.ts
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
import * as React from 'react';
|
||||
import AuthorizedRoute, { authority } from './AuthorizedRoute';
|
||||
export type IReactComponent<P = any> =
|
||||
| React.StatelessComponent<P>
|
||||
| React.ComponentClass<P>
|
||||
| React.ClassicComponentClass<P>;
|
||||
|
||||
type Secured = (
|
||||
authority: authority,
|
||||
error?: React.ReactNode
|
||||
) => <T extends IReactComponent>(target: T) => T;
|
||||
|
||||
type check = <T extends IReactComponent, S extends IReactComponent>(
|
||||
authority: authority,
|
||||
target: T,
|
||||
Exception: S
|
||||
) => T | S;
|
||||
|
||||
export interface IAuthorizedProps {
|
||||
authority: authority;
|
||||
noMatch?: React.ReactNode;
|
||||
}
|
||||
|
||||
export class Authorized extends React.Component<IAuthorizedProps, any> {
|
||||
public static Secured: Secured;
|
||||
public static AuthorizedRoute: typeof AuthorizedRoute;
|
||||
public static check: check;
|
||||
}
|
||||
|
||||
declare function renderAuthorize(currentAuthority: string): typeof Authorized;
|
||||
|
||||
export default renderAuthorize;
|
11
src/components/Authorized/index.js
Normal file
11
src/components/Authorized/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
import Authorized from './Authorized';
|
||||
import AuthorizedRoute from './AuthorizedRoute';
|
||||
import Secured from './Secured';
|
||||
import check from './CheckPermissions';
|
||||
import renderAuthorize from './renderAuthorize';
|
||||
|
||||
Authorized.Secured = Secured;
|
||||
Authorized.AuthorizedRoute = AuthorizedRoute;
|
||||
Authorized.check = check;
|
||||
|
||||
export default renderAuthorize(Authorized);
|
58
src/components/Authorized/index.md
Normal file
58
src/components/Authorized/index.md
Normal file
@ -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 | - |
|
25
src/components/Authorized/renderAuthorize.js
Normal file
25
src/components/Authorized/renderAuthorize.js
Normal file
@ -0,0 +1,25 @@
|
||||
/* eslint-disable import/no-mutable-exports */
|
||||
let CURRENT = 'NULL';
|
||||
/**
|
||||
* use authority or getAuthority
|
||||
* @param {string|()=>String} currentAuthority
|
||||
*/
|
||||
const renderAuthorize = Authorized => currentAuthority => {
|
||||
if (currentAuthority) {
|
||||
if (typeof currentAuthority === 'function') {
|
||||
CURRENT = currentAuthority();
|
||||
}
|
||||
if (
|
||||
Object.prototype.toString.call(currentAuthority) === '[object String]' ||
|
||||
Array.isArray(currentAuthority)
|
||||
) {
|
||||
CURRENT = currentAuthority;
|
||||
}
|
||||
} else {
|
||||
CURRENT = 'NULL';
|
||||
}
|
||||
return Authorized;
|
||||
};
|
||||
|
||||
export { CURRENT };
|
||||
export default Authorized => renderAuthorize(Authorized);
|
10
src/components/AvatarList/AvatarItem.d.ts
vendored
Normal file
10
src/components/AvatarList/AvatarItem.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
import * as React from 'react';
|
||||
export interface IAvatarItemProps {
|
||||
tips: React.ReactNode;
|
||||
src: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default class AvatarItem extends React.Component<IAvatarItemProps, any> {
|
||||
constructor(props: IAvatarItemProps);
|
||||
}
|
24
src/components/AvatarList/demo/maxLength.md
Normal file
24
src/components/AvatarList/demo/maxLength.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
order: 0
|
||||
title:
|
||||
zh-CN: 要显示的最大项目
|
||||
en-US: Max Items to Show
|
||||
---
|
||||
|
||||
`maxLength` attribute specifies the maximum number of items to show while `excessItemsStyle` style the excess
|
||||
item component.
|
||||
|
||||
````jsx
|
||||
import AvatarList from 'ant-design-pro/lib/AvatarList';
|
||||
|
||||
ReactDOM.render(
|
||||
<AvatarList size="mini" maxLength={3} excessItemsStyle={{ color: '#f56a00', backgroundColor: '#fde3cf' }}>
|
||||
<AvatarList.Item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
|
||||
<AvatarList.Item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
|
||||
<AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||
<AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||
<AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||
<AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||
</AvatarList>
|
||||
, mountNode);
|
||||
````
|
20
src/components/AvatarList/demo/simple.md
Normal file
20
src/components/AvatarList/demo/simple.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
order: 0
|
||||
title:
|
||||
zh-CN: 基础样例
|
||||
en-US: Basic Usage
|
||||
---
|
||||
|
||||
Simplest of usage.
|
||||
|
||||
````jsx
|
||||
import AvatarList from 'ant-design-pro/lib/AvatarList';
|
||||
|
||||
ReactDOM.render(
|
||||
<AvatarList size="mini">
|
||||
<AvatarList.Item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
|
||||
<AvatarList.Item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
|
||||
<AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
|
||||
</AvatarList>
|
||||
, mountNode);
|
||||
````
|
14
src/components/AvatarList/index.d.ts
vendored
Normal file
14
src/components/AvatarList/index.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import AvatarItem from './AvatarItem';
|
||||
|
||||
export interface IAvatarListProps {
|
||||
size?: 'large' | 'small' | 'mini' | 'default';
|
||||
maxLength?: number;
|
||||
excessItemsStyle?: React.CSSProperties;
|
||||
style?: React.CSSProperties;
|
||||
children: React.ReactElement<AvatarItem> | Array<React.ReactElement<AvatarItem>>;
|
||||
}
|
||||
|
||||
export default class AvatarList extends React.Component<IAvatarListProps, any> {
|
||||
public static Item: typeof AvatarItem;
|
||||
}
|
24
src/components/AvatarList/index.en-US.md
Normal file
24
src/components/AvatarList/index.en-US.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
title: AvatarList
|
||||
order: 1
|
||||
cols: 1
|
||||
---
|
||||
|
||||
A list of user's avatar for project or group member list frequently. If a large or small AvatarList is desired, set the `size` property to either `large` or `small` and `mini` respectively. Omit the `size` property for a AvatarList with the default size.
|
||||
|
||||
## API
|
||||
|
||||
### AvatarList
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
| ---------------- | --------------------- | ---------------------------------- | --------- |
|
||||
| size | size of list | `large`、`small` 、`mini`, `default` | `default` |
|
||||
| maxLength | max items to show | number | - |
|
||||
| excessItemsStyle | the excess item style | CSSProperties | - |
|
||||
|
||||
### AvatarList.Item
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
| -------- | -------------------------------------------- | --------- | ------- |
|
||||
| tips | title tips for avatar item | ReactNode | - |
|
||||
| src | the address of the image for an image avatar | string | - |
|
61
src/components/AvatarList/index.js
Normal file
61
src/components/AvatarList/index.js
Normal file
@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import { Tooltip, Avatar } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import styles from './index.less';
|
||||
|
||||
const avatarSizeToClassName = size =>
|
||||
classNames(styles.avatarItem, {
|
||||
[styles.avatarItemLarge]: size === 'large',
|
||||
[styles.avatarItemSmall]: size === 'small',
|
||||
[styles.avatarItemMini]: size === 'mini',
|
||||
});
|
||||
|
||||
const AvatarList = ({ children, size, maxLength, excessItemsStyle, ...other }) => {
|
||||
const numOfChildren = React.Children.count(children);
|
||||
const numToShow = maxLength >= numOfChildren ? numOfChildren : maxLength;
|
||||
|
||||
const childrenWithProps = React.Children.toArray(children)
|
||||
.slice(0, numToShow)
|
||||
.map(child =>
|
||||
React.cloneElement(child, {
|
||||
size,
|
||||
})
|
||||
);
|
||||
|
||||
if (numToShow < numOfChildren) {
|
||||
const cls = avatarSizeToClassName(size);
|
||||
|
||||
childrenWithProps.push(
|
||||
<li key="exceed" className={cls}>
|
||||
<Avatar size={size} style={excessItemsStyle}>{`+${numOfChildren - maxLength}`}</Avatar>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...other} className={styles.avatarList}>
|
||||
<ul> {childrenWithProps} </ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Item = ({ src, size, tips, onClick = () => {} }) => {
|
||||
const cls = avatarSizeToClassName(size);
|
||||
|
||||
return (
|
||||
<li className={cls} onClick={onClick}>
|
||||
{tips ? (
|
||||
<Tooltip title={tips}>
|
||||
<Avatar src={src} size={size} style={{ cursor: 'pointer' }} />
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Avatar src={src} size={size} />
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
AvatarList.Item = Item;
|
||||
|
||||
export default AvatarList;
|
50
src/components/AvatarList/index.less
Normal file
50
src/components/AvatarList/index.less
Normal file
@ -0,0 +1,50 @@
|
||||
@import '~antd/lib/style/themes/default.less';
|
||||
|
||||
.avatarList {
|
||||
display: inline-block;
|
||||
ul {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
font-size: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.avatarItem {
|
||||
display: inline-block;
|
||||
width: @avatar-size-base;
|
||||
height: @avatar-size-base;
|
||||
margin-left: -8px;
|
||||
font-size: @font-size-base;
|
||||
:global {
|
||||
.ant-avatar {
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.avatarItemLarge {
|
||||
width: @avatar-size-lg;
|
||||
height: @avatar-size-lg;
|
||||
}
|
||||
|
||||
.avatarItemSmall {
|
||||
width: @avatar-size-sm;
|
||||
height: @avatar-size-sm;
|
||||
}
|
||||
|
||||
.avatarItemMini {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
:global {
|
||||
.ant-avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
|
||||
.ant-avatar-string {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
src/components/AvatarList/index.test.js
Normal file
29
src/components/AvatarList/index.test.js
Normal file
@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import range from 'lodash/range';
|
||||
import { mount } from 'enzyme';
|
||||
import AvatarList from './index';
|
||||
|
||||
const renderItems = numItems =>
|
||||
range(numItems).map(i => (
|
||||
<AvatarList.Item
|
||||
key={i}
|
||||
tips="Jake"
|
||||
src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png"
|
||||
/>
|
||||
));
|
||||
|
||||
describe('AvatarList', () => {
|
||||
it('renders all items', () => {
|
||||
const wrapper = mount(<AvatarList>{renderItems(4)}</AvatarList>);
|
||||
expect(wrapper.find('AvatarList').length).toBe(1);
|
||||
expect(wrapper.find('Item').length).toBe(4);
|
||||
expect(wrapper.findWhere(node => node.key() === 'exceed').length).toBe(0);
|
||||
});
|
||||
|
||||
it('renders max of 3 items', () => {
|
||||
const wrapper = mount(<AvatarList maxLength={3}>{renderItems(4)}</AvatarList>);
|
||||
expect(wrapper.find('AvatarList').length).toBe(1);
|
||||
expect(wrapper.find('Item').length).toBe(3);
|
||||
expect(wrapper.findWhere(node => node.key() === 'exceed').length).toBe(1);
|
||||
});
|
||||
});
|
25
src/components/AvatarList/index.zh-CN.md
Normal file
25
src/components/AvatarList/index.zh-CN.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
title: AvatarList
|
||||
subtitle: 用户头像列表
|
||||
order: 1
|
||||
cols: 1
|
||||
---
|
||||
|
||||
一组用户头像,常用在项目/团队成员列表。可通过设置 `size` 属性来指定头像大小。
|
||||
|
||||
## API
|
||||
|
||||
### AvatarList
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| ---------------- | -------- | ---------------------------------- | --------- |
|
||||
| size | 头像大小 | `large`、`small` 、`mini`, `default` | `default` |
|
||||
| maxLength | 要显示的最大项目 | number | - |
|
||||
| excessItemsStyle | 多余的项目风格 | CSSProperties | - |
|
||||
|
||||
### AvatarList.Item
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| ---- | ------ | --------- | --- |
|
||||
| tips | 头像展示文案 | ReactNode | - |
|
||||
| src | 头像图片连接 | string | - |
|
15
src/components/Charts/Bar/index.d.ts
vendored
Normal file
15
src/components/Charts/Bar/index.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
import * as React from 'react';
|
||||
export interface IBarProps {
|
||||
title: React.ReactNode;
|
||||
color?: string;
|
||||
padding?: [number, number, number, number];
|
||||
height: number;
|
||||
data: Array<{
|
||||
x: string;
|
||||
y: number;
|
||||
}>;
|
||||
autoLabel?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default class Bar extends React.Component<IBarProps, any> {}
|
113
src/components/Charts/Bar/index.js
Normal file
113
src/components/Charts/Bar/index.js
Normal file
@ -0,0 +1,113 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Chart, Axis, Tooltip, Geom } from 'bizcharts';
|
||||
import Debounce from 'lodash-decorators/debounce';
|
||||
import Bind from 'lodash-decorators/bind';
|
||||
import autoHeight from '../autoHeight';
|
||||
import styles from '../index.less';
|
||||
|
||||
@autoHeight()
|
||||
class Bar extends Component {
|
||||
state = {
|
||||
autoHideXLabels: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('resize', this.resize, { passive: true });
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.resize);
|
||||
}
|
||||
|
||||
handleRoot = n => {
|
||||
this.root = n;
|
||||
};
|
||||
|
||||
handleRef = n => {
|
||||
this.node = n;
|
||||
};
|
||||
|
||||
@Bind()
|
||||
@Debounce(400)
|
||||
resize() {
|
||||
if (!this.node) {
|
||||
return;
|
||||
}
|
||||
const canvasWidth = this.node.parentNode.clientWidth;
|
||||
const { data = [], autoLabel = true } = this.props;
|
||||
if (!autoLabel) {
|
||||
return;
|
||||
}
|
||||
const minWidth = data.length * 30;
|
||||
const { autoHideXLabels } = this.state;
|
||||
|
||||
if (canvasWidth <= minWidth) {
|
||||
if (!autoHideXLabels) {
|
||||
this.setState({
|
||||
autoHideXLabels: true,
|
||||
});
|
||||
}
|
||||
} else if (autoHideXLabels) {
|
||||
this.setState({
|
||||
autoHideXLabels: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
height,
|
||||
title,
|
||||
forceFit = true,
|
||||
data,
|
||||
color = 'rgba(24, 144, 255, 0.85)',
|
||||
padding,
|
||||
} = this.props;
|
||||
|
||||
const { autoHideXLabels } = this.state;
|
||||
|
||||
const scale = {
|
||||
x: {
|
||||
type: 'cat',
|
||||
},
|
||||
y: {
|
||||
min: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const tooltip = [
|
||||
'x*y',
|
||||
(x, y) => ({
|
||||
name: x,
|
||||
value: y,
|
||||
}),
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles.chart} style={{ height }} ref={this.handleRoot}>
|
||||
<div ref={this.handleRef}>
|
||||
{title && <h4 style={{ marginBottom: 20 }}>{title}</h4>}
|
||||
<Chart
|
||||
scale={scale}
|
||||
height={title ? height - 41 : height}
|
||||
forceFit={forceFit}
|
||||
data={data}
|
||||
padding={padding || 'auto'}
|
||||
>
|
||||
<Axis
|
||||
name="x"
|
||||
title={false}
|
||||
label={autoHideXLabels ? false : {}}
|
||||
tickLine={autoHideXLabels ? false : {}}
|
||||
/>
|
||||
<Axis name="y" min={0} />
|
||||
<Tooltip showTitle={false} crosshairs={false} />
|
||||
<Geom type="interval" position="x*y" color={color} tooltip={tooltip} />
|
||||
</Chart>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Bar;
|
14
src/components/Charts/ChartCard/index.d.ts
vendored
Normal file
14
src/components/Charts/ChartCard/index.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
import { CardProps } from 'antd/lib/card';
|
||||
import * as React from 'react';
|
||||
|
||||
export interface IChartCardProps extends CardProps {
|
||||
title: React.ReactNode;
|
||||
action?: React.ReactNode;
|
||||
total?: React.ReactNode | number | (() => React.ReactNode | number);
|
||||
footer?: React.ReactNode;
|
||||
contentHeight?: number;
|
||||
avatar?: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default class ChartCard extends React.Component<IChartCardProps, any> {}
|
82
src/components/Charts/ChartCard/index.js
Normal file
82
src/components/Charts/ChartCard/index.js
Normal file
@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import { Card } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import styles from './index.less';
|
||||
|
||||
const renderTotal = total => {
|
||||
let totalDom;
|
||||
switch (typeof total) {
|
||||
case 'undefined':
|
||||
totalDom = null;
|
||||
break;
|
||||
case 'function':
|
||||
totalDom = <div className={styles.total}>{total()}</div>;
|
||||
break;
|
||||
default:
|
||||
totalDom = <div className={styles.total}>{total}</div>;
|
||||
}
|
||||
return totalDom;
|
||||
};
|
||||
|
||||
class ChartCard extends React.PureComponent {
|
||||
renderConnet = () => {
|
||||
const { contentHeight, title, avatar, action, total, footer, children, loading } = this.props;
|
||||
if (loading) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
<div className={styles.chartCard}>
|
||||
<div
|
||||
className={classNames(styles.chartTop, {
|
||||
[styles.chartTopMargin]: !children && !footer,
|
||||
})}
|
||||
>
|
||||
<div className={styles.avatar}>{avatar}</div>
|
||||
<div className={styles.metaWrap}>
|
||||
<div className={styles.meta}>
|
||||
<span className={styles.title}>{title}</span>
|
||||
<span className={styles.action}>{action}</span>
|
||||
</div>
|
||||
{renderTotal(total)}
|
||||
</div>
|
||||
</div>
|
||||
{children && (
|
||||
<div className={styles.content} style={{ height: contentHeight || 'auto' }}>
|
||||
<div className={contentHeight && styles.contentFixed}>{children}</div>
|
||||
</div>
|
||||
)}
|
||||
{footer && (
|
||||
<div
|
||||
className={classNames(styles.footer, {
|
||||
[styles.footerMargin]: !children,
|
||||
})}
|
||||
>
|
||||
{footer}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
loading = false,
|
||||
contentHeight,
|
||||
title,
|
||||
avatar,
|
||||
action,
|
||||
total,
|
||||
footer,
|
||||
children,
|
||||
...rest
|
||||
} = this.props;
|
||||
return (
|
||||
<Card loading={loading} bodyStyle={{ padding: '20px 24px 8px 24px' }} {...rest}>
|
||||
{this.renderConnet()}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChartCard;
|
75
src/components/Charts/ChartCard/index.less
Normal file
75
src/components/Charts/ChartCard/index.less
Normal file
@ -0,0 +1,75 @@
|
||||
@import '~antd/lib/style/themes/default.less';
|
||||
|
||||
.chartCard {
|
||||
position: relative;
|
||||
.chartTop {
|
||||
position: relative;
|
||||
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: 4px;
|
||||
right: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
}
|
8
src/components/Charts/Field/index.d.ts
vendored
Normal file
8
src/components/Charts/Field/index.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import * as React from 'react';
|
||||
export interface IFieldProps {
|
||||
label: React.ReactNode;
|
||||
value: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default class Field extends React.Component<IFieldProps, any> {}
|
12
src/components/Charts/Field/index.js
Normal file
12
src/components/Charts/Field/index.js
Normal file
@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
import styles from './index.less';
|
||||
|
||||
const Field = ({ label, value, ...rest }) => (
|
||||
<div className={styles.field} {...rest}>
|
||||
<span className={styles.label}>{label}</span>
|
||||
<span className={styles.number}>{value}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Field;
|
17
src/components/Charts/Field/index.less
Normal file
17
src/components/Charts/Field/index.less
Normal file
@ -0,0 +1,17 @@
|
||||
@import '~antd/lib/style/themes/default.less';
|
||||
|
||||
.field {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
.label,
|
||||
.number {
|
||||
font-size: @font-size-base;
|
||||
line-height: 22px;
|
||||
}
|
||||
.number {
|
||||
margin-left: 8px;
|
||||
color: @heading-color;
|
||||
}
|
||||
}
|
11
src/components/Charts/Gauge/index.d.ts
vendored
Normal file
11
src/components/Charts/Gauge/index.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
import * as React from 'react';
|
||||
export interface IGaugeProps {
|
||||
title: React.ReactNode;
|
||||
color?: string;
|
||||
height: number;
|
||||
bgColor?: number;
|
||||
percent: number;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default class Gauge extends React.Component<IGaugeProps, any> {}
|
167
src/components/Charts/Gauge/index.js
Normal file
167
src/components/Charts/Gauge/index.js
Normal file
@ -0,0 +1,167 @@
|
||||
import React from 'react';
|
||||
import { Chart, Geom, Axis, Coord, Guide, Shape } from 'bizcharts';
|
||||
import autoHeight from '../autoHeight';
|
||||
|
||||
const { Arc, Html, Line } = Guide;
|
||||
|
||||
const defaultFormatter = val => {
|
||||
switch (val) {
|
||||
case '2':
|
||||
return '差';
|
||||
case '4':
|
||||
return '中';
|
||||
case '6':
|
||||
return '良';
|
||||
case '8':
|
||||
return '优';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
Shape.registerShape('point', 'pointer', {
|
||||
drawShape(cfg, group) {
|
||||
let point = cfg.points[0];
|
||||
point = this.parsePoint(point);
|
||||
const center = this.parsePoint({
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
group.addShape('line', {
|
||||
attrs: {
|
||||
x1: center.x,
|
||||
y1: center.y,
|
||||
x2: point.x,
|
||||
y2: point.y,
|
||||
stroke: cfg.color,
|
||||
lineWidth: 2,
|
||||
lineCap: 'round',
|
||||
},
|
||||
});
|
||||
return group.addShape('circle', {
|
||||
attrs: {
|
||||
x: center.x,
|
||||
y: center.y,
|
||||
r: 6,
|
||||
stroke: cfg.color,
|
||||
lineWidth: 3,
|
||||
fill: '#fff',
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@autoHeight()
|
||||
class Gauge extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
height,
|
||||
percent,
|
||||
forceFit = true,
|
||||
formatter = defaultFormatter,
|
||||
color = '#2F9CFF',
|
||||
bgColor = '#F0F2F5',
|
||||
} = this.props;
|
||||
const cols = {
|
||||
value: {
|
||||
type: 'linear',
|
||||
min: 0,
|
||||
max: 10,
|
||||
tickCount: 6,
|
||||
nice: true,
|
||||
},
|
||||
};
|
||||
const data = [{ value: percent / 10 }];
|
||||
return (
|
||||
<Chart height={height} data={data} scale={cols} padding={[-16, 0, 16, 0]} forceFit={forceFit}>
|
||||
<Coord type="polar" startAngle={-1.25 * Math.PI} endAngle={0.25 * Math.PI} radius={0.8} />
|
||||
<Axis name="1" line={null} />
|
||||
<Axis
|
||||
line={null}
|
||||
tickLine={null}
|
||||
subTickLine={null}
|
||||
name="value"
|
||||
zIndex={2}
|
||||
gird={null}
|
||||
label={{
|
||||
offset: -12,
|
||||
formatter,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
fill: 'rgba(0, 0, 0, 0.65)',
|
||||
textAlign: 'center',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Guide>
|
||||
<Line
|
||||
start={[3, 0.905]}
|
||||
end={[3, 0.85]}
|
||||
lineStyle={{
|
||||
stroke: color,
|
||||
lineDash: null,
|
||||
lineWidth: 2,
|
||||
}}
|
||||
/>
|
||||
<Line
|
||||
start={[5, 0.905]}
|
||||
end={[5, 0.85]}
|
||||
lineStyle={{
|
||||
stroke: color,
|
||||
lineDash: null,
|
||||
lineWidth: 3,
|
||||
}}
|
||||
/>
|
||||
<Line
|
||||
start={[7, 0.905]}
|
||||
end={[7, 0.85]}
|
||||
lineStyle={{
|
||||
stroke: color,
|
||||
lineDash: null,
|
||||
lineWidth: 3,
|
||||
}}
|
||||
/>
|
||||
<Arc
|
||||
zIndex={0}
|
||||
start={[0, 0.965]}
|
||||
end={[10, 0.965]}
|
||||
style={{
|
||||
stroke: bgColor,
|
||||
lineWidth: 10,
|
||||
}}
|
||||
/>
|
||||
<Arc
|
||||
zIndex={1}
|
||||
start={[0, 0.965]}
|
||||
end={[data[0].value, 0.965]}
|
||||
style={{
|
||||
stroke: color,
|
||||
lineWidth: 10,
|
||||
}}
|
||||
/>
|
||||
<Html
|
||||
position={['50%', '95%']}
|
||||
html={() => `
|
||||
<div style="width: 300px;text-align: center;font-size: 12px!important;">
|
||||
<p style="font-size: 14px; color: rgba(0,0,0,0.43);margin: 0;">${title}</p>
|
||||
<p style="font-size: 24px;color: rgba(0,0,0,0.85);margin: 0;">
|
||||
${data[0].value * 10}%
|
||||
</p>
|
||||
</div>`}
|
||||
/>
|
||||
</Guide>
|
||||
<Geom
|
||||
line={false}
|
||||
type="point"
|
||||
position="value*1"
|
||||
shape="pointer"
|
||||
color={color}
|
||||
active={false}
|
||||
/>
|
||||
</Chart>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Gauge;
|
29
src/components/Charts/MiniArea/index.d.ts
vendored
Normal file
29
src/components/Charts/MiniArea/index.d.ts
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
import * as React from 'react';
|
||||
|
||||
// g2已经更新到3.0
|
||||
// 不带的写了
|
||||
|
||||
export interface IAxis {
|
||||
title: any;
|
||||
line: any;
|
||||
gridAlign: any;
|
||||
labels: any;
|
||||
tickLine: any;
|
||||
grid: any;
|
||||
}
|
||||
|
||||
export interface IMiniAreaProps {
|
||||
color?: string;
|
||||
height: number;
|
||||
borderColor?: string;
|
||||
line?: boolean;
|
||||
animate?: boolean;
|
||||
xAxis?: IAxis;
|
||||
yAxis?: IAxis;
|
||||
data: Array<{
|
||||
x: number | string;
|
||||
y: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
export default class MiniArea extends React.Component<IMiniAreaProps, any> {}
|
108
src/components/Charts/MiniArea/index.js
Normal file
108
src/components/Charts/MiniArea/index.js
Normal file
@ -0,0 +1,108 @@
|
||||
import React from 'react';
|
||||
import { Chart, Axis, Tooltip, Geom } from 'bizcharts';
|
||||
import autoHeight from '../autoHeight';
|
||||
import styles from '../index.less';
|
||||
|
||||
@autoHeight()
|
||||
class MiniArea extends React.PureComponent {
|
||||
render() {
|
||||
const {
|
||||
height,
|
||||
data = [],
|
||||
forceFit = true,
|
||||
color = 'rgba(24, 144, 255, 0.2)',
|
||||
borderColor = '#1089ff',
|
||||
scale = {},
|
||||
borderWidth = 2,
|
||||
line,
|
||||
xAxis,
|
||||
yAxis,
|
||||
animate = true,
|
||||
} = this.props;
|
||||
|
||||
const padding = [36, 5, 30, 5];
|
||||
|
||||
const scaleProps = {
|
||||
x: {
|
||||
type: 'cat',
|
||||
range: [0, 1],
|
||||
...scale.x,
|
||||
},
|
||||
y: {
|
||||
min: 0,
|
||||
...scale.y,
|
||||
},
|
||||
};
|
||||
|
||||
const tooltip = [
|
||||
'x*y',
|
||||
(x, y) => ({
|
||||
name: x,
|
||||
value: y,
|
||||
}),
|
||||
];
|
||||
|
||||
const chartHeight = height + 54;
|
||||
|
||||
return (
|
||||
<div className={styles.miniChart} style={{ height }}>
|
||||
<div className={styles.chartContent}>
|
||||
{height > 0 && (
|
||||
<Chart
|
||||
animate={animate}
|
||||
scale={scaleProps}
|
||||
height={chartHeight}
|
||||
forceFit={forceFit}
|
||||
data={data}
|
||||
padding={padding}
|
||||
>
|
||||
<Axis
|
||||
key="axis-x"
|
||||
name="x"
|
||||
label={false}
|
||||
line={false}
|
||||
tickLine={false}
|
||||
grid={false}
|
||||
{...xAxis}
|
||||
/>
|
||||
<Axis
|
||||
key="axis-y"
|
||||
name="y"
|
||||
label={false}
|
||||
line={false}
|
||||
tickLine={false}
|
||||
grid={false}
|
||||
{...yAxis}
|
||||
/>
|
||||
<Tooltip showTitle={false} crosshairs={false} />
|
||||
<Geom
|
||||
type="area"
|
||||
position="x*y"
|
||||
color={color}
|
||||
tooltip={tooltip}
|
||||
shape="smooth"
|
||||
style={{
|
||||
fillOpacity: 1,
|
||||
}}
|
||||
/>
|
||||
{line ? (
|
||||
<Geom
|
||||
type="line"
|
||||
position="x*y"
|
||||
shape="smooth"
|
||||
color={borderColor}
|
||||
size={borderWidth}
|
||||
tooltip={false}
|
||||
/>
|
||||
) : (
|
||||
<span style={{ display: 'none' }} />
|
||||
)}
|
||||
</Chart>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MiniArea;
|
12
src/components/Charts/MiniBar/index.d.ts
vendored
Normal file
12
src/components/Charts/MiniBar/index.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
import * as React from 'react';
|
||||
export interface IMiniBarProps {
|
||||
color?: string;
|
||||
height: number;
|
||||
data: Array<{
|
||||
x: number | string;
|
||||
y: number;
|
||||
}>;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default class MiniBar extends React.Component<IMiniBarProps, any> {}
|
51
src/components/Charts/MiniBar/index.js
Normal file
51
src/components/Charts/MiniBar/index.js
Normal file
@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import { Chart, Tooltip, Geom } from 'bizcharts';
|
||||
import autoHeight from '../autoHeight';
|
||||
import styles from '../index.less';
|
||||
|
||||
@autoHeight()
|
||||
class MiniBar extends React.Component {
|
||||
render() {
|
||||
const { height, forceFit = true, color = '#1890FF', data = [] } = this.props;
|
||||
|
||||
const scale = {
|
||||
x: {
|
||||
type: 'cat',
|
||||
},
|
||||
y: {
|
||||
min: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const padding = [36, 5, 30, 5];
|
||||
|
||||
const tooltip = [
|
||||
'x*y',
|
||||
(x, y) => ({
|
||||
name: x,
|
||||
value: y,
|
||||
}),
|
||||
];
|
||||
|
||||
// for tooltip not to be hide
|
||||
const chartHeight = height + 54;
|
||||
|
||||
return (
|
||||
<div className={styles.miniChart} style={{ height }}>
|
||||
<div className={styles.chartContent}>
|
||||
<Chart
|
||||
scale={scale}
|
||||
height={chartHeight}
|
||||
forceFit={forceFit}
|
||||
data={data}
|
||||
padding={padding}
|
||||
>
|
||||
<Tooltip showTitle={false} crosshairs={false} />
|
||||
<Geom type="interval" position="x*y" color={color} tooltip={tooltip} />
|
||||
</Chart>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default MiniBar;
|
10
src/components/Charts/MiniProgress/index.d.ts
vendored
Normal file
10
src/components/Charts/MiniProgress/index.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
import * as React from 'react';
|
||||
export interface IMiniProgressProps {
|
||||
target: number;
|
||||
color?: string;
|
||||
strokeWidth?: number;
|
||||
percent?: number;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default class MiniProgress extends React.Component<IMiniProgressProps, any> {}
|
27
src/components/Charts/MiniProgress/index.js
Normal file
27
src/components/Charts/MiniProgress/index.js
Normal file
@ -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;
|
35
src/components/Charts/MiniProgress/index.less
Normal file
35
src/components/Charts/MiniProgress/index.less
Normal file
@ -0,0 +1,35 @@
|
||||
@import '~antd/lib/style/themes/default.less';
|
||||
|
||||
.miniProgress {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 5px 0;
|
||||
.progressWrap {
|
||||
position: relative;
|
||||
background-color: @background-color-base;
|
||||
}
|
||||
.progress {
|
||||
width: 0;
|
||||
height: 100%;
|
||||
background-color: @primary-color;
|
||||
border-radius: 1px 0 0 1px;
|
||||
transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s;
|
||||
}
|
||||
.target {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
span {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 2px;
|
||||
height: 4px;
|
||||
border-radius: 100px;
|
||||
}
|
||||
span:last-child {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
21
src/components/Charts/Pie/index.d.ts
vendored
Normal file
21
src/components/Charts/Pie/index.d.ts
vendored
Normal file
@ -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> {}
|
271
src/components/Charts/Pie/index.js
Normal file
271
src/components/Charts/Pie/index.js
Normal file
@ -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;
|
94
src/components/Charts/Pie/index.less
Normal file
94
src/components/Charts/Pie/index.less
Normal file
@ -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;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
min-width: 200px;
|
||||
margin: 0 20px;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
transform: translateY(-50%);
|
||||
li {
|
||||
height: 22px;
|
||||
margin-bottom: 16px;
|
||||
line-height: 22px;
|
||||
cursor: pointer;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dot {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-right: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.line {
|
||||
display: inline-block;
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
margin-right: 8px;
|
||||
background-color: @border-color-split;
|
||||
}
|
||||
.legendTitle {
|
||||
color: @text-color;
|
||||
}
|
||||
.percent {
|
||||
color: @text-color-secondary;
|
||||
}
|
||||
.value {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
.title {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.total {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
max-height: 62px;
|
||||
text-align: center;
|
||||
transform: translate(-50%, -50%);
|
||||
& > h4 {
|
||||
height: 22px;
|
||||
margin-bottom: 8px;
|
||||
color: @text-color-secondary;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
& > p {
|
||||
display: block;
|
||||
height: 32px;
|
||||
color: @heading-color;
|
||||
font-size: 1.2em;
|
||||
line-height: 32px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.legendBlock {
|
||||
&.hasLegend .chart {
|
||||
width: 100%;
|
||||
margin: 0 0 32px 0;
|
||||
}
|
||||
.legend {
|
||||
position: relative;
|
||||
transform: none;
|
||||
}
|
||||
}
|
15
src/components/Charts/Radar/index.d.ts
vendored
Normal file
15
src/components/Charts/Radar/index.d.ts
vendored
Normal file
@ -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> {}
|
184
src/components/Charts/Radar/index.js
Normal file
184
src/components/Charts/Radar/index.js
Normal file
@ -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 = 5,
|
||||
padding = [35, 30, 16, 30],
|
||||
animate = true,
|
||||
colors = defaultColors,
|
||||
} = this.props;
|
||||
|
||||
const { legendData } = this.state;
|
||||
|
||||
const scale = {
|
||||
value: {
|
||||
min: 0,
|
||||
tickCount,
|
||||
},
|
||||
};
|
||||
|
||||
const chartHeight = height - (hasLegend ? 80 : 22);
|
||||
|
||||
return (
|
||||
<div className={styles.radar} style={{ height }}>
|
||||
{title && <h4>{title}</h4>}
|
||||
<Chart
|
||||
scale={scale}
|
||||
height={chartHeight}
|
||||
forceFit={forceFit}
|
||||
data={data}
|
||||
padding={padding}
|
||||
animate={animate}
|
||||
onGetG2Instance={this.getG2Instance}
|
||||
>
|
||||
<Tooltip />
|
||||
<Coord type="polar" />
|
||||
<Axis
|
||||
name="label"
|
||||
line={null}
|
||||
tickLine={null}
|
||||
grid={{
|
||||
lineStyle: {
|
||||
lineDash: null,
|
||||
},
|
||||
hideFirstLine: false,
|
||||
}}
|
||||
/>
|
||||
<Axis
|
||||
name="value"
|
||||
grid={{
|
||||
type: 'polygon',
|
||||
lineStyle: {
|
||||
lineDash: null,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Geom type="line" position="label*value" color={['name', colors]} size={1} />
|
||||
<Geom
|
||||
type="point"
|
||||
position="label*value"
|
||||
color={['name', colors]}
|
||||
shape="circle"
|
||||
size={3}
|
||||
/>
|
||||
</Chart>
|
||||
{hasLegend && (
|
||||
<Row className={styles.legend}>
|
||||
{legendData.map((item, i) => (
|
||||
<Col
|
||||
span={24 / legendData.length}
|
||||
key={item.name}
|
||||
onClick={() => this.handleLegendClick(item, i)}
|
||||
>
|
||||
<div className={styles.legendItem}>
|
||||
<p>
|
||||
<span
|
||||
className={styles.dot}
|
||||
style={{
|
||||
backgroundColor: !item.checked ? '#aaa' : item.color,
|
||||
}}
|
||||
/>
|
||||
<span>{item.name}</span>
|
||||
</p>
|
||||
<h6>{item.value}</h6>
|
||||
</div>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Radar;
|
46
src/components/Charts/Radar/index.less
Normal file
46
src/components/Charts/Radar/index.less
Normal file
@ -0,0 +1,46 @@
|
||||
@import '~antd/lib/style/themes/default.less';
|
||||
|
||||
.radar {
|
||||
.legend {
|
||||
margin-top: 16px;
|
||||
.legendItem {
|
||||
position: relative;
|
||||
color: @text-color-secondary;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
h6 {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 0;
|
||||
padding-left: 16px;
|
||||
color: @heading-color;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
}
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 0;
|
||||
width: 1px;
|
||||
height: 40px;
|
||||
background-color: @border-color-split;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
> :last-child .legendItem::after {
|
||||
display: none;
|
||||
}
|
||||
.dot {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
margin-right: 6px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
}
|
11
src/components/Charts/TagCloud/index.d.ts
vendored
Normal file
11
src/components/Charts/TagCloud/index.d.ts
vendored
Normal file
@ -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> {}
|
182
src/components/Charts/TagCloud/index.js
Normal file
182
src/components/Charts/TagCloud/index.js
Normal file
@ -0,0 +1,182 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Chart, Geom, Coord, Shape, Tooltip } from 'bizcharts';
|
||||
import DataSet from '@antv/data-set';
|
||||
import Debounce from 'lodash-decorators/debounce';
|
||||
import Bind from 'lodash-decorators/bind';
|
||||
import classNames from 'classnames';
|
||||
import autoHeight from '../autoHeight';
|
||||
import styles from './index.less';
|
||||
|
||||
/* eslint no-underscore-dangle: 0 */
|
||||
/* eslint no-param-reassign: 0 */
|
||||
|
||||
const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png';
|
||||
|
||||
@autoHeight()
|
||||
class TagCloud extends Component {
|
||||
state = {
|
||||
dv: null,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
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;
|
||||
const w = this.root.offsetWidth;
|
||||
|
||||
const onload = () => {
|
||||
const dv = new DataSet.View().source(data);
|
||||
const range = dv.range('value');
|
||||
const [min, max] = range;
|
||||
dv.transform({
|
||||
type: 'tag-cloud',
|
||||
fields: ['name', 'value'],
|
||||
imageMask: this.imageMask,
|
||||
font: 'Verdana',
|
||||
size: [w, h], // 宽高设置最好根据 imageMask 做调整
|
||||
padding: 0,
|
||||
timeInterval: 5000, // max execute time
|
||||
rotate() {
|
||||
return 0;
|
||||
},
|
||||
fontSize(d) {
|
||||
// eslint-disable-next-line
|
||||
return Math.pow((d.value - min) / (max - min), 2) * (17.5 - 5) + 5;
|
||||
},
|
||||
});
|
||||
|
||||
if (this.isUnmount) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
dv,
|
||||
w,
|
||||
h,
|
||||
});
|
||||
};
|
||||
|
||||
if (!this.imageMask) {
|
||||
this.imageMask = new Image();
|
||||
this.imageMask.crossOrigin = '';
|
||||
this.imageMask.src = imgUrl;
|
||||
|
||||
this.imageMask.onload = onload;
|
||||
} else {
|
||||
onload();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, height } = this.props;
|
||||
const { dv, w, h } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.tagCloud, className)}
|
||||
style={{ width: '100%', height }}
|
||||
ref={this.saveRootRef}
|
||||
>
|
||||
{dv && (
|
||||
<Chart
|
||||
width={w}
|
||||
height={h}
|
||||
data={dv}
|
||||
padding={0}
|
||||
scale={{
|
||||
x: { nice: false },
|
||||
y: { nice: false },
|
||||
}}
|
||||
>
|
||||
<Tooltip showTitle={false} />
|
||||
<Coord reflect="y" />
|
||||
<Geom
|
||||
type="point"
|
||||
position="x*y"
|
||||
color="text"
|
||||
shape="cloud"
|
||||
tooltip={[
|
||||
'text*value',
|
||||
function trans(text, value) {
|
||||
return { name: text, value };
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Chart>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TagCloud;
|
6
src/components/Charts/TagCloud/index.less
Normal file
6
src/components/Charts/TagCloud/index.less
Normal file
@ -0,0 +1,6 @@
|
||||
.tagCloud {
|
||||
overflow: hidden;
|
||||
canvas {
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
}
|
14
src/components/Charts/TimelineChart/index.d.ts
vendored
Normal file
14
src/components/Charts/TimelineChart/index.d.ts
vendored
Normal file
@ -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> {}
|
120
src/components/Charts/TimelineChart/index.js
Normal file
120
src/components/Charts/TimelineChart/index.js
Normal file
@ -0,0 +1,120 @@
|
||||
import React from 'react';
|
||||
import { Chart, Tooltip, Geom, Legend, Axis } from 'bizcharts';
|
||||
import DataSet from '@antv/data-set';
|
||||
import Slider from 'bizcharts-plugin-slider';
|
||||
import autoHeight from '../autoHeight';
|
||||
import styles from './index.less';
|
||||
|
||||
@autoHeight()
|
||||
class TimelineChart extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
height = 400,
|
||||
padding = [60, 20, 40, 40],
|
||||
titleMap = {
|
||||
y1: 'y1',
|
||||
y2: 'y2',
|
||||
},
|
||||
borderWidth = 2,
|
||||
data: sourceData,
|
||||
} = this.props;
|
||||
|
||||
const data = Array.isArray(sourceData) ? sourceData : [{ x: 0, y1: 0, y2: 0 }];
|
||||
|
||||
data.sort((a, b) => a.x - b.x);
|
||||
|
||||
let max;
|
||||
if (data[0] && data[0].y1 && data[0].y2) {
|
||||
max = Math.max(
|
||||
[...data].sort((a, b) => b.y1 - a.y1)[0].y1,
|
||||
[...data].sort((a, b) => b.y2 - a.y2)[0].y2
|
||||
);
|
||||
}
|
||||
|
||||
const ds = new DataSet({
|
||||
state: {
|
||||
start: data[0].x,
|
||||
end: data[data.length - 1].x,
|
||||
},
|
||||
});
|
||||
|
||||
const dv = ds.createView();
|
||||
dv.source(data)
|
||||
.transform({
|
||||
type: 'filter',
|
||||
callback: obj => {
|
||||
const date = obj.x;
|
||||
return date <= ds.state.end && date >= ds.state.start;
|
||||
},
|
||||
})
|
||||
.transform({
|
||||
type: 'map',
|
||||
callback(row) {
|
||||
const newRow = { ...row };
|
||||
newRow[titleMap.y1] = row.y1;
|
||||
newRow[titleMap.y2] = row.y2;
|
||||
return newRow;
|
||||
},
|
||||
})
|
||||
.transform({
|
||||
type: 'fold',
|
||||
fields: [titleMap.y1, titleMap.y2], // 展开字段集
|
||||
key: 'key', // key字段
|
||||
value: 'value', // value字段
|
||||
});
|
||||
|
||||
const timeScale = {
|
||||
type: 'time',
|
||||
tickInterval: 60 * 60 * 1000,
|
||||
mask: 'HH:mm',
|
||||
range: [0, 1],
|
||||
};
|
||||
|
||||
const cols = {
|
||||
x: timeScale,
|
||||
value: {
|
||||
max,
|
||||
min: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const SliderGen = () => (
|
||||
<Slider
|
||||
padding={[0, padding[1] + 20, 0, padding[3]]}
|
||||
width="auto"
|
||||
height={26}
|
||||
xAxis="x"
|
||||
yAxis="y1"
|
||||
scales={{ x: timeScale }}
|
||||
data={data}
|
||||
start={ds.state.start}
|
||||
end={ds.state.end}
|
||||
backgroundChart={{ type: 'line' }}
|
||||
onChange={({ startValue, endValue }) => {
|
||||
ds.setState('start', startValue);
|
||||
ds.setState('end', endValue);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.timelineChart} style={{ height: height + 30 }}>
|
||||
<div>
|
||||
{title && <h4>{title}</h4>}
|
||||
<Chart height={height} padding={padding} data={dv} scale={cols} forceFit>
|
||||
<Axis name="x" />
|
||||
<Tooltip />
|
||||
<Legend name="key" position="top" />
|
||||
<Geom type="line" position="x*value" size={borderWidth} color="key" />
|
||||
</Chart>
|
||||
<div style={{ marginRight: -20 }}>
|
||||
<SliderGen />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TimelineChart;
|
3
src/components/Charts/TimelineChart/index.less
Normal file
3
src/components/Charts/TimelineChart/index.less
Normal file
@ -0,0 +1,3 @@
|
||||
.timelineChart {
|
||||
background: #fff;
|
||||
}
|
10
src/components/Charts/WaterWave/index.d.ts
vendored
Normal file
10
src/components/Charts/WaterWave/index.d.ts
vendored
Normal file
@ -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> {}
|
213
src/components/Charts/WaterWave/index.js
Normal file
213
src/components/Charts/WaterWave/index.js
Normal file
@ -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;
|
28
src/components/Charts/WaterWave/index.less
Normal file
28
src/components/Charts/WaterWave/index.less
Normal file
@ -0,0 +1,28 @@
|
||||
@import '~antd/lib/style/themes/default.less';
|
||||
|
||||
.waterWave {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
transform-origin: left;
|
||||
.text {
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
span {
|
||||
color: @text-color-secondary;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
h4 {
|
||||
color: @heading-color;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
.waterWaveCanvasWrapper {
|
||||
transform: scale(0.5);
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
}
|
62
src/components/Charts/autoHeight.js
Normal file
62
src/components/Charts/autoHeight.js
Normal file
@ -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;
|
3
src/components/Charts/bizcharts.d.ts
vendored
Normal file
3
src/components/Charts/bizcharts.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
import * as BizChart from 'bizcharts';
|
||||
|
||||
export = BizChart;
|
3
src/components/Charts/bizcharts.js
Normal file
3
src/components/Charts/bizcharts.js
Normal file
@ -0,0 +1,3 @@
|
||||
import * as BizChart from 'bizcharts';
|
||||
|
||||
export default BizChart;
|
26
src/components/Charts/demo/bar.md
Normal file
26
src/components/Charts/demo/bar.md
Normal file
@ -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);
|
||||
````
|
95
src/components/Charts/demo/chart-card.md
Normal file
95
src/components/Charts/demo/chart-card.md
Normal file
@ -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,
|
||||
);
|
||||
```
|
18
src/components/Charts/demo/gauge.md
Normal file
18
src/components/Charts/demo/gauge.md
Normal file
@ -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);
|
||||
````
|
28
src/components/Charts/demo/mini-area.md
Normal file
28
src/components/Charts/demo/mini-area.md
Normal file
@ -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);
|
||||
````
|
28
src/components/Charts/demo/mini-bar.md
Normal file
28
src/components/Charts/demo/mini-bar.md
Normal file
@ -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);
|
||||
````
|
16
src/components/Charts/demo/mini-pie.md
Normal file
16
src/components/Charts/demo/mini-pie.md
Normal file
@ -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
|
||||
);
|
||||
```
|
12
src/components/Charts/demo/mini-progress.md
Normal file
12
src/components/Charts/demo/mini-progress.md
Normal file
@ -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);
|
||||
````
|
84
src/components/Charts/demo/mix.md
Normal file
84
src/components/Charts/demo/mix.md
Normal file
@ -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);
|
||||
````
|
54
src/components/Charts/demo/pie.md
Normal file
54
src/components/Charts/demo/pie.md
Normal file
@ -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,
|
||||
);
|
||||
```
|
64
src/components/Charts/demo/radar.md
Normal file
64
src/components/Charts/demo/radar.md
Normal file
@ -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);
|
||||
````
|
25
src/components/Charts/demo/tag-cloud.md
Normal file
25
src/components/Charts/demo/tag-cloud.md
Normal file
@ -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);
|
||||
````
|
27
src/components/Charts/demo/timeline-chart.md
Normal file
27
src/components/Charts/demo/timeline-chart.md
Normal file
@ -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);
|
||||
````
|
20
src/components/Charts/demo/waterwave.md
Normal file
20
src/components/Charts/demo/waterwave.md
Normal file
@ -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);
|
||||
````
|
15
src/components/Charts/g2.js
Normal file
15
src/components/Charts/g2.js
Normal file
@ -0,0 +1,15 @@
|
||||
// 全局 G2 设置
|
||||
import { track, setTheme } from 'bizcharts';
|
||||
|
||||
track(false);
|
||||
|
||||
const config = {
|
||||
defaultColor: '#1089ff',
|
||||
shape: {
|
||||
interval: {
|
||||
fillOpacity: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
setTheme(config);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user