vue自定义组件-下拉框

学习目标:

自定义下拉框树结构组件


学习内容:

代码如下

<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>

效果图
vue自定义组件-下拉框_第1张图片


学习时间:

2022-01-14


总结

记录下使用了的组件

你可能感兴趣的:(前端,vue组件,vue.js,javascript,前端)