@longhongguo/form-create-ant-design-vue 3.3.28 → 3.3.31

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,402 +1,407 @@
1
- <template>
2
- <div @click="handleClick">
3
- <CusSelect
4
- :model-value="modelValue"
5
- :options="mergedOptions"
6
- v-model:source-items="sourceItems"
7
- :multiple="multiple"
8
- :max-tag-count="maxTagCount"
9
- :placeholder="placeholder"
10
- :disabled="disabled"
11
- :style="style"
12
- :valueKey="valueKey"
13
- :labelKey="labelKey"
14
- :allowClear="allowClear"
15
- :bordered="bordered"
16
- @update:model-value="handleUpdate"
17
- @change="handleChange"
18
- />
19
- </div>
20
- </template>
21
-
22
- <script>
23
- import { defineComponent } from 'vue'
24
- import CusSelect from '../CusSelect/index.vue'
25
-
26
- export default defineComponent({
27
- name: 'CusStoreSelect',
28
- components: {
29
- CusSelect
30
- },
31
- props: {
32
- // form-create 注入的对象,包含 API 等
33
- formCreateInject: {
34
- type: Object,
35
- default: null
36
- },
37
- // 当前值:统一使用数组格式(支持 v-model)
38
- // 单选时:[value] 或 []
39
- // 多选时:[value1, value2, ...] 或 []
40
- modelValue: {
41
- type: Array,
42
- default: () => []
43
- },
44
- // 选项列表
45
- options: {
46
- type: Array,
47
- default: () => []
48
- },
49
- // 是否多选
50
- multiple: {
51
- type: Boolean,
52
- default: false
53
- },
54
- // 最多显示的 tag 数量(多选模式下有效)
55
- maxTagCount: {
56
- type: Number,
57
- default: undefined // 不限制时显示所有
58
- },
59
- // 占位符
60
- placeholder: {
61
- type: String,
62
- default: '请选择'
63
- },
64
- // 是否禁用样式
65
- disabled: {
66
- type: Boolean,
67
- default: false
68
- },
69
- // 自定义样式
70
- style: {
71
- type: [String, Object],
72
- default: () => ({ width: '100%' })
73
- },
74
- // 选项的 value 字段名
75
- valueKey: {
76
- type: String,
77
- default: 'value'
78
- },
79
- // 选项的 label 字段名
80
- labelKey: {
81
- type: String,
82
- default: 'label'
83
- },
84
- // 是否允许清除
85
- allowClear: {
86
- type: Boolean,
87
- default: false
88
- },
89
- // 字段名,用于跨窗口通信时标识字段
90
- field: {
91
- type: String,
92
- default: ''
93
- },
94
- // 是否有边框
95
- bordered: {
96
- type: Boolean,
97
- default: true
98
- },
99
- // 额外查询参数
100
- extraQuery: {
101
- type: Object,
102
- default: () => ({})
103
- },
104
- extraQueryFn: {
105
- type: Function,
106
- default: () => {}
107
- }
108
- },
109
- emits: ['update:modelValue', 'change'],
110
- data() {
111
- return {
112
- // 消息ID计数器,用于标识每次请求
113
- messageId: 0,
114
- // 存储待处理的回调函数
115
- pendingCallbacks: {},
116
- // 内部维护的选项列表(合并父窗口返回的源对象)
117
- internalOptions: [],
118
- sourceItems: []
119
- }
120
- },
121
- computed: {
122
- // 合并内部选项和外部传入的选项
123
- mergedOptions() {
124
- // 如果内部有选项,优先使用内部选项
125
- if (this.internalOptions.length > 0) {
126
- // 合并去重:根据 valueKey 去重,保留内部选项(后添加的优先)
127
- const optionMap = new Map()
128
- // 先添加外部选项
129
- this.options.forEach((opt) => {
130
- const value = typeof opt === 'object' ? opt[this.valueKey] : opt
131
- optionMap.set(value, opt)
132
- })
133
- // 再添加内部选项(会覆盖相同 value 的选项)
134
- this.internalOptions.forEach((opt) => {
135
- const value = typeof opt === 'object' ? opt[this.valueKey] : opt
136
- optionMap.set(value, opt)
137
- })
138
- return Array.from(optionMap.values())
139
- }
140
- return this.options
141
- }
142
- },
143
- watch: {
144
- // 监听外部 options 变化,初始化内部选项列表
145
- options: {
146
- immediate: true,
147
- handler(newOptions) {
148
- // 如果内部选项为空,且外部有选项,初始化内部选项
149
- if (
150
- this.internalOptions.length === 0 &&
151
- Array.isArray(newOptions) &&
152
- newOptions.length > 0
153
- ) {
154
- this.internalOptions = [...newOptions]
155
- }
156
- }
157
- }
158
- },
159
- mounted() {
160
- // 监听父窗口返回的消息
161
- window.addEventListener('message', this.handleMessage)
162
- },
163
- beforeUnmount() {
164
- // 组件销毁时移除事件监听
165
- window.removeEventListener('message', this.handleMessage)
166
- },
167
- methods: {
168
- // 序列化数据,确保可以被 postMessage 发送
169
- // postMessage 使用结构化克隆算法,无法克隆 Vue 响应式代理对象
170
- serializeForPostMessage(data) {
171
- // 处理 null 和 undefined
172
- if (data === null || data === undefined) {
173
- return data
174
- }
175
-
176
- try {
177
- // 使用 JSON 序列化和反序列化来创建可克隆的副本
178
- // 这会移除 Vue 响应式代理、函数、Symbol 等不可序列化的内容
179
- return JSON.parse(JSON.stringify(data))
180
- } catch (error) {
181
- console.warn('CusStoreSelect: 数据序列化失败,尝试递归处理', error)
182
-
183
- // 如果 JSON 序列化失败(可能是循环引用),尝试递归处理
184
- if (Array.isArray(data)) {
185
- // 空数组直接返回
186
- if (data.length === 0) {
187
- return []
188
- }
189
- // 递归处理数组中的每个元素
190
- return data.map((item) => this.serializeForPostMessage(item))
191
- }
192
-
193
- if (typeof data === 'object') {
194
- const result = {}
195
- for (const key in data) {
196
- if (Object.prototype.hasOwnProperty.call(data, key)) {
197
- try {
198
- result[key] = this.serializeForPostMessage(data[key])
199
- } catch (e) {
200
- // 忽略无法序列化的属性,避免整个序列化失败
201
- console.warn(`CusStoreSelect: 跳过无法序列化的属性: ${key}`, e)
202
- }
203
- }
204
- }
205
- return result
206
- }
207
-
208
- // 基本类型(string, number, boolean)直接返回
209
- return data
210
- }
211
- },
212
- handleClick() {
213
- // 如果禁用,不处理
214
- if (this.disabled) {
215
- return
216
- }
217
-
218
- // 生成唯一消息ID
219
- const msgId = `store-select-${
220
- this.field || 'default'
221
- }-${Date.now()}-${++this.messageId}`
222
-
223
- // 序列化所有需要传递的数据,确保可以被 postMessage 发送
224
- // postMessage 无法发送 Vue 响应式代理对象,必须序列化
225
- // 获取当前值,确保是对象数组格式
226
- const currentArrayValue = Array.isArray(this.modelValue)
227
- ? this.modelValue
228
- : []
229
- // 从对象数组中提取 value 值,用于发送给父窗口
230
- // 单选时发送第一个对象的 value,多选时发送所有对象的 value 数组
231
- let valueToSend = null
232
- if (currentArrayValue.length > 0) {
233
- valueToSend = currentArrayValue
234
- }
235
- const serializedCurrentValue =
236
- valueToSend === null || valueToSend === undefined
237
- ? null
238
- : this.serializeForPostMessage(valueToSend)
239
-
240
- // 发送消息给父窗口,请求打开门店选择弹窗
241
- const extraQuery = {
242
- ...this.extraQuery
243
- }
244
- if (this.extraQueryFn) {
245
- Object.assign(extraQuery, this.extraQueryFn())
246
- }
247
- const message = {
248
- type: 'OPEN_STORE_SELECT',
249
- field: this.field || '',
250
- multiple: this.multiple,
251
- currentValue: serializedCurrentValue,
252
- valueKey: this.valueKey,
253
- labelKey: this.labelKey,
254
- extraQuery,
255
- messageId: msgId
256
- }
257
-
258
- // 发送到父窗口(支持 iframe 场景)
259
- if (window.parent && window.parent !== window) {
260
- try {
261
- window.parent.postMessage(message, '*')
262
- } catch (error) {
263
- console.error('CusStoreSelect: 发送消息失败', error)
264
- }
265
- } else {
266
- // 如果不在 iframe 中,也可以发送到当前窗口(用于测试)
267
- console.warn(
268
- 'CusStoreSelect: 当前不在 iframe 环境中,无法向父窗口发送消息'
269
- )
270
- }
271
-
272
- // 存储回调,等待父窗口返回结果
273
- this.pendingCallbacks[msgId] = (value, sourceItems) => {
274
- // 优先使用 sourceItems(源对象数组),如果没有则根据 value 和 options 构建
275
- if (
276
- sourceItems &&
277
- Array.isArray(sourceItems) &&
278
- sourceItems.length > 0
279
- ) {
280
- // 合并到内部选项列表
281
- this.mergeOptions(sourceItems)
282
- }
283
-
284
- this.sourceItems = sourceItems
285
- this.handleUpdate(value)
286
- this.handleChange(value)
287
- }
288
- },
289
- handleMessage(event) {
290
- // 验证消息来源(可选,根据实际需求调整)
291
- // if (event.origin !== 'expected-origin') return
292
-
293
- const data = event.data
294
-
295
- // 检查是否是门店选择返回的消息
296
- if (data && data.type === 'STORE_SELECT_RESULT') {
297
- const { field, value, sourceItems, messageId } = data
298
-
299
- // 验证字段名是否匹配
300
- if (field !== this.field) {
301
- return
302
- }
303
-
304
- // 查找对应的回调函数
305
- const callback = this.pendingCallbacks[messageId]
306
- if (callback) {
307
- // 执行回调,更新值并传递源对象
308
- // sourceItems: 源对象数组,包含完整的选项信息
309
- // 单选时:[{ value: '1001', label: '门店1', ... }]
310
- // 多选时:[{ value: '1001', label: '门店1', ... }, { value: '1002', label: '门店2', ... }]
311
- callback(value, sourceItems)
312
- // 清理已处理的回调
313
- delete this.pendingCallbacks[messageId]
314
- }
315
- }
316
- },
317
- // 合并选项到内部选项列表
318
- mergeOptions(newItems) {
319
- if (!Array.isArray(newItems) || newItems.length === 0) {
320
- return
321
- }
322
-
323
- // 创建选项映射,用于去重
324
- const optionMap = new Map()
325
-
326
- // 先将现有内部选项添加到映射
327
- this.internalOptions.forEach((opt) => {
328
- const value = typeof opt === 'object' ? opt[this.valueKey] : opt
329
- optionMap.set(value, opt)
330
- })
331
-
332
- // 再添加新选项(会覆盖相同 value 的选项)
333
- newItems.forEach((opt) => {
334
- const value = typeof opt === 'object' ? opt[this.valueKey] : opt
335
- optionMap.set(value, opt)
336
- })
337
-
338
- // 更新内部选项列表
339
- this.internalOptions = Array.from(optionMap.values())
340
- },
341
- handleUpdate(value) {
342
- this.$emit('update:modelValue', value)
343
- // 值更新后触发校验
344
- this.triggerValidate()
345
- },
346
- handleChange(value) {
347
- this.$emit(
348
- 'change',
349
- value,
350
- this.sourceItems && this.sourceItems.length > 0
351
- ? this.multiple
352
- ? this.sourceItems
353
- : this.sourceItems[0]
354
- : this.multiple
355
- ? []
356
- : null
357
- )
358
- },
359
- // 触发字段校验
360
- triggerValidate() {
361
- // 使用 nextTick 确保值已经更新完成
362
- this.$nextTick(() => {
363
- this.$nextTick(() => {
364
- try {
365
- // 方式1:通过 formCreateInject 中的 API 触发校验
366
- if (
367
- this.formCreateInject &&
368
- this.formCreateInject.api &&
369
- this.field
370
- ) {
371
- this.formCreateInject.api.validateField(this.field).catch(() => {
372
- // 校验失败时静默处理,错误会通过 form-item 显示
373
- })
374
- return
375
- }
376
-
377
- // 方式2:通过组件实例查找父组件中的 form-create API
378
- let parent = this.$parent
379
- while (parent) {
380
- if (
381
- parent.$options &&
382
- parent.$options.name === 'FormCreate' &&
383
- parent.fapi
384
- ) {
385
- if (this.field && parent.fapi.validateField) {
386
- parent.fapi.validateField(this.field).catch(() => {
387
- // 校验失败时静默处理
388
- })
389
- }
390
- break
391
- }
392
- parent = parent.$parent
393
- }
394
- } catch (error) {
395
- console.warn('CusStoreSelect: 触发校验失败', error)
396
- }
397
- })
398
- })
399
- }
400
- }
401
- })
402
- </script>
1
+ <template>
2
+ <div @click="handleClick">
3
+ <CusSelect
4
+ :model-value="modelValue"
5
+ :options="mergedOptions"
6
+ v-model:source-items="sourceItems"
7
+ :multiple="multiple"
8
+ :max-tag-count="maxTagCount"
9
+ :placeholder="placeholder"
10
+ :disabled="disabled"
11
+ :style="style"
12
+ :valueKey="valueKey"
13
+ :labelKey="labelKey"
14
+ :allowClear="allowClear"
15
+ :bordered="bordered"
16
+ @update:model-value="handleUpdate"
17
+ @change="handleChange"
18
+ />
19
+ </div>
20
+ </template>
21
+
22
+ <script>
23
+ import { defineComponent } from 'vue'
24
+ import CusSelect from '../CusSelect/index.vue'
25
+
26
+ export default defineComponent({
27
+ name: 'CusStoreSelect',
28
+ components: {
29
+ CusSelect
30
+ },
31
+ props: {
32
+ // form-create 注入的对象,包含 API 等
33
+ formCreateInject: {
34
+ type: Object,
35
+ default: null
36
+ },
37
+ // 当前值:统一使用数组格式(支持 v-model)
38
+ // 单选时:[value] 或 []
39
+ // 多选时:[value1, value2, ...] 或 []
40
+ modelValue: {
41
+ type: Array,
42
+ default: () => []
43
+ },
44
+ // 选项列表
45
+ options: {
46
+ type: Array,
47
+ default: () => []
48
+ },
49
+ // 是否多选
50
+ multiple: {
51
+ type: Boolean,
52
+ default: false
53
+ },
54
+ // 最多显示的 tag 数量(多选模式下有效)
55
+ maxTagCount: {
56
+ type: Number,
57
+ default: undefined // 不限制时显示所有
58
+ },
59
+ // 占位符
60
+ placeholder: {
61
+ type: String,
62
+ default: '请选择'
63
+ },
64
+ // 是否禁用样式
65
+ disabled: {
66
+ type: Boolean,
67
+ default: false
68
+ },
69
+ // 自定义样式
70
+ style: {
71
+ type: [String, Object],
72
+ default: () => ({ width: '100%' })
73
+ },
74
+ // 选项的 value 字段名
75
+ valueKey: {
76
+ type: String,
77
+ default: 'value'
78
+ },
79
+ // 选项的 label 字段名
80
+ labelKey: {
81
+ type: String,
82
+ default: 'label'
83
+ },
84
+ // 是否允许清除
85
+ allowClear: {
86
+ type: Boolean,
87
+ default: false
88
+ },
89
+ // 字段名,用于跨窗口通信时标识字段
90
+ field: {
91
+ type: String,
92
+ default: ''
93
+ },
94
+ // 是否有边框
95
+ bordered: {
96
+ type: Boolean,
97
+ default: true
98
+ },
99
+ // 额外查询参数
100
+ extraQuery: {
101
+ type: Object,
102
+ default: () => ({})
103
+ },
104
+ extraQueryFn: {
105
+ type: Function,
106
+ default: () => {}
107
+ }
108
+ },
109
+ emits: ['update:modelValue', 'change'],
110
+ data() {
111
+ return {
112
+ // 消息ID计数器,用于标识每次请求
113
+ messageId: 0,
114
+ // 存储待处理的回调函数
115
+ pendingCallbacks: {},
116
+ // 内部维护的选项列表(合并父窗口返回的源对象)
117
+ internalOptions: [],
118
+ sourceItems: []
119
+ }
120
+ },
121
+ computed: {
122
+ // 合并内部选项和外部传入的选项
123
+ mergedOptions() {
124
+ // 如果内部有选项,优先使用内部选项
125
+ if (this.internalOptions.length > 0) {
126
+ // 合并去重:根据 valueKey 去重,保留内部选项(后添加的优先)
127
+ const optionMap = new Map()
128
+ // 先添加外部选项
129
+ this.options.forEach((opt) => {
130
+ const value = typeof opt === 'object' ? opt[this.valueKey] : opt
131
+ optionMap.set(value, opt)
132
+ })
133
+ // 再添加内部选项(会覆盖相同 value 的选项)
134
+ this.internalOptions.forEach((opt) => {
135
+ const value = typeof opt === 'object' ? opt[this.valueKey] : opt
136
+ optionMap.set(value, opt)
137
+ })
138
+ return Array.from(optionMap.values())
139
+ }
140
+ return this.options
141
+ }
142
+ },
143
+ watch: {
144
+ // 监听外部 options 变化,初始化内部选项列表
145
+ options: {
146
+ immediate: true,
147
+ handler(newOptions) {
148
+ // 如果内部选项为空,且外部有选项,初始化内部选项
149
+ if (
150
+ this.internalOptions.length === 0 &&
151
+ Array.isArray(newOptions) &&
152
+ newOptions.length > 0
153
+ ) {
154
+ this.internalOptions = [...newOptions]
155
+ }
156
+ }
157
+ }
158
+ },
159
+ mounted() {
160
+ // 监听父窗口返回的消息
161
+ window.addEventListener('message', this.handleMessage)
162
+ },
163
+ beforeUnmount() {
164
+ // 组件销毁时移除事件监听
165
+ window.removeEventListener('message', this.handleMessage)
166
+ },
167
+ methods: {
168
+ // 序列化数据,确保可以被 postMessage 发送
169
+ // postMessage 使用结构化克隆算法,无法克隆 Vue 响应式代理对象
170
+ serializeForPostMessage(data) {
171
+ // 处理 null 和 undefined
172
+ if (data === null || data === undefined) {
173
+ return data
174
+ }
175
+
176
+ try {
177
+ // 使用 JSON 序列化和反序列化来创建可克隆的副本
178
+ // 这会移除 Vue 响应式代理、函数、Symbol 等不可序列化的内容
179
+ return JSON.parse(JSON.stringify(data))
180
+ } catch (error) {
181
+ console.warn('CusStoreSelect: 数据序列化失败,尝试递归处理', error)
182
+
183
+ // 如果 JSON 序列化失败(可能是循环引用),尝试递归处理
184
+ if (Array.isArray(data)) {
185
+ // 空数组直接返回
186
+ if (data.length === 0) {
187
+ return []
188
+ }
189
+ // 递归处理数组中的每个元素
190
+ return data.map((item) => this.serializeForPostMessage(item))
191
+ }
192
+
193
+ if (typeof data === 'object') {
194
+ const result = {}
195
+ for (const key in data) {
196
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
197
+ try {
198
+ result[key] = this.serializeForPostMessage(data[key])
199
+ } catch (e) {
200
+ // 忽略无法序列化的属性,避免整个序列化失败
201
+ console.warn(`CusStoreSelect: 跳过无法序列化的属性: ${key}`, e)
202
+ }
203
+ }
204
+ }
205
+ return result
206
+ }
207
+
208
+ // 基本类型(string, number, boolean)直接返回
209
+ return data
210
+ }
211
+ },
212
+ handleClick() {
213
+ // 如果禁用,不处理
214
+ if (this.disabled) {
215
+ return
216
+ }
217
+
218
+ // 如果处于预览/只读模式,不处理点击事件
219
+ if (this.formCreateInject && this.formCreateInject.preview) {
220
+ return
221
+ }
222
+
223
+ // 生成唯一消息ID
224
+ const msgId = `store-select-${
225
+ this.field || 'default'
226
+ }-${Date.now()}-${++this.messageId}`
227
+
228
+ // 序列化所有需要传递的数据,确保可以被 postMessage 发送
229
+ // postMessage 无法发送 Vue 响应式代理对象,必须序列化
230
+ // 获取当前值,确保是对象数组格式
231
+ const currentArrayValue = Array.isArray(this.modelValue)
232
+ ? this.modelValue
233
+ : []
234
+ // 从对象数组中提取 value 值,用于发送给父窗口
235
+ // 单选时发送第一个对象的 value,多选时发送所有对象的 value 数组
236
+ let valueToSend = null
237
+ if (currentArrayValue.length > 0) {
238
+ valueToSend = currentArrayValue
239
+ }
240
+ const serializedCurrentValue =
241
+ valueToSend === null || valueToSend === undefined
242
+ ? null
243
+ : this.serializeForPostMessage(valueToSend)
244
+
245
+ // 发送消息给父窗口,请求打开门店选择弹窗
246
+ const extraQuery = {
247
+ ...this.extraQuery
248
+ }
249
+ if (this.extraQueryFn) {
250
+ Object.assign(extraQuery, this.extraQueryFn())
251
+ }
252
+ const message = {
253
+ type: 'OPEN_STORE_SELECT',
254
+ field: this.field || '',
255
+ multiple: this.multiple,
256
+ currentValue: serializedCurrentValue,
257
+ valueKey: this.valueKey,
258
+ labelKey: this.labelKey,
259
+ extraQuery,
260
+ messageId: msgId
261
+ }
262
+
263
+ // 发送到父窗口(支持 iframe 场景)
264
+ if (window.parent && window.parent !== window) {
265
+ try {
266
+ window.parent.postMessage(message, '*')
267
+ } catch (error) {
268
+ console.error('CusStoreSelect: 发送消息失败', error)
269
+ }
270
+ } else {
271
+ // 如果不在 iframe 中,也可以发送到当前窗口(用于测试)
272
+ console.warn(
273
+ 'CusStoreSelect: 当前不在 iframe 环境中,无法向父窗口发送消息'
274
+ )
275
+ }
276
+
277
+ // 存储回调,等待父窗口返回结果
278
+ this.pendingCallbacks[msgId] = (value, sourceItems) => {
279
+ // 优先使用 sourceItems(源对象数组),如果没有则根据 value 和 options 构建
280
+ if (
281
+ sourceItems &&
282
+ Array.isArray(sourceItems) &&
283
+ sourceItems.length > 0
284
+ ) {
285
+ // 合并到内部选项列表
286
+ this.mergeOptions(sourceItems)
287
+ }
288
+
289
+ this.sourceItems = sourceItems
290
+ this.handleUpdate(value)
291
+ this.handleChange(value)
292
+ }
293
+ },
294
+ handleMessage(event) {
295
+ // 验证消息来源(可选,根据实际需求调整)
296
+ // if (event.origin !== 'expected-origin') return
297
+
298
+ const data = event.data
299
+
300
+ // 检查是否是门店选择返回的消息
301
+ if (data && data.type === 'STORE_SELECT_RESULT') {
302
+ const { field, value, sourceItems, messageId } = data
303
+
304
+ // 验证字段名是否匹配
305
+ if (field !== this.field) {
306
+ return
307
+ }
308
+
309
+ // 查找对应的回调函数
310
+ const callback = this.pendingCallbacks[messageId]
311
+ if (callback) {
312
+ // 执行回调,更新值并传递源对象
313
+ // sourceItems: 源对象数组,包含完整的选项信息
314
+ // 单选时:[{ value: '1001', label: '门店1', ... }]
315
+ // 多选时:[{ value: '1001', label: '门店1', ... }, { value: '1002', label: '门店2', ... }]
316
+ callback(value, sourceItems)
317
+ // 清理已处理的回调
318
+ delete this.pendingCallbacks[messageId]
319
+ }
320
+ }
321
+ },
322
+ // 合并选项到内部选项列表
323
+ mergeOptions(newItems) {
324
+ if (!Array.isArray(newItems) || newItems.length === 0) {
325
+ return
326
+ }
327
+
328
+ // 创建选项映射,用于去重
329
+ const optionMap = new Map()
330
+
331
+ // 先将现有内部选项添加到映射
332
+ this.internalOptions.forEach((opt) => {
333
+ const value = typeof opt === 'object' ? opt[this.valueKey] : opt
334
+ optionMap.set(value, opt)
335
+ })
336
+
337
+ // 再添加新选项(会覆盖相同 value 的选项)
338
+ newItems.forEach((opt) => {
339
+ const value = typeof opt === 'object' ? opt[this.valueKey] : opt
340
+ optionMap.set(value, opt)
341
+ })
342
+
343
+ // 更新内部选项列表
344
+ this.internalOptions = Array.from(optionMap.values())
345
+ },
346
+ handleUpdate(value) {
347
+ this.$emit('update:modelValue', value)
348
+ // 值更新后触发校验
349
+ this.triggerValidate()
350
+ },
351
+ handleChange(value) {
352
+ this.$emit(
353
+ 'change',
354
+ value,
355
+ this.sourceItems && this.sourceItems.length > 0
356
+ ? this.multiple
357
+ ? this.sourceItems
358
+ : this.sourceItems[0]
359
+ : this.multiple
360
+ ? []
361
+ : null
362
+ )
363
+ },
364
+ // 触发字段校验
365
+ triggerValidate() {
366
+ // 使用 nextTick 确保值已经更新完成
367
+ this.$nextTick(() => {
368
+ this.$nextTick(() => {
369
+ try {
370
+ // 方式1:通过 formCreateInject 中的 API 触发校验
371
+ if (
372
+ this.formCreateInject &&
373
+ this.formCreateInject.api &&
374
+ this.field
375
+ ) {
376
+ this.formCreateInject.api.validateField(this.field).catch(() => {
377
+ // 校验失败时静默处理,错误会通过 form-item 显示
378
+ })
379
+ return
380
+ }
381
+
382
+ // 方式2:通过组件实例查找父组件中的 form-create API
383
+ let parent = this.$parent
384
+ while (parent) {
385
+ if (
386
+ parent.$options &&
387
+ parent.$options.name === 'FormCreate' &&
388
+ parent.fapi
389
+ ) {
390
+ if (this.field && parent.fapi.validateField) {
391
+ parent.fapi.validateField(this.field).catch(() => {
392
+ // 校验失败时静默处理
393
+ })
394
+ }
395
+ break
396
+ }
397
+ parent = parent.$parent
398
+ }
399
+ } catch (error) {
400
+ console.warn('CusStoreSelect: 触发校验失败', error)
401
+ }
402
+ })
403
+ })
404
+ }
405
+ }
406
+ })
407
+ </script>