Compare commits

...
100 Commits
Author SHA1 Message Date
zuiidea f5f013ff23 Merge branch 'develop' 2017-09-11 09:35:39 +08:00
zuiidea 8cff3af897 Merge branch 'master' of https://github.com/zuiidea/antd-admin 2017-09-11 09:35:32 +08:00
zuiidea 322d5a6d0e merge #576 2017-09-11 09:35:09 +08:00
zuiidea 2ee26f51a7 Merge branch 'yangbin1994-bug575' into develop 2017-09-11 09:27:00 +08:00
zuiidea e670ed2f96 Merge branch 'bug575' of https://github.com/yangbin1994/antd-admin into yangbin1994-bug575 2017-09-11 09:26:36 +08:00
杨斌 601a03b5b4 fix bug#575 2017-09-10 02:26:27 +08:00
zuiidea fa4385055b Update README.md 2017-09-09 14:54:18 +08:00
zuiidea 01f4de55df Merge pull request #571 from zuiidea/develop
Develop
2017-09-09 14:52:50 +08:00
zuiidea dedd34e8e8 4.3.5 2017-09-09 14:50:47 +08:00
zuiidea d1b373201e fix some grammar and details 2017-09-09 14:20:51 +08:00
zuiidea dbcc90e7ad 出去吃烧烤,回来接着看 2017-09-08 16:41:05 +08:00
zuiidea 87fce74d5b Update app.js
fix mobile Header menu
2017-09-08 15:45:38 +08:00
zuiidea 5c51679ad7 Merge branch 'yangbin1994-dva2.0' into develop 2017-09-08 14:56:35 +08:00
zuiidea 6d17b5b876 Merge branch 'yangbin1994-dva2.0' 2017-09-08 14:51:51 +08:00
杨斌 1288853112 解决冲突 2017-09-08 14:19:40 +08:00
杨斌 d3e7e17789 query-string 2017-09-08 13:27:07 +08:00
杨斌 7bf031699a add LoadingComponent 2017-09-06 20:36:14 +08:00
杨斌 a6eb693636 dva@2.0 2017-09-06 11:13:26 +08:00
zuiidea 39c377e0e2 Merge pull request #552 from zuiidea/develop
add entry.ejs and add lte IE10 polyfill
2017-09-06 09:56:25 +08:00
zuiidea 67b90c4df0 add entry.ejs and add lte IE10 polyfill 2017-09-06 09:55:41 +08:00
zuiidea 0e2727f9c8 Merge pull request #542 from zuiidea/develop
4.3.4
2017-09-04 11:48:32 +08:00
zuiidea 0cf02e7324 4.3.4 2017-09-04 11:47:24 +08:00
zuiidea 68c9f2b65f Merge pull request #540 from zuiidea/develop
fix login success redirect;fix css load delay
2017-09-04 11:03:13 +08:00
zuiidea 587b1f6b9d fix login success redirect;fix css load delay 2017-09-04 11:01:31 +08:00
zuiidea d94580a253 Merge pull request #302 from jackple/patch-1
Update app.js
2017-09-04 09:58:42 +08:00
zuiidea 531e3c0fcc Merge pull request #532 from p2227/patch-1
fixes #529
2017-09-04 09:57:39 +08:00
zuiidea d68de7a30c Merge pull request #378 from chenkang084/remote
fixed:when click breadcrumb back to other page,the left menu displaye…
2017-09-04 09:57:24 +08:00
zuiidea 45cb70f006 Merge pull request #518 from quanpower/patch-1
Update roadhogrc.js
2017-09-04 09:56:18 +08:00
kuang af85384f11 fixes #529 2017-08-28 23:20:38 -05:00
William Zhang 208539134f Update roadhogrc.js
proxy加引号,我这报错了,去掉了正常运行
2017-08-19 18:06:16 +08:00
zuiidea a3f41e3d64 Merge pull request #496 from zuiidea/develop
fix weather
2017-08-13 17:20:42 +08:00
zuiidea 3855c402c9 fix weather 2017-08-13 17:18:46 +08:00
zuiidea 6a58d8434b Merge pull request #493 from zuiidea/develop
fix lint
2017-08-10 19:11:39 +08:00
zuiidea d26a92ce79 fix lint 2017-08-10 19:11:10 +08:00
zuiidea 0d20717c3c Merge pull request #488 from zuiidea/develop
update
2017-08-09 09:46:45 +08:00
zuiidea a0d81dcda9 update 2017-08-08 19:46:38 +08:00
zuiidea 4490af8553 Merge pull request #486 from zuiidea/develop
Develop
2017-08-07 14:43:08 +08:00
zuiidea 75cc86152d 4.3.2 2017-08-07 14:28:32 +08:00
zuiidea 3d9f263330 add Alias 2017-08-07 11:46:42 +08:00
zuiidea 70b5d05f1a format 2017-08-03 14:01:49 +08:00
zuiidea 4da23b9133 version.js 2017-08-03 13:50:43 +08:00
zuiidea da1c52e82b npm run build:new 2017-07-31 17:28:03 +08:00
zuiidea 6ae39bfd55 Merge pull request #453 from phproot/master
fix README.md
2017-07-31 11:51:42 +08:00
Code d86f0ebd78 fix README.md 2017-07-31 11:38:39 +08:00
zuiidea ecbd8bf51e fix little bug 2017-07-31 10:49:15 +08:00
zuiidea 0c9fabe2b4 Merge pull request #452 from zuiidea/develop
fix build:dll
2017-07-30 23:51:07 +08:00
zuiidea 0f27528d2f fix build:dll 2017-07-30 23:50:12 +08:00
zuiidea a36d44d402 Merge pull request #450 from zuiidea/develop
Develop
2017-07-30 14:54:15 +08:00
zuiidea 37deddc911 update 4.3.0 2017-07-30 14:51:02 +08:00
zuiidea 52ee780e40 fix build error 2017-07-30 14:25:04 +08:00
zuiidea 057853cfde fix ejs-loader 2017-07-29 22:30:39 +08:00
zuiidea c0222c3957 add version build 2017-07-28 19:26:05 +08:00
zuiidea 00d8fc5ea5 add build:dll 2017-07-27 20:02:20 +08:00
zuiidea 1e08d7adaa optimized code 2017-07-27 19:13:31 +08:00
zuiidea b2b0aa872f Merge pull request #431 from zuiidea/develop
pagenation style
2017-07-21 19:03:48 +08:00
zuiidea a2b4ee4047 Merge branch 'master' into develop 2017-07-21 19:03:42 +08:00
zuiidea c6b477c993 pagenation style 2017-07-21 19:02:11 +08:00
zuiidea 548d7a8ddb Merge pull request #427 from zuiidea/develop
fix webpack.resoleve.alias module import/no-unresolved
2017-07-19 10:33:14 +08:00
zuiidea 755c1250e5 fix webpack.resoleve.alias module import/no-unresolved 2017-07-19 10:32:39 +08:00
zuiidea c5ebb91980 Merge pull request #413 from afc163/patch-1
No need customzied card style
2017-07-17 10:50:26 +08:00
zuiidea ae12492887 Merge pull request #416 from zuiidea/develop
update package.json
2017-07-17 10:48:01 +08:00
zuiidea 28a5863701 update package.json 2017-07-17 10:47:31 +08:00
偏右 bf65aa5478 Don't need customzied card style
https://github.com/ant-design/ant-design/issues/6833
2017-07-14 13:49:28 +08:00
zuiidea acd3fe8337 Merge pull request #407 from zuiidea/develop
fix permission.visit is null
2017-07-12 19:41:58 +08:00
zuiidea 3ad5a6cfb6 fix permission.visit is null 2017-07-12 19:41:32 +08:00
zuiidea c278c8f6f8 Merge pull request #393 from zuiidea/develop
666
2017-07-10 10:08:26 +08:00
zuiidea 71c75e15ff 666 2017-07-10 10:07:22 +08:00
zuiidea ec8db0e0fb Merge pull request #389 from zuiidea/develop
Develop
2017-07-06 19:16:04 +08:00
zuiidea 2a3587bae5 fix user role and update package.json 2017-07-06 19:15:32 +08:00
zuiidea a33772cbbc Merge branch 'master' into develop 2017-07-06 19:08:06 +08:00
zuiidea 89c58b2f98 Merge pull request #385 from zuiidea/develop
Develop
2017-07-05 19:46:48 +08:00
zuiidea b31bd32ca2 roleType 2017-07-05 19:45:57 +08:00
zuiidea f113fbb238 revise menu property : router => route 2017-07-05 19:29:14 +08:00
rengised a320c77ecb Merge branch 'master' into remote 2017-07-05 15:08:43 +08:00
zuiidea ce1d957212 Merge pull request #382 from zuiidea/develop
Develop
2017-07-05 13:56:00 +08:00
zuiidea 7f40ac6a60 Page Permission 2017-07-05 13:54:57 +08:00
zuiidea 3a01626df9 Permissions Simple Version 2017-07-04 23:41:13 +08:00
zuiidea 6df97916fa webpack alias 2017-07-04 22:44:48 +08:00
chenkang1 7f091d7642 fixed:the error request should be capured in catch method 2017-07-03 14:06:28 +08:00
chenkang1 d8f222f312 fixed:when click breadcrumb back to other page,the left menu displayed the last select item 2017-07-01 16:08:03 +08:00
zuiidea 3378510166 Merge pull request #363 from kuanger/develop
fix logout menu block by show more button
2017-06-19 16:14:49 +08:00
hausir edf1158351 fix logout menu block by show more button 2017-06-19 13:51:03 +08:00
zuiidea e4bd4e9aa0 Merge pull request #355 from blackboyhc/master
Update app.js
2017-06-19 10:18:58 +08:00
zuiidea 5904eae0c9 Merge pull request #349 from zuiidea/develop
Develop
2017-06-19 10:18:42 +08:00
zuiidea 7f26153472 Merge pull request #358 from wuyao1994/dev
showModal return bug
2017-06-19 10:16:59 +08:00
elviswu 4b9e142aa8 showModal return bug 2017-06-16 16:09:22 +08:00
blackboyhc 2679361559 Update app.js
如果在config.js中添加了openPages,比如openPages: ['/login', '/home'],但是进入/home页面仍然要先登录,所以在app model中如果query不成功需要判断当前页面是否被设置成openPages,再决定是否跳转到/login
2017-06-15 17:31:24 +08:00
zuiidea 2d246655e0 add example 2017-06-13 20:36:47 +08:00
zuiidea 04b147d33f proxy example 2017-06-13 20:30:24 +08:00
zuiidea 81801983a6 fix request 2017-06-13 20:26:38 +08:00
zuiidea e484b89518 Merge pull request #340 from zuiidea/develop
add eslint-loader
2017-06-09 10:01:45 +08:00
zuiidea bfff28411d add eslint-loader 2017-06-09 10:00:59 +08:00
zuiidea ae97c9d18a Merge pull request #335 from zuiidea/develop
Develop
2017-06-08 11:22:54 +08:00
zuiidea a063d8be39 fix production className to hash 2017-06-08 11:22:10 +08:00
zuiidea 79eab09941 fix production className to hash 2017-06-08 10:52:05 +08:00
zuiidea 2e3cb5970d pre eslint 2017-06-07 20:16:15 +08:00
zuiidea 460a8c906b Merge pull request #334 from zuiidea/develop
Develop
2017-06-07 19:47:37 +08:00
zuiidea 05a86e2f56 Filename and Classname To Hash 2017-06-07 19:46:56 +08:00
zuiidea 0295da6326 add Posts Page 2017-06-07 14:24:26 +08:00
jackple f6021cbaa4 Update app.js
先判断menu长度,解决当远程加载menu时刷新页面,当前位置不会选中
2017-05-21 20:32:50 +08:00
113 changed files with 2772 additions and 2076 deletions
+1
View File
@@ -1 +1,2 @@
src/**/*-test.js
src/public
+19 -1
View File
@@ -6,6 +6,7 @@
"comma-dangle": [2, "always-multiline"],
"max-len": 0,
"react/jsx-first-prop-new-line": 0,
"react/jsx-filename-extension": 0,
"space-before-function-paren": [2, "always"],
"no-unused-expressions": [0, {
"allowShortCircuit": true,
@@ -18,10 +19,24 @@
"no-param-reassign": 0,
"no-restricted-syntax": 0,
"no-eval": 0,
"no-continue": 0,
"react/jsx-no-bind": 0,
"no-unused-vars": [2, { "ignoreRestSiblings": true }],
"no-underscore-dangle": 0,
"global-require": 0,
"import/no-unresolved": 0,
"import/extensions": 0,
"jsx-a11y/href-no-hash": 0,
"react/no-array-index-key": 0,
"react/require-default-props": 0,
"react/forbid-prop-types": 0,
"react/no-string-refs": 0,
"react/no-find-dom-node": 0,
"react/prefer-stateless-function": 0,
"import/no-extraneous-dependencies": 0,
"import/prefer-default-export": 0,
"react/no-danger": 0,
"jsx-a11y/no-static-element-interactions": 0,
},
"parser": "babel-eslint",
"parserOptions": {
@@ -31,5 +46,8 @@
"jsx": true,
"experimentalObjectRestSpread": true
}
}
},
"settings": {
"import/resolver": "node"
}
}
+4 -4
View File
@@ -1,4 +1,4 @@
dist
node_modules
yarn.lock
npm-debug.log
dist
node_modules
yarn.lock
npm-debug.log
+44 -14
View File
@@ -1,27 +1,57 @@
const path = require('path')
const { version } = require('./package.json')
const svgSpriteDirs = [
path.resolve(__dirname, 'src/svg/'),
require.resolve('antd').replace(/index\.js$/, ''),
require.resolve('antd').replace(/index\.js$/, '')
]
export default {
entry: 'src/index.js',
svgSpriteLoaderDirs: svgSpriteDirs,
"theme": "./theme.config.js",
"env": {
"development": {
"extraBabelPlugins": [
"dva-hmr",
"transform-runtime",
["import", { "libraryName": "antd", "style": true }]
theme: "./theme.config.js",
publicPath: `/${version}/`,
outputPath: `./dist/${version}`,
// 接口代理示例
proxy: {
"/api/v1/weather": {
"target": "https://api.seniverse.com/",
"changeOrigin": true,
"pathRewrite": { "^/api/v1/weather": "/v3/weather" }
},
// "/api/v2": {
// "target": "http://192.168.0.110",
// "changeOrigin": true,
// "pathRewrite": { "^/api/v2" : "/api/v2" }
// }
},
env: {
development: {
extraBabelPlugins: [
"dva-hmr",
"transform-runtime",
[
"import", {
"libraryName": "antd",
"style": true
}
]
},
"production": {
"extraBabelPlugins": [
"transform-runtime",
["import", { "libraryName": "antd", "style": true}]
]
},
production: {
extraBabelPlugins: [
"transform-runtime",
[
"import", {
"libraryName": "antd",
"style": true
}
]
}
]
}
},
dllPlugin: {
exclude: ["babel-runtime", "roadhog", "cross-env"],
include: ["dva/router", "dva/saga", "dva/fetch"]
}
}
+48 -21
View File
@@ -1,15 +1,15 @@
# Antd Admin
[![React Native](https://img.shields.io/badge/react-^15.4.4-brightgreen.svg?style=flat-square)](https://github.com/facebook/react)
[![Ant Design](https://img.shields.io/badge/ant--design-^2.10.0-yellowgreen.svg?style=flat-square)](https://github.com/ant-design/ant-design)
[![dva](https://img.shields.io/badge/dva-^1.2.0-orange.svg?style=flat-square)](https://github.com/dvajs/dva)
[![React](https://img.shields.io/badge/react-^15.6.1-brightgreen.svg?style=flat-square)](https://github.com/facebook/react)
[![Ant Design](https://img.shields.io/badge/ant--design-^2.11.2-yellowgreen.svg?style=flat-square)](https://github.com/ant-design/ant-design)
[![dva](https://img.shields.io/badge/dva-^2.0.1-orange.svg?style=flat-square)](https://github.com/dvajs/dva)
[![GitHub issues](https://img.shields.io/github/issues/zuiidea/antd-admin.svg?style=flat-square)](https://github.com/zuiidea/antd-admin)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/zuiidea/antd-admin/pulls)
[![MIT](https://img.shields.io/dub/l/vibe-d.svg?style=flat-square)](http://opensource.org/licenses/MIT)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com)
演示地址 <http://zuiidea.github.io/antd-admin> \| [备用地址](http://47.92.30.98:8000)
演示地址 <http://antd-admin.zuiidea.com>
## 特性
@@ -21,28 +21,32 @@
## 更新日志
### 4.2.2
`2017-06-01`
### 4.3.5
-   新增用户管理批量删除。[#320](https://github.com/zuiidea/antd-admin/issues/320)
`2017-09-04`
### 4.2.1
-   更新[dva](https://github.com/dvajs/dva)至`2.0.1`,感谢[@yangbin1994](https://github.com/yangbin1994)的[PR](https://github.com/zuiidea/antd-admin/pull/567)。
`2017-05-12`
### 4.3.4
-   新增IconFont扩展方案,本地使用方案。[#270](https://github.com/zuiidea/antd-admin/issues/270) [Live](http://47.92.30.98:666/UIElement/iconfont)
-   新增常见场景使用loading。 [dva-loading](https://github.com/dvajs/dva-loading)
`2017-09-04`
### 4.2
-   更新[roadhog](https://github.com/sorrycc/roadhog)至`1.2.1`,修复闪屏问题。
-   修复build css未压缩[#529](https://github.com/zuiidea/antd-admin/issues/529),classname未Hash[#477](https://github.com/zuiidea/antd-admin/issues/477)。
-   更新登录成功使用router跳转[#479](https://github.com/zuiidea/antd-admin/issues/479)。
`2017-04-28`
### 4.3.0
-   修改user相关API使用`Restful`风格。
-   增加user页面多条件查询。[#266](https://github.com/zuiidea/antd-admin/issues/226)
-   修复菜单默认高亮。[#201](https://github.com/zuiidea/antd-admin/issues/201)
`2017-07-30`
[More Change Log](https://github.com/zuiidea/antd-admin/wiki/Change-Log)
-   更新[roadhog](https://github.com/sorrycc/roadhog)至`1.0.0-beta.7`,得益于`webpack@3.4`,编译速度不减。
-   基于使用roadhog的dll插件,开发时编译耗时减半。
-   新增打包时按项目版本号生成文件目录。[#449](https://github.com/zuiidea/antd-admin/issues/449)
-   更新`eslint`,并适当修改了`.eslintrc`。
[More Change Log](https://github.com/zuiidea/antd-admin/wiki/Change-Log)
## 开发构建
@@ -51,6 +55,7 @@
```bash
├── /dist/ # 项目输出目录
├── /src/ # 项目源码目录
│ ├── /public/ # 公共文件,编译时copy至dist目录
│ ├── /components/ # UI组件及UI相关方法
│ │ ├── skin.less # 全局样式
│ │ └── vars.less # 全局样式变量
@@ -83,25 +88,36 @@
克隆项目文件:
git clone https://github.com/zuiidea/antd-admin.git
```bash
git clone https://github.com/zuiidea/antd-admin.git
```
进入目录安装依赖:
npm i 或者 yarn install
```bash
#开始前请确保没有安装roadhog、webpack到NPM全局目录
npm i 或者 yarn install
```
开发:
```bash
npm run build:dll #第一次npm run dev时需运行此命令,使开发时编译更快
npm run dev
打开 http://localhost:8000
```
构建:
[详情](https://github.com/zuiidea/antd-admin/issues/269)
```bash
npm run build
将会生成dist目录
将会打包至dist/{version}目录 #package.json里version字段
npm run build:new
将会打包至dist/{version增加1}目录 #package.json里version字段
```
代码检测:
@@ -110,7 +126,18 @@ npm run build
npm run lint
```
项目部署 [#269](https://github.com/zuiidea/antd-admin/issues/269)
## FAQ
- 项目打包后如何部署? [#269](https://github.com/zuiidea/antd-admin/issues/269)
- 如何做权限管理? [#384](https://github.com/zuiidea/antd-admin/issues/384)
- 如何使用mock.js模拟接口,怎么使用线上接口? [#348](https://github.com/zuiidea/antd-admin/issues/348)
- 如何使用Iconfont,如何使用本地的svg图标? [#270](https://github.com/zuiidea/antd-admin/issues/270)
- 怎么按版本打包,上线时不影响正在访问的用户? [#449](https://github.com/zuiidea/antd-admin/issues/449)
- windows处理CRLF?[参考](http://blog.csdn.net/lysc_forever/article/details/42835203)
```bash
git config --global core.autocrlf false
```
## 参考
+43 -29
View File
@@ -1,50 +1,64 @@
{
"private": true,
"name": "antd-admin",
"version": "4.3.6",
"dependencies": {
"antd": "^2.10.0",
"antd": "^2.11.2",
"axios": "^0.16.2",
"babel-polyfill": "^6.23.0",
"classnames": "^2.2.5",
"cross-env": "^5.0.5",
"d3-shape": "^1.2.0",
"draftjs-to-markdown": "^0.4.0",
"dva": "^1.2.0",
"dva": "^2.0.1",
"dva-loading": "^0.2.0",
"ejs-loader": "^0.3.0",
"history": "^4.7.2",
"js-beautify": "^1.6.14",
"jsonp": "^0.2.1",
"less-vars-to-js": "^1.1.2",
"lodash": "^4.17.4",
"mockjs": "^1.0.1-beta3",
"nprogress": "^0.2.0",
"qs": "^6.2.0",
"react": "^15.4.4",
"react-countup": "1.3.0",
"react-dom": "^15.4.1",
"react-draft-wysiwyg": "^1.8.1",
"react-helmet": "^5.0.0",
"recharts": "^0.19.0"
"path-to-regexp": "^1.7.0",
"prop-types": "^15.5.10",
"qs": "^6.5.0",
"query-string": "^5.0.0",
"rc-tween-one": "^1.3.1",
"react": "^15.6.1",
"react-countup": "^2.1.1",
"react-dom": "^15.6.1",
"react-draft-wysiwyg": "^1.10.0",
"react-helmet": "^5.1.3",
"recharts": "^0.22.4"
},
"devDependencies": {
"axios": "^0.15.3",
"babel-eslint": "^6.1.2",
"babel-eslint": "^7.2.3",
"babel-plugin-dev-expression": "^0.2.1",
"babel-plugin-dva-hmr": "^0.3.2",
"babel-plugin-import": "^1.1.1",
"babel-plugin-transform-runtime": "^6.9.0",
"babel-polyfill": "^6.23.0",
"babel-runtime": "^6.9.2",
"copy-webpack-plugin": "^4.0.1",
"draftjs-to-html": "^0.7.0",
"eslint": "^3.17.1",
"eslint-config-airbnb": "^9.0.1",
"eslint-plugin-import": "^1.16.0",
"eslint-plugin-jsx-a11y": "^1.4.2",
"eslint-plugin-react": "^5.1.1",
"jsonp": "^0.2.1",
"less-vars-to-js": "^1.1.2",
"mockjs": "^1.0.1-beta3",
"path-to-regexp": "^1.7.0",
"rc-tween-one": "^1.0.0",
"dva-model-extend": "^0.1.1",
"eslint": "^4.1.1",
"eslint-config-airbnb": "^15.0.2",
"eslint-import-resolver-node": "^0.3.1",
"eslint-loader": "^1.9.0",
"eslint-plugin-import": "^2.6.1",
"eslint-plugin-jsx-a11y": "^6.0.2",
"eslint-plugin-react": "^7.1.0",
"html-webpack-plugin": "^2.29.0",
"redbox-react": "^1.2.10",
"roadhog": "0.6.0-beta.6"
"roadhog": "^1.2.2"
},
"pre-commit": [
"lint"
],
"pre-commit": ["lint"],
"scripts": {
"dev": "set BROWSER=none&&set HOST=0.0.0.0&&roadhog server",
"dev": "cross-env BROWSER=none HOST=0.0.0.0 roadhog server",
"lint": "eslint --fix --ext .js src",
"build": "roadhog build"
"build": "roadhog build",
"build:dll": "roadhog buildDll",
"build:new": "node version && roadhog build"
}
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Table } from 'antd'
import { request } from '../../utils'
import { request } from 'utils'
import lodash from 'lodash'
import './DataTable.less'
+1 -1
View File
@@ -1,7 +1,7 @@
import React from 'react'
import { Editor } from 'react-draft-wysiwyg'
import styles from './Editor.less'
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'
import styles from './Editor.less'
const DraftEditor = (props) => {
+2 -2
View File
@@ -11,8 +11,8 @@ const FilterItem = ({
<div className={styles.filterItem}>
{labelArray.length > 0
? <div className={styles.labelWrap}>
{labelArray.map((item, index) => <span className="labelText" key={index}>{item}</span>)}
</div>
{labelArray.map((item, index) => <span className="labelText" key={index}>{item}</span>)}
</div>
: ''}
<div className={styles.item}>
{children}
+15 -10
View File
@@ -1,17 +1,17 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Breadcrumb, Icon } from 'antd'
import { Link } from 'dva/router'
import styles from './Bread.less'
import { Link } from 'react-router-dom'
import pathToRegexp from 'path-to-regexp'
import { queryArray } from '../../utils'
import { queryArray } from 'utils'
import styles from './Bread.less'
const Bread = ({ menu }) => {
const Bread = ({ menu, location }) => {
// 匹配当前路由
let pathArray = []
let current
for (let index in menu) {
if (menu[index].router && pathToRegexp(menu[index].router).exec(location.pathname)) {
if (menu[index].route && pathToRegexp(menu[index].route).exec(location.pathname)) {
current = menu[index]
break
}
@@ -25,7 +25,11 @@ const Bread = ({ menu }) => {
}
if (!current) {
pathArray.push(menu[0])
pathArray.push(menu[0] || {
id: 1,
icon: 'laptop',
name: 'Dashboard',
})
pathArray.push({
id: 404,
name: 'Not Found',
@@ -38,14 +42,14 @@ const Bread = ({ menu }) => {
const breads = pathArray.map((item, key) => {
const content = (
<span>{item.icon
? <Icon type={item.icon} style={{ marginRight: 4 }} />
: ''}{item.name}</span>
? <Icon type={item.icon} style={{ marginRight: 4 }} />
: ''}{item.name}</span>
)
return (
<Breadcrumb.Item key={key}>
{((pathArray.length - 1) !== key)
? <Link to={item.router}>
{content}
? <Link to={item.route || '#'}>
{content}
</Link>
: content}
</Breadcrumb.Item>
@@ -63,6 +67,7 @@ const Bread = ({ menu }) => {
Bread.propTypes = {
menu: PropTypes.array,
location: PropTypes.object,
}
export default Bread
+8 -8
View File
@@ -1,8 +1,8 @@
@import "../../themes/vars.less";
.bread {
height: 64px;
line-height: 64px;
padding: 0 24px;
margin-bottom: -24px;
}
@import "~themes/vars";
.bread {
height: 64px;
line-height: 64px;
padding: 0 24px;
margin-bottom: -24px;
}
+3 -3
View File
@@ -1,9 +1,9 @@
import React from 'react'
import { config } from 'utils'
import styles from './Footer.less'
import { config } from '../../utils'
const Footer = () => <div className={styles.footer}>
const Footer = () => (<div className={styles.footer}>
{config.footerText}
</div>
</div>)
export default Footer
+12 -12
View File
@@ -1,12 +1,12 @@
@import "../../themes/vars.less";
.footer {
height: 48px;
line-height: 48px;
text-align: center;
font-size: 12px;
color: #999;
background: #fff;
box-shadow: @shadow-2;
width: 100%;
}
@import "~themes/vars";
.footer {
height: 48px;
line-height: 48px;
text-align: center;
font-size: 12px;
color: #999;
background: #fff;
box-shadow: @shadow-2;
width: 100%;
}
+14 -6
View File
@@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Menu, Icon, Popover } from 'antd'
import classnames from 'classnames'
import styles from './Header.less'
import Menus from './Menu'
@@ -26,18 +27,25 @@ const Header = ({ user, logout, switchSider, siderFold, isNavbar, menuPopoverVis
<Icon type="bars" />
</div>
</Popover>
: <div className={styles.button} onClick={switchSider}>
<Icon type={siderFold ? 'menu-unfold' : 'menu-fold'} />
: <div
className={styles.button}
onClick={switchSider}
>
<Icon type={classnames({ 'menu-unfold': siderFold, 'menu-fold': !siderFold })} />
</div>}
<div className={styles.rightWarpper}>
<div className={styles.button}>
<Icon type="mail" />
</div>
<Menu mode="horizontal" onClick={handleClickMenu}>
<SubMenu style={{
float: 'right',
}} title={< span > <Icon type="user" />
{user.username} < /span>}
<SubMenu
style={{
float: 'right',
}}
title={<span>
<Icon type="user" />
{user.username}
</span>}
>
<Menu.Item key="logout">
Sign out
+59 -58
View File
@@ -1,58 +1,59 @@
@import "../../themes/vars.less";
.header {
:global .ant-menu-horizontal {
& > .ant-menu-submenu {
float: right;
}
border: none;
display: inline-block;
}
box-shadow: @shadow-2;
position: relative;
display: flex;
justify-content: space-between;
height: 47px;
background-color: #fff;
.rightWarpper {
display: flex;
padding-right: 16px;
}
.button {
width: 47px;
height: 47px;
line-height: 47px;
text-align: center;
font-size: 18px;
cursor: pointer;
transition: @transition-ease-in;
&:hover {
color: @primary-color;
background-color: fade(@primary-color, 15%);
}
}
}
.popovermenu {
width: 280px;
margin-left: 6px;
:global .ant-popover-inner-content {
padding: 0;
.ant-menu-inline .ant-menu-item,
.ant-menu-vertical .ant-menu-item {
border-right: 0;
height: 48px;
line-height: 48px;
}
.ant-menu-inline .ant-menu-item-selected,
.ant-menu-inline .ant-menu-selected {
border-right: 0;
}
}
}
@import "~themes/vars";
.header {
:global .ant-menu-horizontal {
& > .ant-menu-submenu {
float: right;
}
border: none;
display: inline-block;
}
box-shadow: @shadow-2;
position: relative;
display: flex;
justify-content: space-between;
height: 47px;
background-color: #fff;
z-index: 999;
.rightWarpper {
display: flex;
padding-right: 16px;
}
.button {
width: 47px;
height: 47px;
line-height: 47px;
text-align: center;
font-size: 18px;
cursor: pointer;
transition: @transition-ease-in;
&:hover {
color: @primary-color;
background-color: fade(@primary-color, 15%);
}
}
}
.popovermenu {
width: 280px;
margin-left: 6px;
:global .ant-popover-inner-content {
padding: 0;
.ant-menu-inline .ant-menu-item,
.ant-menu-vertical .ant-menu-item {
border-right: 0;
height: 48px;
line-height: 48px;
}
.ant-menu-inline .ant-menu-item-selected,
.ant-menu-inline .ant-menu-selected {
border-right: 0;
}
}
}
+292 -292
View File
@@ -1,292 +1,292 @@
@import "../../themes/vars.less";
.layout {
position: relative;
height: 100vh;
&.withnavbar {
.main {
margin-left: 0;
}
}
&.fold {
.sider {
width: 42px;
.logo {
img {
width: 28px;
margin: 6px 7px;
}
}
:global {
.ant-menu-root {
width: 100%;
overflow: visible;
& > .ant-menu-item {
padding: 0;
text-align: center;
.anticon {
font-size: 14px;
margin-right: 0;
}
}
& > .ant-menu-submenu {
& > .ant-menu-submenu-title {
padding: 0;
text-align: center;
.anticon {
font-size: 14px;
margin-right: 0;
}
&::after {
display: none;
}
}
}
}
}
}
.main {
margin-left: 42px;
}
}
.sider {
width: 224px;
background: #3e3e3e;
position: absolute;
overflow: visible;
padding-bottom: 24px;
height: 100vh;
transition: @transition-ease-out;
box-shadow: @shadow-1;
color: #999;
&.light {
background: #fff;
.switchtheme {
background: #fff;
border-top: solid 1px #f8f8f8;
}
}
.logo {
text-align: center;
height: 40px;
line-height: 40px;
cursor: pointer;
margin: 28px 0;
transition: @transition-ease-out;
overflow: hidden;
img {
width: 40px;
margin-right: 8px;
transition: @transition-ease-out;
}
span {
vertical-align: text-bottom;
font-size: 16px;
text-transform: uppercase;
display: inline-block;
}
.anticon {
transition: @transition-ease-out;
}
}
.switchtheme {
width: 100%;
position: absolute;
bottom: 0;
height: 48px;
background-color: @dark-half;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 16px 0 24px;
overflow: hidden;
z-index: 1000;
span {
white-space: nowrap;
overflow: hidden;
}
:global {
.anticon {
min-width: 14px;
margin-right: 8px;
font-size: 14px;
}
}
}
:global {
.ant-menu-dark,
.ant-menu-dark .ant-menu-sub {
color: #999;
}
.ant-menu-root {
height: ~"calc(100vh - 144px)";
overflow-x: hidden;
border-right: none;
&::-webkit-scrollbar-thumb {
background-color: transparent;
}
&:hover {
&::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
}
}
&.ant-menu-vertical {
.ant-menu-sub {
height: 0;
opacity: 0;
transition: none;
}
.ant-menu-submenu-active {
.ant-menu-sub {
height: auto;
opacity: 1;
}
}
}
}
.ant-menu {
transition: @transition-ease-out;
.ant-menu-item,
.ant-menu-submenu-title {
overflow: hidden;
white-space: normal;
}
}
}
}
.main {
margin-left: 224px;
overflow: auto;
height: 100vh;
transition: @transition-ease-out;
:global {
.content-inner {
background: #fff;
padding: 24px;
box-shadow: @shadow-1;
min-height: e("calc(100vh - 184px)");
}
}
.container {
margin: 24px;
}
.content {
min-height: e("calc(100vh - 184px)");
position: relative;
}
}
}
:global .ant-menu-dark {
color: #999;
background-color: #3e3e3e;
.ant-menu-submenu-title:hover,
.ant-menu-submenu:hover {
color: @primary-color;
}
.ant-menu-sub {
color: #999;
}
.ant-menu-submenu-selected {
color: @primary-color;
&:not(.ant-menu-submenu-open) {
background-color: @dark-half;
}
}
.ant-menu-item,
.ant-menu-submenu-title {
& > a {
color: #999;
}
&:hover > a {
color: @primary-color;
}
}
&.ant-menu-inline {
.ant-menu-item-selected {
background-color: @dark-half;
}
}
.ant-menu-item-selected {
color: @primary-color;
background-color: @dark-half;
& > a {
color: @primary-color;
}
&:hover {
background-color: @dark-half;
}
}
}
.spin {
:global .ant-spin-container {
height: 100vh;
}
}
@media (max-width: 767px) {
.layout {
.main {
.container {
margin: 12px;
}
.bread {
padding: 0 12px;
}
.content {
min-height: e("calc(100vh - 160px)");
}
:global {
.content-inner {
padding: 12px;
min-height: e("calc(100vh - 160px)");
}
}
}
}
}
@import "~themes/vars";
.layout {
position: relative;
height: 100vh;
&.withnavbar {
.main {
margin-left: 0;
}
}
&.fold {
.sider {
width: 42px;
.logo {
img {
width: 28px;
margin: 6px 7px;
}
}
:global {
.ant-menu-root {
width: 100%;
overflow: visible;
& > .ant-menu-item {
padding: 0;
text-align: center;
.anticon {
font-size: 14px;
margin-right: 0;
}
}
& > .ant-menu-submenu {
& > .ant-menu-submenu-title {
padding: 0;
text-align: center;
.anticon {
font-size: 14px;
margin-right: 0;
}
&::after {
display: none;
}
}
}
}
}
}
.main {
margin-left: 42px;
}
}
.sider {
width: 224px;
background: #3e3e3e;
position: absolute;
overflow: visible;
padding-bottom: 24px;
height: 100vh;
transition: @transition-ease-out;
box-shadow: @shadow-1;
color: #999;
&.light {
background: #fff;
.switchtheme {
background: #fff;
border-top: solid 1px #f8f8f8;
}
}
.logo {
text-align: center;
height: 40px;
line-height: 40px;
cursor: pointer;
margin: 28px 0;
transition: @transition-ease-out;
overflow: hidden;
img {
width: 40px;
margin-right: 8px;
transition: @transition-ease-out;
}
span {
vertical-align: text-bottom;
font-size: 16px;
text-transform: uppercase;
display: inline-block;
}
.anticon {
transition: @transition-ease-out;
}
}
.switchtheme {
width: 100%;
position: absolute;
bottom: 0;
height: 48px;
background-color: @dark-half;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 16px 0 24px;
overflow: hidden;
z-index: 1000;
span {
white-space: nowrap;
overflow: hidden;
}
:global {
.anticon {
min-width: 14px;
margin-right: 8px;
font-size: 14px;
}
}
}
:global {
.ant-menu-dark,
.ant-menu-dark .ant-menu-sub {
color: #999;
}
.ant-menu-root {
height: ~"calc(100vh - 144px)";
overflow-x: hidden;
border-right: none;
&::-webkit-scrollbar-thumb {
background-color: transparent;
}
&:hover {
&::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
}
}
&.ant-menu-vertical {
.ant-menu-sub {
height: 0;
opacity: 0;
transition: none;
}
.ant-menu-submenu-active {
.ant-menu-sub {
height: auto;
opacity: 1;
}
}
}
}
.ant-menu {
transition: @transition-ease-out;
.ant-menu-item,
.ant-menu-submenu-title {
overflow: hidden;
white-space: normal;
}
}
}
}
.main {
margin-left: 224px;
overflow: auto;
height: 100vh;
transition: @transition-ease-out;
:global {
.content-inner {
background: #fff;
padding: 24px;
box-shadow: @shadow-1;
min-height: e("calc(100vh - 184px)");
}
}
.container {
margin: 24px;
}
.content {
min-height: e("calc(100vh - 184px)");
position: relative;
}
}
}
:global .ant-menu-dark {
color: #999;
background-color: #3e3e3e;
.ant-menu-submenu-title:hover,
.ant-menu-submenu:hover {
color: @primary-color;
}
.ant-menu-sub {
color: #999;
}
.ant-menu-submenu-selected {
color: @primary-color;
&:not(.ant-menu-submenu-open) {
background-color: @dark-half;
}
}
.ant-menu-item,
.ant-menu-submenu-title {
& > a {
color: #999;
}
&:hover > a {
color: @primary-color;
}
}
&.ant-menu-inline {
.ant-menu-item-selected {
background-color: @dark-half;
}
}
.ant-menu-item-selected {
color: @primary-color;
background-color: @dark-half;
& > a {
color: @primary-color;
}
&:hover {
background-color: @dark-half;
}
}
}
.spin {
:global .ant-spin-container {
height: 100vh;
}
}
@media (max-width: 767px) {
.layout {
.main {
.container {
margin: 12px;
}
.bread {
padding: 0 12px;
}
.content {
min-height: e("calc(100vh - 160px)");
}
:global {
.content-inner {
padding: 12px;
min-height: e("calc(100vh - 160px)");
}
}
}
}
}
+17 -14
View File
@@ -1,18 +1,18 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Menu, Icon } from 'antd'
import { Link } from 'dva/router'
import { arrayToTree, queryArray } from '../../utils'
import { Link } from 'react-router-dom'
import { arrayToTree, queryArray } from 'utils'
import pathToRegexp from 'path-to-regexp'
const Menus = ({ siderFold, darkTheme, location, handleClickNavMenu, navOpenKeys, changeOpenKeys, menu }) => {
const Menus = ({ siderFold, darkTheme, handleClickNavMenu, navOpenKeys, changeOpenKeys, menu, location }) => {
// 生成树状
const menuTree = arrayToTree(menu.filter(_ => _.mpid !== -1), 'id', 'mpid')
const menuTree = arrayToTree(menu.filter(_ => _.mpid !== '-1'), 'id', 'mpid')
const levelMap = {}
// 递归生成菜单
const getMenus = (menuTreeN, siderFoldN) => {
return menuTreeN.map(item => {
return menuTreeN.map((item) => {
if (item.children) {
if (item.mpid) {
levelMap[item.id] = item.mpid
@@ -22,7 +22,7 @@ const Menus = ({ siderFold, darkTheme, location, handleClickNavMenu, navOpenKeys
key={item.id}
title={<span>
{item.icon && <Icon type={item.icon} />}
{(!siderFoldN || menuTree.indexOf(item) < 0) && item.name}
{(!siderFoldN || !menuTree.includes(item)) && item.name}
</span>}
>
{getMenus(item.children, siderFoldN)}
@@ -31,9 +31,9 @@ const Menus = ({ siderFold, darkTheme, location, handleClickNavMenu, navOpenKeys
}
return (
<Menu.Item key={item.id}>
<Link to={item.router}>
<Link to={item.route || '#'}>
{item.icon && <Icon type={item.icon} />}
{(!siderFoldN || menuTree.indexOf(item) < 0) && item.name}
{(!siderFoldN || !menuTree.includes(item)) && item.name}
</Link>
</Menu.Item>
)
@@ -60,8 +60,8 @@ const Menus = ({ siderFold, darkTheme, location, handleClickNavMenu, navOpenKeys
}
const onOpenChange = (openKeys) => {
const latestOpenKey = openKeys.find(key => !(navOpenKeys.indexOf(key) > -1))
const latestCloseKey = navOpenKeys.find(key => !(openKeys.indexOf(key) > -1))
const latestOpenKey = openKeys.find(key => !navOpenKeys.includes(key))
const latestCloseKey = navOpenKeys.find(key => !openKeys.includes(key))
let nextOpenKeys = []
if (latestOpenKey) {
nextOpenKeys = getAncestorKeys(latestOpenKey).concat(latestOpenKey)
@@ -82,7 +82,7 @@ const Menus = ({ siderFold, darkTheme, location, handleClickNavMenu, navOpenKeys
let currentMenu
let defaultSelectedKeys
for (let item of menu) {
if (item.router && pathToRegexp(item.router).exec(location.pathname)) {
if (item.route && pathToRegexp(item.route).exec(location.pathname)) {
currentMenu = item
break
}
@@ -102,13 +102,17 @@ const Menus = ({ siderFold, darkTheme, location, handleClickNavMenu, navOpenKeys
defaultSelectedKeys = getPathArray(menu, currentMenu, 'mpid', 'id')
}
if (!defaultSelectedKeys) {
defaultSelectedKeys = ['1']
}
return (
<Menu
{...menuProps}
mode={siderFold ? 'vertical' : 'inline'}
theme={darkTheme ? 'dark' : 'light'}
onClick={handleClickNavMenu}
defaultSelectedKeys={defaultSelectedKeys}
selectedKeys={defaultSelectedKeys}
>
{menuItems}
</Menu>
@@ -119,11 +123,10 @@ Menus.propTypes = {
menu: PropTypes.array,
siderFold: PropTypes.bool,
darkTheme: PropTypes.bool,
location: PropTypes.object,
isNavbar: PropTypes.bool,
handleClickNavMenu: PropTypes.func,
navOpenKeys: PropTypes.array,
changeOpenKeys: PropTypes.func,
location: PropTypes.object,
}
export default Menus
+1 -1
View File
@@ -1,8 +1,8 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Icon, Switch } from 'antd'
import { config } from 'utils'
import styles from './Layout.less'
import { config } from '../../utils'
import Menus from './Menu'
const Sider = ({ siderFold, darkTheme, location, changeTheme, navOpenKeys, changeOpenKeys, menu }) => {
+1 -1
View File
@@ -1 +1 @@
@import "../../themes/vars.less";
@import "~themes/vars";
+25
View File
@@ -0,0 +1,25 @@
import React from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import styles from './Loader.less'
const Loader = ({ spinning, fullScreen }) => {
return (<div className={classNames(styles.loader, {
[styles.hidden]: !spinning,
[styles.fullScreen]: fullScreen,
})}
>
<div className={styles.warpper}>
<div className={styles.inner} />
<div className={styles.text} >LOADING</div>
</div>
</div>)
}
Loader.propTypes = {
spinning: PropTypes.bool,
fullScreen: PropTypes.bool,
}
export default Loader
+68
View File
@@ -0,0 +1,68 @@
.loader {
display: block;
background-color: #fff;
width: 100%;
position: absolute;
top: 0;
bottom: 0;
left: 0;
z-index: 100000;
display: flex;
justify-content: center;
align-items: center;
opacity: 1;
text-align: center;
&.fullScreen {
position: fixed;
}
.warpper {
width: 100px;
height: 100px;
display: inline-flex;
flex-direction: column;
justify-content: space-around;
}
.inner {
width: 48px;
height: 48px;
margin: 0 auto;
text-indent: -12345px;
border-top: 1px solid rgba(0, 0, 0, 0.08);
border-right: 1px solid rgba(0, 0, 0, 0.08);
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
border-left: 1px solid rgba(0, 0, 0, 0.7);
border-radius: 50%;
z-index: 100001;
:local {
animation: spinner 600ms infinite linear;
}
}
.text {
width: 100px;
height: 20px;
text-align: center;
font-size: 12px;
letter-spacing: 4px;
color: #000;
}
&.hidden {
z-index: -1;
opacity: 0;
transition: opacity 1s ease 0.5s, z-index 0.1s ease 1.5s;
}
}
@keyframes spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"name": "Loader",
"version": "0.0.0",
"private": true,
"main": "./Loader.js"
}
+34
View File
@@ -0,0 +1,34 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import Loader from '../Loader'
import styles from './Page.less'
export default class Page extends Component {
render () {
const { className, children, loading = false, inner = false } = this.props
const loadingStyle = {
height: 'calc(100vh - 184px)',
overflow: 'hidden',
}
return (
<div
className={classnames(className, {
[styles.contentInner]: inner,
})}
style={loading ? loadingStyle : null}
>
{loading ? <Loader spinning /> : ''}
{children}
</div>
)
}
}
Page.propTypes = {
className: PropTypes.string,
children: PropTypes.node,
loading: PropTypes.bool,
inner: PropTypes.bool,
}
+14
View File
@@ -0,0 +1,14 @@
@import "~themes/vars";
.contentInner{
background: #fff;
padding: 24px;
box-shadow: @shadow-1;
min-height: e("calc(100vh - 184px)");
}
@media (max-width: 767px) {
.contentInner {
padding: 12px;
min-height: e("calc(100vh - 160px)");
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"name": "Page",
"version": "0.0.0",
"private": true,
"main": "./Page.js"
}
+3 -3
View File
@@ -1,8 +1,8 @@
import React from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import styles from './Search.less'
import { Input, Select, Button, Icon } from 'antd'
import styles from './Search.less'
class Search extends React.Component {
state = {
@@ -18,13 +18,13 @@ class Search extends React.Component {
}
if (this.props.onSearch) this.props.onSearch(data)
}
handleInputChange = e => {
handleInputChange = (e) => {
this.setState({
...this.state,
clearVisible: e.target.value !== '',
})
}
handleSelectChange = value => {
handleSelectChange = (value) => {
this.setState({
...this.state,
selectValue: value,
+5 -1
View File
@@ -4,7 +4,9 @@ import Iconfont from './Iconfont'
import Search from './Search'
import Editor from './Editor'
import FilterItem from './FilterItem'
import Loader from './Loader'
import * as Layout from './Layout/index.js'
import Page from './Page'
import layer from './layer'
@@ -16,5 +18,7 @@ export {
Search,
Editor,
FilterItem,
Loader,
layer,
}
Page,
}
+6 -3
View File
@@ -1,8 +1,10 @@
/* global document */
import { Modal, message } from 'antd'
import React from 'react'
import ReactDOM from 'react-dom'
import classnames from 'classnames'
import styles from './layer.less'
const { info, success, error, warning, confirm } = Modal
const layer = {
@@ -15,7 +17,7 @@ const layer = {
confirm,
}
layer.close = (index) => new Promise((resolve, reject) => {
layer.close = index => new Promise((resolve, reject) => {
const { prefixCls } = layer
let div = document.getElementById(`${prefixCls}-reference-${index}`)
if (index === undefined) {
@@ -41,7 +43,7 @@ layer.closeAll = () => {
let i = 0
while (i < references.length) {
layer.close()
i++
i += 1
}
}
@@ -50,7 +52,8 @@ layer.open = (config) => {
const { content, ...modalProps } = props
const { className, wrapClassName = '', verticalCenter = true } = modalProps
const { prefixCls } = layer
const index = layer.index++
layer.index += 1
const index = layer.index
let div = document.createElement('div')
div.id = `${prefixCls}-reference-${index}`
div.className = `${prefixCls}-reference`
+25
View File
@@ -0,0 +1,25 @@
<% htmlWebpackPlugin.options.headScripts = htmlWebpackPlugin.options.headScripts || [] %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="A admin dashboard application demo built upon Ant Design and Dva.js">
<title>antd admin</title>
<!--[if lte IE 10]>
<script
src="https://as.alipayobjects.com/g/component/??console-polyfill/0.2.2/index.js,media-match/2.0.2/media.match.min.js"></script>
<![endif]-->
<% for (item of htmlWebpackPlugin.options.headScripts) { %>
<script src="<%= item %>"></script>
<% } %>
</head>
<body>
<div id="root"></div>
</body>
</html>
-32
View File
@@ -1,32 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="stylesheet" href="/index.css" />
<!--[if lte IE 10]>
<script
src="https://as.alipayobjects.com/g/component/??console-polyfill/0.2.2/index.js,media-match/2.0.2/media.match.min.js"></script>
<![endif]-->
</head>
<body>
<div id="root"></div>
<script type="text/javascript">
(function() {
var __oldConsoleError = window.console.error.bind(console)
window.console.error = function(message, b) {
if (message === "Warning: Accessing PropTypes via the main React package is deprecated. Use the prop-types package from npm instead." ||
"Warning: A Component: React.createClass is deprecated and will be removed in version 16. Use plain JavaScript classes instead. If you're not yet ready to migrate, create-react-class is available on npm as a drop-in replacement.") {
return false
}
__oldConsoleError(message)
}
})()
</script>
<script src="/index.js"></script>
</body>
</html>
+4 -5
View File
@@ -1,16 +1,15 @@
import './index.html'
import 'babel-polyfill'
import { message } from 'antd'
import dva from 'dva'
import createLoading from 'dva-loading'
import { browserHistory } from 'dva/router'
import { message } from 'antd'
import createHistory from 'history/createBrowserHistory'
import 'babel-polyfill'
// 1. Initialize
const app = dva({
...createLoading({
effects: true,
}),
history: browserHistory,
history: createHistory(),
onError (error) {
message.error(error.message)
},
+63
View File
@@ -0,0 +1,63 @@
const Mock = require('mockjs')
const config = require('../utils/config')
const queryArray = (array, key, keyAlias = 'key') => {
if (!(array instanceof Array)) {
return null
}
let data
for (const item of array) {
if (item[keyAlias] === key) {
data = item
break
}
}
if (data) {
return data
}
return null
}
const NOTFOUND = {
message: 'Not Found',
documentation_url: 'http://localhost:8000/request',
}
let postId = 0
const posts = Mock.mock({
'data|100': [
{
id () {
postId += 1
return postId + 10000
},
'status|1-2': 1,
title: '@title',
author: '@last',
categories: '@word',
tags: '@word',
'views|10-200': 1,
'comments|10-200': 1,
visibility: () => {
return Mock.mock('@pick(["Public",'
+ '"Password protected", '
+ '"Private"])')
},
date: '@dateTime',
image () {
return Mock.Random.image('100x100', Mock.Random.color(), '#757575', 'png', this.author.substr(0, 1))
},
},
],
}).data
module.exports = {
queryArray,
NOTFOUND,
Mock,
posts,
config,
}
+2
View File
@@ -1,6 +1,8 @@
import { color } from '../utils/theme'
const Mock = require('mockjs')
const config = require('../utils/config')
const { apiPrefix } = config
const Dashboard = Mock.mock({
+164
View File
@@ -0,0 +1,164 @@
const { config } = require('./common')
const { apiPrefix } = config
let database = [
{
id: '1',
icon: 'laptop',
name: 'Dashboard',
route: '/dashboard',
},
{
id: '2',
bpid: '1',
name: 'Users',
icon: 'user',
route: '/user',
},
{
id: '7',
bpid: '1',
name: 'Posts',
icon: 'shopping-cart',
route: '/post',
},
{
id: '21',
mpid: '-1',
bpid: '2',
name: 'User Detail',
route: '/user/:id',
},
{
id: '3',
bpid: '1',
name: 'Request',
icon: 'api',
route: '/request',
},
{
id: '4',
bpid: '1',
name: 'UI Element',
icon: 'camera-o',
},
{
id: '41',
bpid: '4',
mpid: '4',
name: 'IconFont',
icon: 'heart-o',
route: '/UIElement/iconfont',
},
{
id: '42',
bpid: '4',
mpid: '4',
name: 'DataTable',
icon: 'database',
route: '/UIElement/dataTable',
},
{
id: '43',
bpid: '4',
mpid: '4',
name: 'DropOption',
icon: 'bars',
route: '/UIElement/dropOption',
},
{
id: '44',
bpid: '4',
mpid: '4',
name: 'Search',
icon: 'search',
route: '/UIElement/search',
},
{
id: '45',
bpid: '4',
mpid: '4',
name: 'Editor',
icon: 'edit',
route: '/UIElement/editor',
},
{
id: '46',
bpid: '4',
mpid: '4',
name: 'layer (Function)',
icon: 'credit-card',
route: '/UIElement/layer',
},
{
id: '5',
bpid: '1',
name: 'Recharts',
icon: 'code-o',
},
{
id: '51',
bpid: '5',
mpid: '5',
name: 'LineChart',
icon: 'line-chart',
route: '/chart/lineChart',
},
{
id: '52',
bpid: '5',
mpid: '5',
name: 'BarChart',
icon: 'bar-chart',
route: '/chart/barChart',
},
{
id: '53',
bpid: '5',
mpid: '5',
name: 'AreaChart',
icon: 'area-chart',
route: '/chart/areaChart',
},
{
id: '6',
bpid: '1',
name: 'Test Navigation',
icon: 'setting',
},
{
id: '61',
bpid: '6',
mpid: '6',
name: 'Test Navigation1',
route: '/navigation/navigation1',
},
{
id: '62',
bpid: '6',
mpid: '6',
name: 'Test Navigation2',
route: '/navigation/navigation2',
},
{
id: '621',
bpid: '62',
mpid: '62',
name: 'Test Navigation21',
route: '/navigation/navigation2/navigation1',
},
{
id: '622',
bpid: '62',
mpid: '62',
name: 'Test Navigation22',
route: '/navigation/navigation2/navigation2',
},
]
module.exports = {
[`GET ${apiPrefix}/menus`] (req, res) {
res.status(200).json(database)
},
}
+31
View File
@@ -0,0 +1,31 @@
const { config, posts } = require('./common')
const { apiPrefix } = config
let database = posts
module.exports = {
[`GET ${apiPrefix}/posts`] (req, res) {
const { query } = req
let { pageSize, page, ...other } = query
pageSize = pageSize || 10
page = page || 1
let newData = database
for (let key in other) {
if ({}.hasOwnProperty.call(other, key)) {
newData = newData.filter((item) => {
if ({}.hasOwnProperty.call(item, key)) {
return String(item[key]).trim().indexOf(decodeURI(other[key]).trim()) > -1
}
return true
})
}
}
res.status(200).json({
data: newData.slice((page - 1) * pageSize, page * pageSize),
total: newData.length,
})
},
}
+20 -10
View File
@@ -1,6 +1,7 @@
const qs = require('qs')
const Mock = require('mockjs')
const config = require('../utils/config')
const { apiPrefix } = config
let usersListData = Mock.mock({
@@ -25,14 +26,23 @@ let usersListData = Mock.mock({
let database = usersListData.data
const EnumRoleType = {
ADMIN: 'admin',
DEFAULT: 'guest',
DEVELOPER: 'developer',
}
const userPermission = {
DEFAULT: [
'dashboard', 'chart',
],
ADMIN: [
'dashboard', 'users', 'UIElement', 'UIElementIconfont', 'chart',
],
DEVELOPER: ['dashboard', 'users', 'UIElement', 'UIElementIconfont', 'chart'],
DEFAULT: {
visit: ['1', '2', '21', '7', '5', '51', '52', '53'],
role: EnumRoleType.DEFAULT,
},
ADMIN: {
role: EnumRoleType.ADMIN,
},
DEVELOPER: {
role: EnumRoleType.DEVELOPER,
},
}
const adminUsers = [
@@ -82,7 +92,7 @@ module.exports = {
[`POST ${apiPrefix}/user/login`] (req, res) {
const { username, password } = req.body
const user = adminUsers.filter((item) => item.username === username)
const user = adminUsers.filter(item => item.username === username)
if (user.length > 0 && user[0].password === password) {
const now = new Date()
@@ -165,7 +175,7 @@ module.exports = {
[`DELETE ${apiPrefix}/users`] (req, res) {
const { ids } = req.body
database = database.filter((item) => !ids.some(_ => _ === item.id))
database = database.filter(item => !ids.some(_ => _ === item.id))
res.status(204).end()
},
@@ -195,7 +205,7 @@ module.exports = {
const { id } = req.params
const data = queryArray(database, id, 'id')
if (data) {
database = database.filter((item) => item.id !== id)
database = database.filter(item => item.id !== id)
res.status(204).end()
} else {
res.status(404).json(NOTFOUND)
+80 -29
View File
@@ -1,21 +1,53 @@
import { query, logout } from '../services/app'
/* global window */
/* global document */
/* global location */
import { routerRedux } from 'dva/router'
import { parse } from 'qs'
import { config } from '../utils'
import config from 'config'
import { EnumRoleType } from 'enums'
import { query, logout } from 'services/app'
import * as menusService from 'services/menus'
import queryString from 'query-string'
const { prefix } = config
export default {
namespace: 'app',
state: {
user: {},
permissions: {
visit: [],
},
menu: [
{
id: 1,
icon: 'laptop',
name: 'Dashboard',
router: '/dashboard',
},
],
menuPopoverVisible: false,
siderFold: localStorage.getItem(`${prefix}siderFold`) === 'true',
darkTheme: localStorage.getItem(`${prefix}darkTheme`) === 'true',
siderFold: window.localStorage.getItem(`${prefix}siderFold`) === 'true',
darkTheme: window.localStorage.getItem(`${prefix}darkTheme`) === 'true',
isNavbar: document.body.clientWidth < 769,
navOpenKeys: JSON.parse(localStorage.getItem(`${prefix}navOpenKeys`)) || [],
navOpenKeys: JSON.parse(window.localStorage.getItem(`${prefix}navOpenKeys`)) || [],
locationPathname: '',
locationQuery: {},
},
subscriptions: {
setupHistory ({ dispatch, history }) {
history.listen((location) => {
dispatch({
type: 'updateState',
payload: {
locationPathname: location.pathname,
locationQuery: queryString.parse(location.search),
},
})
})
},
setup ({ dispatch }) {
dispatch({ type: 'query' })
let tid
@@ -30,30 +62,51 @@ export default {
},
effects: {
*query ({
* query ({
payload,
}, { call, put }) {
const data = yield call(query, parse(payload))
if (data.success && data.user) {
}, { call, put, select }) {
const { success, user } = yield call(query, payload)
const { locationPathname } = yield select(_ => _.app)
if (success && user) {
const { list } = yield call(menusService.query)
const { permissions } = user
let menu = list
if (permissions.role === EnumRoleType.ADMIN || permissions.role === EnumRoleType.DEVELOPER) {
permissions.visit = list.map(item => item.id)
} else {
menu = list.filter((item) => {
const cases = [
permissions.visit.includes(item.id),
item.mpid ? permissions.visit.includes(item.mpid) || item.mpid === '-1' : true,
item.bpid ? permissions.visit.includes(item.bpid) : true,
]
return cases.every(_ => _)
})
}
yield put({
type: 'querySuccess',
payload: data.user,
type: 'updateState',
payload: {
user,
permissions,
menu,
},
})
if (location.pathname === '/login') {
yield put(routerRedux.push('/dashboard'))
}
} else {
if (location.pathname !== '/login') {
let from = location.pathname
if (location.pathname === '/dashboard') {
from = '/dashboard'
}
window.location = `${location.origin}/login?from=${from}`
yield put(routerRedux.push({
pathname: '/dashboard',
}))
}
} else if (config.openPages && config.openPages.indexOf(locationPathname) < 0) {
yield put(routerRedux.push({
pathname: '/login',
search: queryString.stringify({
from: locationPathname,
}),
}))
}
},
*logout ({
* logout ({
payload,
}, { call, put }) {
const data = yield call(logout, parse(payload))
@@ -64,10 +117,8 @@ export default {
}
},
*changeNavbar ({
payload,
}, { put, select }) {
const { app } = yield(select(_ => _))
* changeNavbar (action, { put, select }) {
const { app } = yield (select(_ => _))
const isNavbar = document.body.clientWidth < 769
if (isNavbar !== app.isNavbar) {
yield put({ type: 'handleNavbar', payload: isNavbar })
@@ -76,15 +127,15 @@ export default {
},
reducers: {
querySuccess (state, { payload: user }) {
updateState (state, { payload }) {
return {
...state,
user,
...payload,
}
},
switchSider (state) {
localStorage.setItem(`${prefix}siderFold`, !state.siderFold)
window.localStorage.setItem(`${prefix}siderFold`, !state.siderFold)
return {
...state,
siderFold: !state.siderFold,
@@ -92,7 +143,7 @@ export default {
},
switchTheme (state) {
localStorage.setItem(`${prefix}darkTheme`, !state.darkTheme)
window.localStorage.setItem(`${prefix}darkTheme`, !state.darkTheme)
return {
...state,
darkTheme: !state.darkTheme,
+47
View File
@@ -0,0 +1,47 @@
import modelExtend from 'dva-model-extend'
const model = {
reducers: {
updateState (state, { payload }) {
return {
...state,
...payload,
}
},
},
}
const pageModel = modelExtend(model, {
state: {
list: [],
pagination: {
showSizeChanger: true,
showQuickJumper: true,
showTotal: total => `Total ${total} Items`,
current: 1,
total: 0,
},
},
reducers: {
querySuccess (state, { payload }) {
const { list, pagination } = payload
return {
...state,
list,
pagination: {
...state.pagination,
...pagination,
},
}
},
},
})
module.exports = {
model,
pageModel,
}
+40 -197
View File
@@ -1,178 +1,17 @@
import { myCity, queryWeather, query } from '../services/dashboard'
import { parse } from 'qs'
import modelExtend from 'dva-model-extend'
import { query } from 'services/dashboard'
import { model } from 'models/common'
import * as weatherService from 'services/weather'
// zuimei 摘自 http://www.zuimeitianqi.com/res/js/index.js
let zuimei = {
parseActualData (actual) {
let weather = {
icon: `http://www.zuimeitianqi.com/res/icon/${zuimei.getIconName(actual.wea, 'big')}`,
name: zuimei.getWeatherName(actual.wea),
temperature: actual.tmp,
dateTime: new Date(actual.PTm).format('MM-dd hh:mm'),
}
return weather
},
getIconName (wea, flg) {
let myDate = new Date()
let hour = myDate.getHours()
let num = 0
if (wea.indexOf('/') !== -1) {
let weas = wea.split('/')
if (hour < 12) {
num = zuimei.replaceIcon(weas[0])
if (num < 6) {
num = `${num}_${flg}_night.png`
} else {
num = `${num}_${flg}.png`
}
} else if (hour >= 12) {
num = zuimei.replaceIcon(weas[1])
if (hour >= 18) {
num = `${num}_${flg}_night.png`
} else {
num = `${num}_${flg}.png`
}
}
} else {
if ((hour >= 18 && hour <= 23) || (hour >= 0 && hour <= 6)) {
num = `${num}_${flg}_night.png`
} else {
num = `${num}_${flg}.png`
}
}
return num
},
replaceIcon (num) {
if (num === 21) {
num = 7
} else if (num === 22) {
num = 8
} else if (num === 10 || num === 11 || num === 12 || num === 23 || num === 24 || num === 25) {
num = 9
} else if (num === 13 || num === 15 || num === 26 || num === 27 || num === 34) {
num = 14
} else if (num === 17 || num === 28) {
num = 16
} else if (num === 35) {
num = 18
} else if (num === 31 || num === 32 || num === 33) {
num = 20
} else if (num === 30) {
num = 29
}
return num
},
getWeatherName (wea) {
let name = ''
if (wea.indexOf('/') !== -1) {
let weas = wea.split('/')
name = `${zuimei.getWeatherByCode(weas[0])}转${zuimei.getWeatherByCode(weas[1])}`
} else {
name = zuimei.getWeatherByCode(wea)
}
return name
},
getWeatherByCode (number) {
let wea = ''
let num = Number(number)
if (num === 0) {
wea = '晴'
} else if (num === 1) {
wea = '多云'
} else if (num === 2) {
wea = '阴'
} else if (num === 3) {
wea = '阵雨'
} else if (num === 4) {
wea = '雷阵雨'
} else if (num === 5) {
wea = '雷阵雨并伴有冰雹'
} else if (num === 6) {
wea = '雨夹雪'
} else if (num === 7) {
wea = '小雨'
} else if (num === 8) {
wea = '中雨'
} else if (num === 9) {
wea = '大雨'
} else if (num === 10) {
wea = '暴雨'
} else if (num === 11) {
wea = '大暴雨'
} else if (num === 12) {
wea = '特大暴雨'
} else if (num === 13) {
wea = '阵雪'
} else if (num === 14) {
wea = '小雪'
} else if (num === 15) {
wea = '中雪'
} else if (num === 16) {
wea = '大雪'
} else if (num === 17) {
wea = '暴雪'
} else if (num === 18) {
wea = '雾'
} else if (num === 19) {
wea = '冻雨'
} else if (num === 20) {
wea = '沙尘暴'
} else if (num === 21) {
wea = '小雨-中雨'
} else if (num === 22) {
wea = '中雨-大雨'
} else if (num === 23) {
wea = '大雨-暴雨'
} else if (num === 24) {
wea = '暴雨-大暴雨'
} else if (num === 25) {
wea = '大暴雨-特大暴雨'
} else if (num === 26) {
wea = '小雪-中雪'
} else if (num === 27) {
wea = '中雪-大雪'
} else if (num === 28) {
wea = '大雪-暴雪'
} else if (num === 29) {
wea = '浮沉'
} else if (num === 30) {
wea = '扬沙'
} else if (num === 31) {
wea = '强沙尘暴'
} else if (num === 32) {
wea = '飑'
} else if (num === 33) {
wea = '龙卷风'
} else if (num === 34) {
wea = '若高吹雪'
} else if (num === 35) {
wea = '轻雾'
} else if (num === 53) {
wea = '霾'
} else if (num === 99) {
wea = '未知'
}
return wea
},
}
export default {
export default modelExtend(model, {
namespace: 'dashboard',
state: {
weather: {
city: '成都',
temperature: '5',
city: '深圳',
temperature: '30',
name: '晴',
icon: 'http://www.zuimeitianqi.com/res/icon/0_big.png',
dateTime: new Date().format('MM-dd hh:mm'),
icon: '//s5.sencdn.com/web/icons/3d_50/2.png',
},
sales: [],
quote: {
@@ -189,42 +28,46 @@ export default {
},
},
subscriptions: {
setup ({ dispatch }) {
dispatch({ type: 'query' })
dispatch({ type: 'queryWeather' })
setup ({ dispatch, history }) {
history.listen(({ pathname }) => {
if (pathname === '/dashboard' || pathname === '/') {
dispatch({ type: 'query' })
dispatch({ type: 'queryWeather' })
}
})
},
},
effects: {
*query ({
* query ({
payload,
}, { call, put }) {
const data = yield call(query, parse(payload))
yield put({ type: 'queryWeather', payload: { ...data } })
yield put({
type: 'updateState',
payload: data,
})
},
*queryWeather ({
payload,
* queryWeather ({
payload = {},
}, { call, put }) {
const myCityResult = yield call(myCity, { flg: 0 })
const result = yield call(queryWeather, { cityCode: myCityResult.selectCityCode })
const weather = zuimei.parseActualData(result.data.actual)
weather.city = myCityResult.selectCityName
yield put({ type: 'queryWeatherSuccess', payload: {
weather,
} })
},
},
reducers: {
queryWeatherSuccess (state, action) {
return {
...state,
...action.payload,
}
},
queryWeather (state, action) {
return {
...state,
...action.payload,
payload.location = 'shenzhen'
const result = yield call(weatherService.query, payload)
const { success } = result
if (success) {
const data = result.results[0]
const weather = {
city: data.location.name,
temperature: data.now.temperature,
name: data.now.text,
icon: `//s5.sencdn.com/web/icons/3d_50/${data.now.code}.png`,
}
yield put({
type: 'updateState',
payload: {
weather,
},
})
}
},
},
}
})
+9 -25
View File
@@ -1,24 +1,21 @@
import { login } from '../services/login'
import { routerRedux } from 'dva/router'
import { queryURL } from '../utils'
import { login } from 'services/login'
export default {
namespace: 'login',
state: {
loginLoading: false,
},
state: {},
effects: {
*login ({
* login ({
payload,
}, { put, call }) {
yield put({ type: 'showLoginLoading' })
}, { put, call, select }) {
const data = yield call(login, payload)
yield put({ type: 'hideLoginLoading' })
const { locationQuery } = yield select(_ => _.app)
if (data.success) {
const from = queryURL('from')
const { from } = locationQuery
yield put({ type: 'app/query' })
if (from) {
if (from && from !== '/login') {
yield put(routerRedux.push(from))
} else {
yield put(routerRedux.push('/dashboard'))
@@ -28,18 +25,5 @@ export default {
}
},
},
reducers: {
showLoginLoading (state) {
return {
...state,
loginLoading: true,
}
},
hideLoginLoading (state) {
return {
...state,
loginLoading: false,
}
},
},
}
+46
View File
@@ -0,0 +1,46 @@
import modelExtend from 'dva-model-extend'
import { query } from 'services/posts'
import { pageModel } from 'models/common'
import queryString from 'query-string'
export default modelExtend(pageModel, {
namespace: 'post',
subscriptions: {
setup ({ dispatch, history }) {
history.listen((location) => {
if (location.pathname === '/post') {
dispatch({ type: 'query',
payload: {
status: 2,
...queryString.parse(location.search),
} })
}
})
},
},
effects: {
* query ({
payload,
}, { call, put }) {
const data = yield call(query, payload)
if (data.success) {
yield put({
type: 'querySuccess',
payload: {
list: data.data,
pagination: {
current: Number(payload.page) || 1,
pageSize: Number(payload.pageSize) || 10,
total: data.total,
},
},
})
} else {
throw data
}
},
},
})
+21 -44
View File
@@ -1,36 +1,32 @@
import { create, remove, update } from '../services/user'
import * as usersService from '../services/users'
import { parse } from 'qs'
/* global window */
import modelExtend from 'dva-model-extend'
import { config } from 'utils'
import { create, remove, update } from 'services/user'
import * as usersService from 'services/users'
import queryString from 'query-string'
import { pageModel } from './common'
const { query } = usersService
const { prefix } = config
export default {
export default modelExtend(pageModel, {
namespace: 'user',
state: {
list: [],
currentItem: {},
modalVisible: false,
modalType: 'create',
selectedRowKeys: [],
isMotion: localStorage.getItem('antdAdminUserIsMotion') === 'true',
pagination: {
showSizeChanger: true,
showQuickJumper: true,
showTotal: total => `共 ${total} 条`,
current: 1,
total: null,
},
isMotion: window.localStorage.getItem(`${prefix}userIsMotion`) === 'true',
},
subscriptions: {
setup ({ dispatch, history }) {
history.listen(location => {
history.listen((location) => {
if (location.pathname === '/user') {
dispatch({
type: 'query',
payload: location.query,
payload: queryString.parse(location.search),
})
}
})
@@ -39,8 +35,7 @@ export default {
effects: {
*query ({ payload }, { call, put }) {
payload = parse(location.search.substr(1))
* query ({ payload = {} }, { call, put }) {
const data = yield call(query, payload)
if (data) {
yield put({
@@ -57,7 +52,7 @@ export default {
}
},
*'delete' ({ payload }, { call, put, select }) {
* delete ({ payload }, { call, put, select }) {
const data = yield call(remove, { id: payload })
const { selectedRowKeys } = yield select(_ => _.user)
if (data.success) {
@@ -68,7 +63,7 @@ export default {
}
},
*'multiDelete' ({ payload }, { call, put }) {
* multiDelete ({ payload }, { call, put }) {
const data = yield call(usersService.remove, payload)
if (data.success) {
yield put({ type: 'updateState', payload: { selectedRowKeys: [] } })
@@ -78,7 +73,7 @@ export default {
}
},
*create ({ payload }, { call, put }) {
* create ({ payload }, { call, put }) {
const data = yield call(create, payload)
if (data.success) {
yield put({ type: 'hideModal' })
@@ -88,7 +83,7 @@ export default {
}
},
*update ({ payload }, { select, call, put }) {
* update ({ payload }, { select, call, put }) {
const id = yield select(({ user }) => user.currentItem.id)
const newUser = { ...payload, id }
const data = yield call(update, newUser)
@@ -104,25 +99,8 @@ export default {
reducers: {
querySuccess (state, action) {
const { list, pagination } = action.payload
return { ...state,
list,
pagination: {
...state.pagination,
...pagination,
} }
},
updateState (state, { payload }) {
return {
...state,
...payload,
}
},
showModal (state, action) {
return { ...state, ...action.payload, modalVisible: true }
showModal (state, { payload }) {
return { ...state, ...payload, modalVisible: true }
},
hideModal (state) {
@@ -130,10 +108,9 @@ export default {
},
switchIsMotion (state) {
localStorage.setItem('antdAdminUserIsMotion', !state.isMotion)
window.localStorage.setItem(`${prefix}userIsMotion`, !state.isMotion)
return { ...state, isMotion: !state.isMotion }
},
},
}
})
+3 -3
View File
@@ -11,8 +11,8 @@ export default {
subscriptions: {
setup ({ dispatch, history }) {
history.listen(() => {
const match = pathToRegexp('/user/:id').exec(location.pathname)
history.listen(({ pathname }) => {
const match = pathToRegexp('/user/:id').exec(pathname)
if (match) {
dispatch({ type: 'query', payload: { id: match[1] } })
}
@@ -21,7 +21,7 @@ export default {
},
effects: {
*query ({
* query ({
payload,
}, { call, put }) {
const data = yield call(query, payload)

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

+79 -128
View File
@@ -1,141 +1,92 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Router } from 'dva/router'
import App from './routes/app'
import { Switch, Route, Redirect, routerRedux } from 'dva/router'
import dynamic from 'dva/dynamic'
import App from 'routes/app'
const registerModel = (app, model) => {
if (!(app._models.filter(m => m.namespace === model.namespace).length === 1)) {
app.model(model)
}
}
const { ConnectedRouter } = routerRedux
const Routers = function ({ history, app }) {
const error = dynamic({
app,
component: () => import('./routes/error'),
})
const routes = [
{
path: '/',
component: App,
getIndexRoute (nextState, cb) {
require.ensure([], require => {
registerModel(app, require('./models/dashboard'))
cb(null, { component: require('./routes/dashboard/') })
}, 'dashboard')
},
childRoutes: [
{
path: 'dashboard',
getComponent (nextState, cb) {
require.ensure([], require => {
registerModel(app, require('./models/dashboard'))
cb(null, require('./routes/dashboard/'))
}, 'dashboard')
},
}, {
path: 'user',
getComponent (nextState, cb) {
require.ensure([], require => {
registerModel(app, require('./models/user'))
cb(null, require('./routes/user/'))
}, 'user')
},
}, {
path: 'user/:id',
getComponent (nextState, cb) {
require.ensure([], require => {
registerModel(app, require('./models/user/detail'))
cb(null, require('./routes/user/detail/'))
}, 'user-detail')
},
}, {
path: 'login',
getComponent (nextState, cb) {
require.ensure([], require => {
registerModel(app, require('./models/login'))
cb(null, require('./routes/login/'))
}, 'login')
},
}, {
path: 'request',
getComponent (nextState, cb) {
require.ensure([], require => {
cb(null, require('./routes/request/'))
}, 'request')
},
}, {
path: 'UIElement/iconfont',
getComponent (nextState, cb) {
require.ensure([], require => {
cb(null, require('./routes/UIElement/iconfont/'))
}, 'UIElement-iconfont')
},
}, {
path: 'UIElement/search',
getComponent (nextState, cb) {
require.ensure([], require => {
cb(null, require('./routes/UIElement/search/'))
}, 'UIElement-search')
},
}, {
path: 'UIElement/dropOption',
getComponent (nextState, cb) {
require.ensure([], require => {
cb(null, require('./routes/UIElement/dropOption/'))
}, 'UIElement-dropOption')
},
}, {
path: 'UIElement/layer',
getComponent (nextState, cb) {
require.ensure([], require => {
cb(null, require('./routes/UIElement/layer/'))
}, 'UIElement-layer')
},
}, {
path: 'UIElement/dataTable',
getComponent (nextState, cb) {
require.ensure([], require => {
cb(null, require('./routes/UIElement/dataTable/'))
}, 'UIElement-dataTable')
},
}, {
path: 'UIElement/editor',
getComponent (nextState, cb) {
require.ensure([], require => {
cb(null, require('./routes/UIElement/editor/'))
}, 'UIElement-editor')
},
}, {
path: 'chart/lineChart',
getComponent (nextState, cb) {
require.ensure([], require => {
cb(null, require('./routes/chart/lineChart/'))
}, 'chart-lineChart')
},
}, {
path: 'chart/barChart',
getComponent (nextState, cb) {
require.ensure([], require => {
cb(null, require('./routes/chart/barChart/'))
}, 'chart-barChart')
},
}, {
path: 'chart/areaChart',
getComponent (nextState, cb) {
require.ensure([], require => {
cb(null, require('./routes/chart/areaChart/'))
}, 'chart-areaChart')
},
}, {
path: '*',
getComponent (nextState, cb) {
require.ensure([], require => {
cb(null, require('./routes/error/'))
}, 'error')
},
},
],
path: '/dashboard',
models: () => [import('./models/dashboard')],
component: () => import('./routes/dashboard/'),
}, {
path: '/user',
models: () => [import('./models/user')],
component: () => import('./routes/user/'),
}, {
path: '/user/:id',
models: () => [import('./models/user/detail')],
component: () => import('./routes/user/detail/'),
}, {
path: '/login',
models: () => [import('./models/login')],
component: () => import('./routes/login/'),
}, {
path: '/request',
component: () => import('./routes/request/'),
}, {
path: '/UIElement/iconfont',
component: () => import('./routes/UIElement/iconfont/'),
}, {
path: '/UIElement/search',
component: () => import('./routes/UIElement/search/'),
}, {
path: '/UIElement/dropOption',
component: () => import('./routes/UIElement/dropOption/'),
}, {
path: '/UIElement/layer',
component: () => import('./routes/UIElement/layer/'),
}, {
path: '/UIElement/dataTable',
component: () => import('./routes/UIElement/dataTable/'),
}, {
path: '/UIElement/editor',
component: () => import('./routes/UIElement/editor/'),
}, {
path: '/chart/lineChart',
component: () => import('./routes/chart/lineChart/'),
}, {
path: '/chart/barChart',
component: () => import('./routes/chart/barChart/'),
}, {
path: '/chart/areaChart',
component: () => import('./routes/chart/areaChart/'),
}, {
path: '/post',
models: () => [import('./models/post')],
component: () => import('./routes/post/'),
},
]
return <Router history={history} routes={routes} />
return (
<ConnectedRouter history={history}>
<App>
<Switch>
<Route exact path="/" render={() => (<Redirect to="/dashboard" />)} />
{
routes.map(({ path, ...dynamics }, key) => (
<Route key={key}
exact
path={path}
component={dynamic({
app,
...dynamics,
})}
/>
))
}
<Route component={error} />
</Switch>
</App>
</ConnectedRouter>
)
}
Routers.propTypes = {
+3 -3
View File
@@ -1,5 +1,5 @@
import React from 'react'
import { DataTable } from '../../../components'
import { DataTable } from 'components'
import { Table, Row, Col, Card, Select } from 'antd'
class DataTablePage extends React.Component {
@@ -35,7 +35,7 @@ class DataTablePage extends React.Component {
dataKey: 'results',
},
columns: [
{ title: 'Name', dataIndex: 'name', render: (text) => `${text.first} ${text.last}` },
{ title: 'Name', dataIndex: 'name', render: text => `${text.first} ${text.last}` },
{ title: 'Phone', dataIndex: 'phone' },
{ title: 'Gender', dataIndex: 'gender' },
],
@@ -53,7 +53,7 @@ class DataTablePage extends React.Component {
dataKey: 'results',
},
columns: [
{ title: 'Name', dataIndex: 'name', render: (text) => `${text.first} ${text.last}` },
{ title: 'Name', dataIndex: 'name', render: text => `${text.first} ${text.last}` },
{ title: 'Phone', dataIndex: 'phone' },
{ title: 'Gender', dataIndex: 'gender' },
],
+3 -3
View File
@@ -1,8 +1,8 @@
import React from 'react'
import { DropOption } from '../../../components'
import { DropOption } from 'components'
import { Table, Row, Col, Card, message } from 'antd'
const DropOptionPage = () => <div className="content-inner">
const DropOptionPage = () => (<div className="content-inner">
<Row gutter={32}>
<Col lg={8} md={12}>
<Card title="默认">
@@ -90,6 +90,6 @@ const DropOptionPage = () => <div className="content-inner">
/>
</Col>
</Row>
</div>
</div>)
export default DropOptionPage
+1 -1
View File
@@ -1,5 +1,5 @@
import React from 'react'
import { Editor } from '../../../components'
import { Editor } from 'components'
import { convertToRaw } from 'draft-js'
import { Row, Col, Card } from 'antd'
import draftToHtml from 'draftjs-to-html'
+19 -19
View File
@@ -1,8 +1,8 @@
import React from 'react'
import { Iconfont } from '../../../components'
import { Iconfont } from 'components'
import { Table, Row, Col, Icon } from 'antd'
import styles from './index.less'
import './emoji.js'
import './emoji'
const colorfulIcons = ['boluo', 'baixiangguo', 'chengzi', 'boluomei', 'caomei', 'dayouzi', 'chelizi', 'fanqie', 'hamigua', 'ganlan',
'juzi', 'heimei', 'huolongguo', 'hongmei', 'lizi', 'lanmei', 'mangguo', 'mihoutao', 'longyan', 'mugua', 'lizi1', 'ningmeng']
@@ -24,35 +24,35 @@ const localRequireSVGIcons = [
require('../../../svg/cute/think.svg'),
]
const IcoPage = () => <div className="content-inner">
const IcoPage = () => (<div className="content-inner">
<Icon type="star-oo" />
<h2 style={{ margin: '16px 0' }}>Colorful Icon</h2>
<ul className={styles.list}>
{colorfulIcons.map(item => <li key={item}>
{colorfulIcons.map(item => (<li key={item}>
<Iconfont className={styles.icon} colorful type={item} />
<span className={styles.name}>{item}</span>
</li>)}
</li>))}
</ul>
<h2 style={{ margin: '16px 0' }}>Flat Icon</h2>
<ul className={styles.list}>
{flatIcons.map(item => <li key={item}>
{flatIcons.map(item => (<li key={item}>
<Iconfont className={styles.icon} type={item} />
<span className={styles.name}>{item}</span>
</li>)}
</li>))}
</ul>
<h2 style={{ margin: '16px 0' }}>Local SVG</h2>
<ul className={styles.list}>
{localSVGIcons.map(item => <li key={item}>
{localSVGIcons.map(item => (<li key={item}>
<Iconfont className={styles.icon} colorful type={item} />
<span className={styles.name}>{item}</span>
</li>)}
</li>))}
</ul>
<h2 style={{ margin: '16px 0' }}>Local Require SVG</h2>
<ul className={styles.list}>
{localRequireSVGIcons.map(item => <li key={item}>
<Iconfont className={styles.icon} colorful type={item} />
<span className={styles.name}>{item}</span>
</li>)}
{localRequireSVGIcons.map(item => (<li key={item.default.id}>
<Iconfont className={styles.icon} colorful type={item.default.id} />
<span className={styles.name}>{item.default.id}</span>
</li>))}
</ul>
<h2 style={{ margin: '16px 0' }}>API</h2>
<Row>
@@ -100,17 +100,17 @@ const IcoPage = () => <div className="content-inner">
<div style={{ margin: '16px 0', lineHeight: 2 }}>
<p>
<a href="http://www.iconfont.cn/user/detail?uid=116813">何阿酥</a> colorful fruit icon
<a href="http://www.iconfont.cn/collections/detail?cid=4014" target="_blank"> http://www.iconfont.cn/collections/detail?cid=4014</a>
<a href="http://www.iconfont.cn/collections/detail?cid=4014" target="_blank" rel="noopener noreferrer"> http://www.iconfont.cn/collections/detail?cid=4014</a>
</p>
<p>
<a href="http://www.iconfont.cn/user/detail?uid=496384">ColinXu</a> colorful 'tsundere' emoji icon
<a href="http://www.iconfont.cn/collections/detail?cid=4116" target="_blank"> http://www.iconfont.cn/collections/detail?cid=4116</a>
<a href="http://www.iconfont.cn/user/detail?uid=496384">ColinXu</a> colorful &apos;tsundere&apos; emoji icon
<a href="http://www.iconfont.cn/collections/detail?cid=4116" target="_blank" rel="noopener noreferrer"> http://www.iconfont.cn/collections/detail?cid=4116</a>
</p>
<p>
<a href="http://www.iconfont.cn/user/detail?uid=116813">咕噜小莫莫</a> colorful 'face cute' emoji icon
<a href="http://www.iconfont.cn/collections/detail?cid=4268" target="_blank"> http://www.iconfont.cn/collections/detail?cid=4268</a>
<a href="http://www.iconfont.cn/user/detail?uid=116813">咕噜小莫莫</a> colorful &apos;face cute&apos; emoji icon
<a href="http://www.iconfont.cn/collections/detail?cid=4268" target="_blank" rel="noopener noreferrer"> http://www.iconfont.cn/collections/detail?cid=4268</a>
</p>
</div>
</div>
</div>)
export default IcoPage
+3 -3
View File
@@ -1,5 +1,5 @@
import React from 'react'
import { layer } from '../../../components'
import { layer } from 'components'
import { Table, Row, Col, Button, Card } from 'antd'
let Enum = {
@@ -26,7 +26,7 @@ const IcoPage = () => {
<h2 style={{ margin: '16px 0' }}>API</h2>
<div style={{ margin: '16px 0' }}>
<h2 style={{ margin: '4px 0' }}>layer.open(config)</h2>
config对象与<a href="https://ant.design/components/modal-cn/#API" target="_blank">Modal</a>的参数基本一致,config属性如下。
config对象与<a href="https://ant.design/components/modal-cn/#API" target="_blank" rel="noopener noreferrer">Modal</a>的参数基本一致,config属性如下。
注意:1.visible属性一般不需要设置;2.afterClose无效,layer.close()可代替;3.layer.open()返回一个唯一的layer Id
</div>
<Row>
@@ -69,7 +69,7 @@ const IcoPage = () => {
},
{
props: 'confirmLoading',
desciption: '确定按钮 loading ',
desciption: '确定按钮 loading',
type: 'boolean',
default: '无',
},
+3 -3
View File
@@ -1,8 +1,8 @@
import React from 'react'
import { Search } from '../../../components'
import { Search } from 'components'
import { Table, Row, Col, Card } from 'antd'
const SearchPage = () => <div className="content-inner">
const SearchPage = () => (<div className="content-inner">
<Row gutter={32}>
<Col lg={8} md={12}>
<Card title="默认">
@@ -93,6 +93,6 @@ const SearchPage = () => <div className="content-inner">
/>
</Col>
</Row>
</div>
</div>)
export default SearchPage
+31 -20
View File
@@ -1,19 +1,29 @@
/* global window */
import React from 'react'
import NProgress from 'nprogress'
import PropTypes from 'prop-types'
import pathToRegexp from 'path-to-regexp'
import { connect } from 'dva'
import { Layout } from '../components'
import { classnames, config, menu } from '../utils'
import { Layout, Loader } from 'components'
import { classnames, config } from 'utils'
import { Helmet } from 'react-helmet'
import { withRouter } from 'dva/router'
import '../themes/index.less'
import './app.less'
import NProgress from 'nprogress'
const { prefix } = config
import Error from './error'
const { prefix, openPages } = config
const { Header, Bread, Footer, Sider, styles } = Layout
let lastHref
const App = ({ children, location, dispatch, app, loading }) => {
const { user, siderFold, darkTheme, isNavbar, menuPopoverVisible, navOpenKeys } = app
const App = ({ children, dispatch, app, loading, location }) => {
const { user, siderFold, darkTheme, isNavbar, menuPopoverVisible, navOpenKeys, menu, permissions } = app
let { pathname } = location
pathname = pathname.startsWith('/') ? pathname : `/${pathname}`
const { iconFontJS, iconFontCSS, logo } = config
const current = menu.filter(item => pathToRegexp(item.route || '').exec(pathname))
const hasPermission = current.length ? permissions.visit.includes(current[0].id) : false
const href = window.location.href
if (lastHref !== href) {
@@ -27,8 +37,8 @@ const App = ({ children, location, dispatch, app, loading }) => {
const headerProps = {
menu,
user,
siderFold,
location,
siderFold,
isNavbar,
menuPopoverVisible,
navOpenKeys,
@@ -48,48 +58,49 @@ const App = ({ children, location, dispatch, app, loading }) => {
const siderProps = {
menu,
location,
siderFold,
darkTheme,
location,
navOpenKeys,
changeTheme () {
dispatch({ type: 'app/switchTheme' })
},
changeOpenKeys (openKeys) {
localStorage.setItem(`${prefix}navOpenKeys`, JSON.stringify(openKeys))
window.localStorage.setItem(`${prefix}navOpenKeys`, JSON.stringify(openKeys))
dispatch({ type: 'app/handleNavOpenKeys', payload: { navOpenKeys: openKeys } })
},
}
const breadProps = {
menu,
location,
}
if (config.openPages && config.openPages.indexOf(location.pathname) > -1) {
return <div>{children}</div>
if (openPages && openPages.includes(pathname)) {
return (<div>
<Loader fullScreen spinning={loading.effects['app/query']} />
{children}
</div>)
}
const { iconFontJS, iconFontCSS, logo } = config
return (
<div>
<Loader fullScreen spinning={loading.effects['app/query']} />
<Helmet>
<title>ANTD ADMIN</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href={logo} type="image/x-icon" />
{iconFontJS && <script src={iconFontJS}></script>}
{iconFontJS && <script src={iconFontJS} />}
{iconFontCSS && <link rel="stylesheet" href={iconFontCSS} />}
</Helmet>
<div className={classnames(styles.layout, { [styles.fold]: isNavbar ? false : siderFold }, { [styles.withnavbar]: isNavbar })}>
{!isNavbar ? <aside className={classnames(styles.sider, { [styles.light]: !darkTheme })}>
<Sider {...siderProps} />
{siderProps.menu.length === 0 ? null : <Sider {...siderProps} />}
</aside> : ''}
<div className={styles.main}>
<Header {...headerProps} />
<Bread {...breadProps} location={location} />
<Bread {...breadProps} />
<div className={styles.container}>
<div className={styles.content}>
{children}
{hasPermission ? children : <Error />}
</div>
</div>
<Footer />
@@ -107,4 +118,4 @@ App.propTypes = {
loading: PropTypes.object,
}
export default connect(({ app, loading }) => ({ app, loading }))(App)
export default withRouter(connect(({ app, loading }) => ({ app, loading }))(App))
+72 -72
View File
@@ -1,72 +1,72 @@
@import "../themes/vars.less";
:global {
#nprogress {
pointer-events: none;
.bar {
background: @primary-color;
position: fixed;
z-index: 1024;
top: 0;
left: 0;
right: 0;
width: 100%;
height: 2px;
}
.peg {
display: block;
position: absolute;
right: 0;
width: 100px;
height: 100%;
box-shadow: 0 0 10px @primary-color,0 0 5px @primary-color;
opacity: 1.0;
transform: rotate(3deg) translate(0px,-4px);
}
.spinner {
display: block;
position: fixed;
z-index: 1031;
top: 15px;
right: 15px;
}
.spinner-icon {
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-top-color: @primary-color;
border-left-color: @primary-color;
border-radius: 50%;
:local {
animation: nprogress-spinner 400ms linear infinite;
}
}
}
.nprogress-custom-parent {
overflow: hidden;
position: relative;
#nprogress {
.bar,
.spinner {
position: absolute;
}
}
}
}
@keyframes nprogress-spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@import "~themes/vars";
:global {
#nprogress {
pointer-events: none;
.bar {
background: @primary-color;
position: fixed;
z-index: 1024;
top: 0;
left: 0;
right: 0;
width: 100%;
height: 2px;
}
.peg {
display: block;
position: absolute;
right: 0;
width: 100px;
height: 100%;
box-shadow: 0 0 10px @primary-color,0 0 5px @primary-color;
opacity: 1.0;
transform: rotate(3deg) translate(0px,-4px);
}
.spinner {
display: block;
position: fixed;
z-index: 1031;
top: 15px;
right: 15px;
}
.spinner-icon {
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-top-color: @primary-color;
border-left-color: @primary-color;
border-radius: 50%;
:local {
animation: nprogress-spinner 400ms linear infinite;
}
}
}
.nprogress-custom-parent {
overflow: hidden;
position: relative;
#nprogress {
.bar,
.spinner {
position: absolute;
}
}
}
}
@keyframes nprogress-spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
+4 -4
View File
@@ -1,16 +1,16 @@
import React from 'react'
import PropTypes from 'prop-types'
import styles from './Container.less'
import { ResponsiveContainer } from 'recharts'
import styles from './Container.less'
const Container = ({ children, ratio = 5 / 2, minHeight = 250, maxHeight = 350 }) => <div className={styles.container} style={{ minHeight, maxHeight }}>
<div style={{ marginTop: `${100 / ratio}%` || '100%' }}></div>
const Container = ({ children, ratio = 5 / 2, minHeight = 250, maxHeight = 350 }) => (<div className={styles.container} style={{ minHeight, maxHeight }}>
<div style={{ marginTop: `${100 / ratio}%` || '100%' }} />
<div className={styles.content} style={{ minHeight, maxHeight }}>
<ResponsiveContainer>
{children}
</ResponsiveContainer>
</div>
</div>
</div>)
Container.propTypes = {
children: PropTypes.element.isRequired,
+48 -34
View File
@@ -1,7 +1,6 @@
import React from 'react'
import { Row, Col, Card, Button } from 'antd'
import Container from '../Container'
import * as d3 from 'd3-shape'
import { Row, Col, Card, Button } from 'antd'
import {
AreaChart,
Area,
@@ -10,6 +9,7 @@ import {
CartesianGrid,
Tooltip,
} from 'recharts'
import Container from '../Container'
const data = [
{
@@ -135,12 +135,14 @@ const colProps = {
const SimpleAreaChart = () => (
<Container>
<AreaChart data={data} margin={{
top: 10,
right: 30,
left: 0,
bottom: 0,
}}>
<AreaChart data={data}
margin={{
top: 10,
right: 30,
left: 0,
bottom: 0,
}}
>
<XAxis dataKey="name" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
@@ -152,12 +154,14 @@ const SimpleAreaChart = () => (
const StackedAreaChart = () => (
<Container>
<AreaChart data={mixData} margin={{
top: 10,
right: 30,
left: 0,
bottom: 0,
}}>
<AreaChart data={mixData}
margin={{
top: 10,
right: 30,
left: 0,
bottom: 0,
}}
>
<XAxis dataKey="name" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
@@ -192,9 +196,11 @@ const renderTooltipContent = (o) => {
<p className="total">{`${label} (Total: ${total})`}</p>
<ul className="list">
{payload.map((entry, index) => (
<li key={`item-${index}`} style={{
color: entry.color,
}}>
<li key={`item-${index}`}
style={{
color: entry.color,
}}
>
{`${entry.name}: ${entry.value}(${getPercent(entry.value, total)})`}
</li>
))}
@@ -205,12 +211,15 @@ const renderTooltipContent = (o) => {
const PercentAreaChart = () => (
<Container>
<AreaChart data={percentData} stackOffset="expand" margin={{
top: 10,
right: 30,
left: 0,
bottom: 0,
}}>
<AreaChart data={percentData}
stackOffset="expand"
margin={{
top: 10,
right: 30,
left: 0,
bottom: 0,
}}
>
<XAxis dataKey="month" />
<YAxis tickFormatter={toPercent} />
<CartesianGrid strokeDasharray="3 3" />
@@ -226,12 +235,14 @@ const PercentAreaChart = () => (
const cardinal = d3.curveCardinal.tension(0.2)
const CardinalAreaChart = () => (
<Container>
<AreaChart data={mixData} margin={{
top: 10,
right: 30,
left: 0,
bottom: 0,
}}>
<AreaChart data={mixData}
margin={{
top: 10,
right: 30,
left: 0,
bottom: 0,
}}
>
<XAxis dataKey="name" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
@@ -244,11 +255,14 @@ const CardinalAreaChart = () => (
const EditorPage = () => (
<div className="content-inner">
<Button type="primary" size="large" style={{
position: 'absolute',
right: 0,
top: -48,
}}>
<Button type="primary"
size="large"
style={{
position: 'absolute',
right: 0,
top: -48,
}}
>
<a href="http://recharts.org/#/en-US/examples/TinyBarChart" target="blank">Show More</a>
</Button>
<Row gutter={32}>
+44 -33
View File
@@ -1,7 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Row, Col, Card, Button } from 'antd'
import Container from '../Container'
import {
BarChart,
Bar,
@@ -11,6 +10,7 @@ import {
Tooltip,
Legend,
} from 'recharts'
import Container from '../Container'
const data = [
{
@@ -96,12 +96,14 @@ const colProps = {
const SimpleBarChart = () => (
<Container>
<BarChart data={data} margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}>
<BarChart data={data}
margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}
>
<XAxis dataKey="name" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
@@ -115,12 +117,14 @@ const SimpleBarChart = () => (
const StackedBarChart = () => (
<Container>
<BarChart data={data} margin={{
top: 20,
right: 30,
left: 20,
bottom: 5,
}}>
<BarChart data={data}
margin={{
top: 20,
right: 30,
left: 20,
bottom: 5,
}}
>
<XAxis dataKey="name" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
@@ -134,12 +138,14 @@ const StackedBarChart = () => (
const MixBarChart = () => (
<Container>
<BarChart data={mixData} margin={{
top: 20,
right: 30,
left: 20,
bottom: 5,
}}>
<BarChart data={mixData}
margin={{
top: 20,
right: 30,
left: 20,
bottom: 5,
}}
>
<XAxis dataKey="name" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
@@ -155,8 +161,8 @@ const MixBarChart = () => (
// CustomShapeBarChart
const getPath = (x, y, width, height) => {
return `M${x},${y + height}
C${x + width / 3},${y + height} ${x + width / 2},${y + height / 3} ${x + width / 2}, ${y}
C${x + width / 2},${y + height / 3} ${x + 2 * width / 3},${y + height} ${x + width}, ${y + height}
C${x + (width / 3)},${y + height} ${x + (width / 2)},${y + (height / 3)} ${x + (width / 2)}, ${y}
C${x + (width / 2)},${y + (height / 3)} ${x + (2 * (width / 3))},${y + height} ${x + width}, ${y + height}
Z`
}
@@ -175,27 +181,32 @@ TriangleBar.propTypes = {
const CustomShapeBarChart = () => (
<Container>
<BarChart data={mixData} margin={{
top: 20,
right: 30,
left: 20,
bottom: 5,
}}>
<BarChart data={mixData}
margin={{
top: 20,
right: 30,
left: 20,
bottom: 5,
}}
>
<XAxis dataKey="name" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
<Bar dataKey="female" fill="#8884d8" shape={< TriangleBar />} label />
<Bar dataKey="female" fill="#8884d8" shape={<TriangleBar />} label />
</BarChart>
</Container>
)
const EditorPage = () => (
<div className="content-inner">
<Button type="primary" size="large" style={{
position: 'absolute',
right: 0,
top: -48,
}}>
<Button type="primary"
size="large"
style={{
position: 'absolute',
right: 0,
top: -48,
}}
>
<a href="http://recharts.org/#/en-US/examples/TinyBarChart" target="blank">Show More</a>
</Button>
<Row gutter={32}>
+56 -34
View File
@@ -1,7 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Row, Col, Card, Button } from 'antd'
import Container from '../Container'
import {
LineChart,
Line,
@@ -11,6 +10,7 @@ import {
Tooltip,
Legend,
} from 'recharts'
import Container from '../Container'
const data = [
{
@@ -58,20 +58,26 @@ const colProps = {
const SimpleLineChart = () => (
<Container>
<LineChart data={data} margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}>
<LineChart data={data}
margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}
>
<XAxis dataKey="name" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="pv" stroke="#8884d8" activeDot={{
r: 8,
}} />
<Line type="monotone"
dataKey="pv"
stroke="#8884d8"
activeDot={{
r: 8,
}}
/>
<Line type="monotone" dataKey="uv" stroke="#82ca9d" />
</LineChart>
</Container>
@@ -79,12 +85,17 @@ const SimpleLineChart = () => (
const VerticalLineChart = () => (
<Container>
<LineChart layout="vertical" width={600} height={300} data={data} margin={{
top: 20,
right: 30,
left: 20,
bottom: 5,
}}>
<LineChart layout="vertical"
width={600}
height={300}
data={data}
margin={{
top: 20,
right: 30,
left: 20,
bottom: 5,
}}
>
<XAxis type="number" />
<YAxis dataKey="name" type="category" />
<CartesianGrid strokeDasharray="3 3" />
@@ -98,12 +109,16 @@ const VerticalLineChart = () => (
const DashedLineChart = () => (
<Container>
<LineChart width={600} height={300} data={data} margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}>
<LineChart width={600}
height={300}
data={data}
margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}
>
<XAxis dataKey="name" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
@@ -140,18 +155,22 @@ CustomizedDot.propTypes = {
const CustomizedDotLineChart = () => (
<Container>
<LineChart width={600} height={300} data={data} margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}>
<LineChart width={600}
height={300}
data={data}
margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}
>
<XAxis dataKey="name" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="pv" stroke="#8884d8" dot={< CustomizedDot />} />
<Line type="monotone" dataKey="pv" stroke="#8884d8" dot={<CustomizedDot />} />
<Line type="monotone" dataKey="uv" stroke="#82ca9d" />
</LineChart>
</Container>
@@ -159,11 +178,14 @@ const CustomizedDotLineChart = () => (
const EditorPage = () => (
<div className="content-inner">
<Button type="primary" size="large" style={{
position: 'absolute',
right: 0,
top: -48,
}}>
<Button type="primary"
size="large"
style={{
position: 'absolute',
right: 0,
top: -48,
}}
>
<a href="http://recharts.org/#/en-US/examples/TinyBarChart" target="blank">Show More</a>
</Button>
<Row gutter={32}>
+1 -1
View File
@@ -1,8 +1,8 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Table, Tag } from 'antd'
import { color } from 'utils'
import styles from './browser.less'
import { color } from '../../../utils'
const status = {
1: {
+3 -3
View File
@@ -1,8 +1,8 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Table, Tag } from 'antd'
import { color } from 'utils'
import styles from './comments.less'
import { color } from '../../../utils'
const status = {
1: {
@@ -30,14 +30,14 @@ function Comments ({ data }) {
}, {
title: 'content',
dataIndex: 'content',
render: (text, it) => <div>
render: (text, it) => (<div>
<h5 className={styles.name}>{it.name}</h5>
<p className={styles.content}>{it.content}</p>
<div className={styles.daterow}>
<Tag color={status[it.status].color}>{status[it.status].text}</Tag>
<span className={styles.date}>{it.date}</span>
</div>
</div>,
</div>),
},
]
return (
+43 -43
View File
@@ -1,43 +1,43 @@
@import "../../../themes/vars.less";
.comments {
:global .ant-table-thead > tr > th {
background: #fff;
border-bottom: solid 1px @border-color-base;
}
.avatar {
width: 48px;
height: 48px;
background-position: center;
background-size: cover;
border-radius: 50%;
background: #f8f8f8;
display: inline-block;
}
.content {
text-align: left;
color: #757575;
}
.date {
color: #a3a3a3;
line-height: 30px;
}
.daterow {
display: flex;
justify-content: space-between;
}
.name {
font-size: 14px;
color: #474747;
text-align: left;
}
.avatarcolumn {
vertical-align: top;
}
}
@import "~themes/vars";
.comments {
:global .ant-table-thead > tr > th {
background: #fff;
border-bottom: solid 1px @border-color-base;
}
.avatar {
width: 48px;
height: 48px;
background-position: center;
background-size: cover;
border-radius: 50%;
background: #f8f8f8;
display: inline-block;
}
.content {
text-align: left;
color: #757575;
}
.date {
color: #a3a3a3;
line-height: 30px;
}
.daterow {
display: flex;
justify-content: space-between;
}
.name {
font-size: 14px;
color: #474747;
text-align: left;
}
.avatarcolumn {
vertical-align: top;
}
}
+4 -4
View File
@@ -1,9 +1,9 @@
import React from 'react'
import PropTypes from 'prop-types'
import styles from './completed.less'
import classnames from 'classnames'
import { color } from '../../../utils'
import { color } from 'utils'
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'
import styles from './completed.less'
function Completed ({ data }) {
return (
@@ -12,7 +12,7 @@ function Completed ({ data }) {
<ResponsiveContainer minHeight={360}>
<AreaChart data={data}>
<Legend verticalAlign="top"
content={prop => {
content={(prop) => {
const { payload } = prop
return (<ul className={classnames({ [styles.legend]: true, clearfix: true })}>
{payload.map((item, key) => <li key={key}><span className={styles.radiusdot} style={{ background: item.color }} />{item.value}</li>)}
@@ -24,7 +24,7 @@ function Completed ({ data }) {
<CartesianGrid vertical={false} stroke={color.borderBase} strokeDasharray="3 3" />
<Tooltip
wrapperStyle={{ border: 'none', boxShadow: '4px 4px 40px rgba(0, 0, 0, 0.05)' }}
content={content => {
content={(content) => {
const list = content.payload.map((item, key) => <li key={key} className={styles.tipitem}><span className={styles.radiusdot} style={{ background: item.color }} />{`${item.name}:${item.value}`}</li>)
return <div className={styles.tooltip}><p className={styles.tiptitle}>{content.label}</p><ul>{list}</ul></div>
}}
+50 -50
View File
@@ -1,50 +1,50 @@
@import "../../../themes/vars.less";
.sales {
.title {
margin-left: 32px;
font-size: 16px;
}
}
.radiusdot {
width: 12px;
height: 12px;
margin-right: 8px;
border-radius: 50%;
display: inline-block;
}
.legend {
color: @dark-half;
text-align: right;
color: #999;
font-size: 14px;
li {
height: 48px;
line-height: 48px;
display: inline-block;
&+li {
margin-left: 24px;
}
}
}
.tooltip {
background: #fff;
padding: 20px;
font-size: 14px;
.tiptitle {
font-weight: 700;
font-size: 16px;
margin-bottom: 8px;
}
.tipitem {
height: 32px;
line-height: 32px;
}
}
@import "~themes/vars";
.sales {
.title {
margin-left: 32px;
font-size: 16px;
}
}
.radiusdot {
width: 12px;
height: 12px;
margin-right: 8px;
border-radius: 50%;
display: inline-block;
}
.legend {
color: @dark-half;
text-align: right;
color: #999;
font-size: 14px;
li {
height: 48px;
line-height: 48px;
display: inline-block;
&+li {
margin-left: 24px;
}
}
}
.tooltip {
background: #fff;
padding: 20px;
font-size: 14px;
.tiptitle {
font-weight: 700;
font-size: 16px;
margin-bottom: 8px;
}
.tipitem {
height: 32px;
line-height: 32px;
}
}
+2 -2
View File
@@ -1,9 +1,9 @@
import React from 'react'
import PropTypes from 'prop-types'
import styles from './cpu.less'
import { color } from '../../../utils'
import { color } from 'utils'
import CountUp from 'react-countup'
import { LineChart, Line, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from 'recharts'
import styles from './cpu.less'
const countUpProps = {
start: 0,
+32 -32
View File
@@ -1,32 +1,32 @@
@import "../../../themes/vars.less";
.numberCard {
padding: 32px;
margin-bottom: 24px;
cursor: pointer;
.iconWarp {
font-size: 54px;
float: left;
}
.content {
width: 100%;
padding-left: 78px;
.title {
line-height: 16px;
font-size: 16px;
margin-bottom: 8px;
height: 16px;
.text-overflow();
}
.number {
line-height: 32px;
font-size: 24px;
height: 32px;
.text-overflow();
}
}
}
@import "~themes/vars";
.numberCard {
padding: 32px;
margin-bottom: 24px;
cursor: pointer;
.iconWarp {
font-size: 54px;
float: left;
}
.content {
width: 100%;
padding-left: 78px;
.title {
line-height: 16px;
font-size: 16px;
margin-bottom: 8px;
height: 16px;
.text-overflow();
}
.number {
line-height: 32px;
font-size: 24px;
height: 32px;
.text-overflow();
}
}
}
+52 -52
View File
@@ -1,52 +1,52 @@
@import "../../../themes/vars.less";
.quote {
color: #fff;
height: 100%;
width: 100%;
padding: 24px;
font-size: 16px;
font-weight: 700;
.inner {
text-overflow: ellipsis;
word-wrap: normal;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 4;
overflow: hidden;
text-indent: 24px;
}
.footer {
position: relative;
margin-top: 14px;
.description {
width: 100%;
p {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 64px;
text-align: right;
&:last-child {
font-weight: 100;
}
}
}
.avatar {
width: 48px;
height: 48px;
background-position: center;
background-size: cover;
border-radius: 50%;
position: absolute;
right: 0;
top: 0;
}
}
}
@import "~themes/vars";
.quote {
color: #fff;
height: 100%;
width: 100%;
padding: 24px;
font-size: 16px;
font-weight: 700;
.inner {
text-overflow: ellipsis;
word-wrap: normal;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 4;
overflow: hidden;
text-indent: 24px;
}
.footer {
position: relative;
margin-top: 14px;
.description {
width: 100%;
p {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 64px;
text-align: right;
&:last-child {
font-weight: 100;
}
}
}
.avatar {
width: 48px;
height: 48px;
background-position: center;
background-size: cover;
border-radius: 50%;
position: absolute;
right: 0;
top: 0;
}
}
}
@@ -1,8 +1,8 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Table, Tag } from 'antd'
import { color } from 'utils'
import styles from './recentSales.less'
import { color } from '../../../utils'
const status = {
1: {
@@ -1,8 +1,8 @@
@import "../../../themes/vars.less";
.recentsales {
:global .ant-table-thead > tr > th {
background: #fff;
border-bottom: solid 1px @border-color-base;
}
}
@import "~themes/vars";
.recentsales {
:global .ant-table-thead > tr > th {
background: #fff;
border-bottom: solid 1px @border-color-base;
}
}
+4 -4
View File
@@ -1,9 +1,9 @@
import React from 'react'
import PropTypes from 'prop-types'
import styles from './sales.less'
import classnames from 'classnames'
import { color } from '../../../utils'
import { color } from 'utils'
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'
import styles from './sales.less'
function Sales ({ data }) {
return (
@@ -12,7 +12,7 @@ function Sales ({ data }) {
<ResponsiveContainer minHeight={360}>
<LineChart data={data}>
<Legend verticalAlign="top"
content={prop => {
content={(prop) => {
const { payload } = prop
return (<ul className={classnames({ [styles.legend]: true, clearfix: true })}>
{payload.map((item, key) => <li key={key}><span className={styles.radiusdot} style={{ background: item.color }} />{item.value}</li>)}
@@ -24,7 +24,7 @@ function Sales ({ data }) {
<CartesianGrid vertical={false} stroke={color.borderBase} strokeDasharray="3 3" />
<Tooltip
wrapperStyle={{ border: 'none', boxShadow: '4px 4px 40px rgba(0, 0, 0, 0.05)' }}
content={content => {
content={(content) => {
const list = content.payload.map((item, key) => <li key={key} className={styles.tipitem}><span className={styles.radiusdot} style={{ background: item.color }} />{`${item.name}:${item.value}`}</li>)
return <div className={styles.tooltip}><p className={styles.tiptitle}>{content.label}</p><ul>{list}</ul></div>
}}
+50 -50
View File
@@ -1,50 +1,50 @@
@import "../../../themes/vars.less";
.sales {
.title {
margin-left: 32px;
font-size: 16px;
}
}
.radiusdot {
width: 12px;
height: 12px;
margin-right: 8px;
border-radius: 50%;
display: inline-block;
}
.legend {
color: @dark-half;
text-align: right;
color: #999;
font-size: 14px;
li {
height: 48px;
line-height: 48px;
display: inline-block;
&+li {
margin-left: 24px;
}
}
}
.tooltip {
background: #fff;
padding: 20px;
font-size: 14px;
.tiptitle {
font-weight: 700;
font-size: 16px;
margin-bottom: 8px;
}
.tipitem {
height: 32px;
line-height: 32px;
}
}
@import "~themes/vars";
.sales {
.title {
margin-left: 32px;
font-size: 16px;
}
}
.radiusdot {
width: 12px;
height: 12px;
margin-right: 8px;
border-radius: 50%;
display: inline-block;
}
.legend {
color: @dark-half;
text-align: right;
color: #999;
font-size: 14px;
li {
height: 48px;
line-height: 48px;
display: inline-block;
&+li {
margin-left: 24px;
}
}
}
.tooltip {
background: #fff;
padding: 20px;
font-size: 14px;
.tiptitle {
font-weight: 700;
font-size: 16px;
margin-bottom: 8px;
}
.tipitem {
height: 32px;
line-height: 32px;
}
}
+3 -2
View File
@@ -1,9 +1,10 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Button } from 'antd'
import styles from './user.less'
import CountUp from 'react-countup'
import { color } from '../../../utils'
import { color } from 'utils'
import styles from './user.less'
const countUpProps = {
start: 0,
duration: 2.75,
+97 -97
View File
@@ -1,97 +1,97 @@
@import "../../../themes/vars.less";
.user {
.header {
display: flex;
justify-content: center;
text-align: center;
color: #fff;
height: 200px;
background-image: ~ "url(./user-background.png)";
background-repeat: no-repeat;
background-size: cover;
align-items: center;
.headerinner {
z-index: 2;
}
&::after {
content: '';
background: @purple;
position: absolute;
width: 100%;
height: 200px;
left: 0;
top: 0;
opacity: 0.4;
z-index: 1;
}
.avatar {
width: 64px;
height: 64px;
background-position: center;
background-size: cover;
border-radius: 50%;
display: inline-block;
}
.name {
font-size: 16px;
}
}
.number {
display: flex;
height: 116px;
justify-content: space-between;
border-bottom: solid 1px #f5f5f5;
.item {
text-align: center;
height: 116px;
width: 100%;
position: relative;
padding: 30px 0;
&+.item {
&::before {
content: '';
display: block;
width: 1px;
height: 116px;
position: absolute;
background: #f5f5f5;
top: 0;
}
}
p {
color: #757575;
&:first-child {
font-size: 16px;
}
&:last-child {
font-size: 20px;
font-weight: 700;
}
}
}
}
.footer {
height: 116px;
display: flex;
justify-content: center;
align-items: center;
:global .ant-btn {
color: @purple;
border-color: @purple;
padding: 6px 16px;
}
}
}
@import "~themes/vars";
.user {
.header {
display: flex;
justify-content: center;
text-align: center;
color: #fff;
height: 200px;
background-image: ~ "url(./user-background.png)";
background-repeat: no-repeat;
background-size: cover;
align-items: center;
.headerinner {
z-index: 2;
}
&::after {
content: '';
background: @purple;
position: absolute;
width: 100%;
height: 200px;
left: 0;
top: 0;
opacity: 0.4;
z-index: 1;
}
.avatar {
width: 64px;
height: 64px;
background-position: center;
background-size: cover;
border-radius: 50%;
display: inline-block;
}
.name {
font-size: 16px;
}
}
.number {
display: flex;
height: 116px;
justify-content: space-between;
border-bottom: solid 1px #f5f5f5;
.item {
text-align: center;
height: 116px;
width: 100%;
position: relative;
padding: 30px 0;
&+.item {
&::before {
content: '';
display: block;
width: 1px;
height: 116px;
position: absolute;
background: #f5f5f5;
top: 0;
}
}
p {
color: #757575;
&:first-child {
font-size: 16px;
}
&:last-child {
font-size: 20px;
font-weight: 700;
}
}
}
}
.footer {
height: 116px;
display: flex;
justify-content: center;
align-items: center;
:global .ant-btn {
color: @purple;
border-color: @purple;
padding: 6px 16px;
}
}
}
+20 -13
View File
@@ -1,20 +1,26 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Spin } from 'antd'
import styles from './weather.less'
function Weather ({ city, icon, dateTime, temperature, name }) {
return (<div className={styles.weather}>
<div className={styles.left}>
<div className={styles.icon} style={{
backgroundImage: `url(${icon})`,
}} />
<p>{name}</p>
</div>
<div className={styles.right}>
<h1 className={styles.temperature}>{`${temperature}°`}</h1>
<p className={styles.description}>{city},{dateTime}</p>
</div>
</div>)
function Weather ({ city, icon, dateTime, temperature, name, loading }) {
return (
<Spin spinning={loading}>
<div className={styles.weather}>
<div className={styles.left}>
<div className={styles.icon}
style={{
backgroundImage: `url(${icon})`,
}}
/>
<p>{name}</p>
</div>
<div className={styles.right}>
<h1 className={styles.temperature}>{`${temperature}°`}</h1>
<p className={styles.description}>{city},{dateTime}</p>
</div>
</div>
</Spin>)
}
Weather.propTypes = {
@@ -23,6 +29,7 @@ Weather.propTypes = {
dateTime: PropTypes.string,
temperature: PropTypes.string,
name: PropTypes.string,
loading: PropTypes.bool,
}
export default Weather
+48 -47
View File
@@ -1,47 +1,48 @@
@import "../../../themes/vars.less";
.weather {
color: #fff;
height: 204px;
padding: 24px;
justify-content: space-between;
display: flex;
font-size: 14px;
.left {
display: flex;
flex-direction: column;
width: 64px;
padding-top: 55px;
.icon {
width: 64px;
height: 64px;
background-position: center;
background-size: cover;
}
p {
margin-top: 16px;
}
}
.right {
display: flex;
flex-direction: column;
width: 50%;
.temperature {
font-size: 36px;
text-align: right;
height: 64px;
}
.description {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: right;
}
}
}
@import "~themes/vars";
.weather {
color: #fff;
height: 204px;
padding: 24px;
justify-content: space-between;
display: flex;
font-size: 14px;
.left {
display: flex;
flex-direction: column;
width: 64px;
padding-top: 55px;
.icon {
width: 64px;
height: 64px;
background-position: center;
background-size: contain;
}
p {
margin-top: 16px;
}
}
.right {
display: flex;
flex-direction: column;
width: 50%;
.temperature {
font-size: 36px;
text-align: right;
height: 64px;
color: #fff;
}
.description {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: right;
}
}
}
+83 -69
View File
@@ -2,9 +2,10 @@ import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'dva'
import { Row, Col, Card } from 'antd'
import { color } from 'utils'
import { Page } from 'components'
import { NumberCard, Quote, Sales, Weather, RecentSales, Comments, Completed, Browser, Cpu, User } from './components'
import styles from './index.less'
import { color } from '../../utils'
const bodyStyle = {
bodyStyle: {
@@ -13,82 +14,95 @@ const bodyStyle = {
},
}
function Dashboard ({ dashboard }) {
function Dashboard ({ dashboard, loading }) {
const { weather, sales, quote, numbers, recentSales, comments, completed, browser, cpu, user } = dashboard
const numberCards = numbers.map((item, key) => <Col key={key} lg={6} md={12}>
const numberCards = numbers.map((item, key) => (<Col key={key} lg={6} md={12}>
<NumberCard {...item} />
</Col>)
</Col>))
return (
<Row gutter={24}>
{numberCards}
<Col lg={18} md={24}>
<Card bordered={false} bodyStyle={{
padding: '24px 36px 24px 0',
}}>
<Sales data={sales} />
</Card>
</Col>
<Col lg={6} md={24}>
<Row gutter={24}>
<Col lg={24} md={12}>
<Card bordered={false} className={styles.weather} bodyStyle={{
padding: 0,
height: 204,
background: color.blue,
}}>
<Weather {...weather} />
</Card>
</Col>
<Col lg={24} md={12}>
<Card bordered={false} className={styles.quote} bodyStyle={{
padding: 0,
height: 204,
background: color.peach,
}}>
<Quote {...quote} />
</Card>
</Col>
</Row>
</Col>
<Col lg={12} md={24}>
<Card bordered={false} {...bodyStyle}>
<RecentSales data={recentSales} />
</Card>
</Col>
<Col lg={12} md={24}>
<Card bordered={false} {...bodyStyle}>
<Comments data={comments} />
</Card>
</Col>
<Col lg={24} md={24}>
<Card bordered={false} bodyStyle={{
padding: '24px 36px 24px 0',
}}>
<Completed data={completed} />
</Card>
</Col>
<Col lg={8} md={24}>
<Card bordered={false} {...bodyStyle}>
<Browser data={browser} />
</Card>
</Col>
<Col lg={8} md={24}>
<Card bordered={false} {...bodyStyle}>
<Cpu {...cpu} />
</Card>
</Col>
<Col lg={8} md={24}>
<Card bordered={false} bodyStyle={{ ...bodyStyle.bodyStyle, padding: 0 }}>
<User {...user} />
</Card>
</Col>
</Row>
<Page loading={loading.models.dashboard && sales.length === 0}>
<Row gutter={24}>
{numberCards}
<Col lg={18} md={24}>
<Card bordered={false}
bodyStyle={{
padding: '24px 36px 24px 0',
}}
>
<Sales data={sales} />
</Card>
</Col>
<Col lg={6} md={24}>
<Row gutter={24}>
<Col lg={24} md={12}>
<Card bordered={false}
className={styles.weather}
bodyStyle={{
padding: 0,
height: 204,
background: color.blue,
}}
>
<Weather {...weather} loading={loading.effects['dashboard/queryWeather']} />
</Card>
</Col>
<Col lg={24} md={12}>
<Card bordered={false}
className={styles.quote}
bodyStyle={{
padding: 0,
height: 204,
background: color.peach,
}}
>
<Quote {...quote} />
</Card>
</Col>
</Row>
</Col>
<Col lg={12} md={24}>
<Card bordered={false} {...bodyStyle}>
<RecentSales data={recentSales} />
</Card>
</Col>
<Col lg={12} md={24}>
<Card bordered={false} {...bodyStyle}>
<Comments data={comments} />
</Card>
</Col>
<Col lg={24} md={24}>
<Card bordered={false}
bodyStyle={{
padding: '24px 36px 24px 0',
}}
>
<Completed data={completed} />
</Card>
</Col>
<Col lg={8} md={24}>
<Card bordered={false} {...bodyStyle}>
<Browser data={browser} />
</Card>
</Col>
<Col lg={8} md={24}>
<Card bordered={false} {...bodyStyle}>
<Cpu {...cpu} />
</Card>
</Col>
<Col lg={8} md={24}>
<Card bordered={false} bodyStyle={{ ...bodyStyle.bodyStyle, padding: 0 }}>
<User {...user} />
</Card>
</Col>
</Row>
</Page>
)
}
Dashboard.propTypes = {
dashboard: PropTypes.object,
loading: PropTypes.object,
}
export default connect(({ dashboard }) => ({ dashboard }))(Dashboard)
export default connect(({ dashboard, loading }) => ({ dashboard, loading }))(Dashboard)
+2 -2
View File
@@ -2,11 +2,11 @@ import React from 'react'
import { Icon } from 'antd'
import styles from './index.less'
const Error = () => <div className="content-inner">
const Error = () => (<div className="content-inner">
<div className={styles.error}>
<Icon type="frown-o" />
<h1>404 Not Found</h1>
</div>
</div>
</div>)
export default Error
+5 -7
View File
@@ -2,21 +2,19 @@ import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'dva'
import { Button, Row, Form, Input } from 'antd'
import { config } from '../../utils'
import { config } from 'utils'
import styles from './index.less'
const FormItem = Form.Item
const Login = ({
login,
loading,
dispatch,
form: {
getFieldDecorator,
validateFieldsAndScroll,
},
}) => {
const { loginLoading } = login
function handleOk () {
validateFieldsAndScroll((errors, values) => {
if (errors) {
@@ -52,7 +50,7 @@ const Login = ({
})(<Input size="large" type="password" onPressEnter={handleOk} placeholder="Password" />)}
</FormItem>
<Row>
<Button type="primary" size="large" onClick={handleOk} loading={loginLoading}>
<Button type="primary" size="large" onClick={handleOk} loading={loading.effects.login}>
Sign in
</Button>
<p>
@@ -68,8 +66,8 @@ const Login = ({
Login.propTypes = {
form: PropTypes.object,
login: PropTypes.object,
dispatch: PropTypes.func,
loading: PropTypes.object,
}
export default connect(({ login }) => ({ login }))(Form.create()(Login))
export default connect(({ loading }) => ({ loading }))(Form.create()(Login))
+55
View File
@@ -0,0 +1,55 @@
import React from 'react'
import { Table } from 'antd'
import styles from './List.less'
const List = ({ ...tableProps }) => {
const columns = [
{
title: 'Image',
dataIndex: 'image',
className: styles.image,
width: 64,
render: text => <img alt="Feture" width={26} src={text} />,
}, {
title: 'Title',
dataIndex: 'title',
}, {
title: 'Author',
dataIndex: 'author',
}, {
title: 'Categories',
dataIndex: 'categories',
}, {
title: 'Tags',
dataIndex: 'tags',
}, {
title: 'Visibility',
dataIndex: 'visibility',
}, {
title: 'Comments',
dataIndex: 'comments',
}, {
title: 'Views',
dataIndex: 'views',
}, {
title: 'Date',
dataIndex: 'date',
},
]
return (
<div>
<Table
{...tableProps}
bordered
scroll={{ x: 1200 }}
columns={columns}
simple
className={styles.table}
rowKey={record => record.id}
/>
</div>
)
}
export default List
+16
View File
@@ -0,0 +1,16 @@
.table {
td.image {
line-height: 1;
img {
border-radius: 2px;
}
}
:global {
.ant-table-tbody > tr > td,
.ant-table-thead > tr > th {
height: 60px;
}
}
}
+66
View File
@@ -0,0 +1,66 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'dva'
import { Tabs } from 'antd'
import { routerRedux } from 'dva/router'
import queryString from 'query-string'
import List from './List'
const TabPane = Tabs.TabPane
const EnumPostStatus = {
UNPUBLISH: 1,
PUBLISHED: 2,
}
const Index = ({ post, dispatch, loading, location }) => {
const { list, pagination } = post
const { query = {}, pathname } = location
const listProps = {
pagination,
dataSource: list,
loading: loading.effects['post/query'],
onChange (page) {
dispatch(routerRedux.push({
pathname,
search: queryString.stringify({
...query,
page: page.current,
pageSize: page.pageSize,
}),
}))
},
}
const handleTabClick = (key) => {
dispatch(routerRedux.push({
pathname,
search: queryString.stringify({
status: key,
}),
}))
}
return (<div className="content-inner">
<Tabs activeKey={query.status === String(EnumPostStatus.UNPUBLISH) ? String(EnumPostStatus.UNPUBLISH) : String(EnumPostStatus.PUBLISHED)} onTabClick={handleTabClick}>
<TabPane tab="Publised" key={String(EnumPostStatus.PUBLISHED)}>
<List {...listProps} />
</TabPane>
<TabPane tab="Unpublish" key={String(EnumPostStatus.UNPUBLISH)}>
<List {...listProps} />
</TabPane>
</Tabs>
</div>)
}
Index.propTypes = {
post: PropTypes.object,
loading: PropTypes.object,
location: PropTypes.object,
dispatch: PropTypes.func,
}
export default connect(({ post, loading }) => ({ post, loading }))(Index)
+31 -21
View File
@@ -1,7 +1,6 @@
import React from 'react'
import styles from './index.less'
import Mock from 'mockjs'
import { request, config } from '../../utils'
import { request, config } from 'utils'
import {
Row,
Col,
@@ -10,20 +9,22 @@ import {
Input,
Button,
} from 'antd'
const { api, baseURL } = config
const { dashboard, users, userLogin, user } = api
import styles from './index.less'
const { api } = config
const { dashboard, users, userLogin, user, v1test, v2test } = api
const requestOptions = [
{
url: baseURL + user.replace('/:id', ''),
url: user.replace('/:id', ''),
desc: 'intercept request by mock.js',
},
{
url: baseURL + dashboard,
url: dashboard,
desc: 'intercept request by mock.js',
},
{
url: baseURL + userLogin,
url: userLogin,
method: 'post',
data: {
username: 'guest',
@@ -32,18 +33,18 @@ const requestOptions = [
desc: 'intercept request by mock.js',
},
{
url: baseURL + users,
url: users,
desc: 'intercept request by mock.js',
},
{
url: baseURL + user,
url: user,
desc: 'intercept request by mock.js',
data: Mock.mock({
id: '@id',
}),
},
{
url: baseURL + user.replace('/:id', ''),
url: user.replace('/:id', ''),
desc: 'intercept request by mock.js',
method: 'post',
data: Mock.mock({
@@ -60,7 +61,7 @@ const requestOptions = [
}),
},
{
url: baseURL + user,
url: user,
desc: 'intercept request by mock.js',
method: 'patch',
data: Mock.mock({
@@ -69,7 +70,7 @@ const requestOptions = [
}),
},
{
url: baseURL + user,
url: user,
desc: 'intercept request by mock.js',
method: 'delete',
data: Mock.mock({
@@ -77,7 +78,12 @@ const requestOptions = [
}),
},
{
url: `${baseURL}/test`,
url: v1test,
desc: 'intercept request by mock.js',
method: 'get',
},
{
url: v2test,
desc: 'intercept request by mock.js',
method: 'get',
},
@@ -129,7 +135,7 @@ export default class RequestPage extends React.Component {
const state = this.state
const curretUrl = value.split('?')[0]
const curretMethod = value.split('?')[1]
const currntItem = requestOptions.filter(item => {
const currntItem = requestOptions.filter((item) => {
const { method = 'get' } = item
return curretUrl === item.url && curretMethod === method
})
@@ -149,14 +155,18 @@ export default class RequestPage extends React.Component {
<div className="content-inner">
<Row gutter={32}>
<Col {...colProps}>
<Card title="Request" style={{
overflow: 'visible',
}}>
<Card title="Request"
style={{
overflow: 'visible',
}}
>
<div className={styles.option}>
<Select style={{
width: '100%',
flex: 1,
}} defaultValue={`${method.toLocaleUpperCase()} ${requestOptions[0].url}`}
<Select
style={{
width: '100%',
flex: 1,
}}
defaultValue={`${method.toLocaleUpperCase()} ${requestOptions[0].url}`}
size="large"
onChange={this.handeleURLChange}
>
+2 -2
View File
@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import moment from 'moment'
import { FilterItem } from '../../components'
import { FilterItem } from 'components'
import { Form, Button, Row, Col, DatePicker, Input, Cascader, Switch } from 'antd'
import city from '../../utils/city'
@@ -101,7 +101,7 @@ const Filter = ({
</FilterItem>
</Col>
<Col {...TwoColProps} xl={{ span: 10 }} md={{ span: 24 }} sm={{ span: 24 }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap' }}>
<div >
<Button type="primary" size="large" className="margin-right" onClick={handleSubmit}>Search</Button>
<Button size="large" onClick={handleReset}>Reset</Button>
+12 -9
View File
@@ -1,15 +1,18 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Table, Modal } from 'antd'
import styles from './List.less'
import classnames from 'classnames'
import AnimTableBody from '../../components/DataTable/AnimTableBody'
import { DropOption } from '../../components'
import { Link } from 'dva/router'
import { DropOption } from 'components'
import { Link } from 'react-router-dom'
import queryString from 'query-string'
import AnimTableBody from 'components/DataTable/AnimTableBody'
import styles from './List.less'
const confirm = Modal.confirm
const List = ({ onDeleteItem, onEditItem, isMotion, location, ...tableProps }) => {
location.query = queryString.parse(location.search)
const handleMenuClick = (record, e) => {
if (e.key === '1') {
onEditItem(record)
@@ -30,7 +33,7 @@ const List = ({ onDeleteItem, onEditItem, isMotion, location, ...tableProps }) =
key: 'avatar',
width: 64,
className: styles.avatar,
render: (text) => <img alt={'avatar'} width={24} src={text} />,
render: text => <img alt={'avatar'} width={24} src={text} />,
}, {
title: 'Name',
dataIndex: 'name',
@@ -48,9 +51,9 @@ const List = ({ onDeleteItem, onEditItem, isMotion, location, ...tableProps }) =
title: 'Gender',
dataIndex: 'isMale',
key: 'isMale',
render: (text) => <span>{text
? 'Male'
: 'Female'}</span>,
render: text => (<span>{text
? 'Male'
: 'Female'}</span>),
}, {
title: 'Phone',
dataIndex: 'phone',
@@ -82,7 +85,7 @@ const List = ({ onDeleteItem, onEditItem, isMotion, location, ...tableProps }) =
current: tableProps.pagination.current,
}
const getBodyWrapper = body => { return isMotion ? <AnimTableBody {...getBodyWrapperProps} body={body} /> : body }
const getBodyWrapper = (body) => { return isMotion ? <AnimTableBody {...getBodyWrapperProps} body={body} /> : body }
return (
<div>
-1
View File
@@ -23,7 +23,6 @@ const Detail = ({ userDetail }) => {
Detail.propTypes = {
userDetail: PropTypes.object,
loading: PropTypes.bool,
}
export default connect(({ userDetail, loading }) => ({ userDetail, loading: loading.models.userDetail }))(Detail)
+21 -17
View File
@@ -3,11 +3,15 @@ import PropTypes from 'prop-types'
import { routerRedux } from 'dva/router'
import { connect } from 'dva'
import { Row, Col, Button, Popconfirm } from 'antd'
import { Page } from 'components'
import queryString from 'query-string'
import List from './List'
import Filter from './Filter'
import Modal from './Modal'
const User = ({ location, dispatch, user, loading }) => {
location.query = queryString.parse(location.search)
const { list, pagination, currentItem, modalVisible, modalType, isMotion, selectedRowKeys } = user
const { pageSize } = pagination
@@ -41,11 +45,11 @@ const User = ({ location, dispatch, user, loading }) => {
const { query, pathname } = location
dispatch(routerRedux.push({
pathname,
query: {
search: queryString.stringify({
...query,
page: page.current,
pageSize: page.pageSize,
},
}),
}))
},
onDeleteItem (id) {
@@ -84,20 +88,20 @@ const User = ({ location, dispatch, user, loading }) => {
onFilterChange (value) {
dispatch(routerRedux.push({
pathname: location.pathname,
query: {
search: queryString.stringify({
...value,
page: 1,
pageSize,
},
}),
}))
},
onSearch (fieldsValue) {
fieldsValue.keyword.length ? dispatch(routerRedux.push({
pathname: '/user',
query: {
search: queryString.stringify({
field: fieldsValue.field,
keyword: fieldsValue.keyword,
},
}),
})) : dispatch(routerRedux.push({
pathname: '/user',
}))
@@ -125,22 +129,22 @@ const User = ({ location, dispatch, user, loading }) => {
}
return (
<div className="content-inner">
<Page inner>
<Filter {...filterProps} />
{
selectedRowKeys.length > 0 &&
<Row style={{ marginBottom: 24, textAlign: 'right', fontSize: 13 }}>
<Col>
{`Selected ${selectedRowKeys.length} items `}
<Popconfirm title={'Are you sure delete these items?'} placement="left" onConfirm={handleDeleteItems}>
<Button type="primary" size="large" style={{ marginLeft: 8 }}>Remove</Button>
</Popconfirm>
</Col>
</Row>
selectedRowKeys.length > 0 &&
<Row style={{ marginBottom: 24, textAlign: 'right', fontSize: 13 }}>
<Col>
{`Selected ${selectedRowKeys.length} items `}
<Popconfirm title={'Are you sure delete these items?'} placement="left" onConfirm={handleDeleteItems}>
<Button type="primary" size="large" style={{ marginLeft: 8 }}>Remove</Button>
</Popconfirm>
</Col>
</Row>
}
<List {...listProps} />
{modalVisible && <Modal {...modalProps} />}
</div>
</Page>
)
}
+2 -1
View File
@@ -1,4 +1,5 @@
import { request, config } from '../utils'
import { request, config } from 'utils'
const { api } = config
const { user, userLogout, userLogin } = api
+2 -15
View File
@@ -1,21 +1,8 @@
import { request, config } from '../utils'
import { request, config } from 'utils'
const { api } = config
const { dashboard } = api
export async function myCity (params) {
return request({
url: 'http://www.zuimeitianqi.com/zuimei/myCity',
data: params,
})
}
export async function queryWeather (params) {
return request({
url: 'http://www.zuimeitianqi.com/zuimei/queryWeather',
data: params,
})
}
export async function query (params) {
return request({
url: dashboard,
+2 -1
View File
@@ -1,4 +1,5 @@
import { request, config } from '../utils'
import { request, config } from 'utils'
const { api } = config
const { userLogin } = api
+12
View File
@@ -0,0 +1,12 @@
import { request, config } from 'utils'
const { api } = config
const { menus } = api
export async function query (params) {
return request({
url: menus,
method: 'get',
data: params,
})
}
+12
View File
@@ -0,0 +1,12 @@
import { request, config } from 'utils'
const { api } = config
const { posts } = api
export async function query (params) {
return request({
url: posts,
method: 'get',
data: params,
})
}
+2 -1
View File
@@ -1,4 +1,5 @@
import { request, config } from '../utils'
import { request, config } from 'utils'
const { api } = config
const { user } = api

Some files were not shown because too many files have changed in this diff Show More