This commit is contained in:
zuiidea
2017-04-27 20:26:30 +08:00
parent fbd0fcb5c4
commit 1b7c076057
16 changed files with 4313 additions and 97 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"
}
+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,
}
+11 -10
View File
@@ -4,10 +4,10 @@ const config = require('../utils/config')
const { apiPrefix } = config
let usersListData = Mock.mock({
'data|100': [
'data|80-100': [
{
id: '@id',
name: '@cname',
name: '@name',
nickName: '@last',
phone: /^1[34578]\d{9}$/,
'age|11-99': 1,
@@ -54,7 +54,6 @@ const adminUsers = [
},
]
const queryArray = (array, key, keyAlias = 'key') => {
if (!(array instanceof Array)) {
return null
@@ -94,16 +93,16 @@ module.exports = {
})
res.json({ success: true, message: 'Ok' })
} else {
res.status(400).send({ message: 'Bad Request' })
res.status(400).end()
}
},
[`GET ${apiPrefix}/user/logout`] (req, res) {
res.clearCookie('token')
res.status(200).send({ message: 'OK' })
res.status(200).end()
},
[`GET ${apiPrefix}/userInfo`] (req, res) {
[`GET ${apiPrefix}/user`] (req, res) {
const cookie = req.headers.cookie || ''
const cookies = qs.parse(cookie.replace(/\s/g, ''), { delimiter: ';' })
const response = {}
@@ -174,11 +173,13 @@ module.exports = {
[`DELETE ${apiPrefix}/user/:id`] (req, res) {
const { id } = req.params
if (!id) {
res.status(400).end()
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)
}
database = database.filter((item) => item.id !== id)
res.status(204).end()
},
[`PATCH ${apiPrefix}/user/:id`] (req, res) {
+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',
+2 -2
View File
@@ -11,11 +11,11 @@ import {
Button,
} from 'antd'
const { api, baseURL } = config
const { userInfo, dashboard, users, userLogin, user } = api
const { dashboard, users, userLogin, user } = api
const requestOptions = [
{
url: baseURL + userInfo,
url: baseURL + user.replace('/:id', ''),
desc: 'intercept request by mock.js',
},
{
+108 -26
View File
@@ -1,50 +1,132 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Form, Button, Row, Col, Switch } from 'antd'
import { Search } from '../../components'
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,
md: 8,
lg: 6,
xl: 48,
style: {
marginBottom: 16,
},
}
const TwoColProps = {
...ColProps,
xl: 96,
}
const UserFilter = ({
field,
keyword,
onSearch,
onAdd,
isMotion,
switchIsMotion,
onFilterChange,
filter,
form: {
getFieldDecorator,
getFieldsValue,
setFieldsValue,
},
}) => {
const searchGroupProps = {
field,
keyword,
size: 'large',
select: true,
selectOptions: [{ value: 'name', name: '姓名' }, { value: 'address', name: '地址' }],
selectProps: {
defaultValue: field || 'name',
},
onSearch: (value) => {
onSearch(value)
},
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 lg={8} md={12} sm={16} xs={24} style={{ marginBottom: 16 }}>
<Search {...searchGroupProps} />
<Col {...ColProps}>
{getFieldDecorator('name', { initialValue: name })(<Search placeholder="Search Name" size="large" onSearch={handleSubmit} />)}
</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 {...ColProps}>
{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}>
<FilterItem label="Createtime">
{getFieldDecorator('createTime', { initialValue: initialCreateTime })(
<RangePicker style={{ width: '100%' }} size="large" onChange={handleChange.bind(null, 'createTime')} />
)}
</FilterItem>
</Col>
<Col {...TwoColProps}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div >
<Button type="primary" size="large" className="margin-right" onClick={handleSubmit}>Search</Button>
<Button size="large" className="margin-right" onClick={handleReset}>Reset</Button>
</div>
<div>
<Switch style={{ marginRight: 16 }} defaultChecked={isMotion} onChange={switchIsMotion} checkedChildren={'动画开'} unCheckedChildren={'动画关'} />
<Button size="large" type="ghost" onClick={onAdd}>Create</Button>
</div>
</div>
</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,
form: PropTypes.object,
filter: PropTypes.object,
onFilterChange: PropTypes.func,
}
export default Form.create()(UserFilter)
+13 -14
View File
@@ -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,51 +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) => {
+21 -20
View File
@@ -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,91 +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>
+1 -1
View File
@@ -1,6 +1,6 @@
.content {
line-height: 2.4;
font-size: 14px;
font-size: 13px;
.item {
display: flex;
+14 -3
View File
@@ -8,7 +8,7 @@ import UserModal from './UserModal'
const User = ({ location, dispatch, user, loading }) => {
const { list, pagination, currentItem, modalVisible, modalType, isMotion } = user
const { field, keyword } = location.query
const { current, pageSize } = pagination
const userModalProps = {
item: modalType === 'create' ? {} : currentItem,
@@ -62,9 +62,20 @@ const User = ({ location, dispatch, user, loading }) => {
}
const userFilterProps = {
field,
keyword,
isMotion,
filter: {
...location.query,
},
onFilterChange (value) {
dispatch(routerRedux.push({
pathname: location.pathname,
query: {
...value,
page: current,
pageSize,
},
}))
},
onSearch (fieldsValue) {
fieldsValue.keyword.length ? dispatch(routerRedux.push({
pathname: '/user',
+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,
})
+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
+4 -2
View File
@@ -101,14 +101,16 @@ export default function request (options) {
const { response } = error
let msg
let status
let otherData = {}
if (response) {
status = response.status
const { data, statusText } = response
otherData = data
status = response.status
msg = data.message || statusText
} else {
status = 600
msg = 'Network Error'
}
return { success: false, status, message: msg }
return { success: false, status, message: msg, ...otherData }
})
}