Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
587b1f6b9d | ||
|
|
d94580a253 | ||
|
|
531e3c0fcc | ||
|
|
d68de7a30c | ||
|
|
45cb70f006 | ||
|
|
af85384f11 | ||
|
|
208539134f | ||
|
|
a3f41e3d64 | ||
|
|
3855c402c9 | ||
|
|
6a58d8434b | ||
|
|
d26a92ce79 | ||
|
|
0d20717c3c | ||
|
|
a0d81dcda9 | ||
|
|
4490af8553 | ||
|
|
75cc86152d | ||
|
|
3d9f263330 | ||
|
|
70b5d05f1a | ||
|
|
4da23b9133 | ||
|
|
da1c52e82b | ||
|
|
6ae39bfd55 | ||
|
|
d86f0ebd78 | ||
|
|
ecbd8bf51e | ||
|
|
0c9fabe2b4 | ||
|
|
0f27528d2f | ||
|
|
a36d44d402 | ||
|
|
37deddc911 | ||
|
|
52ee780e40 | ||
|
|
057853cfde | ||
|
|
c0222c3957 | ||
|
|
00d8fc5ea5 | ||
|
|
1e08d7adaa | ||
|
|
b2b0aa872f | ||
|
|
a2b4ee4047 | ||
|
|
c6b477c993 | ||
|
|
548d7a8ddb | ||
|
|
755c1250e5 | ||
|
|
c5ebb91980 | ||
|
|
ae12492887 | ||
|
|
28a5863701 | ||
|
|
bf65aa5478 | ||
|
|
acd3fe8337 | ||
|
|
3ad5a6cfb6 | ||
|
|
c278c8f6f8 | ||
|
|
71c75e15ff | ||
|
|
ec8db0e0fb | ||
|
|
2a3587bae5 | ||
|
|
a33772cbbc | ||
|
|
89c58b2f98 | ||
|
|
b31bd32ca2 | ||
|
|
f113fbb238 | ||
|
|
a320c77ecb | ||
|
|
ce1d957212 | ||
|
|
7f40ac6a60 | ||
|
|
3a01626df9 | ||
|
|
6df97916fa | ||
|
|
7f091d7642 | ||
|
|
d8f222f312 | ||
|
|
3378510166 | ||
|
|
edf1158351 | ||
|
|
e4bd4e9aa0 | ||
|
|
5904eae0c9 | ||
|
|
7f26153472 | ||
|
|
4b9e142aa8 | ||
|
|
2679361559 | ||
|
|
2d246655e0 | ||
|
|
04b147d33f | ||
|
|
81801983a6 | ||
|
|
e484b89518 | ||
|
|
bfff28411d | ||
|
|
ae97c9d18a | ||
|
|
a063d8be39 | ||
|
|
79eab09941 | ||
|
|
2e3cb5970d | ||
|
|
460a8c906b | ||
|
|
05a86e2f56 | ||
|
|
0295da6326 | ||
|
|
85e12caf12 | ||
|
|
b83c5e7819 | ||
|
|
9cff4e8dfe | ||
|
|
f6021cbaa4 | ||
|
|
6ac7e5e815 | ||
|
|
e63dfc9291 | ||
|
|
81e7d2bdca | ||
|
|
1c46693246 | ||
|
|
4832a720e6 | ||
|
|
54964fabfb | ||
|
|
b60e593f09 | ||
|
|
5359efce36 | ||
|
|
3decccc87d | ||
|
|
d92eecb620 | ||
|
|
b4699e75f1 | ||
|
|
19e07aeb7a | ||
|
|
2c74295843 | ||
|
|
55ef9dfc0e | ||
|
|
255b0a95a7 | ||
|
|
bf260db940 | ||
|
|
3e1e782617 | ||
|
|
a1e8f2ed9b |
@@ -1 +1,2 @@
|
||||
src/**/*-test.js
|
||||
src/public
|
||||
|
||||
@@ -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,23 @@
|
||||
"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,
|
||||
"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 +45,8 @@
|
||||
"jsx": true,
|
||||
"experimentalObjectRestSpread": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": "node"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
dist
|
||||
node_modules
|
||||
yarn.lock
|
||||
npm-debug.log
|
||||
dist
|
||||
node_modules
|
||||
yarn.lock
|
||||
npm-debug.log
|
||||
|
||||
@@ -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 }]
|
||||
entry : 'src/index.js',
|
||||
svgSpriteLoaderDirs : svgSpriteDirs,
|
||||
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"],
|
||||
include: ["dva/router", "dva/saga", "dva/fetch"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,63 @@
|
||||
# Antd Admin
|
||||
|
||||
[](https://github.com/facebook/react)
|
||||
[](https://github.com/ant-design/ant-design)
|
||||
[](https://github.com/dvajs/dva)
|
||||
[](https://github.com/facebook/react)
|
||||
[](https://github.com/ant-design/ant-design)
|
||||
[](https://github.com/dvajs/dva)
|
||||
|
||||
[](https://github.com/zuiidea/antd-admin)
|
||||
[](https://github.com/zuiidea/antd-admin/pulls)
|
||||
[](http://opensource.org/licenses/MIT)
|
||||
[](http://standardjs.com)
|
||||
|
||||
演示地址 <http://zuiidea.github.io/antd-admin/>
|
||||
|
||||
备用地址 <http://47.92.30.98:8000>
|
||||
演示地址 <http://antd-admin.zuiidea.com>
|
||||
|
||||
## 特性
|
||||
|
||||
- 基于[react](https://github.com/facebook/react),[ant-design](https://github.com/ant-design/ant-design),[dva](https://github.com/dvajs/dva),[Mock](https://github.com/nuysoft/Mock) 企业级后台管理系统最佳实践
|
||||
- 基于[Mock](https://github.com/nuysoft/Mock)实现脱离后端独立开发
|
||||
- 基于Antd UI 设计语言,提供后台管理系统常见使用场景
|
||||
- 基于[dva](https://github.com/dvajs/dva)动态加载 Model 和路由,按需加载
|
||||
- 浅度响应式设计
|
||||
- 基于[react](https://github.com/facebook/react),[ant-design](https://github.com/ant-design/ant-design),[dva](https://github.com/dvajs/dva),[Mock](https://github.com/nuysoft/Mock) 企业级后台管理系统最佳实践。
|
||||
- 基于Antd UI 设计语言,提供后台管理系统常见使用场景。
|
||||
- 基于[dva](https://github.com/dvajs/dva)动态加载 Model 和路由,按需加载。
|
||||
- 使用[roadhog](https://github.com/sorrycc/roadhog)本地调试和构建,其中Mock功能实现脱离后端独立开发。
|
||||
- 浅度响应式设计。
|
||||
|
||||
## 更新日志
|
||||
|
||||
### 4.3.0
|
||||
|
||||
`2017-07-30`
|
||||
|
||||
- 更新[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`。
|
||||
|
||||
### 4.2.3
|
||||
|
||||
`2017-07-07`
|
||||
|
||||
- 新增用户按权限访问。[#384](https://github.com/zuiidea/antd-admin/issues/384)
|
||||
|
||||
### 4.2.2
|
||||
|
||||
`2017-06-01`
|
||||
|
||||
- 新增用户管理批量删除。[#320](https://github.com/zuiidea/antd-admin/issues/320)
|
||||
|
||||
### 4.2.1
|
||||
|
||||
`2017-05-12`
|
||||
|
||||
- 新增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)
|
||||
|
||||
### 4.2
|
||||
|
||||
`2017-04-28`
|
||||
|
||||
- 修改user相关API使用`Restful`风格
|
||||
- 增加user页面多条件查询[#266](https://github.com/zuiidea/antd-admin/issues/226)
|
||||
- 修复菜单默认高亮[#201](https://github.com/zuiidea/antd-admin/issues/201)
|
||||
- 修改user相关API使用`Restful`风格。
|
||||
- 增加user页面多条件查询。[#266](https://github.com/zuiidea/antd-admin/issues/226)
|
||||
- 修复菜单默认高亮。[#201](https://github.com/zuiidea/antd-admin/issues/201)
|
||||
|
||||
`2017-04-21`
|
||||
|
||||
- 重写Menu,Bread组件及配置文件,[说明](https://github.com/zuiidea/antd-admin/wiki/%E8%8F%9C%E5%8D%95%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6)
|
||||
|
||||
### 4.1
|
||||
|
||||
`2017-04-14`
|
||||
|
||||
- 升级开发工具为[roadhog](https://github.com/sorrycc/roadhog)
|
||||
- 使用`roadhog`的mock功能
|
||||
- 增强`utils/request.js`跨域处理能力
|
||||
[More Change Log](https://github.com/zuiidea/antd-admin/wiki/Change-Log)
|
||||
|
||||
## 开发构建
|
||||
|
||||
@@ -50,6 +66,7 @@
|
||||
```bash
|
||||
├── /dist/ # 项目输出目录
|
||||
├── /src/ # 项目源码目录
|
||||
│ ├── /public/ # 公共文件,编译时copy至dist目录
|
||||
│ ├── /components/ # UI组件及UI相关方法
|
||||
│ │ ├── skin.less # 全局样式
|
||||
│ │ └── vars.less # 全局样式变量
|
||||
@@ -75,33 +92,43 @@
|
||||
|
||||
文件夹命名说明:
|
||||
|
||||
- components:组件(方法)为单位以文件夹保存,文件夹名组件首字母大写(如`DataTable`),方法首字母小写(如`layer`),文件夹内主文件与文件夹同名,多文件以`index.js`导出对象(如`./src/components/Layout`)
|
||||
- routes:页面为单位以文件夹保存,文件夹名首字母小写(特殊除外,如`UIElement`),文件夹内主文件以`index.js`导出,多文件时可建立`components`文件夹(如`./src/routes/dashboard`),如果有子路由,依次按照路由层次建立文件夹(如`./src/routes/UIElement`)
|
||||
- components:组件(方法)为单位以文件夹保存,文件夹名组件首字母大写(如`DataTable`),方法首字母小写(如`layer`),文件夹内主文件与文件夹同名,多文件以`index.js`导出对象(如`./src/components/Layout`)。
|
||||
- routes:页面为单位以文件夹保存,文件夹名首字母小写(特殊除外,如`UIElement`),文件夹内主文件以`index.js`导出,多文件时可建立`components`文件夹(如`./src/routes/dashboard`),如果有子路由,依次按照路由层次建立文件夹(如`./src/routes/UIElement`)。
|
||||
|
||||
### 快速开始
|
||||
|
||||
克隆项目文件:
|
||||
|
||||
git clone https://github.com/zuiidea/antd-admin.git
|
||||
```bash
|
||||
git clone https://github.com/zuiidea/antd-admin.git
|
||||
```
|
||||
|
||||
进入目录安装依赖:
|
||||
|
||||
npm i 或者 yarn install
|
||||
npm i roadhog@0.6.0-beta.6 -g
|
||||
```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,6 +137,19 @@ npm run build
|
||||
npm run lint
|
||||
```
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
## 参考
|
||||
|
||||
用户列表:<https://github.com/dvajs/dva/tree/master/examples/user-dashboard>
|
||||
@@ -120,8 +160,8 @@ dashboard设计稿:<https://dribbble.com/shots/3108122-Dashboard-Admin> (已
|
||||
|
||||
web
|
||||
|
||||

|
||||

|
||||
|
||||
移动
|
||||
|
||||

|
||||

|
||||
|
||||
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 262 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 3.1 MiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 2.6 MiB |
@@ -1,50 +1,63 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "antd-admin",
|
||||
"version": "4.3.3",
|
||||
"dependencies": {
|
||||
"antd": "^2.9.1",
|
||||
"antd": "^2.11.2",
|
||||
"axios": "^0.16.2",
|
||||
"babel-polyfill": "^6.23.0",
|
||||
"classnames": "^2.2.5",
|
||||
"d3-shape": "^1.2.0",
|
||||
"draftjs-to-markdown": "^0.4.0",
|
||||
"dva": "^1.1.0",
|
||||
"dva": "^1.2.0",
|
||||
"dva-loading": "^0.2.0",
|
||||
"ejs-loader": "^0.3.0",
|
||||
"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.1",
|
||||
"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",
|
||||
"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",
|
||||
"roadhog": "^1.2.1"
|
||||
},
|
||||
"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",
|
||||
"html-webpack-template": "^5.6.0",
|
||||
"redbox-react": "^1.2.10",
|
||||
"roadhog": "^0.6.0-beta.6"
|
||||
"roadhog": "1.0.0-beta.7"
|
||||
},
|
||||
"pre-commit": [
|
||||
"lint"
|
||||
],
|
||||
"pre-commit": ["lint"],
|
||||
"scripts": {
|
||||
"dev": "set BROWSER=none&&set HOST=0.0.0.0&&roadhog server",
|
||||
"lint": "eslint --fix --ext .js src",
|
||||
"build": "roadhog build --analyze"
|
||||
"build": "roadhog build",
|
||||
"build:dll": "roadhog buildDll",
|
||||
"build:new": "node version && roadhog build"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,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) => {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -2,16 +2,16 @@ 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 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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@ 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 { 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,6 +102,10 @@ const Menus = ({ siderFold, darkTheme, location, handleClickNavMenu, navOpenKeys
|
||||
defaultSelectedKeys = getPathArray(menu, currentMenu, 'mpid', 'id')
|
||||
}
|
||||
|
||||
if (!defaultSelectedKeys) {
|
||||
defaultSelectedKeys = ['1']
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu
|
||||
{...menuProps}
|
||||
@@ -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,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 @@
|
||||
@import "../../themes/vars.less";
|
||||
@import "~themes/vars";
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classNames from 'classnames'
|
||||
import styles from './Loader.less'
|
||||
|
||||
const Loader = ({ spinning }) => {
|
||||
return (<div className={classNames(styles.loader, { [styles.hidden]: !spinning })}>
|
||||
<div className={styles.warpper}>
|
||||
<div className={styles.inner} />
|
||||
<div className={styles.text} >LOADING</div>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
|
||||
|
||||
Loader.propTypes = {
|
||||
spinning: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default Loader
|
||||
@@ -0,0 +1,64 @@
|
||||
.loader {
|
||||
display: block;
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 100000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 1;
|
||||
text-align: center;
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Loader",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "./Loader.js"
|
||||
}
|
||||
@@ -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 !== '',
|
||||
})
|
||||
}
|
||||
handeleSelectChange = value => {
|
||||
handleSelectChange = (value) => {
|
||||
this.setState({
|
||||
...this.state,
|
||||
selectValue: value,
|
||||
@@ -42,7 +42,7 @@ class Search extends React.Component {
|
||||
const { clearVisible } = this.state
|
||||
return (
|
||||
<Input.Group compact size={size} className={styles.search} style={style}>
|
||||
{select && <Select ref="searchSelect" onChange={this.handeleSelectChange} size={size} {...selectProps}>
|
||||
{select && <Select ref="searchSelect" onChange={this.handleSelectChange} size={size} {...selectProps}>
|
||||
{selectOptions && selectOptions.map((item, key) => <Select.Option value={item.value} key={key}>{item.name || item.value}</Select.Option>)}
|
||||
</Select>}
|
||||
<Input ref="searchInput" size={size} onChange={this.handleInputChange} onPressEnter={this.handleSearch} defaultValue={keyword} />
|
||||
|
||||
@@ -4,6 +4,7 @@ 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 layer from './layer'
|
||||
@@ -16,5 +17,6 @@ export {
|
||||
Search,
|
||||
Editor,
|
||||
FilterItem,
|
||||
Loader,
|
||||
layer,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -1,20 +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 src="/index.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,9 +1,8 @@
|
||||
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 'babel-polyfill'
|
||||
|
||||
// 1. Initialize
|
||||
const app = dva({
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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({
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -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()
|
||||
@@ -163,6 +173,13 @@ module.exports = {
|
||||
})
|
||||
},
|
||||
|
||||
[`DELETE ${apiPrefix}/users`] (req, res) {
|
||||
const { ids } = req.body
|
||||
database = database.filter(item => !ids.some(_ => _ === item.id))
|
||||
res.status(204).end()
|
||||
},
|
||||
|
||||
|
||||
[`POST ${apiPrefix}/user`] (req, res) {
|
||||
const newData = req.body
|
||||
newData.createTime = Mock.mock('@now')
|
||||
@@ -188,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)
|
||||
|
||||
@@ -1,21 +1,52 @@
|
||||
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'
|
||||
|
||||
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: location.query,
|
||||
},
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
setup ({ dispatch }) {
|
||||
dispatch({ type: 'query' })
|
||||
let tid
|
||||
@@ -30,30 +61,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',
|
||||
query: {
|
||||
from: locationPathname,
|
||||
},
|
||||
}))
|
||||
}
|
||||
},
|
||||
|
||||
*logout ({
|
||||
* logout ({
|
||||
payload,
|
||||
}, { call, put }) {
|
||||
const data = yield call(logout, parse(payload))
|
||||
@@ -64,10 +116,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 +126,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 +142,7 @@ export default {
|
||||
},
|
||||
|
||||
switchTheme (state) {
|
||||
localStorage.setItem(`${prefix}darkTheme`, !state.darkTheme)
|
||||
window.localStorage.setItem(`${prefix}darkTheme`, !state.darkTheme)
|
||||
return {
|
||||
...state,
|
||||
darkTheme: !state.darkTheme,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import modelExtend from 'dva-model-extend'
|
||||
import { query } from 'services/posts'
|
||||
import { pageModel } from 'models/common'
|
||||
|
||||
export default modelExtend(pageModel, {
|
||||
|
||||
namespace: 'post',
|
||||
|
||||
subscriptions: {
|
||||
setup ({ dispatch, history }) {
|
||||
history.listen((location) => {
|
||||
if (location.pathname === '/post') {
|
||||
dispatch({ type: 'query',
|
||||
payload: {
|
||||
status: 2,
|
||||
...location.query,
|
||||
} })
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
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
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -1,29 +1,27 @@
|
||||
import { create, remove, update } from '../services/user'
|
||||
import { query } 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 { pageModel } from './common'
|
||||
|
||||
export default {
|
||||
const { query } = usersService
|
||||
const { prefix } = config
|
||||
|
||||
export default modelExtend(pageModel, {
|
||||
namespace: 'user',
|
||||
|
||||
state: {
|
||||
list: [],
|
||||
currentItem: {},
|
||||
modalVisible: false,
|
||||
modalType: 'create',
|
||||
isMotion: localStorage.getItem('antdAdminUserIsMotion') === 'true',
|
||||
pagination: {
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: total => `共 ${total} 条`,
|
||||
current: 1,
|
||||
total: null,
|
||||
},
|
||||
selectedRowKeys: [],
|
||||
isMotion: window.localStorage.getItem(`${prefix}userIsMotion`) === 'true',
|
||||
},
|
||||
|
||||
subscriptions: {
|
||||
setup ({ dispatch, history }) {
|
||||
history.listen(location => {
|
||||
history.listen((location) => {
|
||||
if (location.pathname === '/user') {
|
||||
dispatch({
|
||||
type: 'query',
|
||||
@@ -36,8 +34,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({
|
||||
@@ -54,16 +51,28 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
*'delete' ({ payload }, { call, put }) {
|
||||
* delete ({ payload }, { call, put, select }) {
|
||||
const data = yield call(remove, { id: payload })
|
||||
const { selectedRowKeys } = yield select(_ => _.user)
|
||||
if (data.success) {
|
||||
yield put({ type: 'updateState', payload: { selectedRowKeys: selectedRowKeys.filter(_ => _ !== payload) } })
|
||||
yield put({ type: 'query' })
|
||||
} else {
|
||||
throw data
|
||||
}
|
||||
},
|
||||
|
||||
*create ({ payload }, { call, put }) {
|
||||
* multiDelete ({ payload }, { call, put }) {
|
||||
const data = yield call(usersService.remove, payload)
|
||||
if (data.success) {
|
||||
yield put({ type: 'updateState', payload: { selectedRowKeys: [] } })
|
||||
yield put({ type: 'query' })
|
||||
} else {
|
||||
throw data
|
||||
}
|
||||
},
|
||||
|
||||
* create ({ payload }, { call, put }) {
|
||||
const data = yield call(create, payload)
|
||||
if (data.success) {
|
||||
yield put({ type: 'hideModal' })
|
||||
@@ -73,7 +82,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)
|
||||
@@ -89,18 +98,8 @@ export default {
|
||||
|
||||
reducers: {
|
||||
|
||||
querySuccess (state, action) {
|
||||
const { list, pagination } = action.payload
|
||||
return { ...state,
|
||||
list,
|
||||
pagination: {
|
||||
...state.pagination,
|
||||
...pagination,
|
||||
} }
|
||||
},
|
||||
|
||||
showModal (state, action) {
|
||||
return { ...state, ...action.payload, modalVisible: true }
|
||||
showModal (state, { payload }) {
|
||||
return { ...state, ...payload, modalVisible: true }
|
||||
},
|
||||
|
||||
hideModal (state) {
|
||||
@@ -108,10 +107,9 @@ export default {
|
||||
},
|
||||
|
||||
switchIsMotion (state) {
|
||||
localStorage.setItem('antdAdminUserIsMotion', !state.isMotion)
|
||||
window.localStorage.setItem(`${prefix}userIsMotion`, !state.isMotion)
|
||||
return { ...state, isMotion: !state.isMotion }
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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 |
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Router } from 'dva/router'
|
||||
import App from './routes/app'
|
||||
import App from 'routes/app'
|
||||
|
||||
const registerModel = (app, model) => {
|
||||
if (!(app._models.filter(m => m.namespace === model.namespace).length === 1)) {
|
||||
@@ -15,119 +15,127 @@ const Routers = function ({ history, app }) {
|
||||
path: '/',
|
||||
component: App,
|
||||
getIndexRoute (nextState, cb) {
|
||||
require.ensure([], require => {
|
||||
registerModel(app, require('./models/dashboard'))
|
||||
cb(null, { component: require('./routes/dashboard/') })
|
||||
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/'))
|
||||
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/'))
|
||||
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/'))
|
||||
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/'))
|
||||
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/'))
|
||||
require.ensure([], (require) => {
|
||||
cb(null, require('routes/request/'))
|
||||
}, 'request')
|
||||
},
|
||||
}, {
|
||||
path: 'UIElement/iconfont',
|
||||
getComponent (nextState, cb) {
|
||||
require.ensure([], require => {
|
||||
cb(null, require('./routes/UIElement/iconfont/'))
|
||||
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/'))
|
||||
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/'))
|
||||
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/'))
|
||||
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/'))
|
||||
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/'))
|
||||
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/'))
|
||||
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/'))
|
||||
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/'))
|
||||
require.ensure([], (require) => {
|
||||
cb(null, require('routes/chart/areaChart/'))
|
||||
}, 'chart-areaChart')
|
||||
},
|
||||
}, {
|
||||
path: 'post',
|
||||
getComponent (nextState, cb) {
|
||||
require.ensure([], (require) => {
|
||||
registerModel(app, require('models/post'))
|
||||
cb(null, require('routes/post/'))
|
||||
}, 'post')
|
||||
},
|
||||
}, {
|
||||
path: '*',
|
||||
getComponent (nextState, cb) {
|
||||
require.ensure([], require => {
|
||||
cb(null, require('./routes/error/'))
|
||||
require.ensure([], (require) => {
|
||||
cb(null, require('routes/error/'))
|
||||
}, 'error')
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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' },
|
||||
],
|
||||
|
||||
@@ -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,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'
|
||||
|
||||
@@ -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 'tsundere' 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 'face cute' 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
|
||||
|
||||
@@ -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: '无',
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
/* 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 '../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) {
|
||||
@@ -28,7 +37,6 @@ const App = ({ children, location, dispatch, app, loading }) => {
|
||||
menu,
|
||||
user,
|
||||
siderFold,
|
||||
location,
|
||||
isNavbar,
|
||||
menuPopoverVisible,
|
||||
navOpenKeys,
|
||||
@@ -48,48 +56,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 spinning={loading.effects['app/query']} />
|
||||
{children}
|
||||
</div>)
|
||||
}
|
||||
|
||||
const { iconFontJS, iconFontCSS, logo } = config
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Loader 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 />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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,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: {
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 { Loader } 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,96 @@ 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>
|
||||
<div>
|
||||
<Loader spinning={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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Dashboard.propTypes = {
|
||||
dashboard: PropTypes.object,
|
||||
loading: PropTypes.object,
|
||||
}
|
||||
|
||||
export default connect(({ dashboard }) => ({ dashboard }))(Dashboard)
|
||||
export default connect(({ dashboard, loading }) => ({ dashboard, loading }))(Dashboard)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'dva'
|
||||
import { Tabs } from 'antd'
|
||||
import { routerRedux } from 'dva/router'
|
||||
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,
|
||||
query: {
|
||||
...query,
|
||||
page: page.current,
|
||||
pageSize: page.pageSize,
|
||||
},
|
||||
}))
|
||||
},
|
||||
}
|
||||
|
||||
const handleTabClick = (key) => {
|
||||
dispatch(routerRedux.push({
|
||||
pathname,
|
||||
query: {
|
||||
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)
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
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 { DropOption } from 'components'
|
||||
import { Link } from 'dva/router'
|
||||
import AnimTableBody from '../../components/DataTable/AnimTableBody'
|
||||
import styles from './List.less'
|
||||
|
||||
const confirm = Modal.confirm
|
||||
|
||||
@@ -30,7 +30,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 +48,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 +82,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>
|
||||
@@ -90,7 +90,7 @@ const List = ({ onDeleteItem, onEditItem, isMotion, location, ...tableProps }) =
|
||||
{...tableProps}
|
||||
className={classnames({ [styles.table]: true, [styles.motion]: isMotion })}
|
||||
bordered
|
||||
scroll={{ x: 1200 }}
|
||||
scroll={{ x: 1250 }}
|
||||
columns={columns}
|
||||
simple
|
||||
rowKey={record => record.id}
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
.ant-table-tbody > tr > td,
|
||||
.ant-table-thead > tr > th {
|
||||
&:nth-child(1) {
|
||||
width: 6%;
|
||||
width: 5%;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
width: 8%;
|
||||
width: 6%;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
@@ -29,7 +29,7 @@
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
width: 6%;
|
||||
width: 8%;
|
||||
}
|
||||
|
||||
&:nth-child(5) {
|
||||
@@ -37,22 +37,26 @@
|
||||
}
|
||||
|
||||
&:nth-child(6) {
|
||||
width: 8%;
|
||||
width: 6%;
|
||||
}
|
||||
|
||||
&:nth-child(7) {
|
||||
width: 16%;
|
||||
width: 8%;
|
||||
}
|
||||
|
||||
&:nth-child(8) {
|
||||
width: 20%;
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
&:nth-child(9) {
|
||||
width: 14%;
|
||||
width: 17%;
|
||||
}
|
||||
|
||||
&:nth-child(10) {
|
||||
width: 13%;
|
||||
}
|
||||
|
||||
&:nth-child(11) {
|
||||
width: 8%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -2,12 +2,13 @@ import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { routerRedux } from 'dva/router'
|
||||
import { connect } from 'dva'
|
||||
import { Row, Col, Button, Popconfirm } from 'antd'
|
||||
import List from './List'
|
||||
import Filter from './Filter'
|
||||
import Modal from './Modal'
|
||||
|
||||
const User = ({ location, dispatch, user, loading }) => {
|
||||
const { list, pagination, currentItem, modalVisible, modalType, isMotion } = user
|
||||
const { list, pagination, currentItem, modalVisible, modalType, isMotion, selectedRowKeys } = user
|
||||
const { pageSize } = pagination
|
||||
|
||||
const modalProps = {
|
||||
@@ -62,6 +63,17 @@ const User = ({ location, dispatch, user, loading }) => {
|
||||
},
|
||||
})
|
||||
},
|
||||
rowSelection: {
|
||||
selectedRowKeys,
|
||||
onChange: (keys) => {
|
||||
dispatch({
|
||||
type: 'user/updateState',
|
||||
payload: {
|
||||
selectedRowKeys: keys,
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const filterProps = {
|
||||
@@ -103,9 +115,29 @@ const User = ({ location, dispatch, user, loading }) => {
|
||||
},
|
||||
}
|
||||
|
||||
const handleDeleteItems = () => {
|
||||
dispatch({
|
||||
type: 'user/multiDelete',
|
||||
payload: {
|
||||
ids: selectedRowKeys,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="content-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>
|
||||
}
|
||||
<List {...listProps} />
|
||||
{modalVisible && <Modal {...modalProps} />}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { request, config } from '../utils'
|
||||
import { request, config } from 'utils'
|
||||
|
||||
const { api } = config
|
||||
const { user, userLogout, userLogin } = api
|
||||
|
||||
|
||||