af-mobile-client-vue3 1.4.17 → 1.4.18

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,529 +1,547 @@
1
- <script setup lang="ts">
2
- import { computed, reactive, ref, watch } from 'vue'
3
-
4
- // 型号接口
5
- interface ModelItem {
6
- name: string
7
- price?: number
8
- }
9
-
10
- // 物品接口
11
- interface ItemType {
12
- name: string
13
- children?: ModelItem[]
14
- }
15
-
16
- // 分类接口
17
- interface CategoryItem {
18
- name: string
19
- children: ItemType[]
20
- }
21
-
22
- // 配置接口
23
- interface ChargeTypeConfig {
24
- value: CategoryItem[]
25
- }
26
-
27
- interface FormData {
28
- workOrderCode?: string
29
- workOrderId?: string
30
- type: string
31
- item: string
32
- model: string // 新增型号字段
33
- unitPrice: string
34
- quantity: number
35
- }
36
-
37
- const props = defineProps<{
38
- isWorkOrder?: boolean
39
- workOrderData?: any
40
- show: boolean
41
- chargeTypes: ChargeTypeConfig
42
- }>()
43
-
44
- const emit = defineEmits<{
45
- (e: 'update:show', value: boolean): void
46
- (e: 'add', item: {
47
- workOrderCode?: string
48
- workOrderId?: string
49
- id: number
50
- type: string
51
- item: string
52
- model: string // 新增型号字段
53
- unitPrice: number
54
- quantity: number
55
- total: string
56
- }): void
57
- }>()
58
-
59
- const showModal = computed({
60
- get: () => props.show,
61
- set: value => emit('update:show', value),
62
- })
63
-
64
- const formData = reactive<FormData>({
65
- workOrderId: '',
66
- type: '',
67
- item: '',
68
- model: '', // 新增型号字段
69
- unitPrice: '',
70
- quantity: 1,
71
- })
72
-
73
- const showWorkOrderSelector = ref(false)
74
- const showTypeSelector = ref(false)
75
- const showItemSelector = ref(false)
76
- const showModelSelector = ref(false) // 新增型号选择器状态
77
- const workOrderError = ref(false)
78
- const typeError = ref(false)
79
- const itemError = ref(false)
80
- const priceError = ref(false)
81
-
82
- // 分类选项
83
- const workOrderOptions = computed(() => {
84
- return props.workOrderData || []
85
- })
86
- const typeOptions = computed(() => {
87
- return props.chargeTypes.value.map(category => ({ text: category.name, value: category.name }))
88
- })
89
-
90
- // 物品选项
91
- const itemOptions = computed(() => {
92
- if (!formData.type)
93
- return []
94
-
95
- const category = props.chargeTypes.value.find(c => c.name === formData.type)
96
- if (!category)
97
- return []
98
-
99
- return category.children.map(item => ({ text: item.name, value: item.name }))
100
- })
101
-
102
- // 型号选项
103
- const modelOptions = computed(() => {
104
- if (!formData.type || !formData.item)
105
- return []
106
-
107
- const category = props.chargeTypes.value.find(c => c.name === formData.type)
108
- if (!category)
109
- return []
110
-
111
- const item = category.children.find(i => i.name === formData.item)
112
- if (!item || !item.children)
113
- return []
114
-
115
- return item.children.map(model => ({ text: model.name, value: model.name }))
116
- })
117
-
118
- // 是否有型号选项
119
- const hasModelOptions = computed(() => {
120
- if (!formData.type || !formData.item)
121
- return false
122
-
123
- const category = props.chargeTypes.value.find(c => c.name === formData.type)
124
- if (!category)
125
- return false
126
- console.log('category', category)
127
- const item = category.children.find(i => i.name === formData.item)
128
- console.log('item', item)
129
- return !!(item && item.children && item.children.length > 0)
130
- })
131
-
132
- // 选择处理函数
133
- function onWorkOrderSelected({ selectedValues, selectedOptions }) {
134
- formData.workOrderId = selectedValues[0]
135
- formData.workOrderCode = selectedOptions[0].text
136
- showWorkOrderSelector.value = false
137
- workOrderError.value = false
138
- }
139
- function onTypeSelected({ selectedValues }) {
140
- console.log('onTypeSelected', selectedValues)
141
- formData.type = selectedValues[0]
142
- formData.item = ''
143
- formData.model = '' // 重置型号
144
- formData.unitPrice = '' // 重置单价
145
- showTypeSelector.value = false
146
- typeError.value = false
147
- }
148
-
149
- function onItemSelected({ selectedValues }) {
150
- formData.item = selectedValues[0]
151
- formData.model = '' // 重置型号
152
- formData.unitPrice = '' // 重置单价
153
- showItemSelector.value = false
154
- itemError.value = false
155
- }
156
-
157
- // 型号选择处理函数
158
- function onModelSelected({ selectedValues }) {
159
- formData.model = selectedValues[0]
160
-
161
- // 设置对应型号的单价
162
- if (formData.type && formData.item && formData.model) {
163
- const category = props.chargeTypes.value.find(c => c.name === formData.type)
164
- if (category) {
165
- const item = category.children.find(i => i.name === formData.item)
166
- if (item && item.children) {
167
- const model = item.children.find(m => m.name === formData.model)
168
- if (model && model.price !== undefined) {
169
- formData.unitPrice = model.price.toString()
170
- }
171
- }
172
- }
173
- }
174
-
175
- showModelSelector.value = false
176
- }
177
-
178
- function handleSubmit() {
179
- // 验证表单
180
- let isValid = true
181
- // 验证工单
182
- if (props.isWorkOrder && !formData.workOrderId) {
183
- workOrderError.value = true
184
- isValid = false
185
- }
186
-
187
- if (!formData.type) {
188
- typeError.value = true
189
- isValid = false
190
- }
191
-
192
- if (!formData.item) {
193
- itemError.value = true
194
- isValid = false
195
- }
196
-
197
- if (!formData.unitPrice || Number.parseFloat(formData.unitPrice) <= 0) {
198
- priceError.value = true
199
- isValid = false
200
- }
201
-
202
- if (!isValid)
203
- return
204
-
205
- const unitPrice = Number.parseFloat(formData.unitPrice)
206
- const quantity = formData.quantity
207
- const total = (unitPrice * quantity).toFixed(2)
208
-
209
- // 提交新费用项
210
- emit('add', {
211
- id: Date.now(),
212
- workOrderId: formData.workOrderId || '',
213
- workOrderCode: formData.workOrderCode || '',
214
- type: formData.type,
215
- item: formData.item,
216
- model: formData.model, // 添加型号
217
- unitPrice,
218
- quantity,
219
- total,
220
- })
221
-
222
- // 重置表单
223
- resetForm()
224
-
225
- // 关闭弹窗
226
- closeModal()
227
- }
228
-
229
- function resetForm() {
230
- formData.workOrderId = ''
231
- formData.workOrderCode = ''
232
- formData.type = ''
233
- formData.item = ''
234
- formData.model = '' // 重置型号
235
- formData.unitPrice = ''
236
- formData.quantity = 1
237
- workOrderError.value = false
238
- typeError.value = false
239
- itemError.value = false
240
- priceError.value = false
241
- }
242
-
243
- function closeModal() {
244
- showModal.value = false
245
- resetForm()
246
- }
247
-
248
- // 监听输入,清除错误状态
249
- // eslint-disable-next-line style/max-statements-per-line
250
- watch(() => formData.workOrderId, () => { workOrderError.value = false })
251
- // eslint-disable-next-line style/max-statements-per-line
252
- watch(() => formData.type, () => { typeError.value = false })
253
- // eslint-disable-next-line style/max-statements-per-line
254
- watch(() => formData.item, () => { itemError.value = false })
255
- // eslint-disable-next-line style/max-statements-per-line
256
- watch(() => formData.unitPrice, () => { priceError.value = false })
257
- </script>
258
-
259
- <template>
260
- <van-popup
261
- v-model:show="showModal"
262
- round
263
- position="center"
264
- :style="{ width: '90%', maxWidth: '420px' }"
265
- close-icon-position="top-right"
266
- class="other-charge-item-modal"
267
- >
268
- <div class="other-charge-item-modal__container">
269
- <div class="other-charge-item-modal__header">
270
- <h3 class="other-charge-item-modal__title">
271
- 添加费用项
272
- </h3>
273
- <van-icon name="cross" @click="closeModal" />
274
- </div>
275
-
276
- <form class="other-charge-item-modal__form" @submit.prevent="handleSubmit">
277
- <div v-if="props.isWorkOrder" class="other-charge-item-modal__field">
278
- <label class="other-charge-item-modal__label">工单</label>
279
- <van-field
280
- v-model="formData.workOrderCode"
281
- placeholder="请选择工单"
282
- readonly
283
- right-icon="arrow-down"
284
- :error="workOrderError"
285
- @click="showWorkOrderSelector = true"
286
- />
287
- </div>
288
-
289
- <div class="other-charge-item-modal__field">
290
- <label class="other-charge-item-modal__label">收费类型</label>
291
- <van-field
292
- v-model="formData.type"
293
- placeholder="请选择收费类型"
294
- readonly
295
- right-icon="arrow-down"
296
- :error="typeError"
297
- @click="showTypeSelector = true"
298
- />
299
- </div>
300
-
301
- <div class="other-charge-item-modal__field">
302
- <label class="other-charge-item-modal__label">具体项目</label>
303
- <van-field
304
- v-model="formData.item"
305
- placeholder="请选择具体项目"
306
- readonly
307
- right-icon="arrow-down"
308
- :disabled="!formData.type"
309
- :error="itemError"
310
- @click="showItemSelector = true"
311
- />
312
- </div>
313
-
314
- <!-- 新增型号选择字段 -->
315
- <div v-if="hasModelOptions" class="other-charge-item-modal__field">
316
- <label class="other-charge-item-modal__label">型号</label>
317
- <van-field
318
- v-model="formData.model"
319
- placeholder="请选择型号"
320
- readonly
321
- right-icon="arrow-down"
322
- :disabled="!formData.item"
323
- @click="showModelSelector = true"
324
- />
325
- </div>
326
-
327
- <div class="other-charge-item-modal__price-quantity">
328
- <div class="other-charge-item-modal__field">
329
- <label class="other-charge-item-modal__label">单价 (元)</label>
330
- <div class="other-charge-item-modal__price-input">
331
- <van-field
332
- v-model="formData.unitPrice"
333
- type="digit"
334
- placeholder="0.00"
335
- :error="priceError"
336
- >
337
- <template #prefix>
338
- <span class="other-charge-item-modal__prefix">¥</span>
339
- </template>
340
- </van-field>
341
- </div>
342
- </div>
343
-
344
- <div class="other-charge-item-modal__field">
345
- <label class="other-charge-item-modal__label">数量</label>
346
- <van-stepper
347
- v-model="formData.quantity"
348
- min="1"
349
- step="1"
350
- input-width="60px"
351
- button-size="28px"
352
- theme="round"
353
- />
354
- </div>
355
- </div>
356
-
357
- <div class="other-charge-item-modal__buttons">
358
- <van-button
359
- plain
360
- type="default"
361
- size="normal"
362
- class="other-charge-item-modal__cancel-btn"
363
- @click="closeModal"
364
- >
365
- 取消
366
- </van-button>
367
- <van-button
368
- type="primary"
369
- size="normal"
370
- native-type="submit"
371
- class="other-charge-item-modal__confirm-btn"
372
- >
373
- 添加
374
- </van-button>
375
- </div>
376
- </form>
377
- </div>
378
-
379
- <!-- 工单 -->
380
- <van-popup
381
- v-model:show="showWorkOrderSelector"
382
- position="bottom"
383
-
384
- teleport="#other-charge-form"
385
- round destroy-on-close
386
- >
387
- <van-picker
388
- :columns="workOrderOptions"
389
- show-toolbar
390
- title="选择工单"
391
- @confirm="onWorkOrderSelected"
392
- @cancel="showWorkOrderSelector = false"
393
- />
394
- </van-popup>
395
-
396
- <!-- 收费类型选择器 -->
397
- <van-popup
398
- v-model:show="showTypeSelector"
399
- position="bottom"
400
- destroy-on-close
401
- teleport="#other-charge-form"
402
- round
403
- >
404
- <van-picker
405
- :columns="typeOptions"
406
- show-toolbar
407
- title="选择收费类型"
408
- @confirm="onTypeSelected"
409
- @cancel="showTypeSelector = false"
410
- />
411
- </van-popup>
412
-
413
- <!-- 具体项目选择器 -->
414
- <van-popup
415
- v-model:show="showItemSelector"
416
- destroy-on-close
417
- position="bottom"
418
- round
419
- teleport="#other-charge-form"
420
- >
421
- <van-picker
422
- :columns="itemOptions"
423
- show-toolbar
424
- title="选择具体项目"
425
- @confirm="onItemSelected"
426
- @cancel="showItemSelector = false"
427
- />
428
- </van-popup>
429
-
430
- <!-- 型号选择器 -->
431
- <van-popup
432
- v-model:show="showModelSelector"
433
- destroy-on-close
434
- position="bottom"
435
- round
436
- teleport="#other-charge-form"
437
- >
438
- <van-picker
439
- :columns="modelOptions"
440
- show-toolbar
441
- title="选择型号"
442
- @confirm="onModelSelected"
443
- @cancel="showModelSelector = false"
444
- />
445
- </van-popup>
446
- </van-popup>
447
- </template>
448
-
449
- <style scoped lang="less">
450
- .other-charge-item-modal {
451
- &__container {
452
- padding: 16px;
453
- }
454
-
455
- &__header {
456
- display: flex;
457
- justify-content: space-between;
458
- align-items: center;
459
- margin-bottom: 16px;
460
- }
461
-
462
- &__title {
463
- font-size: 18px;
464
- font-weight: 500;
465
- color: #1f2937;
466
- margin: 0;
467
- }
468
-
469
- &__form {
470
- .van-field {
471
- background-color: #f9fafb;
472
- border-radius: 6px;
473
- }
474
- }
475
-
476
- &__field {
477
- margin-bottom: 16px;
478
- }
479
-
480
- &__label {
481
- display: block;
482
- font-size: 14px;
483
- font-weight: 500;
484
- color: #374151;
485
- margin-bottom: 4px;
486
- }
487
-
488
- &__price-quantity {
489
- display: grid;
490
- grid-template-columns: 1fr 1fr;
491
- gap: 16px;
492
- }
493
-
494
- &__price-input {
495
- position: relative;
496
- }
497
-
498
- &__prefix {
499
- color: #6b7280;
500
- font-size: 14px;
501
- }
502
-
503
- &__buttons {
504
- display: flex;
505
- justify-content: flex-end;
506
- margin-top: 24px;
507
- gap: 12px;
508
- }
509
-
510
- &__cancel-btn {
511
- border-color: #d1d5db;
512
- color: #374151;
513
- }
514
-
515
- &__confirm-btn {
516
- background-color: #2563eb;
517
- }
518
-
519
- :deep(.van-stepper__input) {
520
- background-color: #f9fafb;
521
- }
522
-
523
- :deep(.van-stepper__minus),
524
- :deep(.van-stepper__plus) {
525
- background-color: #f3f4f6;
526
- border: 1px solid #d1d5db;
527
- }
528
- }
529
- </style>
1
+ <script setup lang="ts">
2
+ import {
3
+ Button as VanButton,
4
+ Field as VanField,
5
+ Icon as VanIcon,
6
+ Picker as VanPicker,
7
+ Popup as VanPopup,
8
+ Stepper as VanStepper,
9
+ } from 'vant'
10
+ import { computed, reactive, ref, watch } from 'vue'
11
+
12
+ // 型号接口
13
+ interface ModelItem {
14
+ name: string
15
+ price?: number
16
+ }
17
+
18
+ // 物品接口
19
+ interface ItemType {
20
+ name: string
21
+ children?: ModelItem[]
22
+ }
23
+
24
+ // 分类接口
25
+ interface CategoryItem {
26
+ name: string
27
+ children: ItemType[]
28
+ }
29
+
30
+ // 配置接口
31
+ interface ChargeTypeConfig {
32
+ value: CategoryItem[]
33
+ }
34
+
35
+ interface FormData {
36
+ workOrderCode?: string
37
+ workOrderId?: string
38
+ type: string
39
+ item: string
40
+ model: string // 新增型号字段
41
+ unitPrice: string
42
+ quantity: number
43
+ }
44
+
45
+ const props = defineProps<{
46
+ isWorkOrder?: boolean
47
+ workOrderData?: any
48
+ show: boolean
49
+ chargeTypes: ChargeTypeConfig
50
+ }>()
51
+
52
+ const emit = defineEmits<{
53
+ (e: 'update:show', value: boolean): void
54
+ (e: 'add', item: {
55
+ workOrderCode?: string
56
+ workOrderId?: string
57
+ id: number
58
+ type: string
59
+ item: string
60
+ model: string // 新增型号字段
61
+ unitPrice: number
62
+ quantity: number
63
+ total: string
64
+ }): void
65
+ }>()
66
+
67
+ const showModal = computed({
68
+ get: () => props.show,
69
+ set: value => emit('update:show', value),
70
+ })
71
+
72
+ // 工单选项
73
+ const workOrderOptions = computed(() => {
74
+ return props.workOrderData || []
75
+ })
76
+
77
+ const formData = reactive<FormData>({
78
+ workOrderCode: workOrderOptions.value.length === 1 ? workOrderOptions.value[0].text : '',
79
+ workOrderId: workOrderOptions.value.length === 1 ? workOrderOptions.value[0].value : '',
80
+ type: '',
81
+ item: '',
82
+ model: '', // 新增型号字段
83
+ unitPrice: '',
84
+ quantity: 1,
85
+ })
86
+
87
+ const showWorkOrderSelector = ref(false)
88
+ const showTypeSelector = ref(false)
89
+ const showItemSelector = ref(false)
90
+ const showModelSelector = ref(false) // 新增型号选择器状态
91
+ const workOrderError = ref(false)
92
+ const typeError = ref(false)
93
+ const itemError = ref(false)
94
+ const priceError = ref(false)
95
+ const isWorkOrder = computed(() => props.isWorkOrder)
96
+
97
+ // 分类选项
98
+ const typeOptions = computed(() => {
99
+ return props.chargeTypes.value.map(category => ({ text: category.name, value: category.name }))
100
+ })
101
+
102
+ // 物品选项
103
+ const itemOptions = computed(() => {
104
+ if (!formData.type)
105
+ return []
106
+
107
+ const category = props.chargeTypes.value.find(c => c.name === formData.type)
108
+ if (!category)
109
+ return []
110
+
111
+ return category.children.map(item => ({ text: item.name, value: item.name }))
112
+ })
113
+
114
+ // 型号选项
115
+ const modelOptions = computed(() => {
116
+ if (!formData.type || !formData.item)
117
+ return []
118
+
119
+ const category = props.chargeTypes.value.find(c => c.name === formData.type)
120
+ if (!category)
121
+ return []
122
+
123
+ const item = category.children.find(i => i.name === formData.item)
124
+ if (!item || !item.children)
125
+ return []
126
+
127
+ return item.children.map(model => ({ text: model.name, value: model.name }))
128
+ })
129
+
130
+ // 是否有型号选项
131
+ const hasModelOptions = computed(() => {
132
+ if (!formData.type || !formData.item)
133
+ return false
134
+
135
+ const category = props.chargeTypes.value.find(c => c.name === formData.type)
136
+ if (!category)
137
+ return false
138
+ console.log('category', category)
139
+ const item = category.children.find(i => i.name === formData.item)
140
+ console.log('item', item)
141
+ return !!(item && item.children && item.children.length > 0)
142
+ })
143
+
144
+ // 选择处理函数
145
+ function onWorkOrderSelected({ selectedValues, selectedOptions }) {
146
+ formData.workOrderId = selectedValues[0]
147
+ formData.workOrderCode = selectedOptions[0].text
148
+ showWorkOrderSelector.value = false
149
+ workOrderError.value = false
150
+ }
151
+ function onTypeSelected({ selectedValues }) {
152
+ console.log('onTypeSelected', selectedValues)
153
+ formData.type = selectedValues[0]
154
+ formData.item = ''
155
+ formData.model = '' // 重置型号
156
+ formData.unitPrice = '' // 重置单价
157
+ showTypeSelector.value = false
158
+ typeError.value = false
159
+ }
160
+
161
+ function onItemSelected({ selectedValues }) {
162
+ formData.item = selectedValues[0]
163
+ formData.model = '' // 重置型号
164
+ formData.unitPrice = '' // 重置单价
165
+ showItemSelector.value = false
166
+ itemError.value = false
167
+ }
168
+
169
+ // 型号选择处理函数
170
+ function onModelSelected({ selectedValues }) {
171
+ formData.model = selectedValues[0]
172
+
173
+ // 设置对应型号的单价
174
+ if (formData.type && formData.item && formData.model) {
175
+ const category = props.chargeTypes.value.find(c => c.name === formData.type)
176
+ if (category) {
177
+ const item = category.children.find(i => i.name === formData.item)
178
+ if (item && item.children) {
179
+ const model = item.children.find(m => m.name === formData.model)
180
+ if (model && model.price !== undefined) {
181
+ formData.unitPrice = model.price.toString()
182
+ }
183
+ }
184
+ }
185
+ }
186
+
187
+ showModelSelector.value = false
188
+ }
189
+
190
+ function handleSubmit() {
191
+ // 验证表单
192
+ let isValid = true
193
+ // 验证工单
194
+ if (isWorkOrder.value && !formData.workOrderId) {
195
+ workOrderError.value = true
196
+ isValid = false
197
+ }
198
+
199
+ if (!formData.type) {
200
+ typeError.value = true
201
+ isValid = false
202
+ }
203
+
204
+ if (!formData.item) {
205
+ itemError.value = true
206
+ isValid = false
207
+ }
208
+
209
+ if (!formData.unitPrice || Number.parseFloat(formData.unitPrice) <= 0) {
210
+ priceError.value = true
211
+ isValid = false
212
+ }
213
+
214
+ if (!isValid)
215
+ return
216
+
217
+ const unitPrice = Number.parseFloat(formData.unitPrice)
218
+ const quantity = formData.quantity
219
+ const total = (unitPrice * quantity).toFixed(2)
220
+
221
+ // 提交新费用项
222
+ emit('add', {
223
+ id: Date.now(),
224
+ workOrderId: formData.workOrderId || '',
225
+ workOrderCode: formData.workOrderCode || '',
226
+ type: formData.type,
227
+ item: formData.item,
228
+ model: formData.model, // 添加型号
229
+ unitPrice,
230
+ quantity,
231
+ total,
232
+ })
233
+
234
+ // 重置表单
235
+ resetForm()
236
+
237
+ // 关闭弹窗
238
+ closeModal()
239
+ }
240
+
241
+ function resetForm() {
242
+ if (isWorkOrder.value && workOrderOptions.value.length === 1) {
243
+ formData.workOrderId = workOrderOptions.value[0].value || ''
244
+ formData.workOrderCode = workOrderOptions.value[0].text || ''
245
+ }
246
+ else {
247
+ formData.workOrderId = ''
248
+ formData.workOrderCode = ''
249
+ }
250
+ formData.type = ''
251
+ formData.item = ''
252
+ formData.model = '' // 重置型号
253
+ formData.unitPrice = ''
254
+ formData.quantity = 1
255
+ workOrderError.value = false
256
+ typeError.value = false
257
+ itemError.value = false
258
+ priceError.value = false
259
+ }
260
+
261
+ function closeModal() {
262
+ showModal.value = false
263
+ resetForm()
264
+ }
265
+
266
+ // 监听输入,清除错误状态
267
+ // eslint-disable-next-line style/max-statements-per-line
268
+ watch(() => formData.workOrderId, () => { workOrderError.value = false })
269
+ // eslint-disable-next-line style/max-statements-per-line
270
+ watch(() => formData.type, () => { typeError.value = false })
271
+ // eslint-disable-next-line style/max-statements-per-line
272
+ watch(() => formData.item, () => { itemError.value = false })
273
+ // eslint-disable-next-line style/max-statements-per-line
274
+ watch(() => formData.unitPrice, () => { priceError.value = false })
275
+ </script>
276
+
277
+ <template>
278
+ <VanPopup
279
+ v-model:show="showModal"
280
+ round
281
+ position="center"
282
+ :style="{ width: '90%', maxWidth: '420px' }"
283
+ close-icon-position="top-right"
284
+ class="other-charge-item-modal"
285
+ >
286
+ <div class="other-charge-item-modal__container">
287
+ <div class="other-charge-item-modal__header">
288
+ <h3 class="other-charge-item-modal__title">
289
+ 添加费用项
290
+ </h3>
291
+ <VanIcon name="cross" @click="closeModal" />
292
+ </div>
293
+
294
+ <form class="other-charge-item-modal__form" @submit.prevent="handleSubmit">
295
+ <div v-if="isWorkOrder" class="other-charge-item-modal__field">
296
+ <label class="other-charge-item-modal__label">所属工单</label>
297
+ <VanField
298
+ v-model="formData.workOrderCode"
299
+ placeholder="请选择工单"
300
+ readonly
301
+ right-icon="arrow-down"
302
+ :error="workOrderError"
303
+ @click="showWorkOrderSelector = true"
304
+ />
305
+ </div>
306
+
307
+ <div class="other-charge-item-modal__field">
308
+ <label class="other-charge-item-modal__label">收费类型</label>
309
+ <VanField
310
+ v-model="formData.type"
311
+ placeholder="请选择收费类型"
312
+ readonly
313
+ right-icon="arrow-down"
314
+ :error="typeError"
315
+ @click="showTypeSelector = true"
316
+ />
317
+ </div>
318
+
319
+ <div class="other-charge-item-modal__field">
320
+ <label class="other-charge-item-modal__label">具体项目</label>
321
+ <VanField
322
+ v-model="formData.item"
323
+ placeholder="请选择具体项目"
324
+ readonly
325
+ right-icon="arrow-down"
326
+ :disabled="!formData.type"
327
+ :error="itemError"
328
+ @click="showItemSelector = true"
329
+ />
330
+ </div>
331
+
332
+ <!-- 新增型号选择字段 -->
333
+ <div v-if="hasModelOptions" class="other-charge-item-modal__field">
334
+ <label class="other-charge-item-modal__label">型号</label>
335
+ <VanField
336
+ v-model="formData.model"
337
+ placeholder="请选择型号"
338
+ readonly
339
+ right-icon="arrow-down"
340
+ :disabled="!formData.item"
341
+ @click="showModelSelector = true"
342
+ />
343
+ </div>
344
+
345
+ <div class="other-charge-item-modal__price-quantity">
346
+ <div class="other-charge-item-modal__field">
347
+ <label class="other-charge-item-modal__label">单价 (元)</label>
348
+ <div class="other-charge-item-modal__price-input">
349
+ <VanField
350
+ v-model="formData.unitPrice"
351
+ type="digit"
352
+ placeholder="0.00"
353
+ :error="priceError"
354
+ >
355
+ <template #prefix>
356
+ <span class="other-charge-item-modal__prefix">¥</span>
357
+ </template>
358
+ </VanField>
359
+ </div>
360
+ </div>
361
+
362
+ <div class="other-charge-item-modal__field">
363
+ <label class="other-charge-item-modal__label">数量</label>
364
+ <VanStepper
365
+ v-model="formData.quantity"
366
+ min="1"
367
+ step="1"
368
+ input-width="60px"
369
+ button-size="28px"
370
+ theme="round"
371
+ />
372
+ </div>
373
+ </div>
374
+
375
+ <div class="other-charge-item-modal__buttons">
376
+ <VanButton
377
+ plain
378
+ type="default"
379
+ size="normal"
380
+ class="other-charge-item-modal__cancel-btn"
381
+ @click="closeModal"
382
+ >
383
+ 取消
384
+ </VanButton>
385
+ <VanButton
386
+ type="primary"
387
+ size="normal"
388
+ native-type="submit"
389
+ class="other-charge-item-modal__confirm-btn"
390
+ >
391
+ 添加
392
+ </VanButton>
393
+ </div>
394
+ </form>
395
+ </div>
396
+
397
+ <!-- 工单 -->
398
+ <VanPopup
399
+ v-model:show="showWorkOrderSelector"
400
+ position="bottom"
401
+
402
+ teleport="#other-charge-form"
403
+ round destroy-on-close
404
+ >
405
+ <VanPicker
406
+ :columns="workOrderOptions"
407
+ show-toolbar
408
+ title="选择工单"
409
+ @confirm="onWorkOrderSelected"
410
+ @cancel="showWorkOrderSelector = false"
411
+ />
412
+ </VanPopup>
413
+
414
+ <!-- 收费类型选择器 -->
415
+ <VanPopup
416
+ v-model:show="showTypeSelector"
417
+ position="bottom"
418
+ destroy-on-close
419
+ teleport="#other-charge-form"
420
+ round
421
+ >
422
+ <VanPicker
423
+ :columns="typeOptions"
424
+ show-toolbar
425
+ title="选择收费类型"
426
+ @confirm="onTypeSelected"
427
+ @cancel="showTypeSelector = false"
428
+ />
429
+ </VanPopup>
430
+
431
+ <!-- 具体项目选择器 -->
432
+ <VanPopup
433
+ v-model:show="showItemSelector"
434
+ destroy-on-close
435
+ position="bottom"
436
+ round
437
+ teleport="#other-charge-form"
438
+ >
439
+ <VanPicker
440
+ :columns="itemOptions"
441
+ show-toolbar
442
+ title="选择具体项目"
443
+ @confirm="onItemSelected"
444
+ @cancel="showItemSelector = false"
445
+ />
446
+ </VanPopup>
447
+
448
+ <!-- 型号选择器 -->
449
+ <VanPopup
450
+ v-model:show="showModelSelector"
451
+ destroy-on-close
452
+ position="bottom"
453
+ round
454
+ teleport="#other-charge-form"
455
+ >
456
+ <VanPicker
457
+ :columns="modelOptions"
458
+ show-toolbar
459
+ title="选择型号"
460
+ @confirm="onModelSelected"
461
+ @cancel="showModelSelector = false"
462
+ />
463
+ </VanPopup>
464
+ </VanPopup>
465
+ </template>
466
+
467
+ <style scoped lang="less">
468
+ .other-charge-item-modal {
469
+ &__container {
470
+ padding: 16px;
471
+ }
472
+
473
+ &__header {
474
+ display: flex;
475
+ justify-content: space-between;
476
+ align-items: center;
477
+ margin-bottom: 16px;
478
+ }
479
+
480
+ &__title {
481
+ font-size: 18px;
482
+ font-weight: 500;
483
+ color: #1f2937;
484
+ margin: 0;
485
+ }
486
+
487
+ &__form {
488
+ .van-field {
489
+ background-color: #f9fafb;
490
+ border-radius: 6px;
491
+ }
492
+ }
493
+
494
+ &__field {
495
+ margin-bottom: 16px;
496
+ }
497
+
498
+ &__label {
499
+ display: block;
500
+ font-size: 14px;
501
+ font-weight: 500;
502
+ color: #374151;
503
+ margin-bottom: 4px;
504
+ }
505
+
506
+ &__price-quantity {
507
+ display: grid;
508
+ grid-template-columns: 1fr 1fr;
509
+ gap: 16px;
510
+ }
511
+
512
+ &__price-input {
513
+ position: relative;
514
+ }
515
+
516
+ &__prefix {
517
+ color: #6b7280;
518
+ font-size: 14px;
519
+ }
520
+
521
+ &__buttons {
522
+ display: flex;
523
+ justify-content: flex-end;
524
+ margin-top: 24px;
525
+ gap: 12px;
526
+ }
527
+
528
+ &__cancel-btn {
529
+ border-color: #d1d5db;
530
+ color: #374151;
531
+ }
532
+
533
+ &__confirm-btn {
534
+ background-color: #2563eb;
535
+ }
536
+
537
+ :deep(.van-stepper__input) {
538
+ background-color: #f9fafb;
539
+ }
540
+
541
+ :deep(.van-stepper__minus),
542
+ :deep(.van-stepper__plus) {
543
+ background-color: #f3f4f6;
544
+ border: 1px solid #d1d5db;
545
+ }
546
+ }
547
+ </style>