今天做数仓需要个树形图,想着本来UI用的就是iView,不想再引入别的插件了,但是iView的Tree组件和产品要求的有些不一样。
要求:要求前面要用加减号图标,不要箭头图标,要有同级虚线连接,需要可编辑、可添加、可删除节点;
但是Iview貌似没有现成的配置,只有个添加、删除大致的配置,如图(一);
因此在参考几篇的帖子之后,决定用iView Tree强大的 render
改造一番;最终效果图 如图(二)
<template>
<div class="v-tree">
<Tree :data="treeList" :render="renderContent" ></Tree>
</div>
</template>
<script>
// import service from 'libs/service/api'
import {
cloneDeep } from 'lodash-es'
export default {
name: 'VTree',
props: {
name: String,
bdId: [String, Number] // 数据库Id
},
data() {
return {
editState: false, // 编辑状态
treeList: [
{
title: '深圳分公司',
expand: true,
// 单独给某个节点设置render,此render会优先Tree组件中的render(此组件可删除,只是为了展示一下,没有实际用处)
render: (h, {
root, node, data }) => {
return h('span', {
style: {
display: 'inline-block',
width: '100%'
}
}, [
h('span', [
h('Icon', {
props: {
type: 'ios-folder'
},
style: {
marginRight: '8px',
}
}),
h( "span" , data.title)
]),
]);
},
children: [
{
title: '父-子',
expand: true,
children: [
{
title: '子',
expand: true
},
{
title: '子2',
expand: true
}
]
}
]
},
{
title: '深圳分公司',
expand: true,
children: [
{
title: '父-子',
expand: true,
children: [
{
title: '子',
expand: true
},
{
title: '子2',
expand: true
}
]
}
]
}
],
buttonProps: {
type: 'default',
size: 'small',
},
// 输入框要修改的内容
inputContent: '',
// 修改前的TreeNode名称
oldName: ''
}
},
mounted() {
// this.getTreeList()
},
methods: {
// 树渲染逻辑
renderContent (h, {
root, node, data }) {
return h('span', {
class: 'common-wrap',
on:{
click:() => {
// 点击Tree节点触发
if (!data.editState) {
this.handleClickTreeNode(root, data, node)
}
}
}
}, [
h('span', [
// 文件前面的文件夹图标
h('Icon', {
props: {
type: `${
data.children === undefined || data.children.length === 0 ? 'md-document' :'ios-folder'}`
},
style: {
marginRight: '8px',
}
}),
h(`${
data.editState ? '' : 'span'}`, data.title),
h(`${
data.editState ? 'input' : ''}`,
{
class: 'edit-input',
attrs:{
value:`${
data.editState ? data.title : ''}`,
autofocus: 'true'
},
style: {
// cursor: 'auto' ,
// borderRadius: '3px',
// border: '1px solid #e5e5e5',
// color: '#515a6e'
},
on:{
change: (event) => {
this.inputContent = event.target.value
}
}
}
)
]),
// 增删改按钮部分
h(`${
data.editState ? '' : 'span'}`,
{
class: 'btnNone'
},
[
// 操作按钮部分
// 编辑按钮
h('Button', {
props: {
...this.buttonProps,
icon: 'md-color-filter'
},
on: {
click: () => {
this.editTree(data) }
}
}),
// 添加按钮
h('Button', {
props: {
...this.buttonProps,
icon: 'md-add'
},
on: {
click: () => {
this.append(data) }
}
}),
// 删除按钮
h('Button', {
props: {
...this.buttonProps,
icon: 'md-remove'
},
on: {
click: () => {
this.remove(root, node, data) }
}
})
]
),
// 确认/取消修改部分
h(`${
data.editState ? 'span' : ''}`,
{
style: {
marginLeft: '.5rem'
}
},
[
// 确认按钮
h('Button', {
props: {
...this.buttonProps,
icon: 'md-checkmark'
},
style: {
border: 0,
background: 'rgba(0,0,0,0)',
fontSize: '16px',
outline: 'none'
},
on: {
click: (event) => {
this.confirmTheChange(data)
}
}
}),
// 取消按钮
h('Button', {
props: {
...this.buttonProps,
icon: 'md-close'
},
style: {
border: '0',
background: 'rgba(0,0,0,0)',
fontSize: '16px',
outline: 'none'
},
on: {
click: () => {
this.cancelChange(data) }
}
})
]
)
])
},
/**
* @description: 获取树列表(这部分可以换成自己的)
* @param {*}
* @return {*}
*/
async getTreeList() {
let res = await service.getCategoryTreeList({
name: this.name,
id: this.bdId
})
if (res.data) {
this.treeList = [...res.data]
// 路由参数带有id,则置为选中状态
if (this.bdId) {
this.filterTableMater(this.bdId, this.treeList)
}
}
},
// 控制Tree当前状态函数
setStates(data){
let editState=data.editState
if (editState) {
this.$set(data, 'editState', false)
} else {
this.$set(data, 'editState', true)
}
},
// Tree修改按钮
editTree(data){
event.stopPropagation()
this.inputContent=data.title
this.oldName=data.title
this.setStates(data)
},
// 添加按钮
append (data) {
event.stopPropagation()
const children = data.children || []
children.push({
title: '新建节点',
expand: true
})
this.$set(data, 'children', children)
},
// 删除按钮
remove (root, node, data) {
event.stopPropagation()
this.$Modal.confirm({
title: '提示',
content: `您确定删除 “${
data.title}” 吗?`,
onOk: () => {
const parentKey = root.find(el => el === node).parent
const parent = root.find(el => el.nodeKey === parentKey).node
const index = parent.children.indexOf(data)
parent.children.splice(index, 1)
this.$Message.info('删除成功')
},
onCancel: () => {
this.$Message.info('取消')
}
})
},
// 确认修改树节点
confirmTheChange(data) {
if (!this.inputContent) {
this.$Notice.warning({
title: '当前输入有误',
})
} else {
if(this.oldName !== this.inputContent){
this.$Modal.confirm({
title: '提示',
content: `您确定将 “${
this.oldName}” 重命名为 “ ${
this.inputContent} ” 吗?`,
onOk: () => {
data.title=this.inputContent
this.$Message.info('修改成功')
},
onCancel: () => {
this.$Message.info('取消')
}
})
this.setStates(data)
} else{
this.setStates(data)
}
}
},
// 取消修改树节点
cancelChange(data) {
this.$Notice.info({
title: '取消修改',
})
this.setStates(data)
},
// 点击Tree节点触发
handleClickTreeNode(root, data, node) {
if (this.bdId) {
// 这里是因为 filterTableMater的时候this.treeList不是响应式的,倒是选中之后再次点击别的不去除样式
this.treeList = cloneDeep(this.treeList)
}
this.$emit('handleTreeNode',node) // 触发父级组件拉去新列表
},
// js 递归遍历查找对象数组的某一个属性
filterTableMater(code, arr) {
for (const item of arr) {
if (item.id === code) {
item.selected = true
}
if (item.children && item.children.length) {
this.filterTableMater(code, item.children)
}
}
}
}
}
</script>
// 大部分改写都在样式这里,如果有必要请仔细看一下
<style lang="less">
// 编辑部分
.btnNone {
display: none;
margin-left: 15px;
button {
margin-right: 8px;
width: 24px;
line-height: 0;
height: 24px;
}
}
.common-wrap {
display: inline-block;
line-height: 25px;
width: 100%;
cursor: pointer;
span:not(.btnNone) {
display: inline-block;
}
.edit-input {
width: 90px;
cursor: auto;
border-radius: 3px;
border: 1px solid #e5e5e5;
color: #515a6e;
padding-left: 10px;
outline: none;
}
.edit-input:focus {
border: 1px solid #2d8cf0 !important;
}
}
.common-wrap:hover .btnNone {
display: inline-block;
}
.common-wrap:hover {
color: #275cd4;
}
// 换箭头到加号
.ivu-tree ul li {
list-style: none;
padding: 0;
white-space: nowrap;
outline: none;
}
.ivu-tree .ivu-tree-arrow {
width: 15px;
}
.ivu-tree .ivu-icon {
// 禁止旋转
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
.ivu-tree .ivu-icon-ios-arrow-forward::before {
// 改变tree默认的三角箭头
content: "\F32F"; // 采用ui库中自带的icon图标
width: 15px;
height: 15px;
display: block;
}
.ivu-tree .ivu-tree-arrow-open .ivu-icon-ios-arrow-forward::before {
// 改变tree默认的三角箭头
content: "\F417"; // 采用ui库中自带的icon图标
display: block;
width: 15px;
height: 15px;
}
// 虚线
.ivu-tree {
.ivu-tree-children {
position: relative;
// padding-left: 16px; // 缩进量
}
.ivu-tree-children::before {
content: "";
height: 100%;
width: 1px;
position: absolute;
left: 7px;
top: -12px;
border-width: 1px;
border-left: 1px dashed #ddd;
}
// 当前层最后一个节点的竖线高度固定
.ivu-tree-children:last-child::before {
height: 20px; // 可以自己调节到合适数值
}
// 横线
.ivu-tree-children::after {
content: "";
width: 11px;
height: 20px;
position: absolute;
left: 9px;
top: 12px;
border-width: 1px;
border-top: 1px dashed #ddd;
}
// 去掉最顶层的虚线,放最下面样式才不会被上面的覆盖了
& > .ivu-tree-children::after {
border-top: none;
}
& > .ivu-tree-children::before {
height: 100%;
width: 1px;
left: 7px;
top: 13px;
border-left: 1px dashed #ddd;
}
& > .ivu-tree-children:last-of-type::before {
border-left: none;
}
}
</style>
到这里就基本完成了,希望对有这部分需求的盆友有用处,加油!!!