业务需求需要使用级联选择器的多选功能,但是使用的UI组件库不支持,在网上寻找类似的也不能完全符合需求,所以自己简单封装了一个组件,主要使用了element-ui中的el-select组件和el-tree组件实现,支持表单校验。效果如下图所示
组件代码如下
<template>
<el-select multiple clearable filterable value-key="value" ref="selectTree" :disabled="disabled"
:placeholder="placeholder" v-model="selectedValue" :filter-method="filterMethod" @visible-change="visibleChange"
@remove-tag="selectValueChangeHandler" @clear="selectValueChangeHandler">
<el-option v-for="item in selectOptions" :key="item.value" :label="item.label" :value="item"
style="display:none"></el-option>
<el-tree ref="tree" :highlight-current="true" :node-key="treeConfig.key" :data="treeData" :props="treeConfig.props"
:default-expanded-keys="expandNodesKey" :filter-node-method="filterNode" @node-click="handleNodeClick">
<span class="custom-tree-node" slot-scope="{node,data}">
<p :class="isCurrentNodeSelected(node, data) ? 'is-current-node-selected' : ''"
:style="`text-overflow:ellipsis;overflow:hidden;height:20px;line-height:20px;width:${treeWidth - 30}px`">
{{ node.label }}</p>
</span>
</el-tree>
</el-select>
</template>
<script>
export default {
name: 'selectTree',
props: {
treeData: Array,
value: Array,
treeWidth: Number,
disabled: Boolean,
placeholder: String,
treeConfig: {
type: Object,
default() {
return {
key: 'value',
parent: 'parent',
parentId: 'parentId',
props: {
label: 'label',
children: 'children'
}
}
}
}
},
model: {
prop: 'value',
event: 'treeChange'
},
data() {
return {
selectedValue: [],
expandNodesKey: []
}
},
computed: {
selectOptions() {
let result = [];
let that = this;
function getChild(item) {
item.forEach((i) => {
if (Array.isArray(i[that.treeConfig.props.children])) {
result.push(i)
getChild(i[that.treeConfig.props.children])
} else {
let child = {
label: i[that.treeConfig.parent] + '-' + i[that.treeConfig.props.label],
value: i[that.treeConfig.key]
}
result.push(child)
}
})
}
getChild(this.treeData)
return result
}
},
watch: {
value: {
immediate: true,
deep: true,
handler: function (val) {
if (Array.isArray(val)) {
this.$nextTick(() => {
this.selectedValue = val;
})
} else {
this.$nextTick(() => {
this.selectedValue = [];
})
}
}
}
},
methods: {
filterMethod(query) {
this.$refs.tree.filter(query)
},
filterNode(query, data) {
if (!query || query.length === 0) return true;
let queryLabel = query;
if (Array.isArray(query) && query.length === 1) {
queryLabel = query[0][this.treeConfig.props.label]
}
let flag = (data[this.treeConfig.props.label].indexOf(queryLabel) !== -1) || (data[this.treeConfig.parent] && data[this.treeConfig.parent].indexOf(queryLabel) !== -1)
return flag;
},
getExpandNodesKey() {
this.expandNodesKey = [];
const value = this.selectedValue;
for (let i = 0; i < value.length; i++) {
this.expandNodesKey.push(value[i][this.treeConfig.parentId])
}
this.expandNodesKey = Array.from(new Set(this.expandNodesKey))
},
visibleChange(visible) {
if (visible) {
this.getExpandNodesKey()
}
},
handleNodeClick(node) {
if (node.children && node.children.length > 0) return
const value = node[this.treeConfig.key]
const index = this.selectedValue.findIndex(item => item[this.treeConfig.key] === value)
if (index > -1) {
this.selectedValue.splice(index, 1)
} else {
this.selectedValue.push(node)
}
this.selectValueChangeHandler()
},
selectValueChangeHandler() {
this.$emit('treeChange', this.selectedValue)
this.dispatch('ElFormItem', 'el.form.change', this.selectedValue)
},
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root;
var name = parent.$options.componentName;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent
if (parent) {
name = parent.$oprions.componentName
}
}
if (parent) {
parent.$emit.apply(parent,[eventName].concat(params))
}
},
isCurrentNodeSelected(node,data){
const value = data[this.treeConfig.key]
const index = this.selectedValue.findIndex(item=>item[this.treeConfig.key]===value)
return index>-1
}
},
created() {
},
mounted() {
},
}
</script>
<style lang="less" scoped>
.is-current-node-selected{
color:#21a6e6;
}
.is-current-node-selected::after{
position: absolute;
right: 20px;
font-size: 12px;
font-weight: 700;
content: "";
width: 6px;
height: 10px;
border: solid #21a6e6;
border-width: 0 3px 3px 0;
transform: rotate(45deg);
-webkit-font-smoothing: antialiased;
}
</style>
组件引用和数据示例如下
<template>
<div>
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-form-item label="数据" prop="data">
<SelectTree placeholder="请选择数据" :treeWidth="500" :treeData="dataList" v-model="form.data"></SelectTree>
</el-form-item>
</el-form>
</div>
</template>
<script>
import SelectTree from './select-tree.vue'
export default {
name: 'SelectTreeTest',
components:{SelectTree},
data() {
return {
form: {
data: []
},
dataList: [
{
value: 'test1',
label: 'test1',
children: [
{
value: '1-v1',
label: 'v1',
parent:'test1',
parentId:'test1'
},
{
value: '1-v2',
label: 'v2',
parent:'test1',
parentId:'test1'
},
]
},
{
value: 'test2',
label: 'test2',
children: [
{
value: '2-v1',
label: 'v1',
parent:'test2',
parentId:'test2'
},
{
value: '2-v2',
label: 'v2',
parent:'test2',
parentId:'test2'
},
]
}
],
rules: {
data: [{ required: true }]
}
}
},
methods: {
},
created() {
},
mounted() {
},
}
</script>
<style lang="less" scoped></style>