0%

《React后台管理系统实战:六》角色管理:

详情请点阅读全文

@TOC

1.角色管理静态页面role/index.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import React,{Component} from 'react'
import {
Card,
Button,
Table,
message
} from 'antd'
import {PAGE_SIZE} from '../../../utils/constans' //常量:分页每页显示多少条数据

export default class Role extends Component{
state={
roles:[
{"_id": "5ca9eaa1b49ef916541160d3",
"name": "测试",
"create_time": 1554639521749,
"__v": 0,
"auth_time": 1558679920395,
"auth_name": "test007"},

{"_id": "5ca9eaa1b49ef916541160d4",
"name": "产品",
"create_time": 1554639521749,
"__v": 0,
"auth_time": 1558679920395,
"auth_name": "test008"},
], //Table datasource
}

//初始化表格列标题,及对应的数据源,dataIndex:对应api返回的数据名
initColumns=()=>{
this.columns=[
{title:'角色名称',dataIndex:'name'},
{title:'创建时间',dataIndex:'create_time'},
{title:'授权时间',dataIndex:'auth_time'},
{title:'授权人',dataIndex:'auth_name'},
]
}

componentWillMount(){
this.initColumns()//运行初始表格列标题,及对应的数据源函数,把表格列数据赋值到this.columus上
}


render(){
const {roles}=this.state

//card的左侧 (Button的disabled:按钮不可用)
const title=(
<span>
<Button type='primary' style={{marginRight:8}}>创建角色</Button>
<Button type='primary' disabled>设置角色权限</Button>
</span>
)

return(
<Card title={title}>
<Table
bordered /**边框 */
rowKey='_id' /**表格行 key 的取值,可以是字符串或一个函数 */
dataSource={roles} /**数据源 */
columns={this.columns} /**列标题,及对应的数据源 */
pagination={{defaultPageSize:PAGE_SIZE}} /**分页设置默认分页数量 */
rowSelection={{type:'radio'}} /**第行前面加一个单选框antd文档找使用方法 */
/>
</Card>
)
}
}

效果 http://localhost:3000/role

在这里插入图片描述

2. api请求:获取所有角色列表

注:在请求前要先向数据库添加一些角色数据,可用getman,或直接数据库添加

1. api/index.js

1
2
//请求所有角色列表
export const reqRoles=()=>ajax(BASE+'/manage/role/list')

2. role/index.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import {reqRoles} from '../../../api' //【1】

state={//【2】清空模拟数据
roles:[], //所有角色列表:连接Table datasource
}

//【3】获取角色列表数据,设置到state中
getRoles=async()=>{
const result=await reqRoles()
if(result.status===0){
const roles=result.data //把返回值赋值给roles
this.setState({
roles //定义的变量和state的roles同名,所以简写
})
}
}

componentDidMount(){
this.getRoles() //【4】函数:获取角色列表设置到state中
}

效果:同1步静态,但授权两项为空

http://localhost:3000/role
在这里插入图片描述

3.点击一行的任意位置选中单选框

onRow用法:https://3x.ant.design/components/table-cn/#onRow-用法
rowSelection用法:https://3x.ant.design/components/table-cn/#rowSelection

1.实现

【0】Table的onRow控制点击当前行的行为
【1】点击角色列表对应行的行为
【2】设置state:选中的role
【3】把当前点击的行赋值到state里的role
【4】取出state的role
【6】Table的selectedRowKeys根据4确定哪个是被选中状态;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
state={
roles:[], //所有角色列表:连接Table datasource
role:{},//【2】选中的role
}

//【1】点击角色列表对应行的行为
onRow=(role)=>{
return{
onClick: event => { //点击行时执行以下
console.log('row onClick()', role)
this.setState({ //【3】把当前点击的行赋值到state里的role
role
})
}
}
}


//render和return间
const {roles,role}=this.state //【4】取出role
//return后:
<Card title={title}>
<Table
bordered /**边框 */
rowKey='_id' /**指定数据主键 */
dataSource={roles} /**数据源 */
columns={this.columns} /**列标题,及对应的数据源 */
pagination={{defaultPageSize:PAGE_SIZE}} /**分页设置默认分页数量 */
rowSelection={{type:'radio',selectedRowKeys:[role._id]} } /**【6】selectedRowKeys根据4确定哪个是被选中状态(请求的数据里有一个:"_id": "5e6b147106c64115446c4fb2",); 第行前面加一个单选框antd文档找使用方法 */
onRow={this.onRow} /**【0】控制点击当前行的行为 */
/>
</Card>

