Browse Source

Extract scaffold to independent git repo

pull/8/head
nikogu 9 years ago
parent
commit
d7ddf3ab8d
  1. 16
      .editorconfig
  2. 40
      .eslintrc
  3. 11
      .gitignore
  4. 26
      .roadhogrc
  5. 79
      .roadhogrc.mock.js
  6. 0
      mock/.gitkeep
  7. 205
      mock/api.js
  8. 184
      mock/chart.js
  9. 85
      mock/notices.js
  10. 74
      mock/profile.js
  11. 128
      mock/rule.js
  12. 45
      mock/utils.js
  13. 47
      package.json
  14. 13
      public/index.html
  15. BIN
      src/assets/yay.jpg
  16. 202
      src/common/nav.js
  17. 31
      src/components/ActivitiesItem/index.js
  18. 41
      src/components/ActivitiesItem/index.less
  19. 43
      src/components/AvatarList/index.js
  20. 29
      src/components/AvatarList/index.less
  21. 86
      src/components/Charts/Bar/index.js
  22. 34
      src/components/Charts/ChartCard/index.js
  23. 45
      src/components/Charts/ChartCard/index.less
  24. 12
      src/components/Charts/Field/index.js
  25. 17
      src/components/Charts/Field/index.less
  26. 195
      src/components/Charts/Gauge/index.js
  27. 29
      src/components/Charts/Icon/index.js
  28. 95
      src/components/Charts/MiniArea/index.js
  29. 78
      src/components/Charts/MiniBar/index.js
  30. 27
      src/components/Charts/MiniProgress/index.js
  31. 37
      src/components/Charts/MiniProgress/index.less
  32. 32
      src/components/Charts/NumberInfo/index.js
  33. 46
      src/components/Charts/NumberInfo/index.less
  34. 224
      src/components/Charts/Pie/index.js
  35. 70
      src/components/Charts/Pie/index.less
  36. 155
      src/components/Charts/Radar/index.js
  37. 38
      src/components/Charts/Radar/index.less
  38. 22
      src/components/Charts/Trend/index.js
  39. 49
      src/components/Charts/Trend/index.less
  40. 189
      src/components/Charts/WaterWave/index.js
  41. 25
      src/components/Charts/WaterWave/index.less
  42. 34
      src/components/Charts/index.js
  43. 9
      src/components/Charts/index.less
  44. 108
      src/components/Countdown/index.js
  45. 17
      src/components/DescriptionList/Description.js
  46. 18
      src/components/DescriptionList/DescriptionList.js
  47. 35
      src/components/DescriptionList/demo/basic.md
  48. 35
      src/components/DescriptionList/demo/vertical.md
  49. 5
      src/components/DescriptionList/index.js
  50. 50
      src/components/DescriptionList/index.less
  51. 29
      src/components/DescriptionList/index.md
  52. 6
      src/components/DescriptionList/responsive.js
  53. 46
      src/components/EditableLinkGroup/index.js
  54. 29
      src/components/EditableLinkGroup/index.less
  55. 21
      src/components/Exception/demo/403.md
  56. 14
      src/components/Exception/demo/404.md
  57. 14
      src/components/Exception/demo/500.md
  58. 26
      src/components/Exception/index.js
  59. 37
      src/components/Exception/index.less
  60. 19
      src/components/Exception/index.md
  61. 19
      src/components/Exception/typeConfig.js
  62. 44
      src/components/FooterToolbar/index.js
  63. 32
      src/components/FooterToolbar/index.less
  64. 9
      src/components/FooterToolbar/index.md
  65. 29
      src/components/GlobalFooter/demo/basic.md
  66. 18
      src/components/GlobalFooter/index.js
  67. 23
      src/components/GlobalFooter/index.less
  68. 16
      src/components/GlobalFooter/index.md
  69. 65
      src/components/HeaderSearch/index.js
  70. 27
      src/components/HeaderSearch/index.less
  71. 32
      src/components/MapChart/index.js
  72. 10
      src/components/MapChart/index.less
  73. 44
      src/components/NoticeIcon/NoticeList.js
  74. 89
      src/components/NoticeIcon/NoticeList.less
  75. 12
      src/components/NoticeIcon/demo/basic.md
  76. 41
      src/components/NoticeIcon/demo/popover.md
  77. 93
      src/components/NoticeIcon/index.js
  78. 36
      src/components/NoticeIcon/index.less
  79. 39
      src/components/NoticeIcon/index.md
  80. 71
      src/components/PageHeader/demo/image.md
  81. 26
      src/components/PageHeader/demo/simple.md
  82. 81
      src/components/PageHeader/demo/standard.md
  83. 67
      src/components/PageHeader/demo/structure.md
  84. 98
      src/components/PageHeader/index.js
  85. 95
      src/components/PageHeader/index.less
  86. 26
      src/components/PageHeader/index.md
  87. 10
      src/components/RadioText/index.js
  88. 12
      src/components/RadioText/index.less
  89. 64
      src/components/Result/demo/classic.md
  90. 39
      src/components/Result/demo/error.md
  91. 20
      src/components/Result/demo/structure.md
  92. 21
      src/components/Result/index.js
  93. 45
      src/components/Result/index.less
  94. 19
      src/components/Result/index.md
  95. 15
      src/components/SearchInput/index.js
  96. 45
      src/components/SearchInput/index.less
  97. 24
      src/components/StandardFormRow/index.js
  98. 68
      src/components/StandardFormRow/index.less
  99. 149
      src/components/StandardTable/index.js
  100. 22
      src/components/StandardTable/index.less

