$ mkdir myapp && cd myapp
$ yarn create umi
$ cnpm i styled-components jsonwebtoken -S
$ npm start
.webpackrc.js
const {resolve}=require('path');
export default {
"alias":{
"@":resolve("src")
}
}
import React,{Component} from 'react';
import {Layout,Form,Input,Radio,Cascader,Select,AutoComplete,Checkbox,Button,message} from 'antd';
import styled from 'styled-components';
import {connect} from 'dva';//react-redux 是用来连接仓库和组件
import addresses from '../../utils/addresses';
import getFieldItems from '../../utils/getFieldItems';
const { Header, Footer, Sider, Content } = Layout;
const FormItem = Form.Item;
const {Option} = Select;
class Login extends Component{
handleSubmit=event => {
event.preventDefault();
this.userForm.props.form.validateFields((err,values) => {
if (err) {message.error('输入不合法!');
} else {
this.props.dispatch({type: this.props.isLogin?'login/login':'login/signup',payload:values});
}
});
}
changeLoginStatus = ()=>{
this.props.dispatch({type:'login/save',payload:{isLogin:!this.props.isLogin}});
}
render(){
return (
<Layout>
<Content>
<LoginForm
isLogin={this.props.isLogin}
changeLoginStatus={this.changeLoginStatus}
handleSubmit={this.handleSubmit}
wrappedComponentRef={inst => this.userForm=inst}
handleSubmit={this.handleSubmit}
/>
</Content>
</Layout>
)
}
}
class LoginForm extends Component{
state ={gender:1,autoCompleteResult:[],repasswordDirty:false}
handleWebsiteChange = (value)=>{//1 [1.com,1.cn,1.org]
let autoCompleteResult=[];
if(value){
autoCompleteResult = [".com",".cn",".org"].map(domain=>value+domain);
}
this.setState({autoCompleteResult});
}
compareWithRepassword = (rule,value,callback)=>{
const form = this.props.form;
if(value&& this.state.repasswordDirty){
form.validateFields(['repassword'],{force:true});
}
callback();
}
compareWithPassword = (rule,value,callback)=>{//自定义校验器 规则 最新的值 回调
const form = this.props.form;
if(value && value !== form.getFieldValue('password')){
callback('密码和确认密码不一致');
}else{
callback();
}
}
repasswordChange = (event)=>{
this.setState({repasswordDirty:this.state.repasswordDirty||event.target.value.length>0});
}
render(){
//一旦把组件用Form.create包裹
let {form:{getFieldDecorator},isLogin,handleSubmit} = this.props;
let formTailItemLayout = {
wrapperCol:{offset:4,span:20}
}
let countrySelector = getFieldDecorator('prefix',{
initialValue:'086'
})(
<Select style={{width:70}}>
<Option value="086">086</Option>
<Option value="087">087</Option>
<Option value="088">088</Option>
</Select>
);
let websiteOptions = this.state.autoCompleteResult.map(item=>(
<AutoComplete.Option key={item}>{item}</AutoComplete.Option>
));
let websiteField = (
<AutoComplete onChange={this.handleWebsiteChange}>
{websiteOptions}
</AutoComplete>
)
let genderField = (
<Radio.Group >
<Radio value={1}>男</Radio>
<Radio value={0}>女</Radio>
</Radio.Group>
)
let filedItems = getFieldItems(getFieldDecorator,[
{visible:true,label:"用户名",name:"username",required:true,input:<Input/>},
{visible:true,label:"密码",name:"password",required:true,input:<Input onChange={this.repasswordChange}/>,rules:[
{validator:this.compareWithRepassword},
{min:1,message:'密码长度最短1位'},
{max:8,message:'密码长度最长8位'}
]},
{visible:!isLogin,label:"确认密码",name:"repassword",required:false,input:<Input/>,rules:[
{validator:this.compareWithPassword}
]},
{visible:!isLogin,label:"邮箱",name:"email",required:true,input:<Input/>,rules:[{type:'email',message:'必须输入一个合法的邮箱!'}]},
{visible:!isLogin,label:"性别",name:"gender",required:true,input:genderField,extra:{initialValue:1}},
{visible:!isLogin,label:"住址",name:"address",required:true,input:<Cascader options={addresses}/>},
{visible:!isLogin,label:"手机号",name:"phone",required:true,rules:[{pattern:/^1\d{10}$/,message:'请输入合法手机号'}],input:<Input addonBefore={countrySelector} style={{width:'100%'}}/>},
{visible:!isLogin,label:"网址",name:"website",input:websiteField},
{visible:!isLogin,name:"agreement",layout:formTailItemLayout,input:<Checkbox>我已经同意本协议</Checkbox>,extra:{valuePropName:'checked'}}
]);
return (
<FormWrapper>
<Form style={{width:'500px'}} onSubmit={handleSubmit}>
<h3>欢迎{isLogin?"登录":"注册"}</h3>
{filedItems}
<FormItem>
<Button type="primary" htmlType="submit" style={{width:'100%'}}>{isLogin?"登录":"注册"}</Button>
已有账号?<a href="#" onClick={this.props.changeLoginStatus}>立刻{isLogin?"注册":"登录"}</a>
</FormItem>
</Form>
</FormWrapper>
)
}
}
LoginForm = Form.create()(LoginForm);
const FormWrapper = styled.div`
display:flex;
justify-content:center;
align-items:center;
height:calc(100vh - 70px );
h3{
text-align:center;
}
form{
border:1px solid #999;
border-radius:5px;
padding:20px;
}
`
//一般来说在dva里,一个页面路由组件会对应一个子状态 login
export default connect(
state=>state.login
)(Login);
src\utils\getFieldItems.js
import {Form} from 'antd';
const FormItem = Form.Item;
let formItemLayout = {
labelCol:{span:4},wrapperCol:{span:20}
}
function getFieldItems(getFieldDecorator,fields){
//{label,name,rules,input}
return fields.filter(field=>field.visible).map((field,index)=>{
let layout = field.layout?field.layout:formItemLayout;
field.extra = field.extra||{};
field.rules = field.rules||[];
return (
<FormItem key={index} label={field.label} {...layout}>
{
getFieldDecorator(field.name,{
rules:[{required:field.required,message:`${field.label}必须输入`},...field.rules],
...field.extra
})(field.input)
}
</FormItem>
)
});
}
export default getFieldItems;
src\utils\addresses.js
export default [
{
value:'guangdong',
label: '广东',
children: [
{value:'guangzhou',label: '广州',},
{value:'dongguan',label: '东莞',}
]
},
{
value:'shandong',
label: '山东',
children: [
{value:'jinan',label: '济南'},
{value:'shouguang',label: '寿光',}
]
}
]
src/utils/request.js
import fetch from 'dva/fetch';
const BASE_URL = 'http://127.0.0.1:7001';
export default function (url, options={}) {
let token = localStorage.getItem('token');
options.headers = options.headers || {};
if (token) {
options.headers.authorization = token;
}
options.method = options.method || 'GET';
options.headers["Content-Type"] = "application/json";
options.headers["Accept"] = "application/json";
options.credentials = 'include';
return fetch(BASE_URL + url,options).then(res => res.json());
}
src/pages/login/models/login.js
import * as service from '../services/login';
import {decode} from 'jsonwebtoken';
import {routerRedux} from 'dva/router';
import {message} from 'antd';
export default {
namespace:'login',
state:{
isLogin:true,//是否正在注册
errorInfo:'',//放着报错信息
userInfo:null//如果用户登录之后,会把用户信息放在这里
},
effects:{
*signup({payload},{call,put}){
let result = yield call(service.signup,payload);
if(result.code === 0){
yield put({type:'save',payload:{isLogin:true}});
}else{
message.error('注册失败!');
}
},
*login({payload},{call,put}){
const result = yield call(service.login,payload);
if(result.code === 0){
const userInfo = decode(result.data);
yield put({type:'save',payload:{userInfo}});
localStorage.setItem('token',result.data);
yield put(routerRedux.push('/admin'));
}else{
message.error('登录失败!');
}
},
*loadUser({payload},{call,put}) {
let token=localStorage.getItem('token');
if (token) {
const userInfo = decode(token);
yield put({type:'save',payload:{userInfo}});
} else {
yield put(routerRedux.push('/login'));
}
}
},
reducers:{
save(state,action){
return {...state,...action.payload};
}
}
}
src/pages/login/services/login.js
import request from '../../../utils/request';
//定义一个注册的方法,调用api接口
export function signup(payload){
return request('/api/signup',{
method:'POST',
body:JSON.stringify(payload)
});
}
//定义一个登陆的方法
export function login(payload) {
return request(`/api/signin`,{
method: 'POST',
body:JSON.stringify(payload)
});
}
src/pages/login/index.js LoginForm
+ import getFieldItems,{formItemLayout} from '../../utils/getFieldItems';
+ const captchaUrl=`http://127.0.0.1:7001/api/captcha?ts=`;
+ refreshCaptcha = (event)=>{
+ event.target.src = captchaUrl+Date.now();
+ }
+ <FormItem label="验证码" {...formItemLayout} extra="证明你不是机器人">
+ <Row gutter={8}>
+ <Col span={12}>
+ {
+ getFieldDecorator('captcha',{
+ rules: [{required:true,message:'必须输入验证码'}]
+ })(<Input />)
+ }
+ </Col>
+ <Col span={12}>
+ <img src={captchaUrl} onClick={this.refreshCaptcha}/>
+ </Col>
+ </Row>
+ </FormItem>
src/pages/admin/_layout.js
import React,{Component,Fragment} from 'react';
import { Layout } from 'antd';
const { Header, Footer, Sider, Content } = Layout;
export default class Admin extends Component{
render() {
return (
<Layout>
<Header>Header</Header>
<Layout>
<Sider>Sider</Sider>
<Content>{this.props.children}</Content>
</Layout>
<Footer>
珠峰培训 ©2018
</Footer>
</Layout>
)
}
}
src\components\AdminHeader\index.js
import React,{Component} from 'react';
import {Layout} from 'antd';
import {connect} from 'dva';
import styles from './index.less';
const {Header}=Layout;
class AdminHeader extends Component{
componentWillMount() {
this.props.dispatch({type:'login/loadUser'});
}
render() {
let {userInfo}=this.props;
return (
<Header className={styles.header}>
<img className={styles.logo} src="http://upload-markdown-images.oss-cn-beijing.aliyuncs.com/zfpxlogo.png" alt="logo"/>
<span className={styles.welcome}>欢迎登录 {userInfo&&userInfo.username}</span>
</Header>
)
}
}
export default connect(
state => state.login
)(AdminHeader);
src\components\AdminHeader\index.less
.logo{
width:120px;
height:32px;
margin:16px;
float:left;
}
.welcome{
float:right;
color:#FFF;
}
src/pages/admin/_layout.js
import React,{Component} from 'react';
import { Layout } from 'antd';
import AdminHeader from '../../components/AdminHeader';
const {Footer,Sider,Content}=Layout;
export default class Admin extends Component{
render() {
return (
<Layout>
<AdminHeader/>
<Layout>
<Sider>Sider</Sider>
<Content>{this.props.children}</Content>
</Layout>
<Footer>
珠峰架构 ©2018
</Footer>
</Layout>
)
}
}
src/components/MenuList.js
import React,{Component,Fragment} from 'react';
import {Menu,Icon} from 'antd';
import Link from 'umi/link';
import {connect} from 'dva';
const SubMenu=Menu.SubMenu;
class MenuList extends Component{
renderMenus=(resources=[]) => {
return resources.map(resource => {
if (resource.children.length>0) {
return (
<SubMenu key={resource.key} title={<span><Icon type={resource.icon} />{resource.name}</span>}>
{this.renderMenus(resource.children)}
</SubMenu>
)
} else {
return <Menu.Item key={resource.key}><Link to={resource.key}><Icon type={resource.icon} />{resource.name}</Link></Menu.Item>;
}
});
}
render() {
let {userInfo}=this.props;
if (!userInfo)
return null;
return (
<Menu
theme="dark"
defaultSelectedKeys={['/admin']}
defaultOpenKeys={['/admin']}
mode="inline"
>
{
this.renderMenus(userInfo.resources)
}
</Menu>
)
}
}
export default connect(
state => state.login
)(MenuList);
app\controller\user.js
let list = await app.mysql.query(`SELECT resource.* FROM role_user,role_resource,resource where role_user.role_id = role_resource.role_id AND role_resource.resource_id = resource.id AND role_user.user_id = ? ORDER BY resource.id ASC`,[user.id]);
let resources = [];
let map = {};
list.forEach(item => {
item.children = [];
map[item.id] = item;
if (item.parent_id == 0) {
resources.push(item);
} else {
map[item.parent_id].children.push(item);
}
});
user.resources=resources;
src/pages/admin/_layout.js
import React,{Component} from 'react';
import { Layout } from 'antd';
import AdminHeader from '../../components/AdminHeader';
import MenuList from '../../components/MenuList';
const {Footer,Sider,Content}=Layout;
export default class Admin extends Component{
render() {
return (
<Layout>
<AdminHeader/>
<Layout>
<Sider>
<MenuList/>
</Sider>
<Content>
{this.props.children}
</Content>
</Layout>
<Footer>
珠峰培训 ©2018
</Footer>
</Layout>
)
}
}
src\pages\admin\user\index.js
import React,{Component,Fragment} from 'react';
import {connect} from 'dva';
import {Card,Table,Button,Modal,Form,Input,message,Popconfirm} from 'antd';
import {PAGE_SIZE} from './constants';
import { routerRedux } from 'dva/router';
const FormItem=Form.Item;
export default
@connect(
state => ({...state.user,loading:state.loading.models.user})
)
class user extends Component{
render(){
const columns=[
{
title: '用户名',
dataIndex: 'username',
key: 'username'
},
{
title: '邮箱',
dataIndex: 'email',
key: 'email'
},
{
title: '性别',
dataIndex: 'gender',
key: 'gender',
render: (val,record)=>(
val===1?'男':'女'
)
}
]
let {list,loading,pageNum,total,dispatch}=this.props;
const pagination={
current: pageNum,
pageSize: PAGE_SIZE,
showQuickJumper: true,
showTotal: (total,range) => {
return `共${total}条`;
},
total,
onChange: (pageNum) => {
dispatch(routerRedux.push(`/admin/user?pageNum=${pageNum}`));
}
}
return (
<Card>
<Table
columns={columns}
dataSource={list}
loading={loading}
rowKey={record => record.id}
pagination={pagination}
/>
</Card>
)
}
}
src\pages\admin\user\constants.js
export const PAGE_SIZE = 3;
src\pages\admin\user\models\user.js
import * as userService from '../services/user';
export default {
namespace: 'user',
state: {
list: [],
pageNum:1,
total:0
},
reducers: {
save(state,{payload}) {
return {...state,...payload};
}
},
effects: {
*fetch({payload: {pageNum=1}},{call,put}) {
const result=yield call(userService.fetch,pageNum);
debugger;
if(result.code === 0){
let { list,total} = result.data;
yield put({type:'save',payload:{list,pageNum:parseInt(pageNum),total}});
}
}
},
subscriptions: {
setup({dispatch,history}) {
return history.listen(({pathname,query}) => {
if (pathname==='/admin/user') {
dispatch({type:'fetch',payload:query});
}
});
}
}
}
src\pages\admin\user\services\user.js
import request from '@/utils/request';
import {PAGE_SIZE} from '../constants';
export function fetch(pageNum) {
return request(`/api/user?pageNum=${pageNum}&pageSize=${PAGE_SIZE}`);
}
src\pages\admin\user\index.js
import React,{Component,Fragment} from 'react';
import {connect} from 'dva';
import {Card,Table,Button,Modal,Form,Input,message,Popconfirm} from 'antd';
import {PAGE_SIZE} from './constants';
import { routerRedux } from 'dva/router';
const FormItem=Form.Item;
export default
@connect(
state => ({...state.user,loading:state.loading.models.user})
)
class user extends Component{
+ save = (payload) => {
+ this.props.dispatch({
+ type: 'user/save',
+ payload
+ });
+ }
+ onAdd=() => {
+ this.save({editVisible: true,isCreate:true,record: {} });
+ }
+ onEditCancel=() => {
+ this.save({ editVisible : false });
+ }
+ onEdit=(record) => {
+ this.save({editVisible: true,isCreate:false,record});
+ }
+ onEditOk=() => {
+ this.editForm.props.form.validateFields((err,values) => {
+ if (err) {
+ return message.error('表单校验失败!');
+ } else {
+ this.props.dispatch({
+ type: this.props.isCreate?'user/create':'user/update',
+ payload:values
+ });
+ }
+ });
+ }
render(){
const columns=[
{
title: '用户名',
dataIndex: 'username',
key: 'username'
},
{
title: '邮箱',
dataIndex: 'email',
key: 'email'
},
{
title: '性别',
dataIndex: 'gender',
key: 'gender',
render: (val,record)=>(
val===1?'男':'女'
)
},
+ {
+ title: '操作',
+ key: 'operation',
+ render: (val,record) => (
+ <Fragment>
+ <Button type="warning" onClick={()=>this.onEdit(record)}>编辑</Button>
+ </Fragment>
+ )
+ }
]
+ let {list,loading,pageNum,total,dispatch,isCreate,editVisible,record}=this.props;
const pagination={
current: pageNum,
pageSize: PAGE_SIZE,
showQuickJumper: true,
showTotal: (total,range) => {
return `共${total}条`;
},
total,
onChange: (pageNum) => {
dispatch(routerRedux.push(`/admin/user?pageNum=${pageNum}`));
}
}
return (
<Card>
<Button type="warning" onClick={this.onAdd}>添加</Button>
<Table
columns={columns}
dataSource={list}
loading={loading}
rowKey={record => record.id}
pagination={pagination}
/>
+ <EditModal
+ wrappedComponentRef={instance =>this.editForm=instance}
+ isCreate={isCreate}
+ visible={editVisible}
+ onOk={this.onEditOk}
+ onCancel={this.onEditCancel}
+ record={record}
+ />
</Card>
)
}
}
+@Form.create()
+class EditModal extends Component{
+ render() {
+ let {visible,onOk,isCreate,onCancel,record,form: {getFieldDecorator}=this.props;
+ let {username,email,id}=record;
+ return (
+ <Modal
+ title={isCreate?'创建用户':'修改用户'}
+ visible={visible}
+ onOk={onOk}
+ onCancel={onCancel}
+ destroyOnClose
+ >
+ <Form>
+ <FormItem>
+ {
+ getFieldDecorator('id',{
+ initialValue: id
+ })(<Input type="hidden" />)
+ }
+ </FormItem>
+ <FormItem
+ label="用户名"
+ >
+ {
+ getFieldDecorator('username',{
+ rules: [{
+ required: true,
+ message:'用户名必须输入'
+ }],
+ initialValue: username
+ })(<Input />)
+ }
+ </FormItem>
+ <FormItem label="邮箱" >
+ {
+ getFieldDecorator('email',{
+ initialValue: email,
+ rules: [{required: true,message:'用户名必须输入'
+ }]})(<Input/>)
+ }
+ </FormItem>
+ </Form>
+ </Modal>
+ )
+ }
+}
src/pages/admin/user/models/user.js
import * as userService from '../services/user';
export default {
namespace: 'user',
state: {
list: [],
pageNum:1,
total:0,
+ editVisible: false,
+ isCreate:true,
+ record: {}
},
reducers: {
save(state,{payload}) {
return {...state,...payload};
}
},
effects: {
*fetch({payload: {pageNum=1}},{call,put}) {
const result=yield call(userService.fetch,pageNum);
debugger;
if(result.code === 0){
let { list,total} = result.data;
yield put({type:'save',payload:{list,pageNum:parseInt(pageNum),total}});
}
},
+ *create({payload},{call,put}) {
+ yield call(userService.create,payload);
+ yield put({type: 'fetch',payload: {pageNum:1}});
+ yield put({type:'save',payload:{editVisible:false}});
+ },
+ *update({payload},{call,put,select}) {
+ yield call(userService.update,payload);
+ let pageNum=yield select(state=>state.user.pageNum);
+ yield put({type: 'fetch',payload: {pageNum}});
+ yield put({type:'save',payload:{editVisible:false}});
+ },
},
subscriptions: {
setup({dispatch,history}) {
return history.listen(({pathname,query}) => {
if (pathname==='/admin/user') {
dispatch({type:'fetch',payload:query});
}
});
}
}
}
src/pages/admin/user/services/user.js
import request from '@/utils/request';
import {PAGE_SIZE} from '../constants';
export function fetch(pageNum) {
return request(`/api/user?pageNum=${pageNum}&pageSize=${PAGE_SIZE}`);
}
+export function create(values) {
+ return request(`/api/user`,{
+ method: 'POST',
+ headers:{"Content-Type":"application/json"},
+ body:JSON.stringify(values)
+ });
+}
+export function update(values) {
+ return request(`/api/user/${values.id}`,{
+ method: 'PUT',
+ headers:{"Content-Type":"application/json"},
+ body:JSON.stringify(values)
+ });
+}
src\pages\admin\user\index.js
+ onDel=(id) => {
+ this.props.dispatch({
+ type: 'user/del',
+ payload:id
+ });
+ }
render: (val,record) => (
<Fragment>
<Button type="warning" onClick={()=>this.onEdit(record) </Button>
+ <Popconfirm
+ okText="确认"
+ cancelText="取消"
+ title="确认删除此用户吗?"
+ onConfirm={() => this.onDel(record.id)}>
+ <Button type="danger">删除</Button>
+ </Popconfirm>
</Fragment>
src\pages\admin\user\models\user.js
+ *del({payload},{call,put}) {
+ yield call(userService.del,payload);
+ yield put({type: 'fetch',payload: {pageNum:1}});
+ }
src\pages\admin\user\services\user.js
+ export function del(id) {
+ return request(`/api/user/${id}`,{
+ method: 'DELETE'
+ });
src\pages\admin\user\index.js
type: 'user/del',
payload:id
});
}
+ onAllDel=() => {
+ this.props.dispatch({
+ type: 'user/delAll',
+ payload:this.props.selectedRowKeys
+ });
+ }
render(){
const columns=[
dispatch(routerRedux.push(`/admin/user?pageNum=${pageNum}`));
}
}
+ const rowSelection={
+ type: 'checkbox',
+ selectedRowKeys: this.props.selectedRowKeys,
+ onChange: (selectedRowKeys) => {
+ this.save({selectedRowKeys});
+ }
}
return (
<Card>
<Button type="warning" onClick={this.onAdd}>添加</Button>
+ <Button type="danger" onClick={this.onAllDel}>全部删除</Button>
<Table
columns={columns}
dataSource={list}
loading={loading}
rowKey={record => record.id}
pagination={pagination}
+ rowSelection={rowSelection}
/>
<EditModal
wrappedComponentRef={instance =>this.editForm=instance}
pages\admin\user\models\user.js
record: {},
+ selectedRowKeys:[]
+ *delAll({payload},{call,put}) {
+ yield call(userService.delAll,payload);
+ yield put({type: 'fetch',payload: {pageNum:1}});
+ }
src\pages\admin\user\services\user.js
+ export function delAll(ids) {
+ return request(`/api/user/${ids[0]}`,{
+ method: 'DELETE',
+ headers: {"Content-Type": "application/json"},
+ body: JSON.stringify(ids)
+ });
+}
src\pages\admin\user\index.js
type: 'user/delAll',
payload:this.props.selectedRowKeys
});
}
+ onSearch=() => {
+ let values=this.searchForm.props.form.getFieldsValue();
+ let where=Object.keys(values).reduce((memo,key) => {
+ if (values[key]){
+ memo[key]=values[key];
+ }
+ return memo;
+ },{});
+ this.props.dispatch({
+ type: 'user/search',
+ payload:where
+ });
+ }
+ let {list,loading,pageNum,total,dispatch,isCreate,editVisible,record,where}=this.props;
+ <>
+ <Card>
+ <SearchForm
+ where={where}
+ onSearch={this.onSearch}
+ wrappedComponentRef={inst=>this.searchForm=inst}/>
</Card>
<Card>
+ </>
+@Form.create()
+class SearchForm extends Component{
+ render() {
+ let {form: {getFieldDecorator},onSearch,where={}}=this.props;
+ return (
+ <Form layout="inline">
+ <FormItem
+ label="用户名"
+ >
+ {
+ getFieldDecorator('username',{initialValue:where.username})(<Input />)
+ }
+ </FormItem>
+ <FormItem
+ label="邮箱"
+ >
+ {
+ getFieldDecorator('email',{initialValue:where.email})(<Input/>)
+ }
+ </FormItem>
+ <FormItem>
+ <Button onClick={onSearch} shape="circle" icon="search"></Button>
+ </FormItem>
+ </Form>
+ )
+ }
+}
pages\admin\user\models\user.js
selectedRowKeys:[],
+ where:{}
+ *fetch({payload: {pageNum=1,where}},{call,put,select}) {
+ if (!where) {
+ where =yield select(state => state.user.where);
+ }
+ if (!pageNum) {
+ pageNum =yield select(state => state.user.pageNum);
+ }
+ const result=yield call(userService.fetch,pageNum,where);
+ if(result.code === 0){
+ let { list,total} = result.data;
+ yield put({type:'save',payload:{list,pageNum:parseInt(pageNum),total}});
+ }
+ },
+ *search({payload:where},{call,put}) {
+ yield put({type: 'fetch',payload: {pageNum:1,where}});
+ },
src\pages\admin\user\services\user.js
+import querystring from 'querystring';
+export function fetch(pageNum,where) {
+ let whereString=querystring.stringify(where);
+ return request(`/api/user?pageNum=${pageNum}&pageSize=${PAGE_SIZE}&${whereString}`);
+}
src\pages\admin\user\index.js
<Table
columns={columns}
dataSource={list}
loading={loading}
rowKey={record => record.id}
pagination={pagination}
rowSelection={rowSelection}
+ onRow = {
+ (record) => {
+ return {
+ onClick: () => {
+ let selectedRowKeys = this.props.selectedRowKeys;
+ let index = selectedRowKeys.indexOf(record.id);
+ if (index == -1) {
+ selectedRowKeys = [...selectedRowKeys, record.id];
+ } else {
+ selectedRowKeys = selectedRowKeys.filter(key => key !=record.id);
+ }
+ this.save({
+ selectedRowKeys
+ });
+ }
+ }
+ }
+ }
/>
src\pages\admin\role\index.js
import React,{Component,Fragment} from 'react';
import {connect} from 'dva';
import {Card,Table,Button,Modal,Form,Input,message,Popconfirm} from 'antd';
import {PAGE_SIZE} from './constants';
import { routerRedux } from 'dva/router';
const FormItem=Form.Item;
const ENTITY='role';
export default
@connect(
state => ({...state[ENTITY],loading:state.loading.models[ENTITY]})
)
class Role extends Component{
save = (payload) => {
this.props.dispatch({
type: `${ENTITY}/save`,
payload
});
}
onAdd=() => {
this.save({editVisible: true,isCreate:true,record: {} });
}
onEditCancel=() => {
this.save({ editVisible : false });
}
onEditOk=() => {
this.editForm.props.form.validateFields((err,values) => {
if (err) {
return message.error('表单校验失败!');
} else {
this.props.dispatch({
type: this.props.isCreate?`${ENTITY}/create`:`${ENTITY}/update`,
payload:values
});
}
});
}
onEdit=(record) => {
this.save({editVisible: true,isCreate:false,record});
}
onDel=(id) => {
this.props.dispatch({
type: `${ENTITY}/del`,
payload:id
});
}
onAllDel=() => {
this.props.dispatch({
type: `${ENTITY}/delAll`,
payload:this.props.selectedRowKeys
});
}
onSearch=() => {
let values=this.searchForm.props.form.getFieldsValue();
let where=Object.keys(values).reduce((memo,key) => {
if (values[key]){
memo[key]=values[key];
}
return memo;
},{});
this.props.dispatch({
type: `${ENTITY}/search`,
payload:where
});
}
render(){
const columns=[
{
title: '名称',
dataIndex: 'name',
key: 'name'
},
{
title: '操作',
key: 'operation',
render: (val,record) => (
<Fragment>
<Button type="warning" onClick={()=>this.onEdit(record)}>编辑</Button>
<Popconfirm
okText="确认"
cancelText="取消"
title="确认删除此用户吗?"
onConfirm={() => this.onDel(record.id)}>
<Button type="danger">删除</Button>
</Popconfirm>
</Fragment>
)
}
]
let {list,loading,pageNum,total,dispatch,isCreate,editVisible,record,where}=this.props;
const pagination={
current: pageNum,
pageSize: PAGE_SIZE,
showQuickJumper: true,
showTotal: (total,range) => {
return `共${total}条`;
},
total,
onChange: (pageNum) => {
dispatch(routerRedux.push(`/admin/${ENTITY}?pageNum=${pageNum}`));
}
}
const rowSelection={
type: 'checkbox',
selectedRowKeys: this.props.selectedRowKeys,
onChange: (selectedRowKeys) => {
this.save({selectedRowKeys});
}
}
return (
<>
<Card>
<SearchForm
where={where}
onSearch={this.onSearch}
wrappedComponentRef={inst=>this.searchForm=inst}/>
</Card>
<Card>
<Button type="warning" onClick={this.onAdd}>添加</Button>
<Button type="danger" onClick={this.onAllDel}>全部删除</Button>
<Table
columns={columns}
dataSource={list}
loading={loading}
rowKey={record => record.id}
pagination={pagination}
rowSelection={rowSelection}
onRow = {
(record) => {
return {
onClick: () => {
let selectedRowKeys = this.props.selectedRowKeys;
let index = selectedRowKeys.indexOf(record.id);
if (index === -1) {
selectedRowKeys = [...selectedRowKeys, record.id];
} else {
selectedRowKeys = selectedRowKeys.filter(key => key !=record.id);
}
this.save({
selectedRowKeys
});
}
}
}
}
/>
<EditModal
wrappedComponentRef={instance =>this.editForm=instance}
isCreate={isCreate}
visible={editVisible}
onOk={this.onEditOk}
onCancel={this.onEditCancel}
record={record}
/>
</Card>
</>
)
}
}
@Form.create()
class SearchForm extends Component{
render() {
let {form: {getFieldDecorator},onSearch,where={}}=this.props;
return (
<Form layout="inline">
<FormItem
label="角色名称"
>
{
getFieldDecorator('name',{initialValue:where.name})(<Input />)
}
</FormItem>
<FormItem>
<Button onClick={onSearch} shape="circle" icon="search"></Button>
</FormItem>
</Form>
)
}
}
@Form.create()
class EditModal extends Component{
render() {
let {visible,onOk,isCreate,onCancel,record,form: {getFieldDecorator}}=this.props;
let {id,name}=record;
return (
<Modal
title={isCreate?'创建角色':'修改角色'}
visible={visible}
onOk={onOk}
onCancel={onCancel}
destroyOnClose
>
<Form>
<FormItem>
{
getFieldDecorator('id',{
initialValue: id
})(<Input type="hidden" />)
}
</FormItem>
<FormItem
label="用户名"
>
{
getFieldDecorator('name',{
rules: [{
required: true,
message:'名称必须输入'
}],
initialValue: name
})(<Input />)
}
</FormItem>
</Form>
</Modal>
)
}
}
src\pages\admin\role\constants.js
export const PAGE_SIZE = 3;
src\pages\admin\role\models\role.js
import * as service from '../services/role';
const ENTITY='role';
export default {
namespace: ENTITY,
state: {
list: [],
pageNum:1,
total:0,
editVisible: false,
isCreate:true,
record: {},
selectedRowKeys:[],
where:{}
},
reducers: {
save(state,{payload}) {
return {...state,...payload};
}
},
effects: {
*fetch({payload: {pageNum=1,where}},{call,put,select}) {
if (!where) {
where =yield select(state => state[ENTITY].where);
}
if (!pageNum) {
pageNum =yield select(state => state[ENTITY].pageNum);
}
const result=yield call(service.fetch,pageNum,where);
if(result.code === 0){
let { list,total} = result.data;
yield put({type:'save',payload:{list,pageNum:parseInt(pageNum),total}});
}
},
*search({payload:where},{call,put}) {
yield put({type: 'fetch',payload: {pageNum:1,where}});
},
*create({payload},{call,put}) {
yield call(service.create,payload);
yield put({type: 'fetch',payload: {pageNum:1}});
yield put({type:'save',payload:{editVisible:false}});
},
*update({payload},{call,put,select}) {
yield call(service.update,payload);
let pageNum=yield select(state=>state[ENTITY].pageNum);
yield put({type: 'fetch',payload: {pageNum}});
yield put({type:'save',payload:{editVisible:false}});
},
*del({payload},{call,put}) {
yield call(service.del,payload);
yield put({type: 'fetch',payload: {pageNum:1}});
},
*delAll({payload},{call,put}) {
yield call(service.delAll,payload);
yield put({type: 'fetch',payload: {pageNum:1}});
}
},
subscriptions: {
setup({dispatch,history}) {
return history.listen(({pathname,query}) => {
if (pathname===`/admin/${ENTITY}`) {
dispatch({type:'fetch',payload:query});
}
});
}
}
}
src\pages\admin\role\services\role.js
import request from '@/utils/request';
import {PAGE_SIZE} from '../constants';
import querystring from 'querystring';
const ENTITY='role';
export function fetch(pageNum,where) {
let whereString=querystring.stringify(where);
return request(`/api/${ENTITY}?pageNum=${pageNum}&pageSize=${PAGE_SIZE}&${whereString}`);
}
export function create(values) {
return request(`/api/${ENTITY}`,{
method: 'POST',
headers:{"Content-Type":"application/json"},
body:JSON.stringify(values)
});
}
export function update(values) {
return request(`/api/${ENTITY}/${values.id}`,{
method: 'PUT',
headers:{"Content-Type":"application/json"},
body:JSON.stringify(values)
});
}
export function del(id) {
return request(`/api/${ENTITY}/${id}`,{
method: 'DELETE'
});
}
export function delAll(ids) {
return request(`/api/${ENTITY}/${ids[0]}`,{
method: 'DELETE',
headers: {"Content-Type": "application/json"},
body: JSON.stringify(ids)
});
}
//list resourceIds=代表这个角色所拥有的资源ID数组
for (let i = 0; i < list.length; i++) {
let rows = await app.mysql.select('role_resource', {
where: { role_id: list[i].id }
});//[{role_id:1,resource_id:2},{role_id:1,resource_id:3}]
list[i].resourceIds = rows.map(item => item.resource_id);//[2,3]
rows = await app.mysql.select('role_user', {
where: { role_id: list[i].id }
});//[{role_id:1,resource_id:2},{role_id:1,resource_id:3}]
list[i].userIds = rows.map(item => item.user_id);//[2,3]
}
+ import {Tree,Card,Table,Button,Modal,Form,Input,message,Popconfirm} from 'antd';
+const TreeNode=Tree.TreeNode;
+ setRolePermission=() => {
+ if (this.props.selectedRows.length===1) {
+ let record=this.props.selectedRows[0];
+ this.save({setPermissionVisible:true,record,checkedKeys:record.resourceIds});
+ } else {
+ message.error('为角色设置权限时要选择并且只能选择一个角色!');
+ }
+ }
+ onCheckPermission=(checkedKeys) => {
+ this.save({checkedKeys});
+ }
+ setRolePermissionOk=() => {
+ this.props.dispatch({
+ type: `${ENTITY}/setRolePermission`
+ });
+ }
+ let {list,loading,pageNum,total,dispatch,isCreate,editVisible,record,where,setPermissionVisible,resources,checkedKeys}=this.props;
+const rowSelection={
+ type: 'checkbox',
+ selectedRowKeys: this.props.selectedRowKeys,
+ selectedRows: this.props.selectedRows,
+ onChange: (selectedRowKeys,selectedRows) => {
+ this.save({selectedRowKeys,selectedRows});
+ }
+}
+ <Button icon="solution" onClick={this.setRolePermission}>设置权限</Button>
<Table
+ rowSelection={rowSelection}
+ <PermissonModal
+ visible={setPermissionVisible}
+ record={record}
+ resources={resources}
+ checkedKeys={checkedKeys}
+ onCheck={this.onCheckPermission}
+ onOk={this.setRolePermissionOk}
+ onCancel={()=>this.save({setPermissionVisible:false})}
+ />
+class PermissonModal extends Component{
+ renderResources=(children) => {
+ return children.map(child => {
+ if (child.children.length > 0) {
+ return (
+ <TreeNode title={child.name} key={child.id}>
+ {this.renderResources(child.children)}
+ </TreeNode>
+ )
+ } else {
+ return <TreeNode title={child.name} key={child.id}/>
+ }
+ });
+ }
+ render() {
+ let {onCheck,visible,onOk,onCancel,checkedKeys,resources=[]}=this.props;
+ return (
+ <Modal
+ visible={visible}
+ title="为角色设置权限"
+ onOk={onOk}
+ onCancel={onCancel}
+ destroyOnClose
+ >
+ <Tree
+ checkable
+ defaultExpandAll
+ onCheck={onCheck}
+ checkedKeys={checkedKeys}
+ >
+ <TreeNode title="所有权限" key={0} disabled>
+ {this.renderResources(resources)}
+ </TreeNode>
+ </Tree>
+ </Modal>
+ )
+ }
+}
src\pages\admin\role\models\role.js
+ selectedRows: [],
+ checkedKeys: [],
+ resources:[],
+ setPermissionVisible:false
+ *getResource({payload},{call,put}) {
+ const resources=yield call(service.getResources);
+ yield put({type:'save',payload:{resources}});
+ },
+ *setRolePermission({payload},{call,put,select}) {
+ let {record,checkedKeys}=yield select(state => state[ENTITY]);
+ yield call(service.setRolePermission,{roleId:record.id,resourceIds:checkedKeys});
+ yield put({type: 'fetch',payload: {}});
+ yield put({type:'save',payload:{setPermissionVisible:false,selectedRowKeys:[],selectedRows:[]}});
+ }
if (pathname===`/admin/${ENTITY}`) {
dispatch({type:'fetch',payload:query});
+ dispatch({type:'getResource'});
}
src\pages\admin\role\services\role.js
+ export function getResources() {
+ return request(`/api/role/getResource`);
+}
+export function setRolePermission(values) {
+ return request(`/api/role/setResource`,{
+ method: 'POST',
+ headers:{"Content-Type":"application/json"},
+ body:JSON.stringify(values)
+ });
+}
+ import { Transfer} from 'antd';
+ setUser = () => {
+ let selectedRows = this.props.selectedRows;
+ if (selectedRows.length == 1) {
+ let record = selectedRows[0];
+ this.save({ setUserVisible: true, record, targetKeys: record.userIds });
+ } else {
+ message.warn('请选中并且只能选中一个角色!');
+ }
+ }
+ setUserOk = () => {
+ this.props.dispatch({
+ type: `${ENTITY}/setUser`
+ });
+ }
+ onSetUserChange = (targetKeys) => {
+ this.save({ targetKeys });
+ }
+ let {list,loading,pageNum,total,dispatch,isCreate,editVisible,record,
where,setPermissionVisible,resources,checkedKeys, setUserVisible, targetKeys, users}=this.props;
+ <Button style={{ marginLeft: 8 }} type="primary" icon="plus-circle" onClick={this.setUser}>分配用户</Button>
+ let selectedRowKeys=this.props.selectedRowKeys;
+ let selectedRows = this.props.selectedRows;
+ let index = selectedRowKeys.indexOf(record.id);
+ if (index===-1) {
+ selectedRowKeys=[...selectedRowKeys,record.id];
+ selectedRows=[...selectedRows,record];
+ } else {
+ selectedRowKeys=selectedRowKeys.filter(key => key!=record.id);
+ selectedRows=selectedRows.filter(row => row.id!=record.id);
+ }
+ this.save({selectedRowKeys,selectedRows});
+ <SetUserModal
+ visible={setUserVisible}
+ record={record}
+ onOk={this.setUserOk}
+ targetKeys={targetKeys}
+ onChange={this.onSetUserChange}
+ users={users}
+ onCancel={() => this.save({ setUserVisible: false })}
+/>
+ class SetUserModal extends Component {
+ render() {
+ let { visible, onOk, onCancel, record, targetKeys, onChange, users } = this.props;
+ return (
+ <Modal
+ title={`为 ${record.name} 分配用户`}
+ visible={visible}
+ onOk={onOk}
+ okText={"确定"}
+ cancelText={"取消"}
+ onCancel={onCancel}
+ destroyOnClose
+ >
+ <Transfer
+ dataSource={users}
+ targetKeys={targetKeys}
+ titles={["待选用户", "已选用户"]}
+ onChange={onChange}
+ render={row => row.username}
+ rowKey={row => row.id}
+ />
+ </Modal>
+ )
+ }
+}
src/pages/admin/roles/models/roles.js
+ setPermissionVisible:false,
+ setUserVisible: false,//窗口是否显示
+ targetKeys: [],//选中的用户
+ users: [] //所有的用户
+ *getUser({ }, { call, put }) {
+ let users = yield call(service.getUser);
+ yield put({ type: 'save', payload: { users } });
+ },
+ *setUser({ }, { call, put, select }) {
+ let record = yield select(state => state.role.record);
+ let targetKeys = yield select(state => state.role.targetKeys);
+ yield call(service.setUser, {
+ roleId: record.id,
+ userIds: targetKeys
+ });
+ yield put({ type: 'save', payload: { setUserVisible: false, selectedRowKeys: [], selectedRows: [] } });
+ yield put({ type: 'fetch', payload: {} });
+ }
dispatch({type:'getResource'});
+ dispatch({ type: 'getUser' });
src/pages/admin/roles/services/roles.js
+export function getUser() {
+ return request(`/api/role/getUser`);
+}
+export function setUser(values) {
+ return request(`/api/role/setUser`, {
+ method: 'POST',
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify(values)
+ });