Compare commits

...
21 Commits
Author SHA1 Message Date
zuiidea 0cf02e7324 4.3.4 2017-09-04 11:47:24 +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 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
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
21 changed files with 587 additions and 708 deletions
+1
View File
@@ -1 +1,2 @@
src/**/*-test.js
src/public
+12 -12
View File
@@ -13,18 +13,18 @@ export default {
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" }
// }
// },
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: [
+23 -8
View File
@@ -9,7 +9,7 @@
[![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,14 @@
## 更新日志
### 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`
@@ -53,11 +61,11 @@
`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)
[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 +107,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
```
开发:
@@ -141,7 +154,9 @@ npm run lint
- 怎么按版本打包,上线时不影响正在访问的用户? [#449](https://github.com/zuiidea/antd-admin/issues/449)
- windows处理CRLF?[参考](http://blog.csdn.net/lysc_forever/article/details/42835203)
git config --global core.autocrlf false
```bash
git config --global core.autocrlf false
```
## 参考
+3 -2
View File
@@ -1,7 +1,7 @@
{
"private": true,
"name": "antd-admin",
"version": "4.3.2",
"version": "4.3.4",
"dependencies": {
"antd": "^2.11.2",
"axios": "^0.16.2",
@@ -27,7 +27,8 @@
"react-dom": "^15.6.1",
"react-draft-wysiwyg": "^1.10.0",
"react-helmet": "^5.1.3",
"recharts": "^0.22.4"
"recharts": "^0.22.4",
"roadhog": "^1.2.1"
},
"devDependencies": {
"babel-eslint": "^7.2.3",
+2 -2
View File
@@ -1,4 +1,3 @@
/* global location */
import React from 'react'
import PropTypes from 'prop-types'
import { Breadcrumb, Icon } from 'antd'
@@ -7,7 +6,7 @@ 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
@@ -68,6 +67,7 @@ const Bread = ({ menu }) => {
Bread.propTypes = {
menu: PropTypes.array,
location: PropTypes.object,
}
export default Bread
+6 -2
View File
@@ -1,4 +1,3 @@
/* global location */
import React from 'react'
import PropTypes from 'prop-types'
import { Menu, Icon } from 'antd'
@@ -6,7 +5,7 @@ import { Link } from 'dva/router'
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 = {}
@@ -103,6 +102,10 @@ const Menus = ({ siderFold, darkTheme, handleClickNavMenu, navOpenKeys, changeOp
defaultSelectedKeys = getPathArray(menu, currentMenu, 'mpid', 'id')
}
if (!defaultSelectedKeys) {
defaultSelectedKeys = ['1']
}
return (
<Menu
{...menuProps}
@@ -123,6 +126,7 @@ Menus.propTypes = {
handleClickNavMenu: PropTypes.func,
navOpenKeys: PropTypes.array,
changeOpenKeys: PropTypes.func,
location: PropTypes.object,
}
export default Menus
+173 -152
View File
@@ -1,152 +1,173 @@
/* global window */
/* global document */
/* global location */
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'
const { prefix } = config
export default {
namespace: 'app',
state: {
user: {},
permissions: {
visit: [],
},
menu: [
{
id: 1,
icon: 'laptop',
name: 'Dashboard',
router: '/dashboard',
},
],
menuPopoverVisible: false,
siderFold: window.localStorage.getItem(`${prefix}siderFold`) === 'true',
darkTheme: window.localStorage.getItem(`${prefix}darkTheme`) === 'true',
isNavbar: document.body.clientWidth < 769,
navOpenKeys: JSON.parse(window.localStorage.getItem(`${prefix}navOpenKeys`)) || [],
},
subscriptions: {
setup ({ dispatch }) {
dispatch({ type: 'query' })
let tid
window.onresize = () => {
clearTimeout(tid)
tid = setTimeout(() => {
dispatch({ type: 'changeNavbar' })
}, 300)
}
},
},
effects: {
* query ({
payload,
}, { call, put }) {
const { success, user } = yield call(query, payload)
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: 'updateState',
payload: {
user,
permissions,
menu,
},
})
if (location.pathname === '/login') {
yield put(routerRedux.push('/dashboard'))
}
} else if (config.openPages && config.openPages.indexOf(location.pathname) < 0) {
let from = location.pathname
window.location = `${location.origin}/login?from=${from}`
}
},
* logout ({
payload,
}, { call, put }) {
const data = yield call(logout, parse(payload))
if (data.success) {
yield put({ type: 'query' })
} else {
throw (data)
}
},
* changeNavbar (action, { put, select }) {
const { app } = yield (select(_ => _))
const isNavbar = document.body.clientWidth < 769
if (isNavbar !== app.isNavbar) {
yield put({ type: 'handleNavbar', payload: isNavbar })
}
},
},
reducers: {
updateState (state, { payload }) {
return {
...state,
...payload,
}
},
switchSider (state) {
window.localStorage.setItem(`${prefix}siderFold`, !state.siderFold)
return {
...state,
siderFold: !state.siderFold,
}
},
switchTheme (state) {
window.localStorage.setItem(`${prefix}darkTheme`, !state.darkTheme)
return {
...state,
darkTheme: !state.darkTheme,
}
},
switchMenuPopver (state) {
return {
...state,
menuPopoverVisible: !state.menuPopoverVisible,
}
},
handleNavbar (state, { payload }) {
return {
...state,
isNavbar: payload,
}
},
handleNavOpenKeys (state, { payload: navOpenKeys }) {
return {
...state,
...navOpenKeys,
}
},
},
}
/* global window */
/* global document */
/* global location */
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'
const { prefix } = config
export default {
namespace: 'app',
state: {
user: {},
permissions: {
visit: [],
},
menu: [
{
id: 1,
icon: 'laptop',
name: 'Dashboard',
router: '/dashboard',
},
],
menuPopoverVisible: false,
siderFold: window.localStorage.getItem(`${prefix}siderFold`) === 'true',
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: location.query,
},
})
})
},
setup ({ dispatch }) {
dispatch({ type: 'query' })
let tid
window.onresize = () => {
clearTimeout(tid)
tid = setTimeout(() => {
dispatch({ type: 'changeNavbar' })
}, 300)
}
},
},
effects: {
* query ({
payload,
}, { 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: 'updateState',
payload: {
user,
permissions,
menu,
},
})
if (location.pathname === '/login') {
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 ({
payload,
}, { call, put }) {
const data = yield call(logout, parse(payload))
if (data.success) {
yield put({ type: 'query' })
} else {
throw (data)
}
},
* changeNavbar (action, { put, select }) {
const { app } = yield (select(_ => _))
const isNavbar = document.body.clientWidth < 769
if (isNavbar !== app.isNavbar) {
yield put({ type: 'handleNavbar', payload: isNavbar })
}
},
},
reducers: {
updateState (state, { payload }) {
return {
...state,
...payload,
}
},
switchSider (state) {
window.localStorage.setItem(`${prefix}siderFold`, !state.siderFold)
return {
...state,
siderFold: !state.siderFold,
}
},
switchTheme (state) {
window.localStorage.setItem(`${prefix}darkTheme`, !state.darkTheme)
return {
...state,
darkTheme: !state.darkTheme,
}
},
switchMenuPopver (state) {
return {
...state,
menuPopoverVisible: !state.menuPopoverVisible,
}
},
handleNavbar (state, { payload }) {
return {
...state,
isNavbar: payload,
}
},
handleNavOpenKeys (state, { payload: navOpenKeys }) {
return {
...state,
...navOpenKeys,
}
},
},
}
+73 -227
View File
@@ -1,227 +1,73 @@
import { parse } from 'qs'
import { myCity, queryWeather, query } from 'services/dashboard'
// 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 {
namespace: 'dashboard',
state: {
weather: {
city: '成都',
temperature: '5',
name: '晴',
icon: 'http://www.zuimeitianqi.com/res/icon/0_big.png',
dateTime: new Date().format('MM-dd hh:mm'),
},
sales: [],
quote: {
avatar: 'http://img.hb.aicdn.com/bc442cf0cc6f7940dcc567e465048d1a8d634493198c4-sPx5BR_fw236',
},
numbers: [],
recentSales: [],
comments: [],
completed: [],
browser: [],
cpu: {},
user: {
avatar: 'http://img.hb.aicdn.com/bc442cf0cc6f7940dcc567e465048d1a8d634493198c4-sPx5BR_fw236',
},
},
subscriptions: {
setup ({ dispatch }) {
dispatch({ type: 'query' })
dispatch({ type: 'queryWeather' })
},
},
effects: {
* query ({
payload,
}, { call, put }) {
const data = yield call(query, parse(payload))
yield put({ type: 'queryWeather', 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,
}
},
},
}
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'
export default modelExtend(model, {
namespace: 'dashboard',
state: {
weather: {
city: '深圳',
temperature: '30',
name: '晴',
icon: '//s5.sencdn.com/web/icons/3d_50/2.png',
},
sales: [],
quote: {
avatar: 'http://img.hb.aicdn.com/bc442cf0cc6f7940dcc567e465048d1a8d634493198c4-sPx5BR_fw236',
},
numbers: [],
recentSales: [],
comments: [],
completed: [],
browser: [],
cpu: {},
user: {
avatar: 'http://img.hb.aicdn.com/bc442cf0cc6f7940dcc567e465048d1a8d634493198c4-sPx5BR_fw236',
},
},
subscriptions: {
setup ({ dispatch, history }) {
history.listen(({ pathname }) => {
if (pathname === '/dashboard' || pathname === '/') {
dispatch({ type: 'query' })
dispatch({ type: 'queryWeather' })
}
})
},
},
effects: {
* query ({
payload,
}, { call, put }) {
const data = yield call(query, parse(payload))
yield put({
type: 'updateState',
payload: data,
})
},
* 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,
},
})
}
},
},
})
+29 -45
View File
@@ -1,45 +1,29 @@
import { routerRedux } from 'dva/router'
import { queryURL } from 'utils'
import { login } from 'services/login'
export default {
namespace: 'login',
state: {
loginLoading: false,
},
effects: {
* login ({
payload,
}, { put, call }) {
yield put({ type: 'showLoginLoading' })
const data = yield call(login, payload)
yield put({ type: 'hideLoginLoading' })
if (data.success) {
const from = queryURL('from')
yield put({ type: 'app/query' })
if (from) {
yield put(routerRedux.push(from))
} else {
yield put(routerRedux.push('/dashboard'))
}
} else {
throw data
}
},
},
reducers: {
showLoginLoading (state) {
return {
...state,
loginLoading: true,
}
},
hideLoginLoading (state) {
return {
...state,
loginLoading: false,
}
},
},
}
import { routerRedux } from 'dva/router'
import { login } from 'services/login'
export default {
namespace: 'login',
state: {},
effects: {
* login ({
payload,
}, { put, call, select }) {
const data = yield call(login, payload)
const { locationQuery } = yield select(_ => _.app)
if (data.success) {
const { from } = locationQuery
yield put({ type: 'app/query' })
if (from && from !== '/login') {
yield put(routerRedux.push(from))
} else {
yield put(routerRedux.push('/dashboard'))
}
} else {
throw data
}
},
},
}
+115 -115
View File
@@ -1,115 +1,115 @@
/* 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'
const { query } = usersService
const { prefix } = config
export default modelExtend(pageModel, {
namespace: 'user',
state: {
currentItem: {},
modalVisible: false,
modalType: 'create',
selectedRowKeys: [],
isMotion: window.localStorage.getItem(`${prefix}userIsMotion`) === 'true',
},
subscriptions: {
setup ({ dispatch, history }) {
history.listen((location) => {
if (location.pathname === '/user') {
dispatch({
type: 'query',
payload: location.query,
})
}
})
},
},
effects: {
* query ({ payload = {} }, { call, put }) {
const data = yield call(query, payload)
if (data) {
yield put({
type: 'querySuccess',
payload: {
list: data.data,
pagination: {
current: Number(payload.page) || 1,
pageSize: Number(payload.pageSize) || 10,
total: data.total,
},
},
})
}
},
* 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
}
},
* 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' })
yield put({ type: 'query' })
} else {
throw data
}
},
* update ({ payload }, { select, call, put }) {
const id = yield select(({ user }) => user.currentItem.id)
const newUser = { ...payload, id }
const data = yield call(update, newUser)
if (data.success) {
yield put({ type: 'hideModal' })
yield put({ type: 'query' })
} else {
throw data
}
},
},
reducers: {
showModal (state, { payload }) {
return { ...state, ...payload, modalVisible: true }
},
hideModal (state) {
return { ...state, modalVisible: false }
},
switchIsMotion (state) {
window.localStorage.setItem(`${prefix}userIsMotion`, !state.isMotion)
return { ...state, isMotion: !state.isMotion }
},
},
})
/* 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'
const { query } = usersService
const { prefix } = config
export default modelExtend(pageModel, {
namespace: 'user',
state: {
currentItem: {},
modalVisible: false,
modalType: 'create',
selectedRowKeys: [],
isMotion: window.localStorage.getItem(`${prefix}userIsMotion`) === 'true',
},
subscriptions: {
setup ({ dispatch, history }) {
history.listen((location) => {
if (location.pathname === '/user') {
dispatch({
type: 'query',
payload: location.query,
})
}
})
},
},
effects: {
* query ({ payload = {} }, { call, put }) {
const data = yield call(query, payload)
if (data) {
yield put({
type: 'querySuccess',
payload: {
list: data.data,
pagination: {
current: Number(payload.page) || 1,
pageSize: Number(payload.pageSize) || 10,
total: data.total,
},
},
})
}
},
* 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
}
},
* 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' })
yield put({ type: 'query' })
} else {
throw data
}
},
* update ({ payload }, { select, call, put }) {
const id = yield select(({ user }) => user.currentItem.id)
const newUser = { ...payload, id }
const data = yield call(update, newUser)
if (data.success) {
yield put({ type: 'hideModal' })
yield put({ type: 'query' })
} else {
throw data
}
},
},
reducers: {
showModal (state, { payload }) {
return { ...state, ...payload, modalVisible: true }
},
hideModal (state) {
return { ...state, modalVisible: false }
},
switchIsMotion (state) {
window.localStorage.setItem(`${prefix}userIsMotion`, !state.isMotion)
return { ...state, isMotion: !state.isMotion }
},
},
})
+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] } })
}
+4 -1
View File
@@ -56,6 +56,7 @@ const App = ({ children, dispatch, app, loading, location }) => {
const siderProps = {
menu,
location,
siderFold,
darkTheme,
navOpenKeys,
@@ -70,6 +71,7 @@ const App = ({ children, dispatch, app, loading, location }) => {
const breadProps = {
menu,
location,
}
if (openPages && openPages.includes(pathname)) {
return (<div>
@@ -79,6 +81,7 @@ const App = ({ children, dispatch, app, loading, location }) => {
}
return (
<div>
<Loader spinning={loading.effects['app/query']} />
<Helmet>
<title>ANTD ADMIN</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -88,7 +91,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} />
+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
+1 -1
View File
@@ -18,7 +18,7 @@
width: 64px;
height: 64px;
background-position: center;
background-size: cover;
background-size: contain;
}
p {
+81 -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 { Loader } from 'components'
import { NumberCard, Quote, Sales, Weather, RecentSales, Comments, Completed, Browser, Cpu, User } from './components'
import styles from './index.less'
@@ -13,92 +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}>
<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>
<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)
+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))
-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,
})
}
+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 })
})
}
+20 -22
View File
@@ -1,38 +1,36 @@
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'
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$/' || 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({
@@ -42,11 +40,11 @@ module.exports = (webpackConfig, env) => {
inject: false,
appMountId: 'root',
template: `!!ejs-loader!${HtmlWebpackTemplate}`,
filename: env === 'production' ? '../index.html' : 'index.html',
filename: production ? '../index.html' : 'index.html',
minify: {
collapseWhitespace: true,
},
scripts: env === 'production' ? null : ['/roadhog.dll.js'],
scripts: production ? null : ['/roadhog.dll.js'],
meta: [
{
name: 'description',