需求说明,一个帐号角色可以设置管理多个项目的菜单权限
且菜单接口每次只能查询特定项目的菜单数据【无法查全部】
开发思路:
1,获取项目接口数组,得到项目数据
2,循环项目数据,以此为参数递归查询菜单数据【递归查询是为保证循环时数据异步请求顺序 不稳定】
3,将菜单数组组装成一个二维数组,以待循环树选择组件作展示 数据使用
4,循环树选择组件,实现树选择菜单功能
5,读取某条用户信息的菜单权限,将返回字符串菜单编码处理成与菜单数据相同的二维数组
6,奖该用户信息的菜单权限数组加载到循环树选择组件作默认选中
开发难点:
1,菜单编号要在指定数组中进行增删改,就需要对其分别 打上标签,这里是额外创建一个numListArry对象,打上对应项目标签顺序下标,以此匹配菜单数据二维数组的下标【因递归查询 ,所以次序固定】
2,理解树选择的点击事件返回的各种参数,将返回的数据 实时更新到该条用户信息的菜单权限数组中,实现树状选择功能
3,各种基础算法都用到不少,如字符串去重,数组求交集,递归,查找等
用户角色信息【表格数据】
项目数据:
菜单数据
numListArry对象,对应角标关系准确匹配数组数据
列表页面代码
1 import React from 'react';2 import {Table,Form,Button,Popconfirm } from 'antd';3 import LoadingMixin from '../../../../../libs/loading.common.mixin';4 import RequestMixin from '../../../../../libs/request.mixin';5 import NotificationMixin from '../../../../../libs/notification.mixin';6 import ModalWrapper from '../../../../../libs/modalwrapper';7 import Helper from '../../../../../libs/helper';8 import './index.css';9 import AddOrUpdateModal from './addorupdatemodal';10 import LocalService from "../../../../../services/local.services";11
12 let prolist = LocalService.getUserInfo() && JSON.parse(LocalService.getUserInfo()) ||[] ;13 const createForm =Form.create;14 let user =React.createClass({15 mixins: [LoadingMixin,NotificationMixin,RequestMixin],16 getInitialState(){17 return{18 data: [],19 menuList: [], //所有菜单信息集合
20 menuArry:[],21 fetchCategoryListArry:[],22 numListArry:{} //菜单ID匹配下标对象
23 }24
25 },26 componentWillMount() {27 this.fetch();28 if(prolist.prolist){29 this.fetchCategoryList(prolist.id,prolist.prolist,0);30 }31
32
33 },34 fetch() {35
36 this.get({ //查询用户角色信息 【数据用以在表格中展示】
37 url: "Api/lists/module/role/key/dac509bd9***********a6f1af4bac",38 param: {},39 noLoading: true
40 }).then(result=>{41 this.setState({data: result.result ||[]});42 this.fetchCategoryListArry()43 });44 },45 fetchCategoryListArry() { //查询项目信息
46 var getSessionId =LocalService.getSessionId()47 this.get({48 url: "Api/lists/module/menu/key/dac509bd90a**************1af4bac",49 param: {50 sessionid:getSessionId || ''
51 },52 noLoading: true
53 }).then(result=>{54 this.setState({fetchCategoryListArry: result.result ||[]});55 });56 },57 fetchCategoryList(userid,thisprolist,numIndex) { //查询菜单
58 var proid =thisprolist[numIndex];59 var getSessionId =LocalService.getSessionId()60 this.get({61 url: "Api/search/module/menu/key/dac509bd90***************6f1af4bac",62 param: {63 userid: userid,64 proid:proid,65 sessionid:getSessionId || ''
66 },67 noLoading: true
68 }).then(result=>{69 let menuList = result.result ||[];70 let treeData =[];71 let menuArry = this.state.menuArry;72 var numListArry=this.state.numListArry;73
74 menuList && menuList.map(item =>{75 let treeItem ={76 id: item.id,77 label: item.title,78 value: item.id,79 key: item.id,80 hasChildren: item.hasChildren,81 proid:proid, //取出项目ID存入每条信息中,方便在树选择中显示为哪个项目的提示【placeholder】
82 numIndex:numIndex83 };84 numListArry[item.id]=numIndex;85 if (item.hasChildren) { //处理接口数据
86 let children =[];87 item.children && item.children.map(menu =>{88 children.push({89 id: menu.id,90 fid: item.id,91 label: menu.title,92 value: menu.id,93 key: menu.id,94 proid:proid,95 numIndex:numIndex96 });97
98 numListArry[menu.id]=numIndex;99 });100 treeItem.children =children;101 }102 treeData.push(treeItem);103 });104 menuArry.push(treeData)105
106 var numArry =[]107 this.setState({menuList: menuArry,numListArry:numListArry}); //将全部菜单数据和处理好的菜单ID下标对象存入各自全局变量
108
109 numIndex++;110 if(numIndex
112 }113 });114 },115 deleteRole(parms){ //删除角色信息接口
116 let that = this;117 if (!parms) return;118 that.post({119 url: "Api/batchDelete/module/role/key/dac509********4bac",120 param: {ids:parms.id},121 noLoading: true
122 }).then(result=>{123 if(result.result) {124 that.success("删除成功");125 that.fetch();126 }127 });128 },129 addOrUpdate(modal,e) {130 e &&e.preventDefault() ;131 e &&e.stopPropagation();132 new ModalWrapper(AddOrUpdateModal, "addOrUpdateModal", () =>{133 this.fetch();134 }, null, {135 menuList:this.state.menuList, //打开弹窗时,将全部菜单数据传入子组件
136 numListArry:this.state.numListArry, //菜单ID下标对象,同上
137 title: modal && modal.id ? '编辑角色' : '新增角色',138 item: modal && modal.id ? Helper.copyObject(modal) : {}, //本条角色信息的数据,包含其已有的菜单数据【回显】
139 isEdit: modal && modal.id ? true:false
140 }).show();141 },142 render(){143 let statusObj ={144 0: "有效",145 1: "无效"
146 };147
148 let columns =[149 { title: '编号',dataIndex: 'id',key: 'id', width: '5%'},150 { title: '角色名称',dataIndex: 'role_name',key: 'role_name', width: '10%'},151 { title: '权限', dataIndex: 'permission',key: 'permission', width: '35%',152 render: (text, record) =>{153 let menuList =[];154 let permission = record.permission.split(',')155 permission && permission.map( item =>{156 this.state.fetchCategoryListArry && this.state.fetchCategoryListArry.map( first =>{157 if(item ==first.id){158 menuList.push(first.title);159 }160 })161 })162
163 if( !menuList==null || !menuList.length==0){164 return(165 menuList.join(',')+'...'
166 )167 }168
169 }170 },171 { title: '创建时间', dataIndex: 'create_time',key: 'create_time', width: '15%',},172 { title: '更新时间', dataIndex: 'update_time',key: 'update_time', width: '15%', },173 { title: '状态', dataIndex: 'is_del',key: 'is_del', width: '7%',174 render: (text, record) =>{175 return(176 statusObj[record["is_del"]]177 )178 }179 },180
181 { title: '操作', key: '#', width: '23%',182 render: (text, record) =>{183 return(184
185 修改
186
187 删除
188
189
190
191 )192 }193 }194 ];195 return(196
197
198
199
200 添加角色
201
202
203
209
210
211
212 )213 }214 });215 user =createForm()(user);216 export default user;
弹窗组件:
1 import React from "react";2 import {Modal, Form, Input, Select, TreeSelect} from 'antd';3 import LoadingMixin from '../../../../../libs/loading.common.mixin';4 import RequestMixin from '../../../../../libs/request.mixin';5 import NotificationMixin from '../../../../../libs/notification.mixin';6 import LocalService from "../../../../../services/local.services";7 const FormItem =Form.Item;8 const createForm =Form.create;9 const Option =Select.Option;10 const SHOW_PARENT =TreeSelect.SHOW_ALL;11 let prolist = LocalService.getUserInfo() && JSON.parse(LocalService.getUserInfo()) ||[] ;12
13 let addOrUpdateModal =React.createClass({14 mixins: [LoadingMixin, NotificationMixin, RequestMixin],15 propTypes: {16 onManualClose: React.PropTypes.func,17 onOk: React.PropTypes.func,18 onCancel: React.PropTypes.func,19 title: React.PropTypes.string,20 item: React.PropTypes.object,21 menuList: React.PropTypes.array,22 isEdit: React.PropTypes.bool,23 numListArry:React.PropTypes.object,24 },25 getInitialState() {26 return{27 item: this.props.item || {}, //用户信息
28 menuList: this.props.menuList || [], //权限集合
29 numListArry: this.props.numListArry ||[],30 permissions: [],31 oldPermissions: [],32 headNav:[],33 permissionsArry:[],34 newPermissionsArry:[],35 headNavsearchPlaceholder:[]36 };37 },38 componentWillMount(){39
40 if (this.props.isEdit) {41 let item = this.state.item || {}; //角色已有权限数据作去重去空转换成数组
42 let permissions = item.permission.split(",");43 let menuList = this.state.menuList;44
45 var arry = permissions.filter(function(element,index,self){ //去重去空
46 return self.indexOf(element) ===index;47 });48 for(var i = 0;i
56 this.setState({permissionsArry: permissions});57 }58 this.getHeaderMenu()59
60 },61 getHeaderMenu(){ //顶部菜单
62 this.get({63 url: "Api/lists/module/project/key/dac509bd90a82719a3569291e12c24a6f1af4bac",64 param: {65 }66 }).then(result =>{67 this.setState({headNav: result.result ||[]});68 var permissions =[]69 var itemList =[]70 var itemStr = this.state.permissionsArry71 var menuList = this.state.menuList;72 var menuListArry =[]73 menuList && menuList.map((item)=>{ //全部菜单数组转换成只含ID的数组【待与传来的角色已有权限数据作交集处理】
74 var newPermissions =[]75 item && item.map((pros)=>{76 if(pros.children){77 newPermissions.push(pros.id);78 pros.children.map( (list) =>{79 newPermissions.push(list.id);80 })81 }else{82 newPermissions.push(pros.id);83 }84 })85
86 Array.intersect = function(arr1, arr2) {87 if(Object.prototype.toString.call(arr1) === "[object Array]" && Object.prototype.toString.call(arr2) === "[object Array]") {88 return arr1.filter(function(v){89 return arr2.indexOf(v)!==-1
90 })91 }92 }93 var mergeArry = Array.intersect(itemStr, newPermissions); //此处求交集原因:因角色所含菜单数据是一个包含菜单ID的无绪字符串,转成数据也无法直接在树选择组件中循环回显
94 menuListArry.push(mergeArry) //所以要先将所有菜单数据的二维数组处理成一个只含ID的二维数组,将其每条子数组与角色所含菜单数据进行交集匹配,
95 }) //处理完的二维数组就可以用在树选择组件中循环回选了
96
97 this.setState({permissions: menuListArry});98
99 var headNavsearchPlaceholder = [] //权限列表提示
100 this.state.headNav && this.state.headNav.map((item)=>{101 headNavsearchPlaceholder[item.id] =item.title102 })103 this.setState({headNavsearchPlaceholder:headNavsearchPlaceholder});104 });105 },106
107 onChange(value,label,extra){108 let newPermissions =[];109 let proid =''
110 let permissions = this.state.permissions;111 var numListArry = this.state.numListArry;112 var numIndex =numListArry[extra.triggerValue];113
114 if(numIndex != null){ //判断 点击数据是否有菜单ID,有的话按照之前numListArry对象中匹配该菜单ID所属项目下标,【对应递归查询的二维数组下标】
115 permissions[numIndex] =value;116 }117 this.setState({permissions:permissions ,oldPermissions:permissions});118 },119 postSubmit(url, param) {120 this.post({121 url: url,122 param: param,123 noLoading: true
124 }).then(result =>{125 if (result &&result.result) {126 this.success(!this.props.isEdit ? '新增成功' : '修改成功');127 this.props.onManualClose && this.props.onManualClose();128 }129 });130 },131 handleSubmit() {132 this.props.form.validateFieldsAndScroll((errors, values) =>{133 if (!errors) {134 var param =values;135 param.permission = this.state.permissions.join(','); //循环后的树选择返回的数据也是个只含菜单ID二维数组,接口只能入传字符串,所以进行数据处理
136 if (!this.props.isEdit) { //判断是新增还是修改
137 this.postSubmit("Api/add/module/role/key/dac509bd90******1af4bac", param);138 }139 else{140 param.id = this.state.item && this.state.item.id;141 this.postSubmit("Api/edit/module/role/key/dac509bd90*****af4bac", param);142 }143 }144 });145 },146 hideModal() {147 this.props.onCancel && this.props.onCancel();148 },149 render() {150 const {getFieldDecorator} = this.props.form;151 const formItemLayout ={152 labelCol: {span: 6},153 wrapperCol: {span: 10},154 };155
156 const treeSelectArry =[]157 this.state.menuList && this.state.menuList.map((item,index)=>{ //循环树选择数据
158 const tProps ={159 value: this.state.permissions[index],160 treeData: item,161 onChange: this.onChange,162 multiple: true,163 treeCheckable: true,164 dropdownStyle: {maxHeight:"350px"},165 showCheckedStrategy: SHOW_PARENT,166 searchPlaceholder: '请选择'+this.state.headNavsearchPlaceholder[item[0].proid]+'权限列表',167 style: {168 width: 300,169 },170 };171 treeSelectArry.push(tProps)172 })173
174 return(175
177
178
179 {getFieldDecorator('role_name', {180 initialValue: this.state.item && this.state.item.role_name || '',181 rules: [{ required: true, message: '请输入角色名称!'}],182 })(183
184 )}185
186
187
188 {189 this.state.menuList && this.state.menuList.map((item,index)=>{ //循环树选择DOM
190
191 return(192
193
194
195 )196 })197 }198 {/**/}199
200
201
202
203 {getFieldDecorator('is_del',{204 initialValue: this.state.is_del && this.state.is_del || '0'
205 })(206
207 无效
208 有效
209
210 )}211
212
213
214
215 )216 }217 });218 addOrUpdateModal =createForm()(addOrUpdateModal);219 export default addOrUpdateModal;