web-component-gallery 2.3.33 → 2.3.35

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.
@@ -1,18 +1,13 @@
1
1
  <template>
2
- <!-- 自定义选择器组件,支持分页加载、搜索等功能 -->
3
- <div
4
- @mousedown="handleMouseDown"
5
- ref="main"
6
- class="ASelectCustom"
7
- >
2
+ <!-- 自定义选择器组件 - 支持单选/多选/tags三种模式 -->
3
+ <div @mousedown="handleMouseDown" ref="main" class="ASelectCustom">
8
4
  <Select
9
5
  :style="{ width: width || '100%' }"
10
- :mode="mode"
11
- :open="isSelectOpen"
6
+ :mode="mode"
12
7
  :placeholder="placeholder"
13
8
  :getPopupContainer="getPopupContainer"
14
9
  @select="handleSelect"
15
- @search="debouncedSearch"
10
+ @search="handleSearchInput"
16
11
  @change="handleChange"
17
12
  @popupScroll="handleScroll"
18
13
  v-bind="selectAttrs"
@@ -27,19 +22,19 @@
27
22
  <slot :name="name" v-bind="data" />
28
23
  </template>
29
24
 
30
- <!-- 下拉菜单渲染 -->
25
+ <!-- 下拉菜单自定义渲染 -->
31
26
  <div slot="dropdownRender" slot-scope="menu">
32
- <!-- 多选模式下的全选按钮 -->
27
+ <!-- 全选按钮:仅多选模式显示 -->
33
28
  <Checkbox
34
- v-if="isMultipleMode"
29
+ v-if="isMultiple && !isTags"
35
30
  :checked="isCheckedAll"
36
31
  @change="handleSelectAll"
37
32
  >
38
33
  全选
39
34
  </Checkbox>
40
35
  <v-nodes :vnodes="menu" />
41
- <!-- 加载状态提示 -->
42
- <div v-if="isLoadingMore" class="loading-more">
36
+ <!-- 加载更多提示:仅分页模式显示 -->
37
+ <div v-if="isLoadingMore && needPagination" class="loading-more">
43
38
  <Icon type="loading" />
44
39
  加载中...
45
40
  </div>
@@ -74,237 +69,142 @@ export default {
74
69
  Checkbox
75
70
  },
76
71
 
77
- /**
78
- * 组件属性定义
79
- * 包含基础配置、数据映射、数据源和分页配置
80
- */
81
72
  props: {
82
- // 基础配置
83
- value: {
84
- type: [String, Number, Array, Boolean, Object],
85
- default: undefined
86
- },
87
- mode: String,
73
+ // ========== 基础配置 ==========
74
+ value: [String, Number, Array, Boolean, Object],
75
+ mode: String, // (默认单选) | 'multiple' | 'tags'
88
76
  width: String,
89
- isInput: {
90
- type: Boolean,
91
- default: false
92
- },
93
- placeholder: {
94
- type: String,
95
- default: '请选择'
96
- },
77
+ isInput: { type: Boolean, default: false }, // 是否允许手动输入
78
+ placeholder: { type: String, default: '请选择' },
97
79
 
98
- // 数据映射 - 定义数据字段的键名
99
- valueKey: {
100
- type: String,
101
- default: 'value'
102
- },
103
- labelKey: {
104
- type: String,
105
- default: 'label'
106
- },
107
- customLabel: String,
80
+ // ========== 数据映射 ==========
81
+ valueKey: { type: String, default: 'value' },
82
+ labelKey: { type: String, default: 'label' },
83
+ customLabel: String, // 自定义标签模板,如 '{{name}} - {{code}}'
108
84
 
109
- // 数据源 - 静态选项或动态处理器
110
- options: {
111
- type: Array,
112
- default: () => []
113
- },
114
- initLoad: {
115
- type: Boolean,
116
- default: true
117
- },
118
- autoClearSearchValue: {
119
- type: Boolean,
120
- default: true
121
- },
122
- listPageParams: Object,
123
- listPageHandler: Function,
85
+ // ========== 数据源配置 ==========
86
+ options: { type: Array, default: () => [] }, // 静态选项
87
+ initLoad: { type: Boolean, default: true }, // 是否初始化加载
88
+ autoClearSearchValue: { type: Boolean, default: true }, // 关闭时是否清空搜索
89
+
90
+ // 分页数据源(动态加载)
91
+ listPageParams: Object, // 额外请求参数
92
+ listPageHandler: Function, // 分页请求函数
124
93
 
125
- // 分页配置
126
- pSize: {
127
- type: Number,
128
- default: 50
129
- },
130
- searchKey: {
131
- type: String,
132
- default: 'keyword'
133
- }
94
+ // ========== 分页配置 ==========
95
+ pSize: { type: Number, default: 50 }, // 每页数量
96
+ searchKey: { type: String, default: 'keyword' } // 搜索参数名
134
97
  },
