基于elementui封装的el-tree-select 支持单选多选懒加载

2,698 阅读3分钟
<template>    <el-select        ref="select"        :value="scope.value"        class="fd-tree-select"        popper-class="fd-tree-select-poper"        :clearable="clearable"        :disabled="disabled"        :filterable="filterable"        :placeholder="placeholder"        :multiple="multiple"        :collapse-tags="collapseTags"        :valueKey="valueKey || getValueKey"        :filter-method="selectFilterNode"        @clear="clear">        <el-tree            ref="tree"            :node-key="scope.props.value"            class="fd-tree"            :props="scope.props"            :data="scope.data"            :show-checkbox="multiple"            :default-expand-all="defaultExpandAll"            :accordion="accordion"            :indent="indent"            :filter-node-method="treeFilterNode"            :default-checked-keys="defaultCheckedKeys"            :default-expanded-keys="defaultExpandKeys"            :check-on-click-node="true"            :expand-on-click-node="false"            :lazy="lazy"            :load="loadNode"            @check="clickCheck"            @node-click="clickNode">            <el-option                ref="option"                slot-scope="{ node, data }"                :label="data[scope.props.label]"                :value="data[scope.props.value]"                :title="data[scope.props.label]"                class="fd-tree-option">            </el-option>        </el-tree>    </el-select></template><script>export default {    name: 'DsTreeSelect',    props: {        // tree数据        data: Array,        // v-model绑定数据        value: [String, Array],        // 是否可以清空        clearable: Boolean,        // 为空的时候文字提示        placeholder: String,        // 是否禁用        disabled: Boolean,        // 是否支持筛选        filterable: Boolean,        // 作为 value 唯一标识的键名,绑定值为对象类型时必填        valueKey: String,        // 多选时是否将选中值按文字的形式展示        collapseTags: Boolean,        // 是否多选        multiple: Boolean,        // 是否展开树所有的节点        defaultExpandAll: Boolean,        // 是否每次只打开一个同级树节点展开        accordion: Boolean,        // 相邻级节点间的水平缩进,单位为像素        indent: Number,        // 是否懒加载        lazy: Boolean,        // 自定义配置项        props: Object    },    data() {        return {            scope: {                // tree数据                data: this.data,                // v-mode绑定数据                value: this.value,                // 自定义配置项                props: this.props            }        };    },    // 计算属性    computed: {        // 默认选中的节点的 key 的数组        defaultCheckedKeys() {            // 获取内部v-model绑定数据            const {value, props} = this.scope;            // 最终返回的list            const _dataList = [];            // 判断v-model绑定值是否是数组类型 数组类型代表多选            if (Array.isArray(value)) {                // 数组类型循环                for (const item of value) {                    // 生成新的list 字符串类型直接返回 object类型 返回props配置的value值                    _dataList.push((typeof item === 'string' || typeof item === 'number') ? item : item[props.value]);                }            } else { // 字符串类型代表单选                _dataList.push(value);            }            return _dataList;        },        // 默认展开的节点的 key 的数组        defaultExpandKeys() {            // 判断是否是懒加载            let _dataList = [];            if (this.lazy) {                for (const item of this.defaultCheckedKeys) {                    const arr = item.split('-');                    for (let i = 0; i < arr.length - 1; i++) {                        _dataList.push(arr.slice(0, i + 1).join('-'));                    }                }            } else {                _dataList = JSON.parse(JSON.stringify(this.defaultCheckedKeys));            }            return _dataList;        },        // 当选中的是对象的时候需要设置value-key        getValueKey() {            // 获取value            const {value} = this.scope;            return (typeof value === 'string' || typeof value === 'number') ? value : '';        }    },    // 监听值变化    watch: {        // 监听组件外部输入值变化 更新到组件内部        value: {            deep: true,            handler(newVal) {                this.scope.value = JSON.parse(JSON.stringify(newVal));            }        },        // 监听自定义配置项 更新到当前组件内部        props: {            immediate: true,            deep: true,            handler(newVal) {                this.scope.props = Object.assign({}, {                    value: 'label', // ID字段名                    label: 'label', // 显示名称                    children: 'children', // 子级字段名                    isLeaf: 'leaf' // 叶子节点字段名                }, newVal);            }        }    },    // 页面的方法    methods: {        // 树组件搜索方法        treeFilterNode(value, data) {            // 获取配置项            const {props} = this.scope;            // 没输入的时候返回全部的节点            if (!value) return true;            // 对label进行过滤            return data[props.label].indexOf(value) !== -1;        },        // 下拉自定义过滤方法        selectFilterNode(value) {            // 调用tree的filter方法            this.$refs.tree.filter(value);        },        // 多选框点击事件        clickCheck(data, node) {            if (this.multiple) {                // 获取全选的所有父节点list                const parent = this.getSimpleCheckedNodes(this.$refs.tree.store);                this.$emit('input', node.checkedKeys.filter(item => {                    return !parent.length || !parent.includes(item);                }));            }        },        // 树节点点击的事件        clickNode(data, node, component) {            const {props} = this.scope;            // 单选的时候点击节点就选中节点 此处注意 不能选中存在子节点的节点            if (!this.multiple && (!data[props.children] || !data[props.children].length)) {                this.$emit('input', JSON.parse(JSON.stringify(data[props.value])));                // 此处需要隐藏弹窗                this.$refs.select.visible = false;            }        },        // 懒加载获取数据方法        loadNode(node, resolve) {            this.$emit('load', node, resolve);        },        // 子节点选中 只返回父节点        getSimpleCheckedNodes(store) {            const {props} = this.scope;            const checkedNodes = [];            const traverse = function(node) {                const childNodes = node.root ? node.root.childNodes : node.childNodes;                for (const child of childNodes) {                    if (child.checked && child.childNodes.length) {                        checkedNodes.push(child.data[props.value]);                    }                    if (child.indeterminate) {                        traverse(child);                    }                }            };            traverse(store);            return checkedNodes;        },        //  清空        clear() {            if (Array.isArray(this.scope.value)) {                return this.$emit('input', []);            }            this.$emit('input', '');        }    }};</script><style lang="less">  /* tree-select 弹层样式 */  .fd-tree-select-poper{    width: 0px;    &.is-multiple .fd-tree-option{      /* hover selected样式初始化 */      &.hover, &.selected{        color: inherit !important;        background: inherit !important;        font-weight: 400 !important;      }    }  }  /* tree结构样式 */  .fd-tree{    .fd-tree-option{      position: relative;      flex: 1;      pointer-events:none;      padding: 0 5px;      height: 26px !important;      line-height: 26px !important;      /* hover selected样式初始化 */      &.hover, &.selected{        background: inherit !important;      }      /* 去掉option勾号 */      &:after{        content: '' !important;      }    }  }</style>