xt-element-ui 2.1.4 → 2.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xt-element-ui",
3
- "version": "2.1.4",
3
+ "version": "2.1.5",
4
4
  "description": "基于 Vue 2.7 + ElementUI 的企业级组件库,提供丰富的自定义组件和扩展组件",
5
5
  "main": "lib/index.common.js",
6
6
  "module": "lib/index.esm.js",
@@ -20,11 +20,17 @@
20
20
  :style="customStyle"
21
21
  @mouseenter="handleMouseEnter"
22
22
  >
23
- <slot name="prefix">{{ prefix }}</slot>
23
+ <template v-if="$slots.prefix">
24
+ <slot name="prefix"></slot>
25
+ </template>
26
+ <template v-else-if="prefix">{{ prefix }}</template>
24
27
  <slot>
25
28
  <template v-if="formattedValue !== undefined">{{ formattedValue }}</template>
26
29
  </slot>
27
- <slot name="suffix">{{ suffix }}</slot>
30
+ <template v-if="$slots.suffix">
31
+ <slot name="suffix"></slot>
32
+ </template>
33
+ <template v-else-if="suffix">{{ suffix }}</template>
28
34
  </span>
29
35
  </el-tooltip>
30
36
  <span
@@ -41,11 +47,17 @@
41
47
  ]"
42
48
  :style="customStyle"
43
49
  >
44
- <slot name="prefix">{{ prefix }}</slot>
50
+ <template v-if="$slots.prefix">
51
+ <slot name="prefix"></slot>
52
+ </template>
53
+ <template v-else-if="prefix">{{ prefix }}</template>
45
54
  <slot>
46
55
  <template v-if="formattedValue !== undefined">{{ formattedValue }}</template>
47
56
  </slot>
48
- <slot name="suffix">{{ suffix }}</slot>
57
+ <template v-if="$slots.suffix">
58
+ <slot name="suffix"></slot>
59
+ </template>
60
+ <template v-else-if="suffix">{{ suffix }}</template>
49
61
  </span>
50
62
  </template>
51
63
 