效果:点一行的任意地方都会选中单选框

在这里插入图片描述

2. 选中一行后,设置权限按钮变可点状态

1
2
3
4
5
6
7
8
9
const {roles,role}=this.state //【1】取出role

//card的左侧 (Button的disabled:按钮不可用【2】如果_id不存在则按钮不可用)
const title=(
<span>
<Button type='primary' style={{marginRight:8}}>创建角色</Button>
<Button type='primary' disabled={!role._id}>设置角色权限</Button>
</span>
)

效果 点任意一行 设置角色权限按钮变如下

在这里插入图片描述

二、添加角色弹窗

1.引入modal弹窗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import {
Card,
Button,
Table,
Modal, //【1】弹窗
message
} from 'antd'

state={
roles:[], //所有角色列表:连接Table datasource
role:{},//选中的role
isShowAdd: false, //【2】是否显示添加界面
}

//【render】

//card的左侧 (Button的disabled:按钮不可用)
const title=(
<span>
{/* 【3】点创建角色:显示创建角色的弹窗 */}
<Button type='primary' style={{marginRight:8}} onClick={()=>{this.setState({isShowAdd:true})}}>创建角色</Button>
<Button type='primary' disabled={!role._id}>设置角色权限</Button>
</span>
)
//【return】

{/* 【4】弹窗 */}
<Modal
title='添加角色'
visible={isShowAdd} /*弹窗可见状态*/
onOk={this.addRole} /*点ok提交信息*/
onCancel={()=>{this.setState({isShowAdd:false})}} /*点取消*/
>
</Modal>

2.创建角色页面把子组件form传给父组件index.jsx—当前role/addForm.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import React,{Component} from 'react'
import{Form,Input} from 'antd'
import PropTypes from 'prop-types' //【1】传值模块

const Item =Form.Item

class AddForm extends Component{
static propTypes={
setForm:PropTypes.func.isRequired //【2】父组件传过来的接收子组件form对象的函数
}

componentWillMount () {
//【3】运行接收到的父组件传过来函数,把form传给父组件
this.props.setForm(this.props.form)
}

render(){
// 取出form的表单验证方法
const { getFieldDecorator } = this.props.form
// 【2.1】指定Item布局的配置对象
const formItemLayout = {
labelCol: { span: 4 }, // 左侧label的宽度
wrapperCol: { span: 15 }, // 右侧包裹的宽度
}

return(
<Form>
{/* 【2.2】把formItemLayout放入Item */}
<Item label='角色名称' {...formItemLayout}>
{
getFieldDecorator('roleName', {
initialValue: '',
rules: [
{required: true, message: '角色名称必须输入'}
]
})(
<Input placeholder='请输入角色名称'/>
)
}
</Item>
</Form>
)
}
}
export default Form.create()(AddForm) //包装AddForm组件使具体form相关方法

3.role/index.jsx向addForm.jsx发送setForm函数接收其传过来的from表单

1
2
3
4
5
6
7
8
9
10
{/* 弹窗 */}
<Modal
title='添加角色'
visible={isShowAdd} /*弹窗可见状态*/
onOk={this.addRole} /*点ok提交信息*/
onCancel={()=>{this.setState({isShowAdd:false})}} /*点取消*/
>
{/* 【1】传递子组件form的函数setForm:(接收一个参数form,令当前组件的form=传过来的form*/}
<AddForm setForm={(form) => this.form = form} />
</Modal>

4.添加角色api接口api/index.jsx

1
2
// 添加角色
export const reqAddRole=(roleName)=>ajax(BASE+'/manage/role/add',{roleName},'POST')

5.引入接口,并执行添加角色role/index.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import {reqRoles,reqAddRole} from '../../../api' //【1】添加角色api