16
.editorconfig

@ -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

40
.eslintrc

@ -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
}
}
}

11
.gitignore

@ -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*

26
.roadhogrc

@ -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"
}
}

79
.roadhogrc.mock.js

@ -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
mock/.gitkeep

205
mock/api.js

@ -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,
};

184
mock/chart.js

@ -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,
};

85
mock/notices.js

@ -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: '待办',
}]);
},
};

74
mock/profile.js

@ -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,
};

128
mock/rule.js

@ -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,
};

45
mock/utils.js

@ -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,
};

47
package.json

@ -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"
}
}

13
public/index.html

@ -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>

BIN
src/assets/yay.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

202
src/common/nav.js

@ -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,
}];

31
src/components/ActivitiesItem/index.js

@ -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>
);

41
src/components/ActivitiesItem/index.less

@ -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;
}
}
}

43
src/components/AvatarList/index.js

@ -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;

29
src/components/AvatarList/index.less

@ -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;
}

86
src/components/Charts/Bar/index.js

@ -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;

34
src/components/Charts/ChartCard/index.js

@ -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;

45
src/components/Charts/ChartCard/index.less

@ -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;
}
}
}

12
src/components/Charts/Field/index.js

@ -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;

17
src/components/Charts/Field/index.less

@ -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;
}
}

195
src/components/Charts/Gauge/index.js

@ -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;

29
src/components/Charts/Icon/index.js

@ -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,
};

95
src/components/Charts/MiniArea/index.js

@ -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;

78
src/components/Charts/MiniBar/index.js

@ -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;

27
src/components/Charts/MiniProgress/index.js

@ -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;

37
src/components/Charts/MiniProgress/index.less

@ -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;
}
}
}

32
src/components/Charts/NumberInfo/index.js

@ -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>
);

46
src/components/Charts/NumberInfo/index.less

@ -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;
}
}
}

224
src/components/Charts/Pie/index.js

@ -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;

70
src/components/Charts/Pie/index.less

@ -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;
}
}
}

155
src/components/Charts/Radar/index.js

@ -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;

38
src/components/Charts/Radar/index.less

@ -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;
}
}
}

22
src/components/Charts/Trend/index.js

@ -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;

49
src/components/Charts/Trend/index.less

@ -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;
}
}

189
src/components/Charts/WaterWave/index.js

@ -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;

25
src/components/Charts/WaterWave/index.less

@ -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;
}
}
}

34
src/components/Charts/index.js

@ -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 => `&yen; ${numeral(val).format('0,0')}`;
export default {
IconUp,
IconDown,
yuan,
Bar,
Pie,
Gauge,
Radar,
MiniBar,
MiniArea,
MiniProgress,
ChartCard,
Trend,
Field,
NumberInfo,
WaterWave,
};

9
src/components/Charts/index.less

@ -0,0 +1,9 @@
.miniChart {
position: relative;
width: 100%;
& > div {
position: absolute;
bottom: -34px;
width: 100%;
}
}

108
src/components/Countdown/index.js

@ -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;

17
src/components/DescriptionList/Description.js

@ -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;

18
src/components/DescriptionList/DescriptionList.js

@ -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>
);
};

35
src/components/DescriptionList/demo/basic.md

@ -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);
````

35
src/components/DescriptionList/demo/vertical.md

@ -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);
````

5
src/components/DescriptionList/index.js

@ -0,0 +1,5 @@
import DescriptionList from './DescriptionList';
import Description from './Description';
DescriptionList.Description = Description;
export default DescriptionList;

50
src/components/DescriptionList/index.less

@ -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;
}
}
}

29
src/components/DescriptionList/index.md

@ -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 | - |

6
src/components/DescriptionList/responsive.js

@ -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 },
};

46
src/components/EditableLinkGroup/index.js