@@ -0,0 +1,8 @@
1
+ import XtTransferTree from './index.vue'
2
+
3
+ XtTransferTree.install = function (Vue) {
4
+ Vue.component(XtTransferTree.name, XtTransferTree)
5
+ }
6
+
7
+ export default XtTransferTree
8
+ export { XtTransferTree }
@@ -0,0 +1,494 @@
1
+ <template>
2
+ <div class="xt-transfer-tree">
3
+ <div class="xt-transfer-tree__panel xt-transfer-tree__source">
4
+ <div class="xt-transfer-tree__header">
5
+ <span class="xt-transfer-tree__title">{{ leftTitle }}</span>
6
+ <span class="xt-transfer-tree__count">{{ leftCheckedCount }}/{{ leftTotalCount }}</span>
7
+ </div>
8
+ <div class="xt-transfer-tree__search" v-if="filterable">
9
+ <el-input
10
+ v-model="leftFilterText"
11
+ placeholder="搜索"
12
+ prefix-icon="el-icon-search"
13
+ size="small"
14
+ />
15
+ </div>
16
+ <div class="xt-transfer-tree__body">
17
+ <el-tree
18
+ ref="leftTree"
19
+ :data="sourceData"
20
+ :props="treeProps"
21
+ :default-expand-all="defaultExpandAll"
22
+ :filter-node-method="filterLeftNode"
23
+ :show-checkbox="showCheckbox"
24
+ :check-strictly="!cascade"
25
+ :default-checked-keys="leftCheckedKeys"
26
+ @check-change="handleLeftCheckChange"
27
+ @node-click="handleLeftNodeClick"
28
+ >
29
+ <span class="xt-transfer-tree__node" slot-scope="{ node, data }">
30
+ <span>{{ node.label }}</span>
31
+ <span v-if="data.children && data.children.length" class="xt-transfer-tree__node-count">
32
+ ({{ data.children.length }})
33
+ </span>
34
+ </span>
35
+ </el-tree>
36
+ </div>
37
+ </div>
38
+
39
+ <div class="xt-transfer-tree__buttons">
40
+ <el-button
41
+ type="primary"
42
+ :disabled="!leftCheckedCount"
43
+ @click="transferToRight"
44
+ :size="buttonSize"
45
+ >
46
+ <i class="el-icon-arrow-right"></i>
47
+ </el-button>
48
+ <el-button
49
+ type="primary"
50
+ :disabled="!rightCheckedCount"
51
+ @click="transferToLeft"
52
+ :size="buttonSize"
53
+ >
54
+ <i class="el-icon-arrow-left"></i>
55
+ </el-button>
56
+ </div>
57
+
58
+ <div class="xt-transfer-tree__panel xt-transfer-tree__target">
59
+ <div class="xt-transfer-tree__header">
60
+ <span class="xt-transfer-tree__title">{{ rightTitle }}</span>
61
+ <span class="xt-transfer-tree__count">{{ rightCheckedCount }}/{{ rightTotalCount }}</span>
62
+ </div>
63
+ <div class="xt-transfer-tree__search" v-if="filterable">
64
+ <el-input
65
+ v-model="rightFilterText"
66
+ placeholder="搜索"
67
+ prefix-icon="el-icon-search"
68
+ size="small"
69
+ />
70
+ </div>
71
+ <div class="xt-transfer-tree__body">
72
+ <el-tree
73
+ ref="rightTree"
74
+ :data="targetData"
75
+ :props="treeProps"
76
+ :default-expand-all="defaultExpandAll"
77
+ :filter-node-method="filterRightNode"
78
+ :show-checkbox="showCheckbox"
79
+ :check-strictly="!cascade"
80
+ :default-checked-keys="rightCheckedKeys"
81
+ @check-change="handleRightCheckChange"
82
+ @node-click="handleRightNodeClick"
83
+ >
84
+ <span class="xt-transfer-tree__node" slot-scope="{ node, data }">
85
+ <span>{{ node.label }}</span>
86
+ <span v-if="data.children && data.children.length" class="xt-transfer-tree__node-count">
87
+ ({{ data.children.length }})
88
+ </span>
89
+ </span>
90
+ </el-tree>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </template>
95
+
96
+ <script>
97
+ export default {
98
+ name: 'XtTransferTree',
99
+
100
+ props: {
101
+ data: {
102
+ type: Array,
103
+ default: () => []
104
+ },
105
+ value: {
106
+ type: Array,
107
+ default: () => []
108
+ },
109
+ leftTitle: {
110
+ type: String,
111
+ default: '待选择'
112
+ },
113
+ rightTitle: {
114
+ type: String,
115
+ default: '已选择'
116
+ },
117
+ treeProps: {
118
+ type: Object,
119
+ default: () => ({
120
+ label: 'label',
121
+ children: 'children',
122
+ value: 'id'
123
+ })
124
+ },
125
+ defaultExpandAll: {
126
+ type: Boolean,
127
+ default: true
128
+ },
129
+ filterable: {
130
+ type: Boolean,
131
+ default: false
132
+ },
133
+ showCheckbox: {
134
+ type: Boolean,
135
+ default: true
136
+ },
137
+ cascade: {
138
+ type: Boolean,
139
+ default: true
140
+ },
141
+ transferMode: {
142
+ type: String,
143
+ default: 'single',
144
+ validator: (val) => ['single', 'multiple', 'parent-child'].includes(val)
145
+ },
146
+ buttonSize: {
147
+ type: String,
148
+ default: 'small'
149
+ }
150
+ },
151
+
152
+ data() {
153
+ return {
154
+ leftFilterText: '',
155
+ rightFilterText: '',
156
+ leftCheckedKeys: [],
157
+ rightCheckedKeys: []
158
+ }
159
+ },
160
+
161
+ computed: {
162
+ sourceData() {
163
+ const valueSet = new Set(this.value)
164
+ return this.filterTreeData(this.data, valueSet, true)
165
+ },
166
+
167
+ targetData() {
168
+ const valueSet = new Set(this.value)
169
+ return this.filterTreeData(this.data, valueSet, false)
170
+ },
171
+
172
+ leftTotalCount() {
173
+ return this.countTreeNodes(this.sourceData)
174
+ },
175
+
176
+ rightTotalCount() {
177
+ return this.countTreeNodes(this.targetData)
178
+ },
179
+
180
+ leftCheckedCount() {
181
+ return this.leftCheckedKeys.length
182
+ },
183
+
184
+ rightCheckedCount() {
185
+ return this.rightCheckedKeys.length
186
+ }
187
+ },
188
+
189
+ watch: {
190
+ leftFilterText(val) {
191
+ this.$refs.leftTree && this.$refs.leftTree.filter(val)
192
+ },
193
+ rightFilterText(val) {
194
+ this.$refs.rightTree && this.$refs.rightTree.filter(val)
195
+ }
196
+ },
197
+
198
+ methods: {
199
+ filterTreeData(data, valueSet, exclude) {
200
+ return data
201
+ .map(node => {
202
+ const isTarget = valueSet.has(node[this.treeProps.value])
203
+ if (exclude && isTarget) return null
204
+ if (!exclude && !isTarget) return null
205
+
206
+ const result = { ...node }
207
+ if (node.children && node.children.length) {
208
+ const filteredChildren = this.filterTreeData(node.children, valueSet, exclude)
209
+ if (filteredChildren.length) {
210
+ result.children = filteredChildren
211
+ } else {
212
+ delete result.children
213
+ }
214
+ }
215
+ return result
216
+ })
217
+ .filter(Boolean)
218
+ },
219
+
220
+ countTreeNodes(data) {
221
+ let count = 0
222
+ data.forEach(node => {
223
+ count++
224
+ if (node.children) {
225
+ count += this.countTreeNodes(node.children)
226
+ }
227
+ })
228
+ return count
229
+ },
230
+
231
+ getAllKeys(node, keys = []) {
232
+ keys.push(node[this.treeProps.value])
233
+ if (node.children) {
234
+ node.children.forEach(child => this.getAllKeys(child, keys))
235
+ }
236
+ return keys
237
+ },
238
+
239
+ getParentKeys(data, key, parentKeys = []) {
240
+ for (const node of data) {
241
+ if (node[this.treeProps.value] === key) {
242
+ return parentKeys
243
+ }
244
+ if (node.children) {
245
+ const result = this.getParentKeys(node.children, key, [...parentKeys, node[this.treeProps.value]])
246
+ if (result.length > 0) return result
247
+ }
248
+ }
249
+ return []
250
+ },
251
+
252
+ filterLeftNode(value, data) {
253
+ if (!value) return true
254
+ return String(data[this.treeProps.label]).toLowerCase().indexOf(value.toLowerCase()) !== -1
255
+ },
256
+
257
+ filterRightNode(value, data) {
258
+ if (!value) return true
259
+ return String(data[this.treeProps.label]).toLowerCase().indexOf(value.toLowerCase()) !== -1
260
+ },
261
+
262
+ handleLeftCheckChange(data, checked) {
263
+ const key = data[this.treeProps.value]
264
+ if (checked) {
265
+ if (!this.leftCheckedKeys.includes(key)) {
266
+ this.leftCheckedKeys.push(key)
267
+ }
268
+ if (this.cascade && data.children) {
269
+ data.children.forEach(child => {
270
+ const childKey = child[this.treeProps.value]
271
+ if (!this.leftCheckedKeys.includes(childKey)) {
272
+ this.leftCheckedKeys.push(childKey)
273
+ }
274
+ })
275
+ }
276
+ } else {
277
+ this.leftCheckedKeys = this.leftCheckedKeys.filter(k => k !== key)
278
+ if (this.cascade && data.children) {
279
+ data.children.forEach(child => {
280
+ this.leftCheckedKeys = this.leftCheckedKeys.filter(k => k !== child[this.treeProps.value])
281
+ })
282
+ }
283
+ }
284
+ },
285
+
286
+ handleRightCheckChange(data, checked) {
287
+ const key = data[this.treeProps.value]
288
+ if (checked) {
289
+ if (!this.rightCheckedKeys.includes(key)) {
290
+ this.rightCheckedKeys.push(key)
291
+ }
292
+ if (this.cascade && data.children) {
293
+ data.children.forEach(child => {
294
+ const childKey = child[this.treeProps.value]
295
+ if (!this.rightCheckedKeys.includes(childKey)) {
296
+ this.rightCheckedKeys.push(childKey)
297
+ }
298
+ })
299
+ }
300
+ } else {
301
+ this.rightCheckedKeys = this.rightCheckedKeys.filter(k => k !== key)
302
+ if (this.cascade && data.children) {
303
+ data.children.forEach(child => {
304
+ this.rightCheckedKeys = this.rightCheckedKeys.filter(k => k !== child[this.treeProps.value])
305
+ })
306
+ }
307
+ }
308
+ },
309
+
310
+ handleLeftNodeClick(data) {
311
+ if (!this.showCheckbox) {
312
+ const key = data[this.treeProps.value]
313
+ const keysToTransfer = this.getTransferKeys(data)
314
+ this.transferKeys(keysToTransfer, true)
315
+ }
316
+ },
317
+
318
+ handleRightNodeClick(data) {
319
+ if (!this.showCheckbox) {
320
+ const key = data[this.treeProps.value]
321
+ const keysToTransfer = this.getTransferKeys(data)
322
+ this.transferKeys(keysToTransfer, false)
323
+ }
324
+ },
325
+
326
+ getTransferKeys(data) {
327
+ const key = data[this.treeProps.value]
328
+ switch (this.transferMode) {
329
+ case 'parent-child':
330
+ return this.getAllKeys(data)
331
+ case 'multiple':
332
+ return [key]
333
+ default:
334
+ return [key]
335
+ }
336
+ },
337
+
338
+ transferToRight() {
339
+ const keys = [...this.leftCheckedKeys]
340
+ this.transferKeys(keys, true)
341
+ this.leftCheckedKeys = []
342
+ },
343
+
344
+ transferToLeft() {
345
+ const keys = [...this.rightCheckedKeys]
346
+ this.transferKeys(keys, false)
347
+ this.rightCheckedKeys = []
348
+ },
349
+
350
+ transferKeys(keys, toRight) {
351
+ const currentValue = [...this.value]
352
+ const newKeys = new Set(currentValue)
353
+
354
+ if (toRight) {
355
+ keys.forEach(k => newKeys.add(k))
356
+ if (this.cascade) {
357
+ keys.forEach(k => {
358
+ const parentKeys = this.getParentKeys(this.data, k)
359
+ parentKeys.forEach(pk => newKeys.add(pk))
360
+ })
361
+ }
362
+ } else {
363
+ keys.forEach(k => newKeys.delete(k))
364
+ if (this.cascade) {
365
+ keys.forEach(k => {
366
+ const allChildKeys = []
367
+ this.findAllChildKeys(this.data, k, allChildKeys)
368
+ allChildKeys.forEach(ck => newKeys.delete(ck))
369
+ })
370
+ }
371
+ }
372
+
373
+ const newValue = Array.from(newKeys)
374
+ this.$emit('update:value', newValue)
375
+ this.$emit('change', {
376
+ value: newValue,
377
+ addedKeys: toRight ? keys : [],
378
+ removedKeys: toRight ? [] : keys
379
+ })
380
+ },
381
+
382
+ findAllChildKeys(data, parentKey, childKeys) {
383
+ for (const node of data) {
384
+ if (node[this.treeProps.value] === parentKey && node.children) {
385
+ node.children.forEach(child => {
386
+ childKeys.push(child[this.treeProps.value])
387
+ if (child.children) {
388
+ this.findAllChildKeys([child], child[this.treeProps.value], childKeys)
389
+ }
390
+ })
391
+ }
392
+ if (node.children) {
393
+ this.findAllChildKeys(node.children, parentKey, childKeys)
394
+ }
395
+ }
396
+ },
397
+
398
+ clearSelection() {
399
+ this.leftCheckedKeys = []
400
+ this.rightCheckedKeys = []
401
+ }
402
+ }
403
+ }
404
+ </script>
405
+
406
+ <style lang="scss" scoped>
407
+ .xt-transfer-tree {
408
+ display: flex;
409
+ align-items: stretch;
410
+ width: 100%;
411
+ height: 100%;
412
+ }
413
+
414
+ .xt-transfer-tree__panel {
415
+ flex: 1;
416
+ display: flex;
417
+ flex-direction: column;
418
+ border: 1px solid #ebeef5;
419
+ border-radius: 4px;
420
+ overflow: hidden;
421
+ }
422
+
423
+ .xt-transfer-tree__source {
424
+ margin-right: 8px;
425
+ }
426
+
427
+ .xt-transfer-tree__target {
428
+ margin-left: 8px;
429
+ }
430
+
431
+ .xt-transfer-tree__header {
432
+ display: flex;
433
+ align-items: center;
434
+ justify-content: space-between;
435
+ padding: 12px;
436
+ background: #f5f7fa;
437
+ border-bottom: 1px solid #ebeef5;
438
+ }
439
+
440
+ .xt-transfer-tree__title {
441
+ font-size: 14px;
442
+ font-weight: 600;
443
+ color: #303133;
444
+ }
445
+
446
+ .xt-transfer-tree__count {
447
+ font-size: 12px;
448
+ color: #909399;
449
+ }
450
+
451
+ .xt-transfer-tree__search {
452
+ padding: 8px 12px;
453
+ border-bottom: 1px solid #ebeef5;
454
+ }
455
+
456
+ .xt-transfer-tree__body {
457
+ flex: 1;
458
+ overflow-y: auto;
459
+ padding: 8px;
460
+ }
461
+
462
+ .xt-transfer-tree__buttons {
463
+ display: flex;
464
+ flex-direction: column;
465
+ justify-content: center;
466
+ align-items: center;
467
+ padding: 8px;
468
+ gap: 8px;
469
+ }
470
+
471
+ .xt-transfer-tree__node {
472
+ display: flex;
473
+ align-items: center;
474
+ gap: 4px;
475
+ }
476
+
477
+ .xt-transfer-tree__node-count {
478
+ font-size: 12px;
479
+ color: #909399;
480
+ }
481
+
482
+ .xt-transfer-tree__body::-webkit-scrollbar {
483
+ width: 6px;
484
+ }
485
+
486
+ .xt-transfer-tree__body::-webkit-scrollbar-thumb {
487
+ background: #c1c1c1;
488
+ border-radius: 3px;
489
+ }
490
+
491
+ .xt-transfer-tree__body::-webkit-scrollbar-track {
492
+ background: transparent;
493
+ }
494
+ </style>
package/src/index.js CHANGED
@@ -46,6 +46,7 @@ import XtMulti from './components/xt-chart/XtMulti.vue'
46
46
  import XtPage from './components/xt-page'
47
47
  import XtSelectTree from './components/xt-select-tree'
48
48
  import XtUpload from './components/xt-upload'
49
+ import XtTransferTree from './components/xt-transfer-tree' // XtTransferTree 组件(树形穿梭框)
49
50
 
50
51
 
51
52
  const components = [
@@ -78,7 +79,8 @@ const components = [
78
79
  XtMulti,
79
80
  XtPage,
80
81
  XtSelectTree,
81
- XtUpload
82
+ XtUpload,
83
+ XtTransferTree
82
84
  ]
83
85
 
84
86
  const install = function (Vue, options = {}) {
@@ -156,7 +158,8 @@ export default {
156
158
  XtMulti,
157
159
  XtPage,
158
160
  XtSelectTree,
159
- XtUpload
161
+ XtUpload,
162
+ XtTransferTree
160
163
  }
161
164
 
162
165
  // XtChart 组件按需导出(使用时需自行安装 echarts 依赖)