web-component-gallery 2.3.32 → 2.3.34
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/dist/js.umd.js +1 -1
- package/lib/directory/Directory.js +0 -1
- package/lib/form-comp/ASelectCustom.vue +199 -280
- package/lib/search/index.vue +72 -26
- package/lib/search/style/index.less +1 -2
- package/package.json +1 -1
|
@@ -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="
|
|
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="
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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
|
-
|
|
179
|
-
|
|
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
|
-
|
|
183
|
-
|
|
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
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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.
|
|
166
|
+
document.addEventListener('click', this.handleOutsideClick)
|
|
254
167
|
},
|
|
255
168
|
|
|
256
169
|
beforeDestroy() {
|
|
257
|
-
this.
|
|
258
|
-
this.
|
|
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
|
-
|
|
287
|
-
|
|
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
|
-
|
|
294
|
-
this.
|
|
295
|
-
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
241
|
+
handleSearchInput(value) {
|
|
242
|
+
// 输入模式下更新值(仅单选)
|
|
243
|
+
if (this.isInput && !this.isMultiple) this.innerValue = value
|
|
244
|
+
|
|
344
245
|
this.searchValue = value
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
if (
|
|
414
|
-
this.
|
|
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
|
-
|
|
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.
|
|
356
|
+
this.preloadSelected(selectedIds)
|
|
433
357
|
},
|
|
434
358
|
|
|
435
359
|
/**
|
|
436
|
-
*
|
|
360
|
+
* 重置并重新获取数据
|
|
437
361
|
*/
|
|
438
|
-
|
|
362
|
+
resetAndFetch() {
|
|
439
363
|
this.searchValue = ''
|
|
440
364
|
this.pagination.current = 1
|
|
441
365
|
this.fetchData()
|
|
442
366
|
},
|
|
443
367
|
|
|
444
368
|
/**
|
|
445
|
-
*
|
|
446
|
-
* @param {Array} options - 待去重的选项数组
|
|
447
|
-
* @returns {Array} 去重后的选项数组
|
|
369
|
+
* 数组去重(基于valueKey)
|
|
448
370
|
*/
|
|
449
|
-
|
|
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
|
-
*
|
|
463
|
-
*
|
|
382
|
+
* 预加载选中项数据
|
|
383
|
+
* 通过ids批量查询选中项的完整信息
|
|
464
384
|
*/
|
|
465
|
-
async
|
|
385
|
+
async preloadSelected(selectedIds) {
|
|
466
386
|
try {
|
|
467
|
-
const
|
|
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.
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
504
|
-
this.pagination.current === 1 && isReset
|
|
505
|
-
?
|
|
506
|
-
:
|
|
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
|
-
|
|
533
|
-
const
|
|
534
|
-
this.innerOptions.map(item => [item[this.valueKey],
|
|
459
|
+
filterNewRecords(data) {
|
|
460
|
+
const existMap = new Map(
|
|
461
|
+
this.innerOptions.map(item => [item[this.valueKey], true])
|
|
535
462
|
)
|
|
536
|
-
return data.filter(item => !
|
|
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
|
-
|
|
553
|
-
|
|
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
|
-
*
|
|
561
|
-
* @returns {String} 处理后的标签文本
|
|
485
|
+
* 解析自定义标签模板
|
|
486
|
+
* 支持 {{field}} 和 {{nested.field}} 语法
|
|
562
487
|
*/
|
|
563
|
-
|
|
488
|
+
parseCustomLabel(item) {
|
|
564
489
|
if (!this.customLabel) return item[this.labelKey]
|
|
565
490
|
|
|
566
491
|
try {
|
|
567
|
-
|
|
568
|
-
|
|
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
|
|
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()
|