af-mobile-client-vue3 1.4.23 → 1.4.24
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.
- package/package.json +1 -1
- package/src/components/common/otherCharge/ChargePrintSelectorAndRemarks.vue +137 -137
- package/src/components/common/otherCharge/CodePayment.vue +357 -357
- package/src/components/common/otherCharge/FileUploader.vue +602 -602
- package/src/components/common/otherCharge/GridFileUploader.vue +846 -846
- package/src/components/common/otherCharge/PaymentMethodSelector.vue +202 -202
- package/src/components/common/otherCharge/PaymentMethodSelectorCard.vue +45 -45
- package/src/components/common/otherCharge/ReceiptModal.vue +273 -273
- package/src/components/common/otherCharge/index.ts +43 -43
- package/src/components/data/OtherCharge/OtherChargeForm.vue +744 -744
- package/src/components/data/OtherCharge/OtherChargeItem.vue +168 -168
- package/src/components/data/OtherCharge/OtherChargeItemModal.vue +547 -547
- package/src/components/data/XFormItem/index.vue +1638 -1632
- package/src/router/routes.ts +427 -427
- package/src/utils/queryFormDefaultRangePicker.ts +57 -57
- package/src/views/component/OtherCharge/index.vue +42 -42
- package/src/views/component/XCellListView/index.vue +138 -107
- package/src/views/component/XFormGroupView/index.vue +82 -78
- package/src/views/component/XFormView/index.vue +46 -41
|
@@ -1,744 +1,744 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { FileItem } from '../../common/otherCharge/FileUploader.vue'
|
|
3
|
-
import CardContainer from '@af-mobile-client-vue3/components/data/CardContainer/CardContainer.vue'
|
|
4
|
-
import CardHeader from '@af-mobile-client-vue3/components/data/CardContainer/CardHeader.vue'
|
|
5
|
-
import { getConfigByNameAsync, runLogic } from '@af-mobile-client-vue3/services/api/common'
|
|
6
|
-
import useUserStore from '@af-mobile-client-vue3/stores/modules/user'
|
|
7
|
-
import {
|
|
8
|
-
showDialog,
|
|
9
|
-
showFailToast,
|
|
10
|
-
showToast,
|
|
11
|
-
Button as VanButton,
|
|
12
|
-
Field as VanField,
|
|
13
|
-
Icon as VanIcon,
|
|
14
|
-
} from 'vant'
|
|
15
|
-
import { computed, onMounted, reactive, ref } from 'vue'
|
|
16
|
-
// import { otherCharge } from '@/services/api/business'
|
|
17
|
-
import ChargePrintSelectorAndRemarks from '../../common/otherCharge/ChargePrintSelectorAndRemarks.vue'
|
|
18
|
-
import {
|
|
19
|
-
CodePayment,
|
|
20
|
-
FileUploader,
|
|
21
|
-
GridFileUploader,
|
|
22
|
-
PaymentMethodSelectorCard,
|
|
23
|
-
ReceiptModal,
|
|
24
|
-
} from '../../common/otherCharge/index'
|
|
25
|
-
import OtherChargeItem from './OtherChargeItem.vue'
|
|
26
|
-
import OtherChargeItemModal from './OtherChargeItemModal.vue'
|
|
27
|
-
|
|
28
|
-
interface BusinessUser {
|
|
29
|
-
f_workflow_id?: string
|
|
30
|
-
f_userinfo_id: string
|
|
31
|
-
f_userinfo_code: string
|
|
32
|
-
f_user_name: string
|
|
33
|
-
f_userfiles_id?: string
|
|
34
|
-
f_orgid?: string
|
|
35
|
-
// f_user_phone: string
|
|
36
|
-
// f_user_type: string
|
|
37
|
-
// f_address: string
|
|
38
|
-
// f_meter_type: string
|
|
39
|
-
// f_meternumber: string
|
|
40
|
-
// f_balance: string
|
|
41
|
-
// f_user_state: string
|
|
42
|
-
// f_last_gas_date?: string
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface ChargeItem {
|
|
46
|
-
id: number
|
|
47
|
-
workOrderCode?: string
|
|
48
|
-
workOrderId?: string
|
|
49
|
-
type: string
|
|
50
|
-
item: string
|
|
51
|
-
model?: string // 添加型号字段
|
|
52
|
-
unitPrice: number
|
|
53
|
-
quantity: number
|
|
54
|
-
total: string
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface ReceiptData {
|
|
58
|
-
transactionId: string
|
|
59
|
-
userName: string
|
|
60
|
-
userId: string
|
|
61
|
-
cardNo: string
|
|
62
|
-
items: ChargeItem[]
|
|
63
|
-
totalAmount: string
|
|
64
|
-
paymentMethod: string
|
|
65
|
-
remarks: string
|
|
66
|
-
operator: string
|
|
67
|
-
dateTime: string
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// 收费类型配置接口
|
|
71
|
-
interface ModelItem {
|
|
72
|
-
name: string
|
|
73
|
-
price?: number
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
interface ItemType {
|
|
77
|
-
name: string
|
|
78
|
-
children?: ModelItem[]
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
interface ChargeTypeCategory {
|
|
82
|
-
name: string
|
|
83
|
-
children: ItemType[]
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
interface ChargeTypeConfig {
|
|
87
|
-
value: ChargeTypeCategory[]
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// 添加其他收费配置接口
|
|
91
|
-
interface OtherChargeConfig {
|
|
92
|
-
hasPrint: boolean
|
|
93
|
-
printType: string
|
|
94
|
-
payment: string
|
|
95
|
-
allowedMethods: string[] | null
|
|
96
|
-
floor: boolean
|
|
97
|
-
calculatePreByCollection: boolean
|
|
98
|
-
printLogic: string
|
|
99
|
-
submitLogic: string
|
|
100
|
-
fileTypes?: {
|
|
101
|
-
userType: string
|
|
102
|
-
picMinNum: number
|
|
103
|
-
description?: string
|
|
104
|
-
}[]
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// 接收用户信息prop
|
|
108
|
-
const props = defineProps<{
|
|
109
|
-
user?: BusinessUser
|
|
110
|
-
isWorkOrder?: boolean
|
|
111
|
-
workOrderData?: any
|
|
112
|
-
}>()
|
|
113
|
-
|
|
114
|
-
const emit = defineEmits<{
|
|
115
|
-
(e: 'closeOperation'): void
|
|
116
|
-
(e: 'complete'): void
|
|
117
|
-
}>()
|
|
118
|
-
|
|
119
|
-
// 获取操作员信息
|
|
120
|
-
const currUser = useUserStore().getLogin().f
|
|
121
|
-
|
|
122
|
-
// 组件状态
|
|
123
|
-
const chargeItems = ref<ChargeItem[]>([])
|
|
124
|
-
const paymentMethod = ref('现金缴费') // 默认支付方式,会被配置覆盖
|
|
125
|
-
const receiptType = ref('普通收据')
|
|
126
|
-
const remarks = ref('')
|
|
127
|
-
const fileList = ref<FileItem[]>([])
|
|
128
|
-
const showAddItemModal = ref(false)
|
|
129
|
-
const showQrCodePayment = ref(false)
|
|
130
|
-
// 子组件的响应组件引用
|
|
131
|
-
const printRef = ref()
|
|
132
|
-
// 收费类型数据
|
|
133
|
-
const chargeTypeConfig = reactive<ChargeTypeConfig>({
|
|
134
|
-
value: [],
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
// 配置数据
|
|
138
|
-
const config = reactive<Partial<OtherChargeConfig>>({
|
|
139
|
-
payment: '现金缴费',
|
|
140
|
-
allowedMethods: null,
|
|
141
|
-
printLogic: '',
|
|
142
|
-
fileTypes: [],
|
|
143
|
-
submitLogic: '',
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
const gridFileUploaderRef = ref()
|
|
147
|
-
|
|
148
|
-
// 判断是否使用宫格上传组件
|
|
149
|
-
const useGridUploader = computed(() => {
|
|
150
|
-
return config.fileTypes && config.fileTypes.length > 0
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
// 初始化获取配置
|
|
154
|
-
async function initConfig() {
|
|
155
|
-
try {
|
|
156
|
-
// 新的收费类型数据
|
|
157
|
-
await runLogic('getMaterialInfo', {
|
|
158
|
-
f_org_id: currUser.resources.orgid,
|
|
159
|
-
}, 'af-revenue').then((res: any[]) => {
|
|
160
|
-
// 1. 获取所有不同的 f_material_type,且不为空
|
|
161
|
-
const types = Array.from(new Set(res.filter(item => item.f_material_type).map(item => item.f_material_type)))
|
|
162
|
-
|
|
163
|
-
// 2. 组装树结构
|
|
164
|
-
const materialsConfig = types.map((typeName) => {
|
|
165
|
-
// 找到该类型下所有分类
|
|
166
|
-
const categories = res.filter(item => item.type === '分类' && item.f_material_type === typeName)
|
|
167
|
-
return {
|
|
168
|
-
value: typeName,
|
|
169
|
-
label: typeName,
|
|
170
|
-
children: categories.map((cat) => {
|
|
171
|
-
// 找到该分类下所有型号
|
|
172
|
-
const models = res.filter(item => item.type === '型号' && String(item.parent_id) === String(cat.id))
|
|
173
|
-
return {
|
|
174
|
-
value: cat.name,
|
|
175
|
-
label: cat.name,
|
|
176
|
-
children: models.map(model => ({
|
|
177
|
-
value: model.name,
|
|
178
|
-
label: model.name,
|
|
179
|
-
price: model.f_material_price,
|
|
180
|
-
})),
|
|
181
|
-
}
|
|
182
|
-
}).filter(cat => cat.children.length > 0), // 只保留有型号的分类
|
|
183
|
-
}
|
|
184
|
-
}).filter(type => type.children.length > 0) // 只保留有分类的类型
|
|
185
|
-
|
|
186
|
-
const chargeTypes = materialsConfig.map(item => ({
|
|
187
|
-
name: item.label,
|
|
188
|
-
children: item.children?.map(child => ({
|
|
189
|
-
name: child.label,
|
|
190
|
-
children: child.children?.map(model => ({
|
|
191
|
-
name: model.label,
|
|
192
|
-
price: model.price ? Number(model.price) : undefined,
|
|
193
|
-
})) || [],
|
|
194
|
-
})) || [],
|
|
195
|
-
}))
|
|
196
|
-
chargeTypeConfig.value = chargeTypes
|
|
197
|
-
console.log('新的收费类型数据', chargeTypes)
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
// 获取其他收费类型配置
|
|
201
|
-
const chargeConfig = await getConfigByNameAsync('mobile_otherChargeConfig')
|
|
202
|
-
if (chargeConfig) {
|
|
203
|
-
// 更新配置
|
|
204
|
-
Object.assign(config, chargeConfig)
|
|
205
|
-
|
|
206
|
-
// 设置默认支付方式
|
|
207
|
-
if (config.payment) {
|
|
208
|
-
paymentMethod.value = config.payment
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
catch (error) {
|
|
213
|
-
console.error('初始化配置信息失败', error)
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// 在组件挂载时调用初始化配置函数
|
|
218
|
-
onMounted(async () => {
|
|
219
|
-
await initConfig()
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
// 计算总金额
|
|
223
|
-
const totalAmount = computed(() => {
|
|
224
|
-
if (chargeItems.value.length === 0)
|
|
225
|
-
return '0.00'
|
|
226
|
-
const total = chargeItems.value.reduce((sum, item) => sum + Number.parseFloat(item.total), 0)
|
|
227
|
-
return total.toFixed(2)
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
// 收据数据
|
|
231
|
-
const receiptData = reactive<ReceiptData>({
|
|
232
|
-
transactionId: '',
|
|
233
|
-
userName: '',
|
|
234
|
-
userId: '',
|
|
235
|
-
cardNo: '',
|
|
236
|
-
items: [],
|
|
237
|
-
totalAmount: '0.00',
|
|
238
|
-
paymentMethod: 'cash',
|
|
239
|
-
remarks: '',
|
|
240
|
-
operator: '营业员',
|
|
241
|
-
dateTime: '',
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
// 计算属性获取用户信息
|
|
245
|
-
const userInfo = computed(() => {
|
|
246
|
-
if (!props.user)
|
|
247
|
-
return null
|
|
248
|
-
return {
|
|
249
|
-
name: props.user.f_user_name,
|
|
250
|
-
id: props.user.f_userinfo_code,
|
|
251
|
-
// meterNumber: props.user.f_meternumber,
|
|
252
|
-
}
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
// 添加费用项
|
|
256
|
-
function addChargeItem(item: ChargeItem) {
|
|
257
|
-
chargeItems.value.push(item)
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// 移除费用项
|
|
261
|
-
function removeChargeItem(itemId: number) {
|
|
262
|
-
chargeItems.value = chargeItems.value.filter(item => item.id !== itemId)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// 生成收据
|
|
266
|
-
function generateReceipt() {
|
|
267
|
-
// 验证是否有费用项
|
|
268
|
-
if (chargeItems.value.length === 0) {
|
|
269
|
-
showToast('请至少添加一个费用项!')
|
|
270
|
-
return
|
|
271
|
-
}
|
|
272
|
-
if (!paymentMethod.value) {
|
|
273
|
-
showToast('请选择付款方式!')
|
|
274
|
-
return
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// 验证文件上传要求
|
|
278
|
-
if (useGridUploader.value && gridFileUploaderRef.value) {
|
|
279
|
-
const isValid = gridFileUploaderRef.value.validateAll()
|
|
280
|
-
if (!isValid) {
|
|
281
|
-
return
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
// 微信/支付宝支付走单独的流程,通过事件回调处理支付结果
|
|
285
|
-
if (paymentMethod.value.includes('支付宝') || paymentMethod.value.includes('微信')) {
|
|
286
|
-
showQrCodePayment.value = true
|
|
287
|
-
}
|
|
288
|
-
// 其他支付方式直接调用收费接口进行处理
|
|
289
|
-
else {
|
|
290
|
-
processPayment()
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// 抽取支付处理逻辑为单独函数
|
|
295
|
-
async function processPayment(tradeNo = ''): Promise<any> {
|
|
296
|
-
try {
|
|
297
|
-
// 更新收据数据
|
|
298
|
-
receiptData.items = chargeItems.value
|
|
299
|
-
receiptData.totalAmount = totalAmount.value
|
|
300
|
-
receiptData.paymentMethod = paymentMethod.value
|
|
301
|
-
receiptData.remarks = remarks.value
|
|
302
|
-
|
|
303
|
-
// 更新用户信息
|
|
304
|
-
if (userInfo.value) {
|
|
305
|
-
receiptData.userName = userInfo.value.name
|
|
306
|
-
receiptData.userId = userInfo.value.id
|
|
307
|
-
// receiptData.cardNo = userInfo.value.meterNumber
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// 生成交易单号
|
|
311
|
-
receiptData.transactionId = `OC${Date.now().toString().slice(-10)}`
|
|
312
|
-
|
|
313
|
-
// 获取操作员信息
|
|
314
|
-
const currUser = useUserStore().getLogin().f
|
|
315
|
-
receiptData.operator = currUser.resources.name
|
|
316
|
-
|
|
317
|
-
// 设置日期时间
|
|
318
|
-
const now = new Date()
|
|
319
|
-
receiptData.dateTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
|
|
320
|
-
|
|
321
|
-
// 调用API提交数据
|
|
322
|
-
const newChargeItems = chargeItems.value.map((item) => {
|
|
323
|
-
return {
|
|
324
|
-
f_work_order_id: item.workOrderId || '',
|
|
325
|
-
f_service_id: item.workOrderCode || '',
|
|
326
|
-
f_brand_spec: item.type,
|
|
327
|
-
f_typename: item.item,
|
|
328
|
-
f_typenumber: item.model,
|
|
329
|
-
f_number: item.quantity,
|
|
330
|
-
f_unitprice: item.unitPrice,
|
|
331
|
-
f_collection: item.total,
|
|
332
|
-
}
|
|
333
|
-
})
|
|
334
|
-
const param = {
|
|
335
|
-
f_workflow_id: props.user.f_workflow_id,
|
|
336
|
-
f_userinfo_id: props.user.f_userinfo_id,
|
|
337
|
-
// f_serial_id: this.model.f_serial_id,
|
|
338
|
-
// nopay_id: this.model.nopay_id,
|
|
339
|
-
f_collection: totalAmount.value,
|
|
340
|
-
f_comments: remarks.value,
|
|
341
|
-
f_sale_person: currUser.resources.name,
|
|
342
|
-
f_payment: paymentMethod.value,
|
|
343
|
-
// f_voucher_number: this.model.f_voucher_number,
|
|
344
|
-
f_bill_style: receiptType.value,
|
|
345
|
-
// f_service_person: this.model.f_service_person,
|
|
346
|
-
fileIds: fileList.value.map(file => file.result?.id).filter(Boolean),
|
|
347
|
-
f_operator: currUser.resources.name,
|
|
348
|
-
f_operatorid: currUser.resources.id,
|
|
349
|
-
f_orgid: currUser.resources.orgid,
|
|
350
|
-
f_orgname: currUser.resources.orgs,
|
|
351
|
-
f_depid: currUser.resources.depids,
|
|
352
|
-
f_depname: currUser.resources.deps,
|
|
353
|
-
otherDetail: newChargeItems,
|
|
354
|
-
}
|
|
355
|
-
console.log('param---->', param)
|
|
356
|
-
runLogic(config.submitLogic, param).then(
|
|
357
|
-
async (res) => {
|
|
358
|
-
await printRef.value.printParams(config.printLogic, (res as any).id)
|
|
359
|
-
},
|
|
360
|
-
).catch(() => {
|
|
361
|
-
showFailToast('其他收费失败')
|
|
362
|
-
})
|
|
363
|
-
}
|
|
364
|
-
catch (error) {
|
|
365
|
-
console.error(error)
|
|
366
|
-
showToast(`其他收费失败${error.message || error.msg || '未知错误'}`)
|
|
367
|
-
throw error
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async function handleQrCodePaymentSuccess(paymentResult) {
|
|
372
|
-
try {
|
|
373
|
-
// 使用公共处理函数处理支付
|
|
374
|
-
await processPayment(paymentResult.tradeNo)
|
|
375
|
-
showQrCodePayment.value = false
|
|
376
|
-
}
|
|
377
|
-
catch (error) {
|
|
378
|
-
console.error(error)
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
function handleQrCodePaymentCancel() {
|
|
383
|
-
showQrCodePayment.value = false
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// 取消操作
|
|
387
|
-
function cancelOperation() {
|
|
388
|
-
showDialog({
|
|
389
|
-
title: '确认取消',
|
|
390
|
-
message: '是否确认取消当前收费操作?',
|
|
391
|
-
showCancelButton: true,
|
|
392
|
-
confirmButtonText: '确认',
|
|
393
|
-
cancelButtonText: '返回',
|
|
394
|
-
confirmButtonColor: '#2563eb',
|
|
395
|
-
}).then((result) => {
|
|
396
|
-
if (result === 'confirm') {
|
|
397
|
-
emit('closeOperation')
|
|
398
|
-
}
|
|
399
|
-
})
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// 文件上传相关
|
|
403
|
-
function onFileAdded(): void {
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function onFileRemoved(): void {
|
|
407
|
-
}
|
|
408
|
-
</script>
|
|
409
|
-
|
|
410
|
-
<template>
|
|
411
|
-
<div id="other-charge-form" class="other-charge-form">
|
|
412
|
-
<!-- 信息提示 -->
|
|
413
|
-
<div class="other-charge-form__info-box">
|
|
414
|
-
<VanIcon name="info-o" class="other-charge-form__info-icon" />
|
|
415
|
-
<div class="other-charge-form__info-content">
|
|
416
|
-
<p class="other-charge-form__info-text">
|
|
417
|
-
请填写收费信息,系统将自动计算应收金额。
|
|
418
|
-
</p>
|
|
419
|
-
<p class="other-charge-form__info-user">
|
|
420
|
-
当前用户:<span class="other-charge-form__user-name">{{ userInfo ? userInfo.name : '' }}</span>
|
|
421
|
-
</p>
|
|
422
|
-
</div>
|
|
423
|
-
</div>
|
|
424
|
-
|
|
425
|
-
<form class="other-charge-form__container" @submit.prevent="generateReceipt">
|
|
426
|
-
<!-- 费用项列表区域 -->
|
|
427
|
-
<CardContainer class="other-charge-form__section-margin">
|
|
428
|
-
<CardHeader title="费用项目">
|
|
429
|
-
<template #extra>
|
|
430
|
-
<VanButton
|
|
431
|
-
type="primary"
|
|
432
|
-
size="small"
|
|
433
|
-
icon="plus"
|
|
434
|
-
plain
|
|
435
|
-
@click="showAddItemModal = true"
|
|
436
|
-
>
|
|
437
|
-
添加费用项
|
|
438
|
-
</VanButton>
|
|
439
|
-
</template>
|
|
440
|
-
</CardHeader>
|
|
441
|
-
|
|
442
|
-
<div class="other-charge-form__items-container">
|
|
443
|
-
<template v-if="chargeItems.length > 0">
|
|
444
|
-
<OtherChargeItem
|
|
445
|
-
v-for="(item, index) in chargeItems"
|
|
446
|
-
:key="item.id"
|
|
447
|
-
:item="item"
|
|
448
|
-
:index="index"
|
|
449
|
-
:is-work-order="props.isWorkOrder"
|
|
450
|
-
:work-order-data="props.workOrderData"
|
|
451
|
-
@remove="removeChargeItem"
|
|
452
|
-
/>
|
|
453
|
-
</template>
|
|
454
|
-
<p v-else class="text-gray-400 text-center">
|
|
455
|
-
尚未添加收费项目
|
|
456
|
-
</p>
|
|
457
|
-
</div>
|
|
458
|
-
</CardContainer>
|
|
459
|
-
|
|
460
|
-
<!-- 收费信息 -->
|
|
461
|
-
<CardContainer class="other-charge-form__section-margin">
|
|
462
|
-
<CardHeader title="收费信息" />
|
|
463
|
-
<div class="other-charge-form__field">
|
|
464
|
-
<label class="other-charge-form__label">应收金额</label>
|
|
465
|
-
<div class="other-charge-form__amount-input">
|
|
466
|
-
<VanField
|
|
467
|
-
v-model="totalAmount"
|
|
468
|
-
readonly
|
|
469
|
-
input-align="right"
|
|
470
|
-
>
|
|
471
|
-
<template #prefix>
|
|
472
|
-
<span class="other-charge-form__prefix">¥</span>
|
|
473
|
-
</template>
|
|
474
|
-
<template #suffix>
|
|
475
|
-
<span class="other-charge-form__suffix">元</span>
|
|
476
|
-
</template>
|
|
477
|
-
</VanField>
|
|
478
|
-
</div>
|
|
479
|
-
</div>
|
|
480
|
-
|
|
481
|
-
<div class="other-charge-form__field">
|
|
482
|
-
<label class="other-charge-form__label">收费项目数</label>
|
|
483
|
-
<div class="other-charge-form__count-input">
|
|
484
|
-
<VanField
|
|
485
|
-
:model-value="chargeItems.length.toString()"
|
|
486
|
-
readonly
|
|
487
|
-
input-align="right"
|
|
488
|
-
>
|
|
489
|
-
<template #suffix>
|
|
490
|
-
<span class="other-charge-form__suffix">项</span>
|
|
491
|
-
</template>
|
|
492
|
-
</VanField>
|
|
493
|
-
</div>
|
|
494
|
-
</div>
|
|
495
|
-
</CardContainer>
|
|
496
|
-
|
|
497
|
-
<!-- 打印及备注 -->
|
|
498
|
-
<ChargePrintSelectorAndRemarks
|
|
499
|
-
v-model="receiptType"
|
|
500
|
-
v-model:remarks="remarks"
|
|
501
|
-
class="other-charge-form__section-margin"
|
|
502
|
-
/>
|
|
503
|
-
|
|
504
|
-
<!-- 支付方式 -->
|
|
505
|
-
<PaymentMethodSelectorCard
|
|
506
|
-
v-model="paymentMethod"
|
|
507
|
-
title="支付方式"
|
|
508
|
-
:user="props.user"
|
|
509
|
-
:allowed-methods="config.allowedMethods"
|
|
510
|
-
class="other-charge-form__section-margin"
|
|
511
|
-
/>
|
|
512
|
-
|
|
513
|
-
<!-- 文件上传区域 -->
|
|
514
|
-
<!-- 宫格上传组件(多种文件类型时使用) -->
|
|
515
|
-
<GridFileUploader
|
|
516
|
-
v-if="useGridUploader"
|
|
517
|
-
ref="gridFileUploaderRef"
|
|
518
|
-
v-model:file-list="fileList"
|
|
519
|
-
:file-types="config.fileTypes"
|
|
520
|
-
title="上传附件"
|
|
521
|
-
class="other-charge-form__section-margin"
|
|
522
|
-
/>
|
|
523
|
-
<CardContainer
|
|
524
|
-
v-else
|
|
525
|
-
class="other-charge-form__section-margin"
|
|
526
|
-
>
|
|
527
|
-
<!-- 普通上传组件(单一文件类型或无配置时使用) -->
|
|
528
|
-
<FileUploader
|
|
529
|
-
v-model:file-list="fileList"
|
|
530
|
-
title="上传附件"
|
|
531
|
-
:multiple="true"
|
|
532
|
-
:user-type="config.fileTypes && config.fileTypes.length > 0 ? config.fileTypes[0].userType : '其他收费'"
|
|
533
|
-
@file-added="onFileAdded"
|
|
534
|
-
@file-removed="onFileRemoved"
|
|
535
|
-
/>
|
|
536
|
-
</CardContainer>
|
|
537
|
-
|
|
538
|
-
<!-- 应收金额展示 -->
|
|
539
|
-
<CardContainer class="other-charge-form__summary">
|
|
540
|
-
<div class="other-charge-form__total-content">
|
|
541
|
-
<p class="other-charge-form__total-label">
|
|
542
|
-
应收金额:<span class="other-charge-form__total-value">¥ {{ totalAmount }}</span>
|
|
543
|
-
</p>
|
|
544
|
-
<p class="other-charge-form__total-count">
|
|
545
|
-
收费项目:{{ chargeItems.length }} 项
|
|
546
|
-
</p>
|
|
547
|
-
</div>
|
|
548
|
-
</CardContainer>
|
|
549
|
-
|
|
550
|
-
<!-- 按钮区域 -->
|
|
551
|
-
<div class="other-charge-form__buttons">
|
|
552
|
-
<VanButton
|
|
553
|
-
plain
|
|
554
|
-
type="default"
|
|
555
|
-
class="other-charge-form__cancel-btn"
|
|
556
|
-
@click="cancelOperation"
|
|
557
|
-
>
|
|
558
|
-
取消
|
|
559
|
-
</VanButton>
|
|
560
|
-
<VanButton
|
|
561
|
-
type="primary"
|
|
562
|
-
native-type="submit"
|
|
563
|
-
class="other-charge-form__confirm-btn"
|
|
564
|
-
:disabled="chargeItems.length === 0"
|
|
565
|
-
>
|
|
566
|
-
确认收费
|
|
567
|
-
</VanButton>
|
|
568
|
-
</div>
|
|
569
|
-
</form>
|
|
570
|
-
|
|
571
|
-
<!-- 添加费用项弹窗 -->
|
|
572
|
-
<OtherChargeItemModal
|
|
573
|
-
v-model:show="showAddItemModal"
|
|
574
|
-
:is-work-order="props.isWorkOrder"
|
|
575
|
-
:work-order-data="props.workOrderData"
|
|
576
|
-
:charge-types="chargeTypeConfig"
|
|
577
|
-
@add="addChargeItem"
|
|
578
|
-
/>
|
|
579
|
-
|
|
580
|
-
<!-- 收据弹窗 -->
|
|
581
|
-
<ReceiptModal ref="printRef" @close="emit('closeOperation')" />
|
|
582
|
-
<!-- 二维码支付弹窗 -->
|
|
583
|
-
<CodePayment
|
|
584
|
-
v-if="showQrCodePayment"
|
|
585
|
-
v-model:show="showQrCodePayment"
|
|
586
|
-
:payment="paymentMethod"
|
|
587
|
-
:collection="totalAmount"
|
|
588
|
-
:form-data="{}"
|
|
589
|
-
type="其他收费"
|
|
590
|
-
:user="props.user"
|
|
591
|
-
@payment-success="handleQrCodePaymentSuccess"
|
|
592
|
-
@payment-cancel="handleQrCodePaymentCancel"
|
|
593
|
-
/>
|
|
594
|
-
</div>
|
|
595
|
-
</template>
|
|
596
|
-
|
|
597
|
-
<style scoped lang="less">
|
|
598
|
-
.other-charge-form {
|
|
599
|
-
padding: 12px;
|
|
600
|
-
|
|
601
|
-
&__info-box {
|
|
602
|
-
display: flex;
|
|
603
|
-
background: linear-gradient(to right, #ebf4ff, #e0f2fe);
|
|
604
|
-
border-left: 4px solid #3b82f6;
|
|
605
|
-
border-radius: 6px;
|
|
606
|
-
padding: 12px;
|
|
607
|
-
margin-bottom: 16px;
|
|
608
|
-
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
&__info-icon {
|
|
612
|
-
font-size: 20px;
|
|
613
|
-
color: #3b82f6;
|
|
614
|
-
margin-top: 3px;
|
|
615
|
-
margin-right: 12px;
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
&__info-content {
|
|
619
|
-
flex: 1;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
&__info-text {
|
|
623
|
-
margin: 0 0 4px 0;
|
|
624
|
-
font-size: 14px;
|
|
625
|
-
color: #1e40af;
|
|
626
|
-
font-weight: 500;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
&__info-user {
|
|
630
|
-
margin: 0;
|
|
631
|
-
font-size: 14px;
|
|
632
|
-
color: #2563eb;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
&__user-name {
|
|
636
|
-
font-weight: 600;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
&__container {
|
|
640
|
-
margin-bottom: 16px;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
&__main-content {
|
|
644
|
-
background-color: #f9fafb;
|
|
645
|
-
border-radius: 8px;
|
|
646
|
-
border: 1px solid #e5e7eb;
|
|
647
|
-
padding: 16px;
|
|
648
|
-
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
649
|
-
margin-bottom: 16px;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
&__two-columns {
|
|
653
|
-
display: grid;
|
|
654
|
-
grid-template-columns: 1fr;
|
|
655
|
-
gap: 16px;
|
|
656
|
-
margin-bottom: 16px;
|
|
657
|
-
|
|
658
|
-
@media (min-width: 640px) {
|
|
659
|
-
grid-template-columns: 1fr 1fr;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
&__field {
|
|
664
|
-
margin-bottom: 16px;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
&__label {
|
|
668
|
-
display: block;
|
|
669
|
-
font-size: 12px;
|
|
670
|
-
font-weight: 500;
|
|
671
|
-
color: #6b7280;
|
|
672
|
-
margin-bottom: 4px;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
&__amount-input,
|
|
676
|
-
&__count-input {
|
|
677
|
-
:deep(.van-field) {
|
|
678
|
-
background-color: #f9fafb;
|
|
679
|
-
border-radius: 6px;
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
&__prefix,
|
|
684
|
-
&__suffix {
|
|
685
|
-
color: #6b7280;
|
|
686
|
-
font-size: 14px;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
&__total-content {
|
|
690
|
-
width: 100%;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
&__total-label {
|
|
694
|
-
font-size: 16px;
|
|
695
|
-
font-weight: 500;
|
|
696
|
-
color: #4b5563;
|
|
697
|
-
margin: 0 0 4px 0;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
&__total-value {
|
|
701
|
-
font-size: 20px;
|
|
702
|
-
font-weight: 600;
|
|
703
|
-
color: #2563eb;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
&__total-count {
|
|
707
|
-
font-size: 14px;
|
|
708
|
-
color: #6b7280;
|
|
709
|
-
margin: 0;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
&__buttons {
|
|
713
|
-
display: flex;
|
|
714
|
-
justify-content: flex-end;
|
|
715
|
-
gap: 12px;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
&__cancel-btn {
|
|
719
|
-
border-color: #d1d5db;
|
|
720
|
-
color: #4b5563;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
&__confirm-btn {
|
|
724
|
-
background-color: #2563eb;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
&__file-uploader {
|
|
728
|
-
margin-bottom: 16px;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
&__section-margin {
|
|
732
|
-
margin-bottom: 16px;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
&__summary {
|
|
736
|
-
background: linear-gradient(to right, #ebf4ff, #e0f2fe);
|
|
737
|
-
border: 1px solid #bfdbfe;
|
|
738
|
-
border-radius: 8px;
|
|
739
|
-
padding: 12px;
|
|
740
|
-
margin-bottom: 16px;
|
|
741
|
-
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
</style>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { FileItem } from '../../common/otherCharge/FileUploader.vue'
|
|
3
|
+
import CardContainer from '@af-mobile-client-vue3/components/data/CardContainer/CardContainer.vue'
|
|
4
|
+
import CardHeader from '@af-mobile-client-vue3/components/data/CardContainer/CardHeader.vue'
|
|
5
|
+
import { getConfigByNameAsync, runLogic } from '@af-mobile-client-vue3/services/api/common'
|
|
6
|
+
import useUserStore from '@af-mobile-client-vue3/stores/modules/user'
|
|
7
|
+
import {
|
|
8
|
+
showDialog,
|
|
9
|
+
showFailToast,
|
|
10
|
+
showToast,
|
|
11
|
+
Button as VanButton,
|
|
12
|
+
Field as VanField,
|
|
13
|
+
Icon as VanIcon,
|
|
14
|
+
} from 'vant'
|
|
15
|
+
import { computed, onMounted, reactive, ref } from 'vue'
|
|
16
|
+
// import { otherCharge } from '@/services/api/business'
|
|
17
|
+
import ChargePrintSelectorAndRemarks from '../../common/otherCharge/ChargePrintSelectorAndRemarks.vue'
|
|
18
|
+
import {
|
|
19
|
+
CodePayment,
|
|
20
|
+
FileUploader,
|
|
21
|
+
GridFileUploader,
|
|
22
|
+
PaymentMethodSelectorCard,
|
|
23
|
+
ReceiptModal,
|
|
24
|
+
} from '../../common/otherCharge/index'
|
|
25
|
+
import OtherChargeItem from './OtherChargeItem.vue'
|
|
26
|
+
import OtherChargeItemModal from './OtherChargeItemModal.vue'
|
|
27
|
+
|
|
28
|
+
interface BusinessUser {
|
|
29
|
+
f_workflow_id?: string
|
|
30
|
+
f_userinfo_id: string
|
|
31
|
+
f_userinfo_code: string
|
|
32
|
+
f_user_name: string
|
|
33
|
+
f_userfiles_id?: string
|
|
34
|
+
f_orgid?: string
|
|
35
|
+
// f_user_phone: string
|
|
36
|
+
// f_user_type: string
|
|
37
|
+
// f_address: string
|
|
38
|
+
// f_meter_type: string
|
|
39
|
+
// f_meternumber: string
|
|
40
|
+
// f_balance: string
|
|
41
|
+
// f_user_state: string
|
|
42
|
+
// f_last_gas_date?: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ChargeItem {
|
|
46
|
+
id: number
|
|
47
|
+
workOrderCode?: string
|
|
48
|
+
workOrderId?: string
|
|
49
|
+
type: string
|
|
50
|
+
item: string
|
|
51
|
+
model?: string // 添加型号字段
|
|
52
|
+
unitPrice: number
|
|
53
|
+
quantity: number
|
|
54
|
+
total: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface ReceiptData {
|
|
58
|
+
transactionId: string
|
|
59
|
+
userName: string
|
|
60
|
+
userId: string
|
|
61
|
+
cardNo: string
|
|
62
|
+
items: ChargeItem[]
|
|
63
|
+
totalAmount: string
|
|
64
|
+
paymentMethod: string
|
|
65
|
+
remarks: string
|
|
66
|
+
operator: string
|
|
67
|
+
dateTime: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 收费类型配置接口
|
|
71
|
+
interface ModelItem {
|
|
72
|
+
name: string
|
|
73
|
+
price?: number
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface ItemType {
|
|
77
|
+
name: string
|
|
78
|
+
children?: ModelItem[]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface ChargeTypeCategory {
|
|
82
|
+
name: string
|
|
83
|
+
children: ItemType[]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface ChargeTypeConfig {
|
|
87
|
+
value: ChargeTypeCategory[]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 添加其他收费配置接口
|
|
91
|
+
interface OtherChargeConfig {
|
|
92
|
+
hasPrint: boolean
|
|
93
|
+
printType: string
|
|
94
|
+
payment: string
|
|
95
|
+
allowedMethods: string[] | null
|
|
96
|
+
floor: boolean
|
|
97
|
+
calculatePreByCollection: boolean
|
|
98
|
+
printLogic: string
|
|
99
|
+
submitLogic: string
|
|
100
|
+
fileTypes?: {
|
|
101
|
+
userType: string
|
|
102
|
+
picMinNum: number
|
|
103
|
+
description?: string
|
|
104
|
+
}[]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 接收用户信息prop
|
|
108
|
+
const props = defineProps<{
|
|
109
|
+
user?: BusinessUser
|
|
110
|
+
isWorkOrder?: boolean
|
|
111
|
+
workOrderData?: any
|
|
112
|
+
}>()
|
|
113
|
+
|
|
114
|
+
const emit = defineEmits<{
|
|
115
|
+
(e: 'closeOperation'): void
|
|
116
|
+
(e: 'complete'): void
|
|
117
|
+
}>()
|
|
118
|
+
|
|
119
|
+
// 获取操作员信息
|
|
120
|
+
const currUser = useUserStore().getLogin().f
|
|
121
|
+
|
|
122
|
+
// 组件状态
|
|
123
|
+
const chargeItems = ref<ChargeItem[]>([])
|
|
124
|
+
const paymentMethod = ref('现金缴费') // 默认支付方式,会被配置覆盖
|
|
125
|
+
const receiptType = ref('普通收据')
|
|
126
|
+
const remarks = ref('')
|
|
127
|
+
const fileList = ref<FileItem[]>([])
|
|
128
|
+
const showAddItemModal = ref(false)
|
|
129
|
+
const showQrCodePayment = ref(false)
|
|
130
|
+
// 子组件的响应组件引用
|
|
131
|
+
const printRef = ref()
|
|
132
|
+
// 收费类型数据
|
|
133
|
+
const chargeTypeConfig = reactive<ChargeTypeConfig>({
|
|
134
|
+
value: [],
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// 配置数据
|
|
138
|
+
const config = reactive<Partial<OtherChargeConfig>>({
|
|
139
|
+
payment: '现金缴费',
|
|
140
|
+
allowedMethods: null,
|
|
141
|
+
printLogic: '',
|
|
142
|
+
fileTypes: [],
|
|
143
|
+
submitLogic: '',
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const gridFileUploaderRef = ref()
|
|
147
|
+
|
|
148
|
+
// 判断是否使用宫格上传组件
|
|
149
|
+
const useGridUploader = computed(() => {
|
|
150
|
+
return config.fileTypes && config.fileTypes.length > 0
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// 初始化获取配置
|
|
154
|
+
async function initConfig() {
|
|
155
|
+
try {
|
|
156
|
+
// 新的收费类型数据
|
|
157
|
+
await runLogic('getMaterialInfo', {
|
|
158
|
+
f_org_id: currUser.resources.orgid,
|
|
159
|
+
}, 'af-revenue').then((res: any[]) => {
|
|
160
|
+
// 1. 获取所有不同的 f_material_type,且不为空
|
|
161
|
+
const types = Array.from(new Set(res.filter(item => item.f_material_type).map(item => item.f_material_type)))
|
|
162
|
+
|
|
163
|
+
// 2. 组装树结构
|
|
164
|
+
const materialsConfig = types.map((typeName) => {
|
|
165
|
+
// 找到该类型下所有分类
|
|
166
|
+
const categories = res.filter(item => item.type === '分类' && item.f_material_type === typeName)
|
|
167
|
+
return {
|
|
168
|
+
value: typeName,
|
|
169
|
+
label: typeName,
|
|
170
|
+
children: categories.map((cat) => {
|
|
171
|
+
// 找到该分类下所有型号
|
|
172
|
+
const models = res.filter(item => item.type === '型号' && String(item.parent_id) === String(cat.id))
|
|
173
|
+
return {
|
|
174
|
+
value: cat.name,
|
|
175
|
+
label: cat.name,
|
|
176
|
+
children: models.map(model => ({
|
|
177
|
+
value: model.name,
|
|
178
|
+
label: model.name,
|
|
179
|
+
price: model.f_material_price,
|
|
180
|
+
})),
|
|
181
|
+
}
|
|
182
|
+
}).filter(cat => cat.children.length > 0), // 只保留有型号的分类
|
|
183
|
+
}
|
|
184
|
+
}).filter(type => type.children.length > 0) // 只保留有分类的类型
|
|
185
|
+
|
|
186
|
+
const chargeTypes = materialsConfig.map(item => ({
|
|
187
|
+
name: item.label,
|
|
188
|
+
children: item.children?.map(child => ({
|
|
189
|
+
name: child.label,
|
|
190
|
+
children: child.children?.map(model => ({
|
|
191
|
+
name: model.label,
|
|
192
|
+
price: model.price ? Number(model.price) : undefined,
|
|
193
|
+
})) || [],
|
|
194
|
+
})) || [],
|
|
195
|
+
}))
|
|
196
|
+
chargeTypeConfig.value = chargeTypes
|
|
197
|
+
console.log('新的收费类型数据', chargeTypes)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
// 获取其他收费类型配置
|
|
201
|
+
const chargeConfig = await getConfigByNameAsync('mobile_otherChargeConfig')
|
|
202
|
+
if (chargeConfig) {
|
|
203
|
+
// 更新配置
|
|
204
|
+
Object.assign(config, chargeConfig)
|
|
205
|
+
|
|
206
|
+
// 设置默认支付方式
|
|
207
|
+
if (config.payment) {
|
|
208
|
+
paymentMethod.value = config.payment
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
console.error('初始化配置信息失败', error)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 在组件挂载时调用初始化配置函数
|
|
218
|
+
onMounted(async () => {
|
|
219
|
+
await initConfig()
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
// 计算总金额
|
|
223
|
+
const totalAmount = computed(() => {
|
|
224
|
+
if (chargeItems.value.length === 0)
|
|
225
|
+
return '0.00'
|
|
226
|
+
const total = chargeItems.value.reduce((sum, item) => sum + Number.parseFloat(item.total), 0)
|
|
227
|
+
return total.toFixed(2)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
// 收据数据
|
|
231
|
+
const receiptData = reactive<ReceiptData>({
|
|
232
|
+
transactionId: '',
|
|
233
|
+
userName: '',
|
|
234
|
+
userId: '',
|
|
235
|
+
cardNo: '',
|
|
236
|
+
items: [],
|
|
237
|
+
totalAmount: '0.00',
|
|
238
|
+
paymentMethod: 'cash',
|
|
239
|
+
remarks: '',
|
|
240
|
+
operator: '营业员',
|
|
241
|
+
dateTime: '',
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
// 计算属性获取用户信息
|
|
245
|
+
const userInfo = computed(() => {
|
|
246
|
+
if (!props.user)
|
|
247
|
+
return null
|
|
248
|
+
return {
|
|
249
|
+
name: props.user.f_user_name,
|
|
250
|
+
id: props.user.f_userinfo_code,
|
|
251
|
+
// meterNumber: props.user.f_meternumber,
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
// 添加费用项
|
|
256
|
+
function addChargeItem(item: ChargeItem) {
|
|
257
|
+
chargeItems.value.push(item)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 移除费用项
|
|
261
|
+
function removeChargeItem(itemId: number) {
|
|
262
|
+
chargeItems.value = chargeItems.value.filter(item => item.id !== itemId)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 生成收据
|
|
266
|
+
function generateReceipt() {
|
|
267
|
+
// 验证是否有费用项
|
|
268
|
+
if (chargeItems.value.length === 0) {
|
|
269
|
+
showToast('请至少添加一个费用项!')
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
if (!paymentMethod.value) {
|
|
273
|
+
showToast('请选择付款方式!')
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 验证文件上传要求
|
|
278
|
+
if (useGridUploader.value && gridFileUploaderRef.value) {
|
|
279
|
+
const isValid = gridFileUploaderRef.value.validateAll()
|
|
280
|
+
if (!isValid) {
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// 微信/支付宝支付走单独的流程,通过事件回调处理支付结果
|
|
285
|
+
if (paymentMethod.value.includes('支付宝') || paymentMethod.value.includes('微信')) {
|
|
286
|
+
showQrCodePayment.value = true
|
|
287
|
+
}
|
|
288
|
+
// 其他支付方式直接调用收费接口进行处理
|
|
289
|
+
else {
|
|
290
|
+
processPayment()
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 抽取支付处理逻辑为单独函数
|
|
295
|
+
async function processPayment(tradeNo = ''): Promise<any> {
|
|
296
|
+
try {
|
|
297
|
+
// 更新收据数据
|
|
298
|
+
receiptData.items = chargeItems.value
|
|
299
|
+
receiptData.totalAmount = totalAmount.value
|
|
300
|
+
receiptData.paymentMethod = paymentMethod.value
|
|
301
|
+
receiptData.remarks = remarks.value
|
|
302
|
+
|
|
303
|
+
// 更新用户信息
|
|
304
|
+
if (userInfo.value) {
|
|
305
|
+
receiptData.userName = userInfo.value.name
|
|
306
|
+
receiptData.userId = userInfo.value.id
|
|
307
|
+
// receiptData.cardNo = userInfo.value.meterNumber
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// 生成交易单号
|
|
311
|
+
receiptData.transactionId = `OC${Date.now().toString().slice(-10)}`
|
|
312
|
+
|
|
313
|
+
// 获取操作员信息
|
|
314
|
+
const currUser = useUserStore().getLogin().f
|
|
315
|
+
receiptData.operator = currUser.resources.name
|
|
316
|
+
|
|
317
|
+
// 设置日期时间
|
|
318
|
+
const now = new Date()
|
|
319
|
+
receiptData.dateTime = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`
|
|
320
|
+
|
|
321
|
+
// 调用API提交数据
|
|
322
|
+
const newChargeItems = chargeItems.value.map((item) => {
|
|
323
|
+
return {
|
|
324
|
+
f_work_order_id: item.workOrderId || '',
|
|
325
|
+
f_service_id: item.workOrderCode || '',
|
|
326
|
+
f_brand_spec: item.type,
|
|
327
|
+
f_typename: item.item,
|
|
328
|
+
f_typenumber: item.model,
|
|
329
|
+
f_number: item.quantity,
|
|
330
|
+
f_unitprice: item.unitPrice,
|
|
331
|
+
f_collection: item.total,
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
const param = {
|
|
335
|
+
f_workflow_id: props.user.f_workflow_id,
|
|
336
|
+
f_userinfo_id: props.user.f_userinfo_id,
|
|
337
|
+
// f_serial_id: this.model.f_serial_id,
|
|
338
|
+
// nopay_id: this.model.nopay_id,
|
|
339
|
+
f_collection: totalAmount.value,
|
|
340
|
+
f_comments: remarks.value,
|
|
341
|
+
f_sale_person: currUser.resources.name,
|
|
342
|
+
f_payment: paymentMethod.value,
|
|
343
|
+
// f_voucher_number: this.model.f_voucher_number,
|
|
344
|
+
f_bill_style: receiptType.value,
|
|
345
|
+
// f_service_person: this.model.f_service_person,
|
|
346
|
+
fileIds: fileList.value.map(file => file.result?.id).filter(Boolean),
|
|
347
|
+
f_operator: currUser.resources.name,
|
|
348
|
+
f_operatorid: currUser.resources.id,
|
|
349
|
+
f_orgid: currUser.resources.orgid,
|
|
350
|
+
f_orgname: currUser.resources.orgs,
|
|
351
|
+
f_depid: currUser.resources.depids,
|
|
352
|
+
f_depname: currUser.resources.deps,
|
|
353
|
+
otherDetail: newChargeItems,
|
|
354
|
+
}
|
|
355
|
+
console.log('param---->', param)
|
|
356
|
+
runLogic(config.submitLogic, param).then(
|
|
357
|
+
async (res) => {
|
|
358
|
+
await printRef.value.printParams(config.printLogic, (res as any).id)
|
|
359
|
+
},
|
|
360
|
+
).catch(() => {
|
|
361
|
+
showFailToast('其他收费失败')
|
|
362
|
+
})
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
console.error(error)
|
|
366
|
+
showToast(`其他收费失败${error.message || error.msg || '未知错误'}`)
|
|
367
|
+
throw error
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function handleQrCodePaymentSuccess(paymentResult) {
|
|
372
|
+
try {
|
|
373
|
+
// 使用公共处理函数处理支付
|
|
374
|
+
await processPayment(paymentResult.tradeNo)
|
|
375
|
+
showQrCodePayment.value = false
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
console.error(error)
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function handleQrCodePaymentCancel() {
|
|
383
|
+
showQrCodePayment.value = false
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// 取消操作
|
|
387
|
+
function cancelOperation() {
|
|
388
|
+
showDialog({
|
|
389
|
+
title: '确认取消',
|
|
390
|
+
message: '是否确认取消当前收费操作?',
|
|
391
|
+
showCancelButton: true,
|
|
392
|
+
confirmButtonText: '确认',
|
|
393
|
+
cancelButtonText: '返回',
|
|
394
|
+
confirmButtonColor: '#2563eb',
|
|
395
|
+
}).then((result) => {
|
|
396
|
+
if (result === 'confirm') {
|
|
397
|
+
emit('closeOperation')
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// 文件上传相关
|
|
403
|
+
function onFileAdded(): void {
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function onFileRemoved(): void {
|
|
407
|
+
}
|
|
408
|
+
</script>
|
|
409
|
+
|
|
410
|
+
<template>
|
|
411
|
+
<div id="other-charge-form" class="other-charge-form">
|
|
412
|
+
<!-- 信息提示 -->
|
|
413
|
+
<div class="other-charge-form__info-box">
|
|
414
|
+
<VanIcon name="info-o" class="other-charge-form__info-icon" />
|
|
415
|
+
<div class="other-charge-form__info-content">
|
|
416
|
+
<p class="other-charge-form__info-text">
|
|
417
|
+
请填写收费信息,系统将自动计算应收金额。
|
|
418
|
+
</p>
|
|
419
|
+
<p class="other-charge-form__info-user">
|
|
420
|
+
当前用户:<span class="other-charge-form__user-name">{{ userInfo ? userInfo.name : '' }}</span>
|
|
421
|
+
</p>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
|
|
425
|
+
<form class="other-charge-form__container" @submit.prevent="generateReceipt">
|
|
426
|
+
<!-- 费用项列表区域 -->
|
|
427
|
+
<CardContainer class="other-charge-form__section-margin">
|
|
428
|
+
<CardHeader title="费用项目">
|
|
429
|
+
<template #extra>
|
|
430
|
+
<VanButton
|
|
431
|
+
type="primary"
|
|
432
|
+
size="small"
|
|
433
|
+
icon="plus"
|
|
434
|
+
plain
|
|
435
|
+
@click="showAddItemModal = true"
|
|
436
|
+
>
|
|
437
|
+
添加费用项
|
|
438
|
+
</VanButton>
|
|
439
|
+
</template>
|
|
440
|
+
</CardHeader>
|
|
441
|
+
|
|
442
|
+
<div class="other-charge-form__items-container">
|
|
443
|
+
<template v-if="chargeItems.length > 0">
|
|
444
|
+
<OtherChargeItem
|
|
445
|
+
v-for="(item, index) in chargeItems"
|
|
446
|
+
:key="item.id"
|
|
447
|
+
:item="item"
|
|
448
|
+
:index="index"
|
|
449
|
+
:is-work-order="props.isWorkOrder"
|
|
450
|
+
:work-order-data="props.workOrderData"
|
|
451
|
+
@remove="removeChargeItem"
|
|
452
|
+
/>
|
|
453
|
+
</template>
|
|
454
|
+
<p v-else class="text-gray-400 text-center">
|
|
455
|
+
尚未添加收费项目
|
|
456
|
+
</p>
|
|
457
|
+
</div>
|
|
458
|
+
</CardContainer>
|
|
459
|
+
|
|
460
|
+
<!-- 收费信息 -->
|
|
461
|
+
<CardContainer class="other-charge-form__section-margin">
|
|
462
|
+
<CardHeader title="收费信息" />
|
|
463
|
+
<div class="other-charge-form__field">
|
|
464
|
+
<label class="other-charge-form__label">应收金额</label>
|
|
465
|
+
<div class="other-charge-form__amount-input">
|
|
466
|
+
<VanField
|
|
467
|
+
v-model="totalAmount"
|
|
468
|
+
readonly
|
|
469
|
+
input-align="right"
|
|
470
|
+
>
|
|
471
|
+
<template #prefix>
|
|
472
|
+
<span class="other-charge-form__prefix">¥</span>
|
|
473
|
+
</template>
|
|
474
|
+
<template #suffix>
|
|
475
|
+
<span class="other-charge-form__suffix">元</span>
|
|
476
|
+
</template>
|
|
477
|
+
</VanField>
|
|
478
|
+
</div>
|
|
479
|
+
</div>
|
|
480
|
+
|
|
481
|
+
<div class="other-charge-form__field">
|
|
482
|
+
<label class="other-charge-form__label">收费项目数</label>
|
|
483
|
+
<div class="other-charge-form__count-input">
|
|
484
|
+
<VanField
|
|
485
|
+
:model-value="chargeItems.length.toString()"
|
|
486
|
+
readonly
|
|
487
|
+
input-align="right"
|
|
488
|
+
>
|
|
489
|
+
<template #suffix>
|
|
490
|
+
<span class="other-charge-form__suffix">项</span>
|
|
491
|
+
</template>
|
|
492
|
+
</VanField>
|
|
493
|
+
</div>
|
|
494
|
+
</div>
|
|
495
|
+
</CardContainer>
|
|
496
|
+
|
|
497
|
+
<!-- 打印及备注 -->
|
|
498
|
+
<ChargePrintSelectorAndRemarks
|
|
499
|
+
v-model="receiptType"
|
|
500
|
+
v-model:remarks="remarks"
|
|
501
|
+
class="other-charge-form__section-margin"
|
|
502
|
+
/>
|
|
503
|
+
|
|
504
|
+
<!-- 支付方式 -->
|
|
505
|
+
<PaymentMethodSelectorCard
|
|
506
|
+
v-model="paymentMethod"
|
|
507
|
+
title="支付方式"
|
|
508
|
+
:user="props.user"
|
|
509
|
+
:allowed-methods="config.allowedMethods"
|
|
510
|
+
class="other-charge-form__section-margin"
|
|
511
|
+
/>
|
|
512
|
+
|
|
513
|
+
<!-- 文件上传区域 -->
|
|
514
|
+
<!-- 宫格上传组件(多种文件类型时使用) -->
|
|
515
|
+
<GridFileUploader
|
|
516
|
+
v-if="useGridUploader"
|
|
517
|
+
ref="gridFileUploaderRef"
|
|
518
|
+
v-model:file-list="fileList"
|
|
519
|
+
:file-types="config.fileTypes"
|
|
520
|
+
title="上传附件"
|
|
521
|
+
class="other-charge-form__section-margin"
|
|
522
|
+
/>
|
|
523
|
+
<CardContainer
|
|
524
|
+
v-else
|
|
525
|
+
class="other-charge-form__section-margin"
|
|
526
|
+
>
|
|
527
|
+
<!-- 普通上传组件(单一文件类型或无配置时使用) -->
|
|
528
|
+
<FileUploader
|
|
529
|
+
v-model:file-list="fileList"
|
|
530
|
+
title="上传附件"
|
|
531
|
+
:multiple="true"
|
|
532
|
+
:user-type="config.fileTypes && config.fileTypes.length > 0 ? config.fileTypes[0].userType : '其他收费'"
|
|
533
|
+
@file-added="onFileAdded"
|
|
534
|
+
@file-removed="onFileRemoved"
|
|
535
|
+
/>
|
|
536
|
+
</CardContainer>
|
|
537
|
+
|
|
538
|
+
<!-- 应收金额展示 -->
|
|
539
|
+
<CardContainer class="other-charge-form__summary">
|
|
540
|
+
<div class="other-charge-form__total-content">
|
|
541
|
+
<p class="other-charge-form__total-label">
|
|
542
|
+
应收金额:<span class="other-charge-form__total-value">¥ {{ totalAmount }}</span>
|
|
543
|
+
</p>
|
|
544
|
+
<p class="other-charge-form__total-count">
|
|
545
|
+
收费项目:{{ chargeItems.length }} 项
|
|
546
|
+
</p>
|
|
547
|
+
</div>
|
|
548
|
+
</CardContainer>
|
|
549
|
+
|
|
550
|
+
<!-- 按钮区域 -->
|
|
551
|
+
<div class="other-charge-form__buttons">
|
|
552
|
+
<VanButton
|
|
553
|
+
plain
|
|
554
|
+
type="default"
|
|
555
|
+
class="other-charge-form__cancel-btn"
|
|
556
|
+
@click="cancelOperation"
|
|
557
|
+
>
|
|
558
|
+
取消
|
|
559
|
+
</VanButton>
|
|
560
|
+
<VanButton
|
|
561
|
+
type="primary"
|
|
562
|
+
native-type="submit"
|
|
563
|
+
class="other-charge-form__confirm-btn"
|
|
564
|
+
:disabled="chargeItems.length === 0"
|
|
565
|
+
>
|
|
566
|
+
确认收费
|
|
567
|
+
</VanButton>
|
|
568
|
+
</div>
|
|
569
|
+
</form>
|
|
570
|
+
|
|
571
|
+
<!-- 添加费用项弹窗 -->
|
|
572
|
+
<OtherChargeItemModal
|
|
573
|
+
v-model:show="showAddItemModal"
|
|
574
|
+
:is-work-order="props.isWorkOrder"
|
|
575
|
+
:work-order-data="props.workOrderData"
|
|
576
|
+
:charge-types="chargeTypeConfig"
|
|
577
|
+
@add="addChargeItem"
|
|
578
|
+
/>
|
|
579
|
+
|
|
580
|
+
<!-- 收据弹窗 -->
|
|
581
|
+
<ReceiptModal ref="printRef" @close="emit('closeOperation')" />
|
|
582
|
+
<!-- 二维码支付弹窗 -->
|
|
583
|
+
<CodePayment
|
|
584
|
+
v-if="showQrCodePayment"
|
|
585
|
+
v-model:show="showQrCodePayment"
|
|
586
|
+
:payment="paymentMethod"
|
|
587
|
+
:collection="totalAmount"
|
|
588
|
+
:form-data="{}"
|
|
589
|
+
type="其他收费"
|
|
590
|
+
:user="props.user"
|
|
591
|
+
@payment-success="handleQrCodePaymentSuccess"
|
|
592
|
+
@payment-cancel="handleQrCodePaymentCancel"
|
|
593
|
+
/>
|
|
594
|
+
</div>
|
|
595
|
+
</template>
|
|
596
|
+
|
|
597
|
+
<style scoped lang="less">
|
|
598
|
+
.other-charge-form {
|
|
599
|
+
padding: 12px;
|
|
600
|
+
|
|
601
|
+
&__info-box {
|
|
602
|
+
display: flex;
|
|
603
|
+
background: linear-gradient(to right, #ebf4ff, #e0f2fe);
|
|
604
|
+
border-left: 4px solid #3b82f6;
|
|
605
|
+
border-radius: 6px;
|
|
606
|
+
padding: 12px;
|
|
607
|
+
margin-bottom: 16px;
|
|
608
|
+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
&__info-icon {
|
|
612
|
+
font-size: 20px;
|
|
613
|
+
color: #3b82f6;
|
|
614
|
+
margin-top: 3px;
|
|
615
|
+
margin-right: 12px;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
&__info-content {
|
|
619
|
+
flex: 1;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
&__info-text {
|
|
623
|
+
margin: 0 0 4px 0;
|
|
624
|
+
font-size: 14px;
|
|
625
|
+
color: #1e40af;
|
|
626
|
+
font-weight: 500;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
&__info-user {
|
|
630
|
+
margin: 0;
|
|
631
|
+
font-size: 14px;
|
|
632
|
+
color: #2563eb;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
&__user-name {
|
|
636
|
+
font-weight: 600;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
&__container {
|
|
640
|
+
margin-bottom: 16px;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
&__main-content {
|
|
644
|
+
background-color: #f9fafb;
|
|
645
|
+
border-radius: 8px;
|
|
646
|
+
border: 1px solid #e5e7eb;
|
|
647
|
+
padding: 16px;
|
|
648
|
+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
649
|
+
margin-bottom: 16px;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
&__two-columns {
|
|
653
|
+
display: grid;
|
|
654
|
+
grid-template-columns: 1fr;
|
|
655
|
+
gap: 16px;
|
|
656
|
+
margin-bottom: 16px;
|
|
657
|
+
|
|
658
|
+
@media (min-width: 640px) {
|
|
659
|
+
grid-template-columns: 1fr 1fr;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
&__field {
|
|
664
|
+
margin-bottom: 16px;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
&__label {
|
|
668
|
+
display: block;
|
|
669
|
+
font-size: 12px;
|
|
670
|
+
font-weight: 500;
|
|
671
|
+
color: #6b7280;
|
|
672
|
+
margin-bottom: 4px;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
&__amount-input,
|
|
676
|
+
&__count-input {
|
|
677
|
+
:deep(.van-field) {
|
|
678
|
+
background-color: #f9fafb;
|
|
679
|
+
border-radius: 6px;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
&__prefix,
|
|
684
|
+
&__suffix {
|
|
685
|
+
color: #6b7280;
|
|
686
|
+
font-size: 14px;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
&__total-content {
|
|
690
|
+
width: 100%;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
&__total-label {
|
|
694
|
+
font-size: 16px;
|
|
695
|
+
font-weight: 500;
|
|
696
|
+
color: #4b5563;
|
|
697
|
+
margin: 0 0 4px 0;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
&__total-value {
|
|
701
|
+
font-size: 20px;
|
|
702
|
+
font-weight: 600;
|
|
703
|
+
color: #2563eb;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
&__total-count {
|
|
707
|
+
font-size: 14px;
|
|
708
|
+
color: #6b7280;
|
|
709
|
+
margin: 0;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
&__buttons {
|
|
713
|
+
display: flex;
|
|
714
|
+
justify-content: flex-end;
|
|
715
|
+
gap: 12px;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
&__cancel-btn {
|
|
719
|
+
border-color: #d1d5db;
|
|
720
|
+
color: #4b5563;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
&__confirm-btn {
|
|
724
|
+
background-color: #2563eb;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
&__file-uploader {
|
|
728
|
+
margin-bottom: 16px;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
&__section-margin {
|
|
732
|
+
margin-bottom: 16px;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
&__summary {
|
|
736
|
+
background: linear-gradient(to right, #ebf4ff, #e0f2fe);
|
|
737
|
+
border: 1px solid #bfdbfe;
|
|
738
|
+
border-radius: 8px;
|
|
739
|
+
padding: 12px;
|
|
740
|
+
margin-bottom: 16px;
|
|
741
|
+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
</style>
|