> 技术文档 > vue2用elementUI做单选下拉树

vue2用elementUI做单选下拉树


1. 需求

elementUI的form表单如果实现一个下拉树的需求,也不想用级联的功能,就得自己手动搓代码了。

2. 代码

组件代码

<template> <div ref=\"treeSelect\" class=\"tree-select\"> <div class=\"tree-select-dom\" @click=\"handleIconClick\"> <el-input v-model=\"inputValue\" :placeholder=\"placeholder\" :clearable=\"clearable\" :disabled=\"disabled\" readonly @click.native=\"handleInputClick\" @clear=\"clearSelection\" > <i slot=\"suffix\" :class=\"[ \'el-input__icon\', showPopover ? \'el-icon-arrow-up\' : \'el-icon-arrow-down\' ]\" /> </el-input> </div> <el-popover ref=\"popover\" v-model=\"showPopover\" v-click-outside=\"handleClickOutside\" placement=\"bottom-start\" trigger=\"manual\" :width=\"popoverWidth\" popper-class=\"tree-select-popover\" > <div class=\"popover-content\"> <div class=\"tree-filter-input\"> <el-input v-if=\"filterable\" v-model=\"filterText\" placeholder=\"输入关键字过滤\" prefix-icon=\"el-icon-search\" size=\"small\" /> </div> <el-tree ref=\"tree\" class=\"tree-select-tree\" :data=\"data\" :props=\"defaultProps\" :highlight-current=\"true\" :filter-node-method=\"filterNode\" :node-key=\"nodeKey\" :default-expand-all=\"defaultExpandAll\" :expand-on-click-node=\"false\" @node-click=\"handleNodeClick\" > <div slot-scope=\"{ node }\" class=\"custom-tree-node\"> <span :class=\"[\'tree-node-label\', { \'is-leaf\': node.isLeaf }]\">  <i v-if=\"!node.isLeaf\" class=\"el-icon-folder\" />  <i v-else class=\"el-icon-document\" />  {{ node.label }} </span> </div> </el-tree> </div> </el-popover> </div></template>
export default { name: \'TreeSelect\', directives: { // 自定义指令:点击外部区域关闭下拉框 \'click-outside\': { bind: function(el, binding, vnode) { el.clickOutsideEvent = function(event) { if (!(el === event.target || el.contains(event.target))) { vnode.context[binding.expression](event); } }; document.body.addEventListener(\'click\', el.clickOutsideEvent); }, unbind: function(el) { document.body.removeEventListener(\'click\', el.clickOutsideEvent); } } }, props: { // 树形数据 data: { type: Array, default: () => [] }, // 树节点配置 props: { type: Object, default: () => ({}) }, // 选中的节点值 value: { type: [String, Number], default: null }, // 占位文本 placeholder: { type: String, default: \'请选择\' }, // 是否可清空 clearable: { type: Boolean, default: true }, // 是否禁用 disabled: { type: Boolean, default: false }, // 是否可搜索 filterable: { type: Boolean, default: false }, // 是否默认展开所有节点 defaultExpandAll: { type: Boolean, default: false }, // 节点唯一标识字段 nodeKey: { type: String, default: \'id\' }, // 弹出框宽度 popoverWidth: { type: [String, Number], default: 300 } }, data() { return { showPopover: false, filterText: \'\', currentValue: null, currentLabel: \'\', defaultProps: { children: \'children\', label: \'label\' } }; }, computed: { inputValue() { return this.currentLabel || \'\'; } }, watch: { value: { immediate: true, handler(newVal) { this.currentValue = newVal; if (newVal) { this.setCurrentLabel(); } else { this.currentLabel = \'\'; } } }, props: { immediate: true, handler(newProps) { this.defaultProps = Object.assign({}, this.defaultProps, newProps); } }, filterText(val) { this.$refs.tree.filter(val); } }, methods: { // 处理输入框点击 handleInputClick() { if (this.disabled) return; this.togglePopover(); }, // 处理图标点击 handleIconClick(e) { e.stopPropagation(); if (this.disabled) return; this.togglePopover(); }, // 切换下拉框显示状态 togglePopover() { this.showPopover = !this.showPopover; if (this.showPopover) { this.$nextTick(() => { if (this.currentValue) { this.$refs.tree.setCurrentKey(this.currentValue); } }); } }, // 节点点击事件 handleNodeClick(data, node) { if (node.isLeaf) { this.currentValue = data[this.nodeKey]; this.currentLabel = node.label; this.$emit(\'input\', this.currentValue); this.$emit(\'change\', this.currentValue, node); this.showPopover = false; } else { // 非叶子节点点击展开/折叠 node.expanded = !node.expanded; } }, // 设置当前显示的标签 setCurrentLabel() { if (!this.data.length || !this.currentValue) return; const findNode = (data, value) => { for (const node of data) { if (node[this.nodeKey] === value) { return node; } if (node[this.defaultProps.children] && node[this.defaultProps.children].length) { const result = findNode(node[this.defaultProps.children], value); if (result) return result; } } return null; }; const node = findNode(this.data, this.currentValue); if (node) { this.currentLabel = node[this.defaultProps.label]; } }, // 清空选择 clearSelection() { this.currentValue = null; this.currentLabel = \'\'; this.$emit(\'input\', null); this.$emit(\'change\', null); this.showPopover = false; }, // 过滤节点 filterNode(value, data, node) { if (!value) return true; return node.label && node.label.toLowerCase().indexOf(value.toLowerCase()) !== -1; }, // 处理点击外部区域 handleClickOutside() { this.showPopover = false; }, // 获取当前选中的节点 getCurrentNode() { return this.$refs.tree.getCurrentNode(); } }};
<style lang=\"less\" scoped>.tree-select { position: relative; display: inline-block; width: 100%;}.tree-select-dom{ position: relative; &::after{ position: absolute; top: 0; left: 0; width: 100%; height: 100%; content: \'\'; cursor: pointer; }}.tree-select .el-input { cursor: pointer;}.tree-select .el-input__icon { transition: transform 0.3s; cursor: pointer;}.popover-content { padding: 10px; max-height: 300px; overflow-y: auto; background: #fff; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);}.tree-filter-input { margin-bottom: 10px;}.custom-tree-node { flex: 1; display: flex; align-items: center; font-size: 14px; padding: 5px 0;}.tree-node-label { display: flex; align-items: center; transition: all 0.2s;}.tree-node-label:hover { color: #3498db;}.tree-node-label i { margin-right: 8px;}.tree-node-label.is-leaf i { color: #2ecc71;}</style><style lang=\"less\">.tree-select-popover{ background-color: #4e5470; padding: 0; .popover-content{ background-color: #4e5470; display: flex; flex-direction: column; .tree-filter-input{ flex-shrink: 0; } } .tree-select-tree{ flex: 1; overflow-x: hidden; overflow-y: auto; } .el-input__inner{ padding-left: 30px; }}</style>

3. 使用

<template><TreeSingleSelect v-model=\"selectedValue1\" :data=\"treeData\" :prop=\"{ children: \'subitems\', label: \'name\' }\" placeholder=\"请选择部门\" node-key=\"id\" filterable :default-expand-all=\"true\" class=\"demo-component\" /></template>
import TreeSingleSelect from \'@/components/treeSelect/TreeSingleSelect.vue\';export default {components: {TreeSingleSelect },data() {return {selectedValue1: \'\',treeData: [ {  id: 1,  label: \'集团总部\',  children: [ {  id: 101,  label: \'总裁办公室\',  children: [  { id: 1011, label: \'秘书处\' },  { id: 1012, label: \'行政科\' }  ] }, {  id: 102,  label: \'人力资源部\',  children: [  { id: 1021, label: \'招聘组\' },  { id: 1022, label: \'培训组\' },  { id: 1023, label: \'薪酬组\' }  ] }  ] } ]}}}

4. 效果

vue2用elementUI做单选下拉树

求关注 vue2用elementUI做单选下拉树