react 使用antd的TreeSelect树选择组件实现多个树选择循环
需求说明,一个帐号角色可以设置管理多个项目的菜单权限
且菜单接口每次只能查询特定项目的菜单数据【无法查全部】
开发思路:
1,获取项目接口数组,得到项目数据
2,循环项目数据,以此为参数递归查询菜单数据【递归查询是为保证循环时数据异步请求顺序 不稳定】
3,将菜单数组组装成一个二维数组,以待循环树选择组件作展示 数据使用
4,循环树选择组件,实现树选择菜单功能
5,读取某条用户信息的菜单权限,将返回字符串菜单编码处理成与菜单数据相同的二维数组
6,奖该用户信息的菜单权限数组加载到循环树选择组件作默认选中
开发难点:
1,菜单编号要在指定数组中进行增删改,就需要对其分别 打上标签,这里是额外创建一个numListArry对象,打上对应项目标签顺序下标,以此匹配菜单数据二维数组的下标【因递归查询 ,所以次序固定】
2,理解树选择的点击事件返回的各种参数,将返回的数据 实时更新到该条用户信息的菜单权限数组中,实现树状选择功能
3,各种基础算法都用到不少,如字符串去重,数组求交集,递归,查找等
用户角色信息【表格数据】
项目数据:
菜单数据
numListArry对象,对应角标关系准确匹配数组数据
列表页面代码
import React from 'react';
import {Table,Form,Button,Popconfirm } from 'antd';
import LoadingMixin from '../../../../../libs/loading.common.mixin';
import RequestMixin from '../../../../../libs/request.mixin';
import NotificationMixin from '../../../../../libs/notification.mixin';
import ModalWrapper from '../../../../../libs/modalwrapper';
import Helper from '../../../../../libs/helper';
import './index.css';
import AddOrUpdateModal from './addorupdatemodal';
import LocalService from "../../../../../services/local.services"; let prolist = LocalService.getUserInfo() && JSON.parse(LocalService.getUserInfo()) || [] ;
const createForm = Form.create;
let user = React.createClass({
mixins: [LoadingMixin,NotificationMixin,RequestMixin],
getInitialState(){
return {
data: [],
menuList: [], //所有菜单信息集合
menuArry:[],
fetchCategoryListArry:[],
numListArry:{} //菜单ID匹配下标对象
} },
componentWillMount() {
this.fetch();
if(prolist.prolist){
this.fetchCategoryList(prolist.id,prolist.prolist,0);
} },
fetch() { this.get({ //查询用户角色信息 【数据用以在表格中展示】
url: "Api/lists/module/role/key/dac509bd9***********a6f1af4bac",
param: {},
noLoading: true
}).then(result=> {
this.setState({data: result.result || []});
this.fetchCategoryListArry()
});
},
fetchCategoryListArry() { //查询项目信息
var getSessionId = LocalService.getSessionId()
this.get({
url: "Api/lists/module/menu/key/dac509bd90a**************1af4bac",
param: {
sessionid:getSessionId || ''
},
noLoading: true
}).then(result=> {
this.setState({fetchCategoryListArry: result.result || []});
});
},
fetchCategoryList(userid,thisprolist,numIndex) { //查询菜单
var proid = thisprolist[numIndex];
var getSessionId = LocalService.getSessionId()
this.get({
url: "Api/search/module/menu/key/dac509bd90***************6f1af4bac",
param: {
userid: userid,
proid:proid,
sessionid:getSessionId || ''
},
noLoading: true
}).then(result=> {
let menuList = result.result || [];
let treeData = [];
let menuArry = this.state.menuArry;
var numListArry=this.state.numListArry; menuList && menuList.map(item => {
let treeItem = {
id: item.id,
label: item.title,
value: item.id,
key: item.id,
hasChildren: item.hasChildren,
proid:proid, //取出项目ID存入每条信息中,方便在树选择中显示为哪个项目的提示【placeholder】
numIndex:numIndex
};
numListArry[item.id]=numIndex;
if (item.hasChildren) { //处理接口数据
let children = [];
item.children && item.children.map(menu => {
children.push({
id: menu.id,
fid: item.id,
label: menu.title,
value: menu.id,
key: menu.id,
proid:proid,
numIndex:numIndex
}); numListArry[menu.id]=numIndex;
});
treeItem.children = children;
}
treeData.push(treeItem);
});
menuArry.push(treeData) var numArry = []
this.setState({menuList: menuArry,numListArry:numListArry}); //将全部菜单数据和处理好的菜单ID下标对象存入各自全局变量 numIndex++;
if(numIndex<thisprolist.length){
this.fetchCategoryList(userid,thisprolist,numIndex); //递归查询
}
});
},
deleteRole(parms){ //删除角色信息接口
let that = this;
if (!parms) return;
that.post({
url: "Api/batchDelete/module/role/key/dac509********4bac",
param: {ids:parms.id},
noLoading: true
}).then(result=> {
if (result.result) {
that.success("删除成功");
that.fetch();
}
});
},
addOrUpdate(modal,e) {
e && e.preventDefault() ;
e && e.stopPropagation();
new ModalWrapper(AddOrUpdateModal, "addOrUpdateModal", () => {
this.fetch();
}, null, {
menuList:this.state.menuList, //打开弹窗时,将全部菜单数据传入子组件
numListArry:this.state.numListArry, //菜单ID下标对象,同上
title: modal && modal.id ? '编辑角色' : '新增角色',
item: modal && modal.id ? Helper.copyObject(modal) : {}, //本条角色信息的数据,包含其已有的菜单数据【回显】
isEdit: modal && modal.id ? true:false
}).show();
},
render(){
let statusObj = {
0: "有效",
1: "无效"
}; let columns = [
{ title: '编号',dataIndex: 'id',key: 'id', width: '5%'},
{ title: '角色名称',dataIndex: 'role_name',key: 'role_name', width: '10%'},
{ title: '权限', dataIndex: 'permission',key: 'permission', width: '35%',
render: (text, record) => {
let menuList = [];
let permission = record.permission.split(',')
permission && permission.map( item =>{
this.state.fetchCategoryListArry && this.state.fetchCategoryListArry.map( first =>{
if(item == first.id){
menuList.push(first.title);
}
})
}) if( !menuList==null || !menuList.length==0){
return (
menuList.join(',')+'...'
)
} }
},
{ title: '创建时间', dataIndex: 'create_time',key: 'create_time', width: '15%',},
{ title: '更新时间', dataIndex: 'update_time',key: 'update_time', width: '15%', },
{ title: '状态', dataIndex: 'is_del',key: 'is_del', width: '7%',
render: (text, record) => {
return (
statusObj[record["is_del"]]
)
}
}, { title: '操作', key: '#', width: '23%',
render: (text, record) => {
return (
<div>
<Button type="primary" style={{ marginRight: 8 }} onClick={this.addOrUpdate.bind(this,record)}>修改</Button>
<Popconfirm title="确定删除角色?" onConfirm={this.deleteRole.bind(this,record)} okText="确定" cancelText="取消">
<Button type="primary">删除</Button>
</Popconfirm>
</div> )
}
}
];
return (
<div className="role">
<div className="title">
<h2>角色管理</h2> <Button type="primary" onClick={this.addOrUpdate.bind(this,'')}>添加角色</Button>
</div>
<div className="list-role">
<Table columns={columns}
dataSource={this.state.data}
pagination={false}
scroll={{ y: 600 }}
rowKey={(record) => record.id}
>
</Table>
</div>
</div>
)
}
});
user = createForm()(user);
export default user;
弹窗组件:
import React from "react";
import {Modal, Form, Input, Select, TreeSelect} from 'antd';
import LoadingMixin from '../../../../../libs/loading.common.mixin';
import RequestMixin from '../../../../../libs/request.mixin';
import NotificationMixin from '../../../../../libs/notification.mixin';
import LocalService from "../../../../../services/local.services";
const FormItem = Form.Item;
const createForm = Form.create;
const Option = Select.Option;
const SHOW_PARENT = TreeSelect.SHOW_ALL;
let prolist = LocalService.getUserInfo() && JSON.parse(LocalService.getUserInfo()) || [] ; let addOrUpdateModal = React.createClass({
mixins: [LoadingMixin, NotificationMixin, RequestMixin],
propTypes: {
onManualClose: React.PropTypes.func,
onOk: React.PropTypes.func,
onCancel: React.PropTypes.func,
title: React.PropTypes.string,
item: React.PropTypes.object,
menuList: React.PropTypes.array,
isEdit: React.PropTypes.bool,
numListArry:React.PropTypes.object,
},
getInitialState() {
return {
item: this.props.item || {}, //用户信息
menuList: this.props.menuList || [], //权限集合
numListArry: this.props.numListArry || [],
permissions: [],
oldPermissions: [],
headNav:[],
permissionsArry:[],
newPermissionsArry:[],
headNavsearchPlaceholder:[]
};
},
componentWillMount(){ if (this.props.isEdit) {
let item = this.state.item || {}; //角色已有权限数据作去重去空转换成数组
let permissions = item.permission.split(",");
let menuList = this.state.menuList; var arry = permissions.filter(function(element,index,self){ //去重去空
return self.indexOf(element) === index;
});
for(var i = 0;i<arry.length;i++){
if(arry[i]==''||arry[i]==null||typeof(arry[i])==undefined){
arry.splice(i,1);
i=i-1;
}
}
permissions = arry; this.setState({permissionsArry: permissions});
}
this.getHeaderMenu() },
getHeaderMenu(){ //顶部菜单
this.get({
url: "Api/lists/module/project/key/dac509bd90a82719a3569291e12c24a6f1af4bac",
param: {
}
}).then(result => {
this.setState({headNav: result.result || []});
var permissions = []
var itemList =[]
var itemStr = this.state.permissionsArry
var menuList = this.state.menuList;
var menuListArry =[]
menuList && menuList.map((item)=>{ //全部菜单数组转换成只含ID的数组【待与传来的角色已有权限数据作交集处理】
var newPermissions =[]
item && item.map((pros)=>{
if (pros.children){
newPermissions.push(pros.id);
pros.children.map( (list) =>{
newPermissions.push(list.id);
})
}else{
newPermissions.push(pros.id);
}
}) Array.intersect = function(arr1, arr2) {
if(Object.prototype.toString.call(arr1) === "[object Array]" && Object.prototype.toString.call(arr2) === "[object Array]") {
return arr1.filter(function(v){
return arr2.indexOf(v)!==-1
})
}
}
var mergeArry = Array.intersect(itemStr, newPermissions); // 此处求交集原因:因角色所含菜单数据是一个包含菜单ID的无绪字符串,转成数据也无法直接在树选择组件中循环回显
menuListArry.push(mergeArry) //所以要先将所有菜单数据的二维数组处理成一个只含ID的二维数组,将其每条子数组与角色所含菜单数据进行交集匹配,
}) //处理完的二维数组就可以用在树选择组件中循环回选了 this.setState({permissions: menuListArry}); var headNavsearchPlaceholder = [] //权限列表提示
this.state.headNav && this.state.headNav.map((item)=>{
headNavsearchPlaceholder[item.id] = item.title
})
this.setState({headNavsearchPlaceholder:headNavsearchPlaceholder});
});
}, onChange(value,label,extra){
let newPermissions = [];
let proid =''
let permissions = this.state.permissions;
var numListArry = this.state.numListArry;
var numIndex = numListArry[extra.triggerValue]; if(numIndex != null){ //判断 点击数据是否有菜单ID,有的话按照之前numListArry对象中匹配该菜单ID所属项目下标,【对应递归查询的二维数组下标】
permissions[numIndex] = value;
}
this.setState({permissions:permissions ,oldPermissions:permissions});
},
postSubmit(url, param) {
this.post({
url: url,
param: param,
noLoading: true
}).then(result => {
if (result && result.result) {
this.success(!this.props.isEdit ? '新增成功' : '修改成功');
this.props.onManualClose && this.props.onManualClose();
}
});
},
handleSubmit() {
this.props.form.validateFieldsAndScroll((errors, values) => {
if (!errors) {
var param = values;
param.permission = this.state.permissions.join(','); //循环后的树选择返回的数据也是个只含菜单ID二维数组,接口只能入传字符串,所以进行数据处理
if (!this.props.isEdit) { //判断是新增还是修改
this.postSubmit("Api/add/module/role/key/dac509bd90******1af4bac", param);
}
else {
param.id = this.state.item && this.state.item.id;
this.postSubmit("Api/edit/module/role/key/dac509bd90*****af4bac", param);
}
}
});
},
hideModal() {
this.props.onCancel && this.props.onCancel();
},
render() {
const {getFieldDecorator} = this.props.form;
const formItemLayout = {
labelCol: {span: 6},
wrapperCol: {span: 10},
}; const treeSelectArry = []
this.state.menuList && this.state.menuList.map((item,index)=>{ //循环树选择数据
const tProps = {
value: this.state.permissions[index],
treeData: item,
onChange: this.onChange,
multiple: true,
treeCheckable: true,
dropdownStyle: {maxHeight:"350px"},
showCheckedStrategy: SHOW_PARENT,
searchPlaceholder: '请选择'+this.state.headNavsearchPlaceholder[item[0].proid]+'权限列表',
style: {
width: 300,
},
};
treeSelectArry.push(tProps)
}) return (
<Modal title={this.props.title || '新增'} visible={true} width="550px" onOk={this.handleSubmit}
onCancel={this.hideModal} maskClosable={false}>
<Form layout="horizontal" autoComplete="off">
<FormItem {...formItemLayout} label="角色名称" >
{getFieldDecorator('role_name', {
initialValue: this.state.item && this.state.item.role_name || '',
rules: [{ required: true, message: '请输入角色名称!' }],
})(
<Input placeholder="请输入角色名称"/>
)}
</FormItem> <FormItem {...formItemLayout} label="权限列表">
{
this.state.menuList && this.state.menuList.map((item,index)=>{ //循环树选择DOM return (
<div style={{ marginBottom : "10px" }} key={index}>
<TreeSelect {...treeSelectArry[index]} />
</div>
)
})
}
{/*<TreeSelect {...tProps} />*/}
</FormItem> <FormItem{...formItemLayout} label="状态">
{getFieldDecorator('is_del',{
initialValue: this.state.is_del && this.state.is_del || '0'
})(
<Select style={{ width: 100 }}>
<Option value="1">无效</Option>
<Option value="0">有效</Option>
</Select>
)}
</FormItem> </Form>
</Modal>
)
}
});
addOrUpdateModal = createForm()(addOrUpdateModal);
export default addOrUpdateModal;
react 使用antd的TreeSelect树选择组件实现多个树选择循环的更多相关文章
- 封装react antd的form表单组件
form表单在我们日常的开发过程中被使用到的概率还是很大的,比如包含了登录.注册.修改个人信息.新增修改业务数据等的公司内部管理系统.而在使用时这些表单的样式如高度.上下边距.边框.圆角.阴影.高亮等 ...
- react树状组件
最近在react项目中需要一个树状组件,但是又不想因为这个去引入一套UI组件,故自己封装了一个基于react的树状组件, 个人认为比较难得部分在于数据的处理,话不多说直接上代码: 下面是tree.js ...
- 封装react antd的upload上传组件
上传文件也是我们在实际开发中常遇到的功能,比如上传产品图片以供更好地宣传我们的产品,上传excel文档以便于更好地展示更多的产品信息,上传zip文件以便于更好地收集一些资料信息等等.至于为何要把上传组 ...
- jeecgboot-vue3笔记(八)——treeSelect树形选择组件的使用(一次性加载)
使用效果 前端代码 定义interface export interface TreeDataItem { value: string; key: string; title?: string; sl ...
- 循序渐进VUE+Element 前端应用开发(8)--- 树列表组件的使用
在我前面随笔<循序渐进VUE+Element 前端应用开发(6)--- 常规Element 界面组件的使用>里面曾经介绍过一些常规的界面组件的处理,主要介绍到单文本输入框.多文本框.下拉列 ...
- react-native DatePicker日期选择组件的实现
本教程的实现效果如下: 为了实现其淡入/淡出的覆盖效果, 还有取消按钮, 在此用了一个三方的组件, 大家可以先安装一下: 三方组件的地址:https://github.com/eyaleizenber ...
- react系列(二)高阶组件-HOC
高阶组件 简单来说,高阶组件可以看做一个函数,且该函数接受一个组件作为参数,并返回一个新的组件. 我在之前的博客<闭包和类>中提到一个观点,面向对象的好处就在于,易于理解,方便维护和复用. ...
- Vue3学习(十五)之 级联选择组件Cascader的使用
写在前面 好像又过去了一周,依旧是什么也没产出,不是懒,而是心情不好,什么也不想干,失眠是常事. 应该是从今年开始,突然感觉博客园就像是我自己的日记一样,承载着自己的喜怒哀乐和酸甜苦辣咸,当然,尴尬的 ...
- JS组件系列——不容错过的两款Bootstrap Icon图标选择组件
前言:最近好多朋友在群里面聊到bootstrap icon图标的问题,比如最常见的菜单管理,每个菜单肯定需要一个对应的菜单图标,要是有一个可视化的图标选择组件就好了,最好是直接选择图标,就能得到对应的 ...
随机推荐
- loj2472 「九省联考 2018」IIIDX
ref #include <algorithm> #include <iostream> #include <cstdio> using namespace std ...
- Eclipse 创建 Java 包---Eclipse教程第09课
打开新建 Java 包向导 你可以使用新建 Java 包向导来创建 Java 包.Java 包向导打开方式有: 通过点击 "File" 菜单并选择 New > Package ...
- 关于 Google Chrome “Your connection is not private” 问题的处理
今天下午访问google网站的时候,突然不能访问了,提示“Your connection is not private”(你的连接不是私密连接):查看XX-NET的设置,显示“请检查浏览器代理设置”. ...
- oracle数据库DB_NAME、DBID、DB_UNIQUE_NAME等的区别
目录 DB_NAME DBID DB_UNIQUE_NAME: INSTANCE_NAME: SID: SERVICE_NAME GLOBAL_DATABASE_NAME: DB_NAME ①是数据库 ...
- Jenkins拾遗--第三篇(用户权限管理)
采访过很多实用Jenkins的同学,发现Jenkins的安全是一个很薄弱的地方.很多公司用作生产部署的Jenkins安全管理都不是很规范,就更别提测试用的Jenkins了. 其实Jenkins是一个很 ...
- css改变光标
<span style="cursor:auto"> Auto</span><br /> <span style="cursor ...
- selenium获取浏览器控制台日志
public void logsTest(){ WebDriver driver = null; try { System.setProperty("webdriver.chrome.dri ...
- OpenStack-Ironic裸金属简介
一,Ironic简述 简而言之,OpenStack Ironic就是一个进行裸机部署安装的项目. 所谓裸机,就是指没有配置操作系统的计算机.从裸机到应用还需要进行以下操作: (1)硬盘RAID ...
- c++知识点总结--new的一些用法
new operator 将对象产生与heap,不但分配内存而且为该对象调用一个constructor operator new只是分配内存,没有constructor被调用 有个一个特殊版本,称 ...
- nagios原理及配置详解
1.Nagios如何监控Linux机器 NRPE总共由两部分组成:(1).check_nrpe插件,运行在监控主机上.服务器端安装详见:(2).NRPE daemon,运行在远程的linux主机上(通 ...