vue3+element-puls 自定义TreeSelect 树形选择多选

  • Post author:
  • Post category:vue


父组件使用

<treeSelect
v-model="form.assignCreateUids"
:options="dict.treeWithDeptUsers"
:defaultProps="{ label: 'name' }"
/>

子组件代码

<template>
    <el-select :collapse-tags="true" v-model="data.selectValue" multiple :placeholder="props.placeholder"
        :filterable="props.filterable" :filter-method="dataFilter" :popper-append-to-body="props.appendToBody"
        @remove-tag="removeTag" @clear="clearAll" :clearable="props.isCanDelete" style="width:100%;"
        :disabled="props.disabledSelect">
        <el-option :value="data.selectTree" v-loading="data.treeLoading"
            element-loading-background="rgba(255, 255, 255, 0.5)" element-loading-text="loading" class="option-style"
            disabled>
            <el-scrollbar height="260px">
                <div class="check-box">
                    <el-button text link :icon="Checked" @click="handlecheckAll">全选</el-button>
                    <el-button text link :icon="Failed" @click="handleReset">清空</el-button>
                    <el-button text link :icon="DocumentAdd" @click="handleReverseCheck">反选</el-button>
                    <el-button text link :type="data.state === '' ? 'primary' : ''" :icon="Grid"
                        @click="handleState('')">全部</el-button>
                    <el-button text link :type="data.state === 0 ? 'primary' : ''" :icon="Avatar"
                        @click="handleState(0)">在职</el-button>
                    <el-button text link :type="data.state === 1 ? 'primary' : ''" :icon="Promotion"
                        @click="handleState(1)">离职</el-button>
                    
                </div>
                <el-tree :data="data.options" :props="props.defaultProps" ref="treeNode" show-checkbox
                    :node-key="props.defaultProps.value" :filter-node-method="filterNode"
                    :default-checked-keys="props.defaultValue" :default-expanded-keys="props.defaultValue"
                    :check-strictly="props.checkStrictly" @check-change="handleNodeChange">
                </el-tree>
            </el-scrollbar>

        </el-option>
    </el-select>
</template>

<script setup>
import { ref, reactive, nextTick, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { Checked, Failed, DocumentAdd, Avatar, Promotion, Grid } from '@element-plus/icons-vue'
import request from "@/utils/request"
const treeNode = ref(null)
const emits = defineEmits(["update:modelValue", 'changeSelectDataList'])
const props = defineProps(
    {
        // 绑定值
        modelValue: {
            type: Array,
            default: [],
        },
        //编辑时回显的数组
        defaultValue: {
            type: Array,
            default: () => ([])
        },
        //可用选项的数组
        options: {
            type: Array,
            default: () => ([])
        },
        // 配置选项
        defaultProps: {
            type: Object,
            default: () => ({ // 属性值为后端返回的对应的字段名
                children: 'children',
                label: 'name',
                value: 'id'
            })
        },
        // 是否将组件添加到body上面(组件在弹窗或者表格里面时可设为true)
        appendToBody: {
            type: Boolean,
            default: false
        },
        // 是否可搜索
        filterable: {
            type: Boolean,
            default: true // 不可以搜索
        },
        // 是否禁用下拉框
        disabledSelect: {
            type: Boolean,
            default: false
        },
        // 父子不互相关联
        checkStrictly: {
            type: Boolean,
            default: false // 关联
        },
        // 父类id字段名 (如果父子联动则必传该字段,不联动则不用传)
        parentValue: {
            type: String,
            default: 'parentValue'
        },
        // 回显的值是否可被删除 true: 可以删除;false:不能删除
        isCanDelete: {
            type: Boolean,
            default: true
        },
        placeholder: {
            type: String,
            default: '请选择'
        },
        // 不可删除报错
        errMessage: {
            type: String,
            default: '该选项不可被删除'
        }
    }
)


const data = reactive({
    selectTree: [], // 绑定el-option的值
    selectValue: props.defaultValue, // 文本框中的标签
    VALUE_NAME: props.defaultProps.value, // value转换后的字段
    VALUE_TEXT: props.defaultProps.label, // label转换后的字段
    treeLoading: false, // 加载loading~
    options: props.options, // 选项数组
    state: 0 // "" -- 全部   0 -- 在职   1 -- 离职
})

watch(() => props.modelValue, (val) => {
    if (val.length === 0) {
        data.selectValue = []
        handleReset()
    }
})

const selectDefaultValue = (val) => {
    if (val.length) {
        nextTick(() => {
            let datalist = treeNode.value.getCheckedNodes(true)
            if (!props.checkStrictly) {
                const parentList = datalist.filter(v => v[props.defaultProps.children]).map(v => v[data.VALUE_NAME])
                datalist = datalist.filter(v => parentList.indexOf(v[props.parentValue]) === -1)
            }
            data.selectTree = datalist
            data.selectValue = datalist.map(v => v[data.VALUE_TEXT])
        })
    }
}

watch(() => props.options, (val) => {
    data.options = val
    selectDefaultValue(val)
})

watch(() => props.defaultValue, (val) => {
    selectDefaultValue(val)
}, { immediate: true })

// 查询在职离职业务员
const handleState = (type) => {
    data.state = type
    data.treeLoading = true
    request({ url: `请求地址`, method: "GET" }).then(res => {
        data.options = res.data.data
        data.treeLoading = false
    })
}

// 全选
const handlecheckAll = () => {
    data.treeLoading = true
    setTimeout(() => {
        treeNode.value.setCheckedNodes(data.options)
        data.treeLoading = false
    }, 200)
}
// 清空
const handleReset = () => {
    if (props.isCanDelete) {
        data.treeLoading = true
        setTimeout(() => {
            treeNode.value?.setCheckedNodes([])
            data.treeLoading = false
        }, 200)
    } else {
        ElMessage.error(props.errMessage)
    }
}
/**
 * @description: 反选处理方法
 * @param {*} nodes 整个tree的数据
 * @param {*} refs  this.$refs.treeNode
 * @param {*} flag  选中状态
 * @param {*} seleteds 当前选中的节点
 * @return {*}
 */
const batchSelect = (nodes, refs, flag, seleteds) => {
    if (Array.isArray(nodes)) {
        nodes.forEach(element => {
            refs.setChecked(element, flag, true)
        })
    }
    if (Array.isArray(seleteds)) {
        seleteds.forEach(node => {
            refs.setChecked(node, !flag, true)
        })
    }
}
// 反选
const handleReverseCheck = () => {
    if (props.isCanDelete) {
        data.treeLoading = true
        setTimeout(() => {
            let res = treeNode.value
            let nodes = res.getCheckedNodes(true, true)
            batchSelect(data.options, res, true, nodes)
            data.treeLoading = false
        }, 200)
    } else {
        ElMessage.error(props.errMessage)
    }
}
// 输入框关键字
const dataFilter = (val) => {
    treeNode.value.filter(val)
}
/**
 * @description: tree搜索过滤
 * @param {*} value 搜索的关键字
 * @param {*} dataValue  筛选到的节点
 * @return {*}
 */
const filterNode = (value, dataValue) => {
    if (!value) return true
    return dataValue[props.defaultProps.label].toLowerCase().indexOf(value.toLowerCase()) !== -1
}
/**
 * @description: 勾选树形选项
 * @param {*} dataValue 该节点所对应的对象
 * @param {*} self 节点本身是否被选中
 * @param {*} child 节点的子树中是否有被选中的节点
 * @return {*}
 */
const handleNodeChange = (dataValue, self, child) => {
    const flag = props.defaultValue.some(v => v === dataValue[data.VALUE_NAME])
    let datalist = treeNode.value.getCheckedNodes(true)
    if (!self && !props.isCanDelete && flag) {
        ElMessage.error(props.errMessage)
        treeNode.value.setChecked(dataValue, true, true)
    }
    if (!props.checkStrictly) { // 如果联动则需处理父子值关系
        const parentList = datalist.filter(v => v[props.defaultProps.children]).map(v => v[data.VALUE_NAME])
        datalist = datalist.filter(v => parentList.indexOf(v[props.parentValue]) === -1)
    }
    data.selectTree = datalist
    data.selectValue = datalist.map(v => v[data.VALUE_TEXT])
    emits('changeSelectDataList', data.selectTree)
    emits("update:modelValue", handleValue(data.selectTree))
}
// 移除单个标签
const removeTag = (tagName) => {
    const flagName = data.selectTree.filter(v => v[data.VALUE_NAME] === props.defaultValue.find(item => item === v[data.VALUE_NAME])).map(v => v[data.VALUE_TEXT])
    const flag = flagName.includes(tagName)
    if (props.isCanDelete) { // 判断回显的值是否可删除
        data.selectTree = data.selectTree.filter(v => v[data.VALUE_TEXT] !== tagName)
        const selectTreeValue = data.selectTree.map(v => v[data.VALUE_NAME])
        let setlist = treeNode.value.getCheckedNodes()
        setlist = setlist.filter(v => v[data.VALUE_NAME] === selectTreeValue.find(item => item === v[data.VALUE_NAME]))
        nextTick(() => {
            treeNode.value.setCheckedNodes(setlist)
        })
        emits('changeSelectDataList', data.selectTree)
        emits("update:modelValue", handleValue(data.selectTree))
    } else {
        if (!flag) {  // 判断回显时新增的是否可删除
            data.selectTree = data.selectTree.filter(v => v[data.VALUE_TEXT] !== tagName)
            const selectTreeValue = data.selectTree.map(v => v[data.VALUE_NAME])
            let setlist = treeNode.value.getCheckedNodes()
            setlist = setlist.filter(v => v[data.VALUE_NAME] === selectTreeValue.find(item => item === v[data.VALUE_NAME]))
            nextTick(() => {
                treeNode.value.setCheckedNodes(setlist)
            })
            emits('changeSelectDataList', data.selectTree)
            emits("update:modelValue", handleValue(data.selectTree))
        } else {
            data.selectValue = data.selectTree.map(v => v[data.VALUE_TEXT])
            ElMessage.error(data.errMessage)
        }
    }
}
// 文本框清空
const clearAll = () => {
    data.selectTree = []
    treeNode.value.setCheckedNodes([])
    emits('changeSelectDataList', data.selectTree)
    emits("update:modelValue", handleValue(data.selectTree))
}
// 返回值处理
const handleValue = (val) => {
    return val.map(v => v.id)
}
</script>


<style lang="scss" scoped>
.check-box {
    padding: 0 16px;
}

:deep(.el-scrollbar) {
    height: 300px;

    .el-select-dropdown__wrap {
        max-height: 300px;
        overflow: hidden;

        .el-select-dropdown__list {
            padding: 0;
        }
    }
}


.option-style {
    height: 260px !important;
    padding: 0 0 10px 0 !important;
    margin: 0;
}
</style>



版权声明:本文为Jie0817原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。