父组件使用
<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>