feat: 线下登记延期先传凭证自动识别交易单号,弹窗显示套餐名称
Some checks failed
构建并部署前端到测试环境 / deploy (push) Has been cancelled
Some checks failed
构建并部署前端到测试环境 / deploy (push) Has been cancelled
- 登记线下付款时收款凭证放到最前,上传后自动识别交易单号预填(复用凭证识别,需人工核对,不覆盖手填) - 试算后自动带入实收金额 - 延期弹窗显示套餐名称(及资产),不再显示套餐记录 ID
This commit is contained in:
@@ -32,9 +32,21 @@
|
||||
value="special"
|
||||
label="特殊延期" /></ElSelect
|
||||
></ElFormItem>
|
||||
<ElFormItem v-if="mode === 'offline'" label="收款凭证" required
|
||||
><div class="silence-receipt"
|
||||
><VoucherUpload
|
||||
v-model="vouchers"
|
||||
key="vouchers-offline"
|
||||
:max-count="5"
|
||||
@uploading-change="(value) => (uploadingReceipt = value)"
|
||||
/><p class="silence-ocr-tip"
|
||||
>先上传收款凭证,系统会自动识别交易单号,可在下方核对修改。</p
|
||||
></div
|
||||
></ElFormItem
|
||||
>
|
||||
<template v-if="usageId">
|
||||
<ElFormItem label="套餐记录"
|
||||
>{{ usageId }}
|
||||
<ElFormItem label="套餐"
|
||||
>{{ packageLabel }}
|
||||
<ElButton link :loading="eligibilityLoading" @click="loadEligibility"
|
||||
>刷新资格</ElButton
|
||||
></ElFormItem
|
||||
@@ -111,7 +123,15 @@
|
||||
:value="item.id"
|
||||
:label="item.name" /></ElSelect
|
||||
></ElFormItem>
|
||||
<ElFormItem label="交易单号" required><ElInput v-model="tradeNo" /></ElFormItem>
|
||||
<ElFormItem label="交易单号" required
|
||||
><ElInput
|
||||
v-model="tradeNo"
|
||||
:placeholder="ocrLoading ? '正在识别凭证中的交易单号…' : '请输入或核对交易单号'"
|
||||
@input="tradeNoTouched = true"
|
||||
/><span v-if="ocrMessage" :class="['silence-ocr-tip', ocrTone]">{{
|
||||
ocrMessage
|
||||
}}</span></ElFormItem
|
||||
>
|
||||
<ElFormItem v-if="mode === 'offline'" label="实收金额(元)" required
|
||||
><ElInput v-model="paidYuan" inputmode="decimal"
|
||||
/></ElFormItem>
|
||||
@@ -122,7 +142,7 @@
|
||||
value-format="YYYY-MM-DDTHH:mm:ssZ"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
/></ElFormItem>
|
||||
<ElFormItem label="收款凭证" required
|
||||
<ElFormItem v-if="mode !== 'offline'" label="收款凭证" required
|
||||
><VoucherUpload
|
||||
v-model="vouchers"
|
||||
:key="`vouchers-${mode}`"
|
||||
@@ -184,6 +204,7 @@
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
import { PackageSilenceService as Service } from '@/api/modules/packageSilence'
|
||||
import { EmployeeCollectionService } from '@/api/modules/employeeCollection'
|
||||
import { AgentRechargeService } from '@/api/modules/agentRecharge'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { useSchedulerRequest } from '@/composables/useSchedulerRequest'
|
||||
@@ -212,10 +233,22 @@
|
||||
import SilenceTable from './SilenceTable.vue'
|
||||
// allowed 未传时视为允许:Vue 会把未传的布尔 prop 当成 false,不设默认值时资产详情入口一律报"无权执行此操作"
|
||||
const props = withDefaults(
|
||||
defineProps<{ usageId?: number; allowed?: boolean; batchMode?: boolean }>(),
|
||||
defineProps<{
|
||||
usageId?: number
|
||||
allowed?: boolean
|
||||
batchMode?: boolean
|
||||
packageName?: string
|
||||
assetIdentifier?: string
|
||||
}>(),
|
||||
{ allowed: true, batchMode: false }
|
||||
)
|
||||
const emit = defineEmits<{ close: []; changed: [] }>()
|
||||
// 展示套餐名称(及资产),不展示内部套餐记录 ID;调用方未传名称时才退回显示 ID
|
||||
const packageLabel = computed(() => {
|
||||
const name = props.packageName?.trim()
|
||||
if (!name) return props.usageId ? `套餐记录 ${props.usageId}` : '-'
|
||||
return props.assetIdentifier ? `${name}(${props.assetIdentifier})` : name
|
||||
})
|
||||
const { hasAuth } = useAuth(),
|
||||
user = useUserStore()
|
||||
const mode = ref<'online' | 'offline' | 'special'>(
|
||||
@@ -293,6 +326,62 @@
|
||||
quote.value.forUsage === props.usageId &&
|
||||
Number.isSafeInteger(quote.value.amount))
|
||||
)
|
||||
// 线下登记:上传第一张收款凭证后自动识别交易单号(复用代理充值的凭证识别,只预填、需人工核对)
|
||||
const ocrLoading = ref(false),
|
||||
ocrMessage = ref(''),
|
||||
ocrTone = ref<'info' | 'success' | 'warning'>('info'),
|
||||
tradeNoTouched = ref(false)
|
||||
let ocrSequence = 0,
|
||||
ocrKey = ''
|
||||
async function recognizeTradeNo(key: string) {
|
||||
if (!key || key === ocrKey) return
|
||||
ocrKey = key
|
||||
const current = ++ocrSequence
|
||||
ocrLoading.value = true
|
||||
ocrTone.value = 'info'
|
||||
ocrMessage.value = '正在识别交易单号…'
|
||||
try {
|
||||
const res = await AgentRechargeService.recognizePaymentVoucher({ payment_voucher_key: key })
|
||||
if (current !== ocrSequence) return
|
||||
const transactionNo = res.code === 0 ? res.data?.external_transaction_no?.trim() : ''
|
||||
if (!transactionNo) {
|
||||
ocrTone.value = 'warning'
|
||||
ocrMessage.value = '未识别到交易单号,请手动填写'
|
||||
return
|
||||
}
|
||||
if (!tradeNoTouched.value || !tradeNo.value.trim()) {
|
||||
tradeNo.value = transactionNo
|
||||
tradeNoTouched.value = false
|
||||
}
|
||||
ocrTone.value = 'success'
|
||||
ocrMessage.value = `已识别交易单号 ${transactionNo},请对照凭证核对`
|
||||
} catch {
|
||||
if (current !== ocrSequence) return
|
||||
ocrTone.value = 'warning'
|
||||
ocrMessage.value = '识别失败,请手动填写交易单号'
|
||||
} finally {
|
||||
if (current === ocrSequence) ocrLoading.value = false
|
||||
}
|
||||
}
|
||||
watch(
|
||||
() => [mode.value, vouchers.value[0]] as const,
|
||||
([currentMode, firstKey]) => {
|
||||
if (currentMode === 'offline' && firstKey) void recognizeTradeNo(firstKey)
|
||||
if (!firstKey) {
|
||||
ocrKey = ''
|
||||
ocrMessage.value = ''
|
||||
}
|
||||
}
|
||||
)
|
||||
// 线下登记的实收金额必须等于试算金额:试算后自动带入,仍可修改
|
||||
watch(
|
||||
() => quote.value?.amount,
|
||||
(amount) => {
|
||||
if (mode.value === 'offline' && Number.isSafeInteger(amount) && !paidYuan.value) {
|
||||
paidYuan.value = (Number(amount) / 100).toFixed(2)
|
||||
}
|
||||
}
|
||||
)
|
||||
let active = true,
|
||||
version = 0,
|
||||
timer: ReturnType<typeof setTimeout> | undefined
|
||||
@@ -319,6 +408,11 @@
|
||||
basis.value = ''
|
||||
methodId.value = undefined
|
||||
tradeNo.value = ''
|
||||
tradeNoTouched.value = false
|
||||
ocrSequence++
|
||||
ocrKey = ''
|
||||
ocrLoading.value = false
|
||||
ocrMessage.value = ''
|
||||
paidYuan.value = ''
|
||||
paidAt.value = ''
|
||||
batchItems.value = []
|
||||
@@ -597,4 +691,23 @@
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.silence-receipt {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.silence-ocr-tip {
|
||||
margin: 4px 0 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--el-text-color-secondary);
|
||||
|
||||
&.success {
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
v-if="row.silence.extendable && (hasAuth(P.extend) || hasAuth(P.offline))"
|
||||
link
|
||||
type="primary"
|
||||
@click="extendId = row.package_usage_id"
|
||||
@click="openExtension(row)"
|
||||
>延期</ElButton
|
||||
>
|
||||
</template>
|
||||
@@ -140,6 +140,8 @@
|
||||
<SilenceExtendDialog
|
||||
v-if="extendId || batchVisible"
|
||||
:usage-id="extendId"
|
||||
:package-name="extendRow?.package_name"
|
||||
:asset-identifier="extendRow?.asset_identifier"
|
||||
:batch-mode="batchVisible"
|
||||
:allowed="allowed"
|
||||
@close="closeExtension"
|
||||
@@ -232,6 +234,7 @@
|
||||
changing = ref(false)
|
||||
const recordsId = ref<number>(),
|
||||
extendId = ref<number>(),
|
||||
extendRow = ref<Record<string, any>>(),
|
||||
batchVisible = ref(false),
|
||||
policyVisible = ref(false),
|
||||
policyId = ref<number>(),
|
||||
@@ -533,6 +536,10 @@
|
||||
actionError.value = gatewayError(e)
|
||||
}
|
||||
}
|
||||
function openExtension(row: Record<string, any>) {
|
||||
extendRow.value = row
|
||||
extendId.value = row.package_usage_id
|
||||
}
|
||||
async function matchPolicy() {
|
||||
if (!canManagePolicy.value || !matchPackage.value || matching.value) return
|
||||
const scope = pollingScope(user),
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
<SilenceExtendDialog
|
||||
v-if="extendVisible && usageId"
|
||||
:usage-id="usageId"
|
||||
:package-name="packageName"
|
||||
:asset-identifier="assetIdentifier"
|
||||
@close="closeExtension"
|
||||
@changed="dirty = true"
|
||||
/>
|
||||
@@ -56,7 +58,12 @@
|
||||
import type { PackageSilenceInfo } from '@/types/api/packageSilence'
|
||||
import { silenceStatusLabel } from '@/utils/business/packageSilence'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
const props = defineProps<{ silence?: PackageSilenceInfo | null; usageId?: number }>()
|
||||
const props = defineProps<{
|
||||
silence?: PackageSilenceInfo | null
|
||||
usageId?: number
|
||||
packageName?: string
|
||||
assetIdentifier?: string
|
||||
}>()
|
||||
const emit = defineEmits<{ changed: [] }>()
|
||||
const { hasAuth } = useAuth()
|
||||
const recordsVisible = ref(false),
|
||||
|
||||
Reference in New Issue
Block a user