Tree-input 组件使用
<template>
<div class="add-index" style="text-align: left;">
<tree-input
:datas.sync="treeList"
:is-grid="true"
:disabled="false">
</tree-input>
</div>
</template>
<script>
import TreeInput from './tree/input/index'
export default {
components: {
TreeInput
},
data () {
return {
treeList: [{
label: '一级',
value: '1',
children: [{
label: '二级',
value: '11'
}]
}, {
label: '一级2',
value: '2',
children: [{
label: '二级1',
value: '21'
}]
}],
content: ''
}
}
}
</script>
index.vue代码
<template>
<div class="tree-input__index">
<section>
<component
v-for="(item, index) in datas"
:key="index"
:is="'TreeInputItem'"
:datas="item"
:level="level"
:maxLevel="maxLevel"
:disabled="disabled"
:is-grid="isGrid"
:isLast="index === datas.length - 1"
@on-delete="handleDelete">
</component>
</section>
<el-button
type="text"
:disabled="disabled"
@click="addItem">
<i class="el-icon-plus"></i>
{{ `新增${title}` }}
</el-button>
</div>
</template>
<script>
import TreeInputItem from './tree-input-item'
export default {
name: 'tree-input',
components: {
TreeInputItem
},
props: {
title: {
type: String,
default: ''
},
datas: {
type: Array,
default: () => []
},
level: {
type: Number,
default: 1
},
maxLevel: {
type: Number,
default: 5
},
options: {
type: Object,
default: () => ({
label: 'label',
value: 'id',
children: 'children'
})
},
inputW: {
type: Number,
default: 240
},
isGrid: Boolean,
disabled: Boolean
},
computed: {
defaultItem () {
return {
[this.options.label]: '',
[this.options.value]: '',
[this.options.children]: []
}
}
},
methods: {
/**
* 添加平级元素
*/
addItem () {
this.$emit('update:datas', [...this.datas, { ...this.defaultItem }])
},
/**
* 删除该项及子项
*/
handleDelete (item) {
const index = this.datas.findIndex(p => p === item)
const lists = [...this.datas]
lists.splice(index, 1)
this.$emit('update:datas', lists)
}
}
}
</script>
<style lang="less" scoped>
.tree-input__index{
text-align: left;
/deep/ .tree-input__item:not(:last-of-type) {
margin-bottom: 10px;
}
}
</style>
Tree-input-item.vue
<template>
<div class="tree-input__item" :class="[{'is-no-last': isGrid && !isLast && level !== 1}]">
<div class="input-wrap"
:class="[{'is-grid': isGrid && level !== 1}]">
<el-input
v-model="datas[options.label]"
:placeholder="`请输入${title}`"
:style="inputStyle"
:disabled="disabled">
</el-input>
<el-button
type="text"
icon="el-icon-plus"
:disabled="disabled"
v-if="level < maxLevel"
@click="addNextItem">
</el-button>
<el-button
type="text"
icon="el-icon-delete"
:disabled="disabled"
@click="handleDelete">
</el-button>
<el-button
class="collapse-btn"
type="text"
v-if="haveNextChildren(datas)"
@click="handleCollapse">
<transition name="el-fade-in">
<i class="el-icon-arrow-up" v-if="datas && datas.collapse"></i>
</transition>
<transition name="el-fade-in">
<i class="el-icon-arrow-down" v-if="!(datas && datas.collapse)"></i>
</transition>
</el-button>
</div>
<el-collapse-transition>
<div class="children-wrap" v-if="showChildrenWrap(datas)">
<tree-input
:datas.sync="datas[options.children]"
:title="title"
:level="level+1"
:max-level="maxLevel"
:options="options"
:inputW="inputW"
:disabled="disabled"
:is-grid="isGrid">
</tree-input>
</div>
</el-collapse-transition>
</div>
</template>
<script>
export default {
name: 'TreeInputItem',
props: {
title: {
type: String,
default: ''
},
datas: {
type: Object,
default: () => {}
},
level: {
type: Number,
default: 1
},
maxLevel: {
type: Number,
default: 3
},
options: {
type: Object,
default: () => ({
label: 'label',
value: 'value',
children: 'children'
})
},
inputW: {
type: Number,
default: 240
},
isGrid: Boolean,
isLast: Boolean,
disabled: Boolean
},
computed: {
inputStyle () {
return `width: ${this.inputW}px;`
},
defaultItem () {
return {
[this.options.label]: '',
[this.options.value]: '',
[this.options.children]: []
}
}
},
methods: {
/**
* 添加子类
*/
addNextItem () {
const childrens = this.datas[this.options.children] || []
this.$set(this.datas, this.options.children, [...childrens, { ...this.defaultItem }])
this.$nextTick(() => {
this.$set(this.datas, 'collapse', true)
})
},
/**
* 有子分类
*/
haveNextChildren (item) {
return !!((Array.isArray(item[this.options.children]) && item[this.options.children].length !== 0))
},
/**
* 是否展示下一级
*/
showChildrenWrap (item) {
const collapse = this.datas && this.datas.collapse
return this.haveNextChildren(item) && collapse
},
/**
* 展示 | 隐藏 子级
*/
handleCollapse () {
this.$set(this.datas, 'collapse', !this.datas.collapse)
},
/**
* 删除该项
*/
handleDelete () {
this.$emit('on-delete', this.datas)
}
}
}
</script>
<style lang="less" scoped>
.tree-input__item{
display: flex;
flex-direction: column;
text-align: left;
position: relative;
transition: all .2s ease-in-out;
&.is-no-last::after{
display: inline-block;
content: ' ';
width: 5px;
height: 100%;
position: absolute;
top: 0;
left: -10px;
margin: auto;
border-left: 1px solid #1989fa;
transition: height .3s ease-in-out;
}
.input-wrap{
display: flex;
align-items: center;
position: relative;
.el-button{
margin: 0 5px;
padding: 0;
font-size: 16px;
}
&.is-grid::before{
display: inline-block;
content: ' ';
width: 10px;
height: calc(50% + 10px);
position: absolute;
top: -10px;
left: -10px;
margin: auto;
border-left: 1px solid #1989fa;
border-bottom: 1px solid #1989fa;
border-bottom-left-radius: 4px;
}
}
.children-wrap{
padding: 10px 0 0 20px;
}
.collapse-btn{
position: relative;
i{
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 16px;
height: 16px;
margin: auto;
}
}
}
</style>
效果如下: