基于 vue 与 element-ui 组件库封装 tree-select 组件

效果图预览tree-select
组件源码

tree-select.vue

<template>
    <el-select
        class="component tree-select"
        :class="[{
            'disabled': disabled
        },{
            'only-leaf': onlyLeaf
        }]"
        :popper-class="popperClass(
            'component',
            'tree-select',
            'dropdown-wrapper',
            disabled && 'disabled',
            onlyLeaf && 'only-leaf'
        )"
        :value="localValue"

        multiple
        collapse-tags

        filterable
        :filter-method="searchFn"
        @visible-change="e => { !e && searchFn() }"

        @remove-tag="removeItem">
        <el-option
            v-for="item in selectedOptions"
            :key="item[defaultProps.key]"
            :value="item[defaultProps.key]"
            :label="item[defaultProps.label]">
        </el-option>
        <el-option value=""></el-option>
        <div class="disabled-nil" v-if="selectedOptions.length <= 0 && disabled">
            未选择
        </div>
        <el-tree
            class="selected-data-tree not-only-leaf"
            :class="[{
                'disabled': disabled
            }]"
            v-show="selectedOptions.length > 0"
            ref="treeAllEl"
            :default-checked-keys="localValue"
            :default-expand-all="disabled"

            show-checkbox
            check-strictly
            :node-key="defaultProps.key"

            @node-expand="_ => allIsOpen = true"
            @node-collapse="_ => allIsOpen = false"

            check-on-click-node
            @check="e => e.key !== 'root' && removeItem(e[defaultProps.key])"

            :filter-node-method="filterNode"
            :empty-text="disabled ? '无结果' : ''"

            :props="defaultProps"
            :data="[{
                [defaultProps.key]: 'root',
                [defaultProps.label]: '已选',
                [defaultProps.children]: selectedOptions.map(item => ({
                    [defaultProps.key]: item[defaultProps.key],
                    [defaultProps.label]:
                    item[defaultProps.label]
                }))
            }]">
        </el-tree>
        <el-tree
            v-show="!disabled"
            ref="treeEl"
            :default-checked-keys="localValue"

            show-checkbox
            check-strictly
            :node-key="defaultProps.key"

            :check-on-click-node="onlyLeaf"
            @check="e => getValue(e, !multiple ? !selectedOptions[0] || e[defaultProps.key] !== selectedOptions[0][defaultProps.key] : true)"

            :filter-node-method="filterNode"

            :props="defaultProps"
            :data="localOption">
        </el-tree>
    </el-select>
</template>

<script>
let key = ''
let children = ''
let label = ''

export default {
    name: 'TreeSelect',
    props: {
        value: { // 绑定值 优先使用 [6]
            types: Array | Number
        },
        whole: { // 帮定值 [{ key: 6, name: '二级 2-2' }]
            types: Array | Object,
            default: () => []
        },


        options: { // 备选项 { key: 2, name: '一级 2', children: [{ key: 5, name: '二级 2-1' }, { key: 6, name: '二级 2-2' }] }
            type: Array,
            required: true
        },
        props: { // 对应的 key
            type: Object,
            default: () => ({
                key: 'id',
                children: 'children',
                label: 'label'
            })
        },


        disabled: { // 禁用
            type: Boolean,
            default: false
        },
        onlyLeaf: { // 是否只操作叶子节点
            type: Boolean,
            default: true
        },
        multiple: { // 是否多选,默认多选
            type: Boolean,
            default: true
        }
    },
    computed: {
        defaultProps () {
            let result = Object.assign({
                key: 'id',
                children: 'children',
                label: 'label'
            }, this.props)
            result.disabled = e => {
                return this.disabled
                        || this.onlyLeaf
                            && e[children]
                            && e[children].length > 0
            }
            key = result.key
            children = result.children
            label = result.label
            return result
        },
        localValue () {
            if (this.multiple) {
                return this.value || this.whole.map(item => item[this.props.key])
            } else {
                return this.value ? [ this.value ] : [] || [ this.whole[this.props.key] ]
            }
        },
        localOption () {
            this.$nextTick(() => {
                this.selectedOptions = this.$refs.treeEl.getCheckedNodes()
                this.$emit('update:whole', this.selectedOptions.map(item => delete item[children] && item))
            })
            return this.options
        }
    },
    data: () => ({
        allIsOpen: false,
        selectedOptions: []
    }),
    methods: {
        getValue (e, checked = true) {
            if (this.multiple) {
                this.selectedOptions = this.$refs.treeEl.getCheckedNodes()
            } else {
                checked ? this.selectedOptions = [e] : this.selectedOptions = []
                this.$refs.treeEl.setCheckedNodes(this.selectedOptions)
            }
            this.modelEvent()
        },
        removeItem (e) {
            this.selectedOptions = this.selectedOptions.filter(item => item[key] !== e)
            this.$refs.treeEl.setCheckedNodes(this.selectedOptions)
            this.modelEvent()
        },
        popperClass () {
            let classNames = []
            for (let i = 0; i < arguments.length; i++) {
                arguments[i] && classNames.push(arguments[i])
            }
            return classNames.join(' ')
        },
        modelEvent () {
            if (this.multiple) {
                this.$emit('update:whole', this.selectedOptions.map(item => delete item[children] && item))
                this.$emit('input', this.selectedOptions.map(item => item[key]))
            } else {
                this.$emit('update:whole', this.selectedOptions.length > 0 ? delete this.selectedOptions[0][children] && this.selectedOptions[0] : {})
                this.$emit('input', this.selectedOptions.length > 0 ? this.selectedOptions[0][key] : null)
            }
            this.$nextTick(() => {
                this.$refs.treeAllEl.store.nodesMap.root.expanded = this.allIsOpen
            })
        },
        searchFn (val) {
            this.allIsOpen = true
            this.$refs.treeEl.filter(val)
            this.$refs.treeAllEl.filter(val)
        },
        filterNode(value, data) {
            if (!value) return true
            return ((!data[children] || data[children].length <= 0) || !this.onlyLeaf) && data[label].indexOf(value) !== -1
        }
    }
}
</script>

