组件调用
1 <template> 2 <!-- 行模式 --> 3 <el-form inline> 4 <el-form-item label="inline 默认:"> 5 <select-tree :options="options" v-model="selected" /> 6 </el-form-item> 7 <el-form-item label="inline 定义宽度:"> 8 <select-tree width="200" :options="options" v-model="selected" /> 9 </el-form-item> 10 </el-form> 11 <!-- 块模式 --> 12 <el-form> 13 <el-form-item label="自适应:"> 14 <select-tree v-model="selected" :options="options" :props="defaultProps" /> 15 </el-form-item> 16 </el-form> 17 </template> 18 19 <script> 20 import SelectTree from '@/components/widget/SelectTree.vue'; 21 22 export default { 23 name: 'about', 24 components: { 25 SelectTree, 26 }, 27 data() { 28 return { 29 // 默认选中值 30 selected: 'A', 31 // 数据默认字段 32 defaultProps: { 33 parent: 'parentId', // 父级唯一标识 34 value: 'id', // 唯一标识 35 label: 'label', // 标签显示 36 children: 'children', // 子级 37 }, 38 // 数据列表 39 options: [ 40 { 41 parentId: '0', 42 id: 'A', 43 label: 'label-A', 44 children: [ 45 { 46 parentId: 'A', 47 id: 'A-1', 48 label: 'label-A-1', 49 }, 50 ], 51 }, 52 { 53 parentId: '0', 54 value: 'B', 55 label: 'label-B', 56 children: [], 57 }, 58 ], 59 }; 60 }, 61 }; 62 </script>
SelectTree.vue
1 <!-- 树状选择器 --> 2 <template> 3 <el-popover 4 ref="popover" 5 placement="bottom-start" 6 trigger="click" 7 @show="onShowPopover" 8 @hide="onHidePopover"> 9 <el-tree 10 ref="tree" 11 class="select-tree" 12 highlight-current 13 :style="`min-width: ${treeWidth}`" 14 :data="data" 15 :props="props" 16 :expand-on-click-node="false" 17 :filter-node-method="filterNode" 18 :default-expand-all="false" 19 @node-click="onClickNode"> 20 </el-tree> 21 <el-input 22 slot="reference" 23 ref="input" 24 v-model="labelModel" 25 clearable 26 :style="`width: ${width}px`" 27 :class="{ 'rotate': showStatus }" 28 suffix-icon="el-icon-arrow-down" 29 :placeholder="placeholder"> 30 </el-input> 31 </el-popover> 32 </template> 33 34 <script> 35 export default { 36 name: 'Pagination', 37 props: { 38 // 接收绑定参数 39 value: String, 40 // 输入框宽度 41 width: String, 42 // 选项数据 43 options: { 44 type: Array, 45 required: true, 46 }, 47 // 输入框占位符 48 placeholder: { 49 type: String, 50 required: false, 51 default: '请选择', 52 }, 53 // 树节点配置选项 54 props: { 55 type: Object, 56 required: false, 57 default: () => ({ 58 parent: 'parentId', 59 value: 'rowGuid', 60 label: 'areaName', 61 children: 'children', 62 }), 63 }, 64 }, 65 // 设置绑定参数 66 model: { 67 prop: 'value', 68 event: 'selected', 69 }, 70 computed: { 71 // 是否为树状结构数据 72 dataType() { 73 const jsonStr = JSON.stringify(this.options); 74 return jsonStr.indexOf(this.props.children) !== -1; 75 }, 76 // 若非树状结构,则转化为树状结构数据 77 data() { 78 return this.dataType ? this.options : this.switchTree(); 79 }, 80 }, 81 watch: { 82 labelModel(val) { 83 if (!val) { 84 this.valueModel = ''; 85 } 86 this.$refs.tree.filter(val); 87 }, 88 value(val) { 89 this.labelModel = this.queryTree(this.data, val); 90 }, 91 }, 92 data() { 93 return { 94 // 树状菜单显示状态 95 showStatus: false, 96 // 菜单宽度 97 treeWidth: 'auto', 98 // 输入框显示值 99 labelModel: '', 100 // 实际请求传值 101 valueModel: '0', 102 }; 103 }, 104 created() { 105 // 检测输入框原有值并显示对应 label 106 if (this.value) { 107 this.labelModel = this.queryTree(this.data, this.value); 108 } 109 // 获取输入框宽度同步至树状菜单宽度 110 this.$nextTick(() => { 111 this.treeWidth = `${(this.width || this.$refs.input.$refs.input.clientWidth) - 24}px`; 112 }); 113 }, 114 methods: { 115 // 单击节点 116 onClickNode(node) { 117 this.labelModel = node[this.props.label]; 118 this.valueModel = node[this.props.value]; 119 this.onCloseTree(); 120 }, 121 // 偏平数组转化为树状层级结构 122 switchTree() { 123 return this.cleanChildren(this.buildTree(this.options, '0')); 124 }, 125 // 隐藏树状菜单 126 onCloseTree() { 127 this.$refs.popover.showPopper = false; 128 }, 129 // 显示时触发 130 onShowPopover() { 131 this.showStatus = true; 132 this.$refs.tree.filter(false); 133 }, 134 // 隐藏时触发 135 onHidePopover() { 136 this.showStatus = false; 137 this.$emit('selected', this.valueModel); 138 }, 139 // 树节点过滤方法 140 filterNode(query, data) { 141 if (!query) return true; 142 return data[this.props.label].indexOf(query) !== -1; 143 }, 144 // 搜索树状数据中的 ID 145 queryTree(tree, id) { 146 let stark = []; 147 stark = stark.concat(tree); 148 while (stark.length) { 149 const temp = stark.shift(); 150 if (temp[this.props.children]) { 151 stark = stark.concat(temp[this.props.children]); 152 } 153 if (temp[this.props.value] === id) { 154 return temp[this.props.label]; 155 } 156 } 157 return ''; 158 }, 159 // 将一维的扁平数组转换为多层级对象 160 buildTree(data, id = '0') { 161 const fa = (parentId) => { 162 const temp = []; 163 for (let i = 0; i < data.length; i++) { 164 const n = data[i]; 165 if (n[this.props.parent] === parentId) { 166 n.children = fa(n.rowGuid); 167 temp.push(n); 168 } 169 } 170 return temp; 171 }; 172 return fa(id); 173 }, 174 // 清除空 children项 175 cleanChildren(data) { 176 const fa = (list) => { 177 list.map((e) => { 178 if (e.children.length) { 179 fa(e.children); 180 } else { 181 delete e.children; 182 } 183 return e; 184 }); 185 return list; 186 }; 187 return fa(data); 188 }, 189 }, 190 }; 191 </script> 192 193 <style> 194 .el-input.el-input--suffix { 195 cursor: pointer; 196 overflow: hidden; 197 } 198 .el-input.el-input--suffix.rotate .el-input__suffix { 199 transform: rotate(180deg); 200 } 201 .select-tree { 202 max-height: 350px; 203 overflow-y: scroll; 204 } 205 /* 菜单滚动条 */ 206 .select-tree::-webkit-scrollbar { 207 z-index: 11; 208 width: 6px; 209 } 210 .select-tree::-webkit-scrollbar-track, 211 .select-tree::-webkit-scrollbar-corner { 212 background: #fff; 213 } 214 .select-tree::-webkit-scrollbar-thumb { 215 border-radius: 5px; 216 width: 6px; 217 background: #b4bccc; 218 } 219 .select-tree::-webkit-scrollbar-track-piece { 220 background: #fff; 221 width: 6px; 222 } 223 </style>