//state下
//【2】点添加角色弹窗的ok按钮:添加角色
addRole=()=>{
this.form.validateFields(async(err,value)=>{
if(!err){
console.log(value)
//隐藏确认框
this.setState({isShowAdd:false})
//收集数据
const {roleName}=value
this.form.resetFields()//清空表单内数据,方便下次使用

//添加角色请求
const result=await reqAddRole(roleName)
if(result.status===0){
message.success('角色添加成功')
//取出返回的新增role值
const role=result.data
//更新roles状态,使新增的角色显示出来(基于原本状态数据更新)
/*不建议的写法(虽然也能实现)
const roles = this.state.roles
roles.push(role)
this.setState({
roles
})*/
this.setState(state=>({
roles:[...state.roles,role]
}))

}else{
message.error('角色添加失败')
}
}
})
}

★知识点:setState的真正用法

1
2
3
this.setState((state,props)=>({
roles:[...state.roles,role]
}))

效果:http://localhost:3000/role

输入角色,点ok后提交角色,显示添加角色成功;点cacel关闭弹窗
在这里插入图片描述

优化点cancel后,再点创建角色上次的东西还在里面

this.form.resetFields()//【优化】取消时顺便清空表单方便下次使用

1
2
3
4
5
6
7
8
9
10
11
12
13
{/* 添加角色弹窗 */}
<Modal
title='添加角色'
visible={isShowAdd} /*弹窗可见状态*/
onOk={this.addRole} /*点ok提交信息*/
onCancel={()=>{
this.setState({isShowAdd:false})
this.form.resetFields()//【优化】取消时顺便清空表单方便下次使用
}} /*点取消*/
>
{/* 传递子组件form的函数setForm:(接收一个参数form,令当前组件的form=传过来的form) */}
<AddForm setForm={(form) => this.form = form} />
</Modal>

三、设置角色权限

1.设置权限弹窗role/index.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import AuthForm from './authForm' //【4】设置权限弹窗的表单

//class ...
state={
roles:[], //所有角色列表:连接Table datasource
role:{},//选中的role
isShowAdd: false, //是否显示添加角色弹窗
isShowAuth:false, //【0】是否显示设置权限弹窗
}


//render
const {roles,role,isShowAdd,isShowAuth}=this.state //【1】取出isShowAuth
//card的左侧 (Button的disabled:按钮不可用)
const title=(
<span>
{/* 【3】点设置权限:显示对应弹窗onClick={()=>{this.setState({isShowAuth:true})}}*/}
<Button type='primary' style={{marginRight:8}} onClick={()=>{this.setState({isShowAdd:true})}}>创建角色</Button>
<Button type='primary' disabled={!role._id} onClick={()=>{this.setState({isShowAuth:true})}}>设置角色权限</Button>
</span>
)


//return内
{/* 【2】设置权限弹窗 */}
<Modal
title='设置权限'
visible={isShowAuth} /*弹窗可见状态*/
onOk={this.addAuth} /*点ok提交信息*/
onCancel={()=>{
this.setState({isShowAuth:false})

}} /*点取消*/
>
{/* */}
<AuthForm />
</Modal>

2.创建设置权限弹窗内容authForm.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React,{Component} from 'react'
import {Form,Input,Tree} from 'antd'

const Item=Form.Item

export default class AuthForm extends Component{


render(){
// 指定Item布局的配置对象
const formItemLayout = {
labelCol: { span: 4 }, // 左侧label的宽度
wrapperCol: { span: 15 }, // 右侧包裹的宽度
}
return(
<div>
<Item label='角色名称' {...formItemLayout}>
<Input />
</Item>
</div>
)
}
}

效果:http://localhost:3000/role

在这里插入图片描述

3.显示选中的角色名称

1.role/index.jsx把role传递给authForm.jsx

1
2
3
4
5
6
//render
const {roles,role,isShowAdd,isShowAuth}=this.state //【1】娶出role

//return...
{/*【2】把role传递给子组件 */}
<AuthForm role={role} />

2.authForm.jsx接收index传过来的role数据并显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import PropTypes from 'prop-types' //【1】

//class ..
static propTypes={//【2】
role:PropTypes.object
}

//render
//【3】
const {role}=this.props

//return
<Item label='角色名称' {...formItemLayout}>
{/* 【4】显示选中的角色名,并让它呈不可编辑的状态 */}
<Input value={role.name} disabled />
</Item>

效果:http://localhost:3000/role

点设置角色权限
在这里插入图片描述

4.显示树形结构

antd Tree用法:https://3x.ant.design/components/tree-cn/

