
组件调用
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- ${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}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 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 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 6px;
217 background: #b4bccc;
218 }
219 .select-tree::-webkit-scrollbar-track-piece {
220 background: #fff;
221 6px;
222 }
223 </style>