实现功能主要有双击修改节点、增加父节点、增加子节点删除节点以及保存时的非空校验。
注意:图中仅支持四级,且el-cascader的指标和名称不能在同一级。写本文仅为记录,仅供参考,如有不对的地方,欢迎指点。
增加节点和删除节点可以用el-tree现有的方法,但是需要指定node-key
// 删除节点,需要指定node-key
this.$refs.tree.remove(node)
// data表示要追加的子节点的数据
// parentNode表示子节点的 parent 的 data、key 或者 node,指定node-key时三者都可以,没有指定只能使用node
this.$refs.tree.append(data, parentNode)
实现代码:
<template>
<div>
<el-form :model="form" ref="form" :rules="rules">
<div class="overflow-auto pe-4" style="height:300px;">
<el-tree ref="tree" :data="rootNodes" :props="{ label: 'name' }" node-key="id" :expand-on-click-node="false" default-expand-all>
<template slot-scope="{node,data}">
<div class="w-100 d-flex align-items-center">
<el-form-item v-if="data.isEdit" :prop="`name_${data.id}`"
:rules="{ required: true, validator: validateOrg(data.name, ''), trigger: 'folder' }" class="flex-1">
<el-input v-model="data.name" :ref="data.id" placeholder="请输入名称"
@blur="handleInputBlur(node, data)"></el-input>
</el-form-item>
<el-form-item v-else-if="data.type === 'indicator'" :prop="`targetIds_${data.id}`"
:rules="{ type: 'array', required: true, validator: validateOrg(data.targetIds, 'indicator'), trigger: ['blur', 'change'] }"
class="d-flex flex-1">
<el-cascader :ref="data.id" v-model="data.targetIds" placeholder="请输入或选择指标(支持模糊查询)"
:options="indexOption"
:props="{ multiple: true, children: 'targetList', label: 'targetChineseName', value: 'targetId', emitPath: false }"
:show-all-levels="false" filterable clearable class="flex-1"></el-cascader>
</el-form-item>
<div v-else @dblclick="headerDbClick(data)">{{ node.label }}</div>
<el-button v-show="!data.isEdit && data.type === 'folder' && node.level < 4" type="text"
icon="el-icon-plus" :disabled="!!(data.children?.filter(({ type }) => type === 'indicator').length)"
class="ms-2 font-weight-bold" @click="appendSub(node, data)"></el-button>
<svg-icon
v-show="!data.isEdit && data.type === 'folder' && node.level < 4 && !(data.children?.filter(({ type }) => !!type).length)"
icon-class="gather" class="text-primary ms-2 fs-14 flex-auto" @click="addIndex(node, data)"></svg-icon>
<i :class="['el-icon-delete-solid text-primary ms-2', data.isEdit || data.type === 'indicator' ? 'margin-15' : '']"
@click="remove(node, data)"></i>
</div>
</template>
</el-tree>
</div>
</el-form>
<div slot="footer">
<div style="text-align:left;">
<el-button type="warning" class="my-2" size="mini" @click="append">添加新节点</el-button>
</div>
<div class="text-center">
<el-button type="primary" @click="confirm"> 保存</el-button>
<el-button>返回</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
form: {},
rules: {},
rootNodes: [{
id: 1,
name: '请输入名称',
isEdit: false, // 代表是否 编辑
targetIds: [],
type: 'folder', // 代表是否是指标 folder名称 indicator-指标
children: [{
id: 2,
name: '请输入名称',
isEdit: false,
targetIds: [],
type: 'folder',
children: []
}]
}],
id: 3, // 给el-tree每个节点添加一个不重复的id标识
indexOption: [{
value: 1,
label: '东南',
children: [{
value: 2,
label: '上海',
children: [
{ value: 3, label: '普陀' },
{ value: 4, label: '黄埔' },
{ value: 5, label: '徐汇' }
]
}, {
value: 7,
label: '江苏',
children: [
{ value: 8, label: '南京' },
{ value: 9, label: '苏州' },
{ value: 10, label: '无锡' }
]
}, {
value: 12,
label: '浙江',
children: [
{ value: 13, label: '杭州' },
{ value: 14, label: '宁波' },
{ value: 15, label: '嘉兴' }
]
}]
}, {
value: 17,
label: '西北',
children: [{
value: 18,
label: '陕西',
children: [
{ value: 19, label: '西安' },
{ value: 20, label: '延安' }
]
}, {
value: 21,
label: '新疆维吾尔族自治区',
children: [
{ value: 22, label: '乌鲁木齐' },
{ value: 23, label: '克拉玛依' }
]
}]
}],
}
},
methods: {
validateOrg (data, type) {
// 不知道为什么value为undefined
return (rule, value, callback) => {
if (!data?.length) {
callback(new Error(`${type === 'indicator' ? '请选择指标' : '请输入名称'}`))
} else {
callback()
}
}
},
// 双击编辑
headerDbClick (data) {
this.$set(data, 'isEdit', true) // 使用该方法原因防止调接口获取的节点没有isEdit属性
this.$nextTick(() => {
this.$refs[data.id] && this.$refs[data.id].focus()
})
},
// 输入框失去焦点时
handleInputBlur (node, data) {
if (data.name === '') return
data.isEdit = false
},
// 添加父节点
append () {
// 判断当前是否有节点,添加在最后一个节点上
if (this.rootNodes?.length) {
// 如果当前节点已经添加了一个空的子节点,就添加不了了
if (!this.rootNodes[this.rootNodes.length - 1].isEdit) {
const id = this.id++
this.$refs.tree.insertAfter({ name: '', isEdit: true, id, targetIds: [], type: 'folder', children: [] }, this.rootNodes[this.rootNodes.length - 1].id)
this.$nextTick(() => {
this.$refs[id] && this.$refs[id].focus()
})
}
} else {
const id = this.id++
this.rootNodes = [{ name: '', isEdit: true, id, targetIds: [], type: 'folder', children: [] }]
this.$nextTick(() => {
this.$refs[id] && this.$refs[id].focus()
})
}
},
// 添加子节点
appendSub (node, data) {
const id = this.id++
this.$refs.tree.append({ name: '', isEdit: true, id, targetIds: [], type: 'folder', children: [] }, node)
this.$nextTick(() => {
this.$refs[id] && this.$refs[id].focus()
})
},
// 删除
remove (node, data) {
this.$confirm(`当前您正在执行${data.children?.length ? '级联' : ''}删除操作!`, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$refs.tree.remove(node)
}).catch(() => null)
},
// 添加指标,使用级联组件el-cascader
addIndex (node, data) {
const id = this.id++
this.$refs.tree.append({ name: '', isEdit: false, id, targetIds: [], type: 'indicator' }, node)
},
confirm () {
this.$refs.form.validate((valid) => {
if (valid) {
this.$message.success('成功')
} else {
this.$message.warning('失败')
}
})
},
},
}
</script>
<style lang="scss" scoped>
::v-deep .el-tree-node__content {
height: auto;
padding: 5px 0;
}
::v-deep .el-form-item__content {
display: flex;
flex: 1 0 0px !important;
}
.margin-15 {
margin-top: -15px
}
</style>