Compare commits

...
46 Commits
Author SHA1 Message Date
zuiidea fa4385055b Update README.md 2017-09-09 14:54:18 +08:00
zuiidea 01f4de55df Merge pull request #571 from zuiidea/develop
Develop
2017-09-09 14:52:50 +08:00
zuiidea dedd34e8e8 4.3.5 2017-09-09 14:50:47 +08:00
zuiidea d1b373201e fix some grammar and details 2017-09-09 14:20:51 +08:00
zuiidea dbcc90e7ad 出去吃烧烤,回来接着看 2017-09-08 16:41:05 +08:00
zuiidea 87fce74d5b Update app.js
fix mobile Header menu
2017-09-08 15:45:38 +08:00
zuiidea 5c51679ad7 Merge branch 'yangbin1994-dva2.0' into develop 2017-09-08 14:56:35 +08:00
杨斌 1288853112 解决冲突 2017-09-08 14:19:40 +08:00
杨斌 d3e7e17789 query-string 2017-09-08 13:27:07 +08:00
杨斌 7bf031699a add LoadingComponent 2017-09-06 20:36:14 +08:00
杨斌 a6eb693636 dva@2.0 2017-09-06 11:13:26 +08:00
zuiidea 39c377e0e2 Merge pull request #552 from zuiidea/develop
add entry.ejs and add lte IE10 polyfill
2017-09-06 09:56:25 +08:00
zuiidea 67b90c4df0 add entry.ejs and add lte IE10 polyfill 2017-09-06 09:55:41 +08:00
zuiidea 0e2727f9c8 Merge pull request #542 from zuiidea/develop
4.3.4
2017-09-04 11:48:32 +08:00
zuiidea 0cf02e7324 4.3.4 2017-09-04 11:47:24 +08:00
zuiidea 68c9f2b65f Merge pull request #540 from zuiidea/develop
fix login success redirect;fix css load delay
2017-09-04 11:03:13 +08:00
zuiidea 587b1f6b9d fix login success redirect;fix css load delay 2017-09-04 11:01:31 +08:00
zuiidea d94580a253 Merge pull request #302 from jackple/patch-1
Update app.js
2017-09-04 09:58:42 +08:00
zuiidea 531e3c0fcc Merge pull request #532 from p2227/patch-1
fixes #529
2017-09-04 09:57:39 +08:00
zuiidea d68de7a30c Merge pull request #378 from chenkang084/remote
fixed:when click breadcrumb back to other page,the left menu displaye…
2017-09-04 09:57:24 +08:00
zuiidea 45cb70f006 Merge pull request #518 from quanpower/patch-1
Update roadhogrc.js
2017-09-04 09:56:18 +08:00
kuang af85384f11 fixes #529 2017-08-28 23:20:38 -05:00
William Zhang 208539134f Update roadhogrc.js
proxy加引号,我这报错了,去掉了正常运行
2017-08-19 18:06:16 +08:00
zuiidea a3f41e3d64 Merge pull request #496 from zuiidea/develop
fix weather
2017-08-13 17:20:42 +08:00
zuiidea 3855c402c9 fix weather 2017-08-13 17:18:46 +08:00
zuiidea 6a58d8434b Merge pull request #493 from zuiidea/develop
fix lint
2017-08-10 19:11:39 +08:00
zuiidea d26a92ce79 fix lint 2017-08-10 19:11:10 +08:00
zuiidea 0d20717c3c Merge pull request #488 from zuiidea/develop
update
2017-08-09 09:46:45 +08:00
zuiidea a0d81dcda9 update 2017-08-08 19:46:38 +08:00
zuiidea 4490af8553 Merge pull request #486 from zuiidea/develop
Develop
2017-08-07 14:43:08 +08:00
zuiidea 75cc86152d 4.3.2 2017-08-07 14:28:32 +08:00
zuiidea 3d9f263330 add Alias 2017-08-07 11:46:42 +08:00
zuiidea 70b5d05f1a format 2017-08-03 14:01:49 +08:00
zuiidea 4da23b9133 version.js 2017-08-03 13:50:43 +08:00
zuiidea da1c52e82b npm run build:new 2017-07-31 17:28:03 +08:00
zuiidea 6ae39bfd55 Merge pull request #453 from phproot/master
fix README.md
2017-07-31 11:51:42 +08:00
Code d86f0ebd78 fix README.md 2017-07-31 11:38:39 +08:00
zuiidea ecbd8bf51e fix little bug 2017-07-31 10:49:15 +08:00
zuiidea 0c9fabe2b4 Merge pull request #452 from zuiidea/develop
fix build:dll
2017-07-30 23:51:07 +08:00
zuiidea 0f27528d2f fix build:dll 2017-07-30 23:50:12 +08:00
zuiidea a36d44d402 Merge pull request #450 from zuiidea/develop
Develop
2017-07-30 14:54:15 +08:00
zuiidea b2b0aa872f Merge pull request #431 from zuiidea/develop
pagenation style
2017-07-21 19:03:48 +08:00
rengised a320c77ecb Merge branch 'master' into remote 2017-07-05 15:08:43 +08:00
chenkang1 7f091d7642 fixed:the error request should be capured in catch method 2017-07-03 14:06:28 +08:00
chenkang1 d8f222f312 fixed:when click breadcrumb back to other page,the left menu displayed the last select item 2017-07-01 16:08:03 +08:00
jackple f6021cbaa4 Update app.js
先判断menu长度,解决当远程加载menu时刷新页面,当前位置不会选中
2017-05-21 20:32:50 +08:00
53 changed files with 1644 additions and 1692 deletions
+1
View File
@@ -1 +1,2 @@
src/**/*-test.js
src/public
+1
View File
@@ -32,6 +32,7 @@
"react/forbid-prop-types": 0,
"react/no-string-refs": 0,
"react/no-find-dom-node": 0,
"react/prefer-stateless-function": 0,
"import/no-extraneous-dependencies": 0,
"import/prefer-default-export": 0,
"react/no-danger": 0,
+4 -4
View File
@@ -1,4 +1,4 @@
dist
node_modules
yarn.lock
npm-debug.log
dist
node_modules
yarn.lock
npm-debug.log
+20 -20
View File
@@ -7,25 +7,25 @@ const svgSpriteDirs = [
]
export default {
entry : 'src/index.js',
svgSpriteLoaderDirs : svgSpriteDirs,
theme : "./theme.config.js",
publicPath : `/${version}/`,
outputPath : `./dist/${version}`,
entry: 'src/index.js',
svgSpriteLoaderDirs: svgSpriteDirs,
theme: "./theme.config.js",
publicPath: `/${version}/`,
outputPath: `./dist/${version}`,
// 接口代理示例
// "proxy": {
// "/api/v1": {
// "target": "http://api.zuiidea.com",
// "changeOrigin": true,
// "pathRewrite": { "^/api/v1" : "/v1" }
// },
// "/api/v2": {
// "target": "http://192.168.0.110",
// "changeOrigin": true,
// "pathRewrite": { "^/api/v2" : "/api/v2" }
// }
// },
env : {
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",
@@ -50,8 +50,8 @@ export default {
]
}
},
dllPlugin : {
exclude: ["babel-runtime"],
dllPlugin: {
exclude: ["babel-runtime", "roadhog", "cross-env"],
include: ["dva/router", "dva/saga", "dva/fetch"]
}
}
+41 -37
View File
@@ -2,14 +2,14 @@
[![React](https://img.shields.io/badge/react-^15.6.1-brightgreen.svg?style=flat-square)](https://github.com/facebook/react)
[![Ant Design](https://img.shields.io/badge/ant--design-^2.11.2-yellowgreen.svg?style=flat-square)](https://github.com/ant-design/ant-design)
[![dva](https://img.shields.io/badge/dva-^1.2.0-orange.svg?style=flat-square)](https://github.com/dvajs/dva)
[![dva](https://img.shields.io/badge/dva-^2.0.1-orange.svg?style=flat-square)](https://github.com/dvajs/dva)
[![GitHub issues](https://img.shields.io/github/issues/zuiidea/antd-admin.svg?style=flat-square)](https://github.com/zuiidea/antd-admin)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/zuiidea/antd-admin/pulls)
[![MIT](https://img.shields.io/dub/l/vibe-d.svg?style=flat-square)](http://opensource.org/licenses/MIT)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com)
演示地址 <http://zuiidea.github.io/antd-admin> \| [备用地址](http://47.92.30.98:8000)
演示地址 <http://antd-admin.zuiidea.com>
## 特性
@@ -21,6 +21,21 @@
## 更新日志
### 4.3.5
`2017-09-04`
-   更新[dva](https://github.com/dvajs/dva)至`2.0.1`,感谢[@yangbin1994](https://github.com/yangbin1994)的[PR](https://github.com/zuiidea/antd-admin/pull/567)。
### 4.3.4
`2017-09-04`
-   更新[roadhog](https://github.com/sorrycc/roadhog)至`1.2.1`,修复闪屏问题。
-   修复build css未压缩[#529](https://github.com/zuiidea/antd-admin/issues/529),classname未Hash[#477](https://github.com/zuiidea/antd-admin/issues/477)。
-   更新登录成功使用router跳转[#479](https://github.com/zuiidea/antd-admin/issues/479)。
### 4.3.0
`2017-07-30`
@@ -30,34 +45,8 @@
-   新增打包时按项目版本号生成文件目录。[#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)
[More Change Log](https://github.com/zuiidea/antd-admin/wiki/Change-Log)
[More Change Log](https://github.com/zuiidea/antd-admin/wiki/Change-Log)
## 开发构建
@@ -99,11 +88,16 @@
克隆项目文件:
git clone https://github.com/zuiidea/antd-admin.git
```bash
git clone https://github.com/zuiidea/antd-admin.git
```
进入目录安装依赖:
npm i 或者 yarn install
```bash
#开始前请确保没有安装roadhog、webpack到NPM全局目录
npm i 或者 yarn install
```
开发:
@@ -114,11 +108,16 @@ npm run dev
```
构建:
[详情](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字段
```
代码检测:
@@ -129,11 +128,16 @@ 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)
- 项目打包后如何部署? [#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
```
## 参考
+13 -11
View File
@@ -1,18 +1,20 @@
{
"private": true,
"name": "antd-admin",
"version": "4.3.0",
"version": "4.3.5",
"dependencies": {
"antd": "^2.11.2",
"axios": "^0.16.2",
"babel-polyfill": "^6.23.0",
"classnames": "^2.2.5",
"copy-webpack-plugin": "^4.0.1",
"cross-env": "^5.0.5",
"d3-shape": "^1.2.0",
"draftjs-to-markdown": "^0.4.0",
"dva": "^1.2.0",
"dva": "^2.0.1",
"dva-loading": "^0.2.0",
"ejs-loader": "^0.3.0",
"history": "^4.7.2",
"js-beautify": "^1.6.14",
"jsonp": "^0.2.1",
"less-vars-to-js": "^1.1.2",
"lodash": "^4.17.4",
@@ -21,6 +23,7 @@
"path-to-regexp": "^1.7.0",
"prop-types": "^15.5.10",
"qs": "^6.5.0",
"query-string": "^5.0.0",
"rc-tween-one": "^1.3.1",
"react": "^15.6.1",
"react-countup": "^2.1.1",
@@ -36,6 +39,7 @@
"babel-plugin-import": "^1.1.1",
"babel-plugin-transform-runtime": "^6.9.0",
"babel-runtime": "^6.9.2",
"copy-webpack-plugin": "^4.0.1",
"draftjs-to-html": "^0.7.0",
"dva-model-extend": "^0.1.1",
"eslint": "^4.1.1",
@@ -46,17 +50,15 @@
"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": "1.0.0-beta.7"
"roadhog": "^1.2.2"
},
"pre-commit": [
"lint"
],
"pre-commit": ["lint"],
"scripts": {
"dev": "set BROWSER=none&&set HOST=0.0.0.0&&roadhog server",
"dev": "cross-env BROWSER=none HOST=0.0.0.0 roadhog server",
"lint": "eslint --fix --ext .js src",
"build": "roadhog build",
"build:dll": "roadhog buildDll"
"build:dll": "roadhog buildDll",
"build:new": "node version && roadhog build"
}
}
}
+4 -4
View File
@@ -1,13 +1,12 @@
/* global location */
import React from 'react'
import PropTypes from 'prop-types'
import { Breadcrumb, Icon } from 'antd'
import { Link } from 'dva/router'
import { Link } from 'react-router-dom'
import pathToRegexp from 'path-to-regexp'
import { queryArray } from 'utils'
import styles from './Bread.less'
const Bread = ({ menu }) => {
const Bread = ({ menu, location }) => {
// 匹配当前路由
let pathArray = []
let current
@@ -49,7 +48,7 @@ const Bread = ({ menu }) => {
return (
<Breadcrumb.Item key={key}>
{((pathArray.length - 1) !== key)
? <Link to={item.route}>
? <Link to={item.route || '#'}>
{content}
</Link>
: content}
@@ -68,6 +67,7 @@ const Bread = ({ menu }) => {
Bread.propTypes = {
menu: PropTypes.array,
location: PropTypes.object,
}
export default Bread
+8 -8
View File
@@ -1,8 +1,8 @@
@import "../../themes/vars.less";
.bread {
height: 64px;
line-height: 64px;
padding: 0 24px;
margin-bottom: -24px;
}
@import "~themes/vars";
.bread {
height: 64px;
line-height: 64px;
padding: 0 24px;
margin-bottom: -24px;
}
+12 -12
View File
@@ -1,12 +1,12 @@
@import "../../themes/vars.less";
.footer {
height: 48px;
line-height: 48px;
text-align: center;
font-size: 12px;
color: #999;
background: #fff;
box-shadow: @shadow-2;
width: 100%;
}
@import "~themes/vars";
.footer {
height: 48px;
line-height: 48px;
text-align: center;
font-size: 12px;
color: #999;
background: #fff;
box-shadow: @shadow-2;
width: 100%;
}
+59 -59
View File
@@ -1,59 +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;
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;
}
}
}
@import "~themes/vars";
.header {
:global .ant-menu-horizontal {
& > .ant-menu-submenu {
float: right;
}
border: none;
display: inline-block;
}
box-shadow: @shadow-2;
position: relative;
display: flex;
justify-content: space-between;
height: 47px;
background-color: #fff;
z-index: 999;
.rightWarpper {
display: flex;
padding-right: 16px;
}
.button {
width: 47px;
height: 47px;
line-height: 47px;
text-align: center;
font-size: 18px;
cursor: pointer;
transition: @transition-ease-in;
&:hover {
color: @primary-color;
background-color: fade(@primary-color, 15%);
}
}
}
.popovermenu {
width: 280px;
margin-left: 6px;
:global .ant-popover-inner-content {
padding: 0;
.ant-menu-inline .ant-menu-item,
.ant-menu-vertical .ant-menu-item {
border-right: 0;
height: 48px;
line-height: 48px;
}
.ant-menu-inline .ant-menu-item-selected,
.ant-menu-inline .ant-menu-selected {
border-right: 0;
}
}
}
+292 -292
View File
@@ -1,292 +1,292 @@
@import "../../themes/vars.less";
.layout {
position: relative;
height: 100vh;
&.withnavbar {
.main {
margin-left: 0;
}
}
&.fold {
.sider {
width: 42px;
.logo {
img {
width: 28px;
margin: 6px 7px;
}
}
:global {
.ant-menu-root {
width: 100%;
overflow: visible;
& > .ant-menu-item {
padding: 0;
text-align: center;
.anticon {
font-size: 14px;
margin-right: 0;
}
}
& > .ant-menu-submenu {
& > .ant-menu-submenu-title {
padding: 0;
text-align: center;
.anticon {
font-size: 14px;
margin-right: 0;
}
&::after {
display: none;
}
}
}
}
}
}
.main {
margin-left: 42px;
}
}
.sider {
width: 224px;
background: #3e3e3e;
position: absolute;
overflow: visible;
padding-bottom: 24px;
height: 100vh;
transition: @transition-ease-out;
box-shadow: @shadow-1;
color: #999;
&.light {
background: #fff;
.switchtheme {
background: #fff;
border-top: solid 1px #f8f8f8;
}
}
.logo {
text-align: center;
height: 40px;
line-height: 40px;
cursor: pointer;
margin: 28px 0;
transition: @transition-ease-out;
overflow: hidden;
img {
width: 40px;
margin-right: 8px;
transition: @transition-ease-out;
}
span {
vertical-align: text-bottom;
font-size: 16px;
text-transform: uppercase;
display: inline-block;
}
.anticon {
transition: @transition-ease-out;
}
}
.switchtheme {
width: 100%;
position: absolute;
bottom: 0;
height: 48px;
background-color: @dark-half;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 16px 0 24px;
overflow: hidden;
z-index: 1000;
span {
white-space: nowrap;
overflow: hidden;
}
:global {
.anticon {
min-width: 14px;
margin-right: 8px;
font-size: 14px;
}
}
}
:global {
.ant-menu-dark,
.ant-menu-dark .ant-menu-sub {
color: #999;
}
.ant-menu-root {
height: ~"calc(100vh - 144px)";
overflow-x: hidden;
border-right: none;
&::-webkit-scrollbar-thumb {
background-color: transparent;
}
&:hover {
&::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
}
}
&.ant-menu-vertical {
.ant-menu-sub {
height: 0;
opacity: 0;
transition: none;
}
.ant-menu-submenu-active {
.ant-menu-sub {
height: auto;
opacity: 1;
}
}
}
}
.ant-menu {
transition: @transition-ease-out;
.ant-menu-item,
.ant-menu-submenu-title {
overflow: hidden;
white-space: normal;
}
}
}
}
.main {
margin-left: 224px;
overflow: auto;
height: 100vh;
transition: @transition-ease-out;
:global {
.content-inner {
background: #fff;
padding: 24px;
box-shadow: @shadow-1;
min-height: e("calc(100vh - 184px)");
}
}
.container {
margin: 24px;
}
.content {
min-height: e("calc(100vh - 184px)");
position: relative;
}
}
}
:global .ant-menu-dark {
color: #999;
background-color: #3e3e3e;
.ant-menu-submenu-title:hover,
.ant-menu-submenu:hover {
color: @primary-color;
}
.ant-menu-sub {
color: #999;
}
.ant-menu-submenu-selected {
color: @primary-color;
&:not(.ant-menu-submenu-open) {
background-color: @dark-half;
}
}
.ant-menu-item,
.ant-menu-submenu-title {
& > a {
color: #999;
}
&:hover > a {
color: @primary-color;
}
}
&.ant-menu-inline {
.ant-menu-item-selected {
background-color: @dark-half;
}
}
.ant-menu-item-selected {
color: @primary-color;
background-color: @dark-half;
& > a {
color: @primary-color;
}
&:hover {
background-color: @dark-half;
}
}
}
.spin {
:global .ant-spin-container {
height: 100vh;
}
}
@media (max-width: 767px) {
.layout {
.main {
.container {
margin: 12px;
}
.bread {
padding: 0 12px;
}
.content {
min-height: e("calc(100vh - 160px)");
}
:global {
.content-inner {
padding: 12px;
min-height: e("calc(100vh - 160px)");
}
}
}
}
}
@import "~themes/vars";
.layout {
position: relative;
height: 100vh;
&.withnavbar {
.main {
margin-left: 0;
}
}
&.fold {
.sider {
width: 42px;
.logo {
img {
width: 28px;
margin: 6px 7px;
}
}
:global {
.ant-menu-root {
width: 100%;
overflow: visible;
& > .ant-menu-item {
padding: 0;
text-align: center;
.anticon {
font-size: 14px;
margin-right: 0;
}
}
& > .ant-menu-submenu {
& > .ant-menu-submenu-title {
padding: 0;
text-align: center;
.anticon {
font-size: 14px;
margin-right: 0;
}
&::after {
display: none;
}
}
}
}
}
}
.main {
margin-left: 42px;
}
}
.sider {
width: 224px;
background: #3e3e3e;
position: absolute;
overflow: visible;
padding-bottom: 24px;
height: 100vh;
transition: @transition-ease-out;
box-shadow: @shadow-1;
color: #999;
&.light {
background: #fff;
.switchtheme {
background: #fff;
border-top: solid 1px #f8f8f8;
}
}
.logo {
text-align: center;
height: 40px;
line-height: 40px;
cursor: pointer;
margin: 28px 0;
transition: @transition-ease-out;
overflow: hidden;
img {
width: 40px;
margin-right: 8px;
transition: @transition-ease-out;
}
span {
vertical-align: text-bottom;
font-size: 16px;
text-transform: uppercase;
display: inline-block;
}
.anticon {
transition: @transition-ease-out;
}
}
.switchtheme {
width: 100%;
position: absolute;
bottom: 0;
height: 48px;
background-color: @dark-half;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 16px 0 24px;
overflow: hidden;
z-index: 1000;
span {
white-space: nowrap;
overflow: hidden;
}
:global {
.anticon {
min-width: 14px;
margin-right: 8px;
font-size: 14px;
}
}
}
:global {
.ant-menu-dark,
.ant-menu-dark .ant-menu-sub {
color: #999;
}
.ant-menu-root {
height: ~"calc(100vh - 144px)";
overflow-x: hidden;
border-right: none;
&::-webkit-scrollbar-thumb {
background-color: transparent;
}
&:hover {
&::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
}
}
&.ant-menu-vertical {
.ant-menu-sub {
height: 0;
opacity: 0;
transition: none;
}
.ant-menu-submenu-active {
.ant-menu-sub {
height: auto;
opacity: 1;
}
}
}
}
.ant-menu {
transition: @transition-ease-out;
.ant-menu-item,
.ant-menu-submenu-title {
overflow: hidden;
white-space: normal;
}
}
}
}
.main {
margin-left: 224px;
overflow: auto;
height: 100vh;
transition: @transition-ease-out;
:global {
.content-inner {
background: #fff;
padding: 24px;
box-shadow: @shadow-1;
min-height: e("calc(100vh - 184px)");
}
}
.container {
margin: 24px;
}
.content {
min-height: e("calc(100vh - 184px)");
position: relative;
}
}
}
:global .ant-menu-dark {
color: #999;
background-color: #3e3e3e;
.ant-menu-submenu-title:hover,
.ant-menu-submenu:hover {
color: @primary-color;
}
.ant-menu-sub {
color: #999;
}
.ant-menu-submenu-selected {
color: @primary-color;
&:not(.ant-menu-submenu-open) {
background-color: @dark-half;
}
}
.ant-menu-item,
.ant-menu-submenu-title {
& > a {
color: #999;
}
&:hover > a {
color: @primary-color;
}
}
&.ant-menu-inline {
.ant-menu-item-selected {
background-color: @dark-half;
}
}
.ant-menu-item-selected {
color: @primary-color;
background-color: @dark-half;
& > a {
color: @primary-color;
}
&:hover {
background-color: @dark-half;
}
}
}
.spin {
:global .ant-spin-container {
height: 100vh;
}
}
@media (max-width: 767px) {
.layout {
.main {
.container {
margin: 12px;
}
.bread {
padding: 0 12px;
}
.content {
min-height: e("calc(100vh - 160px)");
}
:global {
.content-inner {
padding: 12px;
min-height: e("calc(100vh - 160px)");
}
}
}
}
}
+9 -5
View File
@@ -1,12 +1,11 @@
/* global location */
import React from 'react'
import PropTypes from 'prop-types'
import { Menu, Icon } from 'antd'
import { Link } from 'dva/router'
import { Link } from 'react-router-dom'
import { arrayToTree, queryArray } from 'utils'
import pathToRegexp from 'path-to-regexp'
const Menus = ({ siderFold, darkTheme, handleClickNavMenu, navOpenKeys, changeOpenKeys, menu }) => {
const Menus = ({ siderFold, darkTheme, handleClickNavMenu, navOpenKeys, changeOpenKeys, menu, location }) => {
// 生成树状
const menuTree = arrayToTree(menu.filter(_ => _.mpid !== '-1'), 'id', 'mpid')
const levelMap = {}
@@ -32,7 +31,7 @@ const Menus = ({ siderFold, darkTheme, handleClickNavMenu, navOpenKeys, changeOp
}
return (
<Menu.Item key={item.id}>
<Link to={item.route}>
<Link to={item.route || '#'}>
{item.icon && <Icon type={item.icon} />}
{(!siderFoldN || !menuTree.includes(item)) && item.name}
</Link>
@@ -103,13 +102,17 @@ const Menus = ({ siderFold, darkTheme, handleClickNavMenu, navOpenKeys, changeOp
defaultSelectedKeys = getPathArray(menu, currentMenu, 'mpid', 'id')
}
if (!defaultSelectedKeys) {
defaultSelectedKeys = ['1']
}
return (
<Menu
{...menuProps}
mode={siderFold ? 'vertical' : 'inline'}
theme={darkTheme ? 'dark' : 'light'}
onClick={handleClickNavMenu}
defaultSelectedKeys={defaultSelectedKeys}
selectedKeys={defaultSelectedKeys}
>
{menuItems}
</Menu>
@@ -123,6 +126,7 @@ Menus.propTypes = {
handleClickNavMenu: PropTypes.func,
navOpenKeys: PropTypes.array,
changeOpenKeys: PropTypes.func,
location: PropTypes.object,
}
export default Menus
+1 -1
View File
@@ -1 +1 @@
@import "../../themes/vars.less";
@import "~themes/vars";
+7 -2
View File
@@ -3,8 +3,12 @@ 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 })}>
const Loader = ({ spinning, fullScreen }) => {
return (<div className={classNames(styles.loader, {
[styles.hidden]: !spinning,
[styles.fullScreen]: fullScreen,
})}
>
<div className={styles.warpper}>
<div className={styles.inner} />
<div className={styles.text} >LOADING</div>
@@ -15,6 +19,7 @@ const Loader = ({ spinning }) => {
Loader.propTypes = {
spinning: PropTypes.bool,
fullScreen: PropTypes.bool,
}
export default Loader
+68 -64
View File
@@ -1,64 +1,68 @@
.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);
}
}
.loader {
display: block;
background-color: #fff;
width: 100%;
position: absolute;
top: 0;
bottom: 0;
left: 0;
z-index: 100000;
display: flex;
justify-content: center;
align-items: center;
opacity: 1;
text-align: center;
&.fullScreen {
position: fixed;
}
.warpper {
width: 100px;
height: 100px;
display: inline-flex;
flex-direction: column;
justify-content: space-around;
}
.inner {
width: 48px;
height: 48px;
margin: 0 auto;
text-indent: -12345px;
border-top: 1px solid rgba(0, 0, 0, 0.08);
border-right: 1px solid rgba(0, 0, 0, 0.08);
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
border-left: 1px solid rgba(0, 0, 0, 0.7);
border-radius: 50%;
z-index: 100001;
:local {
animation: spinner 600ms infinite linear;
}
}
.text {
width: 100px;
height: 20px;
text-align: center;
font-size: 12px;
letter-spacing: 4px;
color: #000;
}
&.hidden {
z-index: -1;
opacity: 0;
transition: opacity 1s ease 0.5s, z-index 0.1s ease 1.5s;
}
}
@keyframes spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
+34
View File
@@ -0,0 +1,34 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import Loader from '../Loader'
import styles from './Page.less'
export default class Page extends Component {
render () {
const { className, children, loading = false, inner = false } = this.props
const loadingStyle = {
height: 'calc(100vh - 184px)',
overflow: 'hidden',
}
return (
<div
className={classnames(className, {
[styles.contentInner]: inner,
})}
style={loading ? loadingStyle : null}
>
{loading ? <Loader spinning /> : ''}
{children}
</div>
)
}
}
Page.propTypes = {
className: PropTypes.string,
children: PropTypes.node,
loading: PropTypes.bool,
inner: PropTypes.bool,
}
+14
View File
@@ -0,0 +1,14 @@
@import "~themes/vars";
.contentInner{
background: #fff;
padding: 24px;
box-shadow: @shadow-1;
min-height: e("calc(100vh - 184px)");
}
@media (max-width: 767px) {
.contentInner {
padding: 12px;
min-height: e("calc(100vh - 160px)");
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"name": "Page",
"version": "0.0.0",
"private": true,
"main": "./Page.js"
}
+2
View File
@@ -6,6 +6,7 @@ import Editor from './Editor'
import FilterItem from './FilterItem'
import Loader from './Loader'
import * as Layout from './Layout/index.js'
import Page from './Page'
import layer from './layer'
@@ -19,4 +20,5 @@ export {
FilterItem,
Loader,
layer,
Page,
}
+25
View File
@@ -0,0 +1,25 @@
<% htmlWebpackPlugin.options.headScripts = htmlWebpackPlugin.options.headScripts || [] %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="A admin dashboard application demo built upon Ant Design and Dva.js">
<title>antd admin</title>
<!--[if lte IE 10]>
<script
src="https://as.alipayobjects.com/g/component/??console-polyfill/0.2.2/index.js,media-match/2.0.2/media.match.min.js"></script>
<![endif]-->
<% for (item of htmlWebpackPlugin.options.headScripts) { %>
<script src="<%= item %>"></script>
<% } %>
</head>
<body>
<div id="root"></div>
</body>
</html>
-33
View File
@@ -1,33 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="stylesheet" href="/index.css" />
<!--[if lte IE 10]>
<script
src="https://as.alipayobjects.com/g/component/??console-polyfill/0.2.2/index.js,media-match/2.0.2/media.match.min.js"></script>
<![endif]-->
</head>
<body>
<div id="root"></div>
<script type="text/javascript">
(function() {
var __oldConsoleError = window.console.error.bind(console)
window.console.error = function(message, b) {
if (message === "Warning: Accessing PropTypes via the main React package is deprecated. Use the prop-types package from npm instead." ||
"Warning: A Component: React.createClass is deprecated and will be removed in version 16. Use plain JavaScript classes instead. If you're not yet ready to migrate, create-react-class is available on npm as a drop-in replacement.") {
return false
}
__oldConsoleError(message)
}
})()
</script>
<script src="roadhog.dll.js"></script>
<script src="/index.js"></script>
</body>
</html>
+2 -3
View File
@@ -1,16 +1,15 @@
import { message } from 'antd'
import dva from 'dva'
import createLoading from 'dva-loading'
import { browserHistory } from 'dva/router'
import createHistory from 'history/createBrowserHistory'
import 'babel-polyfill'
import './index.html'
// 1. Initialize
const app = dva({
...createLoading({
effects: true,
}),
history: browserHistory,
history: createHistory(),
onError (error) {
message.error(error.message)
},
+29 -7
View File
@@ -5,8 +5,9 @@ import { routerRedux } from 'dva/router'
import { parse } from 'qs'
import config from 'config'
import { EnumRoleType } from 'enums'
import { query, logout } from '../services/app'
import * as menusService from '../services/menus'
import { query, logout } from 'services/app'
import * as menusService from 'services/menus'
import queryString from 'query-string'
const { prefix } = config
@@ -30,9 +31,23 @@ export default {
darkTheme: window.localStorage.getItem(`${prefix}darkTheme`) === 'true',
isNavbar: document.body.clientWidth < 769,
navOpenKeys: JSON.parse(window.localStorage.getItem(`${prefix}navOpenKeys`)) || [],
locationPathname: '',
locationQuery: {},
},
subscriptions: {
setupHistory ({ dispatch, history }) {
history.listen((location) => {
dispatch({
type: 'updateState',
payload: {
locationPathname: location.pathname,
locationQuery: queryString.parse(location.search),
},
})
})
},
setup ({ dispatch }) {
dispatch({ type: 'query' })
let tid
@@ -49,8 +64,9 @@ export default {
* query ({
payload,
}, { call, put }) {
}, { 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
@@ -76,11 +92,17 @@ export default {
},
})
if (location.pathname === '/login') {
yield put(routerRedux.push('/dashboard'))
yield put(routerRedux.push({
pathname: '/dashboard',
}))
}
} else if (config.openPages && config.openPages.indexOf(location.pathname) < 0) {
let from = location.pathname
window.location = `${location.origin}/login?from=${from}`
} else if (config.openPages && config.openPages.indexOf(locationPathname) < 0) {
yield put(routerRedux.push({
pathname: '/login',
query: {
from: locationPathname,
},
}))
}
},
+40 -194
View File
@@ -1,176 +1,17 @@
import { parse } from 'qs'
import { myCity, queryWeather, query } from '../services/dashboard'
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: {
@@ -187,9 +28,13 @@ 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: {
@@ -197,31 +42,32 @@ export default {
payload,
}, { call, put }) {
const data = yield call(query, parse(payload))
yield put({ type: 'queryWeather', payload: { ...data } })
yield put({
type: 'updateState',
payload: data,
})
},
* queryWeather (action, { 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,
* queryWeather ({
payload = {},
}, { call, put }) {
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,
},
})
}
},
},
}
})
+8 -24
View File
@@ -1,24 +1,21 @@
import { routerRedux } from 'dva/router'
import { queryURL } from 'utils'
import { login } from '../services/login'
import { login } from 'services/login'
export default {
namespace: 'login',
state: {
loginLoading: false,
},
state: {},
effects: {
* 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,
}
},
},
}
+4 -3
View File
@@ -1,6 +1,7 @@
import modelExtend from 'dva-model-extend'
import { query } from '../services/posts'
import { pageModel } from './common'
import { query } from 'services/posts'
import { pageModel } from 'models/common'
import queryString from 'query-string'
export default modelExtend(pageModel, {
@@ -13,7 +14,7 @@ export default modelExtend(pageModel, {
dispatch({ type: 'query',
payload: {
status: 2,
...location.query,
...queryString.parse(location.search),
} })
}
})
+4 -3
View File
@@ -1,8 +1,9 @@
/* 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 { create, remove, update } from 'services/user'
import * as usersService from 'services/users'
import queryString from 'query-string'
import { pageModel } from './common'
const { query } = usersService
@@ -25,7 +26,7 @@ export default modelExtend(pageModel, {
if (location.pathname === '/user') {
dispatch({
type: 'query',
payload: location.query,
payload: queryString.parse(location.search),
})
}
})
+2 -3
View File
@@ -1,4 +1,3 @@
/* global location */
import pathToRegexp from 'path-to-regexp'
import { query } from '../../services/user'
@@ -12,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] } })
}
+79 -136
View File
@@ -1,149 +1,92 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Router } from 'dva/router'
import App from './routes/app'
import { Switch, Route, Redirect, routerRedux } from 'dva/router'
import dynamic from 'dva/dynamic'
import App from 'routes/app'
const registerModel = (app, model) => {
if (!(app._models.filter(m => m.namespace === model.namespace).length === 1)) {
app.model(model)
}
}
const { ConnectedRouter } = routerRedux
const Routers = function ({ history, app }) {
const error = dynamic({
app,
component: () => import('./routes/error'),
})
const routes = [
{
path: '/',
component: App,
getIndexRoute (nextState, cb) {
require.ensure([], (require) => {
registerModel(app, require('./models/dashboard'))
cb(null, { component: require('./routes/dashboard/') })
}, 'dashboard')
},
childRoutes: [
{
path: 'dashboard',
getComponent (nextState, cb) {
require.ensure([], (require) => {
registerModel(app, require('./models/dashboard'))
cb(null, require('./routes/dashboard/'))
}, 'dashboard')
},
}, {
path: 'user',
getComponent (nextState, cb) {
require.ensure([], (require) => {
registerModel(app, require('./models/user'))
cb(null, require('./routes/user/'))
}, 'user')
},
}, {
path: 'user/:id',
getComponent (nextState, cb) {
require.ensure([], (require) => {
registerModel(app, require('./models/user/detail'))
cb(null, require('./routes/user/detail/'))
}, 'user-detail')
},
}, {
path: 'login',
getComponent (nextState, cb) {
require.ensure([], (require) => {
registerModel(app, require('./models/login'))
cb(null, require('./routes/login/'))
}, 'login')
},
}, {
path: 'request',
getComponent (nextState, cb) {
require.ensure([], (require) => {
cb(null, require('./routes/request/'))
}, 'request')
},
}, {
path: 'UIElement/iconfont',
getComponent (nextState, cb) {
require.ensure([], (require) => {
cb(null, require('./routes/UIElement/iconfont/'))
}, 'UIElement-iconfont')
},
}, {
path: 'UIElement/search',
getComponent (nextState, cb) {
require.ensure([], (require) => {
cb(null, require('./routes/UIElement/search/'))
}, 'UIElement-search')
},
}, {
path: 'UIElement/dropOption',
getComponent (nextState, cb) {
require.ensure([], (require) => {
cb(null, require('./routes/UIElement/dropOption/'))
}, 'UIElement-dropOption')
},
}, {
path: 'UIElement/layer',
getComponent (nextState, cb) {
require.ensure([], (require) => {
cb(null, require('./routes/UIElement/layer/'))
}, 'UIElement-layer')
},
}, {
path: 'UIElement/dataTable',
getComponent (nextState, cb) {
require.ensure([], (require) => {
cb(null, require('./routes/UIElement/dataTable/'))
}, 'UIElement-dataTable')
},
}, {
path: 'UIElement/editor',
getComponent (nextState, cb) {
require.ensure([], (require) => {
cb(null, require('./routes/UIElement/editor/'))
}, 'UIElement-editor')
},
}, {
path: 'chart/lineChart',
getComponent (nextState, cb) {
require.ensure([], (require) => {
cb(null, require('./routes/chart/lineChart/'))
}, 'chart-lineChart')
},
}, {
path: 'chart/barChart',
getComponent (nextState, cb) {
require.ensure([], (require) => {
cb(null, require('./routes/chart/barChart/'))
}, 'chart-barChart')
},
}, {
path: 'chart/areaChart',
getComponent (nextState, cb) {
require.ensure([], (require) => {
cb(null, require('./routes/chart/areaChart/'))
}, 'chart-areaChart')
},
}, {
path: '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/'))
}, 'error')
},
},
],
path: '/dashboard',
models: () => [import('./models/dashboard')],
component: () => import('./routes/dashboard/'),
}, {
path: '/user',
models: () => [import('./models/user')],
component: () => import('./routes/user/'),
}, {
path: '/user/:id',
models: () => [import('./models/user/detail')],
component: () => import('./routes/user/detail/'),
}, {
path: '/login',
models: () => [import('./models/login')],
component: () => import('./routes/login/'),
}, {
path: '/request',
component: () => import('./routes/request/'),
}, {
path: '/UIElement/iconfont',
component: () => import('./routes/UIElement/iconfont/'),
}, {
path: '/UIElement/search',
component: () => import('./routes/UIElement/search/'),
}, {
path: '/UIElement/dropOption',
component: () => import('./routes/UIElement/dropOption/'),
}, {
path: '/UIElement/layer',
component: () => import('./routes/UIElement/layer/'),
}, {
path: '/UIElement/dataTable',
component: () => import('./routes/UIElement/dataTable/'),
}, {
path: '/UIElement/editor',
component: () => import('./routes/UIElement/editor/'),
}, {
path: '/chart/lineChart',
component: () => import('./routes/chart/lineChart/'),
}, {
path: '/chart/barChart',
component: () => import('./routes/chart/barChart/'),
}, {
path: '/chart/areaChart',
component: () => import('./routes/chart/areaChart/'),
}, {
path: '/post',
models: () => [import('./models/post')],
component: () => import('./routes/post/'),
},
]
return <Router history={history} routes={routes} />
return (
<ConnectedRouter history={history}>
<App>
<Switch>
<Route exact path="/" render={() => (<Redirect to="/dashboard" />)} />
{
routes.map(({ path, ...dynamics }, key) => (
<Route key={key}
exact
path={path}
component={dynamic({
app,
...dynamics,
})}
/>
))
}
<Route component={error} />
</Switch>
</App>
</ConnectedRouter>
)
}
Routers.propTypes = {
+8 -3
View File
@@ -7,6 +7,7 @@ import { connect } from 'dva'
import { Layout, Loader } from 'components'
import { classnames, config } from 'utils'
import { Helmet } from 'react-helmet'
import { withRouter } from 'dva/router'
import '../themes/index.less'
import './app.less'
import Error from './error'
@@ -36,6 +37,7 @@ const App = ({ children, dispatch, app, loading, location }) => {
const headerProps = {
menu,
user,
location,
siderFold,
isNavbar,
menuPopoverVisible,
@@ -56,6 +58,7 @@ const App = ({ children, dispatch, app, loading, location }) => {
const siderProps = {
menu,
location,
siderFold,
darkTheme,
navOpenKeys,
@@ -70,15 +73,17 @@ const App = ({ children, dispatch, app, loading, location }) => {
const breadProps = {
menu,
location,
}
if (openPages && openPages.includes(pathname)) {
return (<div>
<Loader spinning={loading.effects['app/query']} />
<Loader fullScreen spinning={loading.effects['app/query']} />
{children}
</div>)
}
return (
<div>
<Loader fullScreen spinning={loading.effects['app/query']} />
<Helmet>
<title>ANTD ADMIN</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -88,7 +93,7 @@ const App = ({ children, dispatch, app, loading, location }) => {
</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} />
@@ -113,4 +118,4 @@ App.propTypes = {
loading: PropTypes.object,
}
export default connect(({ app, loading }) => ({ app, loading }))(App)
export default withRouter(connect(({ app, loading }) => ({ app, loading }))(App))
+72 -72
View File
@@ -1,72 +1,72 @@
@import "../themes/vars.less";
:global {
#nprogress {
pointer-events: none;
.bar {
background: @primary-color;
position: fixed;
z-index: 1024;
top: 0;
left: 0;
right: 0;
width: 100%;
height: 2px;
}
.peg {
display: block;
position: absolute;
right: 0;
width: 100px;
height: 100%;
box-shadow: 0 0 10px @primary-color,0 0 5px @primary-color;
opacity: 1.0;
transform: rotate(3deg) translate(0px,-4px);
}
.spinner {
display: block;
position: fixed;
z-index: 1031;
top: 15px;
right: 15px;
}
.spinner-icon {
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-top-color: @primary-color;
border-left-color: @primary-color;
border-radius: 50%;
:local {
animation: nprogress-spinner 400ms linear infinite;
}
}
}
.nprogress-custom-parent {
overflow: hidden;
position: relative;
#nprogress {
.bar,
.spinner {
position: absolute;
}
}
}
}
@keyframes nprogress-spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@import "~themes/vars";
:global {
#nprogress {
pointer-events: none;
.bar {
background: @primary-color;
position: fixed;
z-index: 1024;
top: 0;
left: 0;
right: 0;
width: 100%;
height: 2px;
}
.peg {
display: block;
position: absolute;
right: 0;
width: 100px;
height: 100%;
box-shadow: 0 0 10px @primary-color,0 0 5px @primary-color;
opacity: 1.0;
transform: rotate(3deg) translate(0px,-4px);
}
.spinner {
display: block;
position: fixed;
z-index: 1031;
top: 15px;
right: 15px;
}
.spinner-icon {
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-top-color: @primary-color;
border-left-color: @primary-color;
border-radius: 50%;
:local {
animation: nprogress-spinner 400ms linear infinite;
}
}
}
.nprogress-custom-parent {
overflow: hidden;
position: relative;
#nprogress {
.bar,
.spinner {
position: absolute;
}
}
}
}
@keyframes nprogress-spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
+43 -43
View File
@@ -1,43 +1,43 @@
@import "../../../themes/vars.less";
.comments {
:global .ant-table-thead > tr > th {
background: #fff;
border-bottom: solid 1px @border-color-base;
}
.avatar {
width: 48px;
height: 48px;
background-position: center;
background-size: cover;
border-radius: 50%;
background: #f8f8f8;
display: inline-block;
}
.content {
text-align: left;
color: #757575;
}
.date {
color: #a3a3a3;
line-height: 30px;
}
.daterow {
display: flex;
justify-content: space-between;
}
.name {
font-size: 14px;
color: #474747;
text-align: left;
}
.avatarcolumn {
vertical-align: top;
}
}
@import "~themes/vars";
.comments {
:global .ant-table-thead > tr > th {
background: #fff;
border-bottom: solid 1px @border-color-base;
}
.avatar {
width: 48px;
height: 48px;
background-position: center;
background-size: cover;
border-radius: 50%;
background: #f8f8f8;
display: inline-block;
}
.content {
text-align: left;
color: #757575;
}
.date {
color: #a3a3a3;
line-height: 30px;
}
.daterow {
display: flex;
justify-content: space-between;
}
.name {
font-size: 14px;
color: #474747;
text-align: left;
}
.avatarcolumn {
vertical-align: top;
}
}
+50 -50
View File
@@ -1,50 +1,50 @@
@import "../../../themes/vars.less";
.sales {
.title {
margin-left: 32px;
font-size: 16px;
}
}
.radiusdot {
width: 12px;
height: 12px;
margin-right: 8px;
border-radius: 50%;
display: inline-block;
}
.legend {
color: @dark-half;
text-align: right;
color: #999;
font-size: 14px;
li {
height: 48px;
line-height: 48px;
display: inline-block;
&+li {
margin-left: 24px;
}
}
}
.tooltip {
background: #fff;
padding: 20px;
font-size: 14px;
.tiptitle {
font-weight: 700;
font-size: 16px;
margin-bottom: 8px;
}
.tipitem {
height: 32px;
line-height: 32px;
}
}
@import "~themes/vars";
.sales {
.title {
margin-left: 32px;
font-size: 16px;
}
}
.radiusdot {
width: 12px;
height: 12px;
margin-right: 8px;
border-radius: 50%;
display: inline-block;
}
.legend {
color: @dark-half;
text-align: right;
color: #999;
font-size: 14px;
li {
height: 48px;
line-height: 48px;
display: inline-block;
&+li {
margin-left: 24px;
}
}
}
.tooltip {
background: #fff;
padding: 20px;
font-size: 14px;
.tiptitle {
font-weight: 700;
font-size: 16px;
margin-bottom: 8px;
}
.tipitem {
height: 32px;
line-height: 32px;
}
}
+32 -32
View File
@@ -1,32 +1,32 @@
@import "../../../themes/vars.less";
.numberCard {
padding: 32px;
margin-bottom: 24px;
cursor: pointer;
.iconWarp {
font-size: 54px;
float: left;
}
.content {
width: 100%;
padding-left: 78px;
.title {
line-height: 16px;
font-size: 16px;
margin-bottom: 8px;
height: 16px;
.text-overflow();
}
.number {
line-height: 32px;
font-size: 24px;
height: 32px;
.text-overflow();
}
}
}
@import "~themes/vars";
.numberCard {
padding: 32px;
margin-bottom: 24px;
cursor: pointer;
.iconWarp {
font-size: 54px;
float: left;
}
.content {
width: 100%;
padding-left: 78px;
.title {
line-height: 16px;
font-size: 16px;
margin-bottom: 8px;
height: 16px;
.text-overflow();
}
.number {
line-height: 32px;
font-size: 24px;
height: 32px;
.text-overflow();
}
}
}
+52 -52
View File
@@ -1,52 +1,52 @@
@import "../../../themes/vars.less";
.quote {
color: #fff;
height: 100%;
width: 100%;
padding: 24px;
font-size: 16px;
font-weight: 700;
.inner {
text-overflow: ellipsis;
word-wrap: normal;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 4;
overflow: hidden;
text-indent: 24px;
}
.footer {
position: relative;
margin-top: 14px;
.description {
width: 100%;
p {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 64px;
text-align: right;
&:last-child {
font-weight: 100;
}
}
}
.avatar {
width: 48px;
height: 48px;
background-position: center;
background-size: cover;
border-radius: 50%;
position: absolute;
right: 0;
top: 0;
}
}
}
@import "~themes/vars";
.quote {
color: #fff;
height: 100%;
width: 100%;
padding: 24px;
font-size: 16px;
font-weight: 700;
.inner {
text-overflow: ellipsis;
word-wrap: normal;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 4;
overflow: hidden;
text-indent: 24px;
}
.footer {
position: relative;
margin-top: 14px;
.description {
width: 100%;
p {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 64px;
text-align: right;
&:last-child {
font-weight: 100;
}
}
}
.avatar {
width: 48px;
height: 48px;
background-position: center;
background-size: cover;
border-radius: 50%;
position: absolute;
right: 0;
top: 0;
}
}
}
@@ -1,8 +1,8 @@
@import "../../../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;
}
}
+50 -50
View File
@@ -1,50 +1,50 @@
@import "../../../themes/vars.less";
.sales {
.title {
margin-left: 32px;
font-size: 16px;
}
}
.radiusdot {
width: 12px;
height: 12px;
margin-right: 8px;
border-radius: 50%;
display: inline-block;
}
.legend {
color: @dark-half;
text-align: right;
color: #999;
font-size: 14px;
li {
height: 48px;
line-height: 48px;
display: inline-block;
&+li {
margin-left: 24px;
}
}
}
.tooltip {
background: #fff;
padding: 20px;
font-size: 14px;
.tiptitle {
font-weight: 700;
font-size: 16px;
margin-bottom: 8px;
}
.tipitem {
height: 32px;
line-height: 32px;
}
}
@import "~themes/vars";
.sales {
.title {
margin-left: 32px;
font-size: 16px;
}
}
.radiusdot {
width: 12px;
height: 12px;
margin-right: 8px;
border-radius: 50%;
display: inline-block;
}
.legend {
color: @dark-half;
text-align: right;
color: #999;
font-size: 14px;
li {
height: 48px;
line-height: 48px;
display: inline-block;
&+li {
margin-left: 24px;
}
}
}
.tooltip {
background: #fff;
padding: 20px;
font-size: 14px;
.tiptitle {
font-weight: 700;
font-size: 16px;
margin-bottom: 8px;
}
.tipitem {
height: 32px;
line-height: 32px;
}
}
+97 -97
View File
@@ -1,97 +1,97 @@
@import "../../../themes/vars.less";
.user {
.header {
display: flex;
justify-content: center;
text-align: center;
color: #fff;
height: 200px;
background-image: ~ "url(./user-background.png)";
background-repeat: no-repeat;
background-size: cover;
align-items: center;
.headerinner {
z-index: 2;
}
&::after {
content: '';
background: @purple;
position: absolute;
width: 100%;
height: 200px;
left: 0;
top: 0;
opacity: 0.4;
z-index: 1;
}
.avatar {
width: 64px;
height: 64px;
background-position: center;
background-size: cover;
border-radius: 50%;
display: inline-block;
}
.name {
font-size: 16px;
}
}
.number {
display: flex;
height: 116px;
justify-content: space-between;
border-bottom: solid 1px #f5f5f5;
.item {
text-align: center;
height: 116px;
width: 100%;
position: relative;
padding: 30px 0;
&+.item {
&::before {
content: '';
display: block;
width: 1px;
height: 116px;
position: absolute;
background: #f5f5f5;
top: 0;
}
}
p {
color: #757575;
&:first-child {
font-size: 16px;
}
&:last-child {
font-size: 20px;
font-weight: 700;
}
}
}
}
.footer {
height: 116px;
display: flex;
justify-content: center;
align-items: center;
:global .ant-btn {
color: @purple;
border-color: @purple;
padding: 6px 16px;
}
}
}
@import "~themes/vars";
.user {
.header {
display: flex;
justify-content: center;
text-align: center;
color: #fff;
height: 200px;
background-image: ~ "url(./user-background.png)";
background-repeat: no-repeat;
background-size: cover;
align-items: center;
.headerinner {
z-index: 2;
}
&::after {
content: '';
background: @purple;
position: absolute;
width: 100%;
height: 200px;
left: 0;
top: 0;
opacity: 0.4;
z-index: 1;
}
.avatar {
width: 64px;
height: 64px;
background-position: center;
background-size: cover;
border-radius: 50%;
display: inline-block;
}
.name {
font-size: 16px;
}
}
.number {
display: flex;
height: 116px;
justify-content: space-between;
border-bottom: solid 1px #f5f5f5;
.item {
text-align: center;
height: 116px;
width: 100%;
position: relative;
padding: 30px 0;
&+.item {
&::before {
content: '';
display: block;
width: 1px;
height: 116px;
position: absolute;
background: #f5f5f5;
top: 0;
}
}
p {
color: #757575;
&:first-child {
font-size: 16px;
}
&:last-child {
font-size: 20px;
font-weight: 700;
}
}
}
}
.footer {
height: 116px;
display: flex;
justify-content: center;
align-items: center;
:global .ant-btn {
color: @purple;
border-color: @purple;
padding: 6px 16px;
}
}
}
+20 -15
View File
@@ -1,22 +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 = {
@@ -25,6 +29,7 @@ Weather.propTypes = {
dateTime: PropTypes.string,
temperature: PropTypes.string,
name: PropTypes.string,
loading: PropTypes.bool,
}
export default Weather
+48 -48
View File
@@ -1,48 +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;
color: #fff;
}
.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;
}
}
}
+80 -76
View File
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import { connect } from 'dva'
import { Row, Col, Card } from 'antd'
import { color } from 'utils'
import { Page } from 'components'
import { NumberCard, Quote, Sales, Weather, RecentSales, Comments, Completed, Browser, Cpu, User } from './components'
import styles from './index.less'
@@ -13,92 +14,95 @@ const bodyStyle = {
},
}
function Dashboard ({ dashboard }) {
function Dashboard ({ dashboard, loading }) {
const { weather, sales, quote, numbers, recentSales, comments, completed, browser, cpu, user } = dashboard
const numberCards = numbers.map((item, key) => (<Col key={key} lg={6} md={12}>
<NumberCard {...item} />
</Col>))
return (
<Row gutter={24}>
{numberCards}
<Col lg={18} md={24}>
<Card bordered={false}
bodyStyle={{
padding: '24px 36px 24px 0',
}}
>
<Sales data={sales} />
</Card>
</Col>
<Col lg={6} md={24}>
<Row gutter={24}>
<Col lg={24} md={12}>
<Card bordered={false}
className={styles.weather}
bodyStyle={{
padding: 0,
height: 204,
background: color.blue,
}}
>
<Weather {...weather} />
</Card>
</Col>
<Col lg={24} md={12}>
<Card bordered={false}
className={styles.quote}
bodyStyle={{
padding: 0,
height: 204,
background: color.peach,
}}
>
<Quote {...quote} />
</Card>
</Col>
</Row>
</Col>
<Col lg={12} md={24}>
<Card bordered={false} {...bodyStyle}>
<RecentSales data={recentSales} />
</Card>
</Col>
<Col lg={12} md={24}>
<Card bordered={false} {...bodyStyle}>
<Comments data={comments} />
</Card>
</Col>
<Col lg={24} md={24}>
<Card bordered={false}
bodyStyle={{
padding: '24px 36px 24px 0',
}}
>
<Completed data={completed} />
</Card>
</Col>
<Col lg={8} md={24}>
<Card bordered={false} {...bodyStyle}>
<Browser data={browser} />
</Card>
</Col>
<Col lg={8} md={24}>
<Card bordered={false} {...bodyStyle}>
<Cpu {...cpu} />
</Card>
</Col>
<Col lg={8} md={24}>
<Card bordered={false} bodyStyle={{ ...bodyStyle.bodyStyle, padding: 0 }}>
<User {...user} />
</Card>
</Col>
</Row>
<Page loading={loading.models.dashboard && sales.length === 0}>
<Row gutter={24}>
{numberCards}
<Col lg={18} md={24}>
<Card bordered={false}
bodyStyle={{
padding: '24px 36px 24px 0',
}}
>
<Sales data={sales} />
</Card>
</Col>
<Col lg={6} md={24}>
<Row gutter={24}>
<Col lg={24} md={12}>
<Card bordered={false}
className={styles.weather}
bodyStyle={{
padding: 0,
height: 204,
background: color.blue,
}}
>
<Weather {...weather} loading={loading.effects['dashboard/queryWeather']} />
</Card>
</Col>
<Col lg={24} md={12}>
<Card bordered={false}
className={styles.quote}
bodyStyle={{
padding: 0,
height: 204,
background: color.peach,
}}
>
<Quote {...quote} />
</Card>
</Col>
</Row>
</Col>
<Col lg={12} md={24}>
<Card bordered={false} {...bodyStyle}>
<RecentSales data={recentSales} />
</Card>
</Col>
<Col lg={12} md={24}>
<Card bordered={false} {...bodyStyle}>
<Comments data={comments} />
</Card>
</Col>
<Col lg={24} md={24}>
<Card bordered={false}
bodyStyle={{
padding: '24px 36px 24px 0',
}}
>
<Completed data={completed} />
</Card>
</Col>
<Col lg={8} md={24}>
<Card bordered={false} {...bodyStyle}>
<Browser data={browser} />
</Card>
</Col>
<Col lg={8} md={24}>
<Card bordered={false} {...bodyStyle}>
<Cpu {...cpu} />
</Card>
</Col>
<Col lg={8} md={24}>
<Card bordered={false} bodyStyle={{ ...bodyStyle.bodyStyle, padding: 0 }}>
<User {...user} />
</Card>
</Col>
</Row>
</Page>
)
}
Dashboard.propTypes = {
dashboard: PropTypes.object,
loading: PropTypes.object,
}
export default connect(({ dashboard }) => ({ dashboard }))(Dashboard)
export default connect(({ dashboard, loading }) => ({ dashboard, loading }))(Dashboard)
+4 -6
View File
@@ -8,15 +8,13 @@ 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))
+5 -2
View File
@@ -3,13 +3,16 @@ import PropTypes from 'prop-types'
import { Table, Modal } from 'antd'
import classnames from 'classnames'
import { DropOption } from 'components'
import { Link } from 'dva/router'
import AnimTableBody from '../../components/DataTable/AnimTableBody'
import { Link } from 'react-router-dom'
import queryString from 'query-string'
import AnimTableBody from 'components/DataTable/AnimTableBody'
import styles from './List.less'
const confirm = Modal.confirm
const List = ({ onDeleteItem, onEditItem, isMotion, location, ...tableProps }) => {
location.query = queryString.parse(location.search)
const handleMenuClick = (record, e) => {
if (e.key === '1') {
onEditItem(record)
+6 -2
View File
@@ -3,11 +3,15 @@ import PropTypes from 'prop-types'
import { routerRedux } from 'dva/router'
import { connect } from 'dva'
import { Row, Col, Button, Popconfirm } from 'antd'
import { Page } from 'components'
import queryString from 'query-string'
import List from './List'
import Filter from './Filter'
import Modal from './Modal'
const User = ({ location, dispatch, user, loading }) => {
location.query = queryString.parse(location.search)
const { list, pagination, currentItem, modalVisible, modalType, isMotion, selectedRowKeys } = user
const { pageSize } = pagination
@@ -125,7 +129,7 @@ const User = ({ location, dispatch, user, loading }) => {
}
return (
<div className="content-inner">
<Page inner>
<Filter {...filterProps} />
{
selectedRowKeys.length > 0 &&
@@ -140,7 +144,7 @@ const User = ({ location, dispatch, user, loading }) => {
}
<List {...listProps} />
{modalVisible && <Modal {...modalProps} />}
</div>
</Page>
)
}
-14
View File
@@ -3,20 +3,6 @@ import { request, config } from 'utils'
const { api } = config
const { dashboard } = api
export async function myCity (params) {
return request({
url: 'http://www.zuimeitianqi.com/zuimei/myCity',
data: params,
})
}
export async function queryWeather (params) {
return request({
url: 'http://www.zuimeitianqi.com/zuimei/queryWeather',
data: params,
})
}
export async function query (params) {
return request({
url: dashboard,
+12
View File
@@ -0,0 +1,12 @@
import { request, config } from 'utils'
const { APIV1 } = config
export async function query (params) {
params.key = 'i7sau1babuzwhycn'
return request({
url: `${APIV1}/weather/now.json`,
method: 'get',
data: params,
})
}
+135 -135
View File
@@ -1,135 +1,135 @@
@import "./vars.less";
body {
height: 100%;
overflow-y: hidden;
background-color: #f8f8f8;
}
::-webkit-scrollbar-thumb {
background-color: #e6e6e6;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
:global {
.ant-breadcrumb {
& > span {
&:last-child {
color: #999;
font-weight: normal;
}
}
}
.ant-breadcrumb-link {
.anticon + span {
margin-left: 4px;
}
}
.ant-table {
.ant-table-thead > tr > th {
text-align: center;
}
.ant-table-tbody > tr > td {
text-align: center;
}
&.ant-table-small {
.ant-table-thead > tr > th {
background: #f7f7f7;
}
.ant-table-body > table {
padding: 0;
}
}
}
.ant-table-pagination {
float: none!important;
display: table;
margin: 16px auto !important;
}
.ant-popover-inner {
border: none;
border-radius: 0;
box-shadow: 0 0 20px rgba(100, 100, 100, 0.2);
}
.vertical-center-modal {
display: flex;
align-items: center;
justify-content: center;
.ant-modal {
top: 0;
.ant-modal-body {
max-height: 80vh;
overflow-y: auto;
}
}
}
.ant-form-item-control {
vertical-align: middle;
}
.ant-modal-mask {
background-color: rgba(55, 55, 55, 0.2);
}
.ant-modal-content {
box-shadow: none;
}
.ant-select-dropdown-menu-item {
padding: 12px 16px !important;
}
.margin-right {
margin-right: 16px;
}
a:focus {
text-decoration: none;
}
}
@media (min-width: 1600px) {
:global {
.ant-col-xl-48 {
width: 20%;
}
.ant-col-xl-96 {
width: 40%;
}
}
}
@media (max-width: 767px) {
:global {
.ant-pagination-item,
.ant-pagination-next,
.ant-pagination-options,
.ant-pagination-prev {
margin-bottom: 8px;
}
.ant-card {
.ant-card-head {
padding: 0 12px;
}
.ant-card-body {
padding: 12px;
}
}
}
}
@import "~themes/vars";
body {
height: 100%;
overflow-y: hidden;
background-color: #f8f8f8;
}
::-webkit-scrollbar-thumb {
background-color: #e6e6e6;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
:global {
.ant-breadcrumb {
& > span {
&:last-child {
color: #999;
font-weight: normal;
}
}
}
.ant-breadcrumb-link {
.anticon + span {
margin-left: 4px;
}
}
.ant-table {
.ant-table-thead > tr > th {
text-align: center;
}
.ant-table-tbody > tr > td {
text-align: center;
}
&.ant-table-small {
.ant-table-thead > tr > th {
background: #f7f7f7;
}
.ant-table-body > table {
padding: 0;
}
}
}
.ant-table-pagination {
float: none!important;
display: table;
margin: 16px auto !important;
}
.ant-popover-inner {
border: none;
border-radius: 0;
box-shadow: 0 0 20px rgba(100, 100, 100, 0.2);
}
.vertical-center-modal {
display: flex;
align-items: center;
justify-content: center;
.ant-modal {
top: 0;
.ant-modal-body {
max-height: 80vh;
overflow-y: auto;
}
}
}
.ant-form-item-control {
vertical-align: middle;
}
.ant-modal-mask {
background-color: rgba(55, 55, 55, 0.2);
}
.ant-modal-content {
box-shadow: none;
}
.ant-select-dropdown-menu-item {
padding: 12px 16px !important;
}
.margin-right {
margin-right: 16px;
}
a:focus {
text-decoration: none;
}
}
@media (min-width: 1600px) {
:global {
.ant-col-xl-48 {
width: 20%;
}
.ant-col-xl-96 {
width: 40%;
}
}
}
@media (max-width: 767px) {
:global {
.ant-pagination-item,
.ant-pagination-next,
.ant-pagination-options,
.ant-pagination-prev {
margin-bottom: 8px;
}
.ant-card {
.ant-card-head {
padding: 0 12px;
}
.ant-card-body {
padding: 12px;
}
}
}
}
+14 -14
View File
@@ -1,14 +1,14 @@
@import "./default.less"; // antd-admin
@dark-half: #494949;
@purple: #d897eb;
@shadow-1: 4px 4px 20px 0 rgba(0,0,0,0.01);
@shadow-2: 4px 4px 40px 0 rgba(0,0,0,0.05);
@transition-ease-in: all 0.3s ease-out;
@transition-ease-out: all 0.3s ease-out;
@ease-in: ease-in;
.text-overflow {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
@import "~themes/default"; // antd-admin
@dark-half: #494949;
@purple: #d897eb;
@shadow-1: 4px 4px 20px 0 rgba(0,0,0,0.01);
@shadow-2: 4px 4px 40px 0 rgba(0,0,0,0.05);
@transition-ease-in: all 0.3s ease-out;
@transition-ease-out: all 0.3s ease-out;
@ease-in: ease-in;
.text-overflow {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
+2 -2
View File
@@ -1,2 +1,2 @@
@import "./default.less";
@import "./mixin.less";
@import "~themes/default";
@import "~themes/mixin";
+3 -1
View File
@@ -8,10 +8,11 @@ module.exports = {
logo: '/logo.png',
iconFontCSS: '/iconfont.css',
iconFontJS: '/iconfont.js',
YQL: ['http://www.zuimeitianqi.com'],
CORS: [],
openPages: ['/login'],
apiPrefix: '/api/v1',
APIV1,
APIV2,
api: {
userLogin: `${APIV1}/user/login`,
userLogout: `${APIV1}/user/logout`,
@@ -21,6 +22,7 @@ module.exports = {
user: `${APIV1}/user/:id`,
dashboard: `${APIV1}/dashboard`,
menus: `${APIV1}/menus`,
weather: `${APIV1}/weather`,
v1test: `${APIV1}/test`,
v2test: `${APIV2}/test`,
},
+3 -4
View File
@@ -95,13 +95,12 @@ export default function request (options) {
list: data,
}
}
return {
return Promise.resolve({
success: true,
message: statusText,
statusCode: status,
...data,
}
})
}).catch((error) => {
const { response } = error
let msg
@@ -114,6 +113,6 @@ export default function request (options) {
statusCode = 600
msg = error.message || 'Network Error'
}
return { success: false, statusCode, message: msg }
return Promise.reject({ success: false, statusCode, message: msg })
})
}
+82
View File
@@ -0,0 +1,82 @@
const fs = require('fs')
const path = require('path')
const beautify = require('js-beautify').js_beautify
const config = require('./package.json')
const dist = path.join(`${__dirname}/dist`)
const maxVersion = 5
const writeVersion = () => new Promise((resolve, reject) => {
const { version } = config
const numbers = version.split('.')
numbers[2] = Number(numbers[2]) + 1
config.version = numbers.join('.')
fs.writeFile(path.join(__dirname, 'package.json'), beautify(JSON.stringify(config), { indent_size: 2 }), (err) => {
if (err) {
reject()
}
resolve()
console.log(`version: ${config.version}`)
})
})
const removeFolder = (folderPath) => {
let files = []
if (fs.existsSync(folderPath)) {
files = fs.readdirSync(folderPath)
files.forEach((file) => {
const curPath = `${folderPath}/${file}`
if (fs.statSync(curPath).isDirectory()) {
removeFolder(curPath)
} else {
fs.unlinkSync(curPath)
}
})
fs.rmdirSync(folderPath)
}
}
const start = async () => {
const files = fs.readdirSync(dist)
const promises = files.map(file => new Promise((resolve, reject) => {
fs.stat(`${dist}/${file}`, (err, stats) => {
if (err) {
reject()
} else {
resolve(!stats.isFile()
? file
: null)
}
})
}))
const result = await Promise.all(promises)
const folders = result.filter((item) => {
if (item) {
return item.split('.').length === 3
}
return false
}).sort((a, b) => {
const an = a.split('.').map(_ => Number(_))
const bn = b.split('.').map(_ => Number(_))
if (an[0] === bn[0]) {
if (an[1] === bn[1]) {
return an[2] < bn[2]
}
return an[1] < bn[1]
}
return an[0] < bn[0]
}).filter((item, index) => {
return index > (maxVersion - 1)
})
for (const item of folders) {
await removeFolder(`${dist}/${item}`)
}
await writeVersion()
}
start()
+29 -41
View File
@@ -1,61 +1,45 @@
const HtmlWebpackPlugin = require('html-webpack-plugin')
const HtmlWebpackTemplate = require('html-webpack-template')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const webpack = require('webpack')
module.exports = (webpackConfig, env) => {
const production = env === 'production'
// FilenameHash
webpackConfig.output.chunkFilename = '[name].[hash].js'
webpackConfig.output.chunkFilename = '[name].[chunkhash].js'
if (env === 'production' && webpackConfig.module) {
if (production) {
if (webpackConfig.module) {
// ClassnameHash
webpackConfig.module.rules.map((item) => {
if (item.use && item.use[0] === 'style') {
return item.use.map((iitem) => {
if (iitem && iitem.loader === 'css') {
iitem.options.localIdentName = '[hash:base64:5]'
}
return iitem
})
}
return item
})
webpackConfig.module.rules.map((item) => {
if (String(item.test) === '/\\.less$/' || String(item.test) === '/\\.css/') {
item.use.filter(iitem => iitem.loader === 'css')[0].options.localIdentName = '[hash:base64:5]'
}
return item
})
}
webpackConfig.plugins.push(
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false,
})
)
}
// PreLoaders
// webpackConfig.module.preLoaders = [{
// test: /\.js$/,
// enforce: 'pre',
// loader: 'eslint',
// }]
webpackConfig.plugins = webpackConfig.plugins.concat([
new CopyWebpackPlugin([
{
from: 'src/public',
to: env === 'production' ? '../' : webpackConfig.output.outputPath,
to: production ? '../' : webpackConfig.output.outputPath,
},
]),
new HtmlWebpackPlugin({
hash: true,
mobile: true,
title: 'antd-admin',
inject: false,
appMountId: 'root',
template: `!!ejs-loader!${HtmlWebpackTemplate}`,
filename: env === 'production' ? '../index.html' : 'index.html',
minify: {
template: `${__dirname}/src/entry.ejs`,
filename: production ? '../index.html' : 'index.html',
minify: production ? {
collapseWhitespace: true,
},
scripts: env === 'production' ? null : ['roadhog.dll.js'],
meta: [
{
name: 'description',
content: 'A admin dashboard application demo built upon Ant Design and Dva.js',
}, {
name: 'viewport',
content: 'width=device-width, initial-scale=1.0',
},
],
} : null,
hash: true,
headScripts: production ? null : ['/roadhog.dll.js'],
}),
])
@@ -65,6 +49,10 @@ module.exports = (webpackConfig, env) => {
utils: `${__dirname}/src/utils`,
config: `${__dirname}/src/utils/config`,
enums: `${__dirname}/src/utils/enums`,
services: `${__dirname}/src/services`,
models: `${__dirname}/src/models`,
routes: `${__dirname}/src/routes`,
themes: `${__dirname}/src/themes`,
}
return webpackConfig