需求說明,一個帳號角色可以設置管理多個項目的菜單權限
且菜單接口每次只能查詢特定項目的菜單數據【無法查全部】
開發思路:
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:numIndex 83 }; 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:numIndex 96 }); 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<thisprolist.length){ 111 this.fetchCategoryList(userid,thisprolist,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 <div> 185 <Button type="primary" style={{ marginRight: 8 }} onClick={this.addOrUpdate.bind(this,record)}>修改</Button> 186 <Popconfirm title="確定刪除角色?" onConfirm={this.deleteRole.bind(this,record)} okText="確定" cancelText="取消"> 187 <Button type="primary">刪除</Button> 188 </Popconfirm> 189 </div> 190 191 ) 192 } 193 } 194 ]; 195 return ( 196 <div className="role"> 197 <div className="title"> 198 <h2>角色管理</h2> 199 200 <Button type="primary" onClick={this.addOrUpdate.bind(this,'')}>添加角色</Button> 201 </div> 202 <div className="list-role"> 203 <Table columns={columns} 204 dataSource={this.state.data} 205 pagination={false} 206 scroll={{ y: 600 }} 207 rowKey={(record) => record.id} 208 > 209 </Table> 210 </div> 211 </div> 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<arry.length;i++){ 49 if(arry[i]==''||arry[i]==null||typeof(arry[i])==undefined){ 50 arry.splice(i,1); 51 i=i-1; 52 } 53 } 54 permissions = arry; 55 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.permissionsArry 71 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.title 102 }) 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 <Modal title={this.props.title || '新增'} visible={true} width="550px" onOk={this.handleSubmit} 176 onCancel={this.hideModal} maskClosable={false}> 177 <Form layout="horizontal" autoComplete="off"> 178 <FormItem {...formItemLayout} label="角色名稱" > 179 {getFieldDecorator('role_name', { 180 initialValue: this.state.item && this.state.item.role_name || '', 181 rules: [{ required: true, message: '請輸入角色名稱!' }], 182 })( 183 <Input placeholder="請輸入角色名稱"/> 184 )} 185 </FormItem> 186 187 <FormItem {...formItemLayout} label="權限列表"> 188 { 189 this.state.menuList && this.state.menuList.map((item,index)=>{ //循環樹選擇DOM 190 191 return ( 192 <div style={{ marginBottom : "10px" }} key={index}> 193 <TreeSelect {...treeSelectArry[index]} /> 194 </div> 195 ) 196 }) 197 } 198 {/*<TreeSelect {...tProps} />*/} 199 </FormItem> 200 201 202 <FormItem{...formItemLayout} label="狀態"> 203 {getFieldDecorator('is_del',{ 204 initialValue: this.state.is_del && this.state.is_del || '0' 205 })( 206 <Select style={{ width: 100 }}> 207 <Option value="1">無效</Option> 208 <Option value="0">有效</Option> 209 </Select> 210 )} 211 </FormItem> 212 213 </Form> 214 </Modal> 215 ) 216 } 217 }); 218 addOrUpdateModal = createForm()(addOrUpdateModal); 219 export default addOrUpdateModal;