@ -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;

29
src/components/EditableLinkGroup/index.less

@ -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;
}
}
}

21
src/components/Exception/demo/403.md

@ -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);
````

14
src/components/Exception/demo/404.md

@ -0,0 +1,14 @@
---
order: 0
title: 404
---
404 页面。
````jsx
import { Exception } from 'ant-design-pro';
ReactDOM.render(
<Exception type="404" />
, mountNode);
````

14
src/components/Exception/demo/500.md

@ -0,0 +1,14 @@
---
order: 1
title: 500
---
500 页面。
````jsx
import { Exception } from 'ant-design-pro';
ReactDOM.render(
<Exception type="500" />
, mountNode);
````

26
src/components/Exception/index.js

@ -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>
);
};

37
src/components/Exception/index.less

@ -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;
}
}
}
}

19
src/components/Exception/index.md

@ -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 | - |

19
src/components/Exception/typeConfig.js

@ -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;

44
src/components/FooterToolbar/index.js

@ -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>
);
}
}

32
src/components/FooterToolbar/index.less

@ -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;
}
}

9
src/components/FooterToolbar/index.md

@ -0,0 +1,9 @@
---
category: Components
type: General
title: FooterToolbar
subtitle: 底部固定工具栏
cols: 1
---
## API

29
src/components/GlobalFooter/demo/basic.md

@ -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);
````

18
src/components/GlobalFooter/index.js

@ -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>
);
};

23
src/components/GlobalFooter/index.less

@ -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;
}
}

16
src/components/GlobalFooter/index.md

@ -0,0 +1,16 @@
---
category: Components
type: General
title: GlobalFooter
subtitle: 全局页脚
cols: 1
---
页脚属于全局导航的一部分,作为对顶部导航的补充,通过传递数据控制展示内容。
## API
| 参数 | 说明 | 类型 | 默认值 |
|----------|------------------------------------------|-------------|-------|
| links | 链接数据 | array<{ title: ReactNode, href: string, blankTarget?: boolean }> | - |
| copyright | 版权信息 | ReactNode | - |

65
src/components/HeaderSearch/index.js

@ -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>
);
}
}

27
src/components/HeaderSearch/index.less

@ -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;
}
}

32
src/components/MapChart/index.js

@ -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;

10
src/components/MapChart/index.less

@ -0,0 +1,10 @@
.mapChart {
background-color: #fff;
position: relative;
.canvas {
width: 100%;
& > img {
width: 100%;
}
}
}

44
src/components/NoticeIcon/NoticeList.js

@ -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>
);
}

89
src/components/NoticeIcon/NoticeList.less

@ -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;
}
}

12
src/components/NoticeIcon/demo/basic.md

@ -0,0 +1,12 @@
---
order: 1
title: 通知图标
---
通常用在全局导航上。
````jsx
import { NoticeIcon } from 'ant-design-pro';
ReactDOM.render(<NoticeIcon count={5} />, mountNode);
````

41
src/components/NoticeIcon/demo/popover.md

@ -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);
````

93
src/components/NoticeIcon/index.js

@ -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>
);
}
}

36
src/components/NoticeIcon/index.less

@ -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;
}
}
}

39
src/components/NoticeIcon/index.md

@ -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 | -

71
src/components/PageHeader/demo/image.md

@ -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>

26
src/components/PageHeader/demo/simple.md

@ -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);
````

81
src/components/PageHeader/demo/standard.md

@ -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);
````

67
src/components/PageHeader/demo/structure.md

@ -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>

98
src/components/PageHeader/index.js

@ -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>
);
}
}

95
src/components/PageHeader/index.less

@ -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;
}
}
}

26
src/components/PageHeader/index.md

@ -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` 组件会自动获取。

10
src/components/RadioText/index.js

@ -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>);

12
src/components/RadioText/index.less

@ -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;
}
}
}

64
src/components/Result/demo/classic.md

@ -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);
````

39
src/components/Result/demo/error.md

@ -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);
````

20
src/components/Result/demo/structure.md

@ -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);
````

21
src/components/Result/index.js

@ -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>
);
};

45
src/components/Result/index.less

@ -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;
}
}

19
src/components/Result/index.md

@ -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 | - |

15
src/components/SearchInput/index.js

@ -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>
);

45
src/components/SearchInput/index.less

@ -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;
}
}
}
}

24
src/components/StandardFormRow/index.js

@ -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>
);
};

68
src/components/StandardFormRow/index.less

@ -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;
}
}
}

149
src/components/StandardTable/index.js

@ -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> &nbsp;&nbsp;
服务调用总计 <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;

22
src/components/StandardTable/index.less

@ -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…
Cancel
Save