特性
- 可以自定义主键、配置选项
- 支持预定义节点图标:folder文件夹|normal普通样式
- 多个提示文本可以自定义
- 支持动态接口增删改节点
sgLazyTree源码
<template>
<div :class="$options.name" v-loading="rootLoading">
<div class="tree-header">
<div class="sg-left "></div>
<div class="sg-right ">
<el-button type="text" size="mini" @click.stop="addRoot">{{ (data.text || {}).addRootButtonText
|| `添加根节点` }}<i class="el-icon-circle-plus-outline"></i></el-button>
</div>
</div>
<div class="tree-container">
<el-tree :load="loadNode" lazy ref="tree" :node-key="mainKey" :props="data.props || { label: 'label' }"
:icon-class="`${data.iconType}-tree-node`" :indent="data.indent || 25" @current-change="current_change"
@node-click="nodeClick" highlight-current>
<el-popover popper-class="tree-el-popover" placement="right" width="120" trigger="hover" title="" content=""
:disabled="false" slot-scope="{ node, data }">
<span class="right">
<el-button title="添加" type="text" size="" icon="el-icon-circle-plus-outline"
@click.stop="addNode(node, data)">添加</el-button>
<el-button title="删除" type="text" size="" icon="el-icon-remove-outline"
@click.stop="remove(node, data)">删除</el-button>
</span>
<div slot="reference" class="node-label">
<label class="left" :title="node.label">
{{ node.label }}
</label>
</div>
</el-popover>
</el-tree>
</div>
</div>
</template>
<script>
export default {
name: 'sgLazyTree',
data() {
return {
// 动态树:增删改_________________________________________________________
rootNode: null,//根节点
rootResolve: null,//根节点
focusNodeId: null,//聚焦高亮新添加ID
rootLoading: false,//根节点列表加载
mainKey: 'id',//默认主键
defaultRootId: 'root',//默认根节点ID就是root
// _________________________________________________________
}
},
props: ["data"],
watch: {
data: {
handler(d) {
d.nodeKey && (this.mainKey = d.nodeKey);
d.rootId && (this.defaultRootId = d.rootId);
}, deep: true, immediate: true,
},
},
methods: {
// 动态懒加载树:增删改_________________________________________________________
// 加载根节点
loadRootNode() {
this.rootNode.childNodes = [];
this.loadNode(this.rootNode, this.rootResolve);
},
// 加载常规节点
loadNode(node, resolve) {
let data = {};
if (node.level === 0) {
data = { [this.mainKey]: this.defaultRootId };
this.rootNode = node;//记录根节点
this.rootResolve = resolve;//记录根节点
} else {
data = node.data;
}
this.loadNodeData(data, d => {
resolve(d);
this.rootLoading = false;
this.$nextTick(() => { this.focusNode(this.focusNodeId) });
});
},
// 加载节点数据(通过接口向后台获取数据)
loadNodeData(data, cb) {
let resolve = d => { cb && cb(d) };
this.$emit(`loadNode`, data, resolve);
},
// 聚焦到某一个节点
focusNode(id) {
if (!id) return;
this.$nextTick(() => {
this.$refs.tree.setCurrentKey(id);//高亮显示某个节点
this.$emit(`currentChange`, this.$refs.tree.getCurrentNode());
this.$nextTick(() => {
let dom = document.querySelector(`.el-tree-node.is-current`);
dom && dom.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" });//缓慢滚动
});
});
},
// 添加根节点
addRoot() { this.addNode(this.$refs.tree.root, { [this.mainKey]: this.defaultRootId }) },
// 添加节点
addNode(node, data) {
let isRootNode = data[this.mainKey] === this.defaultRootId;
isRootNode && (this.rootLoading = true);
let resolve = d => {
this.focusNodeId = d[this.mainKey];//记录加载完毕后需要聚焦的节点ID
if (isRootNode) {
this.loadRootNode();
} else {
node.loaded = false; //必须要设置loaded=false,否则第二次展开节点不会触发加载数据
node.expanded ? node.loadData() : node.expand();//如果已展开→触发加载,否则就先展开→触发加载
}
};
this.$emit(`addNode`, data, resolve);
},
// 删除节点
remove(node, data) {
this.$confirm(
(this.data.text || {}).removeConfirmTip || `此操作将永久删除该节点及其下面的节点,是否继续?`,
(this.data.text || {}).removeConfirmTitle || `提示`,
{
dangerouslyUseHTMLString: true,
confirmButtonText: `确定`,
cancelButtonText: `取消`,
type: "warning",
}).then(() => {
this.removeNodeData(node, data)
}).catch(() => { });
},
// 删除节点数据(通过接口向后台删除数据)
removeNodeData(node, data) {
node.loading = true;//出现加载动画
let resolve = d => {
node.loading = false;
this.$message.success(`删除成功`);
// 从显示界面删除节点
let childNodes = node.parent.childNodes;
childNodes.splice(childNodes.findIndex(d => d.data[this.mainKey] === data[this.mainKey]), 1);
};
this.$emit(`removeNode`, data, resolve);
},
// 当前选中节点变化时触发的事件
current_change(d) { this.$emit(`currentChange`, d); },
//点击节点
nodeClick(d) { this.focusNodeId = null; this.$emit(`nodeClick`, d); },
}
};
</script>
<style lang="scss" scoped>
@import "~@/css/sg";
.sgLazyTree {
$treeHeaderHeight: 30px;
width: 100%;
height: 100%;
display: flex;
flex-wrap: nowrap;
flex-direction: column;
white-space: nowrap;
flex-shrink: 0;
flex-grow: 1;
position: relative;
.tree-header {
display: flex;
justify-content: space-between;
align-items: center;
height: $treeHeaderHeight;
}
.tree-container {
position: relative;
overflow: auto;
box-sizing: border-box;
height: calc(100% - #{$treeHeaderHeight});
user-select: none;
@include scrollbarHover();
>>>.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content {
background-color: #409EFF22; // 高亮当前选中节点背景
}
>>>.el-tree {
* {
transition: none;
}
.el-tree-node__children {
min-width: max-content; //这样才会出现水平滚动条
}
.normal-tree-node,
.folder-tree-node {
flex-shrink: 0;
display: block;
padding: 0 !important;
margin: 0;
width: 20px;
height: 20px;
margin-right: 5px;
background: transparent url("/static/img/fileType/folder/folder.svg") no-repeat center / contain;
margin-left: 20px;
&~span:not(.el-icon-loading) {
width: 100%;
.node-label {
height: 40px;
display: flex;
align-items: center;
}
}
&.expanded,
&.is-leaf {
flex-shrink: 0;
transform: rotate(0deg);
background-image: url("/static/img/fileType/folder/folder-open.svg");
}
}
}
}
}
.tree-el-popover {
.el-button {
padding-top: 0;
padding-bottom: 0;
}
}
</style>
用例
<template>
<div style="width: 300px;padding-right: 100px;">
<sgLazyTree :data="lazyTreeData" @currentChange="currentChange" @loadNode="loadNode"
@addNode="addNode" @removeNode="removeNode" />
</div>
</template>
<script>
import sgLazyTree from "@/vue/components/admin/sgLazyTree";
export default {
components: { sgLazyTree, },
data() {
return {
autoId: 0,//自增编号
lazyTreeData: {
nodeKey: `ID`,//主键
props: { label: 'MC' },//配置选项
iconType: 'folder',//节点图标:folder文件夹|normal普通样式
text: {
addRootButtonText: '添加根目录',//添加根节点按钮文本
removeConfirmTitle: '警告!!!',//删除节点提示标题
removeConfirmTip: '此操作将永久删除该文件夹及其下面的文件,是否继续?',//删除节点提示内容
},
},
}
},
methods: {
// 获取当前聚焦节点的数据
currentChange(d) {
console.log(`currentChange`, d);
},
// 加载节点数据
loadNode(data, resolve) { this.$d.column_queryByPid({ data: { PID: data.ID }, doing: { s: d => resolve(d) } }); },
// 添加节点
addNode(data, resolve) {
this.$d.column_save({
data: {
MC: `新增栏目名称(${++this.autoId})`,
LX: 0,
PID: data.ID,//上一级id
}, doing: { s: d => resolve(d) }
});
},
// 删除节点
removeNode(data, resolve) {
this.$d.column_delete({ data: { ID: data.ID }, doing: { s: d => resolve(d) } });
},
},
};
</script>