feat(mobile-oa-h5): inline payment detail transfers aligned with web dialog
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
c825c170e7
commit
d355cd0640
7 files changed
+272
-128
No files matched your search
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<section class="payment-detail-transfers">
|
||||
<div class="payment-detail-transfers__title">应缴批次</div>
|
||||
<van-empty
|
||||
v-if="!localSchedules.length"
|
||||
description="暂无应缴批次"
|
||||
image-size="48"
|
||||
/>
|
||||
<article
|
||||
v-for="(sch, index) in localSchedules"
|
||||
:key="sch.id || index"
|
||||
class="payment-detail-transfers__period"
|
||||
>
|
||||
<header class="payment-detail-transfers__head">
|
||||
<strong>
|
||||
第 {{ sch.scheduleNo || index + 1 }} 期 · 应缴 {{ formatMoney(sch.dueAmount) }} · {{ displayDate(sch.dueDate) }}
|
||||
</strong>
|
||||
<van-tag class="market-tag" :type="scheduleStatusTone(sch)" round>
|
||||
{{ scheduleStatusLabel(sch) }}
|
||||
</van-tag>
|
||||
</header>
|
||||
<div class="payment-detail-transfers__metrics">
|
||||
<div class="payment-detail-transfers__metric">
|
||||
<span>已缴</span>
|
||||
<b>{{ formatMoney(sch.paidAmount) }}</b>
|
||||
</div>
|
||||
<div class="payment-detail-transfers__metric">
|
||||
<span>欠缴</span>
|
||||
<b>{{ formatMoney(sch.arrearsAmount) }}</b>
|
||||
</div>
|
||||
</div>
|
||||
<ScheduleTransferEditor
|
||||
:schedule="sch"
|
||||
:transfer-types="transferTypes"
|
||||
:market-role="marketRole"
|
||||
:context-id="String(detail.id || '')"
|
||||
:disabled="false"
|
||||
@update:schedule="onScheduleUpdate(index, $event)"
|
||||
@changed="$emit('refresh')"
|
||||
/>
|
||||
</article>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { formatMoney } from '../money.js'
|
||||
import { resolveStatusTag, paymentScheduleStatus } from '../status-tag-registry.js'
|
||||
import ScheduleTransferEditor from './ScheduleTransferEditor.vue'
|
||||
|
||||
const props = defineProps({
|
||||
detail: { type: Object, required: true },
|
||||
marketRole: { type: String, required: true },
|
||||
transferTypes: { type: Array, default: () => [] }
|
||||
})
|
||||
|
||||
defineEmits(['refresh'])
|
||||
|
||||
const localSchedules = ref([])
|
||||
|
||||
watch(
|
||||
() => props.detail?.schedules,
|
||||
(schedules) => {
|
||||
localSchedules.value = (Array.isArray(schedules) ? schedules : []).map((row) => ({ ...row }))
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
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 scheduleStatusLabel(schedule) {
|
||||
return resolveStatusTag(paymentScheduleStatus, schedule?.scheduleStatus).label
|
||||
}
|
||||
|
||||
function scheduleStatusTone(schedule) {
|
||||
return resolveStatusTag(paymentScheduleStatus, schedule?.scheduleStatus).tagType
|
||||
}
|
||||
|
||||
function onScheduleUpdate(index, nextSchedule) {
|
||||
const next = [...localSchedules.value]
|
||||
next[index] = nextSchedule
|
||||
localSchedules.value = next
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.payment-detail-transfers {
|
||||
margin: 12px 0 0;
|
||||
}
|
||||
|
||||
.payment-detail-transfers__title {
|
||||
margin: 0 0 10px;
|
||||
padding: 0 2px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #1f2a26;
|
||||
}
|
||||
|
||||
.payment-detail-transfers__period {
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid #eef4f1;
|
||||
border-radius: 12px;
|
||||
background: #fafcfb;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.payment-detail-transfers__head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
background: #e9f9f2;
|
||||
}
|
||||
|
||||
.payment-detail-transfers__head strong {
|
||||
flex: 1;
|
||||
min-width: 140px;
|
||||
font-size: 13px;
|
||||
color: #1f2a26;
|
||||
}
|
||||
|
||||
.payment-detail-transfers__metrics {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px 12px;
|
||||
padding: 12px;
|
||||
border-bottom: 1px dashed #eef4f1;
|
||||
}
|
||||
|
||||
.payment-detail-transfers__metric span {
|
||||
display: block;
|
||||
color: #6b7c76;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.payment-detail-transfers__metric b {
|
||||
font-size: 13px;
|
||||
color: #1f2a26;
|
||||
}
|
||||
</style>
|
||||
@@ -463,7 +463,7 @@ test('market forms share attachment cards and preserve their existing value shap
|
||||
assert.match(presignForm, /createPendingMarketAttachment/)
|
||||
})
|
||||
|
||||
test('payment detail presents every web field and nested transfer field with Chinese labels', () => {
|
||||
test('payment detail presents summary overview fields with Chinese labels', () => {
|
||||
const sections = getMarketModule('payment').detailSections({
|
||||
protocolNo: 'P-1',
|
||||
motherName: '张三',
|
||||
@@ -498,24 +498,21 @@ test('payment detail presents every web field and nested transfer field with Chi
|
||||
}]
|
||||
})
|
||||
|
||||
assert.deepEqual(sections.map((section) => section.title), ['缴费概览', '应缴批次'])
|
||||
assert.deepEqual(sections.map((section) => section.title), ['缴费概览'])
|
||||
assert.equal(sections.find((section) => section.key === 'schedules'), undefined)
|
||||
assert.deepEqual(
|
||||
sections[0].fields.map((field) => field.label),
|
||||
['客户', '联系电话', '样本编码', '缴费方式', '合同额', '已缴 / 待缴', '整体进度', '协议状态', '缴费进度']
|
||||
)
|
||||
assert.equal(sections[1].cards[0].variant, 'schedule')
|
||||
assert.deepEqual(
|
||||
sections[1].cards[0].fields.map((field) => field.label),
|
||||
['应缴金额', '应缴日', '已缴金额', '欠缴金额']
|
||||
)
|
||||
const transfer = sections[1].cards[0].cards[0]
|
||||
assert.equal(transfer.variant, 'transfer')
|
||||
assert.equal(transfer.amount, '¥3,000.00')
|
||||
assert.match(transfer.meta, /微信转账/)
|
||||
assert.deepEqual(
|
||||
transfer.fields.map((field) => field.label),
|
||||
['备注', '财务备注', '转账凭证']
|
||||
)
|
||||
})
|
||||
|
||||
test('payment detail hosts PaymentDetailTransfersPanel and hides transfer bottom actions', () => {
|
||||
const detail = source('../views/market/MarketModuleDetail.vue')
|
||||
assert.match(detail, /PaymentDetailTransfersPanel/)
|
||||
assert.match(detail, /filterPaymentDetailBottomActions/)
|
||||
const panel = source('./components/PaymentDetailTransfersPanel.vue')
|
||||
assert.match(panel, /ScheduleTransferEditor/)
|
||||
assert.match(panel, /应缴批次/)
|
||||
})
|
||||
|
||||
test('payment cards reproduce the Web Excel columns and operation labels', () => {
|
||||
|
||||
@@ -27,7 +27,7 @@ test('payment chase renders payment and protocol payloads with their native deta
|
||||
})
|
||||
|
||||
assert.equal(paymentSections[0].key, 'summary')
|
||||
assert.equal(paymentSections[1].cards.length, 1)
|
||||
assert.equal(paymentSections.find((section) => section.key === 'schedules'), undefined)
|
||||
assert.equal(protocolSections[0].key, 'protocol')
|
||||
})
|
||||
|
||||
@@ -111,12 +111,16 @@ test('protocol adapters preserve nested detail sections', () => {
|
||||
'customer',
|
||||
'samples',
|
||||
'payment',
|
||||
'storageCert',
|
||||
'insurance',
|
||||
'refundRecords',
|
||||
'audits',
|
||||
'statusChange',
|
||||
'attachments'
|
||||
'attachments',
|
||||
'statusChange'
|
||||
])
|
||||
const protocolLabels = protocol.flatMap((section) => (section.fields || []).map((field) => field.label))
|
||||
assert.ok(!protocolLabels.includes('协议编号'))
|
||||
assert.ok(protocolLabels.includes('母亲姓名'))
|
||||
assert.ok(!protocolLabels.includes('新生儿评分(5分钟)'))
|
||||
assert.equal(protocol.at(-1).key, 'statusChange')
|
||||
})
|
||||
|
||||
test('attachment-bearing detail adapters expose typed values instead of path text', () => {
|
||||
@@ -135,14 +139,9 @@ test('attachment-bearing detail adapters expose typed values instead of path tex
|
||||
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'
|
||||
})
|
||||
assert.equal(payment.find((section) => section.key === 'schedules'), undefined)
|
||||
assert.equal(payment.length, 1)
|
||||
assert.equal(payment[0].key, 'summary')
|
||||
|
||||
const presign = getMarketModule('presign').detailSections({
|
||||
transfers: [{ transferAttachments: 'deposit-proof.png' }]
|
||||
@@ -217,7 +216,7 @@ test('payment detail recognizes the converted cell-storage protocol status', ()
|
||||
assert.equal(detail[0].fields.find((field) => field.label === '协议状态').value, '转细胞存储')
|
||||
})
|
||||
|
||||
test('payment detail presents real backend progress, schedule statuses, and combined pay mode', () => {
|
||||
test('payment detail presents real backend progress and combined pay mode in summary only', () => {
|
||||
const sections = getMarketModule('payment').detailSections({
|
||||
payMode: 'djfq',
|
||||
paymentProgress: '部分已缴',
|
||||
@@ -228,13 +227,10 @@ test('payment detail presents real backend progress, schedule statuses, and comb
|
||||
]
|
||||
})
|
||||
const summary = sections.find((section) => section.key === 'summary')
|
||||
const schedules = sections.find((section) => section.key === 'schedules').cards
|
||||
|
||||
assert.equal(sections.find((section) => section.key === 'schedules'), undefined)
|
||||
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('payment progress dfj aligns with web-admin samplePaymentProgress 待缴费', () => {
|
||||
@@ -278,13 +274,12 @@ test('protocol read-only history uses Web audit and void type labels', () => {
|
||||
|
||||
assert.equal(audits.cards[0].statusText, '审核退回')
|
||||
assert.equal(
|
||||
audits.cards[0].fields.find((field) => field.label === '审核状态').value,
|
||||
'审核退回'
|
||||
)
|
||||
assert.equal(
|
||||
status.fields.find((field) => field.label === '作废类型').value,
|
||||
'租户自定义作废'
|
||||
audits.cards[0].fields.find((field) => field.label === '审核状态'),
|
||||
undefined
|
||||
)
|
||||
assert.equal(status.fields.length, 1)
|
||||
assert.equal(status.fields[0].value, '暂无状态变更记录')
|
||||
assert.equal(status.cards.length, 0)
|
||||
})
|
||||
|
||||
test('donation detail shows void type and preserves unknown original values', () => {
|
||||
@@ -292,15 +287,19 @@ test('donation detail shows void type and preserves unknown original values', ()
|
||||
source: 'research_donation',
|
||||
basic: {
|
||||
donationStatus: 'yzf',
|
||||
voidTime: '2026-10-01 09:30:00',
|
||||
voidDesc: '客户申请',
|
||||
voidType: 'legacy-unknown',
|
||||
voidTypeLabel: 'legacy-unknown'
|
||||
}
|
||||
})
|
||||
const status = sections.find((section) => section.key === 'statusChange')
|
||||
|
||||
assert.equal(status.cards.length, 1)
|
||||
assert.equal(status.cards[0].statusText, '作废')
|
||||
assert.equal(
|
||||
status.fields.find((field) => field.label === '作废类型').value,
|
||||
'legacy-unknown'
|
||||
status.cards[0].fields.find((field) => field.label === '变更说明').value,
|
||||
'作废类型:legacy-unknown;客户申请'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -342,7 +341,7 @@ test('detail adapters hide unresolved references and dictionary codes but preser
|
||||
...(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('P-RAW-1'))
|
||||
assert.ok(values.includes('S-RAW-1'))
|
||||
assert.ok(values.includes('未知人员'))
|
||||
assert.ok(values.includes('未知'))
|
||||
@@ -360,6 +359,7 @@ test('detail adapters hide unresolved references and dictionary codes but preser
|
||||
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, '未知')
|
||||
assert.ok(!freight[1].cards[0].fields.some((field) => field.label === '协议单号'))
|
||||
|
||||
const presign = getMarketModule('presign').detailSections({
|
||||
protocolNo: 'P-RAW-2', regionId: 'region-raw', regionName: 'region-raw',
|
||||
@@ -368,7 +368,7 @@ test('detail adapters hide unresolved references and dictionary codes but preser
|
||||
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.ok(!presign[0].fields.some((field) => field.label === '协议编号'))
|
||||
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, '未知')
|
||||
@@ -377,14 +377,23 @@ test('detail adapters hide unresolved references and dictionary codes but preser
|
||||
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' }] }]
|
||||
protocolNo: 'P-RAW-3',
|
||||
motherName: '客户甲',
|
||||
sampleCode: 'S-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 paymentLabels = payment.flatMap((section) => (section.fields || []).map((field) => field.label))
|
||||
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'))
|
||||
assert.ok(!paymentLabels.includes('协议号'))
|
||||
assert.ok(!paymentValues.includes('P-RAW-3'))
|
||||
assert.ok(paymentValues.includes('客户甲'))
|
||||
assert.ok(paymentValues.includes('S-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}`)
|
||||
}
|
||||
|
||||
@@ -2,48 +2,19 @@ import { formatMoney } from '../money.js'
|
||||
import {
|
||||
resolveStatusTag,
|
||||
samplePaymentProgress,
|
||||
paymentScheduleStatus,
|
||||
payStatus,
|
||||
payMode,
|
||||
protocolStatus,
|
||||
donationStatus
|
||||
} from '../status-tag-registry.js'
|
||||
|
||||
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) {
|
||||
return formatMoney(value)
|
||||
}
|
||||
|
||||
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 progressTag(code) {
|
||||
return resolveStatusTag(samplePaymentProgress, code)
|
||||
}
|
||||
|
||||
function scheduleTag(code) {
|
||||
return resolveStatusTag(paymentScheduleStatus, code)
|
||||
}
|
||||
|
||||
function transferPayTag(code) {
|
||||
return resolveStatusTag(payStatus, code)
|
||||
}
|
||||
|
||||
function payModeTag(code) {
|
||||
return resolveStatusTag(payMode, code)
|
||||
}
|
||||
@@ -57,47 +28,6 @@ function protocolStatusTag(code) {
|
||||
return resolveStatusTag(protocolStatus, key)
|
||||
}
|
||||
|
||||
function transferCard(transfer, index) {
|
||||
const pay = transferPayTag(transfer?.payStatus)
|
||||
const transferType = label(TRANSFER_TYPE, transfer?.transferType)
|
||||
const transferTime = transfer?.transferTime || '—'
|
||||
return {
|
||||
key: String(transfer?.id || `transfer-${index}`),
|
||||
title: `转账笔 ${index + 1}`,
|
||||
variant: 'transfer',
|
||||
transferIndex: index + 1,
|
||||
amount: money(transfer?.transferAmount),
|
||||
meta: `${transferType} · ${transferTime}`,
|
||||
statusText: pay.label,
|
||||
statusTone: pay.tagType,
|
||||
fields: [
|
||||
{ label: '备注', value: transfer?.remark || '—' },
|
||||
{ label: '财务备注', value: transfer?.financeRemark || '—' },
|
||||
{ label: '转账凭证', type: 'attachments', value: transfer?.transferAttachments || '' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleCard(schedule, index) {
|
||||
const status = scheduleTag(schedule?.scheduleStatus)
|
||||
const transfers = schedule?.transfers || []
|
||||
return {
|
||||
key: String(schedule?.id || `schedule-${index}`),
|
||||
title: `第 ${schedule?.scheduleNo || index + 1} 期`,
|
||||
variant: 'schedule',
|
||||
cardsLabel: '转账记录',
|
||||
statusText: status.label,
|
||||
statusTone: status.tagType,
|
||||
fields: [
|
||||
{ label: '应缴金额', value: money(schedule?.dueAmount), money: true },
|
||||
{ label: '应缴日', value: displayDate(schedule?.dueDate) },
|
||||
{ label: '已缴金额', value: money(schedule?.paidAmount), money: true },
|
||||
{ label: '欠缴金额', value: money(schedule?.arrearsAmount), money: true }
|
||||
],
|
||||
cards: transfers.map(transferCard)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
key: 'payment',
|
||||
resource: 'payments',
|
||||
@@ -139,7 +69,6 @@ export default {
|
||||
key: 'summary',
|
||||
title: '缴费概览',
|
||||
fields: [
|
||||
{ label: '协议号', value: detail?.protocolNo || '—' },
|
||||
{ label: '客户', value: detail?.motherName || '—' },
|
||||
{ label: '联系电话', value: detail?.motherPhone || '—' },
|
||||
{ label: '样本编码', value: detail?.sampleCode || '—' },
|
||||
@@ -150,12 +79,6 @@ export default {
|
||||
{ label: '协议状态', value: protocol.label, tag: true, tagType: protocol.tagType },
|
||||
{ label: '缴费进度', value: progress.label, tag: true, tagType: progress.tagType }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'schedules',
|
||||
title: '应缴批次',
|
||||
fields: [],
|
||||
cards: (detail?.schedules || []).map(scheduleCard)
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
/** 缴费信息入口进入协议详情时的只读压制 */
|
||||
const PROTOCOL_WRITE_CODES = new Set(['edit', 'delete', 'create'])
|
||||
|
||||
/** 缴费详情底栏隐藏的转账动作(改由 PaymentDetailTransfersPanel 内联办理) */
|
||||
const PAYMENT_TRANSFER_BOTTOM_CODES = new Set([
|
||||
'register-transfer',
|
||||
'edit-transfer',
|
||||
'delete-payment-transfer',
|
||||
'submit',
|
||||
'withdraw'
|
||||
])
|
||||
|
||||
export function isPaymentEntryView(query) {
|
||||
return String(query?.from || '') === 'payment'
|
||||
}
|
||||
@@ -16,3 +25,9 @@ export function filterProtocolActionsForPaymentEntry(actions) {
|
||||
(action) => !PROTOCOL_WRITE_CODES.has(actionCode(action))
|
||||
)
|
||||
}
|
||||
|
||||
export function filterPaymentDetailBottomActions(actions) {
|
||||
return (Array.isArray(actions) ? actions : []).filter(
|
||||
(action) => !PAYMENT_TRANSFER_BOTTOM_CODES.has(actionCode(action))
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,8 @@ import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import {
|
||||
isPaymentEntryView,
|
||||
filterProtocolActionsForPaymentEntry
|
||||
filterProtocolActionsForPaymentEntry,
|
||||
filterPaymentDetailBottomActions
|
||||
} from './payment-entry.js'
|
||||
|
||||
test('isPaymentEntryView detects from=payment', () => {
|
||||
@@ -26,3 +27,24 @@ test('filterProtocolActionsForPaymentEntry hides edit and delete', () => {
|
||||
[{ code: 'view' }]
|
||||
)
|
||||
})
|
||||
|
||||
test('filterPaymentDetailBottomActions hides transfer bottom actions', () => {
|
||||
assert.deepEqual(
|
||||
filterPaymentDetailBottomActions([
|
||||
'view',
|
||||
'register-transfer',
|
||||
'edit-transfer',
|
||||
'delete-payment-transfer',
|
||||
'submit',
|
||||
'withdraw'
|
||||
]),
|
||||
['view']
|
||||
)
|
||||
assert.deepEqual(
|
||||
filterPaymentDetailBottomActions([
|
||||
{ code: 'register-transfer', scheduleId: 's1' },
|
||||
{ code: 'view' }
|
||||
]),
|
||||
[{ code: 'view' }]
|
||||
)
|
||||
})
|
||||
@@ -29,6 +29,13 @@
|
||||
:cards="section.cards"
|
||||
:raw="section.raw || section"
|
||||
/>
|
||||
<PaymentDetailTransfersPanel
|
||||
v-if="context.module === 'payment' && detail"
|
||||
:detail="detail"
|
||||
:market-role="context.marketRole"
|
||||
:transfer-types="paymentTransferTypes"
|
||||
@refresh="loadDetail"
|
||||
/>
|
||||
</template>
|
||||
<van-empty v-else description="暂无详情数据" />
|
||||
<template #bottom>
|
||||
@@ -50,6 +57,7 @@ import {
|
||||
deletePresignOrder,
|
||||
deletePresignTransfer,
|
||||
fetchMarketDetail,
|
||||
fetchMarketFormOptions,
|
||||
performMarketAction,
|
||||
submitPresignTransfer,
|
||||
withdrawPresignTransfer
|
||||
@@ -57,7 +65,8 @@ import {
|
||||
import { collectAllowedActions } from '../../market/action-policy.js'
|
||||
import {
|
||||
isPaymentEntryView,
|
||||
filterProtocolActionsForPaymentEntry
|
||||
filterProtocolActionsForPaymentEntry,
|
||||
filterPaymentDetailBottomActions
|
||||
} from '../../market/payment-entry.js'
|
||||
import { marketErrorMessage } from '../../market/market-error.js'
|
||||
import { getMarketModule } from '../../market/module-registry.js'
|
||||
@@ -79,6 +88,7 @@ import {
|
||||
import MarketDetailSection from '../../market/components/MarketDetailSection.vue'
|
||||
import MarketBottomBar from '../../market/components/MarketBottomBar.vue'
|
||||
import MarketPageShell from '../../market/components/MarketPageShell.vue'
|
||||
import PaymentDetailTransfersPanel from '../../market/components/PaymentDetailTransfersPanel.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -94,9 +104,13 @@ const id = computed(() => String(route.params.id || '').trim())
|
||||
const moduleTitle = computed(() => MARKET_MODULE_TITLES[context.value.module])
|
||||
const roleTitle = computed(() => MARKET_ROLE_TITLES[context.value.marketRole])
|
||||
const moduleContract = computed(() => getMarketModule(context.value.module))
|
||||
const summarySubtitle = computed(
|
||||
() => detail.value?.subtitle || detail.value?.code || detail.value?.basic?.protocolNo || detail.value?.basic?.donationNo || detail.value?.protocolNo || '业务详情'
|
||||
)
|
||||
const summarySubtitle = computed(() => {
|
||||
const current = detail.value || {}
|
||||
if (context.value.module === 'protocol') {
|
||||
return current.subtitle || '业务详情'
|
||||
}
|
||||
return current.subtitle || current.code || current.basic?.donationNo || '业务详情'
|
||||
})
|
||||
const summaryTitle = computed(
|
||||
() => detail.value?.motherName || detail.value?.customer?.motherName || detail.value?.card?.motherName || detail.value?.title || detail.value?.name || detail.value?.code || moduleTitle.value
|
||||
)
|
||||
@@ -109,6 +123,7 @@ const statusTone = computed(() => resolveStatusTone(
|
||||
))
|
||||
const actionPending = ref(false)
|
||||
const pendingAction = ref('')
|
||||
const paymentTransferTypes = ref([])
|
||||
const allowedActions = computed(() => {
|
||||
const raw = collectAllowedActions(
|
||||
context.value.module,
|
||||
@@ -118,6 +133,9 @@ const allowedActions = computed(() => {
|
||||
if (context.value.module === 'protocol' && isPaymentEntryView(route.query)) {
|
||||
return filterProtocolActionsForPaymentEntry(raw)
|
||||
}
|
||||
if (context.value.module === 'payment') {
|
||||
return filterPaymentDetailBottomActions(raw)
|
||||
}
|
||||
return raw
|
||||
})
|
||||
const sections = computed(() => {
|
||||
@@ -323,6 +341,20 @@ async function loadDetail() {
|
||||
: undefined
|
||||
})
|
||||
detailState.value = finishMarketDetailRequest(detailState.value, requestId, result)
|
||||
if (requestContext.module === 'payment') {
|
||||
try {
|
||||
const options = await fetchMarketFormOptions(requestContext, {
|
||||
businessId: requestIdValue
|
||||
}, { signal: abortController.signal })
|
||||
if (requestId !== requestSequence) return
|
||||
paymentTransferTypes.value = options?.transferTypes || []
|
||||
} catch (optionsError) {
|
||||
if (optionsError?.name === 'AbortError') return
|
||||
paymentTransferTypes.value = []
|
||||
}
|
||||
} else {
|
||||
paymentTransferTypes.value = []
|
||||
}
|
||||
} catch (error) {
|
||||
if (error?.name === 'AbortError') return
|
||||
detailState.value = failMarketDetailRequest(
|
||||
|
||||
Reference in new issue
Block a user