自定义下拉框树结构组件
代码如下
<template>
<div class="treeSelect">
<el-select
ref="mySelect"
v-model="valueTitle"
:multiple="multiple"
:collapse-tags="collapse"
:clearable="clearable"
:disabled="disabled"
:size="size"
:style="selectStyle"
@input="$emit('input', $event)"
@clear="handleClear"
@remove-tag="handleRemoveTag"
>
<el-option :value="valueId" :label="label">
<el-tree
id="tree-option"
ref="selectTree"
:accordion="true"
:show-checkbox="multiple"
:data="dataOptions"
:props="treeProps"
:node-key="treeProps.id"
:check-strictly="checkStrictly"
:default-expand-all="expand"
:auto-expand-parent="expandParent"
:expand-on-click-node="expandNode"
:default-expanded-keys="defaultExpandedKeys"
@node-click="handleNodeClick"
@check-change="handleCheckChange"
>
<template v-slot="{ node, data }">
<span class="el-tree-node__label">{{ node.label }}</span>
<span v-if="isShowCount(data)" class="count">({{ data.children.length }})</span>
</template>
</el-tree>
</el-option>
</el-select>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, toRefs, watch, nextTick, computed, onMounted } from "vue";
export default defineComponent({
name: 'tree-select',
props: {
// 是否可多选,默认单选
multiple: {
type: Boolean,
default: false
},
// 可清空选项
clearable: {
type: Boolean,
default: () => { return false }
},
// -------------------- el-tree --------------------
// 配置项
treeProps: {
type: Object,
default: () => {
return {
id: 'id', // ID字段名
label: 'title', // 显示名称
children: 'children' // 子级字段名
}
}
},
// 选项列表数据(树形结构的对象数组)
options: {
type: Array,
default: () => { return [] }
},
// 是否每次只打开一个同级树节点展开
accordion: {
type: Boolean,
default: () => { return false }
},
// 显示节点个数
showCount: {
type: Boolean,
default: () => { return false }
},
// 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 false
checkStrictly: {
type: Boolean,
default: () => { return false }
},
// 是否展开所有节点,默认展开
expand: {
type: Boolean,
default() {
return true;
}
},
// 展开子节点的时候是否自动展开父节点 默认值为 true
expandParent: {
type: Boolean,
default() {
return true;
}
},
// 是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。
expandNode: {
type: Boolean,
default() {
return true;
}
},
// -------------------- el-select --------------------
// 初始值 - 绑定value是为了外面也可以传值改变到里面的值双向绑定
value: {
type: [String, Number, Boolean, Array],
default: () => { return 0 }
},
// 多选时是否将选中值按文字的形式展示
collapse: {
type: Boolean,
default: false
},
// 选择框大小
size: {
type: String,
default: () => { return 'small' }
},
// 选择框 宽度
width: {
type: String,
default: '270px'
},
// 是否禁用
disabled: {
type: Boolean,
default: () => { return false }
}
},
setup(props: any, { emit }) {
const formatProps = computed(() => {
return JSON.parse(JSON.stringify(JSON.parse(JSON.stringify(props))))
})
const selectTree = ref()
const mySelect = ref()
const data = reactive({
valueId: <any>formatProps.value.value ? formatProps.value.value : 0,
valueTitle: '',
label: '', // 分组
defaultExpandedKeys: <any>[],
})
/**
* @description: 初始化组件
* @param {*}
* @return {*}
*/
const initHandle = () => {
// 单选
if (!formatProps.value.multiple) {
if (selectTree.value) {
var node = selectTree.value.getNode(data.valueId);
if (node) {
data.valueTitle = node.data[formatProps.value.treeProps.label]; // 初始化显示
data.defaultExpandedKeys = [data.valueId]; // 设置默认展开
nextTick(() => {
selectTree.value.setCurrentKey(data.valueId); // 设置默认选中
})
}
}
}
// 多选
if (data.valueId && formatProps.value.multiple) {
data.defaultExpandedKeys = data.valueId; // 设置默认展开
nextTick(() => {
selectTree.value.setCheckedKeys(data.valueId); // 设置默认选中
})
}
}
/**
* @description: 单选 - 节点被点击时的回调,返回被点击的节点数据
* @param {*}
* @return {*}
*/
const handleNodeClick = (node: any) => {
data.valueTitle = node[formatProps.value.treeProps.label];
data.valueId = node[formatProps.value.treeProps.id];
data.defaultExpandedKeys = <any>[];
//单选点击一项收起下拉框
if (mySelect.value && !formatProps.value.multiple) {
mySelect.value.blur()
}
emit('getValue', data.valueId, data.valueTitle);
}
/**
* @description: 多选,节点勾选状态发生变化时的回调
* @param {*}
* @return {*}
*/
const handleCheckChange = (val: any, checked: any) => {
let currentNode = selectTree.value.getNode(val);
if (formatProps.value.checkStrictly) {
// 用于:父子节点严格互不关联时,父节点勾选变化时通知子节点同步变化,实现单向关联
if (checked) {
// 选中 子节点只要被选中父节点就被选中
parentNodeChange(currentNode);
} else {
// 未选中 处理子节点全部未选中
childNodeChange(currentNode);
}
}
// 用于:父子节点严格关联时
// console.log(data.$refs.selectTree.getHalfCheckedKeys())
data.valueId = selectTree.value.getCheckedKeys();
var checkedNodes = selectTree.value.getCheckedNodes();
data.valueTitle = checkedNodes.map((node: any) => {
return node[formatProps.value.treeProps.label];
});
data.defaultExpandedKeys = [];
emit('getValue', data.valueId, data.valueTitle);
}
/**
* @description: 清除选中
* @param {*}
* @return {*}
*/
const handleClear = () => {
data.valueTitle = '';
data.valueId = '';
data.defaultExpandedKeys = [];
// 清除树
if (formatProps.value.multiple) {
selectTree.value.setCheckedKeys(null);
} else {
selectTree.value.setCurrentKey(null);
}
emit('getValue', 0);
}
/**
* @description: 多选 删除任一标签选项的回调
* @param {*}
* @return {*}
*/
const handleRemoveTag = (val: any) => {
var checkedNodes = selectTree.value.getCheckedNodes();
var node = checkedNodes.find((node: any) => node[formatProps.value.treeProps.label] === val);
selectTree.value.setChecked(node[formatProps.value.treeProps.id], false);
}
// 统一处理子节点为不选中
const childNodeChange = (node: any) => {
for (let i = 0; i < node.childNodes.length; i++) {
node.childNodes[i].checked = false;
childNodeChange(node.childNodes[i]);
}
}
// 统一处理父节点为选中
const parentNodeChange = (node: any) => {
if (node.parent.key !== undefined) {
node.parent.checked = true;
parentNodeChange(node.parent);
}
}
/**
* formatProps.value.options
* formatProps.value.treeProps
*/
const listToTree = (list: any, config: any) => {
let conf = {};
Object.assign(conf, config);
const nodeMap = new Map();
const result = <any>[];
const { id, children, pid }: any = conf;
for (const node of list) {
nodeMap.set(node[id], node);
}
for (const node of list) {
const parent = nodeMap.get(node[pid]);
(parent ? (parent.children ? parent.children : parent.children = []) : result).push(node);
}
return result;
}
const dataOptions = computed(() => {
return listToTree(formatProps.value.options, formatProps.value.treeProps)
})
const isShowCount = (val: any) => {
return val.children && val.children.length && formatProps.value.showCount
}
onMounted(() => {
initHandle()
})
watch(() => formatProps.value.value, (newVal, oldVal) => {
data.valueId = newVal
initHandle()
})
watch(() => data.valueId, (newVal, oldVal) => {
emit('input', newVal);
})
const selectStyle = computed(() => {
return {
width: `${formatProps.value.width}`
};
})
return {
handleClear,
handleNodeClick,
handleCheckChange,
handleRemoveTag,
selectStyle,
selectTree,
dataOptions,
mySelect,
isShowCount,
...toRefs(data)
}
},
})
</script>
<style lang="scss" scoped>
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
height: auto;
padding: 0;
overflow: hidden;
}
.el-scrollbar .el-select-dropdown__item.selected {
font-weight: normal;
}
// 横向滚动条
.el-scrollbar__bar.is-horizontal {
height: 6px;
left: 2px;
}
// 纵向滚动条
.el-scrollbar__bar.is-vertical {
width: 6px;
top: 2px;
}
// 字体和大小
.custom-tree-node {
font-family: "Microsoft YaHei";
font-size: 14px;
position: relative;
}
// 原生el-tree-node的div是块级元素,需要改为inline-block,才能显示滚动条
.treeSelect .el-tree > .el-tree-node {
display: inline-block;
min-width: 100%;
}
// ul li ::v-deep .el-tree .el-tree-node__content {
// height: auto;
// padding: 0 20px;
// }
// .el-tree-node__label {
// font-weight: normal;
// }
.el-tree ::v-deep .is-current .el-tree-node__label {
color: #1b65b9;
font-weight: 700;
}
.el-tree ::v-deep .is-current .el-tree-node__children .el-tree-node__label {
color: #606266;
font-weight: normal;
}
.count {
opacity: 0.7;
}
</style>
2022-01-14
记录下使用了的组件