实现了树形菜单的拖动排序、新增、删除、修改和查询功能。其中使用了vue3、elementplus、ts和vite,需要提前安装,另外,这里因使用了unplugin-auto-import和unplugin-vue-components两个插件,所以能自动选择性导入elementplus元素,实际上可直接使用按需导入框架(链接地址)即可。
<!--suppress ALL -->
<template>
<el-input v-model="filterText" placeholder="输入查找菜单关键词" />
<el-tree
ref="treeRef"
class="filter-tree"
:filter-node-method="filterNode"
:props="defaultProps"
:allow-drop="allowDrop"
:allow-drag="allowDrag"
:data="dataSource"
:render-content="renderContent"
:expand-on-click-node="false"
draggable
default-expand-all
node-key="id"
@node-drag-start="handleDragStart"
@node-drag-enter="handleDragEnter"
@node-drag-leave="handleDragLeave"
@node-drag-over="handleDragOver"
@node-drag-end="handleDragEnd"
@node-drop="handleDrop"
highlight-current
check-on-click-node
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<span>{{ node.label }}</span>
<span>
<a @click="append(data)"> 添加 </a>
<a style="margin-left: 8px" @click="remove(node, data)"> 删除 </a>
</span>
</span>
</template>
</el-tree>
</template>
<script lang="ts" setup>
import type Node from "element-plus/es/components/tree/src/model/node";
import type { DragEvents } from "element-plus/es/components/tree/src/model/useDragNode";
import type {
AllowDropType,
NodeDropType,
} from "element-plus/es/components/tree/src/tree.type";
import { ref, watch } from "vue";
import { ElMessageBox } from "element-plus";
// import Modal from '@/component/Modal.vue'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const handleDragStart = (node: Node, ev: DragEvents) => {
console.log("drag start", node);
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const handleDragEnter = (
draggingNode: Node,
dropNode: Node,
ev: DragEvents
) => {
console.log("tree drag enter:", dropNode.label);
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const handleDragLeave = (
draggingNode: Node,
dropNode: Node,
ev: DragEvents
) => {
console.log("tree drag leave:", dropNode.label);
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const handleDragOver = (draggingNode: Node, dropNode: Node, ev: DragEvents) => {
console.log("tree drag over:", dropNode.label);
};
const handleDragEnd = (
draggingNode: Node,
dropNode: Node,
dropType: NodeDropType,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
ev: DragEvents
) => {
console.log("tree drag end:", dropNode && dropNode.label, dropType);
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const handleDrop = (
draggingNode: Node,
dropNode: Node,
dropType: NodeDropType,
ev: DragEvents
) => {
console.log("tree drop:", dropNode.label, dropType);
};
const allowDrop = (draggingNode: Node, dropNode: Node, type: AllowDropType) => {
if (dropNode.data.label === "二级 3-1") {
return type !== "inner";
}
return true;
};
const allowDrag = (draggingNode: Node) => {
return !draggingNode.data.label.includes("三级 3-1-1");
};
// 内部调用
interface Tree {
id: number;
label: string;
children?: Tree[];
}
// 初始化id
let id = 1000;
// 没什么用不定义无问题
const defaultProps = {
children: "children",
label: "label",
};
const filterText = ref("");
const treeRef = ref<InstanceType<any>>();
// 监听查找框变化
watch(filterText, (val) => {
treeRef.value!.filter(val);
});
// 查找框数据 这里 data原来是Tree,在vscode中报错现在更改为any正常
const filterNode = (value: string, data: any) => {
if (!value) return true;
return data.label.includes(value);
};
// 添加
const append = (data: Tree) => {
const newChild = { id: id++, label: "New node", children: [] };
if (!data.children) {
data.children = [];
}
data.children.push(newChild);
dataSource.value = [...dataSource.value];
};
// 删除
const remove = (node: Node, data: Tree) => {
const parent = node.parent;
const children: Tree[] = parent.data.children || parent.data;
const index = children.findIndex((d) => d.id === data.id);
children.splice(index, 1);
dataSource.value = [...dataSource.value];
};
// 编辑(data.label="赋值")
const edit = (data: Tree) => {
ElMessageBox.prompt("请输入标题", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
customClass: "my-message-box",
})
.then(({ value }) => {
if (value !== null) {
data.label = value;
}
})
.catch(() => {
// 处理取消按钮的点击事件
});
};
// 更新页面
const renderContent = (
h: any,
{
node,
data,
store,
}: {
node: Node;
data: Tree;
store: Node["store"];
}
) => {
return h(
"span",
{
class: "custom-tree-node",
},
h("span", null, node.label),
h(
"span",
null,
h(
"a",
{
style: "margin-left: 58px",
onClick: () => edit(data),
},
"编辑 "
),
h(
"a",
{
style: "margin-left: 8px",
onClick: () => append(data),
},
"添加 "
),
h(
"a",
{
style: "margin-left: 8px",
onClick: () => remove(node, data),
},
"删除 "
)
)
);
};
// 初始数据
const dataSource = ref<Tree[]>([
{
id: 1,
label: "一级 1",
children: [
{
id: 2,
label: "二级 1-1",
children: [
{
id: 3,
label: "三级 1-1-1",
},
],
},
],
},
{
id: 4,
label: "一级 2",
children: [
{
id: 5,
label: "二级 2-1",
children: [
{
id: 6,
label: "三级 2-1-1",
},
],
},
{
id: 7,
label: "二级 2-2",
children: [
{
id: 8,
label: "三级 2-2-1",
},
],
},
],
},
{
id: 9,
label: "一级 3",
children: [
{
id: 10,
label: "二级 3-1",
children: [
{
id: 11,
label: "三级 3-1-1",
},
],
},
{
id: 12,
label: "二级 3-2",
children: [
{
id: 13,
label: "三级 3-2-1",
},
],
},
],
},
]);
</script>
<style scoped>
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 12px;
padding-right: 8px;
}
.filter-tree {
font-size: 16px;
}
.my-message-box .el-message-box__wrapper {
width: 500px;
height: 300px;
}
</style>
注意:ElMessageBox、ElMenu等如果在.eslintrc-auto-import.json或components.d.ts中全局引入则不要再像上面代码中那样再次引入,否则二次重复引入将会显示不正常。
另附一例:下面是vue3+typescript+scss简单的拖曳排序代码
<template>
<div class="main">
<div
class="item"
v-for="item in dataList"
:key="item"
draggable="true"
@dragstart="dragstart(item)"
@dragenter="dragenter(item)"
@dragend="dragend(item)"
>
{{ item }}
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const dataList = ref<string[]>([
'1第一条数据',
'2第二条数据',
'3第三条数据',
'4第四条数据',
'5第五条数据'
])
const clickVal = ref<string>()
const moveVal = ref<string>()
const endVal = ref<string>()
const dragstart = (item: string): void => {
clickVal.value = item
}
const dragenter = (item: string): void => {
moveVal.value = item
}
const dragend = (item: string): void => {
endVal.value = item
let index = dataList.value.indexOf(item) //移动元素的当前位置
let moveObj = moveVal.value ? moveVal.value : ''
let addIndex = dataList.value.indexOf(moveObj) //要移动后的位置
dataList.value.splice(index, 1)
dataList.value.splice(addIndex, 0, item)
}
</script>
<style lang="scss" scoped>
.main {
margin-left: 10px;
margin-top: 50px;
display: flex;
.item {
cursor: pointer;
margin-bottom: 10px;
width: 300px;
text-align: center;
height: 60px;
line-height: 60px;
background-color: cadetblue;
color: #fff;
margin-left: 50px;
}
}
</style>