element树形结构新建编辑节点组件
el-tree 只要数据结构对就能直接用的组件
功能:
1.可新建最外层一级节点
2.可编辑删除每一级节点
3.可在每一级节点下新增子节点(添加下级)
4.可拖动排序,但只能平级拖动(为了防止拖动乱了的情况)
5.可搜索
6.初始化折叠状态,逐个打开编辑后,保持当前节点打开状态
效果如图:
(附赠数据格式)
代码如下:
父组件引用:
<template> <div class="view-tree"> <!-- 树组件 --> <leftTree :treeData="treeData" @handleTreeClick="handleTreeClick" ></leftTree> </div> </template> <script> import { treeListApi } from '@/http/thematicLibrary'; import leftTree from './components/leftTree'; export default { components: { leftTree, }, data() { return { treeData: [], // 左侧菜单树信息 orgId: '', //组织机构id departTitle: '' //左侧状态选择的值 }; }, created() { this.getlistList(); //获取目录 }, methods: { //获取目录 getlistList() { treeListApi().then(res => { if (res.data.code === 200) { this.treeData = res.data.data; let route = this.$route.query.columnId; if (route) { this.orgId = route; } else { if (this.treeData.length > 0) { this.orgId = this.treeData[0].id; } } } }); }, // 树点击事件 handleTreeClick(data) { this.orgId = data.id; this.departTitle = data.name; } } }; </script>
树子组件:
<template> <div class="tree-box"> <el-input v-model.trim="searchValue" class="input-search" placeholder="搜索" clearable /> <div class="group-wrap"> <el-button type="info" size="small" @click="addEditFn('新建一级节点', null)" > <i class="iconfont icon_a-zu66381"></i> 新建一级节点</el-button > </div> <el-tree ref="tree" class="filter-tree" :data="filterList" :props="defaultProps" :check-strictly="true" :default-expand-all="expand" :filter-node-method="filterNode" node-key="id" :check-on-click-node="true" :current-node-key="currentValue" :expand-on-click-node="false" :highlight-current="true" @node-click="handleNodeClick" :default-expanded-keys="keys" draggable @node-drop="handleDrop" :allow-drop="allowDrop" > <span class="ltree-content-item" slot-scope="{ data }"> <span class="ltree-content-name">{{ data.name }}</span> <el-popover placement="bottom" popper-class="thematic_popper" trigger="click" @show="handleStatusWarn(data.id)" v-has="'labelManagement/labelEdit'" > <ul class="popover-wrap" v-if="popId == data.id"> <li @click="addEditFn('编辑', data)">编辑</li> <li @click="deleteFn(data.id)" v-if="data.pid !== '-1'">删除</li> <li @click="addEditFn('添加下级', data)">添加下级</li> </ul> <div slot="reference"> <i slot="reference" class="icon_a-lujing4703 iconfont ltree-content-icon" ></i> </div> </el-popover> </span> </el-tree> <!-- 弹窗 --> <el-dialog :title="title" :visible.sync="dialogVisible" width="520px" @open="openTagFn" @close="closeTagFn" > <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="search-form" > <el-form-item v-if="typeTitle == '添加下级' || ruleForm.pid != ''" label="所属上级" prop="pName" class="work-item-label" > <el-input v-model="ruleForm.pName" disabled></el-input> </el-form-item> <el-form-item label="节点名称" prop="name" class="work-item-label"> <el-input v-model="ruleForm.name" maxlength="20" placeholder="请输入节点名称" ></el-input> </el-form-item> </el-form> <span slot="footer" class="dialog-footer"> <el-button size="mini" type="default" @click="dialogVisible = false" >取 消</el-button > <el-button size="mini" type="info" @click.prevent="submit('ruleForm')" >确 定</el-button > </span> </el-dialog> </div> </template> <script> import { treeListApi, addApi, editApi, delApi, detailApi, updateSortApi } from '@/http/thematicLibrary'; import CircularJSON from 'circular-json'; export default { props: { treeData: Array }, data() { return { keys: [], defaultProps: { children: 'children', label: 'name', value: 'id' }, popId: '', expand: false, //默认展开 searchValue: '', //搜索的 currentValue: '', //高亮的 filterList: [], title: '', //弹窗标题 dialogVisible: false, pidList: [], //新建一级节点列表 ruleForm: { name: '', id: '', level: '', pName: '', //父名称 pid: '' }, typeTitle: '', //弹窗标题区别 typeFlag: 'add', //编辑还是删除区别 rules: { name: [{ required: true, message: '请输入节点名称', trigger: 'blur' }] } }; }, watch: { treeData(val) { this.$nextTick(() => { this.filterList = val; if (this.filterList.length > 0) { console.log(this.$route.query.columnId, 'columnId'); console.log(this.$route.query.departName, 'departName'); this.currentValue = this.$route.query.columnId ? this.$route.query.columnId : this.filterList[0].id; this.$nextTick(() => { this.$refs.tree.setCurrentKey(this.currentValue); //一定要加这个选中了否则样式没有出来 }); } }, 500); }, searchValue(val) { this.$refs.tree.filter(val); this.getFilterData(); } }, mounted() { this.$nextTick(() => { setTimeout(() => { if (this.treeData.length > 0) { this.currentValue = this.$route.query.columnId ? this.$route.query.columnId : this.treeData[0].id; this.$refs.tree.setCurrentKey(this.currentValue); } }, 500); }); }, methods: { //拖动后的回调函数 handleDrop(draggingNode, dropNode, dropType, ev) { let params = []; if (draggingNode.data.pid == '-1') { params = this.filterList; } else { this.filterList.map((v) => { abc(v); }); } //循环 function abc(item) { if (item.id == draggingNode.data.pid) { params = item.children; } else { item.children.map((val) => { abc(val); }); } } updateSortApi({ thematicLibraryUpdateDTOList: params }).then((res) => { if (res.data.code == 200) { this.$message({ type: 'success', message: '排序成功!' }); this.getList(); this.expand = false; } }); }, //哪些不能拖动 allowDrop(draggingNode, dropNode, type) { if (draggingNode.data.pid == dropNode.data.pid && type != 'inner') { return true; } else { return false; } }, //获取列表树 getList() { treeListApi().then((res) => { if (res.data.code === 200) { this.filterList = res.data.data; setTimeout(() => { let route = this.$route.query.columnId; this.currentValue = route; this.$refs.tree.setCurrentKey(this.currentValue); }, 500); } }); }, //新增或编辑 addEditFn(type, data) { console.log(data, 'dataaaa'); this.typeTitle = type; this.title = type; if (type == '添加下级') { this.ruleForm.pName = data.name; this.ruleForm.pid = data.id; this.ruleForm.level = Number(data.level) + 1; this.ruleForm.name = ''; this.ruleForm.id = ''; } else if (type == '新建一级节点') { this.ruleForm.pName = ''; this.ruleForm.pid = ''; this.ruleForm.level = 0; this.ruleForm.name = ''; this.ruleForm.id = ''; } else { if (data.level == 0) { this.ruleForm.pName = ''; this.ruleForm.pid = ''; this.ruleForm.level = 1; this.ruleForm.name = data.name; this.ruleForm.id = data.id; } else { detailApi(data.id).then((res) => { if (res.data.code == 200) { this.ruleForm.pName = res.data.data.pidName; this.ruleForm.pid = res.data.data.pid; this.ruleForm.level = Number(data.level) + 1; this.ruleForm.name = data.name; this.ruleForm.id = data.id; } }); } } this.dialogVisible = true; }, // 提交 submit(formName) { this.$refs[formName].validate((valid) => { if (valid) { let data = {}; if (this.typeTitle == '添加下级') { data = { pid: this.ruleForm.pid, name: this.ruleForm.name, level: this.ruleForm.level }; addApi(data).then((res) => { if (res.data.code == 200) { this.$message({ type: 'success', message: '添加成功!' }); this.getList(); this.keys = res.data.data this.dialogVisible = false; } }); } else if (this.typeTitle == '新建一级节点') { data = { level: '0', pid: '-1', name: this.ruleForm.name }; addApi(data).then((res) => { if (res.data.code == 200) { this.$message({ type: 'success', message: '添加成功!' }); this.getList(); this.dialogVisible = false; } }); } else { data = { id: this.ruleForm.id, name: this.ruleForm.name }; editApi(data).then((res) => { if (res.data.code == 200) { this.$message({ type: 'success', message: '修改成功!' }); this.getList(); this.dialogVisible = false; } }); } } else { return false; } }); }, //打开弹窗 openTagFn() {}, //关闭 closeTagFn() { this.$refs['ruleForm'].resetFields(); }, //关闭其他popover handleStatusWarn(id) { this.popId = id; }, //点击节点 handleNodeClick(data) { this.popId = data.id; if (data.id === this.$route.query.columnId) return; this.$emit('handleTreeClick', data); this.$router.push({ query: { columnId: data.id, departName: data.name } }); }, //删除 deleteFn(id) { this.$confirm('是否删除此条数据?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { delApi(id).then((res) => { if (res.data.code === 200) { this.$message({ type: 'success', message: '删除成功!' }); this.getList(); } }); }); }, //过滤后的数据 getFilterData() { if (this.searchValue) { const rootData = this.$refs.tree.root; if (rootData.visible) { const childNodesStr = CircularJSON.stringify(rootData.childNodes); const childNodes = CircularJSON.parse(childNodesStr); this.filterData = this.recursionNodes(childNodes); this.handleNodeClick(this.filterData[0]); this.currentValue = this.filterData[0].id; this.$nextTick(() => { this.$refs.tree.setCurrentKey(this.currentValue); //一定要加这个选中了否则样式没有出来 }); } else { console.log('暂无数据'); } } }, //递归遍历数据 recursionNodes(childNodes) { const nodes = childNodes; const result = []; for (const item of nodes) { if (item.visible) { result.push(item.data); } if (item.childNodes && item.childNodes.length) { const tempResult = this.recursionNodes(item.childNodes); item.data.children = tempResult; } } return result; }, //搜索 filterNode(value, data, node) { if (!value) return true; let parentNode = node.parent, labels = [node.label], level = 1; while (level < node.level) { labels = [...labels, parentNode.label]; parentNode = parentNode.parent; level++; } return labels.some((label) => label.indexOf(value) !== -1); } } }; </script> <style lang="scss" scoped> .input-search { width: 280px; margin: 5px 20px 15px 20px; /deep/ .el-input__suffix { margin-top: -4px; } } /deep/.el-input__inner { background: #f4f6fc; border-radius: 6px; color: #6b7177; border: none; } .tree-box { width: 100%; height: calc(100% - 50px); // 添加按钮 .group-wrap { display: block; padding: 0 20px !important; margin-bottom: 10px; box-sizing: border-box; /deep/ .el-button { height: 30px; display: inline-block; padding: 0px 11px !important; font-size: 12px !important; .iconfont { font-size: 12px; margin: 0 !important; } } /deep/ .el-button:hover { opacity: 0.8; } } .el-tree { background: #fff; min-width: 100%; display: inline-block !important; box-sizing: border-box; padding: 0 10px; } // 添加下级按钮 .ltree-content-item { // width: 150px; height: 40px; line-height: 40px; padding: 0 28px 0 0px; box-sizing: border-box; display: flex; justify-content: space-between; cursor: pointer; .ltree-content-name { font-size: 13px; font-family: Microsoft YaHei; font-weight: 400; color: #6b7177; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .ltree-content-icon { width: 20px; color: #6b7177; font-size: 10px; cursor: pointer; display: none; } } .ltree-content-item:hover { .ltree-content-name { color: #0069ba; } .ltree-content-icon { display: block; } } } ::v-deep .el-tree-node__content { height: 40px; &:hover { background: #f4f6fc; .el-tree-node__label { color: #0069ba; } .ltree-content-icon { display: block; } } .el-tree-node__label { color: #6b7177; // max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } } ::v-deep .el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content { background: #f4f6fc; .el-tree-node__label { color: #0069ba; } .ltree-content-name { color: #0069ba; } .ltree-content-icon { display: block; } } /deep/ .el-dialog .el-form-item__content { margin-left: 10px !important; .el-select { width: 100%; } } .popover-wrap { width: 70px; border-radius: 6px; li { font-size: 12px; font-family: Microsoft YaHei; font-weight: 400; line-height: 35px; color: #6b7177; background: #fff; cursor: pointer; padding-left: 20px; } li:first-child { border-radius: 6px 6px 0 0; } li:last-child { border-radius: 0 0 6px 6px; } li:hover { color: #0069ba; } } </style> <style> .thematic_popper { width: 85px !important; border: none; } .thematic_popper.el-popper { margin-top: -2px !important; } .el-popper.thematic_popper { border: 0px; min-width: 70px; background: #fff; padding: 0; border-radius: 6px !important; transform: translateX(20px); } .thematic_popper .popper__arrow { top: -7px !important; background: transparent !important; border: none !important; } .thematic_popper .popper__arrow::after { background: transparent !important; border: none !important; } </style>