实现效果
代码
实现方式:使用递归组件
components/myTree.vue
<template>
<div class="tree-box">
<div v-for="(item, index) in data" :key="item.id">
<div class="box-item">
<template>
<div class="common-wrap">
<a-form-model-item :prop="`${parentPropKey}.${index}`" :rules="rules.notRepeat">
<template slot="label">
<!-- 收起、展开图标 -->
<span class="icon-expand">
<a-icon :type="`${item.expand ? 'caret-down' : 'caret-right'}`" class="icon-expand"
@click="item.expand = !item.expand" />
</span>
<!-- 必填图标 -->
<span class="icon-required">*</span>
<!-- 表单名称 -->
{{ item.name }}
</template>
<a-input v-model.trim="item.val" :placeholder="`请输入${item.name}`" :max-length="20"
allowClear />
<div class="btn-wrap">
<a-button type="primary" v-if="index === 0" @click="addData(item.level)">+</a-button>
<a-button type="danger" v-else @click="removeData(item)">-</a-button>
</div>
</a-form-model-item>
</div>
</template>
</div>
<myTree v-for="(childItem, childIndex) in item.childKeys" :key="item.id + '_' + childIndex"
v-show="item.expand" :data="item['children' + childIndex]"
:parentPropKey="`${parentPropKey}.${index}.children${childIndex}`" :rules="rules"
@validateTree="validateTree">
</myTree>
</div>
</div>
</template>
<script>
export default {
name: 'myTree',
props: {
data: {
type: Array,
},
parentPropKey: {
type: String
},
rules: {
type: Object
},
},
inject: ['getChildByLevel'],
computed: {
// 值数组
valArr() {
return this.data.map(item => (item.val || ''))
},
},
watch: {
// 校验名称不能重复
valArr: {
handler: function () {
this.checkNotRepeat()
},
immediate: true
},
},
methods: {
// 增加数据
addData(level) {
const childObj = this.getChildByLevel(level)
this.data.push(childObj)
},
// 按钮删除数据
removeData(item) {
const { id } = item
const index = this.data.findIndex(i => i.id === id)
this.data.splice(index, 1)
},
// 校验名称不能重复
checkNotRepeat() {
const valueArr = JSON.parse(JSON.stringify(this.valArr))
const parentPropKey = this.parentPropKey
this.data.forEach((item, index) => {
const { val, isRepeat: oldIsRepeat } = item
const newIsRepeat = valueArr.filter(valItem => valItem && valItem === val).length > 1
item.isRepeat = newIsRepeat
if (oldIsRepeat !== newIsRepeat) {
this.validateTree(`${parentPropKey}.${index}`)
}
})
},
validateTree(propKey) {
this.$emit('validateTree', propKey)
},
// 下拉框定位
getPopupContainer(triggerNode) {
return triggerNode.parentNode
},
},
}
</script>
<style lang="less" scoped>
.tree-box {
padding-left: 40px;
&:first-of-type {
padding-left: 0;
}
.box-item {
margin-bottom: 10px;
.btn-wrap {
.ant-btn {
width: 30px;
}
}
.icon-required {
color: #f5222d;
font-size: 14px;
font-family: SimSun, sans-serif;
line-height: 1;
}
.common-wrap /deep/ .ant-form-item {
.icon-expand {
line-height: 40px;
}
.icon-required {
visibility: hidden;
}
.ant-form-item-required {
&::before {
display: none;
}
.icon-required {
visibility: visible;
}
}
.ant-form-item-control-wrapper {
width: 360px;
.ant-form-item-children {
padding-right: 50px;
display: block;
.btn-wrap {
position: absolute;
right: 6px;
top: 0;
}
}
}
}
}
}
</style>
components/treeConfig.js
// 多级树源数据
const treeObj = (function () {
const baseObj = {
'1': {
name: '层级1',
level: '1',
expand: true,
val: '',
isRepeat: false,
childKeys: [['2']]
},
'2': {
name: '层级2',
level: '2',
expand: true,
val: '',
isRepeat: false,
childKeys: [['3']]
},
'3': {
name: '层级3',
level: '3',
expand: true,
val: '',
isRepeat: false,
},
}
return getTreeObj(baseObj)
})()
// 将数据转换成树结构
function getTreeObj(obj) {
obj = JSON.parse(JSON.stringify(obj))
Object.keys(obj).forEach(level => {
let levelInfo = obj[level]
let childKeys = levelInfo.childKeys
childKeys && childKeys.forEach((item, index) => {
levelInfo['children' + index] = item.map(childKey => {
return obj[childKey]
})
})
})
return obj
}
// 根据level获取当前级别数据
export const getChildByLevel = function (level) {
let obj = JSON.parse(JSON.stringify(treeObj[level] || {}))
return addOnlyId([obj])[0]
}
// 给数据加id
export function addOnlyId(data) {
return data.map((item, index) => {
item.id = item.level + '_' + new Date().getTime() + '_' + index
item.childKeys && item.childKeys.forEach((childItem, childIndex) => {
const childSign = 'children' + childIndex
item[childSign] = addOnlyId(item[childSign])
})
return item
})
}
demo.vue
<template>
<a-form-model ref="moduleForm" :model="form" :rules="rules" :label-col="{ span: 4 }" :wrapper-col="{ span: 8 }">
<myTree :data="form.treeData" parentPropKey="treeData" :rules="rules" @validateTree="validateTree">
</myTree>
</a-form-model>
</template>
<script>
import myTree from '../components/myTree'
import { getChildByLevel } from '../components/treeConfig'
export default {
provide: {
'getChildByLevel': getChildByLevel,
},
data() {
return {
form: {
treeData: [],
},
rules: {
// 校验名称不能重复
notRepeat: [
{ required: true, message: '必填项' },
{ validator: this.checkNotRepeat },
],
}
}
},
mounted() {
this.form.treeData = [getChildByLevel('1')]
},
methods: {
// 校验名称不能重复
checkNotRepeat(rule, value, callback) {
const { val, isRepeat } = value
if (!val) {
callback('必填项')
} else if (val && isRepeat) {
callback('名称不能重复')
} else {
callback()
}
},
// 校验树的某些字段
validateTree(propKey) {
const currentForm = this.$refs.moduleForm
currentForm.validateField(propKey)
},
},
components: {
myTree
}
}
</script>
<style lang="less" scoped></style>