af-mobile-client-vue3 1.4.14 → 1.4.16

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.
Files changed (25) hide show
  1. package/package.json +1 -1
  2. package/src/components/common/otherCharge/ChargePrintSelectorAndRemarks.vue +132 -0
  3. package/src/components/common/otherCharge/CodePayment.vue +351 -0
  4. package/src/components/common/otherCharge/FileUploader.vue +599 -0
  5. package/src/components/common/otherCharge/GridFileUploader.vue +840 -0
  6. package/src/components/common/otherCharge/PaymentMethodSelector.vue +202 -0
  7. package/src/components/common/otherCharge/PaymentMethodSelectorCard.vue +45 -0
  8. package/src/components/common/otherCharge/ReceiptModal.vue +269 -0
  9. package/src/components/common/otherCharge/index.ts +43 -0
  10. package/src/components/core/Signature/SignatureComponent.vue +312 -0
  11. package/src/components/core/Signature/signature.ts +38 -0
  12. package/src/components/data/OtherCharge/OtherChargeForm.vue +736 -0
  13. package/src/components/data/OtherCharge/OtherChargeItem.vue +166 -0
  14. package/src/components/data/OtherCharge/OtherChargeItemModal.vue +529 -0
  15. package/src/components/data/XBadge/index.vue +1 -1
  16. package/src/components/data/XFormItem/index.vue +0 -2
  17. package/src/router/routes.ts +421 -421
  18. package/src/stores/modules/setting.ts +1 -0
  19. package/src/stores/modules/user.ts +1 -1
  20. package/src/utils/queryFormDefaultRangePicker.ts +57 -57
  21. package/src/views/component/XCellListView/index.vue +138 -104
  22. package/src/views/component/XFormGroupView/index.vue +82 -78
  23. package/src/views/component/XFormView/index.vue +46 -41
  24. package/src/views/user/login/LoginForm.vue +5 -9
  25. package/vite.config.ts +2 -9
@@ -0,0 +1,166 @@
1
+ <script setup lang="ts">
2
+ interface ChargeItem {
3
+ id: number
4
+ workOrderCode?: string
5
+ workOrderId?: string
6
+ type: string
7
+ item: string
8
+ unitPrice: number
9
+ quantity: number
10
+ total: string
11
+ }
12
+
13
+ defineProps<{
14
+ item: ChargeItem
15
+ index: number
16
+ isWorkOrder?: boolean
17
+ workOrderData?: any
18
+ }>()
19
+
20
+ defineEmits<{
21
+ (e: 'remove', id: number): void
22
+ }>()
23
+
24
+ function formatPrice(price: number): string {
25
+ return price.toFixed(2)
26
+ }
27
+ </script>
28
+
29
+ <template>
30
+ <div class="other-charge-item">
31
+ <div class="other-charge-item__header">
32
+ <h5 class="other-charge-item__title">
33
+ 费用项 #{{ index + 1 }}
34
+ </h5>
35
+ <van-icon
36
+ name="cross"
37
+ class="other-charge-item__delete"
38
+ @click="$emit('remove', item.id)"
39
+ />
40
+ </div>
41
+
42
+ <div class="other-charge-item__info">
43
+ <div v-if="isWorkOrder" class="other-charge-item__field">
44
+ <p class="other-charge-item__label">
45
+ 工单
46
+ </p>
47
+ <p class="other-charge-item__value">
48
+ {{ item.workOrderCode }}
49
+ </p>
50
+ </div>
51
+ <div class="other-charge-item__field">
52
+ <p class="other-charge-item__label">
53
+ 收费类型
54
+ </p>
55
+ <p class="other-charge-item__value">
56
+ {{ item.type }}
57
+ </p>
58
+ </div>
59
+ <div class="other-charge-item__field">
60
+ <p class="other-charge-item__label">
61
+ 具体项目
62
+ </p>
63
+ <p class="other-charge-item__value">
64
+ {{ item.item }}
65
+ </p>
66
+ </div>
67
+ </div>
68
+
69
+ <div class="other-charge-item__details">
70
+ <div class="other-charge-item__field">
71
+ <p class="other-charge-item__label">
72
+ 单价
73
+ </p>
74
+ <p class="other-charge-item__value">
75
+ ¥{{ formatPrice(item.unitPrice) }}
76
+ </p>
77
+ </div>
78
+ <div class="other-charge-item__field">
79
+ <p class="other-charge-item__label">
80
+ 数量
81
+ </p>
82
+ <p class="other-charge-item__value">
83
+ {{ item.quantity }}
84
+ </p>
85
+ </div>
86
+ <div class="other-charge-item__field">
87
+ <p class="other-charge-item__label">
88
+ 小计
89
+ </p>
90
+ <p class="other-charge-item__value--highlight">
91
+ ¥{{ item.total }}
92
+ </p>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ </template>
97
+
98
+ <style scoped lang="less">
99
+ .other-charge-item {
100
+ background-color: #f9fafb;
101
+ padding: 12px;
102
+ border-radius: 6px;
103
+ border: 1px solid #e5e7eb;
104
+ margin-bottom: 12px;
105
+
106
+ &__header {
107
+ display: flex;
108
+ justify-content: space-between;
109
+ align-items: flex-start;
110
+ margin-bottom: 8px;
111
+ }
112
+
113
+ &__title {
114
+ font-size: 12px;
115
+ font-weight: 500;
116
+ color: #374151;
117
+ margin: 0;
118
+ }
119
+
120
+ &__delete {
121
+ color: #ef4444;
122
+ font-size: 16px;
123
+
124
+ &:active {
125
+ color: #b91c1c;
126
+ }
127
+ }
128
+
129
+ &__info {
130
+ display: grid;
131
+ grid-template-columns: 1fr 1fr;
132
+ gap: 12px;
133
+ margin-bottom: 8px;
134
+ }
135
+
136
+ &__details {
137
+ display: grid;
138
+ grid-template-columns: 1fr 1fr 1fr;
139
+ gap: 12px;
140
+ }
141
+
142
+ &__field {
143
+ margin-bottom: 0;
144
+ }
145
+
146
+ &__label {
147
+ font-size: 12px;
148
+ color: #6b7280;
149
+ margin: 0 0 2px 0;
150
+ }
151
+
152
+ &__value {
153
+ font-size: 14px;
154
+ font-weight: 500;
155
+ color: #1f2937;
156
+ margin: 0;
157
+
158
+ &--highlight {
159
+ font-size: 14px;
160
+ font-weight: 500;
161
+ color: #2563eb;
162
+ margin: 0;
163
+ }
164
+ }
165
+ }
166
+ </style>
@@ -0,0 +1,529 @@
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>
@@ -4,7 +4,7 @@ import dayjs from 'dayjs/esm/index'
4
4
  import { ref } from 'vue'
5
5
 
6
6
  const { serviceName, dictName, dictValue, dictType } = defineProps<{
7
- serviceName: string
7
+ serviceName?: string
8
8
  dictName: string | null | undefined
9
9
  dictValue: string | number | null | undefined
10
10
  // 用于非字典型的格式化展示:如日期/小数/整数等
@@ -225,8 +225,6 @@ function checkModel(val = props.modelValue) {
225
225
  */
226
226
  function getDefaultValue() {
227
227
  const val = props.modelValue
228
- console.warn('>>>>>props', props)
229
- console.warn('>>>> attr', attr)
230
228
  // 如果有有效值,直接返回(datePicker 初始值需按 formValueFormat 归一)
231
229
  // 目的:外部通过 formData 传入的初始值在组件挂载即与配置格式一致
232
230
  if (checkModel(val)) {