上效果
本树做一些限制,只能同级上下移。子级添加只能最大2级,限制判断用allow-drop事件
:allow-drop="allowDrop"
if (type == 'inner') return false //只能同级拖拽
if (draggingNode.level > categoryInfo.maxLevel) return false
html源码
<!DOCTYPE html>
<html lang="en">
<!--
* @Name: mallSalesReports.html
* @Description:
* @Author Lani
* @date 2024-02-28 18:32:36
-->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>商品类别</title>
<script src="../js/vue3.3.8/vue.global.js"></script>
<script src="../js/elementPlus/index.full.js"></script>
<link rel="stylesheet" href="../js/elementPlus/index.css">
<style>
.el-header {
height: 88px;
background-color: #fff;
z-index: 2;
}
#main-body .el-main {
padding: 0;
z-index: 1;
}
.el-aside {
display: flex;
justify-content: center;
align-items: center;
}
.el-tree .el-tree-node__content {
height: 50px !important;
border-top: 1px solid #aaa;
}
.tree {
border-bottom: 1px solid #aaa;
border-left: 1px solid #aaa;
border-right: 1px solid #aaa;
/*width: 600px;*/
}
.custom-tree-node1 {
display: flex;
flex: 1;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding: 8px;
height: 50px;
}
</style>
</head>
<body>
<div id="app" class="common-layout" v-cloak>
<el-affix :offset="0">
<el-row :gutter="0" style="border-bottom: 0px solid #d0d0d0;margin:0 20px;padding: 15px 0;background-color: #fff;">
<el-col :span="22">
<div class="flex-r">
<h4 class="menu-title-color fw7 fz22">商品销售报表</h4>
</div>
</el-col>
<el-col :span="2" style="text-align: end">
<el-button type="primary" size="large" @click="handleAddTopLevelNode">新增一级目录</el-button>
</el-col>
</el-row>
</el-affix>
<el-container>
<el-main id="main-body">
<el-tree
ref="menutree"
:allow-drop="allowDrop" :allow-drag="allowDrag"
:data="treeData" draggable
:expand-on-click-node="false"
default-expand-all node-key="id"
class="tree">
<template #default="{ node, data }">
<div class="custom-tree-node1">
<div>
<el-input v-if="data.isEdit" v-model="data.DICT_VALUE" maxlength="12" show-word-limit></el-input>
<el-text v-else="!node.isEdit">{{ data.DICT_VALUE }}</el-text>
</div>
<div style="margin-left: 40px;">
<el-button type="text" size="small" v-if="data.up"
@click="handleMoveUp(node, data, 'up')">上移<i class="el-icon-top"></i></el-button>
<el-button
type="text" size="small" v-if="data.down"
@click="handleMoveDown(node, data, 'down')">下移
<i class="el-icon-bottom"></i></el-button>
<el-button @click="append(node,data)" type="primary" v-if="data.subNode"> 添加子级</el-button>
<el-button style="margin-left: 18px" @click="edit(node, data)">
{{ data.operateBtnText }}
</el-button>
<el-button style="margin-left: 18px" @click="remove(node, data)" v-if="data.delete"> 删除</el-button>
</div>
</div>
</template>
</el-tree>
</el-main>
</el-container>
</div>
</body>
<script type="module">
import zhCn from "../js/elementPlus/locale/zh-cn.mjs";
const {createApp, ref, reactive, watch, toRaw, toRefs, shallowRef} = Vue
const _app = createApp({
setup() {
const categoryInfo = reactive({
maxLevel: 2,//类别层级最大2
treeData: [
{ //默认保留第一个节点数不能为空
label: '全部商品',
DICT_VALUE: '全部商品',
up: false, down: true, subNode: true, delete: false, operateBtnText: '修改',
id: 1,
children: [
{
label: 'Level two 1-1',
DICT_VALUE: 'Level two 1-1',
id: 2,
up: false, down: true, subNode: true, delete: true, operateBtnText: '修改',
},
],
},
{ //默认保留第一个节点数不能为空
label: '花吃了那女孩',
DICT_VALUE: '花吃了那女孩',
up: false, down: true, subNode: true, delete: false, operateBtnText: '修改',
id: 1,
children: [
{
label: 'Level two 1-1',
DICT_VALUE: 'Level two 1-1',
id: 2,
up: false, down: true, subNode: true, delete: true, operateBtnText: '修改',
},
],
},
],
id: 1
}
)
const payStatus = ref('')
const num = ref(20)
const timer = ref(null);
const clickCount = ref(0);
const locale = ref('')
const searchKeywords = ref('')
/*
* 判断结点拖拽
* */
const allowDrop = (draggingNode, dropNode, type) => {
// console.log('|--正在拖拽draggingNode,type', draggingNode, type)
// console.log('|--dropNode', dropNode)
if (type == 'inner') return false //只能同级拖拽
if (draggingNode.level > categoryInfo.maxLevel) return false
return true//允许拖拽
}
const allowDrag = (draggingNode) => {
return !draggingNode.data.label.includes('Level three 3-1-1')
}
const toast = (message, type = 'warning', fn = null) => {
ElementPlus.ElMessage({
message,
type, fn
})
}
/*
* 插入:$refs.menutree.insertBefore
* 删除:$refs.menutree.remove
* */
const handleMoveUp = (node) => { // 上移的原理就是现在选中节点上方复制一个一模一样的节点,然后删掉原来那个
const {$treeNodeId, ...newData} = node.data
console.log('|--选中结点', node.data, vm.$refs.menutree, $treeNodeId, newData, node.previousSibling.data.$treeNodeId)
if (vm.$refs.menutree) vm.$refs.menutree.insertBefore(newData, node.previousSibling)
// if (vm.$refs.menutree) vm.$refs.menutree.insertBefore(newData, node.previousSibling.data.$treeNodeId)
if (vm.$refs.menutree) vm.$refs.menutree.remove(node)
saveCategoryRequest()
}
const handleMoveDown = (node) => { // 下移的原理就是现在选中节点下方复制一个一模一样的节点,然后删掉原来那个
const {$treeNodeId, ...newData} = node.data
if (vm.$refs.menutree) vm.$refs.menutree.insertAfter(newData, node.nextSibling)
// if (vm.$refs.menutree) vm.$refs.menutree.insertAfter(newData, node.nextSibling.data.$treeNodeId)
if (vm.$refs.menutree) vm.$refs.menutree.remove(node)
saveCategoryRequest()
}
/*
* node: 当前节点,
* data: 当前节点
* */
const append = (node, data) => {//给当前节点,添加一个子节点
console.log('|--添加', node, data)
if (node.level >= categoryInfo.maxLevel) {
toast(`级别最大只能为${categoryInfo.maxLevel}`)
return
}
categoryInfo.id = categoryInfo.id + 1
const newChild = {
label: '二级类别 ',
id: categoryInfo.id,
children: [],
up: data.children.length > 0 ? true : false,
down: false,
subNode: false,
delete: true,
isEdit: true,
operateBtnText: '保存',
/*API字段*/
"DICT_SEQ": 0,
DICT_VALUE: '二级类别 ',
}
if (!data.children) {
data.children = []
}
data.children.push(newChild)
data.items = JSON.parse(JSON.stringify(data.children))
if (data.children.length > 1) {
data.children[data.children.length - 2].down = true
}
}
const handleAddTopLevelNode = () => { //添加一级类别
categoryInfo.id = categoryInfo.id + 1
let newTopLevelNode = {
id: categoryInfo.id,
label: '一级类别 ',
up: true, down: false, subNode: true, delete: true, isEdit: true, operateBtnText: '保存',
children: [],
items: [],
"DICT_SEQ": 0,
DICT_VALUE: '一级类别 ',
}
categoryInfo.treeData.push(newTopLevelNode)
if (categoryInfo.treeData.length > 1) {
categoryInfo.treeData[categoryInfo.treeData.length - 2].down = true
}
}
/*
* isAPI: true:用于请求掊口,删除不用字段, false 用于ui渲染增加一些字段
* */
const refreshTree = (data, isAPI = false) => {
for (let i = 0; i < data.length; i++) {
// data[i].id = data[i].DICT_SEQ
if (isAPI && data[i]) { // 上传修改
try {
delete data[i].label
delete data[i].up
delete data[i].down
delete data[i].subNode
delete data[i].delete
delete data[i].isEdit
delete data[i].operateBtnText
data[i].items = JSON.parse(JSON.stringify(data[i].children)) //同步items
if ((!data[i].children) || (data[i].children.length <= 0)) continue
data[i].items = refreshTree(data[i].items, isAPI)
delete data[i].children
} catch (e) {
}
continue
}
//渲染ui
data[i].label = data[i].DICT_VALUE
data[i].up = (i != 0)
data[i].down = (i != (data.length - 1))
data[i].subNode = true
data[i].delete = true
data[i].isEdit = false
categoryInfo.id = categoryInfo.id + 1
data[i].id = categoryInfo.id
data[i].operateBtnText = '修改'
data[i].children = []
if ((!data[i].items) || (data[i].items.length <= 0)) continue
data[i].children = refreshTree(data[i].items, isAPI)
// console.log(data[i].children)
}
return data
}
const saveCategoryRequest = async () => {
console.log('|-http 请求-')
toast('Http请求')
}
return {
...toRefs(categoryInfo),
num,
locale, searchKeywords,
clickCount, timer,
headerCellStyle: {borderTop: '2px solid #d0d0d0', background: '#f5f5f5', color: '#333', fontWeight: 500},
// 方法
allowDrag, allowDrop,
toast, append, handleMoveDown, handleMoveUp,
//移除节点
remove: (node, data) => {
console.log("|--del", node, data, node.data.$treeNodeId)
const {$treeNodeId} = node.data
console.log('|--', $treeNodeId, vm.$refs.menutree)
// return
if (categoryInfo.treeData.length <= 1) {
toast('至少保留一个结点')
return;
}
if (!vm.$refs.menutree) return
console.log('|--remove')
// vm.$refs.menutree.remove($treeNodeId)
vm.$refs.menutree.remove(node) //OK
// vm.$refs.menutree.remove(data)
saveCategoryRequest()
/* const parent = node.parent;
const children = parent.data.children || parent.data;
const index = children.findIndex((d) => d.id === data.id);
children.splice(index, 1);*/
}, //移除节点
edit: (node, data) => {
let btnText = '保存'
if (data.isEdit) {
saveCategoryRequest()
btnText = '修改'
}
data.isEdit = !data.isEdit
data.operateBtnText = btnText
},
handleAddTopLevelNode,
payStatusItemSelectedEvent: (item, index) => {
payStatus.value = item.item
},
refreshTree, saveCategoryRequest,
}
},
async mounted() {
},
})
_app.use(ElementPlus, {locale: zhCn})
const vm = _app.mount('#app')
</script>
</html>