<style lang="scss">
.component.tree-select {
    .el-select-dropdown__item { // 隐藏 select 组件原本的选项
        display: none;
    }

    // disabled 样式
    &.disabled {
        .el-tag__close {
            display: none;
        }
    }
    .disabled-nil {
        font-size: 14px;
        color: #DCDFE6;
        width: 100%;
        text-align: center;
    }
    // disabled 样式 结束
    .not-only-leaf :not(.is-leaf)+.el-checkbox {
        display: none;
    }
    &.only-leaf :not(.is-leaf)+.is-disabled {
        display: none;
    }
    .selected-data-tree:not(.disabled) {
        .el-tree__empty-block {
            display: none;
        }
    }
}
</style>

使用方法

App.vue

<template>
    <div class="select-demos">
        <p>{{selected}} -- {{selectedN}}</p>
        <p>{{wholeSelected}} -- {{wholeSelectedN}}</p>
        <tree-select
            :options="treeData"
            v-model="selected"
            :whole.sync="wholeSelected"
            :disabled="false"
            :onlyLeaf="true"
            :multiple="true"
            :props="{
                key: 'key',
                children: 'children',
                label: 'name'
            }">
        </tree-select>
        <tree-select
            :options="treeData"
            v-model="selectedN"
            :whole.sync="wholeSelectedN"
            :disabled="false"
            :onlyLeaf="true"
            :multiple="false"
            :props="{
                key: 'key',
                children: 'children',
                label: 'name'
            }">
        </tree-select>
    </div>
</template>

<script>
import treeData from '@/assets/config.js'
import TreeSelect from '@/components/TreeSelect.vue'

export default {
    name: 'SelectDemos',
    data: () => ({
        treeData: [],
        selectedN: null,
        wholeSelectedN: {},
        selected: [
            // 9
        ],
        wholeSelected: [
            // {
            //     key: 1,
            //     name: "一级 1"
            // }
        ]
    }),
    mounted () {
        setTimeout(() => {
            this.treeData = treeData
        }, 1000)
    },
    components: {
        TreeSelect
    }
}
</script>

相关配置文件

config.js

export default [{
    key: 1,
    name: '一级 1',
    children: [{
        key: 4,
        name: '二级 1-1',
        children: [{
            key: 9,
            name: '三级 1-1-1'
        }, {
            key: 10,
            name: '三级 1-1-2'
        }]
    }]
}, {
    key: 2,
    name: '一级 2',
    children: [{
        key: 5,
        name: '二级 2-1'
    }, {
        key: 6,
        name: '二级 2-2'
    }]
}, {
    key: 3,
    name: '一级 3',
    children: [{
        key: 7,
        name: '二级 3-1'
    }, {
        key: 8,
        name: '二级 3-2'
    }]
}]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值