4.1显示静态tree

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import menuList from '../../../config/menuConfig' //【1】导入菜单列表
const { TreeNode } = Tree //【2】拿出TreeNode
//class

//return
{/* 【3】到antd复制一个tree进行修改 */}
<Tree
checkable
defaultExpandAll={true} /*默认展开所有节点*/
>
<TreeNode title="parent 1" key="0-0">
<TreeNode title="parent 1-0" key="0-0-0" >
<TreeNode title="leaf" key="0-0-0-0" />
<TreeNode title="leaf" key="0-0-0-1" />
</TreeNode>
<TreeNode title="parent 1-1" key="0-0-1">
<TreeNode title='sss' key="0-0-1-0" />
</TreeNode>
</TreeNode>
</Tree>

效果:

在这里插入图片描述

4.2 显示菜单列表Tree

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import menuList from '../../../config/menuConfig' //【1】导入菜单列表
//【2】获取菜单列表
getTreeNodes=(menuList)=>{
//代替map函数:reduce((初始值pre,当前正在处理的数组item)={},初始值[])
return menuList.reduce((pre,item)=>{
pre.push(
<TreeNode title={item.title} key={item.key}>
{/* 如果有children则调用本函数,把children再运行一次 */}
{item.children ? this.getTreeNodes(item.children):null}
</TreeNode>
)
return pre
},[])
}
//【3】在页面加载前调用一次菜单
componentWillMount(){
this.treeNodes=this.getTreeNodes(menuList)
}

【return】
{/* 到antd复制一个tree进行修改 */}
<Tree
checkable
defaultExpandAll={true} /*默认展开所有节点*/
>
{/* 【4】外面包个根节点,平台权限,内调用3步的treeNodes */}
<TreeNode title='平台权限' key='all'>
{this.treeNodes}
</TreeNode>

</Tree>

效果:

在这里插入图片描述

5.获取当前Tree的选中状态,及改变选中状态authForm.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#【class】
constructor(props){
super(props)
//【1】根据传入的角色生成初始状态
const {menus} = this.props.role
this.state={
checkedKeys:menus
}
}

static propTypes={// 【0】接收父传值
role:PropTypes.object
}

//【5】更新Tree的选中状态
onCheck=(checkedKeys)=>{
console.log('oncheck:',checkedKeys)
this.setState({checkedKeys})
}

#【render()】
//【2】取出Tree要选中的节点
const {checkedKeys}=this.state

#【return】
{/* 到antd复制一个tree进行修改 */}
<Tree
checkable
defaultExpandAll={true} /*默认展开所有节点*/
checkedKeys={checkedKeys} /*【3】控制哪些节点为选中状态*/
onCheck={this.onCheck} /*【4】点击后更改选中状态*/
>
{/* 外面包个根节点,平台权限,内调用3步的treeNodes */}
<TreeNode title='平台权限' key='all'>
{this.treeNodes}
</TreeNode>

</Tree>

效果:选中节点后状态会对应变化

在这里插入图片描述

6. 更新角色(父子传值等)

1.role/index.jsx更新角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import {reqRoles,reqAddRole,reqUpdateRole} from '../../../api' //【7】引入更新角色函数requpdaterole;   添加角色api
//class
constructor (props) {
super(props)
//【1】创建一个auth的ref用于父子组件传值
this.auth = React.createRef()
}

// 【4】更新角色:点设置权限弹窗里的ok操作
updateRole=async()=>{//【9】加async
// 隐藏确认框
this.setState({isShowAuth: false})

const role=this.state.role
//【5】得到最新的menus => 到authForm.jsx里传值过来(getMenus = () => this.state.checkedKeys)
const menus=this.auth.current.getMenus()
//【6】把接收过来的菜单传给当前role.menus => 到api/index.js里写更新角色接口函数
role.menus=menus
//【8】发送更新请求
console.log(role)
const result = await reqUpdateRole(role)
if (result.status===0){
message.success('设置角色权限成功!')
this.getRoles()
}else{
message.error('更新角色权限失败')
}


}

//render()
//return
{/* 设置权限弹窗 */}
<Modal
title='设置权限'
visible={isShowAuth} /*弹窗可见状态*/
onOk={this.updateRole} /*【3】点ok提交信息*/
onCancel={()=>{this.setState({isShowAuth:false})}} /*点取消*/
>
{/*【2】把this.auth传给子组件 把role传递给子组件 */}
<AuthForm ref={this.auth} role={role} />
</Modal>

