feat(mobile-oa-h5): restyle payment schedule transfers as amount-first cards
Render schedule cards with metric grids and white transfer cards (amount, method·time, notes) in payment and protocol detail views. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
5ce18e56a0
commit
9d7a72673d
7 files changed
+404
-104
No files matched your search
@@ -1,54 +1,173 @@
|
||||
<template>
|
||||
<article class="market-detail-section__nested-card">
|
||||
<strong>{{ card.title }}</strong>
|
||||
<van-tag
|
||||
v-if="card.statusText"
|
||||
class="market-tag"
|
||||
:type="card.statusTone"
|
||||
round
|
||||
:title="card.statusText"
|
||||
>
|
||||
{{ card.statusText }}
|
||||
</van-tag>
|
||||
<div
|
||||
v-for="field in card.fields"
|
||||
:key="field.label"
|
||||
class="market-detail-section__nested-field"
|
||||
:class="{ 'is-attachments': field.type === 'attachments' }"
|
||||
>
|
||||
<small>{{ field.label }}</small>
|
||||
<MarketAttachmentCards
|
||||
v-if="field.type === 'attachments'"
|
||||
:model-value="field.value"
|
||||
:editable="false"
|
||||
/>
|
||||
<van-tag
|
||||
v-else-if="field.tag"
|
||||
class="market-tag"
|
||||
:type="field.tagType || 'primary'"
|
||||
round
|
||||
:title="field.value"
|
||||
<article
|
||||
class="market-detail-section__nested-card"
|
||||
:class="{
|
||||
'is-schedule': card.variant === 'schedule',
|
||||
'is-transfer': card.variant === 'transfer'
|
||||
}"
|
||||
>
|
||||
<template v-if="card.variant === 'transfer'">
|
||||
<div class="market-detail-section__transfer-head">
|
||||
<span class="market-detail-section__transfer-index">{{ card.transferIndex || 1 }}</span>
|
||||
<div class="market-detail-section__transfer-main">
|
||||
<strong class="market-detail-section__transfer-amount">{{ card.amount || '—' }}</strong>
|
||||
<small v-if="card.meta" class="market-detail-section__transfer-meta">{{ card.meta }}</small>
|
||||
</div>
|
||||
<van-tag
|
||||
v-if="card.statusText"
|
||||
class="market-tag"
|
||||
:type="card.statusTone"
|
||||
round
|
||||
:title="card.statusText"
|
||||
>
|
||||
{{ card.statusText }}
|
||||
</van-tag>
|
||||
</div>
|
||||
<div
|
||||
v-if="detailFields.length"
|
||||
class="market-detail-section__transfer-grid"
|
||||
>
|
||||
{{ field.value }}
|
||||
<div
|
||||
v-for="field in detailFields"
|
||||
:key="field.label"
|
||||
class="market-detail-section__transfer-metric"
|
||||
:class="{ 'is-wide': field.type === 'attachments' || field.wide }"
|
||||
>
|
||||
<span>{{ field.label }}</span>
|
||||
<MarketAttachmentCards
|
||||
v-if="field.type === 'attachments'"
|
||||
:model-value="field.value"
|
||||
:editable="false"
|
||||
/>
|
||||
<b v-else>{{ field.value }}</b>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="attachmentFields.length"
|
||||
class="market-detail-section__transfer-voucher"
|
||||
>
|
||||
<div
|
||||
v-for="field in attachmentFields"
|
||||
:key="field.label"
|
||||
class="market-detail-section__nested-field is-attachments"
|
||||
>
|
||||
<small>{{ field.label }}</small>
|
||||
<MarketAttachmentCards
|
||||
:model-value="field.value"
|
||||
:editable="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="card.variant === 'schedule'">
|
||||
<header class="market-detail-section__schedule-head">
|
||||
<strong>{{ card.title }}</strong>
|
||||
<van-tag
|
||||
v-if="card.statusText"
|
||||
class="market-tag"
|
||||
:type="card.statusTone"
|
||||
round
|
||||
:title="card.statusText"
|
||||
>
|
||||
{{ card.statusText }}
|
||||
</van-tag>
|
||||
</header>
|
||||
<div class="market-detail-section__schedule-metrics">
|
||||
<div
|
||||
v-for="field in metricFields"
|
||||
:key="field.label"
|
||||
class="market-detail-section__transfer-metric"
|
||||
>
|
||||
<span>{{ field.label }}</span>
|
||||
<b :class="{ 'is-money': field.money }">{{ field.value }}</b>
|
||||
</div>
|
||||
</div>
|
||||
<div class="market-detail-section__transfer-list">
|
||||
<div class="market-detail-section__transfer-list-label">
|
||||
{{ card.cardsLabel || '转账记录' }}({{ card.cards?.length || 0 }})
|
||||
</div>
|
||||
<div v-if="card.cards?.length" class="market-detail-section__subcards">
|
||||
<MarketDetailNestedCard
|
||||
v-for="child in card.cards"
|
||||
:key="child.key"
|
||||
:card="child"
|
||||
/>
|
||||
</div>
|
||||
<p v-else class="market-detail-section__transfer-empty">暂无转账记录</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<strong>{{ card.title }}</strong>
|
||||
<van-tag
|
||||
v-if="card.statusText"
|
||||
class="market-tag"
|
||||
:type="card.statusTone"
|
||||
round
|
||||
:title="card.statusText"
|
||||
>
|
||||
{{ card.statusText }}
|
||||
</van-tag>
|
||||
<span v-else>{{ field.value }}</span>
|
||||
</div>
|
||||
<div v-if="card.cards?.length" class="market-detail-section__subcards">
|
||||
<MarketDetailNestedCard
|
||||
v-for="child in card.cards"
|
||||
:key="child.key"
|
||||
:card="child"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-for="field in card.fields"
|
||||
:key="field.label"
|
||||
class="market-detail-section__nested-field"
|
||||
:class="{ 'is-attachments': field.type === 'attachments' }"
|
||||
>
|
||||
<small>{{ field.label }}</small>
|
||||
<MarketAttachmentCards
|
||||
v-if="field.type === 'attachments'"
|
||||
:model-value="field.value"
|
||||
:editable="false"
|
||||
/>
|
||||
<van-tag
|
||||
v-else-if="field.tag"
|
||||
class="market-tag"
|
||||
:type="field.tagType || 'primary'"
|
||||
round
|
||||
:title="field.value"
|
||||
>
|
||||
{{ field.value }}
|
||||
</van-tag>
|
||||
<span v-else>{{ field.value }}</span>
|
||||
</div>
|
||||
<div v-if="card.cards?.length" class="market-detail-section__subcards">
|
||||
<MarketDetailNestedCard
|
||||
v-for="child in card.cards"
|
||||
:key="child.key"
|
||||
:card="child"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import MarketAttachmentCards from './MarketAttachmentCards.vue'
|
||||
|
||||
defineOptions({ name: 'MarketDetailNestedCard' })
|
||||
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
card: { type: Object, required: true }
|
||||
})
|
||||
|
||||
const metricFields = computed(() =>
|
||||
(props.card.fields || []).filter((field) => field.type !== 'attachments' && !field.tag)
|
||||
)
|
||||
|
||||
const detailFields = computed(() =>
|
||||
(props.card.fields || []).filter((field) => field.type !== 'attachments')
|
||||
)
|
||||
|
||||
const attachmentFields = computed(() =>
|
||||
(props.card.fields || []).filter((field) => {
|
||||
if (field.type !== 'attachments') return false
|
||||
const raw = field.value
|
||||
if (raw == null || raw === '' || raw === '—') return false
|
||||
if (Array.isArray(raw) && raw.length === 0) return false
|
||||
return true
|
||||
})
|
||||
)
|
||||
</script>
|
||||
@@ -58,7 +58,9 @@ function normalizeDetailField(field, index) {
|
||||
value: type ? field?.value : String(field?.value ?? '-'),
|
||||
type,
|
||||
tag: Boolean(field?.tag),
|
||||
tagType: field?.tagType
|
||||
tagType: field?.tagType,
|
||||
money: Boolean(field?.money),
|
||||
wide: Boolean(field?.wide)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +68,11 @@ function normalizeCard(card, index, prefix = 'card') {
|
||||
return {
|
||||
key: String(card?.key || `${prefix}-${index}`),
|
||||
title: String(card?.title || `明细 ${index + 1}`),
|
||||
variant: String(card?.variant || ''),
|
||||
transferIndex: Number(card?.transferIndex) || index + 1,
|
||||
amount: card?.amount == null || card?.amount === '' ? '' : String(card.amount),
|
||||
meta: card?.meta == null || card?.meta === '' ? '' : String(card.meta),
|
||||
cardsLabel: String(card?.cardsLabel || ''),
|
||||
statusText: String(card?.statusText || card?.statusLabel || ''),
|
||||
statusTone: resolveStatusTone(
|
||||
card?.statusText || card?.statusLabel,
|
||||
|
||||
@@ -427,13 +427,18 @@ test('payment detail presents every web field and nested transfer field with Chi
|
||||
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(
|
||||
sections[1].cards[0].cards[0].fields.map((field) => field.label),
|
||||
['转账方式', '转账金额', '转账时间', '转账凭证', '备注', '财务备注']
|
||||
transfer.fields.map((field) => field.label),
|
||||
['备注', '财务备注', '转账凭证']
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -41,36 +41,42 @@ function displayDate(value) {
|
||||
|
||||
function transferCard(transfer, index) {
|
||||
const statusText = label(PAY_STATUS, 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,
|
||||
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 || '—' },
|
||||
{ label: '财务备注', value: transfer?.financeRemark || '—' }
|
||||
{ label: '财务备注', value: transfer?.financeRemark || '—' },
|
||||
{ label: '转账凭证', type: 'attachments', value: transfer?.transferAttachments || '' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleCard(schedule, index) {
|
||||
const statusText = label(SCHEDULE_STATUS, schedule?.scheduleStatus)
|
||||
const transfers = schedule?.transfers || []
|
||||
return {
|
||||
key: String(schedule?.id || `schedule-${index}`),
|
||||
title: `第 ${schedule?.scheduleNo || index + 1} 期`,
|
||||
variant: 'schedule',
|
||||
cardsLabel: '转账记录',
|
||||
statusText,
|
||||
statusTone: statusText === '已缴清' ? 'success' : statusText === '部分到账' ? 'primary' : 'warning',
|
||||
fields: [
|
||||
{ label: '应缴金额', value: money(schedule?.dueAmount) },
|
||||
{ label: '应缴金额', value: money(schedule?.dueAmount), money: true },
|
||||
{ label: '应缴日', value: displayDate(schedule?.dueDate) },
|
||||
{ label: '已缴金额', value: money(schedule?.paidAmount) },
|
||||
{ label: '欠缴金额', value: money(schedule?.arrearsAmount) }
|
||||
{ label: '已缴金额', value: money(schedule?.paidAmount), money: true },
|
||||
{ label: '欠缴金额', value: money(schedule?.arrearsAmount), money: true }
|
||||
],
|
||||
cards: (schedule?.transfers || []).map(transferCard)
|
||||
cards: transfers.map(transferCard)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,9 +89,13 @@ export default {
|
||||
},
|
||||
listParams: ({ status } = {}) => ({ payMode: status || '' }),
|
||||
statusOptions: [{ text: '全部缴费模式', value: '' }],
|
||||
filterFields: [
|
||||
// 缴费列表用搜索下方 van-dropdown-menu(对齐预签约/运费),不走筛选弹层
|
||||
filterFields: [],
|
||||
inlineDropdownFilters: [
|
||||
{
|
||||
key: 'payMode', label: '缴费方式', type: 'select', options: [
|
||||
key: 'payMode',
|
||||
allText: '全部缴费方式',
|
||||
options: [
|
||||
{ text: '趸交', value: 'dj' },
|
||||
{ text: '分期', value: 'fq' },
|
||||
{ text: '分改趸', value: 'fgd' },
|
||||
@@ -93,13 +103,14 @@ export default {
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'progressStatus', label: '缴费进度', type: 'select', options: [
|
||||
key: 'progressStatus',
|
||||
allText: '全部缴费进度',
|
||||
options: [
|
||||
{ text: '待缴清', value: 'dfj' },
|
||||
{ text: '部分已缴', value: 'bfyj' },
|
||||
{ text: '已缴清', value: 'yjq' }
|
||||
]
|
||||
},
|
||||
{ key: 'dueDateRange', label: '应缴费时间', type: 'date-range' }
|
||||
}
|
||||
],
|
||||
detailSections(detail) {
|
||||
const progress = label(PAYMENT_PROGRESS, detail?.progressStatus || detail?.paymentProgress)
|
||||
|
||||
@@ -25,6 +25,12 @@ const PAYMENT_STATUS_LABELS = Object.freeze({
|
||||
dfj: '已付款', jfqrz: '付款确认中', yjf: '付款完成',
|
||||
tfqrz: '退费确认中', ytf: '已退费'
|
||||
})
|
||||
const TRANSFER_TYPE_LABELS = Object.freeze({
|
||||
wxzz: '微信转账', wx: '微信转账', wechat: '微信转账',
|
||||
zzbzz: '支付宝转账', zfb: '支付宝转账', alipay: '支付宝转账',
|
||||
yhkzz: '银行卡转账', bank: '银行卡转账',
|
||||
xj: '现金', cash: '现金', lszz: '乐刷'
|
||||
})
|
||||
const SCHEDULE_STATUS_LABELS = Object.freeze({ open: '待缴清', cleared: '已缴清', void: '已作废' })
|
||||
|
||||
function value(source, key) {
|
||||
@@ -79,7 +85,11 @@ function transferAttachmentValue(transfer) {
|
||||
}
|
||||
|
||||
function transferTypeText(transfer) {
|
||||
return String(transfer?.transferTypeLabel || transfer?.transferType || '').trim() || '—'
|
||||
const backendLabel = String(transfer?.transferTypeLabel || '').trim()
|
||||
if (backendLabel) return backendLabel
|
||||
const code = String(transfer?.transferType || '').trim()
|
||||
if (!code) return '—'
|
||||
return TRANSFER_TYPE_LABELS[code] || (/[㐀-鿿]/u.test(code) ? code : code)
|
||||
}
|
||||
|
||||
function namedItems(items) {
|
||||
@@ -293,33 +303,40 @@ function sampleLifecycleCards(item, source) {
|
||||
return cards
|
||||
}
|
||||
|
||||
function paymentTransferCard(transfer, transferIndex) {
|
||||
const statusText = label(PAYMENT_STATUS_LABELS, transfer?.payStatus)
|
||||
const transferType = transferTypeText(transfer)
|
||||
const transferTime = value(transfer, 'transferTime')
|
||||
return {
|
||||
key: String(transfer?.id || `transfer-${transferIndex}`),
|
||||
title: `转账 ${transferIndex + 1}`,
|
||||
variant: 'transfer',
|
||||
transferIndex: transferIndex + 1,
|
||||
amount: value(transfer, 'transferAmount'),
|
||||
meta: `${transferType} · ${transferTime}`,
|
||||
statusText,
|
||||
fields: [
|
||||
{ label: '备注', value: value(transfer, 'remark') },
|
||||
{ label: '财务备注', value: value(transfer, 'financeRemark') },
|
||||
{ label: '转账凭证', value: transferAttachmentValue(transfer), type: 'attachments' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function paymentScheduleCards(pay) {
|
||||
return (pay?.schedules || []).map((schedule, scheduleIndex) => ({
|
||||
key: String(schedule?.id || `schedule-${scheduleIndex}`),
|
||||
title: `第 ${schedule?.scheduleNo || scheduleIndex + 1} 期`,
|
||||
variant: 'schedule',
|
||||
cardsLabel: '转账记录',
|
||||
statusText: label(SCHEDULE_STATUS_LABELS, schedule?.scheduleStatus),
|
||||
fields: [
|
||||
{ label: '期次', value: value(schedule, 'scheduleNo') },
|
||||
{ label: '应缴金额', value: value(schedule, 'dueAmount') },
|
||||
{ label: '实缴金额', value: value(schedule, 'paidAmount') },
|
||||
{ label: '欠缴金额', value: value(schedule, 'arrearsAmount') },
|
||||
{ label: '应缴金额', value: value(schedule, 'dueAmount'), money: true },
|
||||
{ label: '应缴日期', value: value(schedule, 'dueDate') },
|
||||
{ label: '状态', value: label(SCHEDULE_STATUS_LABELS, schedule?.scheduleStatus), tag: true }
|
||||
{ label: '实缴金额', value: value(schedule, 'paidAmount'), money: true },
|
||||
{ label: '欠缴金额', value: value(schedule, 'arrearsAmount'), money: true }
|
||||
],
|
||||
cards: (schedule?.transfers || []).map((transfer, transferIndex) => ({
|
||||
key: String(transfer?.id || `transfer-${transferIndex}`),
|
||||
title: `转账 ${transferIndex + 1}`,
|
||||
statusText: label(PAYMENT_STATUS_LABELS, transfer?.payStatus),
|
||||
fields: [
|
||||
{ label: '转账方式', value: transferTypeText(transfer) },
|
||||
{ label: '转账金额', value: value(transfer, 'transferAmount') },
|
||||
{ label: '转账时间', value: value(transfer, 'transferTime') },
|
||||
{ label: '转账凭证', value: transferAttachmentValue(transfer), type: 'attachments' },
|
||||
{ label: '备注', value: value(transfer, 'remark') },
|
||||
{ label: '财务备注', value: value(transfer, 'financeRemark') },
|
||||
{ label: '状态', value: label(PAYMENT_STATUS_LABELS, transfer?.payStatus), tag: true }
|
||||
]
|
||||
}))
|
||||
cards: (schedule?.transfers || []).map(paymentTransferCard)
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -327,29 +344,21 @@ function legacyPaymentDetailCards(payment, details) {
|
||||
return groupLegacyPaymentPeriods({ payment, details }).map((period, index) => ({
|
||||
key: `legacy-payment-${period.scheduleNo || index + 1}`,
|
||||
title: `第 ${period.scheduleNo || index + 1} 期`,
|
||||
variant: 'schedule',
|
||||
cardsLabel: '转账记录',
|
||||
statusText: label(SCHEDULE_STATUS_LABELS, period.scheduleStatus),
|
||||
fields: [
|
||||
{ label: '期次', value: String(period.scheduleNo || index + 1) },
|
||||
{ label: '应缴金额', value: period.dueAmount == null ? '—' : String(period.dueAmount) },
|
||||
{ label: '确认实缴金额', value: String(period.paidAmount) },
|
||||
{ label: '欠缴金额', value: period.arrearsAmount == null ? '—' : String(period.arrearsAmount) },
|
||||
{ label: '应缴金额', value: period.dueAmount == null ? '—' : String(period.dueAmount), money: true },
|
||||
{ label: '应缴日期', value: period.dueDate || '—' },
|
||||
{ label: '期状态', value: label(SCHEDULE_STATUS_LABELS, period.scheduleStatus), tag: true }
|
||||
{ label: '确认实缴金额', value: String(period.paidAmount), money: true },
|
||||
{ label: '欠缴金额', value: period.arrearsAmount == null ? '—' : String(period.arrearsAmount), money: true }
|
||||
],
|
||||
cards: period.transfers.map((detail, transferIndex) => ({
|
||||
key: String(detail?.id || `legacy-transfer-${period.scheduleNo}-${transferIndex}`),
|
||||
title: `转账 ${transferIndex + 1}`,
|
||||
statusText: label(PAYMENT_STATUS_LABELS, detail?.payStatus),
|
||||
fields: [
|
||||
{ label: '转账方式', value: transferTypeText(detail) },
|
||||
{ label: '转账金额', value: value(detail, 'transferAmount') },
|
||||
{ label: '转账时间', value: value(detail, 'transferTime') },
|
||||
{ label: '转账凭证', value: transferAttachmentValue(detail), type: 'attachments' },
|
||||
{ label: '备注', value: value(detail, 'remark') },
|
||||
{ label: '财务备注', value: value(detail, 'financeRemark') },
|
||||
{ label: '状态', value: label(PAYMENT_STATUS_LABELS, detail?.payStatus), tag: true }
|
||||
]
|
||||
}))
|
||||
cards: period.transfers.map((detail, transferIndex) =>
|
||||
paymentTransferCard(
|
||||
{ ...detail, id: detail?.id || `legacy-transfer-${period.scheduleNo}-${transferIndex}` },
|
||||
transferIndex
|
||||
)
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -138,13 +138,15 @@ test('v1 protocol detail renders readonly payment details with backend transfer
|
||||
const payment = sections.find((section) => section.key === 'payment')
|
||||
assert.equal(payment.cards.length, 1)
|
||||
assert.equal(payment.cards[0].cards.length, 2)
|
||||
assert.equal(payment.cards[0].variant, 'schedule')
|
||||
assert.equal(payment.cards[0].fields.find((field) => field.label === '确认实缴金额').value, '600.00')
|
||||
assert.equal(payment.cards[0].fields.find((field) => field.label === '欠缴金额').value, '400.00')
|
||||
assert.equal(payment.cards[0].cards[0].fields.find((field) => field.label === '转账方式').value, '微信支付')
|
||||
for (const expected of ['期次', '应缴金额', '确认实缴金额', '欠缴金额', '应缴日期', '期状态']) {
|
||||
assert.match(payment.cards[0].cards[0].meta, /微信支付/)
|
||||
assert.equal(payment.cards[0].cards[0].amount, '600')
|
||||
for (const expected of ['应缴金额', '确认实缴金额', '欠缴金额', '应缴日期']) {
|
||||
assert.ok(payment.cards[0].fields.some((field) => field.label === expected), `missing ${expected}`)
|
||||
}
|
||||
for (const expected of ['转账方式', '转账金额', '转账时间', '转账凭证', '备注', '财务备注', '状态']) {
|
||||
for (const expected of ['转账凭证', '备注', '财务备注']) {
|
||||
assert.ok(payment.cards[0].cards[0].fields.some((field) => field.label === expected), `missing transfer ${expected}`)
|
||||
}
|
||||
})
|
||||
@@ -206,11 +208,15 @@ test('protocol detail exposes complete deposit and installment transfer fields',
|
||||
])
|
||||
const payment = sections.find((section) => section.key === 'payment').cards[0]
|
||||
const schedule = payment.cards[0]
|
||||
assert.equal(schedule.variant, 'schedule')
|
||||
assert.deepEqual(schedule.fields.map((field) => field.label), [
|
||||
'期次', '应缴金额', '实缴金额', '欠缴金额', '应缴日期', '状态'
|
||||
'应缴金额', '应缴日期', '实缴金额', '欠缴金额'
|
||||
])
|
||||
assert.equal(schedule.cards[0].variant, 'transfer')
|
||||
assert.equal(schedule.cards[0].amount, '3000')
|
||||
assert.match(schedule.cards[0].meta, /微信/)
|
||||
assert.deepEqual(schedule.cards[0].fields.map((field) => field.label), [
|
||||
'转账方式', '转账金额', '转账时间', '转账凭证', '备注', '财务备注', '状态'
|
||||
'备注', '财务备注', '转账凭证'
|
||||
])
|
||||
})
|
||||
|
||||
@@ -230,16 +236,21 @@ test('deposit and installment transfers prefer backend dictionary labels and fal
|
||||
const deposit = sections.find((section) => section.key === 'deposit').cards[0]
|
||||
assert.equal(deposit.fields.find((field) => field.label === '转账方式').value, '微信支付')
|
||||
const transfers = sections.find((section) => section.key === 'payment').cards[0].cards[0].cards
|
||||
assert.equal(transfers[0].fields.find((field) => field.label === '转账方式').value, '支付宝')
|
||||
assert.equal(transfers[1].fields.find((field) => field.label === '转账方式').value, 'unknown-code')
|
||||
assert.match(transfers[0].meta, /^支付宝 ·/)
|
||||
assert.match(transfers[1].meta, /^unknown-code ·/)
|
||||
})
|
||||
|
||||
test('detail renderer recursively renders transfer and task subcards', () => {
|
||||
const section = source('./components/MarketDetailSection.vue')
|
||||
const nested = source('./components/MarketDetailNestedCard.vue')
|
||||
const styles = source('../styles/market.css')
|
||||
assert.match(section, /<MarketDetailNestedCard/)
|
||||
assert.match(nested, /<MarketDetailNestedCard/)
|
||||
assert.match(nested, /card\.cards/)
|
||||
assert.match(nested, /variant === 'transfer'/)
|
||||
assert.match(nested, /variant === 'schedule'/)
|
||||
assert.match(styles, /\.market-detail-section__nested-card\.is-transfer\s*\{/)
|
||||
assert.match(styles, /\.market-detail-section__transfer-amount\s*\{/)
|
||||
})
|
||||
|
||||
test('protocol transfer controls require backend nested capability and frontend whitelist', () => {
|
||||
@@ -275,7 +286,8 @@ test('protocol uploads use the Web 20MB rich attachment policy', () => {
|
||||
test('protocol list and nested payment actions keep 44px mobile touch targets', () => {
|
||||
const styles = source('../styles/market.css')
|
||||
const panel = source('./components/ProtocolSamplePaymentPanel.vue')
|
||||
assert.match(styles, /\.market-list-card__actions \.van-button\s*\{[^}]*min-height:\s*44px;/)
|
||||
assert.match(styles, /\.market-list-card__actions \.van-button\s*\{[^}]*min-height:\s*32px;/)
|
||||
assert.match(styles, /\.market-list-card__actions \.van-button\s*\{[\s\S]*?padding:\s*0 8px;/)
|
||||
assert.match(panel, /\.protocol-sample-pay__transfer-actions[\s\S]*?\.van-button\s*\{[^}]*min-height:\s*44px;/)
|
||||
})
|
||||
|
||||
|
||||
@@ -818,6 +818,143 @@ body.van-toast--unclickable .presign-order-page * {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.market-detail-section__nested-card.is-schedule {
|
||||
display: block;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid #eef4f1;
|
||||
background: #fafcfb;
|
||||
}
|
||||
|
||||
.market-detail-section__nested-card.is-transfer {
|
||||
display: block;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--market-border);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 0 rgb(8 127 87 / 4%);
|
||||
}
|
||||
|
||||
.market-detail-section__schedule-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
background: var(--market-primary-soft);
|
||||
}
|
||||
|
||||
.market-detail-section__schedule-head strong {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.market-detail-section__schedule-metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px 14px;
|
||||
padding: 12px;
|
||||
border-bottom: 1px dashed #eef4f1;
|
||||
}
|
||||
|
||||
.market-detail-section__transfer-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.market-detail-section__transfer-list-label {
|
||||
color: var(--market-muted);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.market-detail-section__transfer-empty {
|
||||
margin: 0;
|
||||
padding: 4px 2px 2px;
|
||||
color: var(--market-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.market-detail-section__transfer-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #eef4f1;
|
||||
}
|
||||
|
||||
.market-detail-section__transfer-index {
|
||||
display: grid;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
flex: 0 0 22px;
|
||||
place-items: center;
|
||||
border-radius: 6px;
|
||||
background: var(--market-primary-soft);
|
||||
color: var(--market-primary-dark);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.market-detail-section__transfer-main {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.market-detail-section__transfer-amount {
|
||||
display: block;
|
||||
color: #c98312;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.market-detail-section__transfer-meta {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
color: var(--market-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.market-detail-section__transfer-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px 12px;
|
||||
}
|
||||
|
||||
.market-detail-section__transfer-metric {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.market-detail-section__transfer-metric.is-wide {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.market-detail-section__transfer-metric > span {
|
||||
color: var(--market-muted);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.market-detail-section__transfer-metric > b {
|
||||
font-weight: 600;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.market-detail-section__transfer-metric > b.is-money {
|
||||
color: #c98312;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.market-detail-section__transfer-voucher {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px dashed #eef4f1;
|
||||
}
|
||||
|
||||
.market-detail-section__nested-field {
|
||||
display: flex;
|
||||
grid-column: 1 / -1;
|
||||
|
||||
Reference in new issue
Block a user