Merge branch '4.2-dev'

This commit is contained in:
zuiidea
2017-04-28 16:42:59 +08:00
33 changed files with 4811 additions and 434 deletions
+29
View File
@@ -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
+17
View File
@@ -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;
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"name": "FilterItem",
"version": "0.0.0",
"private": true,
"main": "./FilterItem.js"
}
+12 -8
View File
@@ -6,7 +6,7 @@ import styles from './Bread.less'
import pathToRegexp from 'path-to-regexp'
import { queryArray } from '../../utils'
const Bread = ({ location, menu }) => {
const Bread = ({ menu }) => {
// 匹配当前路由
let pathArray = []
let current
@@ -17,19 +17,24 @@ const Bread = ({ location, menu }) => {
}
}
if (!current) {
return (<div className={styles.bread}>首页</div>)
}
// 递归查找父级
const getPathArray = (item) => {
pathArray.unshift(item)
if (item.bpid) {
getPathArray(queryArray(menu, item.bpid, 'id'))
}
}
getPathArray(current)
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>{item.icon
@@ -58,7 +63,6 @@ const Bread = ({ location, menu }) => {
Bread.propTypes = {
menu: PropTypes.array,
location: PropTypes.object,
}
export default Bread
+1 -1
View File
@@ -40,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>
+1 -1
View File
@@ -90,7 +90,7 @@ const Menus = ({ siderFold, darkTheme, location, handleClickNavMenu, navOpenKeys
const getPathArray = (array, current, pid, id) => {
let result = [String(current[id])]
const getPath = (item) => {
if (item[pid]) {
if (item && item[pid]) {
result.unshift(String(item[pid]))
getPath(queryArray(array, item[pid], id))
}
+2 -2
View File
@@ -22,8 +22,8 @@ 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>
)
+2
View File
@@ -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,
}
+217
View File
@@ -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)
}
},
}
-187
View File
@@ -1,187 +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[0] && 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 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 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 })
},
}
+2 -2
View File
@@ -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'
@@ -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',
+30 -39
View File
@@ -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 ({
+51
View File
@@ -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,
}
},
},
}
View File
+12 -4
View File
@@ -30,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',
+5 -7
View File
@@ -38,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
View File
@@ -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',
}),
},
{
+129
View File
@@ -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)
@@ -5,7 +5,7 @@ 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
@@ -15,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)
},
@@ -25,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,6 +1,8 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Form, Input, InputNumber, Radio, Modal } from 'antd'
import { Form, Input, InputNumber, Radio, Modal, Cascader } from 'antd'
import city from '../../utils/city'
const FormItem = Form.Item
const formItemLayout = {
@@ -33,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,
@@ -48,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>
+29
View File
@@ -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)
+14
View File
@@ -0,0 +1,14 @@
.content {
line-height: 2.4;
font-size: 13px;
.item {
display: flex;
& > div {
&:first-child {
width: 100px;
}
}
}
}
@@ -6,9 +6,9 @@ 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,
@@ -16,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',
})
},
}
@@ -46,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,
@@ -62,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' })
},
}
@@ -101,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)
-50
View File
@@ -1,50 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
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)
+3 -3
View File
@@ -1,6 +1,6 @@
import { request, config } from '../utils'
const { api } = config
const { userInfo, userLogout, userLogin } = api
const { user, userLogout, userLogin } = api
export async function login (params) {
return request({
@@ -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,
})
+35
View File
@@ -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,
})
}
-24
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -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',
},
}
+8 -1
View File
@@ -10,7 +10,14 @@ module.exports = [
bpid: 1,
name: 'User Manage',
icon: 'user',
router: '/users',
router: '/user',
},
{
id: 21,
mpid: -1,
bpid: 2,
name: 'User Detail',
router: '/user/:id',
},
{
id: 3,
+38 -13
View File
@@ -3,10 +3,11 @@ 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
const fetch = (options) => {
let {
method = 'get',
@@ -15,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, {
@@ -35,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)
}
@@ -76,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 }
})
}