2. authForm.jsx传值

1
2
//【1】为父组件提交获取最新menus数据的方法:把state.checkedKeys传给父组件
getMenus = () => this.state.checkedKeys

3.api/index.js接口

1
2
// 更新角色,传过来的参数就是字典格式,所以role参数不用加花括号
export const reqUpdateRole=(role)=>ajax(BASE+'/manage/role/update',role,'POST')

效果:

勾选后会设置权限成功
在这里插入图片描述

7.优化:当更新过一个角色权限后,其它的角色会变的跟这个角色一样,原因是:收到父组件传的props值后,state内数据没更新authForm.jsx

1
2
3
4
5
6
7
8
9
  //【2】根据新传入的role来更新checkedKeys状态当组件接收到新的属性时自动调用
componentWillReceiveProps (nextProps) {
console.log('componentWillReceiveProps()', nextProps)
const menus = nextProps.role.menus
this.setState({
checkedKeys: menus
})
// this.state.checkedKeys = menus //也可以这样写
}

效果:更新一个角色后,其它角色不会变的跟当前一样

8.授权时间,授权人优化role/index.jsx

【2】添加授权时间及授权人

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import memoryUtils from '../../../utils/memoryUtils' //【1】引入记忆模块用于显示用户名

// 更新角色:点设置权限弹窗里的ok操作
updateRole=async()=>{//加async
// 隐藏确认框
this.setState({isShowAuth: false})

const role=this.state.role
//得到最新的menus => 到authForm.jsx里传值过来(getMenus = () => this.state.checkedKeys)
const menus=this.auth.current.getMenus()
//把接收过来的菜单传给当前role.menus => 到api/index.js里写更新角色接口函数
role.menus=menus

//【2】添加授权时间及授权人
role.auth_time=Date.now()
role.auth_name = memoryUtils.user.username

//发送更新请求
console.log(role)
const result = await reqUpdateRole(role)
if (result.status===0){
message.success('设置角色权限成功!')
this.getRoles()
}else{
message.error('更新角色权限失败')
}


}

效果:设置权限后,会显示对应时间及授权人

在这里插入图片描述

9.把时间戳格式化成字符串形式方便人类查看

0.附:时间格式函数 utils/dateutils.js

1
2
3
4
5
6
7
8
9
//包含n个日期时间处理的工具函数模块

//格式化日期
export function formateDate(time) {
if (!time) return ''
let date = new Date(time)
return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate()
+ ' ' + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds()
}

1.格式化时间role/index.jsx

全写:render:(create_time)=>formateDate(create_time)
简写:render:formateDate

1
2
3
4
5
6
7
8
9
10
11
12
13
import {formateDate} from '../../../utils/dateUtils' //【1】时间格式化

//初始化表格列标题,及对应的数据源,dataIndex:对应api返回的数据名
initColumns=()=>{
//【2】调用函数格式化时间戳
this.columns=[
{title:'角色名称',dataIndex:'name'},
{title:'创建时间',dataIndex:'create_time',render:(create_time)=>formateDate(create_time)},

{title:'授权时间',dataIndex:'auth_time',render:formateDate},
{title:'授权人',dataIndex:'auth_name'},
]
}

效果:时间会变成可读

在这里插入图片描述

附件:完整代码/role/

index.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import React,{Component} from 'react'
import {
Card,
Button,
Table,
Modal, //弹窗
message
} from 'antd'
import {PAGE_SIZE} from '../../../utils/constans'
import {reqRoles,reqAddRole,reqUpdateRole} from '../../../api' //引入更新角色函数requpdaterole; 添加角色api
import AddForm from './addForm' //添加角色弹窗的表单
import AuthForm from './authForm' //设置权限弹窗的表单
import memoryUtils from '../../../utils/memoryUtils' //引入记忆模块用于显示用户名
import {formateDate} from '../../../utils/dateUtils' //【1】时间格式化


