vue-antd的tree组件的异步加载
前言:tree组件自定义slot,数据项需要手动添加scopedSlots。
一、绘制的页面如下:
二、组件代码
/**
*
* @param {*} replaceFields 树结构字段替换 object
* @param {*} defaultExpandParent 默认展开父节点 boolean
* @param {*} default-expand-all 默认展开所有节点 boolean
* @param {*} tree-data 树的数据 array
* @param {*} defaultExpandedKeys 展开默认节点 array
* @param {*} menuList 菜单 array
* @param {*} treeType 树的类型 主题:theme 目录:catalogue
* @param {*} interfaceType 接口参数类型
* @param {*} expand 隐藏菜单 function
* @param {*} rightClick 右击事件 function
* @param {*} select 选中事件 function
* @param {*} load-data 异步加载事件 function
*/
<template>
<div class="tree outside-wrap-container">
<a-tree
v-if="treeData.length"
id="TreeMenu"
ref="tree"
key="id"
:replaceFields="defaultFields"
:defaultExpandParent="true"
:default-expand-all="treeExpandAll"
:tree-data="treeData"
:defaultExpandedKeys="defaultExpandedKeys"
:menuList ='menuList'
:loadedKeys="loadedKeys"
@expand="hiddenMenu"
@rightClick="rightClick"
@select="handleNodeSelect"
:load-data="onLoadData"
v-model="checkedKeys"
>
<template slot="custom" slot-scope="node">
<div
class="custom-tree-node"
:class="clickBlueDag == node.eventKey ? 'click-blue' : ''"
@click="handleNodeClick(node)"
>
<a-icon
:type="iconType(node[replaceFields.type], node)"
:class="
clickBlueDag == node.eventKey ? 'click-blue' : ''
"
/>
<!-- 标签名 -->
<div class="node-ellipsis node">
<a-tooltip
placement="bottom"
:title="node[replaceFields.name]"
>
<span
:key="node.eventKey + 'name'"
class="node"
:class="
clickBlueDag == node.eventKey ? 'click-blue' : ''
"
>
{{ node[replaceFields.name]}}
</span>
</a-tooltip>
</div>
<!-- 气泡提示 -->
<a-tooltip placement="bottom" v-if="node.tooltip">
<template slot="title">
<span>{{ node.tooltip }}</span>
</template>
<a-icon type="question-circle" />
</a-tooltip>
<!-- 操作 -->
<ul
:id="'Menu' + node.eventKey"
v-show="showRightActions && showMenu == node.eventKey"
class="menu"
>
<li
v-show="transJs(item.condition, node)"
@click="menuEvent(node,item.type)"
v-for="item in menuList"
:key="item.name"
>
{{ item.name }}
</li>
</ul>
</div>
</template>
</a-tree>
</div>
</template>
<script>
import Utils from '@/common/utils'
import COMMONAPI from '@/api/api_common.js'
export default {
props: {
menuList: {
type: Array,
required: false,
default: () => {
return []
}
},
defaultExpandedKeys: {
type: Array,
required: false,
default: () => {
return ['-1']
}
},
treeExpandAll: {
type: Boolean,
required: false,
default: false
},
showRightActions: {
// 是否需要右击操作
type: Boolean,
required: true
},
treeType: {
// 目录树或主题住
type: String,
required: false,
default: 'theme'
},
interfaceType: {
// 接口参数
type: String,
required: false,
default: 'DOMAIN'
},
defaultFields: {
// 自定义绑定属性
type: Object,
required: false,
default: () => {
return {
title: 'title',
key: 'key',
name: 'name',
type: 'type' // 节点层级
}
}
}
},
computed: {
replaceFields() {
return {
title: this.defaultFields.title ? this.defaultFields.title : 'title',
key: this.defaultFields.key ? this.defaultFields.key : 'key',
name: this.defaultFields.name ? this.defaultFields.name : 'name',
type: this.defaultFields.type ? this.defaultFields.type : 'type'
}
}
},
data() {
return {
loadedKeys: [],
checkedKeys: [],
showMenu: 0, // 展示菜单
showRename: 0, // 展示重命名
inputRename: '', // 重命名输入框
clickBlueDag: null, // 判断点击字体变色
parent: '', // 父节点
treeDataSlots: [],
treeManage: null,
treeData: []
}
},
created() {
this.loadedKeys =[]
let node = {
id: '-1'
}
this.getLoadData(node, (res) => {
this.setTreeData(res)
})
},
methods: {
onLoadData(node) {
return new Promise(resolve => {
if (node.dataRef.children && node.dataRef.children.length) {
resolve();
return;
}
let nodeInfo = {
id: node.dataRef.id
}
this.getLoadData(nodeInfo, (res) => {
this.treeData = [...res];
resolve();
})
});
},
getLoadData(node, callback) {
// 获取tree列表
let params ={
parentId: node.id,
type: this.interfaceType
}
COMMONAPI.themeLists(params)
.then(res => {
let children = res.map(t => {
return {
...t,
name: t.parentId==='-1'&&this.treeType==='theme'?'主题':t.name,
key: t.id,
scopedSlots: {
title: 'custom'
}
}
})
if(node.id==='-1'){
// 根目录初始化
this.setTreeData(children)
}else{
// 异步加载
const data = [...this.treeData];
Utils.loopTree(data, node.id, item => { // 目标节点更改
item.children = children || []
item.isLeaf = !item.children || item.children.length == 0
}, 'id');
this.setTreeData(this.treeData)
callback(data);
}
})
.catch(() => {})
},
iconType(type, node) {
if (node.layer > -1) {
return 'folder-open'
}
if (this.treeManage && this.treeManage.iconType) {
return this.treeManage.iconType(type)
}
return type === 1 ? 'folder-open' : 'file-text'
},
resetTree() {
this.clickBlueDag = null
},
setTreeData(treeData) {
this.treeData = [...treeData]
this.$forceUpdate()
},
transJs(conditon, node) {
// js语句执行
if (conditon && this.showRightActions) {
let reg = new RegExp(`node.${this.replaceFields.type}`, 'g')
let str = conditon.replace(reg, node[this.replaceFields.type])
let Fn = Function // 一个变量指向Function,防止有些前端编译工具报错,替代eval方法
return new Fn('return ' + str)()
}
},
hiddenMenu(keys, expands) {
if (keys) {
if (keys.length > 1) {
this.$emit(
'treeNodeExpands',
expands.node.$vnode.data.props
)
}
}
// 去除弹框
this.showMenu = 0
},
handleNodeSelect(code) {
// 选择节点
this.$emit('treeNodeSelect', code)
this.hiddenMenu()
},
handleNodeClick(node) {
// 左击树节点
this.clickBlueDag = node.eventKey
this.$emit('treeNodeClick', node)
this.hiddenMenu()
},
rightClick({ event, node }) {
// 右击树节点
this.parent = node.$parent // 父节点
this.showMenu = node.eventKey
// 计算菜单个数
let cal_menu_height = () => {
let height = (Number(this.menuList.length) + 1) * 20 + 2 * 5
return height
}
// 判定是否需要菜单上移
let need_menu_move_up = function() {
let need = false
let dy = window.innerHeight
if (dy < menu_height) {
need = true
}
return need
}
// 需要移动的高度
let move_height = 0
let menu_num = Number(this.menuList.length)
// 没有菜单
if (menu_num == 0) {
this.showMenu = 0
return
}
let menu_height = cal_menu_height()
if (need_menu_move_up()) {
move_height = menu_height
}
// 计算x轴偏移
document.getElementById('Menu' + node.eventKey).style.left =
event.clientX + 30 + 'px'
// 计算y轴偏移
document.getElementById('Menu' + node.eventKey).style.top =
event.clientY - move_height + 'px'
},
menuEvent(node, type){
if(type==='add'){
this.newTask(node)
}else if(type==='edit'){
this.editTask(node)
}else if(type==='delete'){
this.deleteTask(node)
}else if(type==='release'){
this.releaseTask(node)
}
},
newTask(node) {
// 新建任务
this.$emit('treeNewTask', node)
node.expanded = true
this.newTaskData = node
},
editTask(node) {
// 编辑任务
this.$emit('treeEditTask', node)
node.expanded = true
},
deleteTask(node) {
// 删除任务
this.$emit('treeDeleteTask', node)
node.expanded = true
},
releaseTask(node) {
// 发布任务
this.$emit('treeReleaseTask', node)
node.expanded = true
this.newTaskData = node
},
editDataSet(node) {
// 修改
this.$emit('treeEditData', node)
node.expanded = true
this.newTaskData = node
// 计算菜单个数
let cal_menu_height = () => {
let height = (Number(this.menuList.length) + 1) * 20 + 2 * 5
return height
}
let menu_height = cal_menu_height()
let move_height = menu_height
// 计算x轴偏移
document.getElementById('Menu' + node.eventKey).style.left =
event.clientX + 30 + 'px'
// 计算y轴偏移
document.getElementById('Menu' + node.eventKey).style.top =
event.clientY - move_height + 'px'
}
}
}
</script>
<style lang="scss" scoped>
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.tree {
height: 100%;
padding: 20px;
overflow: auto;
.click-blue {
//点击字体颜色
background-color: #f0f4ff;
border-radius: 2px;
color: $primaryColor;
}
/deep/.ant-tree-node-content-wrapper.ant-tree-node-selected {
background-color: inherit !important;
}
}
.custom-tree-node {
width: 100%;
padding: 0 5px;
.node-ellipsis {
margin-left: 10px;
max-width: 120px;
vertical-align: middle;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
}
.menu {
display: inline-block;
position: fixed;
border-radius: 3px;
list-style-type: none;
z-index: 99;
box-shadow: 0 0 5px 1px #00000038;
padding: 0;
li {
padding: 6px 0;
width: 88px;
padding: 0 16px;
font-size: 12px;
height: 32px;
line-height: 32px;
color: #666666;
background-color: #fff;
}
li:hover {
background: #f4f6f7;
color: #333333;
}
}
}
.custom-tree-node:hover {
background-color: #f0f4ff;
border-radius: 2px;
}
/deep/ .ant-tree li span.ant-tree-switcher {
color: #d9d9d9;
}
/deep/.ant-tree li .ant-tree-node-content-wrapper {
min-width: 100%;
// padding: 0 5px;
}
/deep/ .ant-tree li .ant-tree-node-content-wrapper:hover {
background-color: inherit;
}
/deep/.ant-tree li span.ant-tree-switcher,
.ant-tree li span.ant-tree-iconEle {
width: 5px;
}
</style>
三、树循环的js函数
/**
* 树结构
* @param {Array} data 树的结构
* @param {String} key 当前节点id
* @param {String} callback 回调函数
* @param {String} defaultKey 默认节点
* @returns Array
*/
function loopTree(data, key, callback, defaultKey) { // 循环树节点
data.forEach((item, index, arr) => {
if (item[defaultKey] === key) {
return callback(item, index, arr);
}
if (item.children) {
return this.loopTree(item.children, key, callback, defaultKey);
}
})
}