af-mobile-client-vue3 1.4.66 → 1.4.67

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,542 +1,542 @@
1
- <script setup lang="ts">
2
- // 组件依赖:API 调用工具与 Vant 组件
3
- import { runLogic } from '@af-mobile-client-vue3/services/api/common'
4
- import {
5
- showFailToast,
6
- showToast,
7
- Button as VanButton,
8
- Field as VanField,
9
- Icon as VanIcon,
10
- Loading as VanLoading,
11
- Picker as VanPicker,
12
- Popup as VanPopup,
13
- } from 'vant'
14
- import { computed, reactive, ref, watch } from 'vue'
15
-
16
- // 工单信息接口定义
17
- interface WorkOrderOption {
18
- text: string
19
- value?: string
20
- workflowId?: string
21
- }
22
-
23
- // 材料组接口定义
24
- interface MaterialGroupItem {
25
- value: string
26
- label: string
27
- }
28
-
29
- // 表单数据接口定义
30
- interface FormData {
31
- workOrderId: string
32
- workOrderCode: string
33
- groupId: string
34
- groupName: string
35
- }
36
-
37
- // 组件属性定义:控制弹窗显示、工单数据与服务名
38
- const props = defineProps<{
39
- show: boolean
40
- workOrderData: WorkOrderOption[]
41
- serviceName?: string
42
- }>()
43
-
44
- // 组件事件定义:同步 show 状态与新增收费项
45
- const emit = defineEmits<{
46
- (e: 'update:show', value: boolean): void
47
- (e: 'add', item: {
48
- id: number
49
- type: string
50
- item: string
51
- model: string
52
- unitPrice: number
53
- quantity: number
54
- total: string
55
- workOrderCode: string
56
- workOrderId: string
57
- }): void
58
- }>()
59
-
60
- // 计算属性:双向绑定弹窗显隐
61
- const showModal = computed({
62
- get: () => props.show,
63
- set: value => emit('update:show', value),
64
- })
65
-
66
- // 计算属性:确定服务名称(优先使用外部传入)
67
- const serviceAppName = computed(() => props.serviceName || import.meta.env.VITE_APP_SYSTEM_NAME)
68
-
69
- // 全局变量:用于记录当前选中的工单详细信息
70
- const selectedWorkOrder = ref<WorkOrderOption | null>(null)
71
-
72
- // 表单状态:记录选择的工单与材料组
73
- const formData = reactive<FormData>({
74
- workOrderId: '',
75
- workOrderCode: '',
76
- groupId: '',
77
- groupName: '',
78
- })
79
-
80
- // 控制选择器显示状态
81
- const showWorkOrderSelector = ref(false)
82
- const showGroupSelector = ref(false)
83
-
84
- // 加载状态与提示文案
85
- const loading = ref(false)
86
- const materialGroups = ref<MaterialGroupItem[]>([])
87
- const materials = ref<any[]>([])
88
- const groupMessage = ref('')
89
- const materialMessage = ref('')
90
- const initializing = ref(false)
91
-
92
- // 工单下拉选项(仅保留合法 workflowId/value)
93
- const workOrderOptions = computed(() => {
94
- return (props.workOrderData || []).map(order => ({
95
- text: order.text || order.workflowId || order.value || '未命名工单',
96
- value: order.workflowId || order.value || '',
97
- })).filter(option => option.value)
98
- })
99
-
100
- // 材料组选项
101
- const groupOptions = computed(() => materialGroups.value.map(group => ({
102
- text: group.label,
103
- value: group.value,
104
- })))
105
-
106
- // 顶部提示信息:优先展示材料组提示,其次材料提示
107
- const headerMessage = computed(() => {
108
- if (groupMessage.value)
109
- return groupMessage.value
110
- if (materialMessage.value)
111
- return materialMessage.value
112
- return ''
113
- })
114
-
115
- // 提交按钮可用状态:需选择材料组并成功获取材料
116
- const canSubmit = computed(() => Boolean(formData.groupId && materials.value.length > 0 && !loading.value))
117
-
118
- // 关闭弹窗并重置数据
119
- function closeModal() {
120
- showModal.value = false
121
- resetForm()
122
- }
123
-
124
- // 重置所有状态数据
125
- function resetForm() {
126
- formData.workOrderId = ''
127
- formData.workOrderCode = ''
128
- formData.groupId = ''
129
- formData.groupName = ''
130
- materialGroups.value = []
131
- materials.value = []
132
- groupMessage.value = ''
133
- materialMessage.value = ''
134
- showWorkOrderSelector.value = false
135
- showGroupSelector.value = false
136
- selectedWorkOrder.value = null
137
- }
138
-
139
- // 根据 workflowId 定位工单
140
- function getSelectedWorkOrder(workflowId: string) {
141
- return (props.workOrderData || []).find(order => (order.workflowId || order.value) === workflowId)
142
- }
143
-
144
- // 选择工单后更新表单并查询材料组
145
- function onWorkOrderSelected({ selectedValues }: { selectedValues: string[] }) {
146
- const workflowId = selectedValues[0]
147
- const selected = getSelectedWorkOrder(workflowId) || null
148
- selectedWorkOrder.value = selected
149
- formData.workOrderId = workflowId
150
- formData.workOrderCode = selected?.text || '未命名工单'
151
- showWorkOrderSelector.value = false
152
- fetchMaterialGroups(workflowId, true)
153
- }
154
-
155
- // 选择材料组后更新表单并查询材料
156
- function onGroupSelected({ selectedValues }: { selectedValues: string[] }) {
157
- const selectedGroupId = selectedValues[0]
158
- const selectedGroup = materialGroups.value.find(group => group.value === selectedGroupId)
159
- if (selectedGroup) {
160
- formData.groupId = selectedGroup.value
161
- formData.groupName = selectedGroup.label
162
- }
163
- showGroupSelector.value = false
164
- materialMessage.value = ''
165
- materials.value = []
166
- if (formData.groupId) {
167
- fetchMaterialsByGroup(formData.groupId)
168
- }
169
- }
170
-
171
- // 根据工单查询材料组,可选择自动选中第一个材料组
172
- async function fetchMaterialGroups(workflowId: string, autoSelectFirstGroup = false) {
173
- if (!workflowId) {
174
- groupMessage.value = '请选择工单后再获取材料组'
175
- return
176
- }
177
- loading.value = true
178
- groupMessage.value = ''
179
- materialMessage.value = ''
180
- materialGroups.value = []
181
- materials.value = []
182
- formData.groupId = ''
183
- formData.groupName = ''
184
-
185
- try {
186
- const res = await runLogic('getMaterialGroup', {
187
- workflowId,
188
- }, serviceAppName.value)
189
- const groups = Array.isArray(res)
190
- ? res.map((item: any) => ({
191
- value: item.value || item.id || '',
192
- label: item.name || item.label || '未命名材料组',
193
- })).filter((group: MaterialGroupItem) => group.value)
194
- : []
195
-
196
- materialGroups.value = groups
197
- if (!groups.length) {
198
- groupMessage.value = '当前工单暂无可用材料组'
199
- }
200
- else if (autoSelectFirstGroup) {
201
- const [firstGroup] = groups
202
- formData.groupId = firstGroup.value
203
- formData.groupName = firstGroup.label
204
- await fetchMaterialsByGroup(firstGroup.value)
205
- }
206
- }
207
- catch (error) {
208
- console.error('获取材料组失败:', error)
209
- showFailToast('获取材料组失败')
210
- groupMessage.value = '获取材料组失败,请稍后重试'
211
- }
212
- finally {
213
- loading.value = false
214
- }
215
- }
216
-
217
- // 根据材料组查询材料清单
218
- async function fetchMaterialsByGroup(groupId: string) {
219
- if (!groupId)
220
- return
221
- loading.value = true
222
- materialMessage.value = ''
223
- materials.value = []
224
- try {
225
- const res = await runLogic('getMaterialByGroup', {
226
- id: groupId,
227
- }, serviceAppName.value)
228
-
229
- if (Array.isArray(res) && res.length > 0) {
230
- materials.value = res
231
- }
232
- else {
233
- materialMessage.value = '该材料组暂无材料'
234
- }
235
- }
236
- catch (error) {
237
- console.error('获取材料列表失败:', error)
238
- showFailToast('获取材料列表失败')
239
- materialMessage.value = '获取材料失败,请稍后再试'
240
- }
241
- finally {
242
- loading.value = false
243
- }
244
- }
245
-
246
- // 点击添加按钮时校验并返回材料数据
247
- async function handleSubmit() {
248
- if (!formData.workOrderId) {
249
- showToast('请选择工单')
250
- return
251
- }
252
- if (!formData.groupId) {
253
- showToast('请选择材料组')
254
- return
255
- }
256
- if (!materials.value.length) {
257
- showToast('该材料组暂无材料,无法添加')
258
- return
259
- }
260
-
261
- try {
262
- materials.value.forEach((material, index) => {
263
- const unitPrice = Number.parseFloat(material.price ?? '0') || 0
264
- const quantity = Number.parseFloat(material.count ?? '1') || 1
265
- const total = (unitPrice * quantity).toFixed(2)
266
-
267
- emit('add', {
268
- id: Date.now() + index,
269
- item: material.name || '未知材料',
270
- type: material.type || '未分类',
271
- model: material.model || '型号未知',
272
- unitPrice,
273
- quantity,
274
- total,
275
- workOrderId: formData.workOrderId,
276
- workOrderCode: formData.workOrderCode,
277
- })
278
- })
279
- showToast(`成功添加${materials.value.length}项材料`)
280
- closeModal()
281
- }
282
- catch (error) {
283
- console.error('提交材料失败:', error)
284
- showFailToast('添加材料失败')
285
- }
286
- }
287
-
288
- // 初始化默认选择:自动选择首个工单与材料组
289
- async function initDefaultSelections() {
290
- if (formData.workOrderId || initializing.value)
291
- return
292
- if (!workOrderOptions.value.length)
293
- return
294
- initializing.value = true
295
- try {
296
- const firstOrderOption = workOrderOptions.value[0]
297
- const firstOrder = getSelectedWorkOrder(firstOrderOption.value) || null
298
- selectedWorkOrder.value = firstOrder
299
- formData.workOrderId = firstOrderOption.value
300
- formData.workOrderCode = firstOrderOption.text
301
- await fetchMaterialGroups(firstOrderOption.value, true)
302
- }
303
- finally {
304
- initializing.value = false
305
- }
306
- }
307
-
308
- // 监听弹窗显示状态,打开时尝试进行默认选择
309
- watch(() => props.show, (visible) => {
310
- if (visible) {
311
- initDefaultSelections()
312
- }
313
- else {
314
- resetForm()
315
- }
316
- })
317
-
318
- // 监听工单数据变化,必要时重新初始化
319
- watch(() => props.workOrderData, () => {
320
- if (showModal.value && !formData.workOrderId) {
321
- initDefaultSelections()
322
- }
323
- }, { deep: true })
324
- </script>
325
-
326
- <template>
327
- <!-- 材料组选择弹窗容器 -->
328
- <VanPopup
329
- v-model:show="showModal"
330
- round
331
- position="center"
332
- :style="{ width: '90%', maxWidth: '420px' }"
333
- close-icon-position="top-right"
334
- class="other-charge-group-modal"
335
- >
336
- <!-- 主内容区域 -->
337
- <div class="other-charge-group-modal__container">
338
- <!-- 弹窗加载遮罩 -->
339
- <div v-if="loading" class="other-charge-group-modal__loading-mask">
340
- <VanLoading size="24px">
341
- 加载中...
342
- </VanLoading>
343
- </div>
344
- <!-- 弹窗标题区域 -->
345
- <div class="other-charge-group-modal__header">
346
- <h3 class="other-charge-group-modal__title">
347
- 选择材料组
348
- </h3>
349
- <VanIcon name="cross" @click="closeModal" />
350
- </div>
351
- <!-- 顶部提示信息 -->
352
- <p v-if="headerMessage" class="other-charge-group-modal__tip">
353
- {{ headerMessage }}
354
- </p>
355
-
356
- <!-- 选择表单 -->
357
- <form class="other-charge-group-modal__form" @submit.prevent="handleSubmit">
358
- <!-- 工单选择 -->
359
- <div class="other-charge-group-modal__field">
360
- <label class="other-charge-group-modal__label">工单号</label>
361
- <VanField
362
- v-model="formData.workOrderCode"
363
- placeholder="请选择工单"
364
- readonly
365
- right-icon="arrow-down"
366
- :disabled="!workOrderOptions.length"
367
- @click="workOrderOptions.length ? showWorkOrderSelector = true : showToast('暂无工单可选')"
368
- />
369
- </div>
370
-
371
- <!-- 材料组选择 -->
372
- <div class="other-charge-group-modal__field">
373
- <label class="other-charge-group-modal__label">材料组名称</label>
374
- <VanField
375
- v-model="formData.groupName"
376
- placeholder="请先选择工单"
377
- readonly
378
- right-icon="arrow-down"
379
- :disabled="!formData.workOrderId || !materialGroups.length"
380
- @click="formData.workOrderId && materialGroups.length ? showGroupSelector = true : showToast(formData.workOrderId ? '该工单暂无材料组' : '请先选择工单')"
381
- />
382
- </div>
383
-
384
- <!-- 材料数量提示 -->
385
- <p v-if="materials.length" class="other-charge-group-modal__material-count">
386
- 已获取 {{ materials.length }} 条材料
387
- </p>
388
-
389
- <!-- 操作按钮 -->
390
- <div class="other-charge-group-modal__buttons">
391
- <VanButton
392
- plain
393
- type="default"
394
- size="normal"
395
- class="other-charge-group-modal__cancel-btn"
396
- @click="closeModal"
397
- >
398
- 取消
399
- </VanButton>
400
- <VanButton
401
- type="primary"
402
- size="normal"
403
- native-type="submit"
404
- class="other-charge-group-modal__confirm-btn"
405
- :loading="loading"
406
- :disabled="!canSubmit"
407
- >
408
- 添加
409
- </VanButton>
410
- </div>
411
- </form>
412
- </div>
413
-
414
- <!-- 工单选择器弹窗 -->
415
- <VanPopup
416
- v-model:show="showWorkOrderSelector"
417
- position="bottom"
418
- teleport="#other-charge-form"
419
- round
420
- destroy-on-close
421
- >
422
- <VanPicker
423
- :columns="workOrderOptions"
424
- show-toolbar
425
- title="选择工单"
426
- @confirm="onWorkOrderSelected"
427
- @cancel="showWorkOrderSelector = false"
428
- />
429
- </VanPopup>
430
-
431
- <!-- 材料组选择器弹窗 -->
432
- <VanPopup
433
- v-model:show="showGroupSelector"
434
- position="bottom"
435
- teleport="#other-charge-form"
436
- round
437
- destroy-on-close
438
- >
439
- <VanPicker
440
- :columns="groupOptions"
441
- show-toolbar
442
- title="选择材料组"
443
- @confirm="onGroupSelected"
444
- @cancel="showGroupSelector = false"
445
- />
446
- </VanPopup>
447
- </VanPopup>
448
- </template>
449
-
450
- <style scoped lang="less">
451
- /* 组件样式:整体结构 */
452
- .other-charge-group-modal {
453
- /* 容器样式,含内边距与定位 */
454
- &__container {
455
- padding: 16px;
456
- position: relative;
457
- }
458
-
459
- /* 头部区域布局 */
460
- &__header {
461
- display: flex;
462
- justify-content: space-between;
463
- align-items: center;
464
- margin-bottom: 16px;
465
- }
466
-
467
- /* 标题样式 */
468
- &__title {
469
- font-size: 18px;
470
- font-weight: 500;
471
- color: #1f2937;
472
- margin: 0;
473
- }
474
-
475
- /* 表单输入域样式 */
476
- &__form {
477
- .van-field {
478
- background-color: #f9fafb;
479
- border-radius: 6px;
480
- }
481
- }
482
-
483
- /* 单个字段间距 */
484
- &__field {
485
- margin-bottom: 16px;
486
- }
487
-
488
- /* 字段标题样式 */
489
- &__label {
490
- display: block;
491
- font-size: 14px;
492
- font-weight: 500;
493
- color: #374151;
494
- margin-bottom: 4px;
495
- }
496
-
497
- /* 操作按钮区域 */
498
- &__buttons {
499
- display: flex;
500
- justify-content: flex-end;
501
- margin-top: 24px;
502
- gap: 12px;
503
- }
504
-
505
- /* 取消按钮样式 */
506
- &__cancel-btn {
507
- border-color: #d1d5db;
508
- color: #374151;
509
- }
510
-
511
- /* 确认按钮样式 */
512
- &__confirm-btn {
513
- background-color: #2563eb;
514
- }
515
-
516
- /* 加载遮罩样式 */
517
- &__loading-mask {
518
- position: absolute;
519
- inset: 0;
520
- background-color: rgba(255, 255, 255, 0.85);
521
- display: flex;
522
- align-items: center;
523
- justify-content: center;
524
- z-index: 2;
525
- border-radius: 12px;
526
- }
527
-
528
- /* 顶部提示文字 */
529
- &__tip {
530
- margin: 8px 0 0 0;
531
- font-size: 13px;
532
- color: #ef4444;
533
- }
534
-
535
- /* 材料数量提示 */
536
- &__material-count {
537
- font-size: 13px;
538
- color: #10b981;
539
- margin: 0 0 12px 0;
540
- }
541
- }
542
- </style>
1
+ <script setup lang="ts">
2
+ // 组件依赖:API 调用工具与 Vant 组件
3
+ import { runLogic } from '@af-mobile-client-vue3/services/api/common'
4
+ import {
5
+ showFailToast,
6
+ showToast,
7
+ Button as VanButton,
8
+ Field as VanField,
9
+ Icon as VanIcon,
10
+ Loading as VanLoading,
11
+ Picker as VanPicker,
12
+ Popup as VanPopup,
13
+ } from 'vant'
14
+ import { computed, reactive, ref, watch } from 'vue'
15
+
16
+ // 工单信息接口定义
17
+ interface WorkOrderOption {
18
+ text: string
19
+ value?: string
20
+ workflowId?: string
21
+ }
22
+
23
+ // 材料组接口定义
24
+ interface MaterialGroupItem {
25
+ value: string
26
+ label: string
27
+ }
28
+
29
+ // 表单数据接口定义
30
+ interface FormData {
31
+ workOrderId: string
32
+ workOrderCode: string
33
+ groupId: string
34
+ groupName: string
35
+ }
36
+
37
+ // 组件属性定义:控制弹窗显示、工单数据与服务名
38
+ const props = defineProps<{
39
+ show: boolean
40
+ workOrderData: WorkOrderOption[]
41
+ serviceName?: string
42
+ }>()
43
+
44
+ // 组件事件定义:同步 show 状态与新增收费项
45
+ const emit = defineEmits<{
46
+ (e: 'update:show', value: boolean): void
47
+ (e: 'add', item: {
48
+ id: number
49
+ type: string
50
+ item: string
51
+ model: string
52
+ unitPrice: number
53
+ quantity: number
54
+ total: string
55
+ workOrderCode: string
56
+ workOrderId: string
57
+ }): void
58
+ }>()
59
+
60
+ // 计算属性:双向绑定弹窗显隐
61
+ const showModal = computed({
62
+ get: () => props.show,
63
+ set: value => emit('update:show', value),
64
+ })
65
+
66
+ // 计算属性:确定服务名称(优先使用外部传入)
67
+ const serviceAppName = computed(() => props.serviceName || import.meta.env.VITE_APP_SYSTEM_NAME)
68
+
69
+ // 全局变量:用于记录当前选中的工单详细信息
70
+ const selectedWorkOrder = ref<WorkOrderOption | null>(null)
71
+
72
+ // 表单状态:记录选择的工单与材料组
73
+ const formData = reactive<FormData>({
74
+ workOrderId: '',
75
+ workOrderCode: '',
76
+ groupId: '',
77
+ groupName: '',
78
+ })
79
+
80
+ // 控制选择器显示状态
81
+ const showWorkOrderSelector = ref(false)
82
+ const showGroupSelector = ref(false)
83
+
84
+ // 加载状态与提示文案
85
+ const loading = ref(false)
86
+ const materialGroups = ref<MaterialGroupItem[]>([])
87
+ const materials = ref<any[]>([])
88
+ const groupMessage = ref('')
89
+ const materialMessage = ref('')
90
+ const initializing = ref(false)
91
+
92
+ // 工单下拉选项(仅保留合法 workflowId/value)
93
+ const workOrderOptions = computed(() => {
94
+ return (props.workOrderData || []).map(order => ({
95
+ text: order.text || order.workflowId || order.value || '未命名工单',
96
+ value: order.workflowId || order.value || '',
97
+ })).filter(option => option.value)
98
+ })
99
+
100
+ // 材料组选项
101
+ const groupOptions = computed(() => materialGroups.value.map(group => ({
102
+ text: group.label,
103
+ value: group.value,
104
+ })))
105
+
106
+ // 顶部提示信息:优先展示材料组提示,其次材料提示
107
+ const headerMessage = computed(() => {
108
+ if (groupMessage.value)
109
+ return groupMessage.value
110
+ if (materialMessage.value)
111
+ return materialMessage.value
112
+ return ''
113
+ })
114
+
115
+ // 提交按钮可用状态:需选择材料组并成功获取材料
116
+ const canSubmit = computed(() => Boolean(formData.groupId && materials.value.length > 0 && !loading.value))
117
+
118
+ // 关闭弹窗并重置数据
119
+ function closeModal() {
120
+ showModal.value = false
121
+ resetForm()
122
+ }
123
+
124
+ // 重置所有状态数据
125
+ function resetForm() {
126
+ formData.workOrderId = ''
127
+ formData.workOrderCode = ''
128
+ formData.groupId = ''
129
+ formData.groupName = ''
130
+ materialGroups.value = []
131
+ materials.value = []
132
+ groupMessage.value = ''
133
+ materialMessage.value = ''
134
+ showWorkOrderSelector.value = false
135
+ showGroupSelector.value = false
136
+ selectedWorkOrder.value = null
137
+ }
138
+
139
+ // 根据 workflowId 定位工单
140
+ function getSelectedWorkOrder(workflowId: string) {
141
+ return (props.workOrderData || []).find(order => (order.workflowId || order.value) === workflowId)
142
+ }
143
+
144
+ // 选择工单后更新表单并查询材料组
145
+ function onWorkOrderSelected({ selectedValues }: { selectedValues: string[] }) {
146
+ const workflowId = selectedValues[0]
147
+ const selected = getSelectedWorkOrder(workflowId) || null
148
+ selectedWorkOrder.value = selected
149
+ formData.workOrderId = workflowId
150
+ formData.workOrderCode = selected?.text || '未命名工单'
151
+ showWorkOrderSelector.value = false
152
+ fetchMaterialGroups(workflowId, true)
153
+ }
154
+
155
+ // 选择材料组后更新表单并查询材料
156
+ function onGroupSelected({ selectedValues }: { selectedValues: string[] }) {
157
+ const selectedGroupId = selectedValues[0]
158
+ const selectedGroup = materialGroups.value.find(group => group.value === selectedGroupId)
159
+ if (selectedGroup) {
160
+ formData.groupId = selectedGroup.value
161
+ formData.groupName = selectedGroup.label
162
+ }
163
+ showGroupSelector.value = false
164
+ materialMessage.value = ''
165
+ materials.value = []
166
+ if (formData.groupId) {
167
+ fetchMaterialsByGroup(formData.groupId)
168
+ }
169
+ }
170
+
171
+ // 根据工单查询材料组,可选择自动选中第一个材料组
172
+ async function fetchMaterialGroups(workflowId: string, autoSelectFirstGroup = false) {
173
+ if (!workflowId) {
174
+ groupMessage.value = '请选择工单后再获取材料组'
175
+ return
176
+ }
177
+ loading.value = true
178
+ groupMessage.value = ''
179
+ materialMessage.value = ''
180
+ materialGroups.value = []
181
+ materials.value = []
182
+ formData.groupId = ''
183
+ formData.groupName = ''
184
+
185
+ try {
186
+ const res = await runLogic('getMaterialGroup', {
187
+ workflowId,
188
+ }, serviceAppName.value)
189
+ const groups = Array.isArray(res)
190
+ ? res.map((item: any) => ({
191
+ value: item.value || item.id || '',
192
+ label: item.name || item.label || '未命名材料组',
193
+ })).filter((group: MaterialGroupItem) => group.value)
194
+ : []
195
+
196
+ materialGroups.value = groups
197
+ if (!groups.length) {
198
+ groupMessage.value = '当前工单暂无可用材料组'
199
+ }
200
+ else if (autoSelectFirstGroup) {
201
+ const [firstGroup] = groups
202
+ formData.groupId = firstGroup.value
203
+ formData.groupName = firstGroup.label
204
+ await fetchMaterialsByGroup(firstGroup.value)
205
+ }
206
+ }
207
+ catch (error) {
208
+ console.error('获取材料组失败:', error)
209
+ showFailToast('获取材料组失败')
210
+ groupMessage.value = '获取材料组失败,请稍后重试'
211
+ }
212
+ finally {
213
+ loading.value = false
214
+ }
215
+ }
216
+
217
+ // 根据材料组查询材料清单
218
+ async function fetchMaterialsByGroup(groupId: string) {
219
+ if (!groupId)
220
+ return
221
+ loading.value = true
222
+ materialMessage.value = ''
223
+ materials.value = []
224
+ try {
225
+ const res = await runLogic('getMaterialByGroup', {
226
+ id: groupId,
227
+ }, serviceAppName.value)
228
+
229
+ if (Array.isArray(res) && res.length > 0) {
230
+ materials.value = res
231
+ }
232
+ else {
233
+ materialMessage.value = '该材料组暂无材料'
234
+ }
235
+ }
236
+ catch (error) {
237
+ console.error('获取材料列表失败:', error)
238
+ showFailToast('获取材料列表失败')
239
+ materialMessage.value = '获取材料失败,请稍后再试'
240
+ }
241
+ finally {
242
+ loading.value = false
243
+ }
244
+ }
245
+
246
+ // 点击添加按钮时校验并返回材料数据
247
+ async function handleSubmit() {
248
+ if (!formData.workOrderId) {
249
+ showToast('请选择工单')
250
+ return
251
+ }
252
+ if (!formData.groupId) {
253
+ showToast('请选择材料组')
254
+ return
255
+ }
256
+ if (!materials.value.length) {
257
+ showToast('该材料组暂无材料,无法添加')
258
+ return
259
+ }
260
+
261
+ try {
262
+ materials.value.forEach((material, index) => {
263
+ const unitPrice = Number.parseFloat(material.price ?? '0') || 0
264
+ const quantity = Number.parseFloat(material.count ?? '1') || 1
265
+ const total = (unitPrice * quantity).toFixed(2)
266
+
267
+ emit('add', {
268
+ id: Date.now() + index,
269
+ item: material.name || '未知材料',
270
+ type: material.type || '未分类',
271
+ model: material.model || '型号未知',
272
+ unitPrice,
273
+ quantity,
274
+ total,
275
+ workOrderId: formData.workOrderId,
276
+ workOrderCode: formData.workOrderCode,
277
+ })
278
+ })
279
+ showToast(`成功添加${materials.value.length}项材料`)
280
+ closeModal()
281
+ }
282
+ catch (error) {
283
+ console.error('提交材料失败:', error)
284
+ showFailToast('添加材料失败')
285
+ }
286
+ }
287
+
288
+ // 初始化默认选择:自动选择首个工单与材料组
289
+ async function initDefaultSelections() {
290
+ if (formData.workOrderId || initializing.value)
291
+ return
292
+ if (!workOrderOptions.value.length)
293
+ return
294
+ initializing.value = true
295
+ try {
296
+ const firstOrderOption = workOrderOptions.value[0]
297
+ const firstOrder = getSelectedWorkOrder(firstOrderOption.value) || null
298
+ selectedWorkOrder.value = firstOrder
299
+ formData.workOrderId = firstOrderOption.value
300
+ formData.workOrderCode = firstOrderOption.text
301
+ await fetchMaterialGroups(firstOrderOption.value, true)
302
+ }
303
+ finally {
304
+ initializing.value = false
305
+ }
306
+ }
307
+
308
+ // 监听弹窗显示状态,打开时尝试进行默认选择
309
+ watch(() => props.show, (visible) => {
310
+ if (visible) {
311
+ initDefaultSelections()
312
+ }
313
+ else {
314
+ resetForm()
315
+ }
316
+ })
317
+
318
+ // 监听工单数据变化,必要时重新初始化
319
+ watch(() => props.workOrderData, () => {
320
+ if (showModal.value && !formData.workOrderId) {
321
+ initDefaultSelections()
322
+ }
323
+ }, { deep: true })
324
+ </script>
325
+
326
+ <template>
327
+ <!-- 材料组选择弹窗容器 -->
328
+ <VanPopup
329
+ v-model:show="showModal"
330
+ round
331
+ position="center"
332
+ :style="{ width: '90%', maxWidth: '420px' }"
333
+ close-icon-position="top-right"
334
+ class="other-charge-group-modal"
335
+ >
336
+ <!-- 主内容区域 -->
337
+ <div class="other-charge-group-modal__container">
338
+ <!-- 弹窗加载遮罩 -->
339
+ <div v-if="loading" class="other-charge-group-modal__loading-mask">
340
+ <VanLoading size="24px">
341
+ 加载中...
342
+ </VanLoading>
343
+ </div>
344
+ <!-- 弹窗标题区域 -->
345
+ <div class="other-charge-group-modal__header">
346
+ <h3 class="other-charge-group-modal__title">
347
+ 选择材料组
348
+ </h3>
349
+ <VanIcon name="cross" @click="closeModal" />
350
+ </div>
351
+ <!-- 顶部提示信息 -->
352
+ <p v-if="headerMessage" class="other-charge-group-modal__tip">
353
+ {{ headerMessage }}
354
+ </p>
355
+
356
+ <!-- 选择表单 -->
357
+ <form class="other-charge-group-modal__form" @submit.prevent="handleSubmit">
358
+ <!-- 工单选择 -->
359
+ <div class="other-charge-group-modal__field">
360
+ <label class="other-charge-group-modal__label">工单号</label>
361
+ <VanField
362
+ v-model="formData.workOrderCode"
363
+ placeholder="请选择工单"
364
+ readonly
365
+ right-icon="arrow-down"
366
+ :disabled="!workOrderOptions.length"
367
+ @click="workOrderOptions.length ? showWorkOrderSelector = true : showToast('暂无工单可选')"
368
+ />
369
+ </div>
370
+
371
+ <!-- 材料组选择 -->
372
+ <div class="other-charge-group-modal__field">
373
+ <label class="other-charge-group-modal__label">材料组名称</label>
374
+ <VanField
375
+ v-model="formData.groupName"
376
+ placeholder="请先选择工单"
377
+ readonly
378
+ right-icon="arrow-down"
379
+ :disabled="!formData.workOrderId || !materialGroups.length"
380
+ @click="formData.workOrderId && materialGroups.length ? showGroupSelector = true : showToast(formData.workOrderId ? '该工单暂无材料组' : '请先选择工单')"
381
+ />
382
+ </div>
383
+
384
+ <!-- 材料数量提示 -->
385
+ <p v-if="materials.length" class="other-charge-group-modal__material-count">
386
+ 已获取 {{ materials.length }} 条材料
387
+ </p>
388
+
389
+ <!-- 操作按钮 -->
390
+ <div class="other-charge-group-modal__buttons">
391
+ <VanButton
392
+ plain
393
+ type="default"
394
+ size="normal"
395
+ class="other-charge-group-modal__cancel-btn"
396
+ @click="closeModal"
397
+ >
398
+ 取消
399
+ </VanButton>
400
+ <VanButton
401
+ type="primary"
402
+ size="normal"
403
+ native-type="submit"
404
+ class="other-charge-group-modal__confirm-btn"
405
+ :loading="loading"
406
+ :disabled="!canSubmit"
407
+ >
408
+ 添加
409
+ </VanButton>
410
+ </div>
411
+ </form>
412
+ </div>
413
+
414
+ <!-- 工单选择器弹窗 -->
415
+ <VanPopup
416
+ v-model:show="showWorkOrderSelector"
417
+ position="bottom"
418
+ teleport="#other-charge-form"
419
+ round
420
+ destroy-on-close
421
+ >
422
+ <VanPicker
423
+ :columns="workOrderOptions"
424
+ show-toolbar
425
+ title="选择工单"
426
+ @confirm="onWorkOrderSelected"
427
+ @cancel="showWorkOrderSelector = false"
428
+ />
429
+ </VanPopup>
430
+
431
+ <!-- 材料组选择器弹窗 -->
432
+ <VanPopup
433
+ v-model:show="showGroupSelector"
434
+ position="bottom"
435
+ teleport="#other-charge-form"
436
+ round
437
+ destroy-on-close
438
+ >
439
+ <VanPicker
440
+ :columns="groupOptions"
441
+ show-toolbar
442
+ title="选择材料组"
443
+ @confirm="onGroupSelected"
444
+ @cancel="showGroupSelector = false"
445
+ />
446
+ </VanPopup>
447
+ </VanPopup>
448
+ </template>
449
+
450
+ <style scoped lang="less">
451
+ /* 组件样式:整体结构 */
452
+ .other-charge-group-modal {
453
+ /* 容器样式,含内边距与定位 */
454
+ &__container {
455
+ padding: 16px;
456
+ position: relative;
457
+ }
458
+
459
+ /* 头部区域布局 */
460
+ &__header {
461
+ display: flex;
462
+ justify-content: space-between;
463
+ align-items: center;
464
+ margin-bottom: 16px;
465
+ }
466
+
467
+ /* 标题样式 */
468
+ &__title {
469
+ font-size: 18px;
470
+ font-weight: 500;
471
+ color: #1f2937;
472
+ margin: 0;
473
+ }
474
+
475
+ /* 表单输入域样式 */
476
+ &__form {
477
+ .van-field {
478
+ background-color: #f9fafb;
479
+ border-radius: 6px;
480
+ }
481
+ }
482
+
483
+ /* 单个字段间距 */
484
+ &__field {
485
+ margin-bottom: 16px;
486
+ }
487
+
488
+ /* 字段标题样式 */
489
+ &__label {
490
+ display: block;
491
+ font-size: 14px;
492
+ font-weight: 500;
493
+ color: #374151;
494
+ margin-bottom: 4px;
495
+ }
496
+
497
+ /* 操作按钮区域 */
498
+ &__buttons {
499
+ display: flex;
500
+ justify-content: flex-end;
501
+ margin-top: 24px;
502
+ gap: 12px;
503
+ }
504
+
505
+ /* 取消按钮样式 */
506
+ &__cancel-btn {
507
+ border-color: #d1d5db;
508
+ color: #374151;
509
+ }
510
+
511
+ /* 确认按钮样式 */
512
+ &__confirm-btn {
513
+ background-color: #2563eb;
514
+ }
515
+
516
+ /* 加载遮罩样式 */
517
+ &__loading-mask {
518
+ position: absolute;
519
+ inset: 0;
520
+ background-color: rgba(255, 255, 255, 0.85);
521
+ display: flex;
522
+ align-items: center;
523
+ justify-content: center;
524
+ z-index: 2;
525
+ border-radius: 12px;
526
+ }
527
+
528
+ /* 顶部提示文字 */
529
+ &__tip {
530
+ margin: 8px 0 0 0;
531
+ font-size: 13px;
532
+ color: #ef4444;
533
+ }
534
+
535
+ /* 材料数量提示 */
536
+ &__material-count {
537
+ font-size: 13px;
538
+ color: #10b981;
539
+ margin: 0 0 12px 0;
540
+ }
541
+ }
542
+ </style>