export default class Role extends Component{

constructor (props) {
super(props)
//创建一个auth的ref用于父子组件传值
this.auth = React.createRef()
}

state={
roles:[], //所有角色列表:连接Table datasource
role:{},//选中的role
isShowAdd: false, //是否显示添加角色弹窗
isShowAuth:false, //是否显示设置权限弹窗
}

//点击角色列表对应行的行为
onRow=(role)=>{
return{
onClick: event => { //点击行时执行以下
console.log('row onClick()', role)
this.setState({ //把当前点击的行赋值到state里的role
role
})
}
}
}

//获取角色列表数据,设置到state中
getRoles=async()=>{
const result=await reqRoles()
if(result.status===0){
const roles=result.data
this.setState({
roles
})
}
}

//初始化表格列标题,及对应的数据源,dataIndex:对应api返回的数据名
initColumns=()=>{
//【2】调用函数格式化时间戳
this.columns=[
{title:'角色名称',dataIndex:'name'},
{title:'创建时间',dataIndex:'create_time',render:(create_time)=>formateDate(create_time)},
{title:'授权时间',dataIndex:'auth_time',render:formateDate},
{title:'授权人',dataIndex:'auth_name'},
]
}
//点添加角色弹窗的ok按钮:添加角色
addRole=()=>{
this.form.validateFields(async(err,value)=>{
if(!err){
console.log(value)
//隐藏确认框
this.setState({isShowAdd:false})
//收集数据
const {roleName}=value
this.form.resetFields()//清空表单内数据,方便下次使用

//添加角色请求
const result=await reqAddRole(roleName)
if(result.status===0){
message.success('角色添加成功')
//取出返回的新增role值
const role=result.data
//更新roles状态,使新增的角色显示出来(基于原本状态数据更新)
this.setState(state=>({
roles:[...state.roles,role]
}))

}else{
message.error('角色添加失败')
}
}
})
}


// 更新角色:点设置权限弹窗里的ok操作
updateRole=async()=>{//加async
// 隐藏确认框
this.setState({isShowAuth: false})

const role=this.state.role
//得到最新的menus => 到authForm.jsx里传值过来(getMenus = () => this.state.checkedKeys)
const menus=this.auth.current.getMenus()
//把接收过来的菜单传给当前role.menus => 到api/index.js里写更新角色接口函数
role.menus=menus

//【2】添加授权时间及授权人
role.auth_time=Date.now()
role.auth_name = memoryUtils.user.username

//发送更新请求
console.log(role)
const result = await reqUpdateRole(role)
if (result.status===0){
message.success('设置角色权限成功!')
this.getRoles()
}else{
message.error('更新角色权限失败')
}


}

componentWillMount(){
this.initColumns() //函数:运行初始表格列标题,及对应的数据源函数,把表格列数据赋值到this.columus上
}

componentDidMount(){
this.getRoles() //函数:获取角色列表设置到state中
}


render(){
const {roles,role,isShowAdd,isShowAuth}=this.state //娶出role; 取出isShowAuth

//card的左侧 (Button的disabled:按钮不可用)
const title=(
<span>
{/* 点创设置权限:显示对应弹窗; 点创建角色:显示创建角色的弹窗 */}
<Button type='primary' style={{marginRight:8}} onClick={()=>{this.setState({isShowAdd:true})}}>创建角色</Button>
<Button type='primary' disabled={!role._id} onClick={()=>{this.setState({isShowAuth:true})}}>设置角色权限</Button>
</span>
)

return(
<Card title={title}>
<Table
bordered /**边框 */
rowKey='_id' /**表格行 key 的取值,可以是字符串或一个函数 */
dataSource={roles} /**数据源 */
columns={this.columns} /**列标题,及对应的数据源 */
pagination={{defaultPageSize:PAGE_SIZE}} /**分页设置默认分页数量 */
rowSelection={{type:'radio',selectedRowKeys:[role._id]} } /**selectedRowKeys根据4确定哪个是被选中状态; 第行前面加一个单选框antd文档找使用方法 */
onRow={this.onRow} /**控制点击当前行的行为 */
/>

{/* 添加角色弹窗 */}
<Modal
title='添加角色'
visible={isShowAdd} /*弹窗可见状态*/
onOk={this.addRole} /*点ok提交信息*/
onCancel={()=>{
this.setState({isShowAdd:false})
this.form.resetFields()//取消时顺便清空表单方便下次使用
}} /*点取消*/
>
{/* 传递子组件form的函数setForm:(接收一个参数form,令当前组件的form=传过来的form) */}
<AddForm setForm={(form) => this.form = form} />
</Modal>


{/* 设置权限弹窗 */}
<Modal
title='设置权限'
visible={isShowAuth} /*弹窗可见状态*/
onOk={this.updateRole} /*点ok提交信息*/
onCancel={()=>{this.setState({isShowAuth:false})}} /*点取消*/
>
{/*把this.auth传给子组件 把role传递给子组件 */}
<AuthForm ref={this.auth} role={role} />
</Modal>
</Card>
)
}
}

