173 changed files with 11798 additions and 0 deletions
@ -0,0 +1,16 @@ |
|||||
|
# http://editorconfig.org |
||||
|
root = true |
||||
|
|
||||
|
[*] |
||||
|
indent_style = space |
||||
|
indent_size = 2 |
||||
|
end_of_line = lf |
||||
|
charset = utf-8 |
||||
|
trim_trailing_whitespace = true |
||||
|
insert_final_newline = true |
||||
|
|
||||
|
[*.md] |
||||
|
trim_trailing_whitespace = false |
||||
|
|
||||
|
[Makefile] |
||||
|
indent_style = tab |
||||
@ -0,0 +1,40 @@ |
|||||
|
{ |
||||
|
"parser": "babel-eslint", |
||||
|
"extends": "airbnb", |
||||
|
"rules": { |
||||
|
"generator-star-spacing": [0], |
||||
|
"consistent-return": [0], |
||||
|
"react/forbid-prop-types": [0], |
||||
|
"react/jsx-filename-extension": [1, { "extensions": [".js"] }], |
||||
|
"global-require": [1], |
||||
|
"import/prefer-default-export": [0], |
||||
|
"react/jsx-no-bind": [0], |
||||
|
"react/prop-types": [0], |
||||
|
"react/prefer-stateless-function": [0], |
||||
|
"no-else-return": [0], |
||||
|
"no-restricted-syntax": [0], |
||||
|
"import/no-extraneous-dependencies": [0], |
||||
|
"no-use-before-define": [0], |
||||
|
"jsx-a11y/no-static-element-interactions": [0], |
||||
|
"jsx-a11y/no-noninteractive-element-interactions": [0], |
||||
|
"no-nested-ternary": [0], |
||||
|
"arrow-body-style": [0], |
||||
|
"import/extensions": [0], |
||||
|
"no-bitwise": [0], |
||||
|
"no-cond-assign": [0], |
||||
|
"import/no-unresolved": [0], |
||||
|
"comma-dangle": ["error", { |
||||
|
"arrays": "always-multiline", |
||||
|
"objects": "always-multiline", |
||||
|
"imports": "always-multiline", |
||||
|
"exports": "always-multiline", |
||||
|
"functions": "ignore" |
||||
|
}], |
||||
|
"require-yield": [1] |
||||
|
}, |
||||
|
"parserOptions": { |
||||
|
"ecmaFeatures": { |
||||
|
"experimentalObjectRestSpread": true |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. |
||||
|
|
||||
|
# dependencies |
||||
|
/node_modules |
||||
|
|
||||
|
# production |
||||
|
/dist |
||||
|
|
||||
|
# misc |
||||
|
.DS_Store |
||||
|
npm-debug.log* |
||||
@ -0,0 +1,26 @@ |
|||||
|
{ |
||||
|
"entry": "src/index.js", |
||||
|
"env": { |
||||
|
"development": { |
||||
|
"extraBabelPlugins": [ |
||||
|
"dva-hmr", |
||||
|
"transform-runtime", |
||||
|
"transform-decorators-legacy", |
||||
|
["import", { "libraryName": "antd", "style": true }] |
||||
|
] |
||||
|
}, |
||||
|
"production": { |
||||
|
"extraBabelPlugins": [ |
||||
|
"transform-runtime", |
||||
|
"transform-decorators-legacy", |
||||
|
["import", { "libraryName": "antd", "style": true }] |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
"theme": { |
||||
|
"font-size-base": "14px", |
||||
|
"badge-font-size": "12px", |
||||
|
"btn-font-size-lg": "@font-size-base", |
||||
|
"layout-body-background": "#f5f5f5" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,79 @@ |
|||||
|
import mockjs from 'mockjs'; |
||||
|
import { getRule, postRule } from './mock/rule'; |
||||
|
import { getActivities, getNotice, getFakeList } from './mock/api'; |
||||
|
import { getFakeChartData } from './mock/chart'; |
||||
|
import { imgMap } from './mock/utils'; |
||||
|
import { getProfileData } from './mock/profile'; |
||||
|
import { getNotices } from './mock/notices'; |
||||
|
import { format, delay } from 'roadhog-api-doc'; |
||||
|
|
||||
|
// 代码中会兼容本地 service mock 以及部署站点的静态数据
|
||||
|
|
||||
|
const proxy = { |
||||
|
// 支持值为 Object 和 Array
|
||||
|
'GET /api/currentUser': { |
||||
|
$desc: "获取当前用户接口", |
||||
|
$params: { |
||||
|
pageSize: { |
||||
|
desc: '分页', |
||||
|
exp: 2, |
||||
|
}, |
||||
|
}, |
||||
|
$body: { |
||||
|
name: 'momo.zxy', |
||||
|
avatar: imgMap.user, |
||||
|
userid: '00000001', |
||||
|
notifyCount: 12, |
||||
|
}, |
||||
|
}, |
||||
|
// GET POST 可省略
|
||||
|
'GET /api/users': [{ |
||||
|
key: '1', |
||||
|
name: 'John Brown', |
||||
|
age: 32, |
||||
|
address: 'New York No. 1 Lake Park', |
||||
|
}, { |
||||
|
key: '2', |
||||
|
name: 'Jim Green', |
||||
|
age: 42, |
||||
|
address: 'London No. 1 Lake Park', |
||||
|
}, { |
||||
|
key: '3', |
||||
|
name: 'Joe Black', |
||||
|
age: 32, |
||||
|
address: 'Sidney No. 1 Lake Park', |
||||
|
}], |
||||
|
'GET /api/project/notice': getNotice, |
||||
|
'GET /api/activities': getActivities, |
||||
|
'GET /api/rule': getRule, |
||||
|
'POST /api/rule': { |
||||
|
$params: { |
||||
|
pageSize: { |
||||
|
desc: '分页', |
||||
|
exp: 2, |
||||
|
}, |
||||
|
}, |
||||
|
$body: postRule, |
||||
|
}, |
||||
|
'POST /api/forms': (req, res) => { |
||||
|
res.send('Ok'); |
||||
|
}, |
||||
|
'GET /api/tags': mockjs.mock({ |
||||
|
'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }] |
||||
|
}), |
||||
|
'GET /api/fake_list': getFakeList, |
||||
|
'GET /api/fake_chart_data': getFakeChartData, |
||||
|
'GET /api/profile': getProfileData, |
||||
|
'POST /api/login/account': (req, res) => { |
||||
|
res.send({ status: 'error', type: 'account' }); |
||||
|
}, |
||||
|
'POST /api/login/mobile': (req, res) => { |
||||
|
res.send({ status: 'ok', type: 'mobile' }); |
||||
|
}, |
||||
|
'POST /api/register': (req, res) => { |
||||
|
res.send({ status: 'ok' }); |
||||
|
}, |
||||
|
'GET /api/notices': getNotices, |
||||
|
}; |
||||
|
|
||||
|
export default delay(proxy, 1000); |
||||
@ -0,0 +1,205 @@ |
|||||
|
import { imgMap, getUrlParams } from './utils'; |
||||
|
|
||||
|
export function fakeList(count) { |
||||
|
const titles = [ |
||||
|
'凤蝶', |
||||
|
'AntDesignPro', |
||||
|
'DesignLab', |
||||
|
'Basement', |
||||
|
'AntDesign', |
||||
|
'云雀', |
||||
|
'体验云', |
||||
|
'AntDesignMobile', |
||||
|
]; |
||||
|
const avatars = [ |
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/hYjIZrUoBfNxOAYBVDfc.png', // 凤蝶
|
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/HHWPIzPLCLYmVuPivyiA.png', // 云雀
|
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/irqByKtOdKfDojxIWTXF.png', // Basement
|
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/VcmdbCBcwPTGYgbYeMzX.png', // DesignLab
|
||||
|
]; |
||||
|
const covers = [ |
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png', |
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/xMPpMvGSIXusgtgUPAdw.png', |
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/hQReiajgtqzIVFjLXjHp.png', |
||||
|
'https://gw.alipayobjects.com/zos/rmsportal/nczfTaXEzhSpvgZZjBev.png', |
||||
|
]; |
||||
|
|
||||
|
const list = []; |
||||
|
for (let i = 0; i < count; i += 1) { |
||||
|
list.push({ |
||||
|
id: `fake-list-${i}`, |
||||
|
owner: '曲丽丽', |
||||
|
title: titles[i % 8], |
||||
|
avatar: avatars[i % 4], |
||||
|
cover: covers[i % 4], |
||||
|
status: ['active', 'exception', 'normal'][i % 3], |
||||
|
percent: Math.ceil(Math.random() * 50) + 50, |
||||
|
logo: ['https://gw.alipayobjects.com/zos/rmsportal/KoJjkdbuTFxzJmmjuDVR.png', 'https://gw.alipayobjects.com/zos/rmsportal/UxGORCvEXJEsxOfEKZiA.png'][i % 2], |
||||
|
href: 'https://ant.design', |
||||
|
updatedAt: new Date(new Date().getTime() - (1000 * 60 * 60 * 2 * i)), |
||||
|
createdAt: new Date(new Date().getTime() - (1000 * 60 * 60 * 2 * i)), |
||||
|
subDescription: '一句话描述一句话描述', |
||||
|
description: '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。', |
||||
|
activeUser: Math.ceil(Math.random() * 100000) + 100000, |
||||
|
newUser: Math.ceil(Math.random() * 1000) + 1000, |
||||
|
star: Math.ceil(Math.random() * 100) + 100, |
||||
|
like: Math.ceil(Math.random() * 100) + 100, |
||||
|
message: Math.ceil(Math.random() * 10) + 10, |
||||
|
content: '段落示意:蚂蚁金服设计平台 design.alipay.com,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。蚂蚁金服设计平台 design.alipay.com,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。', |
||||
|
members: [ |
||||
|
{ |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/WPOxPBHGyqsgKPsFtVlJ.png', |
||||
|
name: '王昭君', |
||||
|
}, |
||||
|
{ |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/WPOxPBHGyqsgKPsFtVlJ.png', |
||||
|
name: '王昭君', |
||||
|
}, |
||||
|
{ |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/WPOxPBHGyqsgKPsFtVlJ.png', |
||||
|
name: '王昭君', |
||||
|
}, |
||||
|
{ |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/WPOxPBHGyqsgKPsFtVlJ.png', |
||||
|
name: '王昭君', |
||||
|
}, |
||||
|
], |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return list; |
||||
|
} |
||||
|
|
||||
|
export function getFakeList(req, res, u) { |
||||
|
let url = u; |
||||
|
if (!url || Object.prototype.toString.call(url) !== '[object String]') { |
||||
|
url = req.url; |
||||
|
} |
||||
|
|
||||
|
const params = getUrlParams(url); |
||||
|
|
||||
|
const count = (params.count * 1) || 20; |
||||
|
|
||||
|
const result = fakeList(count); |
||||
|
|
||||
|
if (res && res.json) { |
||||
|
res.json(result); |
||||
|
} else { |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const getNotice = [ |
||||
|
{ |
||||
|
id: 'xxx1', |
||||
|
title: '消息列表体验优化', |
||||
|
logo: imgMap.b, |
||||
|
description: '这是一条描述信息这是一条描述信息', |
||||
|
updatedAt: new Date(), |
||||
|
member: '蜂鸟项目组', |
||||
|
}, |
||||
|
{ |
||||
|
id: 'xxx2', |
||||
|
title: 'XX 平台', |
||||
|
logo: imgMap.c, |
||||
|
description: '这是一条描述信息', |
||||
|
updatedAt: new Date('2017-07-24 11:00:00'), |
||||
|
member: '凤蝶精英小分队', |
||||
|
}, |
||||
|
{ |
||||
|
id: 'xxx3', |
||||
|
title: '消息列表体验优化', |
||||
|
logo: imgMap.a, |
||||
|
description: '这是一条描述信息这是一条描述信息', |
||||
|
updatedAt: new Date(), |
||||
|
member: '蜂鸟项目组', |
||||
|
}, |
||||
|
{ |
||||
|
id: 'xxx4', |
||||
|
title: '文档中心1', |
||||
|
logo: imgMap.a, |
||||
|
description: '这是一条描述信息这是一条描述信息', |
||||
|
updatedAt: new Date('2017-07-23 06:23:00'), |
||||
|
member: '成都超级小分队', |
||||
|
}, |
||||
|
{ |
||||
|
id: 'xxx5', |
||||
|
title: '文档中心2', |
||||
|
logo: imgMap.b, |
||||
|
description: '这是一条描述信息这是一条描述信息', |
||||
|
updatedAt: new Date('2017-07-23 06:23:00'), |
||||
|
member: '成都超级小分队', |
||||
|
}, |
||||
|
{ |
||||
|
id: 'xxx6', |
||||
|
title: '智能运营中心', |
||||
|
logo: imgMap.c, |
||||
|
description: '这是一条描述信息这是一条描述信息', |
||||
|
updatedAt: new Date('2017-07-23 06:23:00'), |
||||
|
member: '成都超级小分队', |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
export const getActivities = [ |
||||
|
{ |
||||
|
id: 'trend-1', |
||||
|
updatedAt: new Date(), |
||||
|
user: { |
||||
|
name: '林东东', |
||||
|
avatar: imgMap.a, |
||||
|
}, |
||||
|
action: '在 [凤蝶精英小分队](http://github.com/) 新建项目 [六月迭代](http://github.com/)', |
||||
|
}, |
||||
|
{ |
||||
|
id: 'trend-2', |
||||
|
updatedAt: new Date(), |
||||
|
user: { |
||||
|
name: '林嘻嘻', |
||||
|
avatar: imgMap.c, |
||||
|
}, |
||||
|
action: '在 [凤蝶精英小分队](http://github.com/) 新建项目 [六月迭代](http://github.com/)', |
||||
|
}, |
||||
|
{ |
||||
|
id: 'trend-3', |
||||
|
updatedAt: new Date(), |
||||
|
user: { |
||||
|
name: '林囡囡', |
||||
|
avatar: imgMap.b, |
||||
|
}, |
||||
|
action: '在 [凤蝶精英小分队](http://github.com/) 新建项目 [六月迭代](http://github.com/)', |
||||
|
}, |
||||
|
{ |
||||
|
id: 'trend-4', |
||||
|
updatedAt: new Date(), |
||||
|
user: { |
||||
|
name: '林贝贝', |
||||
|
avatar: imgMap.c, |
||||
|
}, |
||||
|
action: '在 [5 月日常迭代](http://github.com/) 更新至已发布状态', |
||||
|
}, |
||||
|
{ |
||||
|
id: 'trend-5', |
||||
|
updatedAt: new Date(), |
||||
|
user: { |
||||
|
name: '林忠忠', |
||||
|
avatar: imgMap.a, |
||||
|
}, |
||||
|
action: '在 [工程效能](http://github.com/) 发布了 [留言](http://github.com/)', |
||||
|
}, |
||||
|
{ |
||||
|
id: 'trend-6', |
||||
|
updatedAt: new Date(), |
||||
|
user: { |
||||
|
name: '林呜呜', |
||||
|
avatar: imgMap.d, |
||||
|
}, |
||||
|
action: '在 [云雀](http://github.com/) 新建项目 [品牌迭代](http://github.com/)', |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
|
||||
|
export default { |
||||
|
getNotice, |
||||
|
getActivities, |
||||
|
getFakeList, |
||||
|
}; |
||||
@ -0,0 +1,184 @@ |
|||||
|
import moment from 'moment'; |
||||
|
|
||||
|
// mock data
|
||||
|
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, |
||||
|
}); |
||||
|
} |
||||
|
const salesData = []; |
||||
|
for (let i = 0; i < 12; i += 1) { |
||||
|
salesData.push({ |
||||
|
x: `${i + 1}月`, |
||||
|
y: Math.floor(Math.random() * 1000) + 200, |
||||
|
}); |
||||
|
} |
||||
|
const searchData = []; |
||||
|
for (let i = 0; i < 50; i += 1) { |
||||
|
searchData.push({ |
||||
|
index: i + 1, |
||||
|
keyword: `搜索关键词-${i}`, |
||||
|
count: Math.floor(Math.random() * 1000), |
||||
|
range: Math.floor(Math.random() * 100), |
||||
|
status: Math.floor((Math.random() * 10) % 2), |
||||
|
}); |
||||
|
} |
||||
|
const salesTypeData = [ |
||||
|
{ |
||||
|
x: '家用电器', |
||||
|
y: 4544, |
||||
|
}, |
||||
|
{ |
||||
|
x: '食用酒水', |
||||
|
y: 3321, |
||||
|
}, |
||||
|
{ |
||||
|
x: '个护健康', |
||||
|
y: 3113, |
||||
|
}, |
||||
|
{ |
||||
|
x: '服饰箱包', |
||||
|
y: 2341, |
||||
|
}, |
||||
|
{ |
||||
|
x: '母婴产品', |
||||
|
y: 1231, |
||||
|
}, |
||||
|
{ |
||||
|
x: '其他', |
||||
|
y: 1231, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
const salesTypeDataOnline = [ |
||||
|
{ |
||||
|
x: '家用电器', |
||||
|
y: 244, |
||||
|
}, |
||||
|
{ |
||||
|
x: '食用酒水', |
||||
|
y: 321, |
||||
|
}, |
||||
|
{ |
||||
|
x: '个护健康', |
||||
|
y: 311, |
||||
|
}, |
||||
|
{ |
||||
|
x: '服饰箱包', |
||||
|
y: 41, |
||||
|
}, |
||||
|
{ |
||||
|
x: '母婴产品', |
||||
|
y: 121, |
||||
|
}, |
||||
|
{ |
||||
|
x: '其他', |
||||
|
y: 111, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
const salesTypeDataOffline = [ |
||||
|
{ |
||||
|
x: '家用电器', |
||||
|
y: 99, |
||||
|
}, |
||||
|
{ |
||||
|
x: '个护健康', |
||||
|
y: 188, |
||||
|
}, |
||||
|
{ |
||||
|
x: '服饰箱包', |
||||
|
y: 344, |
||||
|
}, |
||||
|
{ |
||||
|
x: '母婴产品', |
||||
|
y: 255, |
||||
|
}, |
||||
|
{ |
||||
|
x: '其他', |
||||
|
y: 65, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
const offlineData = []; |
||||
|
for (let i = 0; i < 10; i += 1) { |
||||
|
offlineData.push({ |
||||
|
name: `门店${i}`, |
||||
|
cvr: Math.ceil(Math.random() * 9) / 10, |
||||
|
}); |
||||
|
} |
||||
|
const offlineChartData = []; |
||||
|
for (let i = 0; i < 20; i += 1) { |
||||
|
offlineChartData.push({ |
||||
|
x: (new Date().getTime()) + (1000 * 60 * 30 * i), |
||||
|
y1: Math.floor(Math.random() * 100) + 10, |
||||
|
y2: Math.floor(Math.random() * 100) + 10, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const radarOriginData = [ |
||||
|
{ |
||||
|
name: '个人', |
||||
|
ref: 10, |
||||
|
koubei: 8, |
||||
|
output: 4, |
||||
|
contribute: 5, |
||||
|
hot: 7, |
||||
|
}, |
||||
|
{ |
||||
|
name: '团队', |
||||
|
ref: 3, |
||||
|
koubei: 9, |
||||
|
output: 6, |
||||
|
contribute: 3, |
||||
|
hot: 1, |
||||
|
}, |
||||
|
{ |
||||
|
name: '部门', |
||||
|
ref: 4, |
||||
|
koubei: 1, |
||||
|
output: 6, |
||||
|
contribute: 5, |
||||
|
hot: 7, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
//
|
||||
|
const radarData = []; |
||||
|
const radarTitleMap = { |
||||
|
ref: '引用', |
||||
|
koubei: '口碑', |
||||
|
output: '产量', |
||||
|
contribute: '贡献', |
||||
|
hot: '热度', |
||||
|
}; |
||||
|
radarOriginData.forEach((item) => { |
||||
|
Object.keys(item).forEach((key) => { |
||||
|
if (key !== 'name') { |
||||
|
radarData.push({ |
||||
|
name: item.name, |
||||
|
label: radarTitleMap[key], |
||||
|
value: item[key], |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
export const getFakeChartData = { |
||||
|
visitData, |
||||
|
salesData, |
||||
|
searchData, |
||||
|
offlineData, |
||||
|
offlineChartData, |
||||
|
salesTypeData, |
||||
|
salesTypeDataOnline, |
||||
|
salesTypeDataOffline, |
||||
|
radarData, |
||||
|
}; |
||||
|
|
||||
|
export default { |
||||
|
getFakeChartData, |
||||
|
}; |
||||
@ -0,0 +1,85 @@ |
|||||
|
export default { |
||||
|
getNotices(req, res) { |
||||
|
res.json([{ |
||||
|
id: '000000001', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', |
||||
|
title: '你收到了 14 份新周报', |
||||
|
datetime: '2017-08-09', |
||||
|
type: '通知', |
||||
|
}, { |
||||
|
id: '000000002', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png', |
||||
|
title: '你推荐的 曲妮妮 已通过第三轮面试', |
||||
|
datetime: '2017-08-08', |
||||
|
type: '通知', |
||||
|
}, { |
||||
|
id: '000000003', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png', |
||||
|
title: '这种模板可以区分多种通知类型', |
||||
|
datetime: '2017-08-07', |
||||
|
read: true, |
||||
|
type: '通知', |
||||
|
}, { |
||||
|
id: '000000004', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', |
||||
|
title: '左侧图标用于区分不同的类型', |
||||
|
datetime: '2017-08-07', |
||||
|
type: '通知', |
||||
|
}, { |
||||
|
id: '000000005', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', |
||||
|
title: '内容不要超过两行字,超出时自动截断', |
||||
|
datetime: '2017-08-07', |
||||
|
type: '通知', |
||||
|
}, { |
||||
|
id: '000000006', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', |
||||
|
title: '曲丽丽 评论了你', |
||||
|
description: '描述信息描述信息描述信息', |
||||
|
datetime: '2017-08-07', |
||||
|
type: '消息', |
||||
|
}, { |
||||
|
id: '000000007', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', |
||||
|
title: '朱偏右 回复了你', |
||||
|
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', |
||||
|
datetime: '2017-08-07', |
||||
|
type: '消息', |
||||
|
}, { |
||||
|
id: '000000008', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', |
||||
|
title: '标题', |
||||
|
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', |
||||
|
datetime: '2017-08-07', |
||||
|
type: '消息', |
||||
|
}, { |
||||
|
id: '000000009', |
||||
|
title: '任务名称', |
||||
|
description: '任务需要在 2017-01-12 20:00 前启动', |
||||
|
extra: '马上到期', |
||||
|
status: 'urgent', |
||||
|
type: '待办', |
||||
|
}, { |
||||
|
id: '000000010', |
||||
|
title: '第三方紧急代码变更', |
||||
|
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', |
||||
|
extra: '马上到期', |
||||
|
status: 'urgent', |
||||
|
type: '待办', |
||||
|
}, { |
||||
|
id: '000000011', |
||||
|
title: '信息安全考试', |
||||
|
description: '指派竹尔于 2017-01-09 前完成更新并发布', |
||||
|
extra: '已耗时 8 天', |
||||
|
status: 'doing', |
||||
|
type: '待办', |
||||
|
}, { |
||||
|
id: '000000012', |
||||
|
title: 'ABCD 版本发布', |
||||
|
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', |
||||
|
extra: '进行中', |
||||
|
status: 'processing', |
||||
|
type: '待办', |
||||
|
}]); |
||||
|
}, |
||||
|
}; |
||||
@ -0,0 +1,74 @@ |
|||||
|
const operation1 = [ |
||||
|
{ |
||||
|
key: 'op1', |
||||
|
type: '订购关系生效', |
||||
|
name: '曲丽丽', |
||||
|
status: 'agree', |
||||
|
updatedAt: '2017-10-03 19:23:12', |
||||
|
memo: '-', |
||||
|
}, |
||||
|
{ |
||||
|
key: 'op2', |
||||
|
type: '财务复审', |
||||
|
name: '付小小', |
||||
|
status: 'reject', |
||||
|
updatedAt: '2017-10-03 19:23:12', |
||||
|
memo: '不通过原因', |
||||
|
}, |
||||
|
{ |
||||
|
key: 'op3', |
||||
|
type: '部门初审', |
||||
|
name: '周毛毛', |
||||
|
status: 'agree', |
||||
|
updatedAt: '2017-10-03 19:23:12', |
||||
|
memo: '-', |
||||
|
}, |
||||
|
{ |
||||
|
key: 'op4', |
||||
|
type: '提交订单', |
||||
|
name: '林东东', |
||||
|
status: 'agree', |
||||
|
updatedAt: '2017-10-03 19:23:12', |
||||
|
memo: '很棒', |
||||
|
}, |
||||
|
{ |
||||
|
key: 'op5', |
||||
|
type: '创建订单', |
||||
|
name: '汗牙牙', |
||||
|
status: 'agree', |
||||
|
updatedAt: '2017-10-03 19:23:12', |
||||
|
memo: '-', |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
const operation2 = [ |
||||
|
{ |
||||
|
key: 'op1', |
||||
|
type: '订购关系生效', |
||||
|
name: '曲丽丽', |
||||
|
status: 'agree', |
||||
|
updatedAt: '2017-10-03 19:23:12', |
||||
|
memo: '-', |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
const operation3 = [ |
||||
|
{ |
||||
|
key: 'op1', |
||||
|
type: '创建订单', |
||||
|
name: '汗牙牙', |
||||
|
status: 'agree', |
||||
|
updatedAt: '2017-10-03 19:23:12', |
||||
|
memo: '-', |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
export const getProfileData = { |
||||
|
operation1, |
||||
|
operation2, |
||||
|
operation3, |
||||
|
}; |
||||
|
|
||||
|
export default { |
||||
|
getProfileData, |
||||
|
}; |
||||
@ -0,0 +1,128 @@ |
|||||
|
import { getUrlParams } from './utils'; |
||||
|
|
||||
|
// mock tableListDataSource
|
||||
|
let tableListDataSource = []; |
||||
|
for (let i = 0; i < 46; i += 1) { |
||||
|
tableListDataSource.push({ |
||||
|
key: i, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: ['https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png'][i % 2], |
||||
|
no: `TradeCode ${i}`, |
||||
|
title: `一个任务名称 ${i}`, |
||||
|
owner: '曲丽丽', |
||||
|
description: '这是一段描述', |
||||
|
callNo: Math.floor(Math.random() * 1000), |
||||
|
status: Math.floor(Math.random() * 10) % 2, |
||||
|
updatedAt: new Date(`2017-07-${Math.floor(i / 2) + 1} ${Math.floor(i / 2) + 1}:00:00`), |
||||
|
createdAt: new Date(`2017-07-${Math.floor(i / 2) + 1} ${Math.floor(i / 2) + 1}:00:00`), |
||||
|
progress: Math.ceil(Math.random() * 100), |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function getRule(req, res, u) { |
||||
|
let url = u; |
||||
|
if (!url || Object.prototype.toString.call(url) !== '[object String]') { |
||||
|
url = req.url; |
||||
|
} |
||||
|
|
||||
|
const params = getUrlParams(url); |
||||
|
|
||||
|
let dataSource = [...tableListDataSource]; |
||||
|
|
||||
|
if (params.sorter) { |
||||
|
const s = params.sorter.split('_'); |
||||
|
dataSource = dataSource.sort((prev, next) => { |
||||
|
if (s[1] === 'descend') { |
||||
|
return next[s[0]] - prev[s[0]]; |
||||
|
} |
||||
|
return prev[s[0]] - next[s[0]]; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (params.status) { |
||||
|
const s = params.status.split(','); |
||||
|
if (s.length === 1) { |
||||
|
dataSource = dataSource.filter(data => parseInt(data.status, 10) === parseInt(s[0], 10)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (params.no) { |
||||
|
dataSource = dataSource.filter(data => data.no.indexOf(params.no) > -1); |
||||
|
} |
||||
|
|
||||
|
let pageSize = 10; |
||||
|
if (params.pageSize) { |
||||
|
pageSize = params.pageSize * 1; |
||||
|
} |
||||
|
|
||||
|
const result = { |
||||
|
list: dataSource, |
||||
|
pagination: { |
||||
|
total: dataSource.length, |
||||
|
pageSize, |
||||
|
current: parseInt(params.currentPage, 10) || 1, |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
if (res && res.json) { |
||||
|
res.json(result); |
||||
|
} else { |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function postRule(req, res, u, b) { |
||||
|
let url = u; |
||||
|
if (!url || Object.prototype.toString.call(url) !== '[object String]') { |
||||
|
url = req.url; |
||||
|
} |
||||
|
|
||||
|
const body = (b && b.body) || req.body; |
||||
|
const method = body.method; |
||||
|
|
||||
|
switch (method) { |
||||
|
/* eslint no-case-declarations:0 */ |
||||
|
case 'delete': |
||||
|
const no = body.no; |
||||
|
tableListDataSource = tableListDataSource.filter(item => no.indexOf(item.no) === -1); |
||||
|
break; |
||||
|
case 'post': |
||||
|
const description = body.description; |
||||
|
const i = Math.ceil(Math.random() * 10000); |
||||
|
tableListDataSource.unshift({ |
||||
|
key: i, |
||||
|
href: 'https://ant.design', |
||||
|
avatar: ['https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png'][i % 2], |
||||
|
no: `TradeCode ${i}`, |
||||
|
title: `一个任务名称 ${i}`, |
||||
|
owner: '曲丽丽', |
||||
|
description, |
||||
|
callNo: Math.floor(Math.random() * 1000), |
||||
|
status: Math.floor(Math.random() * 10) % 2, |
||||
|
updatedAt: new Date(), |
||||
|
createdAt: new Date(), |
||||
|
progress: Math.ceil(Math.random() * 100), |
||||
|
}); |
||||
|
break; |
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
const result = { |
||||
|
list: tableListDataSource, |
||||
|
pagination: { |
||||
|
total: tableListDataSource.length, |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
if (res && res.json) { |
||||
|
res.json(result); |
||||
|
} else { |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default { |
||||
|
getRule, |
||||
|
postRule, |
||||
|
}; |
||||
@ -0,0 +1,45 @@ |
|||||
|
export const imgMap = { |
||||
|
user: 'https://gw.alipayobjects.com/zos/rmsportal/YdMCpIJULitXfqHCFPbF.png', |
||||
|
a: 'https://gw.alipayobjects.com/zos/rmsportal/ZrkcSjizAKNWwJTwcadT.png', |
||||
|
b: 'https://gw.alipayobjects.com/zos/rmsportal/KYlwHMeomKQbhJDRUVvt.png', |
||||
|
c: 'https://gw.alipayobjects.com/zos/rmsportal/gabvleTstEvzkbQRfjxu.png', |
||||
|
d: 'https://gw.alipayobjects.com/zos/rmsportal/jvpNzacxUYLlNsHTtrAD.png', |
||||
|
}; |
||||
|
|
||||
|
// refers: https://www.sitepoint.com/get-url-parameters-with-javascript/
|
||||
|
export function getUrlParams(url) { |
||||
|
const d = decodeURIComponent; |
||||
|
let queryString = url ? url.split('?')[1] : window.location.search.slice(1); |
||||
|
const obj = {}; |
||||
|
if (queryString) { |
||||
|
queryString = queryString.split('#')[0]; |
||||
|
const arr = queryString.split('&'); |
||||
|
for (let i = 0; i < arr.length; i += 1) { |
||||
|
const a = arr[i].split('='); |
||||
|
let paramNum; |
||||
|
const paramName = a[0].replace(/\[\d*\]/, (v) => { |
||||
|
paramNum = v.slice(1, -1); |
||||
|
return ''; |
||||
|
}); |
||||
|
const paramValue = typeof (a[1]) === 'undefined' ? true : a[1]; |
||||
|
if (obj[paramName]) { |
||||
|
if (typeof obj[paramName] === 'string') { |
||||
|
obj[paramName] = d([obj[paramName]]); |
||||
|
} |
||||
|
if (typeof paramNum === 'undefined') { |
||||
|
obj[paramName].push(d(paramValue)); |
||||
|
} else { |
||||
|
obj[paramName][paramNum] = d(paramValue); |
||||
|
} |
||||
|
} else { |
||||
|
obj[paramName] = d(paramValue); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return obj; |
||||
|
} |
||||
|
|
||||
|
export default { |
||||
|
getUrlParams, |
||||
|
imgMap, |
||||
|
}; |
||||
@ -0,0 +1,47 @@ |
|||||
|
{ |
||||
|
"name": "ant-design-admin", |
||||
|
"private": true, |
||||
|
"scripts": { |
||||
|
"start": "roadhog server", |
||||
|
"build": "roadhog build", |
||||
|
"lint": "eslint --ext .js src test", |
||||
|
"precommit": "npm run lint" |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"antd": "next", |
||||
|
"dva": "^1.2.1", |
||||
|
"g-cloud": "^1.0.2-beta", |
||||
|
"g2": "^2.3.8", |
||||
|
"g2-plugin-slider": "^1.2.1", |
||||
|
"lodash": "^4.17.4", |
||||
|
"marked": "^0.3.6", |
||||
|
"numeral": "^2.0.6", |
||||
|
"prop-types": "^15.5.10", |
||||
|
"qs": "^6.5.0", |
||||
|
"react": "^15.4.0", |
||||
|
"react-document-title": "^2.0.3", |
||||
|
"react-dom": "^15.4.0", |
||||
|
"react-redux": "4.x || 5.x", |
||||
|
"react-router": "2.x || 3.x" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"babel-eslint": "^7.1.1", |
||||
|
"babel-plugin-dva-hmr": "^0.3.2", |
||||
|
"babel-plugin-import": "^1.2.1", |
||||
|
"babel-plugin-transform-decorators-legacy": "^1.3.4", |
||||
|
"babel-plugin-transform-runtime": "^6.9.0", |
||||
|
"babel-runtime": "^6.9.2", |
||||
|
"eslint": "^3.0.0", |
||||
|
"eslint-config-airbnb": "latest", |
||||
|
"eslint-plugin-babel": "^4.0.0", |
||||
|
"eslint-plugin-import": "^2.2.0", |
||||
|
"eslint-plugin-jsx-a11y": "^5.0.1", |
||||
|
"eslint-plugin-react": "^7.0.1", |
||||
|
"expect": "^1.20.2", |
||||
|
"husky": "^0.13.4", |
||||
|
"mockjs": "^1.0.1-beta3", |
||||
|
"redbox-react": "^1.3.2", |
||||
|
"roadhog": "^1.0.2", |
||||
|
"roadhog-api-doc": "^0.1.0" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
|
<title>Ant Design Pro</title> |
||||
|
<link rel="stylesheet" href="index.css" /> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="root"></div> |
||||
|
<script src="index.js"></script> |
||||
|
</body> |
||||
|
</html> |
||||
|
After Width: | Height: | Size: 177 KiB |
@ -0,0 +1,202 @@ |
|||||
|
import BasicLayout from '../layouts/BasicLayout'; |
||||
|
import UserLayout from '../layouts/UserLayout'; |
||||
|
|
||||
|
import Analysis from '../routes/Dashboard/Analysis'; |
||||
|
import Monitor from '../routes/Dashboard/Monitor'; |
||||
|
import Workplace from '../routes/Dashboard/Workplace'; |
||||
|
|
||||
|
import TableList from '../routes/List/TableList'; |
||||
|
import CoverCardList from '../routes/List/CoverCardList'; |
||||
|
import CardList from '../routes/List/CardList'; |
||||
|
import FilterCardList from '../routes/List/FilterCardList'; |
||||
|
import SearchList from '../routes/List/SearchList'; |
||||
|
import BasicList from '../routes/List/BasicList'; |
||||
|
|
||||
|
import Profile from '../routes/Profile'; |
||||
|
import BasicForm from '../routes/Forms/BasicForm'; |
||||
|
import AdvancedForm from '../routes/Forms/AdvancedForm'; |
||||
|
import StepForm from '../routes/Forms/StepForm'; |
||||
|
import Step2 from '../routes/Forms/StepForm/Step2'; |
||||
|
import Step3 from '../routes/Forms/StepForm/Step3'; |
||||
|
|
||||
|
import Exception403 from '../routes/Exception/403'; |
||||
|
import Exception404 from '../routes/Exception/404'; |
||||
|
import Exception500 from '../routes/Exception/500'; |
||||
|
|
||||
|
import Success from '../routes/Result/Success'; |
||||
|
import Error from '../routes/Result/Error'; |
||||
|
|
||||
|
import Login from '../routes/User/Login'; |
||||
|
import Register from '../routes/User/Register'; |
||||
|
import RegisterResult from '../routes/User/RegisterResult'; |
||||
|
|
||||
|
function userAdapter(userData) { |
||||
|
userData.children.forEach((item) => { |
||||
|
if (item.children) { |
||||
|
userAdapter(item); |
||||
|
} else { |
||||
|
const userItem = item; |
||||
|
userItem.target = '_blank'; |
||||
|
userItem.noRoute = true; |
||||
|
} |
||||
|
}); |
||||
|
return userData; |
||||
|
} |
||||
|
|
||||
|
export const user = [{ |
||||
|
name: '帐户', |
||||
|
icon: 'setting', |
||||
|
path: 'user', |
||||
|
children: [{ |
||||
|
name: '登录', |
||||
|
path: 'login', |
||||
|
component: Login, |
||||
|
icon: 'setting', |
||||
|
}, { |
||||
|
name: '注册', |
||||
|
path: 'register', |
||||
|
component: Register, |
||||
|
icon: 'setting', |
||||
|
}, { |
||||
|
name: '注册结果', |
||||
|
path: 'register-result', |
||||
|
component: RegisterResult, |
||||
|
icon: 'setting', |
||||
|
}], |
||||
|
}]; |
||||
|
|
||||
|
export const menus = [{ |
||||
|
name: 'Dashboard', |
||||
|
icon: 'setting', |
||||
|
path: 'dashboard', |
||||
|
children: [{ |
||||
|
name: '分析页', |
||||
|
path: 'analysis', |
||||
|
component: Analysis, |
||||
|
icon: 'setting', |
||||
|
}, { |
||||
|
name: '监控页', |
||||
|
path: 'monitor', |
||||
|
component: Monitor, |
||||
|
icon: 'setting', |
||||
|
}, { |
||||
|
name: '工作台', |
||||
|
path: 'workplace', |
||||
|
component: Workplace, |
||||
|
icon: 'setting', |
||||
|
}], |
||||
|
}, { |
||||
|
name: '表单页', |
||||
|
path: 'form', |
||||
|
icon: 'setting', |
||||
|
children: [{ |
||||
|
name: '基础表单', |
||||
|
path: 'basic-form', |
||||
|
component: BasicForm, |
||||
|
icon: 'setting', |
||||
|
}, { |
||||
|
name: '分步表单', |
||||
|
path: 'step-form', |
||||
|
component: StepForm, |
||||
|
icon: 'setting', |
||||
|
children: [{ |
||||
|
path: 'confirm', |
||||
|
component: Step2, |
||||
|
}, { |
||||
|
path: 'result', |
||||
|
component: Step3, |
||||
|
}], |
||||
|
}, { |
||||
|
name: '高级表单', |
||||
|
path: 'advanced-form', |
||||
|
component: AdvancedForm, |
||||
|
icon: 'setting', |
||||
|
}], |
||||
|
}, { |
||||
|
name: '列表页', |
||||
|
path: 'list', |
||||
|
icon: 'setting', |
||||
|
children: [{ |
||||
|
name: '标准表格(表格查询)', |
||||
|
path: 'table-list', |
||||
|
component: TableList, |
||||
|
icon: 'setting', |
||||
|
}, { |
||||
|
name: '标准列表', |
||||
|
path: 'basic-list', |
||||
|
component: BasicList, |
||||
|
icon: 'setting', |
||||
|
}, { |
||||
|
name: '卡片列表', |
||||
|
path: 'card-list', |
||||
|
component: CardList, |
||||
|
icon: 'setting', |
||||
|
}, { |
||||
|
name: '卡片列表(封面)', |
||||
|
path: 'cover-card-list', |
||||
|
component: CoverCardList, |
||||
|
icon: 'setting', |
||||
|
}, { |
||||
|
name: '带筛选卡片列表', |
||||
|
path: 'filter-card-list', |
||||
|
component: FilterCardList, |
||||
|
icon: 'setting', |
||||
|
}, { |
||||
|
name: '搜索列表', |
||||
|
path: 'search', |
||||
|
component: SearchList, |
||||
|
icon: 'setting', |
||||
|
}], |
||||
|
}, { |
||||
|
name: '详情页', |
||||
|
path: 'profile', |
||||
|
component: Profile, |
||||
|
icon: 'setting', |
||||
|
}, { |
||||
|
name: '结果', |
||||
|
path: 'result', |
||||
|
icon: 'setting', |
||||
|
children: [{ |
||||
|
name: '成功', |
||||
|
path: 'success', |
||||
|
component: Success, |
||||
|
icon: 'setting', |
||||
|
}, { |
||||
|
name: '失败', |
||||
|
path: 'fail', |
||||
|
component: Error, |
||||
|
icon: 'setting', |
||||
|
}], |
||||
|
}, { |
||||
|
name: '错误', |
||||
|
path: 'error', |
||||
|
icon: 'setting', |
||||
|
children: [{ |
||||
|
name: '403', |
||||
|
path: '403', |
||||
|
component: Exception403, |
||||
|
icon: 'setting', |
||||
|
}, { |
||||
|
name: '404', |
||||
|
path: '404', |
||||
|
component: Exception404, |
||||
|
icon: 'setting', |
||||
|
}, { |
||||
|
name: '500', |
||||
|
path: '500', |
||||
|
component: Exception500, |
||||
|
icon: 'setting', |
||||
|
}], |
||||
|
}, userAdapter(JSON.parse(JSON.stringify(user[0])))]; |
||||
|
|
||||
|
|
||||
|
export default [{ |
||||
|
component: BasicLayout, |
||||
|
name: '首页', |
||||
|
children: menus, |
||||
|
path: '', |
||||
|
}, { |
||||
|
component: UserLayout, |
||||
|
name: '账户', |
||||
|
children: user, |
||||
|
}]; |
||||
@ -0,0 +1,31 @@ |
|||||
|
import React from 'react'; |
||||
|
import moment from 'moment'; |
||||
|
import marked from 'marked'; |
||||
|
import { Avatar } from 'antd'; |
||||
|
|
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
/* eslint react/no-danger:0 */ |
||||
|
export default ({ data: { user, updatedAt, action } }) => ( |
||||
|
<div |
||||
|
className={styles.activitiesItem} |
||||
|
> |
||||
|
<div className={styles.avatar}> |
||||
|
{ |
||||
|
user.link && <a href={user.link} target="_blank"> |
||||
|
<Avatar src={user.avatar} /> |
||||
|
</a> |
||||
|
} |
||||
|
{ |
||||
|
!user.link && <img src={user.avatar} alt={user.title} /> |
||||
|
} |
||||
|
</div> |
||||
|
<div className={styles.content}> |
||||
|
<div> |
||||
|
<span className={styles.name}>{user.name}</span> |
||||
|
<div dangerouslySetInnerHTML={{ __html: marked(action) }} /> |
||||
|
</div> |
||||
|
<p>{moment(updatedAt).fromNow()}</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
@ -0,0 +1,41 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
|
||||
|
.activitiesItem { |
||||
|
padding: 24px 24px 0 24px; |
||||
|
position: relative; |
||||
|
|
||||
|
.avatar { |
||||
|
position: absolute; |
||||
|
top: 24px; |
||||
|
left: 24px; |
||||
|
img { |
||||
|
display: block; |
||||
|
border-radius: 32px; |
||||
|
width: 32px; |
||||
|
height: 32px; |
||||
|
} |
||||
|
} |
||||
|
.content { |
||||
|
border-bottom: 1px solid @border-color-split; |
||||
|
padding-left: 48px; |
||||
|
padding-bottom: 24px; |
||||
|
font-size: @font-size-base; |
||||
|
a { |
||||
|
color: @primary-color; |
||||
|
} |
||||
|
& > div { |
||||
|
line-height: 22px; |
||||
|
.name { |
||||
|
margin-right: 4px; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
div, p { |
||||
|
display: inline-block; |
||||
|
} |
||||
|
} |
||||
|
& > p { |
||||
|
margin-top: 4px; |
||||
|
line-height: 22px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
import React from 'react'; |
||||
|
import { Tooltip, Avatar } from 'antd'; |
||||
|
import classNames from 'classnames'; |
||||
|
|
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
const AvatarList = ({ children, size, ...other }) => { |
||||
|
const childrenWithProps = React.Children.map(children, child => |
||||
|
React.cloneElement(child, { |
||||
|
size, |
||||
|
}) |
||||
|
); |
||||
|
|
||||
|
return ( |
||||
|
<div {...other} className={styles.avatarList}> |
||||
|
<ul> {childrenWithProps} </ul> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
const Item = ({ src, size, tips, onClick = (() => {}) }) => { |
||||
|
const cls = classNames(styles.avatarItem, { |
||||
|
[styles.avatarItemLarge]: size === 'large', |
||||
|
[styles.avatarItemSmall]: size === 'small', |
||||
|
}); |
||||
|
|
||||
|
return ( |
||||
|
<li className={cls} onClick={onClick} > |
||||
|
{ |
||||
|
tips ? |
||||
|
<Tooltip title={tips}> |
||||
|
<Avatar src={src} size={size} style={{ cursor: 'pointer' }} /> |
||||
|
</Tooltip> |
||||
|
: |
||||
|
<Avatar src={src} size={size} /> |
||||
|
} |
||||
|
</li> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
AvatarList.Item = Item; |
||||
|
|
||||
|
export default AvatarList; |
||||
@ -0,0 +1,29 @@ |
|||||
|
@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; |
||||
|
overflow: hidden; |
||||
|
font-size: @font-size-base; |
||||
|
margin-left: -8px; |
||||
|
width: @avatar-size-base; |
||||
|
height: @avatar-size-base; |
||||
|
} |
||||
|
|
||||
|
.avatarItemLarge { |
||||
|
width: @avatar-size-lg; |
||||
|
height: @avatar-size-lg; |
||||
|
} |
||||
|
|
||||
|
.avatarItemSmall { |
||||
|
width: @avatar-size-sm; |
||||
|
height: @avatar-size-sm; |
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
import React, { PureComponent } from 'react'; |
||||
|
import G2 from 'g2'; |
||||
|
import styles from '../index.less'; |
||||
|
|
||||
|
class Bar extends PureComponent { |
||||
|
componentDidMount() { |
||||
|
this.renderChart(this.props.data); |
||||
|
} |
||||
|
|
||||
|
componentWillReceiveProps(nextProps) { |
||||
|
if (nextProps.data !== this.props.data) { |
||||
|
this.renderChart(nextProps.data); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
handleRef = (n) => { |
||||
|
this.node = n; |
||||
|
} |
||||
|
|
||||
|
renderChart(data) { |
||||
|
const { height = 0, fit = true, color = '#33abfb', margin = [32, 0, 32, 40] } = this.props; |
||||
|
|
||||
|
if (!data || (data && data.length < 1)) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// clean
|
||||
|
this.node.innerHTML = ''; |
||||
|
|
||||
|
const Frame = G2.Frame; |
||||
|
const frame = new Frame(data); |
||||
|
|
||||
|
const chart = new G2.Chart({ |
||||
|
container: this.node, |
||||
|
forceFit: fit, |
||||
|
height: height - 22, |
||||
|
legend: null, |
||||
|
plotCfg: { |
||||
|
margin, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
chart.axis('x', { |
||||
|
title: false, |
||||
|
}); |
||||
|
chart.axis('y', { |
||||
|
title: false, |
||||
|
line: false, |
||||
|
tickLine: false, |
||||
|
}); |
||||
|
|
||||
|
chart.source(frame, { |
||||
|
x: { |
||||
|
type: 'cat', |
||||
|
}, |
||||
|
y: { |
||||
|
min: 0, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
chart.tooltip({ |
||||
|
title: null, |
||||
|
crosshairs: false, |
||||
|
map: { |
||||
|
name: 'x', |
||||
|
}, |
||||
|
}); |
||||
|
chart.interval().position('x*y').color(color); |
||||
|
chart.render(); |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { height, title } = this.props; |
||||
|
|
||||
|
return ( |
||||
|
<div className={styles.chart} style={{ height }}> |
||||
|
<div> |
||||
|
{ title && <h4>{title}</h4>} |
||||
|
<div ref={this.handleRef} /> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default Bar; |
||||
@ -0,0 +1,34 @@ |
|||||
|
import React from 'react'; |
||||
|
import { Card } from 'antd'; |
||||
|
|
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
const ChartCard = ({ contentHeight, title, action, total, footer, children, ...rest }) => ( |
||||
|
<Card |
||||
|
bodyStyle={{ padding: '20px 24px 8px 24px' }} |
||||
|
{...rest} |
||||
|
> |
||||
|
<div className={styles.chartCard}> |
||||
|
<div className={styles.meta}> |
||||
|
<span className={styles.title}>{title}</span> |
||||
|
<span className={styles.action}>{action}</span> |
||||
|
</div> |
||||
|
{ |
||||
|
// eslint-disable-next-line
|
||||
|
total && <p className={styles.total} dangerouslySetInnerHTML={{ __html: total }} /> |
||||
|
} |
||||
|
<div className={styles.content} style={{ height: contentHeight || 'auto' }}> |
||||
|
<div className={contentHeight && styles.contentFixed}> |
||||
|
{children} |
||||
|
</div> |
||||
|
</div> |
||||
|
{ |
||||
|
footer && <div className={styles.footer}> |
||||
|
{footer} |
||||
|
</div> |
||||
|
} |
||||
|
</div> |
||||
|
</Card> |
||||
|
); |
||||
|
|
||||
|
export default ChartCard; |
||||
@ -0,0 +1,45 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
@import "../../../utils/utils.less"; |
||||
|
|
||||
|
.chartCard { |
||||
|
position: relative; |
||||
|
.meta { |
||||
|
color: @text-color-secondary; |
||||
|
font-size: @font-size-base; |
||||
|
position: relative; |
||||
|
line-height: 22px; |
||||
|
height: 22px; |
||||
|
} |
||||
|
.action { |
||||
|
cursor: pointer; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
right: 0; |
||||
|
} |
||||
|
.total { |
||||
|
.textOverflow(); |
||||
|
color: @heading-color; |
||||
|
margin-top: 8px; |
||||
|
font-size: 30px; |
||||
|
line-height: 38px; |
||||
|
height: 38px; |
||||
|
} |
||||
|
.content { |
||||
|
position: relative; |
||||
|
width: 100%; |
||||
|
} |
||||
|
.contentFixed { |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
bottom: 0; |
||||
|
width: 100%; |
||||
|
} |
||||
|
.footer { |
||||
|
border-top: 1px solid @border-color-split; |
||||
|
padding-top: 8px; |
||||
|
margin-top: 11px; |
||||
|
& > * { |
||||
|
position: relative; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
const Field = ({ label, value, ...rest }) => ( |
||||
|
<p className={styles.field} {...rest}> |
||||
|
<span>{label}</span> |
||||
|
<span>{value}</span> |
||||
|
</p> |
||||
|
); |
||||
|
|
||||
|
export default Field; |
||||
@ -0,0 +1,17 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
@import "../../../utils/utils.less"; |
||||
|
|
||||
|
.field { |
||||
|
white-space: nowrap; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
span { |
||||
|
font-size: @font-size-base; |
||||
|
line-height: 22px; |
||||
|
} |
||||
|
span:last-child { |
||||
|
font-weight: 600; |
||||
|
margin-left: 8px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,195 @@ |
|||||
|
import React, { PureComponent } from 'react'; |
||||
|
import G2 from 'g2'; |
||||
|
|
||||
|
const Shape = G2.Shape; |
||||
|
|
||||
|
/* eslint no-underscore-dangle: 0 */ |
||||
|
class Gauge extends PureComponent { |
||||
|
componentDidMount() { |
||||
|
this.renderChart(); |
||||
|
} |
||||
|
|
||||
|
componentWillReceiveProps(nextProps) { |
||||
|
this.renderChart(nextProps); |
||||
|
} |
||||
|
|
||||
|
handleRef = (n) => { |
||||
|
this.node = n; |
||||
|
} |
||||
|
|
||||
|
initChart(nextProps) { |
||||
|
const { title, color = '#00b1f8' } = nextProps || this.props; |
||||
|
|
||||
|
Shape.registShape('point', 'dashBoard', { |
||||
|
drawShape(cfg, group) { |
||||
|
const originPoint = cfg.points[0]; |
||||
|
const point = this.parsePoint({ x: originPoint.x, y: 0.4 }); |
||||
|
|
||||
|
const center = this.parsePoint({ |
||||
|
x: 0, |
||||
|
y: 0, |
||||
|
}); |
||||
|
|
||||
|
const shape = group.addShape('polygon', { |
||||
|
attrs: { |
||||
|
points: [ |
||||
|
[center.x, center.y], |
||||
|
[point.x + 8, point.y], |
||||
|
[point.x + 8, point.y - 2], |
||||
|
[center.x, center.y - 2], |
||||
|
], |
||||
|
radius: 2, |
||||
|
lineWidth: 2, |
||||
|
arrow: false, |
||||
|
fill: color, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
group.addShape('Marker', { |
||||
|
attrs: { |
||||
|
symbol: 'circle', |
||||
|
lineWidth: 2, |
||||
|
fill: color, |
||||
|
radius: 8, |
||||
|
x: center.x, |
||||
|
y: center.y, |
||||
|
}, |
||||
|
}); |
||||
|
group.addShape('Marker', { |
||||
|
attrs: { |
||||
|
symbol: 'circle', |
||||
|
lineWidth: 2, |
||||
|
fill: '#fff', |
||||
|
radius: 5, |
||||
|
x: center.x, |
||||
|
y: center.y, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const origin = cfg.origin; |
||||
|
group.addShape('text', { |
||||
|
attrs: { |
||||
|
x: center.x, |
||||
|
y: center.y + 80, |
||||
|
text: `${origin._origin.value}%`, |
||||
|
textAlign: 'center', |
||||
|
fontSize: 24, |
||||
|
fill: 'rgba(0, 0, 0, 0.85)', |
||||
|
}, |
||||
|
}); |
||||
|
group.addShape('text', { |
||||
|
attrs: { |
||||
|
x: center.x, |
||||
|
y: center.y + 45, |
||||
|
text: title, |
||||
|
textAlign: 'center', |
||||
|
fontSize: 14, |
||||
|
fill: 'rgba(0, 0, 0, 0.43)', |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
return shape; |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
renderChart(nextProps) { |
||||
|
const { height, color = '#00b1f8', bgColor = '#d3f3fe', title, percent } = nextProps || this.props; |
||||
|
const data = [{ name: title, value: percent }]; |
||||
|
|
||||
|
if (this.chart) { |
||||
|
this.chart.clear(); |
||||
|
} |
||||
|
if (this.node) { |
||||
|
this.node.innerHTML = ''; |
||||
|
} |
||||
|
|
||||
|
this.initChart(nextProps); |
||||
|
|
||||
|
const chart = new G2.Chart({ |
||||
|
container: this.node, |
||||
|
forceFit: true, |
||||
|
height, |
||||
|
animate: false, |
||||
|
plotCfg: { |
||||
|
margin: [10, 0, 30, 0], |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
chart.source(data); |
||||
|
|
||||
|
chart.tooltip(false); |
||||
|
|
||||
|
chart.coord('gauge', { |
||||
|
startAngle: -1.2 * Math.PI, |
||||
|
endAngle: 0.20 * Math.PI, |
||||
|
}); |
||||
|
chart.col('value', { |
||||
|
type: 'linear', |
||||
|
nice: true, |
||||
|
min: 0, |
||||
|
max: 100, |
||||
|
tickCount: 6, |
||||
|
subTick: false, |
||||
|
}); |
||||
|
chart.axis('value', { |
||||
|
tickLine: { |
||||
|
stroke: color, |
||||
|
}, |
||||
|
labelOffset: -12, |
||||
|
formatter(val) { |
||||
|
switch (val * 1) { |
||||
|
case 20: |
||||
|
return '差'; |
||||
|
case 40: |
||||
|
return '中'; |
||||
|
case 60: |
||||
|
return '良'; |
||||
|
case 80: |
||||
|
return '优'; |
||||
|
default: |
||||
|
return ''; |
||||
|
} |
||||
|
}, |
||||
|
}); |
||||
|
chart.point().position('value').shape('dashBoard'); |
||||
|
draw(data); |
||||
|
|
||||
|
/* eslint no-shadow: 0 */ |
||||
|
function draw(data) { |
||||
|
const val = data[0].value; |
||||
|
const lineWidth = 18; |
||||
|
chart.guide().clear(); |
||||
|
|
||||
|
chart.guide().arc(() => { |
||||
|
return [0, 0.95]; |
||||
|
}, () => { |
||||
|
return [val, 0.95]; |
||||
|
}, { |
||||
|
stroke: color, |
||||
|
lineWidth, |
||||
|
}); |
||||
|
|
||||
|
chart.guide().arc(() => { |
||||
|
return [val, 0.95]; |
||||
|
}, (arg) => { |
||||
|
return [arg.max, 0.95]; |
||||
|
}, { |
||||
|
stroke: bgColor, |
||||
|
lineWidth, |
||||
|
}); |
||||
|
|
||||
|
chart.changeData(data); |
||||
|
} |
||||
|
|
||||
|
this.chart = chart; |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
return ( |
||||
|
<div ref={this.handleRef} /> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default Gauge; |
||||
@ -0,0 +1,29 @@ |
|||||
|
import React from 'react'; |
||||
|
import { Icon } from 'antd'; |
||||
|
|
||||
|
const IconUp = ({ color }) => ( |
||||
|
<Icon |
||||
|
style={{ |
||||
|
color: (color === false) ? 'rgba(0,0,0,0.43)' : '#00a854', |
||||
|
fontSize: 12, |
||||
|
transform: 'scale(0.83)', |
||||
|
}} |
||||
|
type="caret-up" |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
const IconDown = ({ color }) => ( |
||||
|
<Icon |
||||
|
style={{ |
||||
|
color: (color === false) ? 'rgba(0,0,0,0.43)' : '#f04134', |
||||
|
fontSize: 12, |
||||
|
transform: 'scale(0.83)', |
||||
|
}} |
||||
|
type="caret-down" |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
export default { |
||||
|
IconUp, |
||||
|
IconDown, |
||||
|
}; |
||||
@ -0,0 +1,95 @@ |
|||||
|
import React, { PureComponent } from 'react'; |
||||
|
import G2 from 'g2'; |
||||
|
import styles from '../index.less'; |
||||
|
|
||||
|
class MiniArea extends PureComponent { |
||||
|
componentDidMount() { |
||||
|
this.renderChart(this.props.data); |
||||
|
} |
||||
|
|
||||
|
componentWillReceiveProps(nextProps) { |
||||
|
if (nextProps.data !== this.props.data) { |
||||
|
this.renderChart(nextProps.data); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
handleRef = (n) => { |
||||
|
this.node = n; |
||||
|
} |
||||
|
|
||||
|
renderChart(data) { |
||||
|
const { height = 0, fit = true, color = '#33abfb', line, xAxis, yAxis } = this.props; |
||||
|
|
||||
|
if (!data || (data && data.length < 1)) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// clean
|
||||
|
this.node.innerHTML = ''; |
||||
|
|
||||
|
const chart = new G2.Chart({ |
||||
|
container: this.node, |
||||
|
forceFit: fit, |
||||
|
height: height + 54, |
||||
|
plotCfg: { |
||||
|
margin: [36, 0, 30, 0], |
||||
|
}, |
||||
|
legend: null, |
||||
|
}); |
||||
|
|
||||
|
if (!xAxis && !yAxis) { |
||||
|
chart.axis(false); |
||||
|
} |
||||
|
|
||||
|
if (xAxis) { |
||||
|
chart.axis('x', xAxis); |
||||
|
} else { |
||||
|
chart.axis('x', false); |
||||
|
} |
||||
|
|
||||
|
if (yAxis) { |
||||
|
chart.axis('y', yAxis); |
||||
|
} else { |
||||
|
chart.axis('y', false); |
||||
|
} |
||||
|
|
||||
|
chart.source(data, { |
||||
|
x: { |
||||
|
type: 'cat', |
||||
|
range: [0, 1], |
||||
|
...xAxis, |
||||
|
}, |
||||
|
y: { |
||||
|
min: 0, |
||||
|
...yAxis, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
chart.tooltip({ |
||||
|
title: null, |
||||
|
crosshairs: false, |
||||
|
map: { |
||||
|
name: 'x', |
||||
|
}, |
||||
|
}); |
||||
|
chart.area().position('x*y').color(color).shape('smooth'); |
||||
|
if (line) { |
||||
|
chart.line().position('x*y').color(color).shape('smooth'); |
||||
|
} |
||||
|
chart.render(); |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { height } = this.props; |
||||
|
|
||||
|
return ( |
||||
|
<div className={styles.miniChart} style={{ height }}> |
||||
|
<div> |
||||
|
<div ref={this.handleRef} /> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default MiniArea; |
||||
@ -0,0 +1,78 @@ |
|||||
|
import React, { PureComponent } from 'react'; |
||||
|
import G2 from 'g2'; |
||||
|
import styles from '../index.less'; |
||||
|
|
||||
|
class MiniBar extends PureComponent { |
||||
|
componentDidMount() { |
||||
|
this.renderChart(this.props.data); |
||||
|
} |
||||
|
|
||||
|
componentWillReceiveProps(nextProps) { |
||||
|
if (nextProps.data !== this.props.data) { |
||||
|
this.renderChart(nextProps.data); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
handleRef = (n) => { |
||||
|
this.node = n; |
||||
|
} |
||||
|
|
||||
|
renderChart(data) { |
||||
|
const { height = 0, fit = true, color = '#33ABFB' } = this.props; |
||||
|
|
||||
|
if (!data || (data && data.length < 1)) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// clean
|
||||
|
this.node.innerHTML = ''; |
||||
|
|
||||
|
const Frame = G2.Frame; |
||||
|
const frame = new Frame(data); |
||||
|
|
||||
|
const chart = new G2.Chart({ |
||||
|
container: this.node, |
||||
|
forceFit: fit, |
||||
|
height: height + 54, |
||||
|
plotCfg: { |
||||
|
margin: [36, 0, 30, 0], |
||||
|
}, |
||||
|
legend: null, |
||||
|
}); |
||||
|
|
||||
|
chart.axis(false); |
||||
|
|
||||
|
chart.source(frame, { |
||||
|
x: { |
||||
|
type: 'cat', |
||||
|
}, |
||||
|
y: { |
||||
|
min: 0, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
chart.tooltip({ |
||||
|
title: null, |
||||
|
crosshairs: false, |
||||
|
map: { |
||||
|
name: 'x', |
||||
|
}, |
||||
|
}); |
||||
|
chart.interval().position('x*y').color(color); |
||||
|
chart.render(); |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { height } = this.props; |
||||
|
|
||||
|
return ( |
||||
|
<div className={styles.miniChart} style={{ height }}> |
||||
|
<div> |
||||
|
<div ref={this.handleRef} /> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default MiniBar; |
||||
@ -0,0 +1,27 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
const MiniProgress = ({ target, color, strokeWidth, percent }) => ( |
||||
|
<div className={styles.miniProgress}> |
||||
|
<div |
||||
|
className={styles.target} |
||||
|
style={{ left: (target ? `${target}%` : null) }} |
||||
|
> |
||||
|
<span style={{ backgroundColor: (color || null) }} /> |
||||
|
<span style={{ backgroundColor: (color || null) }} /> |
||||
|
</div> |
||||
|
<div className={styles.progressWrap}> |
||||
|
<div |
||||
|
className={styles.progress} |
||||
|
style={{ |
||||
|
backgroundColor: (color || null), |
||||
|
width: (percent ? `${percent}%` : null), |
||||
|
height: (strokeWidth || null), |
||||
|
}} |
||||
|
/> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
|
||||
|
export default MiniProgress; |
||||
@ -0,0 +1,37 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
@import "../../../utils/utils.less"; |
||||
|
|
||||
|
.miniProgress { |
||||
|
padding: 5px 0; |
||||
|
position: relative; |
||||
|
width: 100%; |
||||
|
.progressWrap { |
||||
|
background-color: @background-color-base; |
||||
|
position: relative; |
||||
|
} |
||||
|
.progress { |
||||
|
transition: all .4s cubic-bezier(.08, .82, .17, 1) 0s; |
||||
|
border-radius: 1px 0 0 1px; |
||||
|
background-color: @primary-color; |
||||
|
width: 0; |
||||
|
height: 100%; |
||||
|
} |
||||
|
.target { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
span { |
||||
|
border-radius: 100px; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
height: 4px; |
||||
|
width: 2px; |
||||
|
} |
||||
|
span:last-child { |
||||
|
top: auto; |
||||
|
bottom: 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,32 @@ |
|||||
|
import React from 'react'; |
||||
|
import { Icon } from 'antd'; |
||||
|
import classNames from 'classnames'; |
||||
|
|
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
export default ({ theme, title, subTitle, total, subTotal, status, ...rest }) => ( |
||||
|
<div |
||||
|
className={ |
||||
|
classNames(styles.numberInfo, { |
||||
|
[styles[`numberInfo${theme}`]]: theme, |
||||
|
}) |
||||
|
} |
||||
|
{...rest} |
||||
|
> |
||||
|
{ |
||||
|
title && <h4>{title}</h4> |
||||
|
} |
||||
|
<h6>{subTitle}</h6> |
||||
|
<div> |
||||
|
<span>{total}</span> |
||||
|
{ |
||||
|
(status || subTotal) && <span className={styles.subTotal}> |
||||
|
{ |
||||
|
status && <Icon type={`caret-${status}`} /> |
||||
|
} |
||||
|
{subTotal} |
||||
|
</span> |
||||
|
} |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
@ -0,0 +1,46 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
@import "../../../utils/utils.less"; |
||||
|
|
||||
|
.numberInfo { |
||||
|
h4 { |
||||
|
color: @heading-color; |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
h6 { |
||||
|
color: @text-color-secondary; |
||||
|
font-size: @font-size-base; |
||||
|
height: 22px; |
||||
|
line-height: 22px; |
||||
|
.textOverflow(); |
||||
|
} |
||||
|
& > div { |
||||
|
margin-top: 8px; |
||||
|
font-size: 0; |
||||
|
.textOverflow(); |
||||
|
& > span { |
||||
|
color: @heading-color; |
||||
|
display: inline-block; |
||||
|
line-height: 32px; |
||||
|
height: 32px; |
||||
|
font-size: 24px; |
||||
|
margin-right: 32px; |
||||
|
} |
||||
|
.subTotal { |
||||
|
color: @text-color-secondary; |
||||
|
font-size: @font-size-base; |
||||
|
vertical-align: top; |
||||
|
i { |
||||
|
font-size: 12px; |
||||
|
transform: scale(0.82); |
||||
|
margin-right: 4px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.numberInfolight { |
||||
|
& > div { |
||||
|
& > span { |
||||
|
color: @text-color; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,224 @@ |
|||||
|
import React, { Component } from 'react'; |
||||
|
import G2 from 'g2'; |
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
/* eslint react/no-danger:0 */ |
||||
|
class Pie extends Component { |
||||
|
state = { |
||||
|
legendData: [], |
||||
|
left: undefined, |
||||
|
} |
||||
|
|
||||
|
componentDidMount() { |
||||
|
this.renderChart(this.props.data); |
||||
|
} |
||||
|
|
||||
|
componentWillReceiveProps(nextProps) { |
||||
|
this.renderChart(nextProps.data); |
||||
|
} |
||||
|
|
||||
|
handleRef = (n) => { |
||||
|
this.node = n; |
||||
|
} |
||||
|
handleTotalRef = (n) => { |
||||
|
this.totalNode = n; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
handleLegendClick = (item, i) => { |
||||
|
const newItem = item; |
||||
|
newItem.checked = !newItem.checked; |
||||
|
|
||||
|
const legendData = this.state.legendData; |
||||
|
legendData[i] = newItem; |
||||
|
|
||||
|
if (this.chart) { |
||||
|
const filterItem = legendData.filter(l => l.checked).map(l => l.x); |
||||
|
this.chart.filter('x', filterItem); |
||||
|
this.chart.repaint(); |
||||
|
} |
||||
|
|
||||
|
this.setState({ |
||||
|
legendData, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
renderChart(data) { |
||||
|
const { |
||||
|
title, height = 0, |
||||
|
hasLegend, fit = true, |
||||
|
margin, percent, color, |
||||
|
inner = 0.75, |
||||
|
animate = true, |
||||
|
} = this.props; |
||||
|
|
||||
|
let selected = this.props.selected || true; |
||||
|
let tooltip = this.props.tooltips || true; |
||||
|
|
||||
|
let formatColor; |
||||
|
if (percent) { |
||||
|
selected = false; |
||||
|
tooltip = false; |
||||
|
formatColor = (value) => { |
||||
|
if (value === '占比') { |
||||
|
return color || '#0096fa'; |
||||
|
} else { |
||||
|
return '#e9e9e9'; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
/* eslint no-param-reassign: */ |
||||
|
data = [ |
||||
|
{ |
||||
|
x: '占比', |
||||
|
y: parseFloat(percent), |
||||
|
}, |
||||
|
{ |
||||
|
x: '反比', |
||||
|
y: 100 - parseFloat(percent), |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
if (!data || (data && data.length < 1)) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
let m = margin; |
||||
|
if (!margin) { |
||||
|
if (hasLegend) { |
||||
|
m = [24, 240, 24, 0]; |
||||
|
} else if (percent) { |
||||
|
m = [0, 0, 0, 0]; |
||||
|
} else { |
||||
|
m = [24, 0, 24, 0]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const h = title ? (height + m[0] + m[2] + (-46)) : (height + m[0] + m[2]); |
||||
|
|
||||
|
// clean
|
||||
|
this.node.innerHTML = ''; |
||||
|
|
||||
|
const Stat = G2.Stat; |
||||
|
|
||||
|
const chart = new G2.Chart({ |
||||
|
container: this.node, |
||||
|
forceFit: fit, |
||||
|
height: h, |
||||
|
plotCfg: { |
||||
|
margin: m, |
||||
|
}, |
||||
|
animate, |
||||
|
}); |
||||
|
|
||||
|
if (!tooltip) { |
||||
|
chart.tooltip(false); |
||||
|
} else { |
||||
|
chart.tooltip({ |
||||
|
title: null, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
chart.axis(false); |
||||
|
chart.legend(false); |
||||
|
|
||||
|
chart.source(data, { |
||||
|
x: { |
||||
|
type: 'cat', |
||||
|
range: [0, 1], |
||||
|
}, |
||||
|
y: { |
||||
|
min: 0, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
chart.coord('theta', { |
||||
|
inner, |
||||
|
}); |
||||
|
|
||||
|
chart.intervalStack().position(Stat.summary.percent('y')).color('x', formatColor).selected(selected); |
||||
|
chart.render(); |
||||
|
|
||||
|
this.chart = chart; |
||||
|
|
||||
|
let legendData = []; |
||||
|
if (hasLegend) { |
||||
|
const geom = chart.getGeoms()[0]; // 获取所有的图形
|
||||
|
const items = geom.getData(); // 获取图形对应的数据
|
||||
|
legendData = items.map((item) => { |
||||
|
/* eslint no-underscore-dangle:0 */ |
||||
|
const origin = item._origin; |
||||
|
origin.color = item.color; |
||||
|
origin.checked = true; |
||||
|
return origin; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
this.setState({ |
||||
|
legendData, |
||||
|
}, () => { |
||||
|
let left = 0; |
||||
|
if (this.totalNode) { |
||||
|
left = -((this.totalNode.offsetWidth / 2) + ((margin || m)[1] / 2)); |
||||
|
} |
||||
|
this.setState({ |
||||
|
left, |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { height, title, valueFormat, subTitle, total, hasLegend } = this.props; |
||||
|
const { legendData, left } = this.state; |
||||
|
const mt = -(((legendData.length * 38) - 16) / 2); |
||||
|
|
||||
|
return ( |
||||
|
<div className={styles.pie} style={{ height }}> |
||||
|
<div> |
||||
|
{ title && <h4 className={styles.title}>{title}</h4>} |
||||
|
<div className={styles.content}> |
||||
|
<div ref={this.handleRef} /> |
||||
|
{ |
||||
|
(subTitle || total) && <div |
||||
|
className={styles.total} |
||||
|
ref={this.handleTotalRef} |
||||
|
style={{ marginLeft: left, opacity: left ? 1 : 0 }} |
||||
|
> |
||||
|
{ |
||||
|
subTitle && <h4>{subTitle}</h4> |
||||
|
} |
||||
|
{ |
||||
|
// eslint-disable-next-line
|
||||
|
total && <p dangerouslySetInnerHTML={{ __html: total }} /> |
||||
|
} |
||||
|
</div> |
||||
|
} |
||||
|
{ |
||||
|
hasLegend && <ul className={styles.legend} style={{ marginTop: mt }}> |
||||
|
{ |
||||
|
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> |
||||
|
<span className={styles.line} /> |
||||
|
<span className={styles.percent}>{`${(item['..percent'] * 100).toFixed(2)}%`}</span> |
||||
|
<span |
||||
|
className={styles.value} |
||||
|
dangerouslySetInnerHTML={{ |
||||
|
__html: valueFormat ? valueFormat(item.y) : item.y, |
||||
|
}} |
||||
|
/> |
||||
|
</li> |
||||
|
)) |
||||
|
} |
||||
|
</ul> |
||||
|
} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default Pie; |
||||
@ -0,0 +1,70 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
@import "../../../utils/utils.less"; |
||||
|
|
||||
|
.pie { |
||||
|
.content { |
||||
|
position: relative; |
||||
|
} |
||||
|
.legend { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
right: 0; |
||||
|
min-width: 200px; |
||||
|
li { |
||||
|
cursor: pointer; |
||||
|
margin-bottom: 16px; |
||||
|
height: 22px; |
||||
|
line-height: 22px; |
||||
|
} |
||||
|
} |
||||
|
.dot { |
||||
|
border-radius: 8px; |
||||
|
display: inline-block; |
||||
|
margin-right: 8px; |
||||
|
position: relative; |
||||
|
top: -1px; |
||||
|
height: 8px; |
||||
|
width: 8px; |
||||
|
} |
||||
|
.line { |
||||
|
background-color: @border-color-split; |
||||
|
display: inline-block; |
||||
|
margin-right: 8px; |
||||
|
width: 1px; |
||||
|
height: 16px; |
||||
|
} |
||||
|
.legendTitle { |
||||
|
color: @text-color; |
||||
|
margin-right: 8px; |
||||
|
} |
||||
|
.percent { |
||||
|
color: @text-color-secondary; |
||||
|
} |
||||
|
.value { |
||||
|
position: absolute; |
||||
|
right: 0; |
||||
|
} |
||||
|
.total { |
||||
|
opacity: 0; |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
top: 50%; |
||||
|
margin-top: -34px; |
||||
|
text-align: center; |
||||
|
height: 62px; |
||||
|
& > h4 { |
||||
|
color: @text-color-secondary; |
||||
|
font-size: 14px; |
||||
|
line-height: 22px; |
||||
|
height: 22px; |
||||
|
margin-bottom: 8px; |
||||
|
} |
||||
|
& > p { |
||||
|
color: @heading-color; |
||||
|
display: block; |
||||
|
font-size: 24px; |
||||
|
height: 32px; |
||||
|
line-height: 32px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,155 @@ |
|||||
|
import React, { PureComponent } from 'react'; |
||||
|
import G2 from 'g2'; |
||||
|
import { Row, Col } from 'antd'; |
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
/* eslint react/no-danger:0 */ |
||||
|
class Radar extends PureComponent { |
||||
|
state = { |
||||
|
legendData: [], |
||||
|
} |
||||
|
|
||||
|
componentDidMount() { |
||||
|
this.renderChart(this.props.data); |
||||
|
} |
||||
|
|
||||
|
componentWillReceiveProps(nextProps) { |
||||
|
if (nextProps.data !== this.props.data) { |
||||
|
this.renderChart(nextProps.data); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
handleRef = (n) => { |
||||
|
this.node = n; |
||||
|
} |
||||
|
|
||||
|
handleLegendClick = (item, i) => { |
||||
|
const newItem = item; |
||||
|
newItem.checked = !newItem.checked; |
||||
|
|
||||
|
const legendData = this.state.legendData; |
||||
|
legendData[i] = newItem; |
||||
|
|
||||
|
if (this.chart) { |
||||
|
const filterItem = legendData.filter(l => l.checked).map(l => l.name); |
||||
|
this.chart.filter('name', filterItem); |
||||
|
this.chart.repaint(); |
||||
|
} |
||||
|
|
||||
|
this.setState({ |
||||
|
legendData, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
renderChart(data) { |
||||
|
const { height = 0, |
||||
|
hasLegend = true, |
||||
|
fit = true, |
||||
|
tickCount = 4, |
||||
|
margin = [16, 0, 16, 0] } = this.props; |
||||
|
|
||||
|
if (!data || (data && data.length < 1)) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// clean
|
||||
|
this.node.innerHTML = ''; |
||||
|
|
||||
|
const chart = new G2.Chart({ |
||||
|
container: this.node, |
||||
|
forceFit: fit, |
||||
|
height: height - 22, |
||||
|
plotCfg: { |
||||
|
margin, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
this.chart = chart; |
||||
|
|
||||
|
chart.source(data, { |
||||
|
value: { |
||||
|
min: 0, |
||||
|
tickCount, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
chart.coord('polar'); |
||||
|
chart.legend(false); |
||||
|
|
||||
|
chart.axis('label', { |
||||
|
line: null, |
||||
|
}); |
||||
|
|
||||
|
chart.axis('value', { |
||||
|
grid: { |
||||
|
type: 'polygon', |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
chart.line().position('label*value').color('name'); |
||||
|
chart.point().position('label*value').color('name').shape('circle'); |
||||
|
|
||||
|
chart.render(); |
||||
|
|
||||
|
if (hasLegend) { |
||||
|
const geom = chart.getGeoms()[0]; // 获取所有的图形
|
||||
|
const items = geom.getData(); // 获取图形对应的数据
|
||||
|
const legendData = items.map((item) => { |
||||
|
/* eslint no-underscore-dangle:0 */ |
||||
|
const origin = item._origin; |
||||
|
const result = { |
||||
|
name: origin[0].name, |
||||
|
color: item.color, |
||||
|
checked: true, |
||||
|
value: origin.reduce((p, n) => p + n.value, 0), |
||||
|
}; |
||||
|
|
||||
|
return result; |
||||
|
}); |
||||
|
|
||||
|
this.setState({ |
||||
|
legendData, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { height, title, hasLegend } = this.props; |
||||
|
const { legendData } = this.state; |
||||
|
|
||||
|
return ( |
||||
|
<div className={styles.radar} style={{ height }}> |
||||
|
<div> |
||||
|
{ title && <h4>{title}</h4>} |
||||
|
<div ref={this.handleRef} /> |
||||
|
{ |
||||
|
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> |
||||
|
{ |
||||
|
i !== (legendData.length - 1) && <div className={styles.split} /> |
||||
|
} |
||||
|
</div> |
||||
|
</Col> |
||||
|
)) |
||||
|
} |
||||
|
</Row> |
||||
|
} |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default Radar; |
||||
@ -0,0 +1,38 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
@import "../../../utils/utils.less"; |
||||
|
|
||||
|
.radar { |
||||
|
.legend { |
||||
|
margin-top: 16px; |
||||
|
.legendItem { |
||||
|
position: relative; |
||||
|
text-align: center; |
||||
|
p { |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
h6 { |
||||
|
color: @heading-color; |
||||
|
font-size: 24px; |
||||
|
line-height: 32px; |
||||
|
margin-top: 2px; |
||||
|
} |
||||
|
.split { |
||||
|
background-color: @border-color-split; |
||||
|
position: absolute; |
||||
|
top: 8px; |
||||
|
right: 0; |
||||
|
height: 40px; |
||||
|
width: 1px; |
||||
|
} |
||||
|
} |
||||
|
.dot { |
||||
|
border-radius: 8px; |
||||
|
display: inline-block; |
||||
|
margin-right: 8px; |
||||
|
position: relative; |
||||
|
top: -1px; |
||||
|
height: 8px; |
||||
|
width: 8px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
import React from 'react'; |
||||
|
import { Icon } from 'antd'; |
||||
|
|
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
const Item = ({ title, flag, children, ...rest }) => ( |
||||
|
<div {...rest} className={styles.trendItem}> |
||||
|
<span className={styles.title}>{title}</span> |
||||
|
{ flag && <span className={styles[flag]}><Icon type={`caret-${flag}`} /></span>} |
||||
|
<span className={styles.value}>{children}</span> |
||||
|
</div> |
||||
|
); |
||||
|
|
||||
|
const Trend = ({ colorType, children, ...rest }) => ( |
||||
|
<div className={colorType ? (styles[`trend${colorType}`] || styles.trend) : styles.trend} {...rest}> |
||||
|
{children} |
||||
|
</div> |
||||
|
); |
||||
|
|
||||
|
Trend.Item = Item; |
||||
|
|
||||
|
export default Trend; |
||||
@ -0,0 +1,49 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
@import "../../../utils/utils.less"; |
||||
|
|
||||
|
.trend { |
||||
|
font-size: 0; |
||||
|
height: 22px; |
||||
|
white-space: nowrap; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
.trendItem { |
||||
|
display: inline-block; |
||||
|
margin-right: 16px; |
||||
|
color: @text-color; |
||||
|
font-size: @font-size-base; |
||||
|
line-height: 22px; |
||||
|
height: 22px; |
||||
|
.title { |
||||
|
margin-right: 4px; |
||||
|
} |
||||
|
.value { |
||||
|
color: @text-color; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
.up, .down { |
||||
|
color: #00a854; |
||||
|
margin-right: 4px; |
||||
|
position: relative; |
||||
|
top: 1px; |
||||
|
i { |
||||
|
font-size: 12px; |
||||
|
transform: scale(0.83); |
||||
|
} |
||||
|
} |
||||
|
.down { |
||||
|
color: #f04134; |
||||
|
top: -1px; |
||||
|
} |
||||
|
} |
||||
|
.trendItem:last-child { |
||||
|
margin-right: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.trendgray { |
||||
|
.trend(); |
||||
|
.trendItem { |
||||
|
color: @text-color-secondary; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,189 @@ |
|||||
|
import React, { PureComponent } from 'react'; |
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
/* eslint no-return-assign: 0 */ |
||||
|
// riddle: https://riddle.alibaba-inc.com/riddles/2d9a4b90
|
||||
|
|
||||
|
class WaterWave extends PureComponent { |
||||
|
static defaultProps = { |
||||
|
height: 160, |
||||
|
} |
||||
|
state = { |
||||
|
radio: 1, |
||||
|
} |
||||
|
|
||||
|
componentDidMount() { |
||||
|
this.renderChart(); |
||||
|
this.resize(); |
||||
|
|
||||
|
window.addEventListener('resize', () => { |
||||
|
this.resize(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
resize() { |
||||
|
const { height } = this.props; |
||||
|
const realWidth = this.root.parentNode.offsetWidth; |
||||
|
if (realWidth < this.props.height) { |
||||
|
const radio = realWidth / height; |
||||
|
this.setState({ |
||||
|
radio, |
||||
|
}); |
||||
|
} else { |
||||
|
this.setState({ |
||||
|
radio: 1, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
renderChart() { |
||||
|
const { percent, color = '#19AFFA' } = this.props; |
||||
|
const data = percent / 100; |
||||
|
|
||||
|
if (!this.node || !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; |
||||
|
|
||||
|
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]); |
||||
|
ctx.fillStyle = color; |
||||
|
ctx.fill(); |
||||
|
ctx.restore(); |
||||
|
} |
||||
|
|
||||
|
function render() { |
||||
|
ctx.clearRect(0, 0, canvasWidth, canvasHeight); |
||||
|
if (circleLock) { |
||||
|
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 = '#108ee9'; |
||||
|
} |
||||
|
} 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(); |
||||
|
} |
||||
|
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})` }}> |
||||
|
<canvas ref={n => (this.node = n)} width={height} height={height} /> |
||||
|
<div className={styles.text} style={{ width: height }}> |
||||
|
{ |
||||
|
title && <span>{title}</span> |
||||
|
} |
||||
|
<h4>{percent}%</h4> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default WaterWave; |
||||
@ -0,0 +1,25 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
@import "../../../utils/utils.less"; |
||||
|
|
||||
|
.waterWave { |
||||
|
display: inline-block; |
||||
|
position: relative; |
||||
|
transform-origin: left; |
||||
|
.text { |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
top: 32px; |
||||
|
text-align: center; |
||||
|
width: 100%; |
||||
|
span { |
||||
|
color: @text-color-secondary; |
||||
|
font-size: 14px; |
||||
|
line-height: 22px; |
||||
|
} |
||||
|
h4 { |
||||
|
color: @heading-color; |
||||
|
line-height: 32px; |
||||
|
font-size: 24px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
import numeral from 'numeral'; |
||||
|
import ChartCard from './ChartCard'; |
||||
|
import Bar from './Bar'; |
||||
|
import Pie from './Pie'; |
||||
|
import Radar from './Radar'; |
||||
|
import Gauge from './Gauge'; |
||||
|
import MiniArea from './MiniArea'; |
||||
|
import MiniBar from './MiniBar'; |
||||
|
import MiniProgress from './MiniProgress'; |
||||
|
import Trend from './Trend'; |
||||
|
import Field from './Field'; |
||||
|
import NumberInfo from './NumberInfo'; |
||||
|
import WaterWave from './WaterWave'; |
||||
|
import { IconUp, IconDown } from './Icon'; |
||||
|
|
||||
|
const yuan = val => `¥ ${numeral(val).format('0,0')}`; |
||||
|
|
||||
|
export default { |
||||
|
IconUp, |
||||
|
IconDown, |
||||
|
yuan, |
||||
|
Bar, |
||||
|
Pie, |
||||
|
Gauge, |
||||
|
Radar, |
||||
|
MiniBar, |
||||
|
MiniArea, |
||||
|
MiniProgress, |
||||
|
ChartCard, |
||||
|
Trend, |
||||
|
Field, |
||||
|
NumberInfo, |
||||
|
WaterWave, |
||||
|
}; |
||||
@ -0,0 +1,9 @@ |
|||||
|
.miniChart { |
||||
|
position: relative; |
||||
|
width: 100%; |
||||
|
& > div { |
||||
|
position: absolute; |
||||
|
bottom: -34px; |
||||
|
width: 100%; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,108 @@ |
|||||
|
import React, { Component } from 'react'; |
||||
|
|
||||
|
function fixedZero(val) { |
||||
|
return val * 1 < 10 ? `0${val}` : val; |
||||
|
} |
||||
|
|
||||
|
class Countdown extends Component { |
||||
|
constructor(props) { |
||||
|
super(props); |
||||
|
|
||||
|
const { targetTime, lastTime } = this.initTime(props); |
||||
|
|
||||
|
this.state = { |
||||
|
targetTime, |
||||
|
lastTime, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
componentDidMount() { |
||||
|
this.tick(); |
||||
|
} |
||||
|
|
||||
|
componentWillReceiveProps(nextProps) { |
||||
|
if (this.props.target !== nextProps.target) { |
||||
|
const { targetTime, lastTime } = this.initTime(nextProps); |
||||
|
this.setState({ |
||||
|
lastTime, |
||||
|
targetTime, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
componentWillUnmount() { |
||||
|
clearTimeout(this.timer); |
||||
|
} |
||||
|
|
||||
|
timer = 0; |
||||
|
interval = 1000; |
||||
|
initTime = (props) => { |
||||
|
let lastTime = 0; |
||||
|
let targetTime = 0; |
||||
|
try { |
||||
|
if (Object.prototype.toString.call(props.target) === '[object Date]') { |
||||
|
targetTime = props.target.getTime(); |
||||
|
} else { |
||||
|
targetTime = new Date(props.target).getTime(); |
||||
|
} |
||||
|
} catch (e) { |
||||
|
throw new Error('invalid target prop', e); |
||||
|
} |
||||
|
|
||||
|
lastTime = targetTime - new Date().getTime(); |
||||
|
|
||||
|
return { |
||||
|
lastTime, |
||||
|
targetTime, |
||||
|
}; |
||||
|
} |
||||
|
// defaultFormat = time => (
|
||||
|
// <span>{moment(time).format('hh:mm:ss')}</span>
|
||||
|
// );
|
||||
|
defaultFormat = (time) => { |
||||
|
const hours = 60 * 60 * 1000; |
||||
|
const minutes = 60 * 1000; |
||||
|
|
||||
|
const h = fixedZero(Math.floor(time / hours)); |
||||
|
const m = fixedZero(Math.floor((time - (h * hours)) / minutes)); |
||||
|
const s = fixedZero(Math.floor((time - (h * hours) - (m * minutes)) / 1000)); |
||||
|
return ( |
||||
|
<span>{h}:{m}:{s}</span> |
||||
|
); |
||||
|
} |
||||
|
tick = () => { |
||||
|
const { onEnd } = this.props; |
||||
|
let { lastTime } = this.state; |
||||
|
|
||||
|
this.timer = setTimeout(() => { |
||||
|
if (lastTime < this.interval) { |
||||
|
clearTimeout(this.timer); |
||||
|
this.setState({ |
||||
|
lastTime: 0, |
||||
|
}); |
||||
|
|
||||
|
if (onEnd) { |
||||
|
onEnd(); |
||||
|
} |
||||
|
} else { |
||||
|
lastTime -= this.interval; |
||||
|
this.setState({ |
||||
|
lastTime, |
||||
|
}); |
||||
|
|
||||
|
this.tick(); |
||||
|
} |
||||
|
}, this.interval); |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { format = this.defaultFormat } = this.props; |
||||
|
const { lastTime } = this.state; |
||||
|
|
||||
|
const result = format(lastTime); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default Countdown; |
||||
@ -0,0 +1,17 @@ |
|||||
|
import React from 'react'; |
||||
|
import classNames from 'classnames'; |
||||
|
import { Col } from 'antd'; |
||||
|
import styles from './index.less'; |
||||
|
import responsive from './responsive'; |
||||
|
|
||||
|
const Description = ({ term, column, className, children, ...restProps }) => { |
||||
|
const clsString = classNames(styles.description, className); |
||||
|
return ( |
||||
|
<Col className={clsString} {...responsive[column]} {...restProps}> |
||||
|
{term && <div className={styles.term}>{term}</div>} |
||||
|
{children && <div className={styles.detail}>{children}</div>} |
||||
|
</Col> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default Description; |
||||
@ -0,0 +1,18 @@ |
|||||
|
import React from 'react'; |
||||
|
import classNames from 'classnames'; |
||||
|
import { Row } from 'antd'; |
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
export default ({ className, title, col = 3, layout = 'horizontal', gutter = 32, |
||||
|
children, ...restProps }) => { |
||||
|
const clsString = classNames(styles.descriptionList, styles[layout], className); |
||||
|
const column = col > 4 ? 4 : col; |
||||
|
return ( |
||||
|
<div className={clsString} {...restProps}> |
||||
|
{title ? <div className={styles.title}>{title}</div> : null} |
||||
|
<Row gutter={gutter}> |
||||
|
{React.Children.map(children, child => React.cloneElement(child, { column }))} |
||||
|
</Row> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,35 @@ |
|||||
|
--- |
||||
|
order: 0 |
||||
|
title: Basic |
||||
|
--- |
||||
|
|
||||
|
基本描述列表。 |
||||
|
|
||||
|
````jsx |
||||
|
import { DescriptionList } from 'ant-design-pro'; |
||||
|
|
||||
|
const { Description } = DescriptionList; |
||||
|
|
||||
|
ReactDOM.render( |
||||
|
<DescriptionList title="title"> |
||||
|
<Description term="Firefox"> |
||||
|
A free, open source, cross-platform, |
||||
|
graphical web browser developed by the |
||||
|
Mozilla Corporation and hundreds of |
||||
|
volunteers. |
||||
|
</Description> |
||||
|
<Description term="Firefox"> |
||||
|
A free, open source, cross-platform, |
||||
|
graphical web browser developed by the |
||||
|
Mozilla Corporation and hundreds of |
||||
|
volunteers. |
||||
|
</Description> |
||||
|
<Description term="Firefox"> |
||||
|
A free, open source, cross-platform, |
||||
|
graphical web browser developed by the |
||||
|
Mozilla Corporation and hundreds of |
||||
|
volunteers. |
||||
|
</Description> |
||||
|
</DescriptionList> |
||||
|
, mountNode); |
||||
|
```` |
||||
@ -0,0 +1,35 @@ |
|||||
|
--- |
||||
|
order: 1 |
||||
|
title: Vertical |
||||
|
--- |
||||
|
|
||||
|
垂直布局。 |
||||
|
|
||||
|
````jsx |
||||
|
import { DescriptionList } from 'ant-design-pro'; |
||||
|
|
||||
|
const { Description } = DescriptionList; |
||||
|
|
||||
|
ReactDOM.render( |
||||
|
<DescriptionList title="title" layout="vertical"> |
||||
|
<Description term="Firefox"> |
||||
|
A free, open source, cross-platform, |
||||
|
graphical web browser developed by the |
||||
|
Mozilla Corporation and hundreds of |
||||
|
volunteers. |
||||
|
</Description> |
||||
|
<Description term="Firefox"> |
||||
|
A free, open source, cross-platform, |
||||
|
graphical web browser developed by the |
||||
|
Mozilla Corporation and hundreds of |
||||
|
volunteers. |
||||
|
</Description> |
||||
|
<Description term="Firefox"> |
||||
|
A free, open source, cross-platform, |
||||
|
graphical web browser developed by the |
||||
|
Mozilla Corporation and hundreds of |
||||
|
volunteers. |
||||
|
</Description> |
||||
|
</DescriptionList> |
||||
|
, mountNode); |
||||
|
```` |
||||
@ -0,0 +1,5 @@ |
|||||
|
import DescriptionList from './DescriptionList'; |
||||
|
import Description from './Description'; |
||||
|
|
||||
|
DescriptionList.Description = Description; |
||||
|
export default DescriptionList; |
||||
@ -0,0 +1,50 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
|
||||
|
.descriptionList { |
||||
|
// offset the padding-bottom of last row |
||||
|
:global { |
||||
|
.ant-row { |
||||
|
margin-bottom: -16px; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
color: @heading-color; |
||||
|
font-weight: 600; |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.term { |
||||
|
padding-bottom: 16px; |
||||
|
margin-right: 8px; |
||||
|
color: @heading-color; |
||||
|
white-space: nowrap; |
||||
|
display: table-cell; |
||||
|
|
||||
|
&:after { |
||||
|
content: ":"; |
||||
|
margin: 0 8px 0 2px; |
||||
|
position: relative; |
||||
|
top: -.5px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.detail { |
||||
|
padding-bottom: 16px; |
||||
|
color: @text-color; |
||||
|
display: table-cell; |
||||
|
} |
||||
|
|
||||
|
&.vertical { |
||||
|
|
||||
|
.term { |
||||
|
padding-bottom: 8px; |
||||
|
display: block; |
||||
|
} |
||||
|
|
||||
|
.detail { |
||||
|
display: block; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
--- |
||||
|
category: Components |
||||
|
type: General |
||||
|
title: DescriptionList |
||||
|
subtitle: 描述列表 |
||||
|
cols: 1 |
||||
|
--- |
||||
|
|
||||
|
描述列表用来展示一系列文本信息。 |
||||
|
|
||||
|
## API |
||||
|
|
||||
|
### DescriptionList |
||||
|
|
||||
|
| 参数 | 说明 | 类型 | 默认值 | |
||||
|
|----------|------------------------------------------|-------------|-------| |
||||
|
| layout | 布局方式 | Enum{'horizontal', 'vertical'} | 'horizontal' | |
||||
|
| col | 指定信息分几列展示 | number(0 < col <= 4) | 3 | |
||||
|
| title | 列表标题 | ReactNode | - | |
||||
|
| gutter | 列表项间距,单位为 `px` | number | 32 | |
||||
|
|
||||
|
### DescriptionList.Description |
||||
|
|
||||
|
| 参数 | 说明 | 类型 | 默认值 | |
||||
|
|----------|------------------------------------------|-------------|-------| |
||||
|
| term | 列表项标题 | ReactNode | - | |
||||
|
|
||||
|
|
||||
|
|
||||
@ -0,0 +1,6 @@ |
|||||
|
export default { |
||||
|
1: { xs: 24 }, |
||||
|
2: { xs: 24, sm: 12 }, |
||||
|
3: { xs: 24, sm: 12, md: 8 }, |
||||
|
4: { xs: 24, sm: 12, md: 6 }, |
||||
|
}; |
||||
@ -0,0 +1,46 @@ |
|||||
|
import React, { PureComponent } from 'react'; |
||||
|
import PropTypes from 'prop-types'; |
||||
|
import { Link } from 'dva/router'; |
||||
|
import { Button, Icon } from 'antd'; |
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
// TODO: 添加逻辑
|
||||
|
|
||||
|
class EditableLinkGroup extends PureComponent { |
||||
|
static defaultProps = { |
||||
|
links: [], |
||||
|
onAdd: () => { |
||||
|
}, |
||||
|
} |
||||
|
state = { |
||||
|
links: this.props.links, |
||||
|
}; |
||||
|
|
||||
|
handleOnClick() { |
||||
|
const { onAdd } = this.props; |
||||
|
onAdd(); |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { links } = this.state; |
||||
|
return ( |
||||
|
<div className={styles.linkGroup}> |
||||
|
{ |
||||
|
links.map(link => <Link key={`linkGroup-item-${link.id || link.title}`} to={link.href}>{link.title}</Link>) |
||||
|
} |
||||
|
{ |
||||
|
<Button size="small" onClick={() => this.handleOnClick()}> |
||||
|
<Icon type="plus" /> 添加 |
||||
|
</Button> |
||||
|
} |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
EditableLinkGroup.propTypes = { |
||||
|
links: PropTypes.array, |
||||
|
onAdd: PropTypes.func, |
||||
|
}; |
||||
|
|
||||
|
export default EditableLinkGroup; |
||||
@ -0,0 +1,29 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
|
||||
|
.linkGroup { |
||||
|
padding: 20px 0 8px 24px; |
||||
|
font-size: 0; |
||||
|
& > a { |
||||
|
color: @text-color; |
||||
|
display: inline-block; |
||||
|
font-size: @font-size-base; |
||||
|
margin-bottom: 13px; |
||||
|
margin-right: 32px; |
||||
|
&:hover { |
||||
|
color: @primary-color; |
||||
|
} |
||||
|
} |
||||
|
& > button { |
||||
|
border-color: @primary-color; |
||||
|
color: @primary-color; |
||||
|
i { |
||||
|
position: relative; |
||||
|
top: -1px; |
||||
|
} |
||||
|
span { |
||||
|
margin-left: 0 !important; |
||||
|
position: relative; |
||||
|
top: -1px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
--- |
||||
|
order: 2 |
||||
|
title: 403 |
||||
|
--- |
||||
|
|
||||
|
403 页面,配合自定义操作。 |
||||
|
|
||||
|
````jsx |
||||
|
import { Exception } from 'ant-design-pro'; |
||||
|
import { Button } from 'antd'; |
||||
|
|
||||
|
const actions = ( |
||||
|
<div> |
||||
|
<Button type="primary" size="large">回到首页</Button> |
||||
|
<Button size="large">查看详情</Button> |
||||
|
</div> |
||||
|
); |
||||
|
ReactDOM.render( |
||||
|
<Exception type="403" actions={actions} /> |
||||
|
, mountNode); |
||||
|
```` |
||||
@ -0,0 +1,14 @@ |
|||||
|
--- |
||||
|
order: 0 |
||||
|
title: 404 |
||||
|
--- |
||||
|
|
||||
|
404 页面。 |
||||
|
|
||||
|
````jsx |
||||
|
import { Exception } from 'ant-design-pro'; |
||||
|
|
||||
|
ReactDOM.render( |
||||
|
<Exception type="404" /> |
||||
|
, mountNode); |
||||
|
```` |
||||
@ -0,0 +1,14 @@ |
|||||
|
--- |
||||
|
order: 1 |
||||
|
title: 500 |
||||
|
--- |
||||
|
|
||||
|
500 页面。 |
||||
|
|
||||
|
````jsx |
||||
|
import { Exception } from 'ant-design-pro'; |
||||
|
|
||||
|
ReactDOM.render( |
||||
|
<Exception type="500" /> |
||||
|
, mountNode); |
||||
|
```` |
||||
@ -0,0 +1,26 @@ |
|||||
|
import React from 'react'; |
||||
|
import classNames from 'classnames'; |
||||
|
import { Button } from 'antd'; |
||||
|
import { Link } from 'react-router'; |
||||
|
import config from './typeConfig'; |
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
|
||||
|
export default ({ className, type, title, desc, img, actions }) => { |
||||
|
const pageType = type in config ? type : '404'; |
||||
|
const clsString = classNames(styles.exception, className); |
||||
|
return ( |
||||
|
<div className={clsString}> |
||||
|
<div className={styles.imgBlock}> |
||||
|
<img src={img || config[pageType].img} alt="" /> |
||||
|
</div> |
||||
|
<div className={styles.content}> |
||||
|
<h1>{title || config[pageType].title}</h1> |
||||
|
<div className={styles.desc}>{desc || config[pageType].desc}</div> |
||||
|
<div className={styles.actions}> |
||||
|
{actions || <Link to="/"><Button size="large" type="primary">返回首页</Button></Link>} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,37 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
|
||||
|
.exception { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
height: 100%; |
||||
|
|
||||
|
.imgBlock { |
||||
|
flex: 0 0 62.5%; |
||||
|
width: 62.5%; |
||||
|
text-align: right; |
||||
|
padding-right: 152px; |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
flex: auto; |
||||
|
|
||||
|
h1 { |
||||
|
color: @text-color; |
||||
|
font-size: 68px; |
||||
|
line-height: 68px; |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.desc { |
||||
|
color: @text-color-secondary; |
||||
|
font-size: 20px; |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.actions { |
||||
|
button:not(:last-child) { |
||||
|
margin-right: 8px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
--- |
||||
|
category: Components |
||||
|
type: General |
||||
|
title: Exception |
||||
|
subtitle: 异常 |
||||
|
cols: 1 |
||||
|
--- |
||||
|
|
||||
|
异常页用于对页面特定的异常状态进行反馈。通常,它包含对错误状态的阐述,并向用户提供建议或操作,避免用户感到迷失和困惑。 |
||||
|
|
||||
|
## API |
||||
|
|
||||
|
| 参数 | 说明 | 类型 | 默认值 | |
||||
|
|-------------|------------------------------------------|-------------|-------| |
||||
|
| type | 页面类型,若配置,则自带对应类型默认的 `title`,`desc`,`img`,此默认设置可以被 `title`,`desc`,`img` 覆盖 | Enum {'403', '404', '500'} | - | |
||||
|
| title | 标题 | ReactNode | - | |
||||
|
| desc | 补充描述 | ReactNode | - | |
||||
|
| img | 背景图片地址 | string | - | |
||||
|
| actions | 建议操作,配置此属性时默认的『返回首页』按钮不生效 | ReactNode | - | |
||||
@ -0,0 +1,19 @@ |
|||||
|
const config = { |
||||
|
403: { |
||||
|
img: 'https://gw.alipayobjects.com/zos/rmsportal/byTGXmzwJVwgotvxHQsU.svg', |
||||
|
title: '403', |
||||
|
desc: '对不起,你没有权限', |
||||
|
}, |
||||
|
404: { |
||||
|
img: 'https://gw.alipayobjects.com/zos/rmsportal/GdXXOjtMMzaPfCziUVYt.svg', |
||||
|
title: '404', |
||||
|
desc: '你要找的页面不存在', |
||||
|
}, |
||||
|
500: { |
||||
|
img: 'https://gw.alipayobjects.com/zos/rmsportal/OpTUNDbQGfEWLubSrJap.svg', |
||||
|
title: '500', |
||||
|
desc: '服务器错误,我们正在维修', |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
export default config; |
||||
@ -0,0 +1,44 @@ |
|||||
|
import React, { Component } from 'react'; |
||||
|
import PropTypes from 'prop-types'; |
||||
|
import classNames from 'classnames'; |
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
export default class FooterToolbar extends Component { |
||||
|
static contextTypes = { |
||||
|
layoutCollapsed: PropTypes.bool, |
||||
|
}; |
||||
|
state = { |
||||
|
width: '', |
||||
|
}; |
||||
|
componentDidMount() { |
||||
|
this.syncWidth(); |
||||
|
} |
||||
|
componentWillReceiveProps() { |
||||
|
this.syncWidth(); |
||||
|
} |
||||
|
syncWidth() { |
||||
|
const sider = document.querySelectorAll('.ant-layout-sider')[0]; |
||||
|
if (sider) { |
||||
|
this.setState({ |
||||
|
width: `calc(100% - ${sider.style.width})`, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
render() { |
||||
|
const { children, style, className, extra, ...restProps } = this.props; |
||||
|
return ( |
||||
|
<div |
||||
|
className={classNames(className, styles.toolbar)} |
||||
|
ref={this.getRefNode} |
||||
|
style={{ |
||||
|
width: this.state.width, |
||||
|
...style, |
||||
|
}} |
||||
|
{...restProps} |
||||
|
> |
||||
|
<div className={styles.left}>{extra}</div> |
||||
|
<div className={styles.right}>{children}</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
|
||||
|
.toolbar { |
||||
|
position: fixed; |
||||
|
width: 100%; |
||||
|
bottom: 0; |
||||
|
right: 0; |
||||
|
height: 56px; |
||||
|
line-height: 56px; |
||||
|
box-shadow: @shadow-1-up; |
||||
|
background: #fff; |
||||
|
padding: 0 28px; |
||||
|
transition: all .3s; |
||||
|
|
||||
|
&:after { |
||||
|
content: ""; |
||||
|
display: block; |
||||
|
clear: both; |
||||
|
} |
||||
|
|
||||
|
.left { |
||||
|
float: left; |
||||
|
} |
||||
|
|
||||
|
.right { |
||||
|
float: right; |
||||
|
} |
||||
|
|
||||
|
button + button { |
||||
|
margin-left: 8px; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
--- |
||||
|
category: Components |
||||
|
type: General |
||||
|
title: FooterToolbar |
||||
|
subtitle: 底部固定工具栏 |
||||
|
cols: 1 |
||||
|
--- |
||||
|
|
||||
|
## API |
||||
@ -0,0 +1,29 @@ |
|||||
|
--- |
||||
|
order: 0 |
||||
|
title: Basic |
||||
|
--- |
||||
|
|
||||
|
基本页脚。 |
||||
|
|
||||
|
````jsx |
||||
|
import { GlobalFooter } from 'ant-design-pro'; |
||||
|
import { Icon } from 'antd'; |
||||
|
|
||||
|
const links = [{ |
||||
|
title: '帮助', |
||||
|
href: '', |
||||
|
}, { |
||||
|
title: '隐私', |
||||
|
href: '', |
||||
|
}, { |
||||
|
title: '条款', |
||||
|
href: '', |
||||
|
blankTarget: true, |
||||
|
}]; |
||||
|
|
||||
|
const copyright = <div>Copyright <Icon type="copyright" /> 2017 蚂蚁金服体验技术部出品</div>; |
||||
|
|
||||
|
ReactDOM.render( |
||||
|
<GlobalFooter links={links} copyright={copyright} /> |
||||
|
, mountNode); |
||||
|
```` |
||||
@ -0,0 +1,18 @@ |
|||||
|
import React from 'react'; |
||||
|
import classNames from 'classnames'; |
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
export default ({ className, links, copyright }) => { |
||||
|
const clsString = classNames(styles.globalFooter, className); |
||||
|
return ( |
||||
|
<div className={clsString}> |
||||
|
{ |
||||
|
links && |
||||
|
<div className={styles.links}> |
||||
|
{links.map(link => <a key={link.title} target={link.blankTarget ? '_blank' : '_self'} href={link.href}>{link.title}</a>)} |
||||
|
</div> |
||||
|
} |
||||
|
{copyright && <div className={styles.copyright}>{copyright}</div>} |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,23 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
|
||||
|
.globalFooter { |
||||
|
padding: 32px 28px 16px; |
||||
|
text-align: center; |
||||
|
|
||||
|
.links { |
||||
|
margin-bottom: 8px; |
||||
|
|
||||
|
a { |
||||
|
color: @text-color-secondary; |
||||
|
|
||||
|
&:not(:last-child) { |
||||
|
margin-right: 40px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.copyright { |
||||
|
color: @text-color-secondary; |
||||
|
font-size: @font-size-base; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
--- |
||||
|
category: Components |
||||
|
type: General |
||||
|
title: GlobalFooter |
||||
|
subtitle: 全局页脚 |
||||
|
cols: 1 |
||||
|
--- |
||||
|
|
||||
|
页脚属于全局导航的一部分,作为对顶部导航的补充,通过传递数据控制展示内容。 |
||||
|
|
||||
|
## API |
||||
|
|
||||
|
| 参数 | 说明 | 类型 | 默认值 | |
||||
|
|----------|------------------------------------------|-------------|-------| |
||||
|
| links | 链接数据 | array<{ title: ReactNode, href: string, blankTarget?: boolean }> | - | |
||||
|
| copyright | 版权信息 | ReactNode | - | |
||||
@ -0,0 +1,65 @@ |
|||||
|
import React, { PureComponent } from 'react'; |
||||
|
import { Input, Icon, AutoComplete } from 'antd'; |
||||
|
import classNames from 'classnames'; |
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
export default class HeaderSearch extends PureComponent { |
||||
|
static defaultProps = { |
||||
|
defaultActiveFirstOption: false, |
||||
|
}; |
||||
|
state = { |
||||
|
searchMode: false, |
||||
|
value: '', |
||||
|
}; |
||||
|
componentWillUnmount() { |
||||
|
clearTimeout(this.timeout); |
||||
|
} |
||||
|
onKeyDown = (e) => { |
||||
|
if (e.key === 'Enter') { |
||||
|
this.timeout = setTimeout(() => { |
||||
|
this.props.onPressEnter(this.state.value); // Fix duplicate onPressEnter
|
||||
|
}, 0); |
||||
|
} |
||||
|
} |
||||
|
onChange = (value) => { |
||||
|
this.setState({ value }); |
||||
|
} |
||||
|
enterSearchMode = () => { |
||||
|
this.setState({ searchMode: true }, () => { |
||||
|
if (this.state.searchMode) { |
||||
|
this.input.refs.input.focus(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
leaveSearchMode = () => { |
||||
|
this.setState({ |
||||
|
searchMode: false, |
||||
|
value: '', |
||||
|
}); |
||||
|
} |
||||
|
render() { |
||||
|
const { className, placeholder, ...restProps } = this.props; |
||||
|
const inputClass = classNames(styles.input, { |
||||
|
[styles.show]: this.state.searchMode, |
||||
|
}); |
||||
|
return ( |
||||
|
<span className={className} onClick={this.enterSearchMode}> |
||||
|
<Icon type="search" /> |
||||
|
<AutoComplete |
||||
|
className={inputClass} |
||||
|
value={this.state.value} |
||||
|
onChange={this.onChange} |
||||
|
onSelect={this.onSelect} |
||||
|
{...restProps} |
||||
|
> |
||||
|
<Input |
||||
|
placeholder={placeholder} |
||||
|
ref={(node) => { this.input = node; }} |
||||
|
onKeyDown={this.onKeyDown} |
||||
|
onBlur={this.leaveSearchMode} |
||||
|
/> |
||||
|
</AutoComplete> |
||||
|
</span> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
.input { |
||||
|
transition: all .3s; |
||||
|
width: 0; |
||||
|
background: transparent; |
||||
|
border-radius: 0; |
||||
|
:global(.ant-select-selection) { |
||||
|
background: transparent; |
||||
|
} |
||||
|
input { |
||||
|
border: 0; |
||||
|
padding-left: 0; |
||||
|
padding-right: 0; |
||||
|
color: #fff; |
||||
|
&::placeholder { |
||||
|
color: rgba(255, 255, 255, .5); |
||||
|
} |
||||
|
} |
||||
|
&, |
||||
|
&:hover, |
||||
|
&:focus { |
||||
|
border-bottom: 1px solid #fff; |
||||
|
} |
||||
|
&.show { |
||||
|
width: 210px; |
||||
|
margin-left: 8px; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
import React, { Component } from 'react'; |
||||
|
import { Tooltip } from 'antd'; |
||||
|
|
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
/* eslint no-return-assign: 0 */ |
||||
|
class MapChart extends Component { |
||||
|
getRect() { |
||||
|
// 0.4657 = 708 / 1520 (img origin size)
|
||||
|
const width = this.root.offsetWidth; |
||||
|
const height = width * 0.4657; |
||||
|
return { |
||||
|
width, |
||||
|
height, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
return ( |
||||
|
<div className={styles.mapChart} ref={n => (this.root = n)}> |
||||
|
<Tooltip title="等待实现"> |
||||
|
<div className={styles.canvas} ref={n => (this.root = n)}> |
||||
|
<img src="https://gw.alipayobjects.com/zos/rmsportal/fBcAYoxWIjlUXwDjqvzg.png" alt="map" /> |
||||
|
<div ref={n => (this.node = n)} /> |
||||
|
</div> |
||||
|
</Tooltip> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default MapChart; |
||||
@ -0,0 +1,10 @@ |
|||||
|
.mapChart { |
||||
|
background-color: #fff; |
||||
|
position: relative; |
||||
|
.canvas { |
||||
|
width: 100%; |
||||
|
& > img { |
||||
|
width: 100%; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
import React from 'react'; |
||||
|
import { Avatar, Icon } from 'antd'; |
||||
|
import classNames from 'classnames'; |
||||
|
import styles from './NoticeList.less'; |
||||
|
|
||||
|
export default function NoticeList({ data = [], onClick, onClear, title, locale }) { |
||||
|
if (data.length === 0) { |
||||
|
return ( |
||||
|
<div className={styles.notFound}> |
||||
|
<Icon type="frown-o" /> |
||||
|
{locale.emptyText} |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
return ( |
||||
|
<div> |
||||
|
<ul className={styles.list}> |
||||
|
{data.map((item, i) => { |
||||
|
const itemCls = classNames(styles.item, { |
||||
|
[styles.read]: item.read, |
||||
|
}); |
||||
|
return ( |
||||
|
<li className={itemCls} key={item.key || i} onClick={() => onClick(item)}> |
||||
|
<div className={styles.wrapper}> |
||||
|
{item.avatar ? <Avatar className={styles.avatar} src={item.avatar} /> : null} |
||||
|
<div className={styles.content}> |
||||
|
<h4 className={styles.title} title={item.title}>{item.title}</h4> |
||||
|
<div className={styles.description} title={item.description}> |
||||
|
{item.description} |
||||
|
</div> |
||||
|
<div className={styles.datetime}>{item.datetime}</div> |
||||
|
<div className={styles.extra}>{item.extra}</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</li> |
||||
|
); |
||||
|
})} |
||||
|
</ul> |
||||
|
<div className={styles.clear} onClick={onClear}> |
||||
|
{locale.clear}{title} |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,89 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
|
||||
|
.list { |
||||
|
max-height: 400px; |
||||
|
overflow: auto; |
||||
|
.item { |
||||
|
transition: all .3s; |
||||
|
overflow: hidden; |
||||
|
cursor: pointer; |
||||
|
|
||||
|
.wrapper { |
||||
|
margin: 0 32px; |
||||
|
padding: 12px 0; |
||||
|
border-bottom: 1px solid @border-color-split; |
||||
|
} |
||||
|
&.read { |
||||
|
opacity: .4; |
||||
|
} |
||||
|
&:last-child .wrapper { |
||||
|
border-bottom: 0; |
||||
|
} |
||||
|
&:hover { |
||||
|
background: @primary-1; |
||||
|
} |
||||
|
.content { |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
.avatar { |
||||
|
margin-right: 16px; |
||||
|
float: left; |
||||
|
margin-top: 4px; |
||||
|
background: #fff; |
||||
|
} |
||||
|
.title { |
||||
|
font-weight: normal; |
||||
|
color: @text-color; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
.description { |
||||
|
color: @text-color-secondary; |
||||
|
font-size: 12px; |
||||
|
margin-top: 8px; |
||||
|
} |
||||
|
.datetime { |
||||
|
color: @text-color-secondary; |
||||
|
font-size: 12px; |
||||
|
margin-top: 4px; |
||||
|
} |
||||
|
.extra { |
||||
|
position: absolute; |
||||
|
right: 0; |
||||
|
top: 0; |
||||
|
color: @text-color-secondary; |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.notFound { |
||||
|
text-align: center; |
||||
|
height: 120px; |
||||
|
line-height: 120px; |
||||
|
font-size: 14px; |
||||
|
color: @text-color-secondary; |
||||
|
> i { |
||||
|
font-size: 16px; |
||||
|
margin-right: 8px; |
||||
|
vertical-align: middle; |
||||
|
margin-top: -1px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.clear { |
||||
|
height: 46px; |
||||
|
line-height: 46px; |
||||
|
text-align: center; |
||||
|
color: @text-color-secondary; |
||||
|
border-radius: 0 0 @border-radius-base @border-radius-base; |
||||
|
border-top: 1px solid @border-color-split; |
||||
|
transition: all .3s; |
||||
|
cursor: pointer; |
||||
|
|
||||
|
&:hover { |
||||
|
color: @text-color; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
--- |
||||
|
order: 1 |
||||
|
title: 通知图标 |
||||
|
--- |
||||
|
|
||||
|
通常用在全局导航上。 |
||||
|
|
||||
|
````jsx |
||||
|
import { NoticeIcon } from 'ant-design-pro'; |
||||
|
|
||||
|
ReactDOM.render(<NoticeIcon count={5} />, mountNode); |
||||
|
```` |
||||
@ -0,0 +1,41 @@ |
|||||
|
--- |
||||
|
order: 2 |
||||
|
title: 带浮层卡片 |
||||
|
--- |
||||
|
|
||||
|
点击展开通知卡片,展现多种类型的通知。 |
||||
|
|
||||
|
````jsx |
||||
|
import { NoticeIcon } from 'ant-design-pro'; |
||||
|
import moment from 'moment'; |
||||
|
|
||||
|
const data = [{ |
||||
|
key: '1', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', |
||||
|
title: '曲丽丽 评论了你', |
||||
|
description: '描述信息描述信息描述信息', |
||||
|
datetime: moment('2017-08-07').fromNow(), |
||||
|
}, { |
||||
|
key: '2', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', |
||||
|
title: '朱偏右 回复了你', |
||||
|
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', |
||||
|
datetime: moment('2017-08-07').fromNow(), |
||||
|
}, { |
||||
|
key: '3', |
||||
|
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', |
||||
|
title: '标题', |
||||
|
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', |
||||
|
datetime: moment('2017-08-07').fromNow(), |
||||
|
}]; |
||||
|
|
||||
|
ReactDOM.render( |
||||
|
<div style={{ width: 300, textAlign: 'right' }}> |
||||
|
<NoticeIcon count={10}> |
||||
|
<NoticeIcon.Tab list={data} title="通知" /> |
||||
|
<NoticeIcon.Tab list={data} title="消息" /> |
||||
|
<NoticeIcon.Tab list={[]} title="待办" /> |
||||
|
</NoticeIcon> |
||||
|
</div> |
||||
|
, mountNode); |
||||
|
```` |
||||
@ -0,0 +1,93 @@ |
|||||
|
import React, { PureComponent } from 'react'; |
||||
|
import { Popover, Icon, Tabs, Badge, Spin } from 'antd'; |
||||
|
import classNames from 'classnames'; |
||||
|
import List from './NoticeList'; |
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
const { TabPane } = Tabs; |
||||
|
|
||||
|
export default class NoticeIcon extends PureComponent { |
||||
|
static defaultProps = { |
||||
|
onItemClick: () => {}, |
||||
|
onPopupVisibleChange: () => {}, |
||||
|
onTabChange: () => {}, |
||||
|
onClear: () => {}, |
||||
|
loading: false, |
||||
|
locale: { |
||||
|
emptyText: '暂无数据', |
||||
|
clear: '清空', |
||||
|
}, |
||||
|
}; |
||||
|
static Tab = TabPane; |
||||
|
constructor(props) { |
||||
|
super(props); |
||||
|
this.state = {}; |
||||
|
if (props.children && props.children[0]) { |
||||
|
this.state.tabType = props.children[0].props.title; |
||||
|
} |
||||
|
} |
||||
|
onItemClick = (item, tabProps) => { |
||||
|
const { onItemClick } = this.props; |
||||
|
onItemClick(item, tabProps); |
||||
|
} |
||||
|
onTabChange = (tabType) => { |
||||
|
this.setState({ tabType }); |
||||
|
this.props.onTabChange(tabType); |
||||
|
} |
||||
|
getNotificationBox() { |
||||
|
const { children, loading, locale } = this.props; |
||||
|
if (!children) { |
||||
|
return null; |
||||
|
} |
||||
|
const panes = children.map((child) => { |
||||
|
const title = child.props.list && child.props.list.length > 0 |
||||
|
? `${child.props.title} (${child.props.list.length})` : child.props.title; |
||||
|
return ( |
||||
|
<TabPane tab={title} key={child.props.title}> |
||||
|
<List |
||||
|
data={child.props.list} |
||||
|
onClick={item => this.onItemClick(item, child.props)} |
||||
|
onClear={() => this.props.onClear(child.props.title)} |
||||
|
title={child.props.title} |
||||
|
locale={locale} |
||||
|
/> |
||||
|
</TabPane> |
||||
|
); |
||||
|
}); |
||||
|
return ( |
||||
|
<Spin spinning={loading} delay={0}> |
||||
|
<Tabs className={styles.tabs} onChange={this.onTabChange}> |
||||
|
{panes} |
||||
|
</Tabs> |
||||
|
</Spin> |
||||
|
); |
||||
|
} |
||||
|
render() { |
||||
|
const { className, count, popupAlign } = this.props; |
||||
|
const noticeButtonClass = classNames(className, styles.noticeButton); |
||||
|
const notificationBox = this.getNotificationBox(); |
||||
|
const trigger = ( |
||||
|
<span className={noticeButtonClass}> |
||||
|
<Badge count={count} className={styles.badge}> |
||||
|
<Icon type="bell" className={styles.icon} /> |
||||
|
</Badge> |
||||
|
</span> |
||||
|
); |
||||
|
if (!notificationBox) { |
||||
|
return trigger; |
||||
|
} |
||||
|
return ( |
||||
|
<Popover |
||||
|
placement="bottomRight" |
||||
|
content={notificationBox} |
||||
|
popupClassName={styles.popover} |
||||
|
trigger="click" |
||||
|
arrowPointAtCenter |
||||
|
popupAlign={popupAlign} |
||||
|
onVisibleChange={this.props.onPopupVisibleChange} |
||||
|
> |
||||
|
{trigger} |
||||
|
</Popover> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
|
||||
|
.popover { |
||||
|
width: 336px; |
||||
|
:global(.ant-popover-inner-content) { |
||||
|
padding: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.noticeButton { |
||||
|
cursor: pointer; |
||||
|
display: inline-block; |
||||
|
transition: all .3s; |
||||
|
} |
||||
|
|
||||
|
.icon { |
||||
|
font-size: 20px; |
||||
|
} |
||||
|
|
||||
|
.tabs { |
||||
|
:global { |
||||
|
.ant-tabs-nav-container { |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
.ant-tabs-nav-scroll { |
||||
|
text-align: center; |
||||
|
} |
||||
|
.ant-tabs-bar { |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
.ant-tabs-nav .ant-tabs-tab { |
||||
|
padding-top: 16px; |
||||
|
padding-bottom: 16px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
--- |
||||
|
category: Components |
||||
|
type: General |
||||
|
title: NoticeIcon |
||||
|
subtitle: 通知菜单 |
||||
|
cols: 1 |
||||
|
--- |
||||
|
|
||||
|
用在顶部导航上,作为整个产品统一的通知中心。 |
||||
|
|
||||
|
## API |
||||
|
|
||||
|
参数 | 说明 | 类型 | 默认值 |
||||
|
----|------|-----|------ |
||||
|
count | 图标上的消息总数 | number | - |
||||
|
loading | 弹出卡片加载状态 | boolean | false |
||||
|
onClear | 点击清空按钮的回调 | function(tabTitle) | - |
||||
|
onItemClick | 点击列表项的回调 | function(item, tabProps) | - |
||||
|
onTabChange | 切换页签的回调 | function(tabTitle) | - |
||||
|
popupAlign | 弹出卡片的位置配置 | Object [alignConfig](https://github.com/yiminghe/dom-align#alignconfig-object-details) | - |
||||
|
onPopupVisibleChange | 弹出卡片显隐的回调 | function(visible) | - |
||||
|
locale | 默认文案 | Object | `{ emptyText: '暂无数据', clear: '清空' }` |
||||
|
|
||||
|
### NoticeIcon.Tab |
||||
|
|
||||
|
参数 | 说明 | 类型 | 默认值 |
||||
|
----|------|-----|------ |
||||
|
title | 消息分类的页签标题 | string | - |
||||
|
data | 列表数据,格式参照下表 | Array | `[]` |
||||
|
|
||||
|
### Tab data |
||||
|
|
||||
|
参数 | 说明 | 类型 | 默认值 |
||||
|
----|------|-----|------ |
||||
|
avatar | 头像图片链接 | string | - |
||||
|
title | 标题 | ReactNode | - |
||||
|
description | 描述信息 | ReactNode | - |
||||
|
datetime | 时间戳 | ReactNode | - |
||||
|
extra | 额外信息,在列表项右上角 | ReactNode | - |
||||
@ -0,0 +1,71 @@ |
|||||
|
--- |
||||
|
order: 2 |
||||
|
title: With Image |
||||
|
--- |
||||
|
|
||||
|
带图片的页头。 |
||||
|
|
||||
|
````jsx |
||||
|
import { PageHeader } from 'ant-design-pro'; |
||||
|
|
||||
|
const content = ( |
||||
|
<div> |
||||
|
<p>段落示意:蚂蚁金服务设计平台-design.alipay.com,用最小的工作量,无缝接入蚂蚁金服生态, |
||||
|
提供跨越设计与开发的体验解决方案。</p> |
||||
|
<div className="link"> |
||||
|
<a> |
||||
|
<img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/wUTAfuNZjhmCIxEPxQVY.svg" /> 快速开始 |
||||
|
</a> |
||||
|
<a> |
||||
|
<img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/qsmGbwvxTAjXfkkrZYov.svg" /> 产品简介 |
||||
|
</a> |
||||
|
<a> |
||||
|
<img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/UGEHGuwlGDalIJlbsNxL.svg" /> 产品文档 |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
|
||||
|
const extra = ( |
||||
|
<div className="imgContainer"> |
||||
|
<img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/RWDkuWwBqMPLpNqGdxDp.png" /> |
||||
|
</div> |
||||
|
); |
||||
|
|
||||
|
const breadcrumbList = [{ |
||||
|
title: '一级菜单', |
||||
|
href: '/', |
||||
|
}, { |
||||
|
title: '二级菜单', |
||||
|
href: '/', |
||||
|
}, { |
||||
|
title: '三级菜单', |
||||
|
}]; |
||||
|
|
||||
|
ReactDOM.render( |
||||
|
<div> |
||||
|
<PageHeader |
||||
|
title="这是一个标题" |
||||
|
content={content} |
||||
|
extraContent={extra} |
||||
|
breadcrumbList={breadcrumbList} |
||||
|
/> |
||||
|
</div> |
||||
|
, mountNode); |
||||
|
```` |
||||
|
|
||||
|
<style> |
||||
|
#scaffold-src-components-PageHeader-demo-image .imgContainer { |
||||
|
text-align: center; |
||||
|
} |
||||
|
#scaffold-src-components-PageHeader-demo-image .link { |
||||
|
margin-top: 16px; |
||||
|
} |
||||
|
#scaffold-src-components-PageHeader-demo-image .link a { |
||||
|
margin-right: 32px; |
||||
|
} |
||||
|
#scaffold-src-components-PageHeader-demo-image .link img { |
||||
|
vertical-align: middle; |
||||
|
margin-right: 8px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,26 @@ |
|||||
|
--- |
||||
|
order: 3 |
||||
|
title: Simple |
||||
|
--- |
||||
|
|
||||
|
简单的页头。 |
||||
|
|
||||
|
````jsx |
||||
|
import { PageHeader } from 'ant-design-pro'; |
||||
|
|
||||
|
const breadcrumbList = [{ |
||||
|
title: '一级菜单', |
||||
|
href: '/', |
||||
|
}, { |
||||
|
title: '二级菜单', |
||||
|
href: '/', |
||||
|
}, { |
||||
|
title: '三级菜单', |
||||
|
}]; |
||||
|
|
||||
|
ReactDOM.render( |
||||
|
<div> |
||||
|
<PageHeader title="页面标题" breadcrumbList={breadcrumbList} /> |
||||
|
</div> |
||||
|
, mountNode); |
||||
|
```` |
||||
@ -0,0 +1,81 @@ |
|||||
|
--- |
||||
|
order: 1 |
||||
|
title: Standard |
||||
|
--- |
||||
|
|
||||
|
标准页头。 |
||||
|
|
||||
|
````jsx |
||||
|
import { PageHeader } from 'ant-design-pro'; |
||||
|
import { Button, Menu, Dropdown, Icon, Row, Col } from 'antd'; |
||||
|
|
||||
|
const menu = ( |
||||
|
<Menu> |
||||
|
<Menu.Item key="1">选项一</Menu.Item> |
||||
|
<Menu.Item key="2">选项二</Menu.Item> |
||||
|
<Menu.Item key="3">选项三</Menu.Item> |
||||
|
</Menu> |
||||
|
); |
||||
|
|
||||
|
const action = ( |
||||
|
<div> |
||||
|
<Button size="large" type="primary">主操作</Button> |
||||
|
<Button size="large">次操作</Button> |
||||
|
<Dropdown overlay={menu}> |
||||
|
<Button size="large"> |
||||
|
更多 <Icon type="down" /> |
||||
|
</Button> |
||||
|
</Dropdown> |
||||
|
</div> |
||||
|
); |
||||
|
|
||||
|
const extra = ( |
||||
|
<Row> |
||||
|
<Col span={12}> |
||||
|
<div style={{ color: 'rgba(0, 0, 0, 0.43)' }}>状态</div> |
||||
|
<div style={{ color: 'rgba(0, 0, 0, 0.85)', fontSize: 20 }}>待审批</div> |
||||
|
</Col> |
||||
|
<Col span={12}> |
||||
|
<div style={{ color: 'rgba(0, 0, 0, 0.43)' }}>订单金额</div> |
||||
|
<div style={{ color: 'rgba(0, 0, 0, 0.85)', fontSize: 20 }}>¥ 568.08</div> |
||||
|
</Col> |
||||
|
</Row> |
||||
|
); |
||||
|
|
||||
|
const breadcrumbList = [{ |
||||
|
title: '一级菜单', |
||||
|
href: '/', |
||||
|
}, { |
||||
|
title: '二级菜单', |
||||
|
href: '/', |
||||
|
}, { |
||||
|
title: '三级菜单', |
||||
|
}]; |
||||
|
|
||||
|
const tabList = [{ |
||||
|
key: 'detail', |
||||
|
tab: '详情', |
||||
|
}, { |
||||
|
key: 'rule', |
||||
|
tab: '规则', |
||||
|
}]; |
||||
|
|
||||
|
function onTabChange(key) { |
||||
|
console.log(key); |
||||
|
} |
||||
|
|
||||
|
ReactDOM.render( |
||||
|
<div> |
||||
|
<PageHeader |
||||
|
title="单号:234231029431" |
||||
|
logo={<img alt="" src="https://gw.alipayobjects.com/zos/rmsportal/JcBAEvlHGhVvBekIJCWT.svg" />} |
||||
|
action={action} |
||||
|
content="DescriptionList 占位" |
||||
|
extraContent={extra} |
||||
|
breadcrumbList={breadcrumbList} |
||||
|
tabList={tabList} |
||||
|
onTabChange={onTabChange} |
||||
|
/> |
||||
|
</div> |
||||
|
, mountNode); |
||||
|
```` |
||||
@ -0,0 +1,67 @@ |
|||||
|
--- |
||||
|
order: 0 |
||||
|
title: Structure |
||||
|
--- |
||||
|
|
||||
|
基本结构,可以形成多种组合。 |
||||
|
|
||||
|
````jsx |
||||
|
import { PageHeader } from 'ant-design-pro'; |
||||
|
|
||||
|
const breadcrumbList = [{ |
||||
|
title: '面包屑', |
||||
|
}]; |
||||
|
|
||||
|
const tabList = [{ |
||||
|
key: '1', |
||||
|
tab: '页签一', |
||||
|
}, { |
||||
|
key: '2', |
||||
|
tab: '页签二', |
||||
|
}, { |
||||
|
key: '3', |
||||
|
tab: '页签三', |
||||
|
}]; |
||||
|
|
||||
|
ReactDOM.render( |
||||
|
<div> |
||||
|
<PageHeader |
||||
|
className="tabs" |
||||
|
title={<div className="title">Title</div>} |
||||
|
logo={<div className="logo">logo</div>} |
||||
|
action={<div className="action">action</div>} |
||||
|
content={<div className="content">content</div>} |
||||
|
extraContent={<div className="extraContent">extraContent</div>} |
||||
|
breadcrumbList={breadcrumbList} |
||||
|
tabList={tabList} |
||||
|
/> |
||||
|
</div> |
||||
|
, mountNode); |
||||
|
```` |
||||
|
|
||||
|
<style> |
||||
|
#scaffold-src-components-PageHeader-demo-structure .code-box-demo { |
||||
|
background: #eee; |
||||
|
} |
||||
|
#scaffold-src-components-PageHeader-demo-structure .logo { |
||||
|
background: #3ba0e9; |
||||
|
color: #fff; |
||||
|
height: 100%; |
||||
|
} |
||||
|
#scaffold-src-components-PageHeader-demo-structure .title { |
||||
|
background: rgba(16, 142, 233, 1); |
||||
|
color: #fff; |
||||
|
} |
||||
|
#scaffold-src-components-PageHeader-demo-structure .action { |
||||
|
background: #7dbcea; |
||||
|
color: #fff; |
||||
|
} |
||||
|
#scaffold-src-components-PageHeader-demo-structure .content { |
||||
|
background: #7dbcea; |
||||
|
color: #fff; |
||||
|
} |
||||
|
#scaffold-src-components-PageHeader-demo-structure .extraContent { |
||||
|
background: #7dbcea; |
||||
|
color: #fff; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,98 @@ |
|||||
|
import React, { PureComponent } from 'react'; |
||||
|
import PropTypes from 'prop-types'; |
||||
|
import { Breadcrumb, Tabs } from 'antd'; |
||||
|
import { Link } from 'dva/router'; |
||||
|
import classNames from 'classnames'; |
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
const TabPane = Tabs.TabPane; |
||||
|
|
||||
|
function itemRender(route, params, routes, paths) { |
||||
|
const last = routes.indexOf(route) === routes.length - 1; |
||||
|
return (last || !route.component) |
||||
|
? <span>{route.breadcrumbName}</span> |
||||
|
: <Link to={paths.join('/') || '/'}>{route.breadcrumbName}</Link>; |
||||
|
} |
||||
|
|
||||
|
export default class PageHeader extends PureComponent { |
||||
|
static contextTypes = { |
||||
|
routes: PropTypes.array, |
||||
|
params: PropTypes.object, |
||||
|
}; |
||||
|
onChange = (key) => { |
||||
|
if (this.props.onTabChange) { |
||||
|
this.props.onTabChange(key); |
||||
|
} |
||||
|
}; |
||||
|
getBreadcrumbProps = () => { |
||||
|
return { |
||||
|
routes: this.props.routes || this.context.routes, |
||||
|
params: this.props.params || this.context.params, |
||||
|
}; |
||||
|
}; |
||||
|
render() { |
||||
|
const { routes, params } = this.getBreadcrumbProps(); |
||||
|
const { title, logo, action, content, extraContent, |
||||
|
breadcrumbList, tabList, className } = this.props; |
||||
|
const clsString = classNames(styles.pageHeader, className); |
||||
|
let breadcrumb; |
||||
|
if (routes && params) { |
||||
|
breadcrumb = ( |
||||
|
<Breadcrumb |
||||
|
className={styles.breadcrumb} |
||||
|
routes={routes.filter(route => route.breadcrumbName)} |
||||
|
params={params} |
||||
|
itemRender={itemRender} |
||||
|
/> |
||||
|
); |
||||
|
} else if (breadcrumbList && breadcrumbList.length) { |
||||
|
breadcrumb = ( |
||||
|
<Breadcrumb className={styles.breadcrumb}> |
||||
|
{ |
||||
|
breadcrumbList.map(item => ( |
||||
|
<Breadcrumb.Item> |
||||
|
{item.href ? <a href="">{item.title}</a> : item.title} |
||||
|
</Breadcrumb.Item>) |
||||
|
) |
||||
|
} |
||||
|
</Breadcrumb> |
||||
|
); |
||||
|
} else { |
||||
|
breadcrumb = null; |
||||
|
} |
||||
|
|
||||
|
const tabDefaultValue = tabList && tabList.filter(item => item.default)[0]; |
||||
|
|
||||
|
return ( |
||||
|
<div className={clsString}> |
||||
|
{breadcrumb} |
||||
|
<div className={styles.detail}> |
||||
|
{logo && <div className={styles.logo}>{logo}</div>} |
||||
|
<div className={styles.main}> |
||||
|
<div className={styles.row}> |
||||
|
{title && <h1 className={styles.title}>{title}</h1>} |
||||
|
{action && <div className={styles.action}>{action}</div>} |
||||
|
</div> |
||||
|
<div className={styles.row}> |
||||
|
{content && <div className={styles.content}>{content}</div>} |
||||
|
{extraContent && <div className={styles.extraContent}>{extraContent}</div>} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
{ |
||||
|
tabList && |
||||
|
tabList.length && |
||||
|
<Tabs |
||||
|
className={styles.tabs} |
||||
|
defaultActiveKey={(tabDefaultValue && tabDefaultValue.key)} |
||||
|
onChange={this.onChange} |
||||
|
> |
||||
|
{ |
||||
|
tabList.map(item => <TabPane tab={item.tab} key={item.key} />) |
||||
|
} |
||||
|
</Tabs> |
||||
|
} |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,95 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
|
||||
|
.pageHeader { |
||||
|
background: @component-background; |
||||
|
padding: 18px 28px 0 36px; |
||||
|
border-bottom: @border-width-base @border-style-base @border-color-split; |
||||
|
|
||||
|
.detail { |
||||
|
display: flex; |
||||
|
} |
||||
|
|
||||
|
.row { |
||||
|
display: flex; |
||||
|
} |
||||
|
|
||||
|
.breadcrumb { |
||||
|
margin-bottom: 18px; |
||||
|
} |
||||
|
|
||||
|
.tabs { |
||||
|
margin: 0 0 -17px -8px; |
||||
|
|
||||
|
:global { |
||||
|
.ant-tabs-bar { |
||||
|
border-bottom: @border-width-base @border-style-base @border-color-split; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.logo { |
||||
|
flex: 0 1 auto; |
||||
|
margin-right: 16px; |
||||
|
padding-top: 1px; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 20px; |
||||
|
font-weight: 500; |
||||
|
color: @heading-color; |
||||
|
} |
||||
|
|
||||
|
.action { |
||||
|
margin-left: 56px; |
||||
|
min-width: 266px; |
||||
|
|
||||
|
button:not(:last-child) { |
||||
|
margin-right: 8px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.title, .action, .content, .extraContent, .main { |
||||
|
flex: auto; |
||||
|
} |
||||
|
|
||||
|
.title, .action { |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.logo, .content, .extraContent { |
||||
|
margin-bottom: 12px; |
||||
|
} |
||||
|
|
||||
|
.action, .extraContent { |
||||
|
text-align: right; |
||||
|
} |
||||
|
|
||||
|
.extraContent { |
||||
|
margin-left: 88px; |
||||
|
min-width: 242px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media screen and (max-width: @screen-md) { |
||||
|
.pageHeader { |
||||
|
.extraContent { |
||||
|
margin-left: 44px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media screen and (max-width: @screen-sm) { |
||||
|
.pageHeader { |
||||
|
.extraContent { |
||||
|
margin-left: 24px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media screen and (max-width: @screen-xs) { |
||||
|
.pageHeader { |
||||
|
.extraContent { |
||||
|
margin-left: 8px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
--- |
||||
|
category: Components |
||||
|
type: General |
||||
|
title: PageHeader |
||||
|
subtitle: 页头 |
||||
|
cols: 1 |
||||
|
--- |
||||
|
|
||||
|
页头用来声明页面的主题,包含了用户所关注的最重要的信息,使用户可以快速理解当前页面是什么以及它的功能。 |
||||
|
|
||||
|
## API |
||||
|
|
||||
|
| 参数 | 说明 | 类型 | 默认值 | |
||||
|
|----------|------------------------------------------|-------------|-------| |
||||
|
| title | title 区域 | ReactNode | - | |
||||
|
| logo | logo区域 | ReactNode | - | |
||||
|
| action | 操作区,位于 title 行的行尾 | ReactNode | - | |
||||
|
| content | 内容区 | ReactNode | - | |
||||
|
| extraContent | 额外内容区,位于content的右侧 | ReactNode | - | |
||||
|
| routes | 面包屑相关属性,router 的路由栈信息 | object[] | - | |
||||
|
| params | 面包屑相关属性,路由的参数 | object | - | |
||||
|
| breadcrumbList | 面包屑数据,配置了 `routes` `params` 时此属性无效 | array<{title: ReactNode, href?: string}> | - | |
||||
|
| tabList | tab 标题列表 | array<{key: string, tab: ReactNode}> | - | |
||||
|
| onTabChange | 切换面板的回调 | (key) => void | - | |
||||
|
|
||||
|
> 面包屑的配置方式有两种,一是结合 `react-router`,通过配置 `routes` 及 `params` 实现,类似 [面包屑 Demo](https://ant.design/components/breadcrumb-cn/#components-breadcrumb-demo-router);二是直接配置 `breadcrumbList`。 你也可以将 `routes` 及 `params` 放到 context 中,`PageHeader` 组件会自动获取。 |
||||
@ -0,0 +1,10 @@ |
|||||
|
import React from 'react'; |
||||
|
import { Radio } from 'antd'; |
||||
|
|
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
const RadioButton = Radio.Button; |
||||
|
|
||||
|
export default props => (<div className={styles.radioText}> |
||||
|
<RadioButton {...props} /> |
||||
|
</div>); |
||||
@ -0,0 +1,12 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
@import "../../utils/utils.less"; |
||||
|
|
||||
|
.radioText { |
||||
|
display: inline; |
||||
|
:global { |
||||
|
.ant-radio-button-wrapper { |
||||
|
border: none; |
||||
|
padding: 0 12px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
--- |
||||
|
order: 1 |
||||
|
title: Classic |
||||
|
--- |
||||
|
|
||||
|
典型结果页面。 |
||||
|
|
||||
|
````jsx |
||||
|
import { Result } from 'ant-design-pro'; |
||||
|
import { Button, Row, Col, Icon, Steps } from 'antd'; |
||||
|
|
||||
|
const Step = Steps.Step; |
||||
|
|
||||
|
const desc1 = ( |
||||
|
<div style={{ fontSize: 14 }}> |
||||
|
<div style={{ marginTop: 4, marginBottom: 8 }}>曲丽丽 <Icon type="dingding-o" /></div> |
||||
|
<div>2016-12-12 12:32</div> |
||||
|
</div> |
||||
|
); |
||||
|
|
||||
|
const desc2 = ( |
||||
|
<div style={{ fontSize: 14 }}> |
||||
|
<div style={{ marginTop: 4, marginBottom: 8 }}>周毛毛 <Icon type="dingding-o" style={{ color: '#00A0E9' }} /></div> |
||||
|
<div><a href="">催一下</a></div> |
||||
|
</div> |
||||
|
); |
||||
|
|
||||
|
const extra = ( |
||||
|
<div> |
||||
|
<div style={{ fontSize: 16, color: 'rgba(0, 0, 0, 0.65)', fontWeight: '600', marginBottom: 16 }}> |
||||
|
项目名称 |
||||
|
</div> |
||||
|
<Row style={{ color: 'rgba(0, 0, 0, 0.65)', marginBottom: 20 }}> |
||||
|
<Col span={6}>项目 ID:23421</Col> |
||||
|
<Col span={6}>负责人:曲丽丽</Col> |
||||
|
<Col span={12}>生效时间:2016-12-12 ~ 2017-12-12</Col> |
||||
|
</Row> |
||||
|
<Steps progressDot current={1}> |
||||
|
<Step title="创建项目" description={desc1} /> |
||||
|
<Step title="部门初审" description={desc2} /> |
||||
|
<Step title="财务复核" /> |
||||
|
<Step title="完成" /> |
||||
|
</Steps> |
||||
|
</div> |
||||
|
); |
||||
|
|
||||
|
const actions = ( |
||||
|
<div> |
||||
|
<Button size="large" type="primary">返回列表</Button> |
||||
|
<Button size="large">查看项目</Button> |
||||
|
<Button size="large">打 印</Button> |
||||
|
</div> |
||||
|
); |
||||
|
|
||||
|
ReactDOM.render( |
||||
|
<Result |
||||
|
type="success" |
||||
|
title="提交成功" |
||||
|
description="提交结果页用于反馈一系列操作任务的处理结果,如果仅是简单操作,使用 Message 全局提示反馈即可。本文字区域可以展示简单的补充说明,如果有类似展示“单据”的需求,下面这个灰色区域可以呈现比较复杂的内容。" |
||||
|
extra={extra} |
||||
|
actions={actions} |
||||
|
/> |
||||
|
, mountNode); |
||||
|
```` |
||||
@ -0,0 +1,39 @@ |
|||||
|
--- |
||||
|
order: 2 |
||||
|
title: Failed |
||||
|
--- |
||||
|
|
||||
|
提交失败。 |
||||
|
|
||||
|
````jsx |
||||
|
import { Result } from 'ant-design-pro'; |
||||
|
import { Button, Icon } from 'antd'; |
||||
|
|
||||
|
const extra = ( |
||||
|
<div> |
||||
|
<div style={{ fontSize: 16, color: 'rgba(0, 0, 0, 0.65)', fontWeight: '600', marginBottom: 16 }}> |
||||
|
您提交的内容有如下错误: |
||||
|
</div> |
||||
|
<div style={{ marginBottom: 8 }}> |
||||
|
<Icon style={{ color: '#f04134', marginRight: 8 }} type="close-circle" />您的账户已被冻结 |
||||
|
<a style={{ marginLeft: 24 }}>立即解冻 <Icon type="right" /></a> |
||||
|
</div> |
||||
|
<div> |
||||
|
<Icon style={{ color: '#f04134', marginRight: 8 }} type="close-circle" />您的账户还不具备申请资格 |
||||
|
<a style={{ marginLeft: 24 }}>立即升级 <Icon type="right" /></a> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
|
||||
|
const actions = <Button size="large" type="primary">返回修改</Button>; |
||||
|
|
||||
|
ReactDOM.render( |
||||
|
<Result |
||||
|
type="error" |
||||
|
title="提交失败" |
||||
|
description="请核对并修改以下信息后,再重新提交。" |
||||
|
extra={extra} |
||||
|
actions={actions} |
||||
|
/> |
||||
|
, mountNode); |
||||
|
```` |
||||
@ -0,0 +1,20 @@ |
|||||
|
--- |
||||
|
order: 0 |
||||
|
title: Structure |
||||
|
--- |
||||
|
|
||||
|
结构包含 `处理结果`,`补充信息` 以及 `操作建议` 三个部分,其中 `处理结果` 由 `提示图标`,`标题` 和 `结果描述` 组成。 |
||||
|
|
||||
|
````jsx |
||||
|
import { Result } from 'ant-design-pro'; |
||||
|
|
||||
|
ReactDOM.render( |
||||
|
<Result |
||||
|
type="success" |
||||
|
title={<div style={{ background: '#7dbcea', color: '#fff' }}>标题</div>} |
||||
|
description={<div style={{ background: 'rgba(16, 142, 233, 1)', color: '#fff' }}>结果描述</div>} |
||||
|
extra="其他补充信息,自带灰底效果" |
||||
|
actions={<div style={{ background: '#3ba0e9', color: '#fff' }}>操作建议,一般放置按钮组</div>} |
||||
|
/> |
||||
|
, mountNode); |
||||
|
```` |
||||
@ -0,0 +1,21 @@ |
|||||
|
import React from 'react'; |
||||
|
import classNames from 'classnames'; |
||||
|
import { Icon } from 'antd'; |
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
export default ({ className, type, title, description, extra, actions, ...restProps }) => { |
||||
|
const iconMap = { |
||||
|
error: <Icon className={styles.error} type="close-circle" />, |
||||
|
success: <Icon className={styles.success} type="check-circle" />, |
||||
|
}; |
||||
|
const clsString = classNames(styles.result, className); |
||||
|
return ( |
||||
|
<div className={clsString} {...restProps}> |
||||
|
<div className={styles.icon}>{iconMap[type]}</div> |
||||
|
<div className={styles.title}>{title}</div> |
||||
|
{description && <div className={styles.description}>{description}</div>} |
||||
|
{extra && <div className={styles.extra}>{extra}</div>} |
||||
|
{actions && <div className={styles.actions}>{actions}</div>} |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,45 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
|
||||
|
.result { |
||||
|
text-align: center; |
||||
|
|
||||
|
.icon { |
||||
|
font-size: 72px; |
||||
|
line-height: 72px; |
||||
|
margin-bottom: 24px; |
||||
|
|
||||
|
& > .success { |
||||
|
color: @success-color; |
||||
|
} |
||||
|
|
||||
|
& > .error { |
||||
|
color: @error-color; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 24px; |
||||
|
color: @heading-color; |
||||
|
font-weight: 500; |
||||
|
line-height: 32px; |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.description { |
||||
|
font-size: 14px; |
||||
|
color: @text-color-secondary; |
||||
|
margin-bottom: 24px; |
||||
|
} |
||||
|
|
||||
|
.extra { |
||||
|
background: rgba(245, 245, 245, 0.5); |
||||
|
padding: 24px 40px; |
||||
|
margin-bottom: 32px; |
||||
|
border-radius: @border-radius-sm; |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
.actions button:not(:last-child) { |
||||
|
margin-right: 8px; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
--- |
||||
|
category: Components |
||||
|
type: General |
||||
|
title: Result |
||||
|
subtitle: 处理结果 |
||||
|
cols: 1 |
||||
|
--- |
||||
|
|
||||
|
结果页用于对用户进行的一系列任务处理结果进行反馈。 |
||||
|
|
||||
|
## API |
||||
|
|
||||
|
| 参数 | 说明 | 类型 | 默认值 | |
||||
|
|----------|------------------------------------------|-------------|-------| |
||||
|
| type | 类型,不同类型自带对应的图标 | Enum {'success', 'error'} | - | |
||||
|
| title | 标题 | ReactNode | - | |
||||
|
| description | 结果描述 | ReactNode | - | |
||||
|
| extra | 补充信息,有默认的灰色背景 | ReactNode | - | |
||||
|
| actions | 操作建议,推荐放置跳转链接,按钮组等 | ReactNode | - | |
||||
@ -0,0 +1,15 @@ |
|||||
|
import React from 'react'; |
||||
|
import { Button, Input } from 'antd'; |
||||
|
|
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
export default ({ onSearch = () => ({}), text = '搜索', ...reset }) => ( |
||||
|
<div className={styles.search}> |
||||
|
<Input |
||||
|
placeholder="请输入" |
||||
|
size="large" |
||||
|
{...reset} |
||||
|
addonAfter={<Button onClick={onSearch} type="primary">{text}</Button>} |
||||
|
/> |
||||
|
</div> |
||||
|
); |
||||
@ -0,0 +1,45 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
@import "../../utils/utils.less"; |
||||
|
|
||||
|
.search { |
||||
|
display: inline-block; |
||||
|
:global { |
||||
|
.ant-input-group-addon { |
||||
|
border: none; |
||||
|
padding: 0; |
||||
|
} |
||||
|
.ant-input-group .ant-input { |
||||
|
width: 522px; |
||||
|
} |
||||
|
} |
||||
|
input { |
||||
|
border-right: none; |
||||
|
height: 40px; |
||||
|
line-height: 40px; |
||||
|
} |
||||
|
button { |
||||
|
border-radius: 0 @border-radius-base @border-radius-base 0; |
||||
|
width: 86px; |
||||
|
height: 40px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media screen and (max-width: @screen-sm) { |
||||
|
.search { |
||||
|
:global { |
||||
|
.ant-input-group .ant-input { |
||||
|
width: 300px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media screen and (max-width: @screen-xs) { |
||||
|
.search { |
||||
|
:global { |
||||
|
.ant-input-group .ant-input { |
||||
|
width: 200px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
import React from 'react'; |
||||
|
import classNames from 'classnames'; |
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
export default ({ title, children, last, block, grid, ...rest }) => { |
||||
|
const cls = classNames(styles.standardFormRow, { |
||||
|
[styles.standardFormRowBlock]: block, |
||||
|
[styles.standardFormRowLast]: last, |
||||
|
[styles.standardFormRowGrid]: grid, |
||||
|
}); |
||||
|
|
||||
|
return ( |
||||
|
<div className={cls} {...rest}> |
||||
|
{ |
||||
|
title && <div className={styles.label}> |
||||
|
<span>{title}</span> |
||||
|
</div> |
||||
|
} |
||||
|
<div className={styles.content}> |
||||
|
{children} |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,68 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
@import "../../utils/utils.less"; |
||||
|
|
||||
|
.standardFormRow { |
||||
|
border-bottom: 1px dashed @border-color-split; |
||||
|
padding-bottom: 16px; |
||||
|
margin-bottom: 16px; |
||||
|
display: flex; |
||||
|
:global { |
||||
|
.ant-form-item { |
||||
|
margin-right: 24px; |
||||
|
} |
||||
|
.ant-form-item-label label { |
||||
|
color: @text-color; |
||||
|
margin-right: 16px; |
||||
|
} |
||||
|
} |
||||
|
.label { |
||||
|
color: @heading-color; |
||||
|
font-size: @font-size-base; |
||||
|
margin-right: 24px; |
||||
|
flex: 0 0 auto; |
||||
|
text-align: right; |
||||
|
& > span { |
||||
|
display: inline-block; |
||||
|
height: 32px; |
||||
|
line-height: 32px; |
||||
|
&:after { |
||||
|
content: ':'; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.content { |
||||
|
flex: 1 1 0; |
||||
|
:global { |
||||
|
.ant-form-item:last-child { |
||||
|
margin-right: 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.standardFormRowLast { |
||||
|
border: none; |
||||
|
padding-bottom: 0; |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
.standardFormRowBlock { |
||||
|
:global { |
||||
|
.ant-form-item, |
||||
|
div.ant-form-item-control-wrapper { |
||||
|
display: block; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.standardFormRowGrid { |
||||
|
:global { |
||||
|
.ant-form-item, |
||||
|
div.ant-form-item-control-wrapper { |
||||
|
display: block; |
||||
|
} |
||||
|
.ant-form-item-label { |
||||
|
float: left; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,149 @@ |
|||||
|
import React, { PureComponent } from 'react'; |
||||
|
import moment from 'moment'; |
||||
|
import { Table, Alert, Badge } from 'antd'; |
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
class StandardTable extends PureComponent { |
||||
|
state = { |
||||
|
selectedRowKeys: [], |
||||
|
selectedRows: [], |
||||
|
totalCallNo: 0, |
||||
|
loading: false, |
||||
|
}; |
||||
|
|
||||
|
componentWillReceiveProps(nextProps) { |
||||
|
// clean state
|
||||
|
if (nextProps.selectedRows.length === 0) { |
||||
|
this.setState({ |
||||
|
selectedRows: [], |
||||
|
selectedRowKeys: [], |
||||
|
totalCallNo: 0, |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
handleRowSelectChange = (selectedRowKeys, selectedRows) => { |
||||
|
const totalCallNo = selectedRows.reduce((sum, val) => { |
||||
|
return sum + parseFloat(val.callNo, 10); |
||||
|
}, 0); |
||||
|
|
||||
|
if (this.props.onSelectRow) { |
||||
|
this.props.onSelectRow(selectedRows); |
||||
|
} |
||||
|
|
||||
|
this.setState({ selectedRowKeys, selectedRows, totalCallNo }); |
||||
|
} |
||||
|
|
||||
|
handleTableChange = (pagination, filters, sorter) => { |
||||
|
this.props.onChange(pagination, filters, sorter); |
||||
|
} |
||||
|
|
||||
|
cleanSelectedKeys = () => { |
||||
|
this.handleRowSelectChange([], []); |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { selectedRowKeys, totalCallNo } = this.state; |
||||
|
const { data: { list, pagination }, loading } = this.props; |
||||
|
|
||||
|
const status = ['关闭', '运行中']; |
||||
|
|
||||
|
const columns = [ |
||||
|
{ |
||||
|
title: '规则编号', |
||||
|
dataIndex: 'no', |
||||
|
}, |
||||
|
{ |
||||
|
title: '描述', |
||||
|
dataIndex: 'description', |
||||
|
}, |
||||
|
{ |
||||
|
title: '服务调用次数', |
||||
|
dataIndex: 'callNo', |
||||
|
sorter: true, |
||||
|
render: val => ( |
||||
|
<p style={{ textAlign: 'center' }}> |
||||
|
{val} 万 |
||||
|
</p> |
||||
|
), |
||||
|
}, |
||||
|
{ |
||||
|
title: '状态', |
||||
|
dataIndex: 'status', |
||||
|
filters: [ |
||||
|
{ |
||||
|
text: status[0], |
||||
|
value: 0, |
||||
|
}, |
||||
|
{ |
||||
|
text: status[1], |
||||
|
value: 1, |
||||
|
}, |
||||
|
], |
||||
|
render(val) { |
||||
|
if (val === 0) { |
||||
|
return <Badge status="default" text={status[val]} />; |
||||
|
} else { |
||||
|
return <Badge status="processing" text={status[val]} />; |
||||
|
} |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
title: '更新时间', |
||||
|
dataIndex: 'updatedAt', |
||||
|
sorter: true, |
||||
|
render: val => <span>{moment(val).format('YYYY-MM-DD HH:mm:ss')}</span>, |
||||
|
}, |
||||
|
{ |
||||
|
title: '操作', |
||||
|
render: () => ( |
||||
|
<p> |
||||
|
<a href="">配置</a> |
||||
|
<span className={styles.splitLine} /> |
||||
|
<a href="">订阅警报</a> |
||||
|
</p> |
||||
|
), |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
const paginationProps = { |
||||
|
showSizeChanger: true, |
||||
|
showQuickJumper: true, |
||||
|
...pagination, |
||||
|
}; |
||||
|
|
||||
|
const rowSelection = { |
||||
|
selectedRowKeys, |
||||
|
onChange: this.handleRowSelectChange, |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<div className={styles.standardTable}> |
||||
|
<div className={styles.tableAlert}> |
||||
|
<Alert |
||||
|
message={( |
||||
|
<p> |
||||
|
已选择 <a>{selectedRowKeys.length}</a> 项 |
||||
|
服务调用总计 <span style={{ fontWeight: 600 }}>{totalCallNo}</span> 万 |
||||
|
<a onClick={this.cleanSelectedKeys} style={{ marginLeft: 8 }}>清空</a> |
||||
|
</p> |
||||
|
)} |
||||
|
type="info" |
||||
|
showIcon |
||||
|
/> |
||||
|
</div> |
||||
|
<Table |
||||
|
loading={loading} |
||||
|
rowKey={record => record.key} |
||||
|
rowSelection={rowSelection} |
||||
|
dataSource={list} |
||||
|
columns={columns} |
||||
|
pagination={paginationProps} |
||||
|
onChange={this.handleTableChange} |
||||
|
/> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default StandardTable; |
||||
@ -0,0 +1,22 @@ |
|||||
|
@import "~antd/lib/style/themes/default.less"; |
||||
|
@import "../../utils/utils.less"; |
||||
|
|
||||
|
.standardTable { |
||||
|
:global { |
||||
|
.ant-table-pagination { |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tableAlert { |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.splitLine { |
||||
|
background: @border-color-split; |
||||
|
display: inline-block; |
||||
|
margin: 0 8px; |
||||
|
width: 1px; |
||||
|
height: 12px; |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue