vue2-client 1.16.21 → 1.16.22

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,478 +1,478 @@
1
- <script>
2
- import { v4 as uuidv4 } from 'uuid'
3
- import {
4
- FormModel,
5
- Input,
6
- Button,
7
- Row,
8
- Col,
9
- Card,
10
- Divider,
11
- message,
12
- Select,
13
- Spin
14
- } from 'ant-design-vue'
15
- import { getConfigByNameAsync, runLogic } from '@vue2-client/services/api/common'
16
- import { mapState } from 'vuex'
17
- import { debounce } from 'lodash'
18
-
19
- export default {
20
- name: 'HAddNativeForm',
21
- components: {
22
- 'a-form-model': FormModel,
23
- 'a-form-item': FormModel.Item,
24
- 'a-input': Input,
25
- 'a-button': Button,
26
- 'a-row': Row,
27
- 'a-col': Col,
28
- 'a-card': Card,
29
- 'a-divider': Divider,
30
- 'a-select': Select,
31
- 'a-select-option': Select.Option,
32
- 'a-spin': Spin
33
- },
34
- props: {
35
- queryParamsName: {
36
- type: String,
37
- required: true
38
- },
39
- serviceName: {
40
- type: String,
41
- default: process.env.VUE_APP_SYSTEM_NAME
42
- }
43
- },
44
- data () {
45
- return {
46
- loading: true,
47
- config: {},
48
- formInstances: [],
49
- // 从配置读取的样式标识(默认值)
50
- formStyle: 'defaultForm'
51
- }
52
- },
53
- computed: {
54
- formItemLayoutComputed () {
55
- // 如果是 inline 布局,则不绑定 formItemLayout,让其使用 Ant Design Vue 默认的 inline 样式
56
- if (this.config.xAddFormLayout === 'inline') {
57
- return {}
58
- }
59
- if (this.config.formItemLayout) {
60
- return {
61
- labelCol: { span: this.config.formItemLayout.labelCol },
62
- wrapperCol: { span: this.config.formItemLayout.wrapperCol }
63
- }
64
- }
65
- // 如果是 horizontal 布局但未提供 formItemLayout,则提供一个默认值
66
- return {
67
- labelCol: { span: 6 }, // 默认标签宽度
68
- wrapperCol: { span: 18 } // 默认控件宽度,确保输入框足够宽
69
- }
70
- },
71
- ...mapState('account', { currUser: 'user' })
72
- },
73
- created () {
74
- this.debouncedSearch = debounce(this.performSearch, 500)
75
- this.fetchConfigAndInitializeForms()
76
- },
77
- methods: {
78
- async fetchConfigAndInitializeForms () {
79
- try {
80
- this.loading = true
81
- const res = await getConfigByNameAsync(this.queryParamsName, this.serviceName)
82
- if (res) {
83
- this.config = res
84
- // 从接口配置设置样式标识
85
- if (this.config.formStyle) {
86
- this.formStyle = this.config.formStyle
87
- }
88
- if (this.config.manyForm) {
89
- this.addFormInstance()
90
- } else {
91
- this.addFormInstance()
92
- }
93
- } else {
94
- console.error('HAddNativeForm: 未能获取到配置内容。')
95
- message.error('未能获取到表单配置,请检查配置名称或网络。')
96
- }
97
- } catch (error) {
98
- console.error('HAddNativeForm: 获取配置时发生错误:', error)
99
- message.error('获取表单配置时发生错误。')
100
- } finally {
101
- this.loading = false
102
- }
103
- },
104
- async getOptions (item, formDataContext, searchKeyword = '') {
105
- if (!item.keyName) {
106
- return
107
- }
108
-
109
- this.$set(item, 'loadingOptions', true)
110
- let fetchedOptions = []
111
-
112
- try {
113
- if (item.keyName.startsWith('logic@')) {
114
- const logicName = item.keyName.substring(6)
115
- const result = await runLogic(logicName, { searchKeyword: searchKeyword }, this.serviceName)
116
-
117
- if (Array.isArray(result)) {
118
- fetchedOptions = result.map(opt => ({
119
- label: opt.label || opt.name || opt.text,
120
- value: (opt.value || opt.id) + '',
121
- ...opt // 将所有原始属性也添加到选项中
122
- }))
123
- } else {
124
- console.warn(`Logic '${logicName}' did not return an array for options. Result:`, result)
125
- }
126
- } else if (item.keyName.startsWith('config@')) {
127
- const configName = item.keyName.substring(7)
128
- const res = await getConfigByNameAsync(configName, this.serviceName)
129
- if (res && res.value && Array.isArray(res.value)) {
130
- fetchedOptions = res.value.map(opt => ({
131
- label: opt.label || opt.name || opt.text,
132
- value: (opt.value || opt.id) + '',
133
- ...opt // 将所有原始属性也添加到选项中
134
- }))
135
- } else {
136
- console.warn(`Config '${configName}' did not return a valid 'value' array for options. Result:`, res)
137
- }
138
- } else {
139
- // 全局字典
140
- if (this.$appdata && typeof this.$appdata.getDictionaryList === 'function') {
141
- const dictionaryList = this.$appdata.getDictionaryList(item.keyName)
142
- if (Array.isArray(dictionaryList)) {
143
- fetchedOptions = dictionaryList.map(opt => ({
144
- label: opt.text,
145
- value: opt.value + '',
146
- ...opt // 将所有原始属性也添加到选项中
147
- }))
148
- } else {
149
- console.warn(`Global dictionary '${item.keyName}' did not return an array. Result:`, dictionaryList)
150
- }
151
- } else {
152
- console.error('$appdata.getDictionaryList is not available. Please ensure it\'s globally provided.')
153
- }
154
- }
155
- } catch (error) {
156
- console.error(`Error fetching options for item '${item.key}':`, error)
157
- fetchedOptions = []
158
- } finally {
159
- this.$set(item, 'options', fetchedOptions)
160
- this.$set(item, 'loadingOptions', false)
161
- }
162
- },
163
- // 搜索处理函数,使用 debounce 优化性能
164
- handleSelectSearch (value, item, formDataContext) {
165
- // 更新搜索关键字,并触发 debouncedSearch
166
- this.$set(item, 'searchKeyword', value)
167
- this.debouncedSearch(item, formDataContext)
168
- },
169
- // 执行实际的搜索操作
170
- performSearch (item, formDataContext) {
171
- this.getOptions(item, formDataContext, item.searchKeyword)
172
- },
173
- // 当选择框获得焦点时,重新加载选项(如果没有搜索关键字)
174
- handleSelectFocus (item, formDataContext) {
175
- if (!item.searchKeyword || item.searchKeyword === '') {
176
- this.getOptions(item, formDataContext)
177
- }
178
- },
179
- // 批量设置所有表单值
180
- setAllFormsData (dataList) {
181
- // 确保有足够多的表单实例来容纳所有数据
182
- while (this.formInstances.length < dataList.length) {
183
- this.addFormInstance()
184
- }
185
- dataList.forEach((data, index) => {
186
- if (this.formInstances[index]) {
187
- Object.assign(this.formInstances[index].formData, data)
188
- }
189
- })
190
- this.$forceUpdate()
191
- },
192
- addFormInstance () {
193
- const newId = uuidv4()
194
- const newFormData = {}
195
- const clonedFormItems = this.config.formItem ? JSON.parse(JSON.stringify(this.config.formItem)) : []
196
-
197
- if (clonedFormItems.length > 0) {
198
- for (const item of clonedFormItems) {
199
- newFormData[item.key] = undefined
200
- if (item.formType === 'select' && item.keyName) {
201
- this.$set(item, 'options', [])
202
- this.$set(item, 'loadingOptions', false)
203
- this.$set(item, 'searchKeyword', '')
204
- this.getOptions(item, newFormData)
205
- }
206
- }
207
- }
208
-
209
- this.formInstances.push({
210
- id: newId,
211
- formData: newFormData,
212
- formItems: clonedFormItems
213
- })
214
- },
215
- deleteFormInstance (id) {
216
- if (this.formInstances.length > 1) {
217
- this.formInstances = this.formInstances.filter(instance => instance.id !== id)
218
- } else {
219
- message.warning('至少需要保留一个表单实例。')
220
- }
221
- },
222
- getRules (item) {
223
- const rules = []
224
- if (item.rule && (item.rule.required === true || item.rule.required === 'true')) {
225
- rules.push({
226
- required: true,
227
- message: `请输入${item.title}`,
228
- trigger: 'blur'
229
- })
230
- }
231
- return rules
232
- },
233
- async onSubmit () {
234
- let allFormsValid = true
235
- const allFormsData = []
236
-
237
- for (const instance of this.formInstances) {
238
- const formRef = this.$refs['formModel_' + instance.id][0]
239
- if (formRef) {
240
- try {
241
- await formRef.validate()
242
- allFormsData.push(instance.formData)
243
- } catch (error) {
244
- allFormsValid = false
245
- break
246
- }
247
- }
248
- }
249
-
250
- if (allFormsValid) {
251
- message.success('表单提交成功!')
252
- this.$emit('submit', allFormsData)
253
- } else {
254
- message.error('部分表单校验失败,请检查。')
255
- }
256
- },
257
- getAllFormsData () {
258
- return this.formInstances.map(instance => {
259
- return instance.formData
260
- })
261
- },
262
- handleSelectChange (value, option, selectItem, formData, allFormItems) {
263
- // 直接获取option.data.props中的完整数据
264
- const selectedOptionData = option?.data?.props
265
- if (selectedOptionData) {
266
- for (const key in selectedOptionData) {
267
- if (Object.prototype.hasOwnProperty.call(selectedOptionData, key) && !['label', 'value'].includes(key)) {
268
- const targetItem = allFormItems.find(item => item.key === key)
269
- if (targetItem) {
270
- formData[key] = selectedOptionData[key]
271
- this.$forceUpdate()
272
- }
273
- }
274
- }
275
- }
276
- }
277
- }
278
- }
279
- </script>
280
-
281
- <template>
282
- <div class="h-add-native-form-group" :class="[`h-add-native-form-${formStyle}`]">
283
- <a-skeleton :loading="loading" :paragraph="{ rows: 4 }" />
284
- <div v-if="!loading">
285
- <div v-for="(formInstance, formIndex) in formInstances" :key="formInstance.id" class="form-instance-wrapper">
286
- <!-- <div v-if="config.manyForm" class="form-instance-title">药品 {{ formIndex + 1 }}</div> -->
287
- <a-form-model
288
- :ref="'formModel_' + formInstance.id"
289
- :model="formInstance.formData"
290
- v-bind="formItemLayoutComputed"
291
- :layout="config.xAddFormLayout || 'horizontal'"
292
- >
293
- <a-row :gutter="16">
294
- <a-col
295
- v-for="(item, itemIndex) in formInstance.formItems"
296
- :key="itemIndex"
297
- :xs="24"
298
- :sm="12"
299
- :md="8"
300
- :lg="6"
301
- :xl="6"
302
- >
303
- <a-form-item
304
- :label="item.title"
305
- :prop="item.key"
306
- :rules="getRules(item)"
307
- >
308
- <a-input
309
- v-if="item.formType === 'input'"
310
- v-model="formInstance.formData[item.key]"
311
- :placeholder="'请输入' + item.title"
312
- style="width: 100%"
313
- />
314
- <a-select
315
- v-else-if="item.formType === 'select'"
316
- v-model="formInstance.formData[item.key]"
317
- :placeholder=" '请选择' + item.title"
318
- :options="item.options"
319
- :loading="item.loadingOptions"
320
- show-search
321
- allowClear
322
- :filter-option="false"
323
- @search="(value) => handleSelectSearch(value, item, formInstance.formData)"
324
- @focus="() => handleSelectFocus(item, formInstance.formData)"
325
- @change="(value, option) => handleSelectChange(value, option, item, formInstance.formData, formInstance.formItems)"
326
- :dropdownMatchSelectWidth="false"
327
- :dropdownStyle="{ minWidth: '200px' }"
328
- style="width: 100%"
329
- >
330
- <a-spin v-if="item.loadingOptions" slot="notFoundContent" size="small" />
331
- </a-select>
332
- <!-- 可以根据需要添加其他表单类型,例如 select, datePicker 等 -->
333
- </a-form-item>
334
- </a-col>
335
- </a-row>
336
- </a-form-model>
337
- <a-divider style="margin: 10px" v-if="config.manyForm && formIndex < formInstances.length - 1" />
338
- </div>
339
-
340
- <!-- 全局添加/删除按钮,只在所有表单实例之后显示 -->
341
- <a-row v-if="config.manyForm" type="flex" justify="start" style="margin-top: 20px; gap: 10px;">
342
- <a-button v-if="config.showAddButton" class="form-action-button" icon="plus" @click="addFormInstance"></a-button>
343
- <a-button v-if="config.showDeleteButton && formInstances.length > 1" class="form-action-button" style="margin-left: 0px" icon="minus" @click="deleteFormInstance(formInstances[formInstances.length - 1].id)"></a-button>
344
- </a-row>
345
-
346
- <a-row v-if="config.showSubmitBtn" type="flex" justify="start" style="margin-top: 20px;">
347
- <a-col>
348
- <a-button type="primary" @click="onSubmit">{{ config.btnName || '提交' }}</a-button>
349
- </a-col>
350
- </a-row>
351
- </div>
352
- </div>
353
- </template>
354
-
355
- <style scoped lang="less">
356
- .h-add-native-form-group {
357
- // 基础样式,对应 defaultForm
358
- &.h-add-native-form-defaultForm {
359
- padding: 0px;
360
- // background-color: #fff;
361
- border-radius: 8px;
362
- }
363
- &.h-add-native-form-medicalForm {
364
- @media (min-width: 1200px) {
365
- :deep(.ant-col-xl-6) {
366
- display: block;
367
- flex: 0 0 42%;
368
- max-width: 42%;
369
- width: 42%;
370
- }
371
- }
372
- }
373
- }
374
-
375
- .form-instance-wrapper {
376
- margin-bottom: 0px;
377
- /* 移除边框和背景色,使其看起来像一个整体 */
378
- /* border: 1px solid #e8e8e8;
379
- border-radius: 4px;
380
- background-color: #fff; */
381
- }
382
-
383
- .form-instance-title {
384
- font-weight: bold;
385
- font-size: 16px;
386
- margin-bottom: 10px;
387
- padding-left: 10px; /* 与表单项对齐 */
388
- color: rgba(0, 0, 0, 0.85);
389
- }
390
-
391
- .form-action-button {
392
- width: 40px;
393
- height: 40px;
394
- border-radius: 8px;
395
- border: 1px solid #d9d9d9;
396
- background-color: #fff;
397
- color: rgba(0, 0, 0, 0.65);
398
- display: flex;
399
- justify-content: center;
400
- align-items: center;
401
- margin-left: 95px;
402
-
403
- &:hover {
404
- border-color: #40a9ff; /* 鼠标悬停时边框颜色 */
405
- color: #40a9ff; /* 鼠标悬停时图标颜色 */
406
- }
407
- }
408
-
409
- // 修复表单控件宽度问题
410
- :deep(.ant-form-item-control-wrapper) {
411
- width: 100%;
412
- }
413
-
414
- :deep(.ant-form-item-control) {
415
- width: 100%;
416
- }
417
-
418
- :deep(.ant-input),
419
- :deep(.ant-select) {
420
- width: 100% !important;
421
- }
422
-
423
- :deep(.ant-card-extra) {
424
- padding: 0;
425
- }
426
-
427
- :deep(.ant-form-item) {
428
- margin-bottom: 5px; // 增加表单项间距
429
- display: flex;
430
- flex-direction: column;
431
- margin-left: 95px;
432
- }
433
-
434
- :deep(.ant-form-item-label) {
435
- text-align: left;
436
- white-space: normal;
437
- }
438
-
439
- :deep(.ant-form-item-control-wrapper) {
440
- flex: 1;
441
- }
442
-
443
- // 针对 inline 布局的样式调整
444
- :deep(.ant-form-inline .ant-form-item) {
445
- margin-right: 0;
446
- margin-bottom: 16px;
447
- display: flex;
448
- flex-direction: row;
449
- align-items: flex-start;
450
- }
451
-
452
- :deep(.ant-form-inline .ant-form-item-label) {
453
- padding-right: 8px;
454
- flex: none;
455
- }
456
-
457
- :deep(.ant-form-inline .ant-form-item-control-wrapper) {
458
- flex: 1;
459
- min-width: 0;
460
- }
461
-
462
- :deep(.ant-form-inline .ant-input),
463
- :deep(.ant-form-inline .ant-select) {
464
- width: 100% !important;
465
- min-width: 120px; // 确保有最小宽度
466
- }
467
-
468
- // 响应式布局调整
469
- @media (max-width: 768px) {
470
- :deep(.ant-col-xs-24) {
471
- width: 100%;
472
- }
473
-
474
- :deep(.ant-form-item) {
475
- margin-bottom: 12px;
476
- }
477
- }
478
- </style>
1
+ <script>
2
+ import { v4 as uuidv4 } from 'uuid'
3
+ import {
4
+ FormModel,
5
+ Input,
6
+ Button,
7
+ Row,
8
+ Col,
9
+ Card,
10
+ Divider,
11
+ message,
12
+ Select,
13
+ Spin
14
+ } from 'ant-design-vue'
15
+ import { getConfigByNameAsync, runLogic } from '@vue2-client/services/api/common'
16
+ import { mapState } from 'vuex'
17
+ import { debounce } from 'lodash'
18
+
19
+ export default {
20
+ name: 'HAddNativeForm',
21
+ components: {
22
+ 'a-form-model': FormModel,
23
+ 'a-form-item': FormModel.Item,
24
+ 'a-input': Input,
25
+ 'a-button': Button,
26
+ 'a-row': Row,
27
+ 'a-col': Col,
28
+ 'a-card': Card,
29
+ 'a-divider': Divider,
30
+ 'a-select': Select,
31
+ 'a-select-option': Select.Option,
32
+ 'a-spin': Spin
33
+ },
34
+ props: {
35
+ queryParamsName: {
36
+ type: String,
37
+ required: true
38
+ },
39
+ serviceName: {
40
+ type: String,
41
+ default: process.env.VUE_APP_SYSTEM_NAME
42
+ }
43
+ },
44
+ data () {
45
+ return {
46
+ loading: true,
47
+ config: {},
48
+ formInstances: [],
49
+ // 从配置读取的样式标识(默认值)
50
+ formStyle: 'defaultForm'
51
+ }
52
+ },
53
+ computed: {
54
+ formItemLayoutComputed () {
55
+ // 如果是 inline 布局,则不绑定 formItemLayout,让其使用 Ant Design Vue 默认的 inline 样式
56
+ if (this.config.xAddFormLayout === 'inline') {
57
+ return {}
58
+ }
59
+ if (this.config.formItemLayout) {
60
+ return {
61
+ labelCol: { span: this.config.formItemLayout.labelCol },
62
+ wrapperCol: { span: this.config.formItemLayout.wrapperCol }
63
+ }
64
+ }
65
+ // 如果是 horizontal 布局但未提供 formItemLayout,则提供一个默认值
66
+ return {
67
+ labelCol: { span: 6 }, // 默认标签宽度
68
+ wrapperCol: { span: 18 } // 默认控件宽度,确保输入框足够宽
69
+ }
70
+ },
71
+ ...mapState('account', { currUser: 'user' })
72
+ },
73
+ created () {
74
+ this.debouncedSearch = debounce(this.performSearch, 500)
75
+ this.fetchConfigAndInitializeForms()
76
+ },
77
+ methods: {
78
+ async fetchConfigAndInitializeForms () {
79
+ try {
80
+ this.loading = true
81
+ const res = await getConfigByNameAsync(this.queryParamsName, this.serviceName)
82
+ if (res) {
83
+ this.config = res
84
+ // 从接口配置设置样式标识
85
+ if (this.config.formStyle) {
86
+ this.formStyle = this.config.formStyle
87
+ }
88
+ if (this.config.manyForm) {
89
+ this.addFormInstance()
90
+ } else {
91
+ this.addFormInstance()
92
+ }
93
+ } else {
94
+ console.error('HAddNativeForm: 未能获取到配置内容。')
95
+ message.error('未能获取到表单配置,请检查配置名称或网络。')
96
+ }
97
+ } catch (error) {
98
+ console.error('HAddNativeForm: 获取配置时发生错误:', error)
99
+ message.error('获取表单配置时发生错误。')
100
+ } finally {
101
+ this.loading = false
102
+ }
103
+ },
104
+ async getOptions (item, formDataContext, searchKeyword = '') {
105
+ if (!item.keyName) {
106
+ return
107
+ }
108
+
109
+ this.$set(item, 'loadingOptions', true)
110
+ let fetchedOptions = []
111
+
112
+ try {
113
+ if (item.keyName.startsWith('logic@')) {
114
+ const logicName = item.keyName.substring(6)
115
+ const result = await runLogic(logicName, { searchKeyword: searchKeyword }, this.serviceName)
116
+
117
+ if (Array.isArray(result)) {
118
+ fetchedOptions = result.map(opt => ({
119
+ label: opt.label || opt.name || opt.text,
120
+ value: (opt.value || opt.id) + '',
121
+ ...opt // 将所有原始属性也添加到选项中
122
+ }))
123
+ } else {
124
+ console.warn(`Logic '${logicName}' did not return an array for options. Result:`, result)
125
+ }
126
+ } else if (item.keyName.startsWith('config@')) {
127
+ const configName = item.keyName.substring(7)
128
+ const res = await getConfigByNameAsync(configName, this.serviceName)
129
+ if (res && res.value && Array.isArray(res.value)) {
130
+ fetchedOptions = res.value.map(opt => ({
131
+ label: opt.label || opt.name || opt.text,
132
+ value: (opt.value || opt.id) + '',
133
+ ...opt // 将所有原始属性也添加到选项中
134
+ }))
135
+ } else {
136
+ console.warn(`Config '${configName}' did not return a valid 'value' array for options. Result:`, res)
137
+ }
138
+ } else {
139
+ // 全局字典
140
+ if (this.$appdata && typeof this.$appdata.getDictionaryList === 'function') {
141
+ const dictionaryList = this.$appdata.getDictionaryList(item.keyName)
142
+ if (Array.isArray(dictionaryList)) {
143
+ fetchedOptions = dictionaryList.map(opt => ({
144
+ label: opt.text,
145
+ value: opt.value + '',
146
+ ...opt // 将所有原始属性也添加到选项中
147
+ }))
148
+ } else {
149
+ console.warn(`Global dictionary '${item.keyName}' did not return an array. Result:`, dictionaryList)
150
+ }
151
+ } else {
152
+ console.error('$appdata.getDictionaryList is not available. Please ensure it\'s globally provided.')
153
+ }
154
+ }
155
+ } catch (error) {
156
+ console.error(`Error fetching options for item '${item.key}':`, error)
157
+ fetchedOptions = []
158
+ } finally {
159
+ this.$set(item, 'options', fetchedOptions)
160
+ this.$set(item, 'loadingOptions', false)
161
+ }
162
+ },
163
+ // 搜索处理函数,使用 debounce 优化性能
164
+ handleSelectSearch (value, item, formDataContext) {
165
+ // 更新搜索关键字,并触发 debouncedSearch
166
+ this.$set(item, 'searchKeyword', value)
167
+ this.debouncedSearch(item, formDataContext)
168
+ },
169
+ // 执行实际的搜索操作
170
+ performSearch (item, formDataContext) {
171
+ this.getOptions(item, formDataContext, item.searchKeyword)
172
+ },
173
+ // 当选择框获得焦点时,重新加载选项(如果没有搜索关键字)
174
+ handleSelectFocus (item, formDataContext) {
175
+ if (!item.searchKeyword || item.searchKeyword === '') {
176
+ this.getOptions(item, formDataContext)
177
+ }
178
+ },
179
+ // 批量设置所有表单值
180
+ setAllFormsData (dataList) {
181
+ // 确保有足够多的表单实例来容纳所有数据
182
+ while (this.formInstances.length < dataList.length) {
183
+ this.addFormInstance()
184
+ }
185
+ dataList.forEach((data, index) => {
186
+ if (this.formInstances[index]) {
187
+ Object.assign(this.formInstances[index].formData, data)
188
+ }
189
+ })
190
+ this.$forceUpdate()
191
+ },
192
+ addFormInstance () {
193
+ const newId = uuidv4()
194
+ const newFormData = {}
195
+ const clonedFormItems = this.config.formItem ? JSON.parse(JSON.stringify(this.config.formItem)) : []
196
+
197
+ if (clonedFormItems.length > 0) {
198
+ for (const item of clonedFormItems) {
199
+ newFormData[item.key] = undefined
200
+ if (item.formType === 'select' && item.keyName) {
201
+ this.$set(item, 'options', [])
202
+ this.$set(item, 'loadingOptions', false)
203
+ this.$set(item, 'searchKeyword', '')
204
+ this.getOptions(item, newFormData)
205
+ }
206
+ }
207
+ }
208
+
209
+ this.formInstances.push({
210
+ id: newId,
211
+ formData: newFormData,
212
+ formItems: clonedFormItems
213
+ })
214
+ },
215
+ deleteFormInstance (id) {
216
+ if (this.formInstances.length > 1) {
217
+ this.formInstances = this.formInstances.filter(instance => instance.id !== id)
218
+ } else {
219
+ message.warning('至少需要保留一个表单实例。')
220
+ }
221
+ },
222
+ getRules (item) {
223
+ const rules = []
224
+ if (item.rule && (item.rule.required === true || item.rule.required === 'true')) {
225
+ rules.push({
226
+ required: true,
227
+ message: `请输入${item.title}`,
228
+ trigger: 'blur'
229
+ })
230
+ }
231
+ return rules
232
+ },
233
+ async onSubmit () {
234
+ let allFormsValid = true
235
+ const allFormsData = []
236
+
237
+ for (const instance of this.formInstances) {
238
+ const formRef = this.$refs['formModel_' + instance.id][0]
239
+ if (formRef) {
240
+ try {
241
+ await formRef.validate()
242
+ allFormsData.push(instance.formData)
243
+ } catch (error) {
244
+ allFormsValid = false
245
+ break
246
+ }
247
+ }
248
+ }
249
+
250
+ if (allFormsValid) {
251
+ message.success('表单提交成功!')
252
+ this.$emit('submit', allFormsData)
253
+ } else {
254
+ message.error('部分表单校验失败,请检查。')
255
+ }
256
+ },
257
+ getAllFormsData () {
258
+ return this.formInstances.map(instance => {
259
+ return instance.formData
260
+ })
261
+ },
262
+ handleSelectChange (value, option, selectItem, formData, allFormItems) {
263
+ // 直接获取option.data.props中的完整数据
264
+ const selectedOptionData = option?.data?.props
265
+ if (selectedOptionData) {
266
+ for (const key in selectedOptionData) {
267
+ if (Object.prototype.hasOwnProperty.call(selectedOptionData, key) && !['label', 'value'].includes(key)) {
268
+ const targetItem = allFormItems.find(item => item.key === key)
269
+ if (targetItem) {
270
+ formData[key] = selectedOptionData[key]
271
+ this.$forceUpdate()
272
+ }
273
+ }
274
+ }
275
+ }
276
+ }
277
+ }
278
+ }
279
+ </script>
280
+
281
+ <template>
282
+ <div class="h-add-native-form-group" :class="[`h-add-native-form-${formStyle}`]">
283
+ <a-skeleton :loading="loading" :paragraph="{ rows: 4 }" />
284
+ <div v-if="!loading">
285
+ <div v-for="(formInstance, formIndex) in formInstances" :key="formInstance.id" class="form-instance-wrapper">
286
+ <!-- <div v-if="config.manyForm" class="form-instance-title">药品 {{ formIndex + 1 }}</div> -->
287
+ <a-form-model
288
+ :ref="'formModel_' + formInstance.id"
289
+ :model="formInstance.formData"
290
+ v-bind="formItemLayoutComputed"
291
+ :layout="config.xAddFormLayout || 'horizontal'"
292
+ >
293
+ <a-row :gutter="16">
294
+ <a-col
295
+ v-for="(item, itemIndex) in formInstance.formItems"
296
+ :key="itemIndex"
297
+ :xs="24"
298
+ :sm="12"
299
+ :md="8"
300
+ :lg="6"
301
+ :xl="6"
302
+ >
303
+ <a-form-item
304
+ :label="item.title"
305
+ :prop="item.key"
306
+ :rules="getRules(item)"
307
+ >
308
+ <a-input
309
+ v-if="item.formType === 'input'"
310
+ v-model="formInstance.formData[item.key]"
311
+ :placeholder="'请输入' + item.title"
312
+ style="width: 100%"
313
+ />
314
+ <a-select
315
+ v-else-if="item.formType === 'select'"
316
+ v-model="formInstance.formData[item.key]"
317
+ :placeholder=" '请选择' + item.title"
318
+ :options="item.options"
319
+ :loading="item.loadingOptions"
320
+ show-search
321
+ allowClear
322
+ :filter-option="false"
323
+ @search="(value) => handleSelectSearch(value, item, formInstance.formData)"
324
+ @focus="() => handleSelectFocus(item, formInstance.formData)"
325
+ @change="(value, option) => handleSelectChange(value, option, item, formInstance.formData, formInstance.formItems)"
326
+ :dropdownMatchSelectWidth="false"
327
+ :dropdownStyle="{ minWidth: '200px' }"
328
+ style="width: 100%"
329
+ >
330
+ <a-spin v-if="item.loadingOptions" slot="notFoundContent" size="small" />
331
+ </a-select>
332
+ <!-- 可以根据需要添加其他表单类型,例如 select, datePicker 等 -->
333
+ </a-form-item>
334
+ </a-col>
335
+ </a-row>
336
+ </a-form-model>
337
+ <a-divider style="margin: 10px" v-if="config.manyForm && formIndex < formInstances.length - 1" />
338
+ </div>
339
+
340
+ <!-- 全局添加/删除按钮,只在所有表单实例之后显示 -->
341
+ <a-row v-if="config.manyForm" type="flex" justify="start" style="margin-top: 20px; gap: 10px;">
342
+ <a-button v-if="config.showAddButton" class="form-action-button" icon="plus" @click="addFormInstance"></a-button>
343
+ <a-button v-if="config.showDeleteButton && formInstances.length > 1" class="form-action-button" style="margin-left: 0px" icon="minus" @click="deleteFormInstance(formInstances[formInstances.length - 1].id)"></a-button>
344
+ </a-row>
345
+
346
+ <a-row v-if="config.showSubmitBtn" type="flex" justify="start" style="margin-top: 20px;">
347
+ <a-col>
348
+ <a-button type="primary" @click="onSubmit">{{ config.btnName || '提交' }}</a-button>
349
+ </a-col>
350
+ </a-row>
351
+ </div>
352
+ </div>
353
+ </template>
354
+
355
+ <style scoped lang="less">
356
+ .h-add-native-form-group {
357
+ // 基础样式,对应 defaultForm
358
+ &.h-add-native-form-defaultForm {
359
+ padding: 0px;
360
+ // background-color: #fff;
361
+ border-radius: 8px;
362
+ }
363
+ &.h-add-native-form-medicalForm {
364
+ @media (min-width: 1200px) {
365
+ :deep(.ant-col-xl-6) {
366
+ display: block;
367
+ flex: 0 0 42%;
368
+ max-width: 42%;
369
+ width: 42%;
370
+ }
371
+ }
372
+ }
373
+ }
374
+
375
+ .form-instance-wrapper {
376
+ margin-bottom: 0px;
377
+ /* 移除边框和背景色,使其看起来像一个整体 */
378
+ /* border: 1px solid #e8e8e8;
379
+ border-radius: 4px;
380
+ background-color: #fff; */
381
+ }
382
+
383
+ .form-instance-title {
384
+ font-weight: bold;
385
+ font-size: 16px;
386
+ margin-bottom: 10px;
387
+ padding-left: 10px; /* 与表单项对齐 */
388
+ color: rgba(0, 0, 0, 0.85);
389
+ }
390
+
391
+ .form-action-button {
392
+ width: 40px;
393
+ height: 40px;
394
+ border-radius: 8px;
395
+ border: 1px solid #d9d9d9;
396
+ background-color: #fff;
397
+ color: rgba(0, 0, 0, 0.65);
398
+ display: flex;
399
+ justify-content: center;
400
+ align-items: center;
401
+ margin-left: 95px;
402
+
403
+ &:hover {
404
+ border-color: #40a9ff; /* 鼠标悬停时边框颜色 */
405
+ color: #40a9ff; /* 鼠标悬停时图标颜色 */
406
+ }
407
+ }
408
+
409
+ // 修复表单控件宽度问题
410
+ :deep(.ant-form-item-control-wrapper) {
411
+ width: 100%;
412
+ }
413
+
414
+ :deep(.ant-form-item-control) {
415
+ width: 100%;
416
+ }
417
+
418
+ :deep(.ant-input),
419
+ :deep(.ant-select) {
420
+ width: 100% !important;
421
+ }
422
+
423
+ :deep(.ant-card-extra) {
424
+ padding: 0;
425
+ }
426
+
427
+ :deep(.ant-form-item) {
428
+ margin-bottom: 5px; // 增加表单项间距
429
+ display: flex;
430
+ flex-direction: column;
431
+ margin-left: 95px;
432
+ }
433
+
434
+ :deep(.ant-form-item-label) {
435
+ text-align: left;
436
+ white-space: normal;
437
+ }
438
+
439
+ :deep(.ant-form-item-control-wrapper) {
440
+ flex: 1;
441
+ }
442
+
443
+ // 针对 inline 布局的样式调整
444
+ :deep(.ant-form-inline .ant-form-item) {
445
+ margin-right: 0;
446
+ margin-bottom: 16px;
447
+ display: flex;
448
+ flex-direction: row;
449
+ align-items: flex-start;
450
+ }
451
+
452
+ :deep(.ant-form-inline .ant-form-item-label) {
453
+ padding-right: 8px;
454
+ flex: none;
455
+ }
456
+
457
+ :deep(.ant-form-inline .ant-form-item-control-wrapper) {
458
+ flex: 1;
459
+ min-width: 0;
460
+ }
461
+
462
+ :deep(.ant-form-inline .ant-input),
463
+ :deep(.ant-form-inline .ant-select) {
464
+ width: 100% !important;
465
+ min-width: 120px; // 确保有最小宽度
466
+ }
467
+
468
+ // 响应式布局调整
469
+ @media (max-width: 768px) {
470
+ :deep(.ant-col-xs-24) {
471
+ width: 100%;
472
+ }
473
+
474
+ :deep(.ant-form-item) {
475
+ margin-bottom: 12px;
476
+ }
477
+ }
478
+ </style>