2.addForm.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import React,{Component} from 'react'
import{Form,Input} from 'antd'
import PropTypes from 'prop-types' //【1】传值模块

const Item =Form.Item

class AddForm extends Component{
static propTypes={
setForm:PropTypes.func.isRequired //【2】父组件传过来的接收子组件form对象的函数
}

componentWillMount () {
//【3】运行接收到的父组件传过来函数,把form传给父组件
this.props.setForm(this.props.form)
}

render(){
// 取出form的表单验证方法
const { getFieldDecorator } = this.props.form
// 【2.1】指定Item布局的配置对象
const formItemLayout = {
labelCol: { span: 4 }, // 左侧label的宽度
wrapperCol: { span: 15 }, // 右侧包裹的宽度
}

return(
<Form>
{/* 【2.2】把formItemLayout放入Item */}
<Item label='角色名称' {...formItemLayout}>
{
getFieldDecorator('roleName', {
initialValue: '',
rules: [
{required: true, message: '角色名称必须输入'}
]
})(
<Input placeholder='请输入角色名称'/>
)
}
</Item>
</Form>
)
}
}
export default Form.create()(AddForm) //包装AddForm组件使具体form相关方法

3.authForm.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import React,{Component} from 'react'
import {Form,Input,Tree} from 'antd'
import PropTypes from 'prop-types' //父子传值
import menuList from '../../../config/menuConfig' //导入菜单列表

const Item=Form.Item
const { TreeNode } = Tree //拿出TreeNode

export default class AuthForm extends Component{
constructor(props){
super(props)
//根据传入的角色生成初始状态
const {menus} = this.props.role
this.state={
checkedKeys:menus
}
}

static propTypes={// 接收父传值
role:PropTypes.object
}

//获取菜单列表
getTreeNodes=(menuList)=>{
//代替map函数:reduce((初始值pre,当前正在处理的数组item)={},初始值[])
return menuList.reduce((pre,item)=>{
pre.push(
<TreeNode title={item.title} key={item.key}>
{/* 如果有children则调用本函数,把children再运行一次 */}
{item.children ? this.getTreeNodes(item.children):null}
</TreeNode>
)
return pre
},[])
}

//更新Tree的选中状态
onCheck=(checkedKeys)=>{
console.log('oncheck:',checkedKeys)
this.setState({checkedKeys})
}



//【1】为父组件提交获取最新menus数据的方法:把state.checkedKeys传给父组件
getMenus = () => this.state.checkedKeys



//在页面加载前调用一次菜单
componentWillMount(){
this.treeNodes=this.getTreeNodes(menuList)
}

//【2】根据新传入的role来更新checkedKeys状态当组件接收到新的属性时自动调用
componentWillReceiveProps (nextProps) {
console.log('componentWillReceiveProps()', nextProps)
const menus = nextProps.role.menus
this.setState({
checkedKeys: menus
})
// this.state.checkedKeys = menus
}



render(){
//取出Tree要选中的节点
const {checkedKeys}=this.state
// 取出role
const {role}=this.props

// 指定Item布局的配置对象
const formItemLayout = {
labelCol: { span: 4 }, // 左侧label的宽度
wrapperCol: { span: 15 }, // 右侧包裹的宽度
}
return(
<div>
<Item label='角色名称' {...formItemLayout}>
{/* 显示选中的角色名,并让它成不可编辑的状态 */}
<Input value={role.name} disabled />
</Item>

{/* 到antd复制一个tree进行修改 */}
<Tree
checkable
defaultExpandAll={true} /*默认展开所有节点*/
checkedKeys={checkedKeys} /*控制哪些节点为选中状态*/
onCheck={this.onCheck} /*点击后更改选中状态*/
>
{/* 外面包个根节点,平台权限,内调用3步的treeNodes */}
<TreeNode title='平台权限' key='all'>
{this.treeNodes}
</TreeNode>

</Tree>
</div>
)
}
}

