mirror of
https://github.com/zuiidea/antd-admin.git
synced 2026-10-20 08:16:39 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed8555f15e | ||
|
|
33d4f1679e | ||
|
|
37dd7d011e | ||
|
|
5641cc61c4 | ||
|
|
30f14769f0 | ||
|
|
1b7c076057 | ||
|
|
fbd0fcb5c4 | ||
|
|
19ee8eacce | ||
|
|
0b0c7c3f91 | ||
|
|
5d6abb67b6 | ||
|
|
1a58dcac95 | ||
|
|
3d2d524898 | ||
|
|
2869ead7bd | ||
|
|
25f7a65897 | ||
|
|
75e6284873 | ||
|
|
5fbb1b338a | ||
|
|
ed2b0b25bd | ||
|
|
a4922c7541 | ||
|
|
4566171265 | ||
|
|
6c48480031 | ||
|
|
3401215d41 | ||
|
|
7284cad8aa | ||
|
|
53b8ee6e63 | ||
|
|
b5423129f7 | ||
|
|
4c6deb49d3 | ||
|
|
b6c3cb8a53 | ||
|
|
a42e749c22 | ||
|
|
cfd0172204 | ||
|
|
4a694b6df8 | ||
|
|
3d03fa0695 | ||
|
|
69fbb4742e | ||
|
|
db8b283def | ||
|
|
4f673b6e3a | ||
|
|
f7616d524b | ||
|
|
08fdd0d04f | ||
|
|
847f003cd8 |
@@ -11,6 +11,9 @@
|
||||
|
||||
演示地址 <http://zuiidea.github.io/antd-admin/>
|
||||
|
||||
备用地址 <http://47.92.30.98:8000>
|
||||
|
||||
|
||||
## 特性
|
||||
|
||||
- 基于[react](https://github.com/facebook/react),[ant-design](https://github.com/ant-design/ant-design),[dva](https://github.com/dvajs/dva),[Mock](https://github.com/nuysoft/Mock) 企业级后台管理系统最佳实践
|
||||
@@ -19,8 +22,26 @@
|
||||
- 基于[dva](https://github.com/dvajs/dva)动态加载 Model 和路由,按需加载
|
||||
- 浅度响应式设计
|
||||
|
||||
## 注意
|
||||
如果使用到roadhog,且出现接口404
|
||||
- 保证`package.json`里没有`roadhog`
|
||||
- 删除`node_modules`
|
||||
- `npm i`
|
||||
- `npm i roadhog@0.6.0-beta.3 -g`
|
||||
|
||||
## 更新日志
|
||||
|
||||
### 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)
|
||||
|
||||
`2017-04-21`
|
||||
|
||||
- 重写Menu,Bread组件及配置文件,[说明](https://github.com/zuiidea/antd-admin/wiki/%E8%8F%9C%E5%8D%95%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6)
|
||||
|
||||
### 4.1
|
||||
|
||||
`2017-04-14`
|
||||
|
||||
+4
-10
@@ -7,6 +7,7 @@
|
||||
"dva": "^1.1.0",
|
||||
"dva-loading": "^0.2.0",
|
||||
"lodash": "^4.17.4",
|
||||
"nprogress": "^0.2.0",
|
||||
"qs": "^6.2.0",
|
||||
"react": "^15.4.1",
|
||||
"react-countup": "1.3.0",
|
||||
@@ -34,6 +35,7 @@
|
||||
"jsonp": "^0.2.1",
|
||||
"less-vars-to-js": "^1.1.2",
|
||||
"mockjs": "^1.0.1-beta3",
|
||||
"path-to-regexp": "^1.7.0",
|
||||
"rc-tween-one": "^1.0.0",
|
||||
"redbox-react": "^1.2.10"
|
||||
},
|
||||
@@ -41,16 +43,8 @@
|
||||
"lint"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "set BROWSER=none&&roadhog server",
|
||||
"dev": "set BROWSER=none&&set HOST=0.0.0.0&&roadhog server",
|
||||
"lint": "eslint --fix --ext .js src",
|
||||
"build": "roadhog build"
|
||||
},
|
||||
"theme": "./src/theme.js",
|
||||
"standard": {
|
||||
"parser": "babel-eslint",
|
||||
"globals": [
|
||||
"location",
|
||||
"localStorage"
|
||||
]
|
||||
"build": "roadhog build --analyze"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { TweenOneGroup } from 'rc-tween-one'
|
||||
|
||||
const enterAnim = [
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Table } from 'antd'
|
||||
import { request } from '../../utils'
|
||||
import lodash from 'lodash'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Dropdown, Button, Icon, Menu } from 'antd'
|
||||
|
||||
const DropOption = ({ onMenuClick, menuOptions = [], buttonStyle, dropdownProps }) => {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styles from './FilterItem.less'
|
||||
|
||||
const FilterItem = ({
|
||||
label = '',
|
||||
children,
|
||||
}) => {
|
||||
const labelArray = label.split('')
|
||||
return (
|
||||
<div className={styles.filterItem}>
|
||||
{labelArray.length > 0
|
||||
? <div className={styles.labelWrap}>
|
||||
{labelArray.map((item, index) => <span className="labelText" key={index}>{item}</span>)}
|
||||
</div>
|
||||
: ''}
|
||||
<div className={styles.item}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
FilterItem.propTypes = {
|
||||
label: PropTypes.string,
|
||||
children: PropTypes.element.isRequired,
|
||||
}
|
||||
|
||||
export default FilterItem
|
||||
@@ -0,0 +1,17 @@
|
||||
.filterItem {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.labelWrap {
|
||||
width: 64px;
|
||||
line-height: 28px;
|
||||
margin-right: 12px;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.item {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "FilterItem",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"main": "./FilterItem.js"
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import './iconfont.less'
|
||||
|
||||
const Iconfont = ({ type }) => <span
|
||||
|
||||
@@ -1,47 +1,50 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Breadcrumb, Icon } from 'antd'
|
||||
import { Link } from 'dva/router'
|
||||
import styles from './Bread.less'
|
||||
import { menu } from '../../utils'
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
import { queryArray } from '../../utils'
|
||||
|
||||
let pathSet = []
|
||||
const getPathSet = function (menuArray, parentPath) {
|
||||
parentPath = parentPath || '/'
|
||||
menuArray.forEach(item => {
|
||||
pathSet[(parentPath + item.key).replace(/\//g, '-').hyphenToHump()] = {
|
||||
path: parentPath + item.key,
|
||||
name: item.name,
|
||||
icon: item.icon || '',
|
||||
clickable: item.clickable === undefined,
|
||||
const Bread = ({ menu }) => {
|
||||
// 匹配当前路由
|
||||
let pathArray = []
|
||||
let current
|
||||
for (let index in menu) {
|
||||
if (menu[index].router && pathToRegexp(menu[index].router).exec(location.pathname)) {
|
||||
current = menu[index]
|
||||
break
|
||||
}
|
||||
if (item.child) {
|
||||
getPathSet(item.child, `${parentPath}${item.key}/`)
|
||||
}
|
||||
})
|
||||
}
|
||||
getPathSet(menu)
|
||||
}
|
||||
|
||||
function Bread ({ location }) {
|
||||
let pathNames = []
|
||||
location.pathname.substr(1).split('/').forEach((item, key) => {
|
||||
if (key > 0) {
|
||||
pathNames.push((`${pathNames[key - 1]}-${item}`).hyphenToHump())
|
||||
} else {
|
||||
pathNames.push((`-${item}`).hyphenToHump())
|
||||
const getPathArray = (item) => {
|
||||
pathArray.unshift(item)
|
||||
if (item.bpid) {
|
||||
getPathArray(queryArray(menu, item.bpid, 'id'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const breadsArray = pathNames.filter(item => (item in pathSet))
|
||||
const breads = breadsArray.map((item, key) => {
|
||||
if (!current) {
|
||||
pathArray.push(menu[0])
|
||||
pathArray.push({
|
||||
id: 404,
|
||||
name: 'Not Found',
|
||||
})
|
||||
} else {
|
||||
getPathArray(current)
|
||||
}
|
||||
|
||||
// 递归查找父级
|
||||
const breads = pathArray.map((item, key) => {
|
||||
const content = (
|
||||
<span>{pathSet[item].icon
|
||||
? <Icon type={pathSet[item].icon} style={{ marginRight: 4 }} />
|
||||
: ''}{pathSet[item].name}</span>
|
||||
<span>{item.icon
|
||||
? <Icon type={item.icon} style={{ marginRight: 4 }} />
|
||||
: ''}{item.name}</span>
|
||||
)
|
||||
return (
|
||||
<Breadcrumb.Item key={key}>
|
||||
{((breadsArray.length - 1) !== key && pathSet[item].clickable)
|
||||
? <Link to={pathSet[item].path}>
|
||||
{((pathArray.length - 1) !== key)
|
||||
? <Link to={item.router}>
|
||||
{content}
|
||||
</Link>
|
||||
: content}
|
||||
@@ -52,12 +55,6 @@ function Bread ({ location }) {
|
||||
return (
|
||||
<div className={styles.bread}>
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item >
|
||||
<Link to="/dashboard">
|
||||
<Icon type="home" />
|
||||
<span>Home</span>
|
||||
</Link>
|
||||
</Breadcrumb.Item>
|
||||
{breads}
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
@@ -65,7 +62,7 @@ function Bread ({ location }) {
|
||||
}
|
||||
|
||||
Bread.propTypes = {
|
||||
location: PropTypes.object,
|
||||
menu: PropTypes.array,
|
||||
}
|
||||
|
||||
export default Bread
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Menu, Icon, Popover } from 'antd'
|
||||
import styles from './Header.less'
|
||||
import Menus from './Menu'
|
||||
|
||||
const SubMenu = Menu.SubMenu
|
||||
|
||||
function Header ({ user, logout, switchSider, siderFold, isNavbar, menuPopoverVisible, location, switchMenuPopover, navOpenKeys, changeOpenKeys }) {
|
||||
function Header ({ user, logout, switchSider, siderFold, isNavbar, menuPopoverVisible, location, switchMenuPopover, navOpenKeys, changeOpenKeys, menu }) {
|
||||
let handleClickMenu = e => e.key === 'logout' && logout()
|
||||
const menusProps = {
|
||||
menu,
|
||||
siderFold: false,
|
||||
darkTheme: false,
|
||||
isNavbar,
|
||||
@@ -38,7 +40,7 @@ function Header ({ user, logout, switchSider, siderFold, isNavbar, menuPopoverVi
|
||||
{user.username} < /span>}
|
||||
>
|
||||
<Menu.Item key="logout">
|
||||
<a>注销</a>
|
||||
Sign out
|
||||
</Menu.Item>
|
||||
</SubMenu>
|
||||
</Menu>
|
||||
@@ -48,6 +50,7 @@ function Header ({ user, logout, switchSider, siderFold, isNavbar, menuPopoverVi
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
menu: PropTypes.array,
|
||||
user: PropTypes.object,
|
||||
logout: PropTypes.func,
|
||||
switchSider: PropTypes.func,
|
||||
|
||||
@@ -1,36 +1,60 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Menu, Icon } from 'antd'
|
||||
import { Link } from 'dva/router'
|
||||
import { menu } from '../../utils'
|
||||
import { arrayToTree, queryArray } from '../../utils'
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
|
||||
const topMenus = menu.map(item => item.key)
|
||||
const getMenus = function (menuArray, siderFold, parentPath = '/') {
|
||||
return menuArray.map(item => {
|
||||
const linkTo = parentPath + item.key
|
||||
if (item.child) {
|
||||
const Menus = ({ siderFold, darkTheme, location, handleClickNavMenu, navOpenKeys, changeOpenKeys, menu }) => {
|
||||
// 生成树状
|
||||
const menuTree = arrayToTree(menu.filter(_ => _.mpid !== -1), 'id', 'mpid')
|
||||
const levelMap = {}
|
||||
|
||||
// 递归生成菜单
|
||||
const getMenus = (menuTreeN, siderFoldN) => {
|
||||
return menuTreeN.map(item => {
|
||||
if (item.children) {
|
||||
if (item.mpid) {
|
||||
levelMap[item.id] = item.mpid
|
||||
}
|
||||
return (
|
||||
<Menu.SubMenu
|
||||
key={item.id}
|
||||
title={<span>
|
||||
{item.icon && <Icon type={item.icon} />}
|
||||
{(!siderFoldN || menuTree.indexOf(item) < 0) && item.name}
|
||||
</span>}
|
||||
>
|
||||
{getMenus(item.children, siderFoldN)}
|
||||
</Menu.SubMenu>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Menu.SubMenu key={linkTo} title={<span>{item.icon ? <Icon type={item.icon} /> : ''}{siderFold && topMenus.indexOf(item.key) >= 0 ? '' : item.name}</span>}>
|
||||
{getMenus(item.child, siderFold, `${linkTo}/`)}
|
||||
</Menu.SubMenu>
|
||||
<Menu.Item key={item.id}>
|
||||
<Link to={item.router}>
|
||||
{item.icon && <Icon type={item.icon} />}
|
||||
{(!siderFoldN || menuTree.indexOf(item) < 0) && item.name}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Menu.Item key={linkTo}>
|
||||
<Link to={linkTo}>
|
||||
{item.icon ? <Icon type={item.icon} /> : ''}
|
||||
{siderFold && topMenus.indexOf(item.key) >= 0 ? '' : item.name}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function Menus ({ siderFold, darkTheme, location, handleClickNavMenu, navOpenKeys, changeOpenKeys }) {
|
||||
const menuItems = getMenus(menu, siderFold)
|
||||
})
|
||||
}
|
||||
const menuItems = getMenus(menuTree, siderFold)
|
||||
|
||||
// 保持选中
|
||||
const getAncestorKeys = (key) => {
|
||||
const map = {
|
||||
'/navigation/navigation2': ['/navigation'],
|
||||
let map = {}
|
||||
const getParent = (index) => {
|
||||
const result = [String(levelMap[index])]
|
||||
if (levelMap[result[0]]) {
|
||||
result.unshift(getParent(result[0])[0])
|
||||
}
|
||||
return result
|
||||
}
|
||||
for (let index in levelMap) {
|
||||
if ({}.hasOwnProperty.call(levelMap, index)) {
|
||||
map[index] = getParent(index)
|
||||
}
|
||||
}
|
||||
return map[key] || []
|
||||
}
|
||||
@@ -53,13 +77,38 @@ function Menus ({ siderFold, darkTheme, location, handleClickNavMenu, navOpenKey
|
||||
openKeys: navOpenKeys,
|
||||
} : {}
|
||||
|
||||
|
||||
// 寻找选中路由
|
||||
let currentMenu
|
||||
let defaultSelectedKeys
|
||||
for (let item of menu) {
|
||||
if (item.router && pathToRegexp(item.router).exec(location.pathname)) {
|
||||
currentMenu = item
|
||||
break
|
||||
}
|
||||
}
|
||||
const getPathArray = (array, current, pid, id) => {
|
||||
let result = [String(current[id])]
|
||||
const getPath = (item) => {
|
||||
if (item && item[pid]) {
|
||||
result.unshift(String(item[pid]))
|
||||
getPath(queryArray(array, item[pid], id))
|
||||
}
|
||||
}
|
||||
getPath(current)
|
||||
return result
|
||||
}
|
||||
if (currentMenu) {
|
||||
defaultSelectedKeys = getPathArray(menu, currentMenu, 'mpid', 'id')
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu
|
||||
{...menuProps}
|
||||
mode={siderFold ? 'vertical' : 'inline'}
|
||||
theme={darkTheme ? 'dark' : 'light'}
|
||||
onClick={handleClickNavMenu}
|
||||
defaultSelectedKeys={[location.pathname !== '/' ? location.pathname : '/dashboard']}
|
||||
defaultSelectedKeys={defaultSelectedKeys}
|
||||
>
|
||||
{menuItems}
|
||||
</Menu>
|
||||
@@ -67,6 +116,7 @@ function Menus ({ siderFold, darkTheme, location, handleClickNavMenu, navOpenKey
|
||||
}
|
||||
|
||||
Menus.propTypes = {
|
||||
menu: PropTypes.array,
|
||||
siderFold: PropTypes.bool,
|
||||
darkTheme: PropTypes.bool,
|
||||
location: PropTypes.object,
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Icon, Switch } from 'antd'
|
||||
import styles from './Layout.less'
|
||||
import { config } from '../../utils'
|
||||
import Menus from './Menu'
|
||||
|
||||
function Sider ({ siderFold, darkTheme, location, changeTheme, navOpenKeys, changeOpenKeys }) {
|
||||
function Sider ({ siderFold, darkTheme, location, changeTheme, navOpenKeys, changeOpenKeys, menu }) {
|
||||
const menusProps = {
|
||||
menu,
|
||||
siderFold,
|
||||
darkTheme,
|
||||
location,
|
||||
@@ -20,14 +22,15 @@ function Sider ({ siderFold, darkTheme, location, changeTheme, navOpenKeys, chan
|
||||
</div>
|
||||
<Menus {...menusProps} />
|
||||
{!siderFold ? <div className={styles.switchtheme}>
|
||||
<span><Icon type="bulb" />切换主题</span>
|
||||
<Switch onChange={changeTheme} defaultChecked={darkTheme} checkedChildren="黑" unCheckedChildren="白" />
|
||||
<span><Icon type="bulb" />Switch Theme</span>
|
||||
<Switch onChange={changeTheme} defaultChecked={darkTheme} checkedChildren="Dark" unCheckedChildren="Light" />
|
||||
</div> : ''}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Sider.propTypes = {
|
||||
menu: PropTypes.array,
|
||||
siderFold: PropTypes.bool,
|
||||
darkTheme: PropTypes.bool,
|
||||
location: PropTypes.object,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ReactDOM from 'react-dom'
|
||||
import styles from './Search.less'
|
||||
import { Input, Select, Button, Icon } from 'antd'
|
||||
|
||||
@@ -3,6 +3,7 @@ import DropOption from './DropOption'
|
||||
import Iconfont from './Iconfont'
|
||||
import Search from './Search'
|
||||
import Editor from './Editor'
|
||||
import FilterItem from './FilterItem'
|
||||
import * as Layout from './Layout/index.js'
|
||||
|
||||
import layer from './layer'
|
||||
@@ -14,5 +15,6 @@ export {
|
||||
Iconfont,
|
||||
Search,
|
||||
Editor,
|
||||
FilterItem,
|
||||
layer,
|
||||
}
|
||||
|
||||
+2
-1
@@ -3,13 +3,14 @@ import 'babel-polyfill'
|
||||
import dva from 'dva'
|
||||
import createLoading from 'dva-loading'
|
||||
import { browserHistory } from 'dva/router'
|
||||
import { message } from 'antd'
|
||||
|
||||
// 1. Initialize
|
||||
const app = dva({
|
||||
...createLoading(),
|
||||
history: browserHistory,
|
||||
onError (error) {
|
||||
console.error('app onError -- ', error)
|
||||
message.error(error.message)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
const qs = require('qs')
|
||||
const Mock = require('mockjs')
|
||||
const config = require('../utils/config')
|
||||
const { apiPrefix } = config
|
||||
|
||||
let usersListData = Mock.mock({
|
||||
'data|80-100': [
|
||||
{
|
||||
id: '@id',
|
||||
name: '@name',
|
||||
nickName: '@last',
|
||||
phone: /^1[34578]\d{9}$/,
|
||||
'age|11-99': 1,
|
||||
address: '@county(true)',
|
||||
isMale: '@boolean',
|
||||
email: '@email',
|
||||
createTime: '@datetime',
|
||||
avatar () {
|
||||
return Mock.Random.image('100x100', Mock.Random.color(), '#757575', 'png', this.nickName.substr(0, 1))
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
let database = usersListData.data
|
||||
|
||||
const userPermission = {
|
||||
DEFAULT: [
|
||||
'dashboard', 'chart',
|
||||
],
|
||||
ADMIN: [
|
||||
'dashboard', 'users', 'UIElement', 'UIElementIconfont', 'chart',
|
||||
],
|
||||
DEVELOPER: ['dashboard', 'users', 'UIElement', 'UIElementIconfont', 'chart'],
|
||||
}
|
||||
|
||||
const adminUsers = [
|
||||
{
|
||||
id: 0,
|
||||
username: 'admin',
|
||||
password: 'admin',
|
||||
permissions: userPermission.ADMIN,
|
||||
}, {
|
||||
id: 1,
|
||||
username: 'guest',
|
||||
password: 'guest',
|
||||
permissions: userPermission.DEFAULT,
|
||||
}, {
|
||||
id: 2,
|
||||
username: '吴彦祖',
|
||||
password: '123456',
|
||||
permissions: userPermission.DEVELOPER,
|
||||
},
|
||||
]
|
||||
|
||||
const queryArray = (array, key, keyAlias = 'key') => {
|
||||
if (!(array instanceof Array)) {
|
||||
return null
|
||||
}
|
||||
let data
|
||||
|
||||
for (let item of array) {
|
||||
if (item[keyAlias] === key) {
|
||||
data = item
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
return data
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const NOTFOUND = {
|
||||
message: 'Not Found',
|
||||
documentation_url: 'http://localhost:8000/request',
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
[`POST ${apiPrefix}/user/login`] (req, res) {
|
||||
const { username, password } = req.body
|
||||
const user = adminUsers.filter((item) => item.username === username)
|
||||
|
||||
if (user && user[0].password === password) {
|
||||
const now = new Date()
|
||||
now.setDate(now.getDate() + 1)
|
||||
res.cookie('token', JSON.stringify({ id: user[0].id, deadline: now.getTime() }), {
|
||||
maxAge: 900000,
|
||||
httpOnly: true,
|
||||
})
|
||||
res.json({ success: true, message: 'Ok' })
|
||||
} else {
|
||||
res.status(400).end()
|
||||
}
|
||||
},
|
||||
|
||||
[`GET ${apiPrefix}/user/logout`] (req, res) {
|
||||
res.clearCookie('token')
|
||||
res.status(200).end()
|
||||
},
|
||||
|
||||
[`GET ${apiPrefix}/user`] (req, res) {
|
||||
const cookie = req.headers.cookie || ''
|
||||
const cookies = qs.parse(cookie.replace(/\s/g, ''), { delimiter: ';' })
|
||||
const response = {}
|
||||
const user = {}
|
||||
if (!cookies.token) {
|
||||
res.status(200).send({ message: 'Not Login' })
|
||||
return
|
||||
}
|
||||
const token = JSON.parse(cookies.token)
|
||||
if (token) {
|
||||
response.success = token.deadline > new Date().getTime()
|
||||
}
|
||||
if (response.success) {
|
||||
const userItem = adminUsers.filter(_ => _.id === token.id)
|
||||
if (userItem.length > 0) {
|
||||
user.permissions = userItem[0].permissions
|
||||
user.username = userItem[0].username
|
||||
user.id = userItem[0].id
|
||||
}
|
||||
}
|
||||
response.user = user
|
||||
res.json(response)
|
||||
},
|
||||
|
||||
[`GET ${apiPrefix}/users`] (req, res) {
|
||||
const { query } = req
|
||||
let { pageSize, page, ...other } = query
|
||||
pageSize = pageSize || 10
|
||||
page = page || 1
|
||||
|
||||
let newData = database
|
||||
for (let key in other) {
|
||||
if ({}.hasOwnProperty.call(other, key)) {
|
||||
newData = newData.filter((item) => {
|
||||
if ({}.hasOwnProperty.call(item, key)) {
|
||||
if (key === 'address') {
|
||||
return other[key].every(iitem => item[key].indexOf(iitem) > -1)
|
||||
} else if (key === 'createTime') {
|
||||
const start = new Date(other[key][0]).getTime()
|
||||
const end = new Date(other[key][1]).getTime()
|
||||
const now = new Date(item[key]).getTime()
|
||||
|
||||
if (start && end) {
|
||||
return now >= start && now <= end
|
||||
}
|
||||
return true
|
||||
}
|
||||
return String(item[key]).trim().indexOf(decodeURI(other[key]).trim()) > -1
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
data: newData.slice((page - 1) * pageSize, page * pageSize),
|
||||
total: newData.length,
|
||||
})
|
||||
},
|
||||
|
||||
[`POST ${apiPrefix}/user`] (req, res) {
|
||||
const newData = req.body
|
||||
newData.createTime = Mock.mock('@now')
|
||||
newData.avatar = newData.avatar || Mock.Random.image('100x100', Mock.Random.color(), '#757575', 'png', newData.nickName.substr(0, 1))
|
||||
newData.id = Mock.mock('@id')
|
||||
|
||||
database.unshift(newData)
|
||||
|
||||
res.status(200).end()
|
||||
},
|
||||
|
||||
[`GET ${apiPrefix}/user/:id`] (req, res) {
|
||||
const { id } = req.params
|
||||
const data = queryArray(database, id, 'id')
|
||||
if (data) {
|
||||
res.status(200).json(data)
|
||||
} else {
|
||||
res.status(404).json(NOTFOUND)
|
||||
}
|
||||
},
|
||||
|
||||
[`DELETE ${apiPrefix}/user/:id`] (req, res) {
|
||||
const { id } = req.params
|
||||
const data = queryArray(database, id, 'id')
|
||||
if (data) {
|
||||
database = database.filter((item) => item.id !== id)
|
||||
res.status(204).end()
|
||||
} else {
|
||||
res.status(404).json(NOTFOUND)
|
||||
}
|
||||
},
|
||||
|
||||
[`PATCH ${apiPrefix}/user/:id`] (req, res) {
|
||||
const { id } = req.params
|
||||
const editItem = req.body
|
||||
let isExist = false
|
||||
|
||||
database = database.map((item) => {
|
||||
if (item.id === id) {
|
||||
isExist = true
|
||||
return Object.assign({}, item, editItem)
|
||||
}
|
||||
return item
|
||||
})
|
||||
|
||||
if (isExist) {
|
||||
res.status(201).end()
|
||||
} else {
|
||||
res.status(404).json(NOTFOUND)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
const qs = require('qs')
|
||||
const Mock = require('mockjs')
|
||||
const config = require('../utils/config')
|
||||
const { apiPrefix } = config
|
||||
|
||||
let usersListData = Mock.mock({
|
||||
'data|100': [
|
||||
{
|
||||
'id|+1': 1,
|
||||
name: '@cname',
|
||||
nickName: '@last',
|
||||
phone: /^1[34578]\d{9}$/,
|
||||
'age|11-99': 1,
|
||||
address: '@county(true)',
|
||||
isMale: '@boolean',
|
||||
email: '@email',
|
||||
createTime: '@datetime',
|
||||
avatar () {
|
||||
return Mock.Random.image('100x100', Mock.Random.color(), '#757575', 'png', this.nickName.substr(0, 1))
|
||||
},
|
||||
},
|
||||
],
|
||||
page: {
|
||||
total: 100,
|
||||
current: 1,
|
||||
},
|
||||
})
|
||||
|
||||
const userPermission = {
|
||||
DEFAULT: [
|
||||
'dashboard', 'chart',
|
||||
],
|
||||
ADMIN: [
|
||||
'dashboard', 'users', 'UIElement', 'UIElementIconfont', 'chart',
|
||||
],
|
||||
DEVELOPER: ['dashboard', 'users', 'UIElement', 'UIElementIconfont', 'chart'],
|
||||
}
|
||||
|
||||
const adminUsers = [
|
||||
{
|
||||
id: 0,
|
||||
username: 'admin',
|
||||
password: 'admin',
|
||||
permissions: userPermission.ADMIN,
|
||||
}, {
|
||||
id: 1,
|
||||
username: 'guest',
|
||||
password: 'guest',
|
||||
permissions: userPermission.DEFAULT,
|
||||
}, {
|
||||
id: 2,
|
||||
username: '吴彦祖',
|
||||
password: '123456',
|
||||
permissions: userPermission.DEVELOPER,
|
||||
},
|
||||
]
|
||||
|
||||
module.exports = {
|
||||
|
||||
[`POST ${apiPrefix}/user/login`] (req, res) {
|
||||
const { username, password } = req.body
|
||||
const user = adminUsers.filter((item) => item.username === username)
|
||||
|
||||
if (user && user[0].password === password) {
|
||||
const now = new Date()
|
||||
now.setDate(now.getDate() + 1)
|
||||
res.cookie('token', JSON.stringify({ id: user[0].id, deadline: now.getTime() }), {
|
||||
maxAge: 900000,
|
||||
httpOnly: true,
|
||||
})
|
||||
res.json({ success: true, message: 'Ok' })
|
||||
} else {
|
||||
res.status(400).send({ message: 'Bad Request' })
|
||||
}
|
||||
},
|
||||
|
||||
[`GET ${apiPrefix}/user/logout`] (req, res) {
|
||||
res.clearCookie('token')
|
||||
res.status(200).send({ message: 'OK' })
|
||||
},
|
||||
|
||||
[`GET ${apiPrefix}/userInfo`] (req, res) {
|
||||
const cookies = qs.parse(req.headers.cookie, { delimiter: ';' })
|
||||
const response = {}
|
||||
const user = {}
|
||||
if (!cookies.token) {
|
||||
res.status(200).send({ message: 'Not Login' })
|
||||
return
|
||||
}
|
||||
const token = JSON.parse(cookies.token)
|
||||
if (token) {
|
||||
response.success = token.deadline > new Date().getTime()
|
||||
}
|
||||
if (response.success) {
|
||||
const userItem = adminUsers.filter(_ => _.id === token.id)
|
||||
if (userItem.length > 0) {
|
||||
user.permissions = userItem[0].permissions
|
||||
user.username = userItem[0].username
|
||||
user.id = userItem[0].id
|
||||
}
|
||||
}
|
||||
response.user = user
|
||||
res.json(response)
|
||||
},
|
||||
|
||||
[`GET ${apiPrefix}/users`] (req, res) {
|
||||
const page = req.query
|
||||
const pageSize = page.pageSize || 10
|
||||
const currentPage = page.page || 1
|
||||
|
||||
let data
|
||||
let newPage
|
||||
|
||||
let newData = usersListData.data.concat()
|
||||
|
||||
if (page.field) {
|
||||
const d = newData.filter((item) => {
|
||||
return item[page.field].indexOf(decodeURI(page.keyword)) > -1
|
||||
})
|
||||
|
||||
data = d.slice((currentPage - 1) * pageSize, currentPage * pageSize)
|
||||
|
||||
newPage = {
|
||||
current: currentPage * 1,
|
||||
total: d.length,
|
||||
}
|
||||
} else {
|
||||
data = usersListData.data.slice((currentPage - 1) * pageSize, currentPage * pageSize)
|
||||
usersListData.page.current = currentPage * 1
|
||||
newPage = usersListData.page
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
data,
|
||||
page: {
|
||||
...newPage,
|
||||
pageSize: Number(pageSize),
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
[`POST ${apiPrefix}/users`] (req, res) {
|
||||
const newData = req.body
|
||||
newData.createTime = Mock.mock('@now')
|
||||
newData.avatar = Mock.Random.image('100x100', Mock.Random.color(), '#757575', 'png', newData.nickName.substr(0, 1))
|
||||
|
||||
newData.id = usersListData.data.length + 1
|
||||
usersListData.data.unshift(newData)
|
||||
|
||||
usersListData.page.total = usersListData.data.length
|
||||
usersListData.page.current = 1
|
||||
|
||||
res.json({ success: true, data: usersListData.data, page: usersListData.page })
|
||||
},
|
||||
|
||||
[`DELETE ${apiPrefix}/users`] (req, res) {
|
||||
const deleteItem = req.body
|
||||
|
||||
usersListData.data = usersListData.data.filter((item) => {
|
||||
if (item.id === deleteItem.id) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
usersListData.page.total = usersListData.data.length
|
||||
|
||||
res.json({ success: true, data: usersListData.data, page: usersListData.page })
|
||||
},
|
||||
|
||||
[`PUT ${apiPrefix}/users`] (req, res) {
|
||||
const editItem = req.body
|
||||
|
||||
editItem.createTime = Mock.mock('@now')
|
||||
editItem.avatar = Mock.Random.image('100x100', Mock.Random.color(), '#757575', 'png', editItem.nickName.substr(0, 1))
|
||||
|
||||
usersListData.data = usersListData.data.map((item) => {
|
||||
if (item.id === editItem.id) {
|
||||
return editItem
|
||||
}
|
||||
return item
|
||||
})
|
||||
|
||||
res.json({ success: true, data: usersListData.data, page: usersListData.page })
|
||||
},
|
||||
}
|
||||
+3
-4
@@ -1,4 +1,4 @@
|
||||
import { getUserInfo, logout } from '../services/app'
|
||||
import { query, logout } from '../services/app'
|
||||
import { routerRedux } from 'dva/router'
|
||||
import { parse } from 'qs'
|
||||
import { config } from '../utils'
|
||||
@@ -13,7 +13,7 @@ export default {
|
||||
siderFold: localStorage.getItem(`${prefix}siderFold`) === 'true',
|
||||
darkTheme: localStorage.getItem(`${prefix}darkTheme`) === 'true',
|
||||
isNavbar: document.body.clientWidth < 769,
|
||||
navOpenKeys: [],
|
||||
navOpenKeys: JSON.parse(localStorage.getItem(`${prefix}navOpenKeys`)) || [],
|
||||
},
|
||||
subscriptions: {
|
||||
setup ({ dispatch }) {
|
||||
@@ -27,7 +27,7 @@ export default {
|
||||
*queryUser ({
|
||||
payload,
|
||||
}, { call, put }) {
|
||||
const data = yield call(getUserInfo, parse(payload))
|
||||
const data = yield call(query, parse(payload))
|
||||
if (data.success && data.user) {
|
||||
yield put({
|
||||
type: 'queryUserSuccess',
|
||||
@@ -133,7 +133,6 @@ export default {
|
||||
}
|
||||
},
|
||||
handleNavOpenKeys (state, { payload: navOpenKeys }) {
|
||||
console.log(navOpenKeys)
|
||||
return {
|
||||
...state,
|
||||
...navOpenKeys,
|
||||
|
||||
@@ -79,8 +79,9 @@ let zuimei = {
|
||||
return name
|
||||
},
|
||||
|
||||
getWeatherByCode (num) {
|
||||
getWeatherByCode (number) {
|
||||
let wea = ''
|
||||
let num = Number(number)
|
||||
if (num === 0) {
|
||||
wea = '晴'
|
||||
} else if (num === 1) {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { create, remove, update, query } from '../services/users'
|
||||
import { create, remove, update } from '../services/user'
|
||||
import { query } from '../services/users'
|
||||
import { routerRedux } from 'dva/router'
|
||||
import { parse } from 'qs'
|
||||
|
||||
export default {
|
||||
|
||||
namespace: 'users',
|
||||
namespace: 'user',
|
||||
|
||||
state: {
|
||||
list: [],
|
||||
@@ -23,7 +25,7 @@ export default {
|
||||
subscriptions: {
|
||||
setup ({ dispatch, history }) {
|
||||
history.listen(location => {
|
||||
if (location.pathname === '/users') {
|
||||
if (location.pathname === '/user') {
|
||||
dispatch({
|
||||
type: 'query',
|
||||
payload: location.query,
|
||||
@@ -34,65 +36,54 @@ export default {
|
||||
},
|
||||
|
||||
effects: {
|
||||
*requery ({ payload }, { put }) {
|
||||
yield put(routerRedux.push({
|
||||
pathname: location.pathname,
|
||||
query: parse(location.search.substr(1)),
|
||||
}))
|
||||
},
|
||||
*query ({ payload }, { call, put }) {
|
||||
const data = yield call(query, parse(payload))
|
||||
const data = yield call(query, payload)
|
||||
if (data) {
|
||||
yield put({
|
||||
type: 'querySuccess',
|
||||
payload: {
|
||||
list: data.data,
|
||||
pagination: data.page,
|
||||
pagination: {
|
||||
current: Number(payload.page) || 1,
|
||||
pageSize: Number(payload.pageSize) || 10,
|
||||
total: data.total,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
*'delete' ({ payload }, { call, put }) {
|
||||
const data = yield call(remove, { id: payload })
|
||||
if (data && data.success) {
|
||||
yield put({
|
||||
type: 'querySuccess',
|
||||
payload: {
|
||||
list: data.data,
|
||||
pagination: {
|
||||
total: data.page.total,
|
||||
current: data.page.current,
|
||||
},
|
||||
},
|
||||
})
|
||||
if (data.success) {
|
||||
yield put({ type: 'requery' })
|
||||
} else {
|
||||
throw data
|
||||
}
|
||||
},
|
||||
*create ({ payload }, { call, put }) {
|
||||
yield put({ type: 'hideModal' })
|
||||
const data = yield call(create, payload)
|
||||
if (data && data.success) {
|
||||
yield put({
|
||||
type: 'querySuccess',
|
||||
payload: {
|
||||
list: data.data,
|
||||
pagination: {
|
||||
total: data.page.total,
|
||||
current: data.page.current,
|
||||
},
|
||||
},
|
||||
})
|
||||
if (data.success) {
|
||||
yield put({ type: 'requery' })
|
||||
} else {
|
||||
throw data
|
||||
}
|
||||
},
|
||||
*update ({ payload }, { select, call, put }) {
|
||||
yield put({ type: 'hideModal' })
|
||||
const id = yield select(({ users }) => users.currentItem.id)
|
||||
const id = yield select(({ user }) => user.currentItem.id)
|
||||
const newUser = { ...payload, id }
|
||||
const data = yield call(update, newUser)
|
||||
if (data && data.success) {
|
||||
yield put({
|
||||
type: 'querySuccess',
|
||||
payload: {
|
||||
list: data.data,
|
||||
pagination: {
|
||||
total: data.page.total,
|
||||
current: data.page.current,
|
||||
},
|
||||
},
|
||||
})
|
||||
if (data.success) {
|
||||
yield put({ type: 'requery' })
|
||||
} else {
|
||||
throw data
|
||||
}
|
||||
},
|
||||
*switchIsMotion ({
|
||||
@@ -0,0 +1,51 @@
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
import { query } from '../../services/user'
|
||||
|
||||
export default {
|
||||
|
||||
namespace: 'userDetail',
|
||||
|
||||
state: {
|
||||
data: {},
|
||||
},
|
||||
|
||||
subscriptions: {
|
||||
setup ({ dispatch, history }) {
|
||||
history.listen(() => {
|
||||
const match = pathToRegexp('/user/:id').exec(location.pathname)
|
||||
if (match) {
|
||||
dispatch({ type: 'query', payload: { id: match[1] } })
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
effects: {
|
||||
*query ({
|
||||
payload,
|
||||
}, { call, put }) {
|
||||
const data = yield call(query, payload)
|
||||
const { success, message, status, ...other } = data
|
||||
if (success) {
|
||||
yield put({
|
||||
type: 'querySuccess',
|
||||
payload: {
|
||||
data: other,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
throw data
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
reducers: {
|
||||
querySuccess (state, { payload }) {
|
||||
const { data } = payload
|
||||
return {
|
||||
...state,
|
||||
data,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
+15
-8
@@ -1,12 +1,11 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Router } from 'dva/router'
|
||||
import App from './routes/app'
|
||||
|
||||
const cached = {}
|
||||
const registerModel = (app, model) => {
|
||||
if (!cached[model.namespace]) {
|
||||
if (!(app._models.filter(m => m.namespace === model.namespace).length === 1)) {
|
||||
app.model(model)
|
||||
cached[model.namespace] = 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,12 +30,20 @@ const Routers = function ({ history, app }) {
|
||||
}, 'dashboard')
|
||||
},
|
||||
}, {
|
||||
path: 'users',
|
||||
path: 'user',
|
||||
getComponent (nextState, cb) {
|
||||
require.ensure([], require => {
|
||||
registerModel(app, require('./models/users'))
|
||||
cb(null, require('./routes/users/'))
|
||||
}, 'users')
|
||||
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',
|
||||
|
||||
+13
-5
@@ -1,9 +1,11 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'dva'
|
||||
import { Layout } from '../components'
|
||||
import { classnames, config } from '../utils'
|
||||
import { classnames, config, menu } from '../utils'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import '../themes/index.less'
|
||||
const { prefix } = config
|
||||
|
||||
const { Header, Bread, Footer, Sider, styles } = Layout
|
||||
|
||||
@@ -11,6 +13,7 @@ const App = ({ children, location, dispatch, app }) => {
|
||||
const { user, siderFold, darkTheme, isNavbar, menuPopoverVisible, navOpenKeys } = app
|
||||
|
||||
const headerProps = {
|
||||
menu,
|
||||
user,
|
||||
siderFold,
|
||||
location,
|
||||
@@ -32,6 +35,7 @@ const App = ({ children, location, dispatch, app }) => {
|
||||
}
|
||||
|
||||
const siderProps = {
|
||||
menu,
|
||||
siderFold,
|
||||
darkTheme,
|
||||
location,
|
||||
@@ -40,11 +44,15 @@ const App = ({ children, location, dispatch, app }) => {
|
||||
dispatch({ type: 'app/changeTheme' })
|
||||
},
|
||||
changeOpenKeys (openKeys) {
|
||||
localStorage.setItem('navOpenKeys', JSON.stringify(openKeys))
|
||||
localStorage.setItem(`${prefix}navOpenKeys`, JSON.stringify(openKeys))
|
||||
dispatch({ type: 'app/handleNavOpenKeys', payload: { navOpenKeys: openKeys } })
|
||||
},
|
||||
}
|
||||
|
||||
const breadProps = {
|
||||
menu,
|
||||
}
|
||||
|
||||
if (config.openPages && config.openPages.indexOf(location.pathname) > -1) {
|
||||
return <div>{children}</div>
|
||||
}
|
||||
@@ -63,7 +71,7 @@ const App = ({ children, location, dispatch, app }) => {
|
||||
</aside> : ''}
|
||||
<div className={styles.main}>
|
||||
<Header {...headerProps} />
|
||||
<Bread location={location} />
|
||||
<Bread {...breadProps} location={location} />
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
{children}
|
||||
@@ -83,4 +91,4 @@ App.propTypes = {
|
||||
app: PropTypes.object,
|
||||
}
|
||||
|
||||
export default connect(({ app }) => ({ app }))(App)
|
||||
export default connect(({ app, loading }) => ({ app, loading }))(App)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styles from './Container.less'
|
||||
import { ResponsiveContainer } from 'recharts'
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Row, Col, Card, Button } from 'antd'
|
||||
import Container from '../Container'
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Row, Col, Card, Button } from 'antd'
|
||||
import Container from '../Container'
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Table, Tag } from 'antd'
|
||||
import styles from './browser.less'
|
||||
import { color } from '../../../utils'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Table, Tag } from 'antd'
|
||||
import styles from './comments.less'
|
||||
import { color } from '../../../utils'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styles from './completed.less'
|
||||
import classnames from 'classnames'
|
||||
import { color } from '../../../utils'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styles from './cpu.less'
|
||||
import { color } from '../../../utils'
|
||||
import CountUp from 'react-countup'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Icon, Card } from 'antd'
|
||||
import CountUp from 'react-countup'
|
||||
import styles from './numberCard.less'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styles from './quote.less'
|
||||
|
||||
function Quote ({ name, content, title, avatar }) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Table, Tag } from 'antd'
|
||||
import styles from './recentSales.less'
|
||||
import { color } from '../../../utils'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styles from './sales.less'
|
||||
import classnames from 'classnames'
|
||||
import { color } from '../../../utils'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Button } from 'antd'
|
||||
import styles from './user.less'
|
||||
import CountUp from 'react-countup'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styles from './weather.less'
|
||||
|
||||
function Weather ({ city, icon, dateTime, temperature, name }) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'dva'
|
||||
import { Row, Col, Card } from 'antd'
|
||||
import { NumberCard, Quote, Sales, Weather, RecentSales, Comments, Completed, Browser, Cpu, User } from './components'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'dva'
|
||||
import { Button, Row, Form, Input } from 'antd'
|
||||
import { config } from '../../utils'
|
||||
@@ -37,28 +38,26 @@ const Login = ({
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请填写用户名',
|
||||
},
|
||||
],
|
||||
})(<Input size="large" onPressEnter={handleOk} placeholder="用户名" />)}
|
||||
})(<Input size="large" onPressEnter={handleOk} placeholder="Username" />)}
|
||||
</FormItem>
|
||||
<FormItem hasFeedback>
|
||||
{getFieldDecorator('password', {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请填写密码',
|
||||
},
|
||||
],
|
||||
})(<Input size="large" type="password" onPressEnter={handleOk} placeholder="密码" />)}
|
||||
})(<Input size="large" type="password" onPressEnter={handleOk} placeholder="Password" />)}
|
||||
</FormItem>
|
||||
<Row>
|
||||
<Button type="primary" size="large" onClick={handleOk} loading={loginLoading}>
|
||||
登录
|
||||
Sign in
|
||||
</Button>
|
||||
<p>
|
||||
<span>账号:guest</span>
|
||||
<span>密码:guest</span>
|
||||
<span>Username:guest</span>
|
||||
<span>Password:guest</span>
|
||||
</p>
|
||||
</Row>
|
||||
|
||||
|
||||
+20
-25
@@ -11,36 +11,42 @@ import {
|
||||
Button,
|
||||
} from 'antd'
|
||||
const { api, baseURL } = config
|
||||
const { userInfo, dashboard, users, userLogin } = api
|
||||
const { dashboard, users, userLogin, user } = api
|
||||
|
||||
const requestOptions = [
|
||||
{
|
||||
url: baseURL + userInfo,
|
||||
url: baseURL + user.replace('/:id', ''),
|
||||
desc: 'intercept request by mock.js',
|
||||
},
|
||||
{
|
||||
url: baseURL + dashboard,
|
||||
desc: 'intercept request by mock.js',
|
||||
},
|
||||
{
|
||||
url: baseURL + users,
|
||||
desc: 'intercept request by mock.js',
|
||||
},
|
||||
{
|
||||
url: baseURL + userLogin,
|
||||
method: 'post',
|
||||
data: {
|
||||
username: 'admin',
|
||||
password: 'admin6',
|
||||
username: 'guest',
|
||||
password: 'guest',
|
||||
},
|
||||
desc: 'intercept request by mock.js',
|
||||
},
|
||||
{
|
||||
url: baseURL + users,
|
||||
desc: 'intercept request by mock.js',
|
||||
},
|
||||
{
|
||||
url: baseURL + user,
|
||||
desc: 'intercept request by mock.js',
|
||||
data: Mock.mock({
|
||||
id: '@id',
|
||||
}),
|
||||
},
|
||||
{
|
||||
url: baseURL + user.replace('/:id', ''),
|
||||
desc: 'intercept request by mock.js',
|
||||
method: 'post',
|
||||
data: Mock.mock({
|
||||
'id|+1': 1000,
|
||||
name: '@cname',
|
||||
nickName: '@last',
|
||||
phone: /^1[34578]\d{9}$/,
|
||||
@@ -48,37 +54,26 @@ const requestOptions = [
|
||||
address: '@county(true)',
|
||||
isMale: '@boolean',
|
||||
email: '@email',
|
||||
createTime: '@datetime',
|
||||
avatar () {
|
||||
return Mock.Random.image('100x100', Mock.Random.color(), '#757575', 'png', this.nickName.substr(0, 1))
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
url: baseURL + users,
|
||||
url: baseURL + user,
|
||||
desc: 'intercept request by mock.js',
|
||||
method: 'put',
|
||||
method: 'patch',
|
||||
data: Mock.mock({
|
||||
id: 1,
|
||||
id: '@id',
|
||||
name: '@cname',
|
||||
nickName: '@last',
|
||||
phone: /^1[34578]\d{9}$/,
|
||||
'age|11-99': 1,
|
||||
address: '@county(true)',
|
||||
isMale: '@boolean',
|
||||
email: '@email',
|
||||
createTime: '@datetime',
|
||||
avatar () {
|
||||
return Mock.Random.image('100x100', Mock.Random.color(), '#757575', 'png', this.nickName.substr(0, 1))
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
url: baseURL + users,
|
||||
url: baseURL + user,
|
||||
desc: 'intercept request by mock.js',
|
||||
method: 'delete',
|
||||
data: Mock.mock({
|
||||
id: 10,
|
||||
id: '@id',
|
||||
}),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import moment from 'moment'
|
||||
import { FilterItem } from '../../components'
|
||||
import { Form, Button, Row, Col, DatePicker, Input, Cascader, Switch } from 'antd'
|
||||
import city from '../../utils/city'
|
||||
|
||||
const Search = Input.Search
|
||||
const { RangePicker } = DatePicker
|
||||
|
||||
const ColProps = {
|
||||
xs: 24,
|
||||
sm: 12,
|
||||
style: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
}
|
||||
|
||||
const TwoColProps = {
|
||||
...ColProps,
|
||||
xl: 96,
|
||||
}
|
||||
|
||||
const UserFilter = ({
|
||||
onAdd,
|
||||
isMotion,
|
||||
switchIsMotion,
|
||||
onFilterChange,
|
||||
filter,
|
||||
form: {
|
||||
getFieldDecorator,
|
||||
getFieldsValue,
|
||||
setFieldsValue,
|
||||
},
|
||||
}) => {
|
||||
const handleFields = (fields) => {
|
||||
const { createTime } = fields
|
||||
if (createTime.length) {
|
||||
fields.createTime = [createTime[0].format('YYYY-MM-DD'), createTime[1].format('YYYY-MM-DD')]
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
let fields = getFieldsValue()
|
||||
fields = handleFields(fields)
|
||||
console.log(fields)
|
||||
onFilterChange(fields)
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
const fields = getFieldsValue()
|
||||
for (let item in fields) {
|
||||
if ({}.hasOwnProperty.call(fields, item)) {
|
||||
if (fields[item] instanceof Array) {
|
||||
fields[item] = []
|
||||
} else {
|
||||
fields[item] = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
setFieldsValue(fields)
|
||||
handleSubmit()
|
||||
}
|
||||
|
||||
const handleChange = (key, values) => {
|
||||
let fields = getFieldsValue()
|
||||
fields[key] = values
|
||||
fields = handleFields(fields)
|
||||
onFilterChange(fields)
|
||||
}
|
||||
const { name, address } = filter
|
||||
|
||||
let initialCreateTime = []
|
||||
if (filter.createTime && filter.createTime[0]) {
|
||||
initialCreateTime[0] = moment(filter.createTime[0])
|
||||
}
|
||||
if (filter.createTime && filter.createTime[1]) {
|
||||
initialCreateTime[1] = moment(filter.createTime[1])
|
||||
}
|
||||
|
||||
return (
|
||||
<Row gutter={24}>
|
||||
<Col {...ColProps} xl={{ span: 4 }} md={{ span: 8 }}>
|
||||
{getFieldDecorator('name', { initialValue: name })(<Search placeholder="Search Name" size="large" onSearch={handleSubmit} />)}
|
||||
</Col>
|
||||
<Col {...ColProps} xl={{ span: 4 }} md={{ span: 8 }}>
|
||||
{getFieldDecorator('address', { initialValue: address })(
|
||||
<Cascader
|
||||
size="large"
|
||||
style={{ width: '100%' }}
|
||||
options={city}
|
||||
placeholder="Please pick an address"
|
||||
onChange={handleChange.bind(null, 'address')}
|
||||
/>)}
|
||||
</Col>
|
||||
<Col {...ColProps} xl={{ span: 6 }} md={{ span: 8 }} sm={{ span: 12 }}>
|
||||
<FilterItem label="Createtime">
|
||||
{getFieldDecorator('createTime', { initialValue: initialCreateTime })(
|
||||
<RangePicker style={{ width: '100%' }} size="large" onChange={handleChange.bind(null, 'createTime')} />
|
||||
)}
|
||||
</FilterItem>
|
||||
</Col>
|
||||
<Col {...TwoColProps} xl={{ span: 10 }} md={{ span: 24 }} sm={{ span: 24 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<div >
|
||||
<Button type="primary" size="large" className="margin-right" onClick={handleSubmit}>Search</Button>
|
||||
<Button size="large" onClick={handleReset}>Reset</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Switch style={{ marginRight: 16 }} size="large" defaultChecked={isMotion} onChange={switchIsMotion} checkedChildren={'Motion'} unCheckedChildren={'Motion'} />
|
||||
<Button size="large" type="ghost" onClick={onAdd}>Create</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
UserFilter.propTypes = {
|
||||
onAdd: PropTypes.func,
|
||||
isMotion: PropTypes.bool,
|
||||
switchIsMotion: PropTypes.func,
|
||||
form: PropTypes.object,
|
||||
filter: PropTypes.object,
|
||||
onFilterChange: PropTypes.func,
|
||||
}
|
||||
|
||||
export default Form.create()(UserFilter)
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Table, Modal } from 'antd'
|
||||
import styles from './UserList.less'
|
||||
import classnames from 'classnames'
|
||||
import AnimTableBody from '../../components/DataTable/AnimTableBody'
|
||||
import { DropOption } from '../../components'
|
||||
|
||||
import { Link } from 'dva/router'
|
||||
|
||||
const confirm = Modal.confirm
|
||||
|
||||
@@ -14,7 +15,7 @@ function list ({ loading, dataSource, pagination, onPageChange, onDeleteItem, on
|
||||
onEditItem(record)
|
||||
} else if (e.key === '2') {
|
||||
confirm({
|
||||
title: '您确定要删除这条记录吗?',
|
||||
title: 'Are you sure delete this record?',
|
||||
onOk () {
|
||||
onDeleteItem(record.id)
|
||||
},
|
||||
@@ -24,50 +25,50 @@ function list ({ loading, dataSource, pagination, onPageChange, onDeleteItem, on
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '头像',
|
||||
title: 'Avatar',
|
||||
dataIndex: 'avatar',
|
||||
key: 'avatar',
|
||||
width: 64,
|
||||
className: styles.avatar,
|
||||
render: (text) => <img alt={'avatar'} width={24} src={text} />,
|
||||
}, {
|
||||
title: '姓名',
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (text, record) => <Link to={`user/${record.id}`}>{text}</Link>,
|
||||
}, {
|
||||
title: '昵称',
|
||||
title: 'NickName',
|
||||
dataIndex: 'nickName',
|
||||
key: 'nickName',
|
||||
}, {
|
||||
title: '年龄',
|
||||
title: 'Age',
|
||||
dataIndex: 'age',
|
||||
key: 'age',
|
||||
render: (text) => <span>{text}岁</span>,
|
||||
}, {
|
||||
title: '性别',
|
||||
title: 'Gender',
|
||||
dataIndex: 'isMale',
|
||||
key: 'isMale',
|
||||
render: (text) => <span>{text
|
||||
? '男'
|
||||
: '女'}</span>,
|
||||
? 'Male'
|
||||
: 'Female'}</span>,
|
||||
}, {
|
||||
title: '电话',
|
||||
title: 'Phone',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone',
|
||||
}, {
|
||||
title: '邮箱',
|
||||
title: 'Email',
|
||||
dataIndex: 'email',
|
||||
key: 'email',
|
||||
}, {
|
||||
title: '住址',
|
||||
title: 'Address',
|
||||
dataIndex: 'address',
|
||||
key: 'address',
|
||||
}, {
|
||||
title: '创建时间',
|
||||
title: 'CreateTime',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
}, {
|
||||
title: '操作',
|
||||
title: 'Operation',
|
||||
key: 'operation',
|
||||
width: 100,
|
||||
render: (text, record) => {
|
||||
@@ -1,5 +1,8 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { Form, Input, InputNumber, Radio, Modal } from 'antd'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Form, Input, InputNumber, Radio, Modal, Cascader } from 'antd'
|
||||
import city from '../../utils/city'
|
||||
|
||||
const FormItem = Form.Item
|
||||
|
||||
const formItemLayout = {
|
||||
@@ -32,12 +35,13 @@ const modal = ({
|
||||
...getFieldsValue(),
|
||||
key: item.key,
|
||||
}
|
||||
data.address = data.address.join(' ')
|
||||
onOk(data)
|
||||
})
|
||||
}
|
||||
|
||||
const modalOpts = {
|
||||
title: `${type === 'create' ? '新建用户' : '修改用户'}`,
|
||||
title: `${type === 'create' ? 'Create User' : 'Update User'}`,
|
||||
visible,
|
||||
onOk: handleOk,
|
||||
onCancel,
|
||||
@@ -47,89 +51,89 @@ const modal = ({
|
||||
return (
|
||||
<Modal {...modalOpts}>
|
||||
<Form layout="horizontal">
|
||||
<FormItem label="姓名:" hasFeedback {...formItemLayout}>
|
||||
<FormItem label="Name" hasFeedback {...formItemLayout}>
|
||||
{getFieldDecorator('name', {
|
||||
initialValue: item.name,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '姓名未填写',
|
||||
},
|
||||
],
|
||||
})(<Input />)}
|
||||
</FormItem>
|
||||
<FormItem label="昵称:" hasFeedback {...formItemLayout}>
|
||||
<FormItem label="NickName" hasFeedback {...formItemLayout}>
|
||||
{getFieldDecorator('nickName', {
|
||||
initialValue: item.nickName,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '昵称未填写',
|
||||
},
|
||||
],
|
||||
})(<Input />)}
|
||||
</FormItem>
|
||||
<FormItem label="性别" hasFeedback {...formItemLayout}>
|
||||
<FormItem label="Gender" hasFeedback {...formItemLayout}>
|
||||
{getFieldDecorator('isMale', {
|
||||
initialValue: item.isMale,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
type: 'boolean',
|
||||
message: '请选择性别',
|
||||
},
|
||||
],
|
||||
})(
|
||||
<Radio.Group>
|
||||
<Radio value>男</Radio>
|
||||
<Radio value={false}>女</Radio>
|
||||
<Radio value>Male</Radio>
|
||||
<Radio value={false}>Female</Radio>
|
||||
</Radio.Group>
|
||||
)}
|
||||
</FormItem>
|
||||
<FormItem label="年龄:" hasFeedback {...formItemLayout}>
|
||||
<FormItem label="Age" hasFeedback {...formItemLayout}>
|
||||
{getFieldDecorator('age', {
|
||||
initialValue: item.age,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '年龄未填写',
|
||||
},
|
||||
],
|
||||
})(<InputNumber min={18} max={100} />)}
|
||||
</FormItem>
|
||||
<FormItem label="电话:" hasFeedback {...formItemLayout}>
|
||||
<FormItem label="Phone" hasFeedback {...formItemLayout}>
|
||||
{getFieldDecorator('phone', {
|
||||
initialValue: item.phone,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '不能为空',
|
||||
pattern: /^1[34578]\d{9}$/,
|
||||
},
|
||||
],
|
||||
})(<Input />)}
|
||||
</FormItem>
|
||||
<FormItem label="邮箱:" hasFeedback {...formItemLayout}>
|
||||
<FormItem label="Email" hasFeedback {...formItemLayout}>
|
||||
{getFieldDecorator('email', {
|
||||
initialValue: item.email,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '不能为空',
|
||||
pattern: /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/,
|
||||
},
|
||||
],
|
||||
})(<Input />)}
|
||||
</FormItem>
|
||||
<FormItem label="住址:" hasFeedback {...formItemLayout}>
|
||||
<FormItem label="Address" hasFeedback {...formItemLayout}>
|
||||
{getFieldDecorator('address', {
|
||||
initialValue: item.address,
|
||||
initialValue: item.address && item.address.split(' '),
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '不能为空',
|
||||
},
|
||||
],
|
||||
})(<Input />)}
|
||||
})(<Cascader
|
||||
size="large"
|
||||
style={{ width: '100%' }}
|
||||
options={city}
|
||||
placeholder="Pick an address"
|
||||
/>)}
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'dva'
|
||||
import styles from './index.less'
|
||||
|
||||
const Detail = ({ userDetail }) => {
|
||||
const { data } = userDetail
|
||||
const content = []
|
||||
for (let key in data) {
|
||||
if ({}.hasOwnProperty.call(data, key)) {
|
||||
content.push(<div key={key} className={styles.item}>
|
||||
<div>{key}</div>
|
||||
<div>{String(data[key])}</div>
|
||||
</div>)
|
||||
}
|
||||
}
|
||||
return (<div className="content-inner">
|
||||
<div className={styles.content}>
|
||||
{content}
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
|
||||
Detail.propTypes = {
|
||||
userDetail: PropTypes.object,
|
||||
loading: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default connect(({ userDetail, loading }) => ({ userDetail, loading: loading.models.userDetail }))(Detail)
|
||||
@@ -0,0 +1,14 @@
|
||||
.content {
|
||||
line-height: 2.4;
|
||||
font-size: 13px;
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
|
||||
& > div {
|
||||
&:first-child {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { routerRedux } from 'dva/router'
|
||||
import { connect } from 'dva'
|
||||
import UserList from './UserList'
|
||||
import UserFilter from './UserFilter'
|
||||
import UserModal from './UserModal'
|
||||
|
||||
function Users ({ location, dispatch, users, loading }) {
|
||||
const { list, pagination, currentItem, modalVisible, modalType, isMotion } = users
|
||||
const { field, keyword } = location.query
|
||||
const User = ({ location, dispatch, user, loading }) => {
|
||||
const { list, pagination, currentItem, modalVisible, modalType, isMotion } = user
|
||||
const { pageSize } = pagination
|
||||
|
||||
const userModalProps = {
|
||||
item: modalType === 'create' ? {} : currentItem,
|
||||
@@ -15,13 +16,13 @@ function Users ({ location, dispatch, users, loading }) {
|
||||
visible: modalVisible,
|
||||
onOk (data) {
|
||||
dispatch({
|
||||
type: `users/${modalType}`,
|
||||
type: `user/${modalType}`,
|
||||
payload: data,
|
||||
})
|
||||
},
|
||||
onCancel () {
|
||||
dispatch({
|
||||
type: 'users/hideModal',
|
||||
type: 'user/hideModal',
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -45,13 +46,13 @@ function Users ({ location, dispatch, users, loading }) {
|
||||
},
|
||||
onDeleteItem (id) {
|
||||
dispatch({
|
||||
type: 'users/delete',
|
||||
type: 'user/delete',
|
||||
payload: id,
|
||||
})
|
||||
},
|
||||
onEditItem (item) {
|
||||
dispatch({
|
||||
type: 'users/showModal',
|
||||
type: 'user/showModal',
|
||||
payload: {
|
||||
modalType: 'update',
|
||||
currentItem: item,
|
||||
@@ -61,30 +62,41 @@ function Users ({ location, dispatch, users, loading }) {
|
||||
}
|
||||
|
||||
const userFilterProps = {
|
||||
field,
|
||||
keyword,
|
||||
isMotion,
|
||||
filter: {
|
||||
...location.query,
|
||||
},
|
||||
onFilterChange (value) {
|
||||
dispatch(routerRedux.push({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
...value,
|
||||
page: 1,
|
||||
pageSize,
|
||||
},
|
||||
}))
|
||||
},
|
||||
onSearch (fieldsValue) {
|
||||
fieldsValue.keyword.length ? dispatch(routerRedux.push({
|
||||
pathname: '/users',
|
||||
pathname: '/user',
|
||||
query: {
|
||||
field: fieldsValue.field,
|
||||
keyword: fieldsValue.keyword,
|
||||
},
|
||||
})) : dispatch(routerRedux.push({
|
||||
pathname: '/users',
|
||||
pathname: '/user',
|
||||
}))
|
||||
},
|
||||
onAdd () {
|
||||
dispatch({
|
||||
type: 'users/showModal',
|
||||
type: 'user/showModal',
|
||||
payload: {
|
||||
modalType: 'create',
|
||||
},
|
||||
})
|
||||
},
|
||||
switchIsMotion () {
|
||||
dispatch({ type: 'users/switchIsMotion' })
|
||||
dispatch({ type: 'user/switchIsMotion' })
|
||||
},
|
||||
}
|
||||
|
||||
@@ -100,11 +112,11 @@ function Users ({ location, dispatch, users, loading }) {
|
||||
)
|
||||
}
|
||||
|
||||
Users.propTypes = {
|
||||
users: PropTypes.object,
|
||||
User.propTypes = {
|
||||
user: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
dispatch: PropTypes.func,
|
||||
loading: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default connect(({ users, loading }) => ({ users, loading: loading.models.users }))(Users)
|
||||
export default connect(({ user, loading }) => ({ user, loading: loading.models.user }))(User)
|
||||
@@ -1,49 +0,0 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { Form, Button, Row, Col, Switch } from 'antd'
|
||||
import { Search } from '../../components'
|
||||
|
||||
const UserFilter = ({
|
||||
field,
|
||||
keyword,
|
||||
onSearch,
|
||||
onAdd,
|
||||
isMotion,
|
||||
switchIsMotion,
|
||||
}) => {
|
||||
const searchGroupProps = {
|
||||
field,
|
||||
keyword,
|
||||
size: 'large',
|
||||
select: true,
|
||||
selectOptions: [{ value: 'name', name: '姓名' }, { value: 'address', name: '地址' }],
|
||||
selectProps: {
|
||||
defaultValue: field || 'name',
|
||||
},
|
||||
onSearch: (value) => {
|
||||
onSearch(value)
|
||||
},
|
||||
}
|
||||
return (
|
||||
<Row gutter={24}>
|
||||
<Col lg={8} md={12} sm={16} xs={24} style={{ marginBottom: 16 }}>
|
||||
<Search {...searchGroupProps} />
|
||||
</Col>
|
||||
<Col lg={{ offset: 8, span: 8 }} md={12} sm={8} xs={24} style={{ marginBottom: 16, textAlign: 'right' }}>
|
||||
{/* <Switch style={{ marginRight: 16 }} defaultChecked={isMotion} onChange={switchIsMotion} checkedChildren={'动画开'} unCheckedChildren={'动画关'} /> */}
|
||||
<Button size="large" type="ghost" onClick={onAdd}>添加</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
UserFilter.propTypes = {
|
||||
form: PropTypes.object.isRequired,
|
||||
onSearch: PropTypes.func,
|
||||
onAdd: PropTypes.func,
|
||||
field: PropTypes.string,
|
||||
keyword: PropTypes.string,
|
||||
isMotion: PropTypes.bool,
|
||||
switchIsMotion: PropTypes.func,
|
||||
}
|
||||
|
||||
export default Form.create()(UserFilter)
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
import { request, config } from '../utils'
|
||||
const { api } = config
|
||||
const { userInfo, userLogout } = api
|
||||
const { user, userLogout, userLogin } = api
|
||||
|
||||
export async function login (params) {
|
||||
return request({
|
||||
url: '/api/login',
|
||||
url: userLogin,
|
||||
method: 'post',
|
||||
data: params,
|
||||
})
|
||||
@@ -18,9 +18,9 @@ export async function logout (params) {
|
||||
})
|
||||
}
|
||||
|
||||
export async function getUserInfo (params) {
|
||||
export async function query (params) {
|
||||
return request({
|
||||
url: userInfo,
|
||||
url: user.replace('/:id', ''),
|
||||
method: 'get',
|
||||
data: params,
|
||||
})
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { request, config } from '../utils'
|
||||
const { api } = config
|
||||
const { user } = api
|
||||
|
||||
export async function query (params) {
|
||||
return request({
|
||||
url: user,
|
||||
method: 'get',
|
||||
data: params,
|
||||
})
|
||||
}
|
||||
|
||||
export async function create (params) {
|
||||
return request({
|
||||
url: user.replace('/:id', ''),
|
||||
method: 'post',
|
||||
data: params,
|
||||
})
|
||||
}
|
||||
|
||||
export async function remove (params) {
|
||||
return request({
|
||||
url: user,
|
||||
method: 'delete',
|
||||
data: params,
|
||||
})
|
||||
}
|
||||
|
||||
export async function update (params) {
|
||||
return request({
|
||||
url: user,
|
||||
method: 'patch',
|
||||
data: params,
|
||||
})
|
||||
}
|
||||
@@ -9,27 +9,3 @@ export async function query (params) {
|
||||
data: params,
|
||||
})
|
||||
}
|
||||
|
||||
export async function create (params) {
|
||||
return request({
|
||||
url: users,
|
||||
method: 'post',
|
||||
data: params,
|
||||
})
|
||||
}
|
||||
|
||||
export async function remove (params) {
|
||||
return request({
|
||||
url: users,
|
||||
method: 'delete',
|
||||
data: params,
|
||||
})
|
||||
}
|
||||
|
||||
export async function update (params) {
|
||||
return request({
|
||||
url: users,
|
||||
method: 'put',
|
||||
data: params,
|
||||
})
|
||||
}
|
||||
|
||||
+7
-14
@@ -113,21 +113,14 @@ body {
|
||||
}
|
||||
}
|
||||
@media (min-width: 1600px) {
|
||||
.ant-col-xl-48 {
|
||||
width: 20%;
|
||||
}
|
||||
:global {
|
||||
.ant-col-xl-48 {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.ant-col-xl-96 {
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1600px) {
|
||||
.ant-col-xl-48 {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.ant-col-xl-96 {
|
||||
width: 40%;
|
||||
.ant-col-xl-96 {
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
|
||||
+4073
File diff suppressed because it is too large
Load Diff
+3
-2
@@ -1,12 +1,12 @@
|
||||
module.exports = {
|
||||
name: 'AntD Admin',
|
||||
prefix: 'antdAdmin',
|
||||
footerText: 'Ant Design Admin 版权所有 © 2016 由 zuiidea 支持',
|
||||
footerText: 'Ant Design Admin © 2017 zuiidea',
|
||||
logo: 'https://t.alipayobjects.com/images/T1QUBfXo4fXXXXXXXX.png',
|
||||
iconFontUrl: '//at.alicdn.com/t/font_c4y7asse3q1cq5mi.js',
|
||||
baseURL: 'http://localhost:8000/api/v1',
|
||||
YQL: ['http://www.zuimeitianqi.com'],
|
||||
CORS: ['http://localhost:7001'],
|
||||
CORS: ['http://localhost:7001', 'http://192.168.1.110:8000'],
|
||||
openPages: ['/login'],
|
||||
apiPrefix: '/api/v1',
|
||||
api: {
|
||||
@@ -14,6 +14,7 @@ module.exports = {
|
||||
userLogout: '/user/logout',
|
||||
userInfo: '/userInfo',
|
||||
users: '/users',
|
||||
user: '/user/:id',
|
||||
dashboard: '/dashboard',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import menu from './menu'
|
||||
import request from './request'
|
||||
import classnames from 'classnames'
|
||||
import { color } from './theme'
|
||||
import lodash from 'lodash'
|
||||
|
||||
// 连字符转驼峰
|
||||
String.prototype.hyphenToHump = function () {
|
||||
@@ -52,6 +53,52 @@ const queryURL = (name) => {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组内查询
|
||||
* @param {array} array
|
||||
* @param {String} id
|
||||
* @param {String} keyAlias
|
||||
* @return {Array}
|
||||
*/
|
||||
const queryArray = (array, key, keyAlias = 'key') => {
|
||||
if (!(array instanceof Array)) {
|
||||
return null
|
||||
}
|
||||
const item = array.filter(_ => _[keyAlias] === key)
|
||||
if (item.length) {
|
||||
return item[0]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组格式转树状结构
|
||||
* @param {array} array
|
||||
* @param {String} id
|
||||
* @param {String} pid
|
||||
* @param {String} children
|
||||
* @return {Array}
|
||||
*/
|
||||
const arrayToTree = (array, id = 'id', pid = 'pid', children = 'children') => {
|
||||
let data = lodash.cloneDeep(array)
|
||||
let result = []
|
||||
let hash = {}
|
||||
data.forEach((item, index) => {
|
||||
hash[data[index][id]] = data[index]
|
||||
})
|
||||
|
||||
data.forEach((item) => {
|
||||
let hashVP = hash[item[pid]]
|
||||
if (hashVP) {
|
||||
!hashVP[children] && (hashVP[children] = [])
|
||||
hashVP[children].push(item)
|
||||
} else {
|
||||
result.push(item)
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
config,
|
||||
menu,
|
||||
@@ -59,4 +106,6 @@ module.exports = {
|
||||
color,
|
||||
classnames,
|
||||
queryURL,
|
||||
queryArray,
|
||||
arrayToTree,
|
||||
}
|
||||
|
||||
+122
-77
@@ -1,102 +1,147 @@
|
||||
module.exports = [
|
||||
{
|
||||
key: 'dashboard',
|
||||
name: 'Dashboard',
|
||||
id: 1,
|
||||
icon: 'laptop',
|
||||
name: 'Dashboard',
|
||||
router: '/dashboard',
|
||||
},
|
||||
{
|
||||
key: 'users',
|
||||
id: 2,
|
||||
bpid: 1,
|
||||
name: 'User Manage',
|
||||
icon: 'user',
|
||||
router: '/user',
|
||||
},
|
||||
{
|
||||
key: 'request',
|
||||
id: 21,
|
||||
mpid: -1,
|
||||
bpid: 2,
|
||||
name: 'User Detail',
|
||||
router: '/user/:id',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
bpid: 1,
|
||||
name: 'Request',
|
||||
icon: 'api',
|
||||
router: '/request',
|
||||
},
|
||||
{
|
||||
key: 'UIElement',
|
||||
id: 4,
|
||||
bpid: 1,
|
||||
name: 'UI Element',
|
||||
icon: 'camera-o',
|
||||
clickable: false,
|
||||
child: [
|
||||
{
|
||||
key: 'iconfont',
|
||||
name: 'IconFont',
|
||||
icon: 'heart-o',
|
||||
},
|
||||
{
|
||||
key: 'dataTable',
|
||||
name: 'DataTable',
|
||||
icon: 'database',
|
||||
},
|
||||
{
|
||||
key: 'dropOption',
|
||||
name: 'DropOption',
|
||||
icon: 'bars',
|
||||
},
|
||||
{
|
||||
key: 'search',
|
||||
name: 'Search',
|
||||
icon: 'search',
|
||||
},
|
||||
{
|
||||
key: 'editor',
|
||||
name: 'Editor',
|
||||
icon: 'edit',
|
||||
},
|
||||
{
|
||||
key: 'layer',
|
||||
name: 'layer (Function)',
|
||||
icon: 'credit-card',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'chart',
|
||||
id: 41,
|
||||
bpid: 4,
|
||||
mpid: 4,
|
||||
name: 'IconFont',
|
||||
icon: 'heart-o',
|
||||
router: '/UIElement/iconfont',
|
||||
},
|
||||
{
|
||||
id: 42,
|
||||
bpid: 4,
|
||||
mpid: 4,
|
||||
name: 'DataTable',
|
||||
icon: 'database',
|
||||
router: '/UIElement/dataTable',
|
||||
},
|
||||
{
|
||||
id: 43,
|
||||
bpid: 4,
|
||||
mpid: 4,
|
||||
name: 'DropOption',
|
||||
icon: 'bars',
|
||||
router: '/UIElement/dropOption',
|
||||
},
|
||||
{
|
||||
id: 44,
|
||||
bpid: 4,
|
||||
mpid: 4,
|
||||
name: 'Search',
|
||||
icon: 'search',
|
||||
router: '/UIElement/search',
|
||||
},
|
||||
{
|
||||
id: 45,
|
||||
bpid: 4,
|
||||
mpid: 4,
|
||||
name: 'Editor',
|
||||
icon: 'edit',
|
||||
router: '/UIElement/editor',
|
||||
},
|
||||
{
|
||||
id: 46,
|
||||
bpid: 4,
|
||||
mpid: 4,
|
||||
name: 'layer (Function)',
|
||||
icon: 'credit-card',
|
||||
router: '/UIElement/layer',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
bpid: 1,
|
||||
name: 'Recharts',
|
||||
icon: 'code-o',
|
||||
child: [
|
||||
{
|
||||
key: 'lineChart',
|
||||
name: 'LineChart',
|
||||
icon: 'line-chart',
|
||||
},
|
||||
{
|
||||
key: 'barChart',
|
||||
name: 'BarChart',
|
||||
icon: 'bar-chart',
|
||||
},
|
||||
{
|
||||
key: 'areaChart',
|
||||
name: 'AreaChart',
|
||||
icon: 'area-chart',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'navigation',
|
||||
id: 51,
|
||||
bpid: 5,
|
||||
mpid: 5,
|
||||
name: 'LineChart',
|
||||
icon: 'line-chart',
|
||||
router: '/chart/lineChart',
|
||||
},
|
||||
{
|
||||
id: 52,
|
||||
bpid: 5,
|
||||
mpid: 5,
|
||||
name: 'BarChart',
|
||||
icon: 'bar-chart',
|
||||
router: '/chart/barChart',
|
||||
},
|
||||
{
|
||||
id: 53,
|
||||
bpid: 5,
|
||||
mpid: 5,
|
||||
name: 'AreaChart',
|
||||
icon: 'area-chart',
|
||||
router: '/chart/areaChart',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
bpid: 1,
|
||||
name: 'Test Navigation',
|
||||
icon: 'setting',
|
||||
child: [
|
||||
{
|
||||
key: 'navigation1',
|
||||
name: 'Test Navigation1',
|
||||
},
|
||||
{
|
||||
key: 'navigation2',
|
||||
name: 'Test Navigation2',
|
||||
child: [
|
||||
{
|
||||
key: 'navigation21',
|
||||
name: 'Test Navigation21',
|
||||
},
|
||||
{
|
||||
key: 'navigation22',
|
||||
name: 'Test Navigation22',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 61,
|
||||
bpid: 6,
|
||||
mpid: 6,
|
||||
name: 'Test Navigation1',
|
||||
router: '/navigation/navigation1',
|
||||
},
|
||||
{
|
||||
id: 62,
|
||||
bpid: 6,
|
||||
mpid: 6,
|
||||
name: 'Test Navigation2',
|
||||
router: '/navigation/navigation2',
|
||||
},
|
||||
{
|
||||
id: 621,
|
||||
bpid: 62,
|
||||
mpid: 62,
|
||||
name: 'Test Navigation21',
|
||||
router: '/navigation/navigation2/navigation1',
|
||||
},
|
||||
{
|
||||
id: 622,
|
||||
bpid: 62,
|
||||
mpid: 62,
|
||||
name: 'Test Navigation22',
|
||||
router: '/navigation/navigation2/navigation2',
|
||||
},
|
||||
]
|
||||
|
||||
+38
-12
@@ -3,6 +3,8 @@ import qs from 'qs'
|
||||
import { YQL, CORS, baseURL } from './config'
|
||||
import jsonp from 'jsonp'
|
||||
import lodash from 'lodash'
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
import { message } from 'antd'
|
||||
|
||||
axios.defaults.baseURL = baseURL
|
||||
|
||||
@@ -14,6 +16,26 @@ const fetch = (options) => {
|
||||
url,
|
||||
} = options
|
||||
|
||||
const cloneData = lodash.cloneDeep(data)
|
||||
|
||||
try {
|
||||
let domin = ''
|
||||
if (url.match(/[a-zA-z]+:\/\/[^/]*/)) {
|
||||
domin = url.match(/[a-zA-z]+:\/\/[^/]*/)[0]
|
||||
url = url.slice(domin.length)
|
||||
}
|
||||
const match = pathToRegexp.parse(url)
|
||||
url = pathToRegexp.compile(url)(data)
|
||||
for (let item of match) {
|
||||
if (item instanceof Object && item.name in cloneData) {
|
||||
delete cloneData[item.name]
|
||||
}
|
||||
}
|
||||
url = domin + url
|
||||
} catch (e) {
|
||||
message.error(e.message)
|
||||
}
|
||||
|
||||
if (fetchType === 'JSONP') {
|
||||
return new Promise((resolve, reject) => {
|
||||
jsonp(url, {
|
||||
@@ -34,17 +56,19 @@ const fetch = (options) => {
|
||||
|
||||
switch (method.toLowerCase()) {
|
||||
case 'get':
|
||||
return axios.get(`${url}${!lodash.isEmpty(data) ? `?${qs.stringify(data)}` : ''}`)
|
||||
return axios.get(url, {
|
||||
params: cloneData,
|
||||
})
|
||||
case 'delete':
|
||||
return axios.delete(url, { data })
|
||||
case 'head':
|
||||
return axios.head(url, data)
|
||||
return axios.delete(url, {
|
||||
data: cloneData,
|
||||
})
|
||||
case 'post':
|
||||
return axios.post(url, data)
|
||||
return axios.post(url, cloneData)
|
||||
case 'put':
|
||||
return axios.put(url, data)
|
||||
return axios.put(url, cloneData)
|
||||
case 'patch':
|
||||
return axios.patch(url, data)
|
||||
return axios.patch(url, cloneData)
|
||||
default:
|
||||
return axios(options)
|
||||
}
|
||||
@@ -75,16 +99,18 @@ export default function request (options) {
|
||||
}
|
||||
}).catch((error) => {
|
||||
const { response } = error
|
||||
let message
|
||||
let msg
|
||||
let status
|
||||
let otherData = {}
|
||||
if (response) {
|
||||
status = response.status
|
||||
const { data, statusText } = response
|
||||
message = data.message || statusText
|
||||
otherData = data
|
||||
status = response.status
|
||||
msg = data.message || statusText
|
||||
} else {
|
||||
status = 600
|
||||
message = 'Network Error'
|
||||
msg = 'Network Error'
|
||||
}
|
||||
return { success: false, status, message }
|
||||
return { success: false, status, message: msg, ...otherData }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
const webpack = require('atool-build/lib/webpack')
|
||||
|
||||
module.exports = function (webpackConfig, env) {
|
||||
webpackConfig.babel.plugins.push('transform-runtime')
|
||||
webpackConfig.babel.plugins.push(['import', {
|
||||
libraryName: 'antd',
|
||||
style: true,
|
||||
}])
|
||||
|
||||
// Support hmr
|
||||
if (env === 'development') {
|
||||
webpackConfig.devtool = '#eval'
|
||||
webpackConfig.babel.plugins.push(['dva-hmr', {
|
||||
entries: [
|
||||
'./src/index.js',
|
||||
],
|
||||
}])
|
||||
} else {
|
||||
webpackConfig.babel.plugins.push('dev-expression')
|
||||
webpackConfig.entry = { index: './src/_index.js' }
|
||||
}
|
||||
|
||||
// Don't extract common.js and common.css
|
||||
webpackConfig.plugins = webpackConfig.plugins.filter((plugin) => {
|
||||
return !(plugin instanceof webpack.optimize.CommonsChunkPlugin)
|
||||
})
|
||||
|
||||
// Support CSS Modules
|
||||
// Parse all less files as css module.
|
||||
webpackConfig.module.loaders.forEach((loader) => {
|
||||
if (typeof loader.test === 'function' && loader.test.toString().indexOf('\\.less$') > -1) {
|
||||
loader.include = /node_modules/
|
||||
loader.test = /\.less$/
|
||||
}
|
||||
if (loader.test.toString() === '/\\.module\\.less$/') {
|
||||
loader.exclude = /node_modules/
|
||||
loader.test = /\.less$/
|
||||
}
|
||||
if (typeof loader.test === 'function' && loader.test.toString().indexOf('\\.css$') > -1) {
|
||||
loader.include = /node_modules/
|
||||
loader.test = /\.css$/
|
||||
}
|
||||
if (loader.test.toString() === '/\\.module\\.css$/') {
|
||||
loader.exclude = /node_modules/
|
||||
loader.test = /\.css$/
|
||||
}
|
||||
})
|
||||
|
||||
return webpackConfig
|
||||
}
|
||||
Reference in New Issue
Block a user