<template>
<div class="department-box">
<div class="custom-tree-container">
<el-tooltip
class="box-item"
effect="dark"
content="按住鼠标即可拖动"
placement="top"
>
<el-tree
:data="dataSource"
node-key="id"
default-expand-all
:expand-on-click-node="false"
draggable
@node-drag-start="handleDragStart"
@node-drag-enter="handleDragEnter"
@node-drag-leave="handleDragLeave"
@node-drag-over="handleDragOver"
@node-drag-end="handleDragEnd"
@node-drop="handleDrop"
>
<template #default="{ node, data }">
<div class="custom-tree-node">
<span>{{ node.label }}</span>
<div class="handle-box">
<div link @click="append(data)" class="del-icon">
增加
</div>
<div
style="margin-left: 4px"
type="danger"
link
@click="handleNodeClick(node, data)"
class="del-icon"
>
编辑
</div>
<el-popconfirm
class="box-item"
title="是否删除该部门"
placement="right"
@confirm="remove(node, data)"
>
<template #reference>
<div style="margin-left: 4px" link class="del-icon">
删除
</div>
</template>
</el-popconfirm>
</div>
</div>
</template>
</el-tree>
</el-tooltip>
</div>
<div class="department-edit-box">
<el-form>
<el-form-item label="部门名称">
<el-input v-model="editData" />
</el-form-item>
</el-form>
<el-button type="primary" @click="saveEdit">保存</el-button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { ElButton, ElMessage } from 'element-plus';
let id = 1000;
// 当前编辑的节点
const currentNode = ref(null);
const editData = ref('');
const editDataId = ref('');
/**
* 处理节点点击事件
* @param {Object} node - 节点对象
* @param {Object} data - 节点数据
*/
const handleNodeClick = (node, data) => {
currentNode.value = node;
editData.value = node.label;
editDataId.value = data.id;
};
/**
* 保存编辑内容
*/
const saveEdit = () => {
if (!currentNode.value) {
ElMessage.warning('请先选择要编辑的节点');
return;
}
if (!editData.value.trim()) {
ElMessage.warning('部门名称不能为空');
return;
}
// 同时更新原始数据源中的对应节点
findAndUpdateLabel(dataSource.value,editDataId.value,editData.value);
};
const findAndUpdateLabel = (nodes, id, newLabel) => {
for (const node of nodes) {
if (node.id === id) {
node.label = newLabel; // 修改标签
return true;
}
if (node.children && node.children.length > 0) {
if (findAndUpdateLabel(node.children, id, newLabel)) {
return true;
}
}
}
return false;
};
/**
* 处理节点开始拖拽事件
* @param {Object} node - 当前被拖拽的节点
* @param {Event} ev - 拖拽事件对象
*/
const handleDragStart = (node, ev) => {
console.log('drag start', node);
};
/**
* 处理节点进入可放置区域事件
* @param {Object} draggingNode - 当前被拖拽的节点
* @param {Object} dropNode - 进入的可放置节点
* @param {Event} ev - 拖拽事件对象
*/
const handleDragEnter = (draggingNode, dropNode, ev) => {
console.log('tree drag enter:', dropNode.label);
};
/**
* 处理节点离开可放置区域事件
* @param {Object} draggingNode - 当前被拖拽的节点
* @param {Object} dropNode - 离开的可放置节点
* @param {Event} ev - 拖拽事件对象
*/
const handleDragLeave = (draggingNode, dropNode, ev) => {
console.log('tree drag leave:', dropNode.label);
};
/**
* 处理节点在可放置区域上方移动事件
* @param {Object} draggingNode - 当前被拖拽的节点
* @param {Object} dropNode - 上方的可放置节点
* @param {Event} ev - 拖拽事件对象
*/
const handleDragOver = (draggingNode, dropNode, ev) => {
console.log('tree drag over:', dropNode.label);
};
/**
* 处理拖拽结束事件
* @param {Object} draggingNode - 当前被拖拽的节点
* @param {Object} dropNode - 放置的目标节点
* @param {string} dropType - 放置类型('before'|'after'|'inner')
* @param {Event} ev - 拖拽事件对象
*/
const handleDragEnd = (draggingNode, dropNode, dropType, ev) => {
console.log('tree drag end:', dropNode && dropNode.label, dropType);
};
/**
* 处理节点放置事件
* @param {Object} draggingNode - 当前被拖拽的节点
* @param {Object} dropNode - 放置的目标节点
* @param {string} dropType - 放置类型('before'|'after'|'inner')
* @param {Event} ev - 拖拽事件对象
*/
const handleDrop = (draggingNode, dropNode, dropType, ev) => {
console.log('tree drop:', dropNode.label, dropType);
};
/**
* 添加子节点
* @param {Object} data - 当前节点数据
*/
const append = (data) => {
const newChild = { id: id++, label: '新节点', children: [] };
if (!data.children) {
data.children = [];
}
data.children.push(newChild);
dataSource.value = [...dataSource.value];
};
/**
* 删除节点
* @param {Object} node - 当前节点对象
* @param {Object} data - 当前节点数据
*/
const remove = (node, data) => {
const parent = node.parent;
const children = parent.data.children || parent.data;
const index = children.findIndex((d) => d.id === data.id);
children.splice(index, 1);
dataSource.value = [...dataSource.value];
};
// 树形数据
const dataSource = ref([
{
id: 1,
label: '总部',
children: [
{
id: 10,
label: '人力资源部',
children: [
{
id: 101,
label: '招聘组',
},
{
id: 102,
label: '培训与发展组',
},
{
id: 103,
label: '薪酬福利组',
},
],
},
{
id: 11,
label: '财务部',
children: [
{
id: 111,
label: '会计组',
},
{
id: 112,
label: '审计组',
},
],
},
],
},
{
id: 2,
label: '技术研发中心',
children: [
{
id: 20,
label: '前端开发部',
children: [
{
id: 201,
label: 'Web组',
},
{
id: 202,
label: '移动端组',
},
],
},
{
id: 21,
label: '后端开发部',
},
{
id: 22,
label: '测试部',
},
{
id: 23,
label: '产品设计部',
},
],
},
{
id: 3,
label: '市场运营中心',
children: [
{
id: 30,
label: '市场营销部',
},
{
id: 31,
label: '客户服务部',
},
{
id: 32,
label: '品牌公关部',
},
],
},
{
id: 4,
label: '区域分公司',
children: [
{
id: 40,
label: '华东分公司',
children: [
{
id: 401,
label: '上海办事处',
},
{
id: 402,
label: '杭州办事处',
},
],
},
{
id: 41,
label: '华南分公司',
},
{
id: 42,
label: '华北分公司',
},
],
},
]);
</script>
<style>
.department-box {
position: relative;
padding-top: 33px;
display: flex;
justify-content: space-between;
gap: 130px;
}
.custom-tree-container {
flex: 1;
padding: 20px;
max-width: 400px;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 15px !important;
padding: 10px 0;
}
.custom-tree-node span {
cursor: pointer;
padding: 2px 5px;
border-radius: 3px;
}
.custom-tree-node span:hover {
background-color: #f5f5f5;
}
.handle-box {
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
}
.del-icon img {
width: 15px;
height: 15px;
}
.department-edit-box {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
padding: 20px;
flex: 1;
height: 400px;
/* box-shadow: 0 0px 2px #e5e5e5; */
border-radius: 5px;
}
body .el-tree-node__content .el-tree-node__expand-icon {
box-sizing: content-box;
padding: 6px 0 !important;
}
body .el-popconfirm__icon {
display: none !important;
}
body .el-popconfirm__main {
justify-content: center;
}
body .el-popconfirm__action{
display: flex;
justify-content: center;
}
</style>