1.结构分两块
<template>
<div style="height:96%;width:100%;max-width:1920px;max-height:1080px;background-color:white;padding:20px;display: flex;flex-direction:row; ">
<!-- 左侧树 -->
<div style="height:100%;width:32%;">
</div>
<!-- 右侧两个表 -->
<div style="height:100%;width:68%;" v-show="showTable">
</div>
</div>
</div>
</template>
2.左侧树写法(主要)
<!-- 左侧树 -->
<div style="height:100%;width:32%;">
<el-row style="height:30px">
<svg-icon icon-class="organizationIcon" />
<span class="ttac_model_title">{{$t('i18n.organization')}}</span>
</el-row>
<el-row style="overflow: hidden; width: 100%; bottom: 0px;height: calc(100% - 30px); ">
<div style="overflow: auto; width: 100%; height: 100%;" @contextmenu.prevent="onTreeRightClick">
<!--
node-key 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的
node-click 节点被点击时的回调
node-contextmenu 当某一节点被鼠标右键点击时会触发该事件
data 展示数据
default-expand-all 是否默认展开所有节点
node-drag-start 节点开始拖拽时触发的事件
node-drag-enter 拖拽进入其他节点时触发的事件
node-drag-leave 拖拽离开某个节点时触发的事件
node-drag-over 在拖拽节点时触发的事件(类似浏览器的 mouseover 事件)
node-drag-end 拖拽结束时(可能未成功)触发的事件
node-drop 拖拽成功完成时触发的事件
allow-drop 拖拽时判定目标节点能否被放置。type 参数有三种情况:'prev'、'inner' 和 'next',分别表示放置在目标节点前、插入至目标节点和放置在目标节点后
allow-drag 判断节点能否被拖拽
expand-on-click-node 是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。
draggable 是否开启拖拽节点功能
highlight-current 是否高亮当前选中节点,默认值是 false。
-->
<el-tree
node-key="id"
@node-click="showOrganizationInfo"
@node-contextmenu="onTreeNodeRightClick"
:data="treeData"
default-expand-all
@node-drag-start="handleDragStart"
@node-drag-enter="handleDragEnter"
@node-drag-leave="handleDragLeave"
@node-drag-over="handleDragOver"
@node-drag-end="handleDragEnd"
@node-drop="handleDrop"
:allow-drop="allowDrop"
:allow-drag="allowDrag"
:expand-on-click-node="false"
draggable
highlight-current
>
<div slot-scope="{node,data}">
<div v-if="data.type==='people'">
<btreeNode :value="data.label" style="margin-left: 3px; z-index: 1;" :nodeKey="data.id" :isUser="data.userId? true:false">
<template #icon>
<svg-icon icon-class="user1" />
</template>
</btreeNode>
</div>
<div v-if="data.type==='group'">
<btreeNode :value="data.label" style="margin-left: 3px; z-index: 1;" :nodeKey="data.id" :isUser="data.userId? true:false"
@onValueChanged="(id,title)=>onNodeNameChanged(id,title,data)" @onSureNodeName="(id,title)=>onSureNodeName(id,title,data)">
<template #icon>
<svg-icon icon-class="department" />
</template>
</btreeNode>
</div>
</div>
</el-tree>
<chooseUserDialog ref="chooseUserDialog" @onSure="onSelectPeople" />
<VueContextMenu id="context-menu" ref="ctxMenu" style="cursor: pointer">
<li v-for="(menu, index) in rightMenus" :key="index" class="ttactheme_hover" style="padding-left: 5px" @click="onRightMenuChoose(menu)">
<svg-icon :icon-class="getMenuIconByRightMenus(menu)" />
<span style="margin-left: 3px"> {{ menu }}</span>
</li>
</VueContextMenu>
</div>
</el-row>
</div>
data数据 :
//获取tree的数据
private async initData() {
//所有数据
let res = await GetAllOranizationInfo()
//处理成树形
this.treeData = this.organData(res, null)
}
处理tree的数据结构,展示树形
private organData(allData: any[], topparentId: string): TreeNodeDC[] {
let res: TreeNodeDC[] = []
// 通过使用filter方法,从res数据中筛选出 用户的parentId 等于 大组织id的数据 , 并将结果保存在filters数组中。
//第一次传入的是null,匹配第一个大的组织
let filters = allData.filter((o) => o.parentId === topparentId)
console.log('filter之后的res,', filters)
//对匹配上的数据进行循环操作
for (let index = 0; index < filters.length; index++) {
//把值放到node里形成新的对象
const element = filters[index]
//默认组织,因为第一个开头传的null,第一次匹配到的就是最大的组织
let node: TreeNodeDC = {
id: element.id,
userId: element.userId,
label: element.name,
parentGroupId: element.parentId,
index: element.index,
children: [],
type: 'group'
}
//type==0是组织(group),type==1是用户(people)
if (element.type === 1) {
node.type = 'people'
}
//递归,调用自身方法,传入 第1(n)次获取到的数据的id,然后走流程
let nodeChildren = this.organData(allData, node.id)
console.log('nodeChildren,', nodeChildren, allData, node.id)
// 通过使用filter方法,从res数据中筛选出 小用户的parentId 等于 大组织id的数据 , 并将结果保存在filters数组中
//这次的就是子级,用户+组织的形式
//对匹配上的数据进行循环操作
//排序
node.children = nodeChildren.sort(function(a, b) {
return a.index - b.index
})
//返回node数组
res.push(node)
}
return res
}
2.1点击用户 / 组织
noadeList数据:
// 点击node触发的方法
private showOrganizationInfo(nodeList) {
let idList = []
// 如果点击的node没有子节点,但是有用户id,说明是用户
if (nodeList.children.length == 0 && nodeList.userId != null) {
//点击的id放到idList
idList = [nodeList.userId]
} else {
// 否则点击的是组织
idList = this.getChildIds(nodeList.children)
}
//如果没有点到数据
if (idList.length == 0) {
this.showTable = false
this.userIds = []
} else {
//打开table显示
this.showTable = true
//没什么作用,赋值方便后续
this.userIds = idList
}
}
//点击组织的处理方法
//递归当前节点下及其子节点的数据,获取单个组织下包括所有id的集合,返回出来
private getChildIds(nodeChildList) {
let userIds = []
let childUserIds = []
nodeChildList.forEach((item) => {
userIds.push(item.userId)
if (item.children.length > 0) {
childUserIds = this.getChildIds(item.children) as any
}
})
return [...userIds, ...childUserIds].filter((v) => v)
}
//节点 拖拽开始 / 进入其他节点 / 拖拽离开 / 在拖拽节点时触发的事件(类似浏览器的 mouseover 事件) / 拖拽结束时(可能未成功)触发的事件
// ***************树********************************************
private handleDragStart(node, ev) {
// console.log('drag start', node)
}
private handleDragEnter(draggingNode, dropNode, ev) {
// console.log('tree drag enter: ', dropNode.label)
}
private handleDragLeave(draggingNode, dropNode, ev) {
// console.log('tree drag leave: ', dropNode.label)
}
private handleDragOver(draggingNode, dropNode, ev) {
// console.log('tree drag over: ', dropNode.label)
}
private handleDragEnd(draggingNode, dropNode, dropType, ev) {
// console.log('tree drag end: ', dropNode && dropNode.label, dropType)
}
3.右击tree的节点发生的事件,阻止冒泡和默认事件,然后启用vue-contextMenu的方法
<VueContextMenu id="context-menu" ref="ctxMenu" style="cursor: pointer">
//rightMenus是根据右键点击的是 组织 还是 用户 来控制显示的是什么
//图标根据rightMenus的数据来决定,switch方法匹配
<li v-for="(menu, index) in rightMenus" :key="index" class="ttactheme_hover" style="padding-left: 5px" @click="onRightMenuChoose(menu)">
<svg-icon :icon-class="getMenuIconByRightMenus(menu)" />
<span style="margin-left: 3px"> {{ menu }}</span>
</li>
</VueContextMenu>
//枚举 i18里的数据
enum rightMenus {
addPeople = 'i18n.rightMenu_addPeople',
addGroup = 'i18n.rightMenu_addCombination',
saveAs = 'i18n.rightMenu_saveAs',
rename = 'i18n.rightMenu_rename',
edit = 'i18n.rightMenu_edit',
delete = 'i18n.rightMenu_delete'
}
右键打开面板 展示哪些数据 的方法:
//右击菜单树,就是右击空白地方
private onTreeRightClick(e: any) {
//window.console.log('右击菜单树', e)
e.stopPropagation()
e.preventDefault()
this.currentRightClickNode = null
this.rightMenus = []
this.rightMenus = [this.$t(rightMenus.addGroup).toString()]
;(this.$refs.ctxMenu as any).open()
}
//右击菜单树节点
//el-tree上有个@node-contextmenu="onTreeNodeRightClick"
private onTreeNodeRightClick(e: any, data: any) {
// window.console.log('右击菜单树节点的节点数据', data)
//禁止事件冒泡
e.stopPropagation()
//阻止默认事件执行
e.preventDefault()
//节点数据
this.currentRightClickNode = data
//清空数组
this.rightMenus = []
//如果点的用户
if (data.type === 'people') {
//只让 枚举数组里 放删除
this.rightMenus = [this.$t(rightMenus.delete).toString()]
} else if (data.type === 'group') {
//添加部门的文字
this.rightMenus = [this.$t(rightMenus.addGroup).toString(),
//添加人员的文字
this.$t(rightMenus.addPeople).toString(),
//添加删除的文字
this.$t(rightMenus.delete).toString()]
}
//通过$refs使用open方法打开框子
;(this.$refs.ctxMenu as any).open()
}
//展示图标的方法
private getMenuIconByRightMenus(rm: rightMenus): string {
let iconString
switch (rm) {
case this.$t(rightMenus.addPeople).toString():
iconString = 'rightmenu_add'
break
case this.$t(rightMenus.addGroup).toString(): {
iconString = 'rightmenu_add'
break
}
case this.$t(rightMenus.edit).toString():
iconString = ''
break
case this.$t(rightMenus.saveAs).toString(): {
iconString = ''
break
}
case this.$t(rightMenus.rename).toString(): {
iconString = ''
break
}
case this.$t(rightMenus.delete).toString(): {
iconString = 'rightmenu_delete'
break
}
default:
break
}
return iconString
}
//点击menu的数据,进行增删改查的操作
private async onRightMenuChoose(menuName: string) {
//window.console.log('onRightMenuChoose:', menuName)
switch (menuName) {
case this.$t(rightMenus.addPeople).toString():
this.addPeople()
break
case this.$t(rightMenus.addGroup).toString():
this.addGroup()
break
case this.$t(rightMenus.delete).toString():
this.deleteNode()
this.onDeletePage(this.currentRightClickNode.id)
break
case this.$t(rightMenus.edit).toString():
this.onEditPage(this.currentRightClickNode.id)
break
default:
break
}
}
4.拖拽成功时触发的事件,基本就是调用接口了
private async handleDrop(draggingNode, dropNode, dropType, ev) {
// ****************************************
let parentGroupId = null
if (dropType === 'inner') {
parentGroupId = dropNode.data.id
} else if (dropType === 'before') {
parentGroupId = dropNode.data.parentGroupId
} else if (dropType === 'after') {
parentGroupId = dropNode.data.parentGroupId
}
let dragingNodeData = this.searchNodeById(draggingNode.data.id, this.treeData)
dragingNodeData.parentGroupId = parentGroupId
if (dragingNodeData.type === 'people') {
let data = this.makeDesignPageDCByNode(dragingNodeData)
await AddOrUpdateOranizationInfo(data)
} else if (dragingNodeData.type === 'group') {
let data = this.makeDesignPageGroupDCByNode(dragingNodeData)
await AddOrUpdateOranizationInfo(data)
}
// ****************************************
let needOrderNodes = []
if (parentGroupId) {
let parentNodeData = this.searchNodeById(parentGroupId, this.treeData)
needOrderNodes = parentNodeData.children
} else {
needOrderNodes = this.treeData
}
for (let index = 0; index < needOrderNodes.length; index++) {
needOrderNodes[index].index = index
}
let updateIndexData: any[] = []
for (let index = 0; index < needOrderNodes.length; index++) {
const element = needOrderNodes[index]
updateIndexData.push({
oranizationId: element.id,
index: element.index
})
}
window.console.log('needOrderNodes:', updateIndexData)
await UpdateOranizationSort(needOrderNodes)
}