feat(mobile-oa-h5): align transfer payStatus labels with 付款 wording

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-10-12 18:10:14 +08:00
1 parent 727002761d
commit cc0b217c89
4 files changed
+792 -5

No files matched your search

@@ -0,0 +1,228 @@
<template>
<article class="presign-transfer-card">
<header class="presign-transfer-card__header">
<div>
<strong>意向金转账 {{ index + 1 }}</strong>
</div>
<van-tag class="market-tag" :type="statusTone" :title="statusLabel">{{ statusLabel }}</van-tag>
</header>
<div class="presign-form-field">
<label>转账方式</label>
<PresignSelectField
:model-value="modelValue.transferType"
:options="transferTypes"
:disabled="!editable"
title="选择转账方式"
@update:model-value="updateField('transferType', $event)"
/>
</div>
<div class="presign-form-field">
<label>转账金额(元)</label>
<OaMoneyField
:model-value="modelValue.transferAmount"
:disabled="!editable"
:show-upper="false"
@update:model-value="updateField('transferAmount', $event)"
/>
</div>
<div class="presign-form-field">
<label>转账时间</label>
<OaDatePicker
:model-value="modelValue.transferTime"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
:disabled="!editable"
@update:model-value="updateField('transferTime', $event)"
/>
</div>
<div class="presign-form-field is-wide">
<label>转账凭证</label>
<PresignAttachmentUploader
:model-value="modelValue.attachments"
:disabled="!editable"
:uploading="uploading"
@upload="$emit('upload', { transfer: modelValue, file: $event })"
@remove="removeAttachment"
/>
</div>
<div class="presign-form-field is-wide">
<label>备注</label>
<van-field
:model-value="modelValue.remark"
type="textarea"
rows="2"
maxlength="512"
show-word-limit
:disabled="!editable"
placeholder="选填"
@update:model-value="updateField('remark', $event)"
/>
</div>
<div v-if="modelValue.financeRemark" class="presign-transfer-card__finance">
<span>财务备注</span>
<strong>{{ modelValue.financeRemark }}</strong>
</div>
<footer v-if="showActions" class="presign-transfer-card__actions">
<van-button
v-if="canDelete"
size="small"
type="danger"
plain
icon="delete-o"
:disabled="busy"
@click="$emit('remove', modelValue)"
>删除</van-button>
<van-button
v-if="can('submit')"
size="small"
type="primary"
icon="passed"
:loading="busyAction === 'submit-transfer'"
:disabled="busy"
@click="$emit('submit-transfer', modelValue)"
>提交财务确认</van-button>
<van-button
v-if="can('withdraw')"
size="small"
type="warning"
plain
icon="revoke"
:loading="busyAction === 'withdraw-transfer'"
:disabled="busy"
@click="$emit('withdraw-transfer', modelValue)"
>撤回</van-button>
</footer>
</article>
</template>
<script setup>
import { computed } from 'vue'
import OaDatePicker from '../../oa/components/OaDatePicker.vue'
import OaMoneyField from '../../oa/components/OaMoneyField.vue'
import PresignAttachmentUploader from './PresignAttachmentUploader.vue'
import PresignSelectField from './PresignSelectField.vue'
const props = defineProps({
modelValue: { type: Object, required: true },
index: { type: Number, required: true },
transferTypes: { type: Array, default: () => [] },
disabled: { type: Boolean, default: false },
uploading: { type: Boolean, default: false },
busyAction: { type: String, default: '' }
})
const emit = defineEmits([
'update:modelValue',
'change',
'remove',
'upload',
'submit-transfer',
'withdraw-transfer'
])
const actionSet = computed(() => new Set(
(props.modelValue.allowedActions || []).map((action) =>
typeof action === 'string' ? action : action?.code
)
))
const newRow = computed(() => !String(props.modelValue.id || '').trim())
const editable = computed(() => !props.disabled && (newRow.value || actionSet.value.has('edit')))
const canDelete = computed(() => !props.disabled && (newRow.value || actionSet.value.has('delete')))
const busy = computed(() => Boolean(props.busyAction) || props.uploading)
const showActions = computed(() => canDelete.value || can('submit') || can('withdraw'))
const statusLabel = computed(() => ({
dfj: '已付款',
jfqrz: '付款确认中',
yjf: '付款完成',
tfqrz: '退费确认中',
ytf: '已退费'
}[String(props.modelValue.payStatus || 'dfj')] || '已付款'))
const statusTone = computed(() => ({
dfj: 'default',
jfqrz: 'warning',
yjf: 'success',
tfqrz: 'warning',
ytf: 'default'
}[String(props.modelValue.payStatus || 'dfj')] || 'default'))
function can(action) {
return !props.disabled && actionSet.value.has(action)
}
function updateField(key, value) {
if (!editable.value) return
const next = { ...props.modelValue, [key]: value }
emit('update:modelValue', next)
emit('change', next)
}
function removeAttachment(index) {
if (!editable.value) return
const attachments = (props.modelValue.attachments || []).filter((_, itemIndex) => itemIndex !== index)
updateField('attachments', attachments)
}
</script>
<style scoped>
.presign-transfer-card {
padding: 14px;
border: 1px solid #dce9e4;
border-radius: 14px;
background: #fff;
}
.presign-transfer-card__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 14px;
padding-bottom: 12px;
border-bottom: 1px solid #edf3f0;
}
.presign-transfer-card__header > div {
display: grid;
min-width: 0;
gap: 3px;
}
.presign-transfer-card__finance {
display: grid;
gap: 4px;
margin-top: 12px;
padding: 10px 12px;
border-radius: 10px;
background: #f4f8f6;
}
.presign-transfer-card__finance span {
color: #6b7f77;
font-size: 12px;
}
.presign-transfer-card__actions {
display: flex;
min-height: 44px;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-top: 14px;
padding-top: 12px;
border-top: 1px solid #edf3f0;
}
.presign-transfer-card__actions .van-button {
min-height: 36px;
border-radius: 9px;
}
</style>
@@ -0,0 +1,332 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { getMarketModule } from './module-registry.js'
test('payment chase uses the singular read-only resource and opens payment detail', () => {
const module = getMarketModule('payment-chase')
assert.equal(module.resource, 'payment-chase')
assert.deepEqual(module.actions.specialist, ['view-payment'])
assert.deepEqual(module.detailTarget({ samplePaymentId: 'pay 1', scheduleId: 'sch-1' }), {
module: 'payment',
id: 'pay 1',
query: { scheduleId: 'sch-1' }
})
})
test('module adapters map list filters to the actual backend query names', () => {
assert.deepEqual(getMarketModule('presign').listParams({ status: 'draft' }), {
orderStatus: 'draft'
})
assert.deepEqual(getMarketModule('protocol').listParams({ status: 'cell_storage' }), {
source: 'cell_storage'
})
assert.deepEqual(getMarketModule('sample').listParams({ status: 'research_donation' }), {
source: 'research_donation'
})
assert.deepEqual(getMarketModule('freight').listParams({ status: 'air' }), {
transportMode: 'air',
includeItems: true
})
})
test('protocol list tabs match Web dual-source tabs without an all-sources option', () => {
assert.deepEqual(getMarketModule('protocol').statusOptions, [
{ text: '细胞存储', value: 'cell_storage' },
{ text: '科研捐赠', value: 'research_donation' }
])
assert.deepEqual(getMarketModule('protocol').listParams({}), {
source: 'cell_storage'
})
assert.deepEqual(getMarketModule('protocol').listParams({ status: '' }), {
source: 'cell_storage'
})
})
test('sample list tabs match Web dual-source tabs without an all-sources option', () => {
assert.deepEqual(getMarketModule('sample').statusOptions, [
{ text: '细胞存储', value: 'cell_storage' },
{ text: '科研捐赠', value: 'research_donation' }
])
assert.deepEqual(getMarketModule('sample').listParams({}), {
source: 'cell_storage'
})
assert.deepEqual(getMarketModule('sample').listParams({ status: '' }), {
source: 'cell_storage'
})
assert.deepEqual(getMarketModule('sample').listParams({ status: 'research_donation' }), {
source: 'research_donation'
})
})
test('presign filters use the backend order status codes', () => {
assert.deepEqual(getMarketModule('presign').statusOptions, [
{ text: '全部状态', value: '' },
{ text: '草稿', value: 'draft' },
{ text: '进行中', value: 'active' },
{ text: '已转正式', value: 'converted' },
{ text: '已作废', value: 'void' }
])
})
test('protocol summary status resolves real codes without exposing unknown codes', () => {
const protocol = getMarketModule('protocol')
assert.equal(protocol.statusLabel({ source: 'cell_storage', basic: { protocolStatus: 'sxz' } }), '生效中')
assert.equal(protocol.statusLabel({ source: 'research_donation', basic: { donationStatus: 'yzf' } }), '已作废')
assert.equal(protocol.statusLabel({ source: 'cell_storage', basic: { protocolStatus: 'raw-status' } }), '未知')
assert.equal(protocol.statusLabel({ source: 'cell_storage', basic: { protocolStatus: '' } }), '—')
})
test('protocol and sample adapters preserve nested detail sections', () => {
const protocol = getMarketModule('protocol').detailSections({
source: 'cell_storage',
protocol: { protocolNo: 'P-1' },
customer: { motherName: '张三' },
collection: { regionName: '华东' },
samples: [{ sampleCode: 'S-1' }],
payment: { payMode: 'yearly' },
attachments: { protocolAttachments: 'a.pdf' }
})
assert.deepEqual(protocol.map((section) => section.key), [
'protocol', 'collection', 'customer', 'samples', 'payment', 'attachments'
])
const sample = getMarketModule('sample').detailSections({
sections: [{ key: 'market-handles', title: '市场办理', storageCert: { statusLabel: '已完成' } }]
})
assert.equal(sample[0].storageCert.statusLabel, '已完成')
})
test('attachment-bearing detail adapters expose typed values instead of path text', () => {
const protocol = getMarketModule('protocol').detailSections({
source: 'cell_storage',
basic: { protocolNo: 'P-1' },
attachments: {
protocolAttachments: 'protocol.pdf',
consentFormUrls: 'consent.jpg'
}
})
const protocolFiles = protocol.find((section) => section.key === 'attachments').fields
assert.ok(protocolFiles.every((field) => field.type === 'attachments'))
assert.equal(protocolFiles[0].value, 'protocol.pdf')
const payment = getMarketModule('payment').detailSections({
schedules: [{ transfers: [{ transferAttachments: 'payment-proof.png' }] }]
})
const paymentFiles = payment[1].cards[0].cards[0].fields.find(
(field) => field.label === '转账凭证'
)
assert.deepEqual(paymentFiles, {
label: '转账凭证',
type: 'attachments',
value: 'payment-proof.png'
})
const sample = getMarketModule('sample').detailSections({
sections: [{
key: 'market-handles',
storageCert: { attachments: 'certificate.pdf' },
insurance: { attachments: 'insurance.jpg' }
}]
})
assert.equal(sample[0].cards[0].fields.find((field) => field.label === '附件').type, 'attachments')
const presign = getMarketModule('presign').detailSections({
transfers: [{ transferAttachments: 'deposit-proof.png' }]
})
assert.equal(presign[1].cards[0].fields.find((field) => field.label === '转账附件').type, 'attachments')
})
test('presign detail prefers attachment metadata so original names remain visible', () => {
const attachments = [
{ fileUrl: '/minio/uuid.pdf', fileName: '意向金凭证.pdf' }
]
const section = getMarketModule('presign').detailSections({
transfers: [{ id: 't1', transferAttachments: '/minio/uuid.pdf', attachments }]
}).find((item) => item.key === 'transfers')
assert.deepEqual(
section.cards[0].fields.find((field) => field.type === 'attachments').value,
attachments
)
})
test('presign detail keeps the Web order fields and transfer cards', () => {
const sections = getMarketModule('presign').detailSections({
orderDate: '20261011',
protocolNo: 'P-1',
motherName: '张三',
expectedDeliveryDate: '20261231',
contractAmount: '28800',
confirmedDepositAmount: '5000',
orderStatus: 'active',
transfers: [{
id: 't-1', transferType: 'wx', transferAmount: '5000',
transferTime: '2026-10-11 10:00:00', payStatus: 'yjf'
}]
})
assert.deepEqual(sections.map((section) => section.key), ['base', 'transfers'])
assert.ok(sections[0].fields.some((field) => field.label === '母亲姓名' && field.value === '张三'))
assert.equal(sections[1].raw.transfers[0].statusLabel, '付款完成')
})
test('payment and sample adapters initialize only editable fields from real detail shapes', () => {
const payment = getMarketModule('payment')
const paymentDetail = {
id: 'payment-1',
schedules: [{ id: 'schedule-1', transfers: [{
id: 'transfer-1', transferAmount: '20', transferType: 'bank',
payStatus: 'dfj', statusLabel: '待提交', statusTone: 'warning'
}] }]
}
assert.deepEqual(payment.initialForm(paymentDetail, { transferId: 'transfer-1' }), {
transfer: { transferAmount: '20', transferType: 'bank' }
})
const sample = getMarketModule('sample')
const sampleDetail = { sections: [{ key: 'market-handles', storageCert: {
handleTime: '2026-10-11', handlerUserId: 'u1', certCode: 'CERT-1', statusLabel: '已完成'
} }] }
assert.deepEqual(sample.initialForm(sampleDetail, 'storage-cert'), {
handle: { handleTime: '2026-10-11', handlerUserId: 'u1' }
})
})
test('sample form options are scoped by the exact source-qualified sample id and action', () => {
const sample = getMarketModule('sample')
assert.deepEqual(sample.formOptionsParams('cell_storage:C-001', 'insurance'), {
sampleId: 'cell_storage:C-001',
action: 'insurance'
})
})
test('protocol personnel fields use isolated hydrated option collections', () => {
const fields = getMarketModule('protocol').formSections.flatMap((section) => section.fields)
assert.equal(fields.find((field) => field.key === 'collectorUserId').optionKey, 'collectorStaff')
assert.equal(fields.find((field) => field.key === 'salesUserId').optionKey, 'salesStaff')
assert.equal(fields.find((field) => field.key === 'regionManagerUserId').optionKey, 'managerStaff')
})
test('payment detail recognizes the converted cell-storage protocol status', () => {
const detail = getMarketModule('payment').detailSections({ protocolStatus: 'zckc' })
assert.equal(detail[0].fields.find((field) => field.label === '协议状态').value, '转细胞存储')
})
test('payment detail presents real backend progress, schedule statuses, and combined pay mode', () => {
const sections = getMarketModule('payment').detailSections({
payMode: 'djfq',
paymentProgress: '部分缴费',
schedules: [
{ id: 'open', scheduleStatus: 'open' },
{ id: 'cleared', scheduleStatus: 'cleared' },
{ id: 'void', scheduleStatus: 'void' }
]
})
const summary = sections.find((section) => section.key === 'summary')
const schedules = sections.find((section) => section.key === 'schedules').cards
assert.equal(summary.fields.find((field) => field.label === '缴费方式').value, '趸交+分期')
assert.equal(summary.fields.find((field) => field.label === '缴费进度').value, '部分缴费')
assert.equal(schedules.find((card) => card.key === 'open').statusText, '待缴费')
assert.equal(schedules.find((card) => card.key === 'cleared').statusText, '已缴清')
assert.equal(schedules.find((card) => card.key === 'void').statusText, '已作废')
})
test('protocol detail recognizes the combined lump-sum and installment pay mode', () => {
const sections = getMarketModule('protocol').detailSections({
source: 'cell_storage',
payment: { payMode: 'djfq' }
})
const payment = sections.find((section) => section.key === 'payment')
assert.equal(payment.fields.find((field) => field.label === '缴费模式').value, '趸交+分期')
})
test('protocol source is selectable only while creating and exposes all attachment fields', () => {
const fields = getMarketModule('protocol').formSections.flatMap((section) => section.fields)
assert.equal(fields.find((field) => field.key === 'source').createOnly, true)
assert.equal(fields.find((field) => field.key === 'source').type, 'tabs')
assert.ok(fields.some((field) => field.key === 'protocolTotalAmount'))
assert.ok(fields.some((field) => field.key === 'healthSurveyFormUrls'))
assert.ok(fields.some((field) => field.key === 'infectiousDiseaseReportUrls'))
})
test('freight unlink options come from complete detail items instead of a global first page', () => {
const freight = getMarketModule('freight')
const detailItems = [
{ sampleSource: 'cell_storage', sampleId: 'old-1', freightId: 'f1' },
{ sampleSource: 'research_donation', sampleId: 'old-2', freightId: 'f1' }
]
const firstPage = [{ sampleSource: 'cell_storage', sampleId: 'new-1', freightId: '' }]
assert.deepEqual(freight.sampleOptions('unlink', detailItems, firstPage), detailItems)
assert.deepEqual(freight.sampleOptions('link', detailItems, firstPage), firstPage)
assert.equal(freight.usesPagedSampleOptions('unlink'), false)
assert.equal(freight.usesPagedSampleOptions('link'), true)
})
test('detail adapters hide unresolved references and dictionary codes but preserve business numbers', () => {
const protocol = getMarketModule('protocol').detailSections({
source: 'cell_storage',
basic: { protocolNo: 'P-RAW-1', protocolStatus: 'raw-status', collectorUserId: 'user-raw', collectorUserName: 'user-raw' },
collection: { regionId: 'region-raw', hospitalId: 'hospital-raw', salesUserId: 'sales-raw', salesUserName: 'sales-raw' },
customer: { newbornSex: 'raw-sex', deliveryMode: 'raw-delivery' },
samples: [{ sampleCode: 'S-RAW-1', sampleTypeId: 'type-raw', unitId: 'unit-raw', sampleStatus: 'ycj' }],
payment: { payMode: 'raw-pay-mode' }
})
const values = protocol.flatMap((section) => [
...(section.fields || []).map((field) => field.value),
...(section.cards || []).flatMap((card) => card.fields.map((field) => field.value))
])
assert.ok(values.includes('P-RAW-1'))
assert.ok(values.includes('S-RAW-1'))
assert.ok(values.includes('未知人员'))
assert.ok(values.includes('未知'))
for (const raw of ['raw-status', 'user-raw', 'region-raw', 'hospital-raw', 'sales-raw', 'raw-sex', 'raw-delivery', 'type-raw', 'unit-raw', 'raw-pay-mode']) {
assert.ok(!values.includes(raw), `must not display ${raw}`)
}
assert.equal(protocol.find((section) => section.key === 'samples').cards[0].statusText, '已采集')
const sample = getMarketModule('sample').detailSections({
sections: [{ key: 'market-handles', storageCert: { handlerUserId: 'handler-raw', handlerUserName: 'handler-raw', statusLabel: 'raw-handle-status' } }]
})
assert.equal(sample[0].cards[0].fields.find((field) => field.label === '办理人员').value, '未知人员')
assert.equal(sample[0].cards[0].statusText, '未知')
const freight = getMarketModule('freight').detailSections({
freightNo: 'F-RAW-1', transportMode: 'raw-transport', createBy: 'creator-raw', creatorName: 'creator-raw',
items: [{ sampleSource: 'raw-source', sampleCode: 'S-RAW-2' }]
})
assert.equal(freight[0].fields.find((field) => field.label === '批次号').value, 'F-RAW-1')
assert.equal(freight[0].fields.find((field) => field.label === '运输方式').value, '未知')
assert.equal(freight[0].fields.find((field) => field.label === '创建人').value, '未知人员')
assert.equal(freight[1].cards[0].fields.find((field) => field.label === '样本编码').value, 'S-RAW-2')
assert.equal(freight[1].cards[0].fields.find((field) => field.label === '来源').value, '未知')
const presign = getMarketModule('presign').detailSections({
protocolNo: 'P-RAW-2', regionId: 'region-raw', regionName: 'region-raw',
hospitalId: 'hospital-raw', hospitalName: 'hospital-raw',
collectorUserId: 'collector-raw', collectorUserName: 'collector-raw',
payMode: 'raw-pay-mode', orderStatus: 'raw-order-status',
transfers: [{ transferType: 'raw-transfer', payStatus: 'raw-pay-status' }]
})
assert.equal(presign[0].fields.find((field) => field.label === '协议编号').value, 'P-RAW-2')
assert.equal(presign[0].fields.find((field) => field.label === '采集地区').value, '未知')
assert.equal(presign[0].fields.find((field) => field.label === '采集专员').value, '未知人员')
assert.equal(presign[0].fields.find((field) => field.label === '缴费方式').value, '未知')
assert.equal(presign[0].fields.find((field) => field.label === '订单状态').value, '未知')
assert.equal(presign[1].cards[0].fields.find((field) => field.label === '转账方式').value, '未知')
assert.equal(presign[1].cards[0].statusText, '未知')
const payment = getMarketModule('payment').detailSections({
protocolNo: 'P-RAW-3', payMode: 'raw-pay-mode', protocolStatus: 'raw-status',
progressStatus: 'raw-progress', schedules: [{ scheduleStatus: 'raw-schedule', transfers: [{ transferType: 'raw-transfer', payStatus: 'raw-pay-status' }] }]
})
const paymentValues = payment.flatMap((section) => [
...(section.fields || []).map((field) => field.value),
...(section.cards || []).flatMap((card) => [card.statusText, ...(card.cards || []).flatMap((nested) => [nested.statusText, ...nested.fields.map((field) => field.value)])])
])
assert.ok(paymentValues.includes('P-RAW-3'))
for (const raw of ['raw-pay-mode', 'raw-status', 'raw-progress', 'raw-schedule', 'raw-transfer', 'raw-pay-status']) {
assert.ok(!paymentValues.includes(raw), `must not display ${raw}`)
}
})
+104
View File
@@ -1,3 +1,79 @@
const PAY_MODE = Object.freeze({ dj: '趸交', fq: '分期', fgd: '分改趸', djfq: '趸交+分期' })
const PROTOCOL_STATUS = Object.freeze({
dsx: '待生效', dqr: '待确认', sxz: '生效中', ygq: '已过期',
yzf: '已作废', sdfq: '属地废弃', zckc: '转细胞存储', zky: '转科研'
})
const PAYMENT_PROGRESS = Object.freeze({
dfj: '待缴费', jfqrz: '付款确认中', bfyj: '部分已缴', yjq: '已缴清',
待缴费: '待缴费', 部分缴费: '部分缴费', 已缴清: '已缴清'
})
const SCHEDULE_STATUS = Object.freeze({
open: '待缴费', cleared: '已缴清', void: '已作废',
pending: '待缴费', partial: '部分到账', paid: '已缴清', closed: '已关闭'
})
const PAY_STATUS = Object.freeze({
dfj: '已付款', jfqrz: '付款确认中', yjf: '付款完成', tfqrz: '退费确认中', ytf: '已退费'
})
const TRANSFER_TYPE = Object.freeze({
wxzz: '微信转账', wx: '微信转账', wechat: '微信转账',
zzbzz: '支付宝转账', zfb: '支付宝转账', alipay: '支付宝转账',
yhkzz: '银行卡转账', bank: '银行卡转账',
xj: '现金', cash: '现金', lszz: '乐刷'
})
function label(map, value) {
const key = String(value || '').trim()
return map[key] || (key ? '未知' : '—')
}
function money(value) {
if (value == null || value === '') return '—'
const amount = Number(value)
if (!Number.isFinite(amount)) return String(value)
return `¥${amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
}
function displayDate(value) {
const text = String(value || '').trim()
return /^\d{8}$/.test(text)
? `${text.slice(0, 4)}-${text.slice(4, 6)}-${text.slice(6, 8)}`
: text || '—'
}
function transferCard(transfer, index) {
const statusText = label(PAY_STATUS, transfer?.payStatus)
return {
key: String(transfer?.id || `transfer-${index}`),
title: `转账笔 ${index + 1}`,
statusText,
statusTone: statusText.includes('确认中') ? 'warning' : statusText === '付款完成' ? 'success' : 'info',
fields: [
{ label: '转账方式', value: label(TRANSFER_TYPE, transfer?.transferType) },
{ label: '转账金额', value: money(transfer?.transferAmount) },
{ label: '转账时间', value: transfer?.transferTime || '—' },
{ label: '转账凭证', type: 'attachments', value: transfer?.transferAttachments || '' },
{ label: '备注', value: transfer?.remark || '—' }
]
}
}
function scheduleCard(schedule, index) {
const statusText = label(SCHEDULE_STATUS, schedule?.scheduleStatus)
return {
key: String(schedule?.id || `schedule-${index}`),
title: `第 ${schedule?.scheduleNo || index + 1} 期`,
statusText,
statusTone: statusText === '已缴清' ? 'success' : statusText === '部分到账' ? 'primary' : 'warning',
fields: [
{ label: '应缴金额', value: money(schedule?.dueAmount) },
{ label: '应缴日', value: displayDate(schedule?.dueDate) },
{ label: '已缴金额', value: money(schedule?.paidAmount) },
{ label: '欠缴金额', value: money(schedule?.arrearsAmount) }
],
cards: (schedule?.transfers || []).map(transferCard)
}
}
export default {
key: 'payment',
resource: 'payments',
@@ -7,6 +83,34 @@ export default {
},
listParams: ({ status } = {}) => ({ payMode: status || '' }),
statusOptions: [{ text: '全部缴费模式', value: '' }],
detailSections(detail) {
const progress = label(PAYMENT_PROGRESS, detail?.progressStatus || detail?.paymentProgress)
const protocolStatus = label(PROTOCOL_STATUS, detail?.protocolStatus)
return [
{
key: 'summary',
title: '缴费概览',
fields: [
{ label: '协议号', value: detail?.protocolNo || '—' },
{ label: '客户', value: detail?.motherName || '—' },
{ label: '联系电话', value: detail?.motherPhone || '—' },
{ label: '样本编码', value: detail?.sampleCode || '—' },
{ label: '缴费方式', value: label(PAY_MODE, detail?.payMode), tag: true, tagType: 'primary' },
{ label: '合同额', value: money(detail?.contractAmount) },
{ label: '已缴 / 待缴', value: `${money(detail?.paidAmount)} / ${money(detail?.dueRemainAmount ?? detail?.remainAmount)}` },
{ label: '整体进度', value: `已缴清 ${Number(detail?.clearedScheduleCount || 0)}/${Number(detail?.activeScheduleCount || 0)} 期` },
{ label: '协议状态', value: protocolStatus, tag: true },
{ label: '缴费进度', value: progress, tag: true }
]
},
{
key: 'schedules',
title: '应缴批次',
fields: [],
cards: (detail?.schedules || []).map(scheduleCard)
}
]
},
initialForm(detail, target = {}) {
const transfer = (detail?.schedules || [])
.flatMap((row) => row.transfers || [])
+128 -5
View File
@@ -1,4 +1,55 @@
import { commonFields } from './shared.js'
import {
formatPresignDate,
formatPresignDateTime,
formatPresignMoney,
presignStatusLabel,
presignStatusTone
} from '../presign/presign-presenter.js'
const paymentStatus = Object.freeze({
dfj: { label: '已付款', tone: 'default' },
jfqrz: { label: '付款确认中', tone: 'warning' },
yjf: { label: '付款完成', tone: 'success' },
tfqrz: { label: '退费确认中', tone: 'warning' },
ytf: { label: '已退费', tone: 'default' }
})
const transferTypeLabels = Object.freeze({
wxzz: '微信转账', wx: '微信转账', wechat: '微信转账',
zzbzz: '支付宝转账', zfb: '支付宝转账', alipay: '支付宝转账',
yhkzz: '银行卡转账', bank: '银行卡转账',
xj: '现金', cash: '现金', lszz: '乐刷'
})
function dictionaryLabel(map, value) {
const key = String(value || '').trim()
return map[key] || (key ? '未知' : '—')
}
function person(name, id) {
const displayName = String(name || '').trim()
const referenceId = String(id || '').trim()
if (displayName && displayName !== referenceId) return displayName
return referenceId ? '未知人员' : '—'
}
function referenceName(name, id) {
const displayName = String(name || '').trim()
const referenceId = String(id || '').trim()
if (displayName && displayName !== referenceId) return displayName
return referenceId ? '未知' : '—'
}
function presignStatus(value) {
const code = String(value || '').trim()
if (!code) return '—'
const result = presignStatusLabel(code)
return result === code ? '未知' : result
}
function displayValue(value) {
return value && value !== '-' ? value : '—'
}
export default {
key: 'presign',
@@ -10,16 +61,88 @@ export default {
listParams: ({ status } = {}) => ({ orderStatus: status || '' }),
statusOptions: [
{ text: '全部状态', value: '' },
{ text: '预签约', value: 'yqy' },
{ text: '已转正式', value: 'yzs' }
{ text: '草稿', value: 'draft' },
{ text: '进行中', value: 'active' },
{ text: '已转正式', value: 'converted' },
{ text: '已作废', value: 'void' }
],
statusLabel(detail) {
return presignStatus(detail?.orderStatus)
},
statusTone(detail) {
return presignStatusTone(detail?.orderStatus)
},
detailSections(detail) {
const status = String(detail?.orderStatus || '')
const transfers = (detail?.transfers || []).map((transfer, index) => {
const paymentCode = String(transfer?.payStatus || 'dfj')
const payment = paymentStatus[paymentCode] || { label: '未知', tone: 'default' }
return {
...transfer,
title: `意向金转账 ${index + 1}`,
statusLabel: payment.label,
statusTone: payment.tone,
transferAmount: formatPresignMoney(transfer?.transferAmount),
transferTime: formatPresignDateTime(transfer?.transferTime)
}
})
return [
{
key: 'base',
title: '预签信息',
fields: [
{ label: '业务日期', value: displayValue(formatPresignDate(detail?.orderDate)) },
{ label: '协议编号', value: detail?.protocolNo || '—' },
{ label: '母亲姓名', value: detail?.motherName || '—' },
{ label: '预产期', value: displayValue(formatPresignDate(detail?.expectedDeliveryDate)) },
{ label: '采集地区', value: referenceName(detail?.regionName, detail?.regionId) },
{ label: '采集医院', value: referenceName(detail?.hospitalName, detail?.hospitalId) },
{ label: '采集专员', value: person(detail?.collectorUserName, detail?.collectorUserId) },
{ label: '采集经理', value: person(detail?.regionManagerUserName, detail?.regionManagerUserId) },
{ label: '采集时间', value: displayValue(formatPresignDateTime(detail?.sampleCollectTime)) },
{ label: '缴费方式', value: dictionaryLabel({ fq: '分期', dj: '趸交' }, detail?.payMode) },
{ label: '合同金额', value: displayValue(formatPresignMoney(detail?.contractAmount)) },
{ label: '到账金额', value: displayValue(formatPresignMoney(detail?.confirmedDepositAmount)) },
{ label: '订单状态', value: presignStatus(status), tag: true, tagType: presignStatusTone(status) },
{ label: '备注', value: detail?.remark || '—' },
{ label: '创建时间', value: displayValue(formatPresignDateTime(detail?.createTime)) }
],
raw: {},
cards: []
},
{
key: 'transfers',
title: '意向金转账',
fields: [],
raw: { transfers },
cards: transfers.map((transfer, index) => ({
key: String(transfer?.id || `transfer-${index}`),
title: transfer.title,
statusText: transfer.statusLabel,
statusTone: transfer.statusTone,
fields: [
{ label: '转账方式', value: dictionaryLabel(transferTypeLabels, transfer.transferType) },
{ label: '转账金额', value: transfer.transferAmount || '—' },
{ label: '转账时间', value: transfer.transferTime || '—' },
{
label: '转账附件',
type: 'attachments',
value: transfer.attachments?.length
? transfer.attachments
: transfer.transferAttachments || ''
},
{ label: '备注', value: transfer.remark || '—' }
]
}))
}
]
},
formSections: [
{
key: 'base', title: '预签信息', fields: [
{ key: 'orderDate', label: '预签日期', type: 'date' },
{ key: 'protocolNo', label: '协议编号' },
{ key: 'orderDate', label: '预签日期', type: 'date', valueFormat: 'YYYYMMDD' },
{ key: 'motherName', label: '客户姓名', required: true },
{ key: 'expectedDeliveryDate', label: '预产期', type: 'date' },
{ key: 'expectedDeliveryDate', label: '预产期', type: 'date', valueFormat: 'YYYYMMDD' },
{ key: 'regionId', label: '采集地区', type: 'select', optionKey: 'regions' },
{ key: 'hospitalId', label: '采集医院', type: 'select', optionKey: 'hospitals' },
{ key: 'sampleCollectTime', label: '采集时间', type: 'datetime' },