135
98
 
136
- /**
137
- * 组件内部数据
138
- * 包含分页状态、当前数据和UI状态
139
- */
140
99
  data() {
141
100
  return {
142
- // 分页状态管理
143
- pagination: {
144
- current: 1,
145
- size: this.pSize,
146
- total: 0
147
- },
148
-
149
- // 选项数据
150
- innerOptions: [],
151
-
152
- // UI交互状态
153
- isInitialized: false, // 是否已初始化
154
- isSelectOpen: false, // 选择器是否打开
155
- isLoadingMore: false, // 是否正在加载更多
156
- searchValue: '', // 搜索值
157
- hasPreloadedSelected: false, // 是否已预加载选中项
158
- prevOptionsHash: null // 上次 options 的哈希值(用于防抖)
101
+ pagination: { current: 1, size: this.pSize, total: 0 },
102
+ innerOptions: [], // 内部选项列表
103
+ isInitialized: false, // 是否已初始化
104
+ isSelectOpen: false, // 下拉框是否打开
105
+ isLoadingMore: false, // 是否正在加载更多
106
+ searchValue: '', // 当前搜索值
107
+ hasPreloadedSelected: false // 是否已预加载选中项
159
108
  }
160
109
  },
161
110
 
162
- /**
163
- * 计算属性
164
- * 提供响应式的数据计算
165
- */
166
111
  computed: {
167
- // 内部值管理
112
+ // 双向绑定
168
113
  innerValue: {
169
- get() {
170
- return this.value
171
- },
172
- set(val) {
173
- this.$emit('input', val)
174
- }
114
+ get() { return this.value || undefined },
115
+ set(val) { this.$emit('input', val) }
175
116
  },
176
117
 
177
- // 选择器属性配置
178
- selectAttrs() {
179
- return {
118
+ // 模式判断
119
+ isTags() { return this.mode === 'tags' },
120
+ isMultiple() { return !!this.mode }, // multiple 或 tags 都为多选
121
+
122
+ // 是否需要分页加载
123
+ needPagination() { return !!this.listPageHandler },
124
+
125
+ // Select组件属性配置
126
+ selectAttrs() {
127
+ const attrs = {
180
128
  allowClear: true,
181
129
  showSearch: true,
182
- // 多选、标签模式下默认最多显示3个Tag
183
- // maxTagCount: 3,
184
- defaultActiveFirstOption: !this.isInput,
185
- filterOption: this.shouldUseLocalFilter,
130
+ defaultActiveFirstOption: !this.isInput && !this.isTags,
131
+ filterOption: this.needPagination ? false : this.filterOption,
186
132
  ...this.$attrs
187
133
  }
188
- },
189
-
190
- // 是否使用本地过滤
191
- shouldUseLocalFilter() {
192
- return !this.listPageHandler ? this.filterOption : false
193
- },
194
-
195
- // 是否为多选模式
196
- isMultipleMode() {
197
- return !!this.mode
134
+ // tags模式不控制open状态
135
+ if (!this.isTags) attrs.open = this.isSelectOpen
136
+ return attrs
198
137
  },
199
138
 
200
139
  // 是否还有更多数据
201
- hasMore() {
202
- return this.innerOptions.length < this.pagination.total
203
- },
140
+ hasMore() { return this.innerOptions.length < this.pagination.total },
204
141
 
205
- // 全选状态
142
+ // 全选状态判断
206
143
  isCheckedAll() {
207
- return this.innerValue?.length === this.innerOptions?.length
144
+ if (!this.innerValue?.length && !this.innerOptions?.length) return false
145
+ return this.innerValue?.length === this.innerOptions?.length && this.innerOptions?.length > 0
208
146
  }
209
147
  },
210
148
 
211
- /**
212
- * 数据监听器
213
- * 响应数据变化并执行相应逻辑
214
- */
215
149
  watch: {
216
150
  // 监听静态选项变化
217
- options: {
218
- handler: 'handleOptionsChange',
219
- immediate: true
220
- },
221
-
222
- // 监听选择器开关状态
223
- isSelectOpen: 'handleSelectOpenChange',
224
-
225
- // 监听分页参数变化
226
- listPageParams: {
227
- handler: 'resetRequest',
228
- deep: true
229
- },
230
-
231
- // 监听分页处理器变化
232
- listPageHandler: {
233
- handler: 'resetRequest',
234
- deep: true
235
- },
236
-
237
- // 监听value变化 - 优先加载已选数据
238
- value: {
239
- handler: 'handleValueChange',
240
- immediate: true
241
- }
151
+ options: { handler: 'syncOptions', immediate: true },
152
+ // 监听下拉框开关
153
+ isSelectOpen: 'handleDropdownToggle',
154
+ // 监听分页参数变化,重置数据
155
+ listPageParams: { handler: 'resetAndFetch', deep: true },
156
+ listPageHandler: { handler: 'resetAndFetch', deep: true },
157
+ // 监听value变化,预加载选中项
158
+ value: { handler: 'handleValueChange', immediate: true }
242
159
  },
243
160
 
244
- /**
245
- * 生命周期钩子
246
- */
247
161
  created() {
248
- // 创建防抖函数
249
- this.setupDebounceFunctions()
162
+ this.initDebounce()
250
163
  },
251
164
 
252
165
  mounted() {
253
- this.bindGlobalEvents()
166
+ document.addEventListener('click', this.handleOutsideClick)
254
167
  },
255
168
 
256
169
  beforeDestroy() {
257
- this.unbindGlobalEvents()
258
- this.cancelDebounceFunctions()
170
+ document.removeEventListener('click', this.handleOutsideClick)
171
+ this.cancelDebounce()
259
172
  },
260
173
 
261
- /**
262
- * 组件方法集合
263
- * 按功能分类组织方法
264
- */
265
174
  methods: {
266
- // ==================== 初始化相关方法 ====================
267
-
268
- /**
269
- * 设置防抖函数
270
- */
271
- setupDebounceFunctions() {
272
- this.debouncedSearch = debounce(this.handleSearch, 800, { leading: true, trailing: false })
273
- this.debouncedLoadMore = debounce(this.loadMoreData, 300, { leading: false, trailing: true })
274
- },
275
-
276
- /**
277
- * 绑定全局事件
278
- */
279
- bindGlobalEvents() {
280
- document.addEventListener('click', this.handleDocumentClick)
281
- },
175
+ // ========== 初始化方法 ==========
282
176
 
283
177
  /**
284
- * 解绑全局事件
178
+ * 初始化防抖函数
179
+ * 仅在需要分页时使用防抖,本地过滤无需防抖
285
180
  */
286
- unbindGlobalEvents() {
287
- document.removeEventListener('click', this.handleDocumentClick)
181
+ initDebounce() {
182
+ if (this.needPagination) {
183
+ // 远程搜索:800ms防抖
184
+ this.debouncedSearch = debounce(this.executeSearch, 800, { leading: true, trailing: false })
185
+ // 滚动加载:300ms防抖
186
+ this.debouncedLoadMore = debounce(this.loadMore, 300, { leading: false, trailing: true })
187
+ }
288
188
  },
289
189
 
290
190
  /**
291
- * 取消防抖函数
191
+ * 取消防抖
292
192
  */
293
- cancelDebounceFunctions() {
294
- this.debouncedSearch?.cancel()
295
- this.debouncedLoadMore?.cancel()
193
+ cancelDebounce() {
194
+ if (this.needPagination) {
195
+ this.debouncedSearch?.cancel()
196
+ this.debouncedLoadMore?.cancel()
197
+ }
296
198
  },
297
199
 
298
- // ==================== 事件处理相关方法 ====================
200
+ // ========== 事件处理方法 ==========
299
201
 
300
202
  /**
301
- * 鼠标按下处理
203
+ * 鼠标按下:打开下拉框
302
204
  */
303
205
  handleMouseDown(e) {
304
206
  e.preventDefault()
305
- if (!this.$attrs.disabled) {
306
- this.isSelectOpen = true
307
- }
207
+ if (!this.$attrs.disabled) this.isSelectOpen = true
308
208
  },
309
209
 
310
210
  /**
@@ -315,200 +215,228 @@ export default {
315
215
  },
316
216
 
317
217
  /**
318
- * 滚动加载更多数据
218
+ * 滚动到底部:加载更多
319
219
  */
320
220
  handleScroll(e) {
221
+ if (!this.needPagination) return
222
+
321
223
  const { scrollTop, scrollHeight, clientHeight } = e.target
322
224
  const reachBottom = scrollTop + clientHeight >= scrollHeight - 10
225
+
323
226
  if (reachBottom && !this.isLoadingMore && this.hasMore) {
324
227
  this.debouncedLoadMore()
325
228
  }
326
229
  },
327
230
 
328
231
  /**
329
- * 值改变事件处理
232
+ * 值改变:清空时重置
330
233
  */
331
234
  handleChange(value) {
332
- if (!value) {
333
- this.resetRequest()
334
- }
235
+ if (!value) this.resetAndFetch()
335
236
  },
336
237
 
337
238
  /**
338
- * 搜索事件处理
239
+ * 搜索输入处理
339
240
  */
340
- handleSearch(value) {
341
- if (this.isInput) {
342
- this.innerValue = value
343
- }
241
+ handleSearchInput(value) {
242
+ // 输入模式下更新值(仅单选)
243
+ if (this.isInput && !this.isMultiple) this.innerValue = value
244
+
344
245
  this.searchValue = value
345
- this.pagination.current = 1
346
- this.fetchData()
246
+
247
+ // 分页模式:重置页码并执行远程搜索
248
+ if (this.needPagination) {
249
+ this.pagination.current = 1
250
+ // 执行搜索(防抖由initDebounce控制)
251
+ this.debouncedSearch()
252
+ }
347
253
  },
348
254
 
349
255
  /**
350
- * 本地搜索过滤
256
+ * 执行搜索逻辑
257
+ * 分页模式:调用fetchData进行远程搜索
258
+ * 本地模式:无需处理,由filterOption自动过滤
259
+ */
260
+ executeSearch() {
261
+ if (this.needPagination) this.fetchData()
262
+ },
263
+
264
+ /**
265
+ * 本地过滤函数
351
266
  */
352
267
  filterOption(input, option) {
353
- if (this.isInput) {
354
- this.innerValue = input
355
- }
356
268
  return option.componentOptions.children[0].text
357
269
  .toLowerCase()
358
270
  .includes(input.toLowerCase())
359
271
  },
360
272
 
361
273
  /**
362
- * 点击文档关闭下拉
274
+ * 点击外部关闭下拉框
363
275
  */
364
- handleDocumentClick(e) {
276
+ handleOutsideClick(e) {
365
277
  if (this.$refs.main && !this.$refs.main.contains(e.target)) {
366
278
  this.isSelectOpen = false
367
279
  }
368
280
  },
369
281
 
370
282
  /**
371
- * 全选处理
283
+ * 全选/取消全选(仅多选模式,排除tags)
372
284
  */
373
285
  handleSelectAll({ target }) {
374
- const selectOptions = target.checked
286
+ if (!this.isMultiple || this.isTags) return
287
+
288
+ this.$emit('input', target.checked
375
289
  ? this.innerOptions.map(item => item[this.valueKey])
376
290
  : []
291
+ )
377
292
  this.isSelectOpen = false
378
- this.$emit('input', selectOptions)
379
293
  },
380
294
 
381
295
  /**
382
- * 选中单个选项
296
+ * 选中单项
383
297
  */
384
298
  handleSelect(value) {
385
299
  if (!value) return
386
300
 
387
301
  this.isSelectOpen = false
302
+
388
303
  const selectedOption = this.innerOptions.find(
389
304
  item => item[this.valueKey] === value
390
305
  )
391
- // 定义为choose事件,与原生select事件区分
306
+
307
+ // 触发choose事件,传递选中值和完整选项对象
392
308
  if (selectedOption) {
393
309
  this.$emit('choose', value, selectedOption)
394
310
  }
395
311
  },
396
312
 
397
- // ==================== 数据处理相关方法 ====================
313
+ // ========== 数据管理方法 ==========
398
314
 
399
315
  /**
400
- * 处理选项变化
316
+ * 同步静态选项
401
317
  */
402
- handleOptionsChange(newVal) {
403
- this.innerOptions = this.listPageHandler
404
- ? this.deduplicateOptions([...newVal, ...this.innerOptions])
318
+ syncOptions(newVal) {
319
+ // 分页模式:合并新旧选项并去重;本地模式:直接使用新选项
320
+ this.innerOptions = this.needPagination
321
+ ? this.deduplicate([...newVal, ...this.innerOptions])
405
322
  : [...newVal]
406
323
  },
407
324
 
408
325
  /**
409
- * 处理选择器开关变化
326
+ * 下拉框开关切换
410
327
  */
411
- handleSelectOpenChange(newVal) {
412
- const shouldClear = !newVal && this.searchValue && this.autoClearSearchValue
413
- if (shouldClear) {
414
- this.resetRequest()
328
+ handleDropdownToggle(isOpen) {
329
+ // 关闭时且开启了自动清空,则重置搜索
330
+ if (!isOpen && this.searchValue && this.autoClearSearchValue) {
331
+ this.resetAndFetch()
415
332
  }
416
333
  },
417
334
 
418
335
  /**
419
- * 处理值变化
336
+ * 值变化处理:预加载选中项
420
337
  */
421
338
  handleValueChange(newVal) {
422
- if (this.hasPreloadedSelected || !this.listPageHandler) {
339
+ // 非分页模式:未初始化时执行加载
340
+ if (!this.needPagination) {
341
+ if (!this.isInitialized && this.initLoad) this.fetchData(false)
423
342
  return
424
343
  }
344
+
345
+ // 已预加载则跳过
346
+ if (this.hasPreloadedSelected) return
425
347
 
348
+ // 无选中值:直接加载第一页
426
349
  if (!newVal || newVal.length === 0) {
427
350
  if (this.initLoad) this.fetchData(false)
428
351
  return
429
352
  }
430
353
 
354
+ // 有选中值:预加载选中项数据
431
355
  const selectedIds = Array.isArray(newVal) ? newVal : [newVal]
432
- this.preloadSelectedData(selectedIds)
356
+ this.preloadSelected(selectedIds)
433
357
  },
434
358
 
435
359
  /**
436
- * 重置分页并重新请求数据
360
+ * 重置并重新获取数据
437
361
  */
438
- resetRequest() {
362
+ resetAndFetch() {
439
363
  this.searchValue = ''
440
364
  this.pagination.current = 1
441
365
  this.fetchData()
442
366
  },
443
367
 
444
368
  /**
445
- * 根据valueKey对选项数组进行去重
446
- * @param {Array} options - 待去重的选项数组
447
- * @returns {Array} 去重后的选项数组
369
+ * 数组去重(基于valueKey
448
370
  */
449
- deduplicateOptions(options) {
371
+ deduplicate(options) {
450
372
  const seen = new Set()
451
373
  return options.filter(item => {
452
374
  const value = item[this.valueKey]
453
- if (seen.has(value)) {
454
- return false
455
- }
375
+ if (seen.has(value)) return false
456
376
  seen.add(value)
457
377
  return true
458
378
  })
459
379
  },
460
380
 
461
381
  /**
462
- * 预加载已选择的ID数据
463
- * @param {Array} selectedIds - 已选择的ID数组
382
+ * 预加载选中项数据
383
+ * 通过ids批量查询选中项的完整信息
464
384
  */
465
- async preloadSelectedData(selectedIds) {
385
+ async preloadSelected(selectedIds) {
466
386
  try {
467
- const params = {
387
+ const { records } = await this.listPageHandler({
468
388
  ...this.listPageParams,
469
389
  ids: selectedIds,
470
390
  current: 1,
471
391
  size: selectedIds.length
472
- }
473
-
474
- const { records } = await this.listPageHandler(params)
392
+ })
475
393
 
476
- this.innerOptions = this.deduplicateOptions([...records])
394
+ this.innerOptions = this.deduplicate([...records])
477
395
  this.hasPreloadedSelected = true
478
396
 
479
- // 请求正常的分页数据
397
+ // 预加载完成后,再加载正常分页数据
480
398
  this.fetchData()
481
399
  } catch (error) {
482
- console.error('预加载已选择数据失败:', error)
483
- // 预加载失败,继续执行正常的分页请求
400
+ console.error('预加载选中数据失败:', error)
484
401
  this.fetchData()
485
402
  }
486
403
  },
487
404
 
488
405
  /**
489
406
  * 获取分页数据
490
- * @param {Boolean} isReset - 是否重置数据
407
+ * @param {Boolean} isReset - 是否重置为第一页
491
408
  */
492
409
  async fetchData(isReset = true) {
493
- if (!this.listPageHandler) return
410
+ // 非分页模式:仅触发初始化事件
411
+ if (!this.needPagination) {
412
+ if (!this.isInitialized && this.initLoad) {
413
+ this.$emit('fetchInitialData', {
414
+ options: [...this.innerOptions],
415
+ pagination: { ...this.pagination }
416
+ })
417
+ this.isInitialized = true
418
+ }
419
+ return
420
+ }
494
421
 
495
422
  try {
496
423
  this.isLoadingMore = true
424
+
497
425
  const { total, records } = await this.listPageHandler({
498
426
  ...this.pagination,
499
427
  ...this.listPageParams,
500
428
  [this.searchKey]: this.searchValue
501
429
  })
502
430
 
503
- let newInnerOptions = []
504
- this.pagination.current === 1 && isReset
505
- ? newInnerOptions = [...this.options, ...records]
506
- : newInnerOptions = [...this.innerOptions, ...this.handleFilter(records)]
431
+ // 第一页:合并静态选项和接口数据;后续页:过滤后追加
432
+ const newOptions = this.pagination.current === 1 && isReset
433
+ ? [...this.options, ...records]
434
+ : [...this.innerOptions, ...this.filterNewRecords(records)]
507
435
 
508
- // 对合并后的数据进行去重
509
- this.innerOptions = this.deduplicateOptions(newInnerOptions)
436
+ this.innerOptions = this.deduplicate(newOptions)
510
437
  this.pagination.total = total
511
438
 
439
+ // 首次加载完成,触发事件
512
440
  if (!this.isInitialized) {
513
441
  this.$emit('fetchInitialData', {
514
442
  options: [...this.innerOptions],
@@ -516,6 +444,7 @@ export default {
516
444
  })
517
445
  }
518
446
  } catch (error) {
447
+ // 请求失败,回退页码
519
448
  this.pagination.current--
520
449
  console.error('分页加载失败:', error)
521
450
  } finally {
@@ -525,18 +454,16 @@ export default {
525
454
  },
526
455
 
527
456
  /**
528
- * 过滤掉重复数据
529
- * @param {Array} data - 待过滤的数据
530
- * @returns {Array} 过滤后的数据
457
+ * 过滤出新记录(排除已存在的)
531
458
  */
532
- handleFilter(data) {
533
- const optionsCache = new Map(
534
- this.innerOptions.map(item => [item[this.valueKey], item])
459
+ filterNewRecords(data) {
460
+ const existMap = new Map(
461
+ this.innerOptions.map(item => [item[this.valueKey], true])
535
462
  )
536
- return data.filter(item => !optionsCache.has(item[this.valueKey]))
463
+ return data.filter(item => !existMap.has(item[this.valueKey]))
537
464
  },
538
465
 
539
- // ==================== 工具方法 ====================
466
+ // ========== 工具方法 ==========
540
467
 
541
468
  /**
542
469
  * 获取选项的唯一key
@@ -546,32 +473,24 @@ export default {
546
473
  },
547
474
 
548
475
  /**
549
- * 获取选项显示标签
476
+ * 获取选项显示文本
550
477
  */
551
478
  getOptionLabel(item) {
552
- if (this.customLabel) {
553
- return this.customLabelHandler(item)
554
- }
555
- return item[this.labelKey]
479
+ return this.customLabel
480
+ ? this.parseCustomLabel(item)
481
+ : item[this.labelKey]
556
482
  },
557
483
 
558
484
  /**
559
- * 安全的自定义标签处理
560
- * @param {Object} item - 选项数据
561
- * @returns {String} 处理后的标签文本
485
+ * 解析自定义标签模板
486
+ * 支持 {{field}} {{nested.field}} 语法
562
487
  */
563
- customLabelHandler(item) {
488
+ parseCustomLabel(item) {
564
489
  if (!this.customLabel) return item[this.labelKey]
565
490
 
566
491
  try {
567
- // 使用安全的模板字符串解析
568
- return this.customLabel.replace(/{{([^{}]+)}}/g, (match, key) => {
569
- const path = key.trim().split('.')
570
- let value = item
571
- for (const p of path) {
572
- if (value == null) return ''
573
- value = value[p]
574
- }
492
+ return this.customLabel.replace(/{{([^{}]+)}}/g, (_, key) => {
493
+ const value = key.trim().split('.').reduce((obj, path) => obj?.[path], item)
575
494
  return value == null ? '-' : value
576
495
  })
577
496
  } catch (e) {
@@ -583,8 +502,8 @@ export default {
583
502
  /**
584
503
  * 加载更多数据
585
504
  */
586
- async loadMoreData() {
587
- if (this.isLoadingMore || !this.hasMore) return
505
+ async loadMore() {
506
+ if (!this.needPagination || this.isLoadingMore || !this.hasMore) return
588
507
 
589
508
  this.pagination.current++
590
509
  await this.fetchData()