3.utils/memoryUtils.jsx

1
2
3
4
5
6
/*
用于在内存中保存数据的工具模块
*/
export default{
user:{},
}

4.api/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import ajax from './ajax'
import jsonp from 'jsonp'
import {message} from 'antd' //借用antd返回信息组件
// const BASE = 'http://localhost:5000'
const BASE = ''

//导出一个函数,第1种写法
//登录接口函数
// export function reqLogin(username,password){
// return ajax('login',{username,password},'POST')
// }

//导出一个函数,第2种写法
// 登录接口函数
export const reqLogin=(username,password)=>ajax(BASE+'login',{username,password},'POST')


//获取产品一级/二级分类列表接口
export const reqCategorys=(parentId)=>ajax(BASE+'/manage/category/list',{parentId})
//添加产品分类接口
export const reqAddCategory=(parentId,categoryName)=>ajax(BASE+'/manage/category/add',{parentId,categoryName},'POST')
//修改产品分类接口
export const reqUpdateCategory=({categoryId,categoryName})=>ajax(BASE+'/manage/category/update',{categoryId,categoryName},'POST')
//根据分类Id获取一个分类
export const reqCategory = (categoryId) => ajax(BASE + '/manage/category/info', {categoryId})
//获取产品列表
export const reqProducts=(pageNum,pageSize)=>ajax(BASE+'/manage/product/list',{pageNum,pageSize})
//产品上下架
export const reqUpdateStatus=(productId,status)=>ajax(BASE+'/manage/product/updateStatus',{productId,status},'POST')

/*搜索商品分页列表 (根据商品名称/商品描述)
searchType(搜索的类型): productName/productDesc*/
export const reqSearchProducts = ({pageNum, pageSize, searchName, searchType}) => ajax(BASE + '/manage/product/search', {
pageNum,
pageSize,
[searchType]: searchName,
})

//添加商品/修改商品:二合一接口,如果参数存在._id则为修改商品,否则为添加商品
export const reqAddUpdatePro=(product)=>ajax(BASE+'/manage/product/'+(product._id?'update':'add'),product,'POST')


// 删除服务器上指定名称图片
export const reqDeletPic=(name)=>ajax(BASE+'/manage/img/delete',{name},'POST')


//请求所有角色列表
export const reqRoles=()=>ajax(BASE+'/manage/role/list')


// 添加角色
export const reqAddRole=(roleName)=>ajax(BASE+'/manage/role/add',{roleName},'POST')
// 更新角色,传过来的参数就是字典格式,所以role参数不用加花括号
export const reqUpdateRole=(role)=>ajax(BASE+'/manage/role/update',role,'POST')


// 天气接口
export const reqWeather=(city) => {
const url = `http://api.map.baidu.com/telematics/v3/weather?location=${city}&output=json&ak=3p49MVra6urFRGOT9s8UBWr2`
//返回一个promise函数
return new Promise((resolve,reject) => {
//发送一个jsonp请求
jsonp(url,{},(err,data) => {
//输出请求的数据到控制台
console.log('jsonp()', err, data)
//如果请求成功
if(!err && data.status==='success'){
//从数据中解构取出图片、天气
const {dayPictureUrl,weather}=data.results[0].weather_data[0]
//异步返回图片、天气给调用函数者
resolve({dayPictureUrl,weather})
}else{//如果请求失败
message.error('天气信息获取失败')
}
})
})
}
//reqWeather('上海')

5. utils/storageUtils.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
进行local数据存储管理的工具模块
*/
import store from 'store'
const USER_KEY = 'user_key'
export default {
/*保存user*/
saveUser (user) {
// localStorage.setItem(USER_KEY, JSON.stringify(user))
store.set(USER_KEY, user)
},

/*读取user*/
getUser () {
// return JSON.parse(localStorage.getItem(USER_KEY) || '{}')
return store.get(USER_KEY) || {}
},

/*删除user*/
removeUser () {
// localStorage.removeItem(USER_KEY)
store.remove(USER_KEY)
}
}

效果

在这里插入图片描述