This commit is contained in:
zlwu committed 2026-05-17 12:31:11 +08:00
1 parent e5ba8f097a
commit 62cfb8fbbf
24 files changed
+3323 -249

No files matched your search

@@ -313,6 +313,56 @@
/>
</div>
</dl>
<template
v-for="flowCtx in [{ nodes: buildPaymentFlowNodes(detail) }]"
:key="`${detail.id || dIdx}-flow`"
>
<div v-if="flowCtx.nodes.length" class="payment-flow">
<div class="payment-flow-scroll">
<div class="payment-flow-track">
<div
v-for="(node, nIdx) in flowCtx.nodes"
:key="node.key"
class="payment-flow-item"
>
<div class="payment-flow-rail">
<span class="payment-flow-node" />
<span
v-if="nIdx < flowCtx.nodes.length - 1"
class="payment-flow-line"
/>
</div>
<div class="payment-flow-body">
<div class="payment-flow-title">{{ node.title }}</div>
<div
v-if="node.amount != null && node.amount !== ''"
class="payment-flow-kv"
>
<span class="payment-flow-kv__label">退费金额(元)</span>
<span class="payment-flow-kv__value">{{ dash(node.amount) }}</span>
</div>
<div
v-if="node.remark != null && String(node.remark).trim() !== ''"
class="payment-flow-kv payment-flow-kv--remark"
>
<span class="payment-flow-kv__label">{{ node.remarkLabel }}</span>
<span class="payment-flow-kv__value">{{ dash(node.remark) }}</span>
</div>
<div v-if="node.attachments.length" class="payment-flow-attachments">
<span class="payment-flow-kv__label">{{ node.attachmentLabel }}</span>
<ProtocolAttachmentReadonly
:files="node.attachments"
@preview="openPreview"
@download="downloadFile"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
@@ -494,6 +544,8 @@
const collectorDisplay = computed(() => {
const fromList = props.listRow?.collectorUserName
if (fromList) return String(fromList)
const fromBundle = protocol.value?.collectorUserName
if (fromBundle) return String(fromBundle)
const id = protocol.value?.collectorUserId
const u = collectorUserOptions.value.find((x) => x.id === id)
return u?.nickName || dash(id)
@@ -502,6 +554,8 @@
const salesDisplay = computed(() => {
const fromList = props.listRow?.salesUserName
if (fromList) return String(fromList)
const fromBundle = collectionInfo.value?.salesUserName
if (fromBundle) return String(fromBundle)
const id = collectionInfo.value?.salesUserId
const u = salesUserOptions.value.find((x) => x.id === id)
return u?.nickName || dash(id)
@@ -510,6 +564,8 @@
const regionDisplay = computed(() => {
const fromList = props.listRow?.regionName
if (fromList) return String(fromList)
const fromBundle = collectionInfo.value?.regionName
if (fromBundle) return String(fromBundle)
const id = collectionInfo.value?.regionId
const r = regionOptions.value.find((x) => x.id === id)
return r?.regionName || dash(id)
@@ -518,6 +574,8 @@
const hospitalDisplay = computed(() => {
const fromList = props.listRow?.hospitalName
if (fromList) return String(fromList)
const fromBundle = collectionInfo.value?.hospitalName
if (fromBundle) return String(fromBundle)
const id = collectionInfo.value?.hospitalId
const h = hospitalOptions.value.find((x) => x.id === id)
return h?.hospitalName || dash(id)
@@ -649,6 +707,131 @@
return Array.isArray(detail.attachments) ? detail.attachments : []
}
function financeConfirmAttachmentFiles(detail: Record<string, any>) {
if (Array.isArray(detail.financeConfirmAttachmentFiles)) {
return detail.financeConfirmAttachmentFiles
}
return urlsToAttachmentFiles(String(detail.financeConfirmAttachments || ''))
}
function refundAttachmentFiles(detail: Record<string, any>) {
if (Array.isArray(detail.refundAttachmentFiles)) {
return detail.refundAttachmentFiles
}
return urlsToAttachmentFiles(String(detail.refundAttachments || ''))
}
function refundFinanceConfirmAttachmentFiles(detail: Record<string, any>) {
if (Array.isArray(detail.refundFinanceConfirmAttachmentFiles)) {
return detail.refundFinanceConfirmAttachmentFiles
}
return urlsToAttachmentFiles(String(detail.refundFinanceConfirmAttachments || ''))
}
function urlsToAttachmentFiles(urls: string) {
if (!urls.trim()) return []
return urls.split(',').map((u, i) => {
const url = u.trim()
if (!url) return null
const name = url.includes('/') ? url.split('/').pop() || `附件${i + 1}` : url
return { id: `url_${i}`, fileUrl: url, fileName: name, fileSize: 0 }
}).filter(Boolean) as Array<Record<string, any>>
}
function hasFinanceRemark(detail: Record<string, any>) {
return String(detail.financeRemark || '').trim() !== ''
}
function hasFinanceConfirmAttachments(detail: Record<string, any>) {
return financeConfirmAttachmentFiles(detail).length > 0
}
function hasRefundApplyInfo(detail: Record<string, any>) {
const amount = detail.refundAmount
const hasAmount = amount != null && amount !== '' && Number(amount) > 0
return (
hasAmount ||
String(detail.refundRemark || '').trim() !== '' ||
refundAttachmentFiles(detail).length > 0
)
}
/** 缴费财务确认:已缴费或退费确认中,且已有财务确认字段 */
function showPaymentFinanceConfirm(detail: Record<string, any>) {
const status = String(detail.payStatus || '')
if (status === 'ytf') return false
if (status !== 'yjf' && status !== 'tfqrz') return false
return hasFinanceRemark(detail) || hasFinanceConfirmAttachments(detail)
}
/** 退费确认申请:发起退费时填写 */
function showRefundApply(detail: Record<string, any>) {
const status = String(detail.payStatus || '')
if (status !== 'tfqrz' && status !== 'ytf') return false
return hasRefundApplyInfo(detail)
}
function hasRefundFinanceRemark(detail: Record<string, any>) {
return String(detail.refundFinanceRemark || '').trim() !== ''
}
function hasRefundFinanceConfirmAttachments(detail: Record<string, any>) {
return refundFinanceConfirmAttachmentFiles(detail).length > 0
}
/** 财务退费确认:已退费时展示的财务确认信息 */
function showRefundFinanceConfirm(detail: Record<string, any>) {
if (String(detail.payStatus || '') !== 'ytf') return false
return hasRefundFinanceRemark(detail) || hasRefundFinanceConfirmAttachments(detail)
}
interface PaymentFlowNode {
key: string
title: string
remarkLabel: string
attachmentLabel: string
remark?: unknown
amount?: unknown
attachments: Array<Record<string, any>>
}
/** 缴费明细 — 财务/退费流程横向节点(按业务顺序) */
function buildPaymentFlowNodes(detail: Record<string, any>): PaymentFlowNode[] {
const nodes: PaymentFlowNode[] = []
if (showPaymentFinanceConfirm(detail)) {
nodes.push({
key: 'receipt-finance',
title: '缴费财务确认',
remarkLabel: '财务备注',
attachmentLabel: '财务附件',
remark: detail.financeRemark,
attachments: financeConfirmAttachmentFiles(detail)
})
}
if (showRefundApply(detail)) {
nodes.push({
key: 'refund-apply',
title: '退费确认申请',
remarkLabel: '退费备注',
attachmentLabel: '退费附件',
remark: detail.refundRemark,
amount: detail.refundAmount,
attachments: refundAttachmentFiles(detail)
})
}
if (showRefundFinanceConfirm(detail)) {
nodes.push({
key: 'refund-finance',
title: '财务退费确认',
remarkLabel: '财务备注',
attachmentLabel: '财务附件',
remark: detail.refundFinanceRemark,
attachments: refundFinanceConfirmAttachmentFiles(detail)
})
}
return nodes
}
function fullFileUrl(suffix: string) {
if (!suffix) return ''
if (suffix.startsWith('http://') || suffix.startsWith('https://') || suffix.startsWith('/'))
@@ -947,11 +1130,133 @@
}
.payment-detail-card__attachments {
margin-top: 8px;
padding-top: 8px;
margin-top: 4px;
padding-top: 12px;
border-top: 1px dashed #e2e8f0;
}
.payment-flow {
margin-top: 14px;
padding-top: 14px;
border-top: 1px solid #e2e8f0;
}
.payment-flow-scroll {
overflow-x: auto;
padding: 0 0 6px 4px;
&::-webkit-scrollbar {
height: 4px;
}
&::-webkit-scrollbar-thumb {
background: #d1d5db;
border-radius: 2px;
}
}
.payment-flow-track {
display: inline-flex;
min-width: 100%;
align-items: flex-start;
}
.payment-flow-item {
display: flex;
flex-direction: column;
min-width: 200px;
flex: 1 0 200px;
max-width: 280px;
}
.payment-flow-rail {
display: flex;
align-items: center;
height: 22px;
}
.payment-flow-node {
position: relative;
z-index: 1;
flex-shrink: 0;
width: 12px;
height: 12px;
border: 2px solid #fff;
border-radius: 50%;
background: var(--primary, #10b981);
box-shadow: 0 0 0 1px var(--primary, #10b981);
}
.payment-flow-line {
flex: 1;
height: 2px;
margin-left: 0;
background: #d1d5db;
}
.payment-flow-body {
padding: 8px 12px 0 0;
font-size: 13px;
line-height: 1.45;
}
.payment-flow-title {
margin-bottom: 8px;
font-size: 13px;
font-weight: 600;
color: #334155;
}
.payment-flow-kv {
display: flex;
flex-direction: column;
gap: 2px;
margin-bottom: 8px;
}
.payment-flow-kv--remark .payment-flow-kv__value {
word-break: break-word;
}
.payment-flow-kv__label {
font-size: 11px;
font-weight: 500;
color: #94a3b8;
}
.payment-flow-kv__value {
color: #1e293b;
}
.payment-flow-attachments {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 0;
:deep(.attachment-list) {
flex-wrap: nowrap;
gap: 8px;
}
:deep(.attachment-item) {
width: 88px;
flex-shrink: 0;
}
:deep(.attachment-preview) {
height: 64px;
}
:deep(.attachment-footer) {
padding: 4px 6px;
}
:deep(.attachment-name) {
font-size: 11px;
}
}
.view-field-block {
display: flex;
flex-direction: column;