feat(web-admin): transfer remark columns and jfqrz withdraw UI.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
c860e39363
commit
8cbbd3548b
8 files changed
+4774
-2650
No files matched your search
@@ -78,6 +78,13 @@ export function fetchSubmitPresignTransferFinance(transferId: string) {
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchWithdrawPresignTransfer(transferId: string) {
|
||||
return request.post({
|
||||
url: `/web/admin/presign-transfers/${transferId}/withdraw`,
|
||||
showSuccessMessage: true
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchPresignTransferReceiptConfirmList(params?: {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
@@ -89,6 +96,17 @@ export function fetchPresignTransferReceiptConfirmList(params?: {
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchPresignTransferReceiptConfirmedList(params?: {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
keyword?: string
|
||||
}) {
|
||||
return request.get<PageList<Record<string, unknown>>>({
|
||||
url: '/web/admin/presign-transfers/receipt-confirmed',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchConfirmPresignTransfer(
|
||||
transferId: string,
|
||||
data?: { financeConfirmAttachments?: string; financeRemark?: string }
|
||||
|
||||
@@ -48,6 +48,13 @@ export function fetchSubmitPaymentTransferFinance(transferId: string) {
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchWithdrawPaymentTransfer(transferId: string) {
|
||||
return request.post({
|
||||
url: `/web/admin/payment-transfers/${transferId}/withdraw`,
|
||||
showSuccessMessage: true
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchPaymentTransferList(params?: {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
@@ -156,3 +163,17 @@ export function fetchSamplePaymentDetail(samplePaymentId: string) {
|
||||
url: `/web/admin/sample-payments/${samplePaymentId}`
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchPaymentChaseList(params?: {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
keyword?: string
|
||||
chaseType?: string
|
||||
payMode?: string
|
||||
salesUserId?: string
|
||||
}) {
|
||||
return request.get<PageList<Record<string, unknown>>>({
|
||||
url: '/web/admin/payment-chase',
|
||||
params
|
||||
})
|
||||
}
|
||||
@@ -9,13 +9,26 @@
|
||||
type="button"
|
||||
role="tab"
|
||||
class="panel-pill-tabs__btn"
|
||||
:class="{ 'is-active': mainTab === 'payment' }"
|
||||
:aria-selected="mainTab === 'payment'"
|
||||
@click="mainTab = 'payment'"
|
||||
:class="{ 'is-active': mainTab === 'presign' }"
|
||||
:aria-selected="mainTab === 'presign'"
|
||||
@click="mainTab = 'presign'"
|
||||
>
|
||||
缴费确认
|
||||
<span v-if="paymentPendingBadge > 0" class="panel-pill-tabs__badge">{{
|
||||
formatTabBadgeCount(paymentPendingBadge)
|
||||
意向金
|
||||
<span v-if="presignPendingBadge > 0" class="panel-pill-tabs__badge">{{
|
||||
formatTabBadgeCount(presignPendingBadge)
|
||||
}}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
class="panel-pill-tabs__btn"
|
||||
:class="{ 'is-active': mainTab === 'formal' }"
|
||||
:aria-selected="mainTab === 'formal'"
|
||||
@click="mainTab = 'formal'"
|
||||
>
|
||||
正式缴费
|
||||
<span v-if="formalPendingBadge > 0" class="panel-pill-tabs__badge">{{
|
||||
formatTabBadgeCount(formalPendingBadge)
|
||||
}}</span>
|
||||
</button>
|
||||
<!-- TODO(P1): 开票改挂 transfer;当前仍走旧 payment_detail 开票 API -->
|
||||
@@ -35,7 +48,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="mainTab === 'payment'" class="panel-entity-body">
|
||||
<div v-show="isPaymentTab" class="panel-entity-body">
|
||||
<div class="finance-section-head finance-section-head--pending">
|
||||
<h3 class="finance-section-title">
|
||||
待确认
|
||||
@@ -49,19 +62,20 @@
|
||||
<el-form
|
||||
:model="pendingSearchForm"
|
||||
class="filter-form"
|
||||
label-position="top"
|
||||
@submit.prevent
|
||||
>
|
||||
<div class="filter-grid">
|
||||
<el-form-item label="关键词">
|
||||
<el-input
|
||||
v-model="pendingSearchForm.keyword"
|
||||
placeholder="协议号/样本编码/客户姓名/身份证号"
|
||||
:placeholder="
|
||||
isPresignTab ? '协议号/客户姓名' : '协议号/样本编码/客户姓名/身份证号'
|
||||
"
|
||||
clearable
|
||||
@keyup.enter="handlePendingSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="应缴费时间">
|
||||
<el-form-item v-if="!isPresignTab" label="应缴费时间">
|
||||
<el-date-picker
|
||||
v-model="pendingInstallmentDueDateRange"
|
||||
type="daterange"
|
||||
@@ -92,50 +106,70 @@
|
||||
<div class="pending-card__head">
|
||||
<div class="pending-card__identity">
|
||||
<div class="pending-card__name">{{ row.motherName || '-' }}</div>
|
||||
<div class="pending-card__sub">
|
||||
{{ row.protocolNo || '-' }}
|
||||
<template v-if="row.sampleCode"> · {{ row.sampleCode }}</template>
|
||||
<template v-if="row.motherIdCard">
|
||||
· {{ maskIdCard(row.motherIdCard) }}
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="cardSubtitle(row)" class="pending-card__sub">{{ cardSubtitle(row) }}</div>
|
||||
</div>
|
||||
<div class="pending-card__amount">
|
||||
<div class="pending-card__amount-val">
|
||||
{{ formatPaymentAmount(row.transferAmount) }}
|
||||
</div>
|
||||
<div class="pending-card__amount-label">转账金额</div>
|
||||
<div class="pending-card__amount-label">
|
||||
{{ isPresignRow(row) ? '待确认意向金' : '待确认转账' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pending-card__grid">
|
||||
<div class="pending-card__field">
|
||||
<label>期次</label>
|
||||
<span>第 {{ row.installmentNo ?? '-' }} 期</span>
|
||||
</div>
|
||||
<div class="pending-card__field">
|
||||
<label>应缴费时间</label>
|
||||
<span>{{ formatPaymentDueDate(row.installmentDueDate) }}</span>
|
||||
</div>
|
||||
<div class="pending-card__field">
|
||||
<label>批次应缴</label>
|
||||
<span>{{ formatPaymentAmount(row.dueAmount) }}</span>
|
||||
</div>
|
||||
<div class="pending-card__field">
|
||||
<label>缴费方式</label>
|
||||
<span>{{ payModeLabel(String(row.payMode || '')) }}</span>
|
||||
</div>
|
||||
<div class="pending-card__field">
|
||||
<label>转账方式</label>
|
||||
<span>{{ transferTypeLabel(String(row.transferType || '')) }}</span>
|
||||
</div>
|
||||
<div class="pending-card__field">
|
||||
<label>转账时间</label>
|
||||
<span>{{ formatDisplayOrDash(row.transferTime) }}</span>
|
||||
</div>
|
||||
<div class="pending-card__field">
|
||||
<label>来源</label>
|
||||
<span>{{ row.source === 'presign' ? '预签约意向金' : '正式协议' }}</span>
|
||||
</div>
|
||||
<div class="pending-card__transfer">
|
||||
<span class="pending-card__transfer-way">{{
|
||||
transferTypeLabel(String(row.transferType || '')) || '-'
|
||||
}}</span>
|
||||
<span class="pending-card__transfer-time">{{
|
||||
formatDisplayOrDash(row.transferTime)
|
||||
}}</span>
|
||||
</div>
|
||||
<div v-if="row.remark" class="pending-card__remark">备注:{{ row.remark }}</div>
|
||||
<div class="pending-card__facts">
|
||||
<template v-if="isPresignRow(row)">
|
||||
<div class="pending-card__field">
|
||||
<label>业务日期</label>
|
||||
<span>{{ formatPaymentDueDate(row.orderDate) }}</span>
|
||||
</div>
|
||||
<div class="pending-card__field">
|
||||
<label>预产期</label>
|
||||
<span>{{ formatPaymentDueDate(row.expectedDeliveryDate) }}</span>
|
||||
</div>
|
||||
<div class="pending-card__field">
|
||||
<label>合同金额</label>
|
||||
<span>{{ formatPaymentAmount(row.contractAmount) }}</span>
|
||||
<em v-if="row.contractPayMode">{{
|
||||
payModeLabel(String(row.contractPayMode || ''))
|
||||
}}</em>
|
||||
</div>
|
||||
<div class="pending-card__field">
|
||||
<label>采集专员</label>
|
||||
<span>{{ row.collectorUserName || '-' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="pending-card__field">
|
||||
<label>期次</label>
|
||||
<span>第 {{ row.installmentNo ?? '-' }} 期</span>
|
||||
</div>
|
||||
<div class="pending-card__field">
|
||||
<label>应缴费时间</label>
|
||||
<span>{{ formatPaymentDueDate(row.installmentDueDate) }}</span>
|
||||
</div>
|
||||
<div class="pending-card__field">
|
||||
<label>批次应缴</label>
|
||||
<span>{{ formatPaymentAmount(row.dueAmount) }}</span>
|
||||
</div>
|
||||
<div class="pending-card__field">
|
||||
<label>缴费方式</label>
|
||||
<span>{{ payModeLabel(String(row.payMode || '')) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="isPresignRow(row) && row.sampleTypeNames" class="pending-card__samples">
|
||||
<label>样本类型</label>
|
||||
<p>{{ row.sampleTypeNames }}</p>
|
||||
</div>
|
||||
<div class="pending-card__foot">
|
||||
<div class="pending-card__meta">
|
||||
@@ -183,19 +217,20 @@
|
||||
<el-form
|
||||
:model="confirmedSearchForm"
|
||||
class="filter-form"
|
||||
label-position="top"
|
||||
@submit.prevent
|
||||
>
|
||||
<div class="filter-grid">
|
||||
<el-form-item label="关键词">
|
||||
<el-input
|
||||
v-model="confirmedSearchForm.keyword"
|
||||
placeholder="协议号/样本编码/客户姓名/身份证号"
|
||||
:placeholder="
|
||||
isPresignTab ? '协议号/客户姓名' : '协议号/样本编码/客户姓名/身份证号'
|
||||
"
|
||||
clearable
|
||||
@keyup.enter="handleConfirmedSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="应缴费时间">
|
||||
<el-form-item v-if="!isPresignTab" label="应缴费时间">
|
||||
<el-date-picker
|
||||
v-model="confirmedInstallmentDueDateRange"
|
||||
type="daterange"
|
||||
@@ -227,6 +262,7 @@
|
||||
v-loading="confirmedLoading"
|
||||
stripe
|
||||
row-key="id"
|
||||
:key="mainTab"
|
||||
@row-dblclick="openViewConfirm"
|
||||
>
|
||||
<el-table-column
|
||||
@@ -250,12 +286,15 @@
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="sampleCode"
|
||||
label="样本编码"
|
||||
min-width="120"
|
||||
:label="isPresignTab ? '样本类型' : '样本编码'"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ row.sampleCode || '-' }}</template>
|
||||
<template #default="{ row }">
|
||||
{{ isPresignTab ? row.sampleTypeNames || '-' : row.sampleCode || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="motherName"
|
||||
@@ -267,6 +306,7 @@
|
||||
<template #default="{ row }">{{ row.motherName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="!isPresignTab"
|
||||
prop="motherIdCard"
|
||||
label="身份证号"
|
||||
min-width="170"
|
||||
@@ -276,27 +316,22 @@
|
||||
<template #default="{ row }">{{ maskIdCard(row.motherIdCard) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="source"
|
||||
label="来源"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="row.source === 'presign' ? 'warning' : 'primary'">
|
||||
{{ row.source === 'presign' ? '预签约' : '正式' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="protocolStatus"
|
||||
label="协议状态"
|
||||
min-width="100"
|
||||
:prop="isPresignTab ? 'orderStatus' : 'protocolStatus'"
|
||||
:label="isPresignTab ? '订单状态' : '协议状态'"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
v-if="isPresignTab"
|
||||
:type="presignOrderStatusTagType(String(row.orderStatus || ''))"
|
||||
size="small"
|
||||
>
|
||||
{{ presignOrderStatusLabel(String(row.orderStatus || '')) }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-else
|
||||
:type="protocolStatusTagType(String(row.protocolStatus || ''))"
|
||||
size="small"
|
||||
>{{
|
||||
@@ -309,15 +344,17 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="!isPresignTab"
|
||||
prop="payMode"
|
||||
label="缴费方式"
|
||||
min-width="100"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ payModeLabel(String(row.payMode || '')) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="!isPresignTab"
|
||||
prop="installmentNo"
|
||||
label="期数"
|
||||
min-width="80"
|
||||
@@ -327,29 +364,35 @@
|
||||
<template #default="{ row }">{{ row.installmentNo ?? '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="installmentDueDate"
|
||||
label="应缴费时间"
|
||||
min-width="120"
|
||||
:prop="isPresignTab ? 'orderDate' : 'installmentDueDate'"
|
||||
:label="isPresignTab ? '业务日期' : '应缴费时间'"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
formatPaymentDueDate(row.installmentDueDate)
|
||||
isPresignTab
|
||||
? formatPaymentDueDate(row.orderDate)
|
||||
: formatPaymentDueDate(row.installmentDueDate)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="dueAmount"
|
||||
label="应缴金额(元)"
|
||||
min-width="120"
|
||||
:prop="isPresignTab ? 'contractAmount' : 'dueAmount'"
|
||||
:label="isPresignTab ? '合同金额(元)' : '应缴金额(元)'"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ formatPaymentAmount(row.dueAmount) }}</template>
|
||||
<template #default="{ row }">{{
|
||||
isPresignTab
|
||||
? formatPaymentAmount(row.contractAmount)
|
||||
: formatPaymentAmount(row.dueAmount)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="transferAmount"
|
||||
label="实缴金额(元)"
|
||||
min-width="120"
|
||||
:label="isPresignTab ? '意向金(元)' : '实缴金额(元)'"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
@@ -375,6 +418,16 @@
|
||||
>
|
||||
<template #default="{ row }">{{ formatDisplayOrDash(row.transferTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ row.remark || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="payStatus"
|
||||
label="状态"
|
||||
@@ -383,7 +436,7 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default>
|
||||
<el-tag type="success" size="small">已缴费</el-tag>
|
||||
<el-tag type="success" size="small">缴费成功</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -440,7 +493,9 @@
|
||||
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="确认收款"
|
||||
:title="
|
||||
isPresignRow(confirmRow) ? '确认收款(预签约意向金)' : '确认收款(正式协议缴费)'
|
||||
"
|
||||
width="900px"
|
||||
class="dialog-form-layout payment-confirm-dialog"
|
||||
align-center
|
||||
@@ -490,7 +545,11 @@
|
||||
|
||||
<el-dialog
|
||||
v-model="viewDialogVisible"
|
||||
title="确认收款信息"
|
||||
:title="
|
||||
isPresignRow(viewRow)
|
||||
? '确认收款信息(预签约意向金)'
|
||||
: '确认收款信息(正式协议缴费)'
|
||||
"
|
||||
width="900px"
|
||||
class="dialog-form-layout payment-confirm-dialog"
|
||||
align-center
|
||||
@@ -532,6 +591,7 @@
|
||||
} from '@/api/protocol-sample-payment'
|
||||
import {
|
||||
fetchPresignTransferReceiptConfirmList,
|
||||
fetchPresignTransferReceiptConfirmedList,
|
||||
fetchConfirmPresignTransfer,
|
||||
fetchRejectPresignTransfer
|
||||
} from '@/api/presign-order'
|
||||
@@ -551,14 +611,27 @@
|
||||
loadTransferTypeDict,
|
||||
transferTypeLabel
|
||||
} from '@/utils/protocol-payment-labels'
|
||||
import {
|
||||
presignOrderStatusLabel,
|
||||
presignOrderStatusTagType
|
||||
} from '@/utils/status-tag-registry'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
import InvoiceRequestTab from './InvoiceRequestTab.vue'
|
||||
|
||||
defineOptions({ name: 'CellStoragePaymentConfirm' })
|
||||
|
||||
const route = useRoute()
|
||||
const mainTab = ref('payment')
|
||||
const paymentPendingBadge = ref(0)
|
||||
const mainTab = ref<'presign' | 'formal' | 'invoice'>(
|
||||
String(route.query.tab || '') === 'invoice'
|
||||
? 'invoice'
|
||||
: String(route.query.tab || '') === 'formal' || String(route.query.tab || '') === 'payment'
|
||||
? 'formal'
|
||||
: 'presign'
|
||||
)
|
||||
const isPaymentTab = computed(() => mainTab.value === 'presign' || mainTab.value === 'formal')
|
||||
const isPresignTab = computed(() => mainTab.value === 'presign')
|
||||
const presignPendingBadge = ref(0)
|
||||
const formalPendingBadge = ref(0)
|
||||
const invoicePendingBadge = ref(0)
|
||||
|
||||
function formatTabBadgeCount(count: number): string {
|
||||
@@ -570,6 +643,23 @@
|
||||
return '¥' + formatMoneyDisplay(v)
|
||||
}
|
||||
|
||||
function isPresignRow(row: Record<string, unknown> | null | undefined) {
|
||||
return String(row?.source || '') === 'presign'
|
||||
}
|
||||
|
||||
function cardSubtitle(row: Record<string, unknown>) {
|
||||
const parts: string[] = []
|
||||
const protocolNo = String(row.protocolNo || '').trim()
|
||||
if (protocolNo) parts.push(protocolNo)
|
||||
if (!isPresignRow(row)) {
|
||||
const sampleCode = String(row.sampleCode || '').trim()
|
||||
if (sampleCode) parts.push(sampleCode)
|
||||
const idCard = String(row.motherIdCard || '').trim()
|
||||
if (idCard) parts.push(maskIdCard(idCard))
|
||||
}
|
||||
return parts.join(' · ')
|
||||
}
|
||||
|
||||
const pendingSearchForm = reactive({ keyword: '' })
|
||||
const confirmedSearchForm = reactive({ keyword: '' })
|
||||
const pendingInstallmentDueDateRange = ref<[string, string] | null>(null)
|
||||
@@ -631,7 +721,8 @@
|
||||
fetchPresignTransferReceiptConfirmList({ page: 1, pageSize: 1 }),
|
||||
fetchProtocolInvoicePendingCount()
|
||||
])
|
||||
paymentPendingBadge.value = (paymentRes.total ?? 0) + (presignRes.total ?? 0)
|
||||
formalPendingBadge.value = paymentRes.total ?? 0
|
||||
presignPendingBadge.value = presignRes.total ?? 0
|
||||
invoicePendingBadge.value = Number(invRes?.count ?? 0)
|
||||
} catch {
|
||||
// 角标加载失败时保留上次数值,避免干扰主流程
|
||||
@@ -641,18 +732,19 @@
|
||||
async function loadPending() {
|
||||
pendingLoading.value = true
|
||||
try {
|
||||
// 正式 + 预签约意向金待办合并(前端 merge,分页近似:各取一页后合并)
|
||||
const [formal, presign] = await Promise.all([
|
||||
fetchPaymentTransferReceiptConfirmList(buildPendingParams()),
|
||||
fetchPresignTransferReceiptConfirmList({
|
||||
if (isPresignTab.value) {
|
||||
const res = await fetchPresignTransferReceiptConfirmList({
|
||||
page: pendingPagination.page,
|
||||
pageSize: pendingPagination.pageSize,
|
||||
keyword: pendingSearchForm.keyword || undefined
|
||||
})
|
||||
])
|
||||
const merged = [...(formal.list || []), ...(presign.list || [])]
|
||||
pendingData.value = merged
|
||||
pendingPagination.total = (formal.total ?? 0) + (presign.total ?? 0)
|
||||
pendingData.value = res.list || []
|
||||
pendingPagination.total = res.total ?? 0
|
||||
} else {
|
||||
const res = await fetchPaymentTransferReceiptConfirmList(buildPendingParams())
|
||||
pendingData.value = res.list || []
|
||||
pendingPagination.total = res.total ?? 0
|
||||
}
|
||||
} finally {
|
||||
pendingLoading.value = false
|
||||
}
|
||||
@@ -661,9 +753,19 @@
|
||||
async function loadConfirmed() {
|
||||
confirmedLoading.value = true
|
||||
try {
|
||||
const res = await fetchPaymentTransferReceiptConfirmedList(buildConfirmedParams())
|
||||
confirmedData.value = res.list || []
|
||||
confirmedPagination.total = res.total ?? 0
|
||||
if (isPresignTab.value) {
|
||||
const res = await fetchPresignTransferReceiptConfirmedList({
|
||||
page: confirmedPagination.page,
|
||||
pageSize: confirmedPagination.pageSize,
|
||||
keyword: confirmedSearchForm.keyword || undefined
|
||||
})
|
||||
confirmedData.value = res.list || []
|
||||
confirmedPagination.total = res.total ?? 0
|
||||
} else {
|
||||
const res = await fetchPaymentTransferReceiptConfirmedList(buildConfirmedParams())
|
||||
confirmedData.value = res.list || []
|
||||
confirmedPagination.total = res.total ?? 0
|
||||
}
|
||||
} finally {
|
||||
confirmedLoading.value = false
|
||||
}
|
||||
@@ -738,7 +840,7 @@
|
||||
async function submitReject() {
|
||||
if (!currentDetailId.value) return
|
||||
try {
|
||||
await ElMessageBox.confirm('确定退回?退回后状态将变为「待缴费」。', '退回确认', {
|
||||
await ElMessageBox.confirm('确定退回?退回后状态将变为「已缴费」。', '退回确认', {
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
@@ -779,20 +881,18 @@
|
||||
|
||||
onMounted(async () => {
|
||||
await loadTransferTypeDict()
|
||||
const tab = route.query.tab
|
||||
if (tab === 'invoice') {
|
||||
mainTab.value = 'invoice'
|
||||
} else {
|
||||
mainTab.value = 'payment'
|
||||
}
|
||||
loadTabPendingCounts()
|
||||
if (mainTab.value === 'payment') {
|
||||
if (isPaymentTab.value) {
|
||||
loadAll()
|
||||
}
|
||||
})
|
||||
|
||||
watch(mainTab, () => {
|
||||
watch(mainTab, (tab) => {
|
||||
loadTabPendingCounts()
|
||||
if (tab === 'invoice') return
|
||||
pendingPagination.page = 1
|
||||
confirmedPagination.page = 1
|
||||
loadAll()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -818,8 +918,7 @@
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
padding: 0 0 4px;
|
||||
line-height: 1.2;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.pending-card-section {
|
||||
@@ -829,23 +928,26 @@
|
||||
|
||||
.pending-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
||||
gap: 12px;
|
||||
padding: 12px 24px 0;
|
||||
grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));
|
||||
gap: 16px;
|
||||
padding: 16px 24px 8px;
|
||||
}
|
||||
|
||||
.pending-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
border: 1px solid var(--mint-border, #e2e8f0);
|
||||
border-radius: 10px;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
padding: 16px 18px;
|
||||
padding: 20px 22px 16px;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
box-shadow 0.15s;
|
||||
|
||||
&:hover {
|
||||
border-color: #c5e0d4;
|
||||
box-shadow: 0 2px 8px rgb(15 23 42 / 5%);
|
||||
border-color: #a7f3d0;
|
||||
box-shadow: 0 4px 14px rgb(15 23 42 / 6%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -853,19 +955,18 @@
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.pending-card__name {
|
||||
font-size: 15px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text, #1e293b);
|
||||
line-height: 1.4;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.pending-card__sub {
|
||||
margin-top: 2px;
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
line-height: 1.4;
|
||||
@@ -877,37 +978,93 @@
|
||||
}
|
||||
|
||||
.pending-card__amount-val {
|
||||
font-size: 18px;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: var(--primary-dark, #059669);
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1.3;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.pending-card__amount-label {
|
||||
margin-top: 2px;
|
||||
font-size: 11px;
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
}
|
||||
|
||||
.pending-card__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
.pending-card__transfer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px 16px;
|
||||
margin-bottom: 14px;
|
||||
padding: 10px 12px;
|
||||
background: var(--primary-bg, #ecfdf5);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.pending-card__transfer-way {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--primary-dark, #059669);
|
||||
}
|
||||
|
||||
.pending-card__transfer-time {
|
||||
font-size: 13px;
|
||||
color: var(--mint-text, #334155);
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pending-card__remark {
|
||||
font-size: 12px;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.pending-card__facts {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px 24px;
|
||||
}
|
||||
|
||||
.pending-card__field label {
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
font-size: 11px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
}
|
||||
|
||||
.pending-card__field span {
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
color: var(--text, #1e293b);
|
||||
line-height: 1.4;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.pending-card__field em {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
}
|
||||
|
||||
.pending-card__samples {
|
||||
padding-top: 2px;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: var(--mint-text, #334155);
|
||||
line-height: 1.55;
|
||||
}
|
||||
}
|
||||
|
||||
.pending-card__foot {
|
||||
@@ -915,6 +1072,7 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-top: 2px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
}
|
||||
@@ -984,8 +1142,8 @@
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.pending-card__grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
.pending-card__facts {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,743 @@
|
||||
<!-- 缴费信息 · Excel 宽表(基础信息 + 第1~6期 + 汇总) -->
|
||||
<template>
|
||||
<div class="payment-excel-list">
|
||||
<div class="filter-block">
|
||||
<el-form :model="searchForm" class="filter-form" @submit.prevent>
|
||||
<div class="filter-grid">
|
||||
<el-form-item label="关键词">
|
||||
<el-input
|
||||
v-model="searchForm.keyword"
|
||||
placeholder="协议号/客户/样本编码/电话"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="缴费方式">
|
||||
<el-select v-model="searchForm.payMode" placeholder="全部" clearable style="width: 100%">
|
||||
<el-option label="趸交" value="dj" />
|
||||
<el-option label="分期" value="fq" />
|
||||
<el-option label="分改趸" value="fgd" />
|
||||
<el-option label="趸交+分期" value="djfq" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="缴费进度">
|
||||
<el-select
|
||||
v-model="searchForm.progressStatus"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in progressStatusOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="应缴费时间">
|
||||
<el-date-picker
|
||||
v-model="dueDateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">
|
||||
<el-icon><RefreshLeft /></el-icon>重置
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">
|
||||
<el-icon><Search /></el-icon>搜索
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="list-body list-wrap excel-scroll">
|
||||
<el-table
|
||||
class="list-table excel-table"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
row-key="id"
|
||||
border
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="64"
|
||||
fixed="left"
|
||||
align="center"
|
||||
:index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1"
|
||||
/>
|
||||
<el-table-column label="姓名" min-width="90" fixed="left" align="center">
|
||||
<template #default="{ row }">{{ row.motherName || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="手机号" min-width="120" align="center">
|
||||
<template #default="{ row }">{{ row.motherPhone || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="protocolNo"
|
||||
label="合同编号"
|
||||
min-width="130"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="签署日期" min-width="110" align="center">
|
||||
<template #default="{ row }">{{ row.signDate || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="存储年限" min-width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.storageYears != null && row.storageYears !== '' ? row.storageYears : '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="存储项目" min-width="120" align="center" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.sampleTypeName || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="合同总额" min-width="100" align="center">
|
||||
<template #default="{ row }">{{ formatAmount(row.contractAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="缴费方式" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="payModeTagType(String(row.payMode || ''))" size="small">
|
||||
{{ payModeLabel(String(row.payMode || '')) || '—' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="优惠金额" min-width="100" align="center">
|
||||
<template #default="{ row }">{{ formatAmount(row.discountAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应收总额" min-width="100" align="center">
|
||||
<template #default="{ row }">{{ formatAmount(row.receivableAmount) }}</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
v-for="p in 6"
|
||||
:key="'period-' + p"
|
||||
:label="'第' + p + '期'"
|
||||
align="center"
|
||||
:class-name="'period-col period-col--' + p"
|
||||
>
|
||||
<el-table-column label="应缴费金额(元)" min-width="120" align="center">
|
||||
<template #default="{ row }">{{ periodDue(row, p - 1) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应缴费时间" min-width="120" align="center">
|
||||
<template #default="{ row }">{{ periodField(row, p - 1, 'dueDate') }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已缴费金额" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<button
|
||||
v-if="periodHasPaid(row, p - 1)"
|
||||
type="button"
|
||||
class="amount-link"
|
||||
@click="openPeriodDialog(row, p - 1)"
|
||||
>
|
||||
{{ periodPaid(row, p - 1) }}
|
||||
</button>
|
||||
<span v-else class="muted">{{ periodPaid(row, p - 1) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已缴费时间" min-width="120" align="center">
|
||||
<template #default="{ row }">{{ periodField(row, p - 1, 'payDate') }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="欠缴费金额" min-width="110" align="center">
|
||||
<template #default="{ row }">{{ periodArrears(row, p - 1) }}</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="累计实缴" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<strong class="amount-strong">{{ formatAmount(row.paidAmount) }}</strong>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="待缴金额" min-width="100" align="center">
|
||||
<template #default="{ row }">{{ formatAmount(row.dueRemainAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.remark || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="260" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleViewProtocol(row)">
|
||||
<el-icon><Document /></el-icon>协议单
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.canConfirm"
|
||||
class="btn-op-item primary"
|
||||
@click="handleConfirm(row)"
|
||||
>
|
||||
<el-icon><Check /></el-icon>缴费确认
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination list-pager-bar">
|
||||
<el-pagination
|
||||
:current-page="pagination.page"
|
||||
:page-size="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="(val) => ((pagination.page = 1), (pagination.pageSize = val), loadData())"
|
||||
@current-change="(val) => ((pagination.page = val), loadData())"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="periodDialogVisible"
|
||||
:title="periodDialogTitle"
|
||||
width="720px"
|
||||
class="dialog-form-layout"
|
||||
align-center
|
||||
append-to-body
|
||||
modal-class="protocol-dialog-mask"
|
||||
destroy-on-close
|
||||
>
|
||||
<div v-loading="periodLoading" class="period-dialog-body">
|
||||
<div class="summary-grid">
|
||||
<div class="summary-item">
|
||||
<label>应缴</label>
|
||||
<strong>{{ formatAmountYuan(periodSummary.dueAmount) }}</strong>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<label>实缴合计</label>
|
||||
<strong class="amount-strong">{{ formatAmountYuan(periodSummary.paidAmount) }}</strong>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<label>欠缴</label>
|
||||
<strong>{{ formatAmountYuan(periodSummary.arrearsAmount) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-subtitle">转账明细</div>
|
||||
<div v-if="!periodTransfers.length" class="muted">暂无转账笔</div>
|
||||
<div v-for="(tr, idx) in periodTransfers" :key="String(tr.id || idx)" class="transfer-card">
|
||||
<div class="transfer-card__main">
|
||||
<div class="transfer-card__top">
|
||||
<strong>第 {{ idx + 1 }} 笔</strong>
|
||||
<el-tag :type="payStatusTagType(String(tr.payStatus || ''))" size="small">
|
||||
{{ payStatusLabel(String(tr.payStatus || '')) || '—' }}
|
||||
</el-tag>
|
||||
<span class="amount-strong">{{ formatAmountYuan(tr.transferAmount) }}</span>
|
||||
</div>
|
||||
<div class="transfer-card__meta">
|
||||
<span>{{ transferTypeLabel(String(tr.transferType || '')) || '—' }}</span>
|
||||
<span>{{ formatDisplayOrDash(tr.transferTime) }}</span>
|
||||
</div>
|
||||
<div v-if="tr.remark" class="transfer-card__remark">备注:{{ tr.remark }}</div>
|
||||
<div v-if="tr.financeRemark" class="transfer-card__remark"
|
||||
>财务备注:{{ tr.financeRemark }}</div
|
||||
>
|
||||
</div>
|
||||
<div class="transfer-card__attach">
|
||||
<span class="transfer-card__attach-label">转账凭证</span>
|
||||
<ProtocolAttachmentReadonly
|
||||
v-if="transferAttachmentFiles(tr).length"
|
||||
:files="transferAttachmentFiles(tr)"
|
||||
size="compact"
|
||||
@preview="handleAttachPreview"
|
||||
@download="handleAttachDownload"
|
||||
/>
|
||||
<span v-else class="muted">暂无凭证</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="periodDialogVisible = false">
|
||||
<el-icon><Close /></el-icon>关闭
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="periodCanConfirm"
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="confirming"
|
||||
@click="openConfirmFromPeriod"
|
||||
>
|
||||
<el-icon><Check /></el-icon>缴费确认
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<PaymentReceiptConfirmDialog
|
||||
v-model="receiptDialogVisible"
|
||||
:preview-row="receiptPreviewRow"
|
||||
:is-transfer="true"
|
||||
:target-id="receiptTargetId"
|
||||
@saved="onReceiptRefresh"
|
||||
@submitted="onReceiptRefresh"
|
||||
/>
|
||||
|
||||
<el-dialog
|
||||
v-model="attachPreviewVisible"
|
||||
title="附件预览"
|
||||
width="720px"
|
||||
align-center
|
||||
append-to-body
|
||||
modal-class="protocol-dialog-mask"
|
||||
destroy-on-close
|
||||
>
|
||||
<img
|
||||
v-if="attachPreviewUrl"
|
||||
:src="attachPreviewUrl"
|
||||
alt="附件预览"
|
||||
class="attach-preview-img"
|
||||
/>
|
||||
</el-dialog>
|
||||
|
||||
<ProtocolViewDialog
|
||||
v-model="protocolVisible"
|
||||
:protocol-id="protocolViewId"
|
||||
:list-row="protocolViewRow"
|
||||
show-audit-history
|
||||
show-storage-cert
|
||||
show-fullmoon-insurance
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Search, RefreshLeft, Check, Document, Close } from '@element-plus/icons-vue'
|
||||
import ProtocolViewDialog from '../protocol/ProtocolViewDialog.vue'
|
||||
import ProtocolAttachmentReadonly from '../protocol/ProtocolAttachmentReadonly.vue'
|
||||
import PaymentReceiptConfirmDialog from '@/components/business/payment-receipt-confirm-dialog/index.vue'
|
||||
import { downloadAttachmentFile } from '@/utils/download-attachment'
|
||||
import {
|
||||
fetchSamplePaymentList,
|
||||
fetchSamplePaymentDetail,
|
||||
fetchCreatePaymentTransfer
|
||||
} from '@/api/protocol-sample-payment'
|
||||
import { type CollectionProtocolRow } from '@/api/collection-protocol'
|
||||
import { formatDisplayOrDash } from '@/utils/datetime-display'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
import {
|
||||
payModeLabel,
|
||||
payModeTagType,
|
||||
samplePaymentProgressLabel,
|
||||
payStatusLabel,
|
||||
payStatusTagType,
|
||||
transferTypeLabel
|
||||
} from '@/utils/status-tag-registry'
|
||||
|
||||
defineOptions({ name: 'CellStoragePaymentExcelList' })
|
||||
|
||||
type PeriodCell = {
|
||||
scheduleId?: string
|
||||
scheduleNo?: number
|
||||
dueAmount?: number | null
|
||||
dueDate?: string
|
||||
displayPaidAmount?: number | null
|
||||
arrearsAmount?: number | null
|
||||
payDate?: string
|
||||
payTypeSummary?: string
|
||||
financeAudit?: string
|
||||
} | null
|
||||
|
||||
const progressStatusOptions = [
|
||||
{ value: 'dfj', label: samplePaymentProgressLabel('dfj') },
|
||||
{ value: 'jfqrz', label: samplePaymentProgressLabel('jfqrz') },
|
||||
{ value: 'bfyj', label: samplePaymentProgressLabel('bfyj') },
|
||||
{ value: 'yjq', label: samplePaymentProgressLabel('yjq') }
|
||||
]
|
||||
|
||||
const route = useRoute()
|
||||
const searchForm = reactive({
|
||||
keyword: '',
|
||||
payMode: '',
|
||||
progressStatus: ''
|
||||
})
|
||||
const dueDateRange = ref<[string, string] | null>(null)
|
||||
const loading = ref(false)
|
||||
const tableData = ref<Record<string, any>[]>([])
|
||||
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
||||
|
||||
const periodDialogVisible = ref(false)
|
||||
const periodLoading = ref(false)
|
||||
const periodDialogTitle = ref('')
|
||||
const periodSummary = reactive({
|
||||
dueAmount: null as number | null,
|
||||
paidAmount: null as number | null,
|
||||
arrearsAmount: null as number | null
|
||||
})
|
||||
const periodTransfers = ref<Record<string, any>[]>([])
|
||||
const periodCanConfirm = ref(false)
|
||||
const periodSchedule = ref<Record<string, any> | null>(null)
|
||||
const periodSamplePaymentId = ref('')
|
||||
const confirming = ref(false)
|
||||
|
||||
const receiptDialogVisible = ref(false)
|
||||
const receiptPreviewRow = ref<Record<string, any> | null>(null)
|
||||
const receiptTargetId = ref('')
|
||||
|
||||
const protocolVisible = ref(false)
|
||||
const protocolViewId = ref('')
|
||||
const protocolViewRow = ref<CollectionProtocolRow | null>(null)
|
||||
|
||||
const attachPreviewVisible = ref(false)
|
||||
const attachPreviewUrl = ref('')
|
||||
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
||||
|
||||
function formatAmount(v: unknown) {
|
||||
if (v == null || v === '') return '—'
|
||||
return formatMoneyDisplay(v)
|
||||
}
|
||||
|
||||
function formatAmountYuan(v: unknown) {
|
||||
if (v == null || v === '') return '—'
|
||||
return `${formatMoneyDisplay(v)} 元`
|
||||
}
|
||||
|
||||
function transferAttachmentFiles(tr: Record<string, any>) {
|
||||
if (Array.isArray(tr.attachments) && tr.attachments.length) {
|
||||
return tr.attachments
|
||||
}
|
||||
const urls = String(tr.transferAttachments || '')
|
||||
.split(',')
|
||||
.map((u) => u.trim())
|
||||
.filter(Boolean)
|
||||
return urls.map((url, idx) => ({
|
||||
id: `url_${idx}`,
|
||||
fileName: url.split('/').pop() || `附件${idx + 1}`,
|
||||
fileUrl: url
|
||||
}))
|
||||
}
|
||||
|
||||
function fullFileUrl(url: string) {
|
||||
if (!url) return ''
|
||||
if (/^https?:\/\//i.test(url) || url.startsWith('/')) return url
|
||||
return minioBaseUrl ? `${minioBaseUrl}/${url.replace(/^\//, '')}` : url
|
||||
}
|
||||
|
||||
function handleAttachPreview(file: Record<string, unknown>) {
|
||||
const url = String(file.fileUrl || '')
|
||||
if (!url) return
|
||||
attachPreviewUrl.value = fullFileUrl(url)
|
||||
attachPreviewVisible.value = true
|
||||
}
|
||||
|
||||
function handleAttachDownload(file: Record<string, unknown>) {
|
||||
downloadAttachmentFile(file, { fullFileUrl })
|
||||
}
|
||||
|
||||
function periodsOf(row: Record<string, any>): PeriodCell[] {
|
||||
const arr = (row.periods || []) as PeriodCell[]
|
||||
const out: PeriodCell[] = []
|
||||
for (let i = 0; i < 6; i++) out.push(arr[i] ?? null)
|
||||
return out
|
||||
}
|
||||
|
||||
function periodRaw(row: Record<string, any>, idx: number): PeriodCell {
|
||||
return periodsOf(row)[idx]
|
||||
}
|
||||
|
||||
function periodDue(row: Record<string, any>, idx: number) {
|
||||
const p = periodRaw(row, idx)
|
||||
return p ? formatAmount(p.dueAmount) : '—'
|
||||
}
|
||||
|
||||
function periodPaid(row: Record<string, any>, idx: number) {
|
||||
const p = periodRaw(row, idx)
|
||||
return p ? formatAmount(p.displayPaidAmount) : '—'
|
||||
}
|
||||
|
||||
function periodHasPaid(row: Record<string, any>, idx: number) {
|
||||
const p = periodRaw(row, idx)
|
||||
if (!p) return false
|
||||
const n = Number(p.displayPaidAmount)
|
||||
return Number.isFinite(n) && n > 0
|
||||
}
|
||||
|
||||
function periodArrears(row: Record<string, any>, idx: number) {
|
||||
const p = periodRaw(row, idx)
|
||||
return p ? formatAmount(p.arrearsAmount) : '—'
|
||||
}
|
||||
|
||||
function periodField(row: Record<string, any>, idx: number, key: keyof NonNullable<PeriodCell>) {
|
||||
const p = periodRaw(row, idx)
|
||||
if (!p) return '—'
|
||||
const v = p[key]
|
||||
return v != null && v !== '' ? String(v) : '—'
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await fetchSamplePaymentList({
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
keyword: searchForm.keyword || undefined,
|
||||
payMode: searchForm.payMode || undefined,
|
||||
progressStatus: searchForm.progressStatus || undefined,
|
||||
installmentDueDateStart: dueDateRange.value?.[0] || undefined,
|
||||
installmentDueDateEnd: dueDateRange.value?.[1] || undefined
|
||||
})
|
||||
tableData.value = (res.list || []) as Record<string, any>[]
|
||||
pagination.total = res.total || 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
searchForm.keyword = ''
|
||||
searchForm.payMode = ''
|
||||
searchForm.progressStatus = ''
|
||||
dueDateRange.value = null
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
function handleViewProtocol(row: Record<string, any>) {
|
||||
protocolViewId.value = String(row.collectionProtocolId || '')
|
||||
protocolViewRow.value = {
|
||||
id: row.collectionProtocolId,
|
||||
protocolNo: row.protocolNo,
|
||||
motherName: row.motherName,
|
||||
motherPhone: row.motherPhone,
|
||||
protocolStatus: row.protocolStatus,
|
||||
voidType: row.voidType
|
||||
} as CollectionProtocolRow
|
||||
protocolVisible.value = true
|
||||
}
|
||||
|
||||
async function openPeriodDialog(row: Record<string, any>, idx: number) {
|
||||
const cell = periodRaw(row, idx)
|
||||
if (!cell?.scheduleId) return
|
||||
periodSamplePaymentId.value = String(row.id || '')
|
||||
periodDialogTitle.value = `${row.motherName || ''} · ${row.protocolNo || ''} · 第 ${idx + 1} 期转账记录`
|
||||
periodDialogVisible.value = true
|
||||
periodLoading.value = true
|
||||
periodTransfers.value = []
|
||||
periodCanConfirm.value = false
|
||||
periodSchedule.value = null
|
||||
try {
|
||||
const detail = await fetchSamplePaymentDetail(String(row.id))
|
||||
const schedules = ((detail.schedules as any[]) || []) as Record<string, any>[]
|
||||
const sch = schedules.find((s) => String(s.id) === String(cell.scheduleId))
|
||||
periodSchedule.value = sch || null
|
||||
periodSummary.dueAmount = sch?.dueAmount ?? cell.dueAmount ?? null
|
||||
// 列表 displayPaidAmount / arrearsAmount 与详情 paidAmount 已对齐(含 dfj+jfqrz+yjf)
|
||||
periodSummary.paidAmount =
|
||||
sch?.paidAmount ?? cell.displayPaidAmount ?? null
|
||||
periodSummary.arrearsAmount =
|
||||
sch?.arrearsAmount ?? cell.arrearsAmount ?? null
|
||||
periodTransfers.value = (sch?.transfers || []) as Record<string, any>[]
|
||||
const hasJfqrz = periodTransfers.value.some((t) => String(t.payStatus) === 'jfqrz')
|
||||
const arrears = Number(periodSummary.arrearsAmount)
|
||||
periodCanConfirm.value = !hasJfqrz && Number.isFinite(arrears) && arrears > 0
|
||||
} finally {
|
||||
periodLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function openConfirmFromPeriod() {
|
||||
const schedule = periodSchedule.value
|
||||
if (!schedule?.id) return
|
||||
confirming.value = true
|
||||
try {
|
||||
const transfers = (schedule.transfers || []) as Record<string, any>[]
|
||||
const dfj = transfers.find((t) => String(t.payStatus) === 'dfj')
|
||||
let transferId = dfj?.id ? String(dfj.id) : ''
|
||||
if (!transferId) {
|
||||
const arrears = Number(schedule.arrearsAmount)
|
||||
const due = Number(schedule.dueAmount)
|
||||
const amount = arrears > 0 ? arrears : due
|
||||
const created = await fetchCreatePaymentTransfer({
|
||||
scheduleId: String(schedule.id),
|
||||
transferAmount: amount
|
||||
})
|
||||
transferId = String((created as any)?.id || (created as any)?.data?.id || '')
|
||||
if (!transferId) {
|
||||
ElMessage.error('创建转账笔失败')
|
||||
return
|
||||
}
|
||||
}
|
||||
receiptTargetId.value = transferId
|
||||
receiptPreviewRow.value = {
|
||||
motherName: periodDialogTitle.value,
|
||||
installmentNo: schedule.scheduleNo,
|
||||
dueAmount: schedule.dueAmount,
|
||||
installmentDueDate: schedule.dueDate,
|
||||
scheduleId: schedule.id,
|
||||
transferAmount: schedule.arrearsAmount || schedule.dueAmount
|
||||
}
|
||||
receiptDialogVisible.value = true
|
||||
periodDialogVisible.value = false
|
||||
} finally {
|
||||
confirming.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleConfirm(row: Record<string, any>) {
|
||||
const detail = await fetchSamplePaymentDetail(String(row.id))
|
||||
const schedules = ((detail.schedules as any[]) || []) as Record<string, any>[]
|
||||
const target = [...schedules]
|
||||
.sort((a, b) => Number(a.scheduleNo || 0) - Number(b.scheduleNo || 0))
|
||||
.find((s) => {
|
||||
const transfers = (s.transfers || []) as any[]
|
||||
if (transfers.some((t) => String(t.payStatus) === 'jfqrz')) return false
|
||||
return Number(s.arrearsAmount) > 0
|
||||
})
|
||||
if (!target) {
|
||||
ElMessage.warning('暂无可确认的期次')
|
||||
return
|
||||
}
|
||||
periodSamplePaymentId.value = String(row.id)
|
||||
periodSchedule.value = target
|
||||
periodDialogTitle.value = `${row.motherName || ''} · ${row.protocolNo || ''} · 第 ${target.scheduleNo} 期`
|
||||
await openConfirmFromPeriod()
|
||||
}
|
||||
|
||||
function onReceiptRefresh() {
|
||||
loadData()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const tab = String(route.query.tab || '')
|
||||
if (['dfj', 'jfqrz', 'bfyj', 'yjq'].includes(tab)) {
|
||||
searchForm.progressStatus = tab
|
||||
}
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.excel-scroll {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.amount-link {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: var(--primary-dark, #059669);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px dashed rgba(5, 150, 105, 0.45);
|
||||
}
|
||||
|
||||
.amount-strong {
|
||||
color: var(--primary-dark, #059669);
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
}
|
||||
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--border, #d9e8e2);
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: #64748b;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
margin: 0 0 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.transfer-card {
|
||||
display: flex;
|
||||
gap: 12px 16px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--border, #d9e8e2);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.transfer-card__main {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.transfer-card__top {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px 10px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.transfer-card__meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 16px;
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.transfer-card__remark {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.transfer-card__attach {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
max-width: 52%;
|
||||
}
|
||||
|
||||
.transfer-card__attach-label {
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.attach-preview-img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
:deep(.period-col--1) {
|
||||
background: #eff6ff;
|
||||
}
|
||||
:deep(.period-col--2) {
|
||||
background: #fff7ed;
|
||||
}
|
||||
:deep(.period-col--3) {
|
||||
background: #fefce8;
|
||||
}
|
||||
:deep(.period-col--4) {
|
||||
background: #f0fdf4;
|
||||
}
|
||||
:deep(.period-col--5) {
|
||||
background: #fff7ed;
|
||||
}
|
||||
:deep(.period-col--6) {
|
||||
background: #ecfdf5;
|
||||
}
|
||||
</style>
|
||||
@@ -4,17 +4,19 @@
|
||||
<el-dialog
|
||||
:model-value="modelValue"
|
||||
:title="orderId ? '编辑预签约订单' : '新增预签约订单'"
|
||||
width="920px"
|
||||
align-center
|
||||
destroy-on-close
|
||||
width="1200px"
|
||||
class="dialog-form-layout"
|
||||
modal-class="order-module-dialog-mask"
|
||||
align-center
|
||||
append-to-body
|
||||
modal-class="protocol-dialog-mask"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
@closed="resetForm"
|
||||
>
|
||||
<DialogFormBody v-loading="loading">
|
||||
<el-form ref="formRef" :model="form" label-position="top" scroll-to-error>
|
||||
<div class="form-grid-2">
|
||||
<div class="form-grid-3">
|
||||
<el-form-item label="业务日期">
|
||||
<el-date-picker
|
||||
v-model="form.orderDate"
|
||||
@@ -75,6 +77,52 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="采集专员">
|
||||
<el-select
|
||||
v-model="form.collectorUserId"
|
||||
class="w-full"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
reserve-keyword
|
||||
:remote-method="remoteSearchCollectorUsers"
|
||||
:loading="collectorLoading"
|
||||
:disabled="isConverted"
|
||||
placeholder="输入姓名检索用户"
|
||||
style="width: 100%"
|
||||
@change="onCollectorUserChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="u in collectorUserOptions"
|
||||
:key="u.id"
|
||||
:label="u.nickName + (u.phoneNo ? ' · ' + u.phoneNo : '')"
|
||||
:value="u.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="采集经理">
|
||||
<el-select
|
||||
v-model="form.regionManagerUserId"
|
||||
class="w-full"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
reserve-keyword
|
||||
:remote-method="remoteSearchManagerUsers"
|
||||
:loading="managerLoading"
|
||||
:disabled="isConverted"
|
||||
placeholder="默认取地区负责人,可修改"
|
||||
style="width: 100%"
|
||||
@change="onManagerUserChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="u in managerUserOptions"
|
||||
:key="u.id"
|
||||
:label="u.nickName + (u.phoneNo ? ' · ' + u.phoneNo : '')"
|
||||
:value="u.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="采集时间">
|
||||
<el-date-picker
|
||||
v-model="form.sampleCollectTime"
|
||||
@@ -102,10 +150,11 @@
|
||||
<el-select v-model="form.orderStatus" style="width: 100%" :disabled="isConverted">
|
||||
<el-option label="草稿" value="draft" />
|
||||
<el-option label="进行中" value="active" />
|
||||
<el-option label="已转正式" value="converted" />
|
||||
<el-option label="已作废" value="void" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="样本类型" class="form-col-span-2" required>
|
||||
<el-form-item label="样本类型" required>
|
||||
<el-select
|
||||
v-model="form.sampleTypeIds"
|
||||
multiple
|
||||
@@ -122,19 +171,19 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
placeholder="选填"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="section-subtitle">意向金转账</div>
|
||||
<div style="margin-bottom: 10px">
|
||||
<div class="transfer-toolbar">
|
||||
<el-button type="primary" size="small" :disabled="isConverted" @click="addTransfer">
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加转账笔
|
||||
@@ -143,70 +192,176 @@
|
||||
已确认意向金合计 ¥{{ formatMoneyDisplay(confirmedDeposit) }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="(tr, idx) in form.transfers"
|
||||
:key="tr.id || idx"
|
||||
class="transfer-card form-grid-2"
|
||||
<el-table
|
||||
:data="form.transfers"
|
||||
class="list-table transfer-table"
|
||||
stripe
|
||||
empty-text="暂无意向金转账笔,请点击上方按钮添加"
|
||||
>
|
||||
<el-form-item label="转账方式">
|
||||
<el-select
|
||||
v-model="tr.transferType"
|
||||
:disabled="isTransferLocked(tr) || isConverted"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in transferTypeOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
<el-table-column type="index" label="#" min-width="50" align="center" />
|
||||
<el-table-column label="转账方式" min-width="140" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.transferType"
|
||||
:disabled="isTransferLocked(row) || isConverted"
|
||||
placeholder="请选择"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in transferTypeOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="转账金额(元)" min-width="140" align="center">
|
||||
<template #default="{ row }">
|
||||
<AmountInput
|
||||
v-model="row.transferAmount"
|
||||
:min="0"
|
||||
:disabled="isTransferLocked(row) || isConverted"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="转账金额(元)">
|
||||
<AmountInput
|
||||
v-model="tr.transferAmount"
|
||||
:min="0"
|
||||
:disabled="isTransferLocked(tr) || isConverted"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="转账时间">
|
||||
<el-date-picker
|
||||
v-model="tr.transferTime"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:disabled="isTransferLocked(tr) || isConverted"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态 / 操作">
|
||||
<el-tag :type="payStatusTagType(String(tr.payStatus || 'dfj'))" size="small">
|
||||
{{ payStatusLabel(String(tr.payStatus || 'dfj')) }}
|
||||
</el-tag>
|
||||
<el-button
|
||||
v-if="tr.id && tr.payStatus === 'dfj' && !isConverted"
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
style="margin-left: 8px"
|
||||
@click="submitTransfer(tr)"
|
||||
>
|
||||
提交财务确认
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!isTransferLocked(tr) && !isConverted"
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
style="margin-left: 4px"
|
||||
@click="form.transfers.splice(idx, 1)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-empty v-if="!form.transfers.length" description="暂无意向金转账笔" :image-size="40" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="转账时间" min-width="190" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-date-picker
|
||||
v-model="row.transferTime"
|
||||
type="datetime"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:disabled="isTransferLocked(row) || isConverted"
|
||||
clearable
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="转账凭证" min-width="280" align="left">
|
||||
<template #default="{ row, $index }">
|
||||
<div class="attachment-upload">
|
||||
<div class="attachment-list">
|
||||
<el-upload
|
||||
v-if="!isTransferLocked(row) && !isConverted"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:multiple="true"
|
||||
accept="image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip,.rar"
|
||||
@change="(f: any) => handleTransferAttachmentChange($index, f)"
|
||||
>
|
||||
<div class="upload-card">
|
||||
<el-icon :size="22" color="#047857"><Plus /></el-icon>
|
||||
<span class="upload-card-text">上传凭证</span>
|
||||
</div>
|
||||
</el-upload>
|
||||
<div
|
||||
v-for="(file, fIdx) in row.attachments || []"
|
||||
:key="file.id || fIdx"
|
||||
class="attachment-item"
|
||||
:class="{ 'is-uploading': file._uploading }"
|
||||
>
|
||||
<div class="attachment-preview">
|
||||
<template v-if="file.fileUrl && isImageUrl(file.fileUrl)">
|
||||
<img :src="fullFileUrl(file.fileUrl)" :alt="file.fileName" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="attachment-preview__icon">
|
||||
<el-icon :size="22"><Document /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="attachment-footer">
|
||||
<span class="attachment-name" :title="file.fileName">{{
|
||||
file.fileName || '附件'
|
||||
}}</span>
|
||||
<el-icon
|
||||
v-if="!isTransferLocked(row) && !isConverted"
|
||||
class="action-btn"
|
||||
title="删除"
|
||||
@click="removeTransferAttachment($index, fIdx)"
|
||||
>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="160" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-if="!isTransferLocked(row) && !isConverted"
|
||||
v-model="row.remark"
|
||||
maxlength="512"
|
||||
clearable
|
||||
placeholder="选填"
|
||||
/>
|
||||
<span v-else>{{ row.remark || '—' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="财务备注"
|
||||
min-width="140"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ row.financeRemark || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="payStatusTagType(String(row.payStatus || 'dfj'))" size="small">
|
||||
{{ payStatusLabel(String(row.payStatus || 'dfj')) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="220" align="center" fixed="right">
|
||||
<template #default="{ row, $index }">
|
||||
<el-button
|
||||
v-if="row.id && row.payStatus === 'dfj' && !isConverted"
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="submitTransfer(row)"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
提交财务确认
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.id && row.payStatus === 'jfqrz' && !isConverted"
|
||||
link
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="withdrawTransfer(row)"
|
||||
>
|
||||
<el-icon><RefreshLeft /></el-icon>
|
||||
撤回
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!isTransferLocked(row) && !isConverted"
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="form.transfers.splice($index, 1)"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
删除
|
||||
</el-button>
|
||||
<span
|
||||
v-if="
|
||||
isConverted ||
|
||||
(isTransferLocked(row) &&
|
||||
!(row.id && (row.payStatus === 'dfj' || row.payStatus === 'jfqrz')))
|
||||
"
|
||||
class="muted-dash"
|
||||
>—</span
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form>
|
||||
</DialogFormBody>
|
||||
|
||||
@@ -231,7 +386,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { Plus, Close, Check } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, Document, Delete, RefreshLeft } from '@element-plus/icons-vue'
|
||||
import AmountInput from '@/components/common/amount-input/index.vue'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
import {
|
||||
@@ -241,10 +396,14 @@
|
||||
getTransferTypeOptions
|
||||
} from '@/utils/protocol-payment-labels'
|
||||
import DialogFormBody from '@/components/business/dialog-form-body/index.vue'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { fetchUploadAttachment } from '@/api/business'
|
||||
import {
|
||||
fetchCellSampleTypeList,
|
||||
fetchCollectionRegionList,
|
||||
fetchHospitalList
|
||||
fetchHospitalList,
|
||||
fetchRegionStaffList,
|
||||
fetchUserList
|
||||
} from '@/api/admin'
|
||||
import {
|
||||
fetchPresignOrderDetail,
|
||||
@@ -252,9 +411,12 @@
|
||||
fetchUpdatePresignOrder,
|
||||
fetchCreatePresignTransfer,
|
||||
fetchUpdatePresignTransfer,
|
||||
fetchSubmitPresignTransferFinance
|
||||
fetchSubmitPresignTransferFinance,
|
||||
fetchWithdrawPresignTransfer
|
||||
} from '@/api/presign-order'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
orderId?: string
|
||||
@@ -272,14 +434,71 @@
|
||||
const sampleTypeOptions = ref<Array<Record<string, any>>>([])
|
||||
const regionOptions = ref<Array<Record<string, any>>>([])
|
||||
const hospitalOptions = ref<Array<Record<string, any>>>([])
|
||||
const collectorUserOptions = ref<Api.User.UserItem[]>([])
|
||||
const managerUserOptions = ref<Api.User.UserItem[]>([])
|
||||
const collectorLoading = ref(false)
|
||||
const managerLoading = ref(false)
|
||||
|
||||
function todayYmd() {
|
||||
const d = new Date()
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${y}${m}${day}`
|
||||
}
|
||||
|
||||
function userStubFromStore(): Api.User.UserItem | null {
|
||||
const currentUser = userStore.getUserInfo
|
||||
const uid = String(currentUser?.id || currentUser?.userId || '')
|
||||
if (!uid) return null
|
||||
return {
|
||||
id: uid,
|
||||
nickName: String(
|
||||
currentUser?.realName || currentUser?.userName || currentUser?.username || ''
|
||||
),
|
||||
phoneNo: String(currentUser?.phone || currentUser?.phoneNo || ''),
|
||||
deptId: '',
|
||||
userNo: '',
|
||||
email: '',
|
||||
sex: '',
|
||||
avatar: '',
|
||||
status: '1',
|
||||
tenantNo: '',
|
||||
orderNum: 0,
|
||||
remark: '',
|
||||
createTime: '',
|
||||
updateTime: ''
|
||||
} as Api.User.UserItem
|
||||
}
|
||||
|
||||
function makeUserStub(id: string, name: string): Api.User.UserItem {
|
||||
return {
|
||||
id,
|
||||
nickName: name || id,
|
||||
phoneNo: '',
|
||||
deptId: '',
|
||||
userNo: '',
|
||||
email: '',
|
||||
sex: '',
|
||||
avatar: '',
|
||||
status: '1',
|
||||
tenantNo: '',
|
||||
orderNum: 0,
|
||||
remark: '',
|
||||
createTime: '',
|
||||
updateTime: ''
|
||||
} as Api.User.UserItem
|
||||
}
|
||||
|
||||
const form = reactive({
|
||||
orderDate: '',
|
||||
orderDate: todayYmd(),
|
||||
protocolNo: '',
|
||||
motherName: '',
|
||||
expectedDeliveryDate: '',
|
||||
regionId: '',
|
||||
hospitalId: '',
|
||||
collectorUserId: '',
|
||||
regionManagerUserId: '',
|
||||
sampleCollectTime: '',
|
||||
payMode: 'dj',
|
||||
contractAmount: null as number | null,
|
||||
@@ -297,13 +516,106 @@
|
||||
return tr.payStatus === 'jfqrz' || tr.payStatus === 'yjf'
|
||||
}
|
||||
|
||||
type TransferAttachment = {
|
||||
id?: string
|
||||
fileName?: string
|
||||
fileUrl?: string
|
||||
_uploading?: boolean
|
||||
}
|
||||
|
||||
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
||||
|
||||
function fullFileUrl(url: string) {
|
||||
if (!url) return ''
|
||||
if (/^https?:\/\//i.test(url)) return url
|
||||
return minioBaseUrl ? `${minioBaseUrl}/${url.replace(/^\//, '')}` : url
|
||||
}
|
||||
|
||||
function isImageUrl(url: string) {
|
||||
return /\.(png|jpe?g|gif|webp|bmp|svg)(\?.*)?$/i.test(url)
|
||||
}
|
||||
|
||||
function attachmentsToUrls(files: TransferAttachment[] | undefined) {
|
||||
return (files || [])
|
||||
.filter((f) => f.fileUrl && !f._uploading)
|
||||
.map((f) => String(f.fileUrl))
|
||||
.join(',')
|
||||
}
|
||||
|
||||
function normalizeTransfer(row: Record<string, any>) {
|
||||
const files = Array.isArray(row.attachments) ? row.attachments : []
|
||||
return {
|
||||
...row,
|
||||
attachments: files.map((f: Record<string, any>, i: number) => ({
|
||||
id: f.id || `att_${i}`,
|
||||
fileName: f.fileName || f.name || '附件',
|
||||
fileUrl: f.fileUrl || f.url || ''
|
||||
})) as TransferAttachment[]
|
||||
}
|
||||
}
|
||||
|
||||
async function handleTransferAttachmentChange(index: number, uploadFile: { raw?: File }) {
|
||||
const file = uploadFile.raw
|
||||
if (!file) return
|
||||
if (file.size > 20 * 1024 * 1024) {
|
||||
ElMessage.warning('单文件不超过 20MB')
|
||||
return
|
||||
}
|
||||
const row = form.transfers[index]
|
||||
if (!row) return
|
||||
if (!Array.isArray(row.attachments)) row.attachments = []
|
||||
const tempId = `temp_${Date.now()}_${Math.random().toString(16).slice(2)}`
|
||||
row.attachments.push({
|
||||
id: tempId,
|
||||
fileName: file.name,
|
||||
fileUrl: '',
|
||||
_uploading: true
|
||||
})
|
||||
const idx = row.attachments.length - 1
|
||||
try {
|
||||
const fd = new FormData()
|
||||
fd.append('folder', 'presign-transfer')
|
||||
fd.append('file', file)
|
||||
const res: any = await fetchUploadAttachment(fd)
|
||||
const data = res?.data || res
|
||||
row.attachments[idx] = {
|
||||
id: data?.id || tempId,
|
||||
fileName: data?.fileName || file.name,
|
||||
fileUrl: data?.fileUrl || data?.url || '',
|
||||
_uploading: false
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('文件上传失败')
|
||||
row.attachments.splice(idx, 1)
|
||||
}
|
||||
}
|
||||
|
||||
function removeTransferAttachment(rowIndex: number, fileIndex: number) {
|
||||
const row = form.transfers[rowIndex]
|
||||
if (!row?.attachments) return
|
||||
row.attachments.splice(fileIndex, 1)
|
||||
}
|
||||
|
||||
function applyDefaultCollector() {
|
||||
const stub = userStubFromStore()
|
||||
if (!stub) {
|
||||
form.collectorUserId = ''
|
||||
collectorUserOptions.value = []
|
||||
return
|
||||
}
|
||||
form.collectorUserId = stub.id
|
||||
collectorUserOptions.value = [stub]
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
form.orderDate = ''
|
||||
form.orderDate = todayYmd()
|
||||
form.protocolNo = ''
|
||||
form.motherName = ''
|
||||
form.expectedDeliveryDate = ''
|
||||
form.regionId = ''
|
||||
form.hospitalId = ''
|
||||
form.collectorUserId = ''
|
||||
form.regionManagerUserId = ''
|
||||
form.sampleCollectTime = ''
|
||||
form.payMode = 'dj'
|
||||
form.contractAmount = null
|
||||
@@ -312,6 +624,90 @@
|
||||
form.remark = ''
|
||||
form.transfers = []
|
||||
form.confirmedDepositAmount = 0
|
||||
collectorUserOptions.value = []
|
||||
managerUserOptions.value = []
|
||||
applyDefaultCollector()
|
||||
}
|
||||
|
||||
async function remoteSearchCollectorUsers(keyword: string) {
|
||||
collectorLoading.value = true
|
||||
try {
|
||||
const res = await fetchUserList({
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
keyword: (keyword || '').trim() || undefined,
|
||||
status: '1'
|
||||
})
|
||||
let list = res.list || []
|
||||
const cur = form.collectorUserId
|
||||
if (cur && !list.some((x) => x.id === cur)) {
|
||||
const keep = collectorUserOptions.value.find((x) => x.id === cur)
|
||||
if (keep) list = [keep, ...list]
|
||||
}
|
||||
collectorUserOptions.value = list
|
||||
} catch {
|
||||
collectorUserOptions.value = []
|
||||
} finally {
|
||||
collectorLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function remoteSearchManagerUsers(keyword: string) {
|
||||
managerLoading.value = true
|
||||
try {
|
||||
const res = await fetchUserList({
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
keyword: (keyword || '').trim() || undefined,
|
||||
status: '1'
|
||||
})
|
||||
let list = res.list || []
|
||||
const cur = form.regionManagerUserId
|
||||
if (cur && !list.some((x) => x.id === cur)) {
|
||||
const keep = managerUserOptions.value.find((x) => x.id === cur)
|
||||
if (keep) list = [keep, ...list]
|
||||
}
|
||||
managerUserOptions.value = list
|
||||
} catch {
|
||||
managerUserOptions.value = []
|
||||
} finally {
|
||||
managerLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onCollectorUserChange(val: string | null) {
|
||||
form.collectorUserId = val || ''
|
||||
}
|
||||
|
||||
function onManagerUserChange(val: string | null) {
|
||||
form.regionManagerUserId = val || ''
|
||||
}
|
||||
|
||||
async function applyDefaultRegionManager(regionId: string) {
|
||||
if (!regionId) {
|
||||
form.regionManagerUserId = ''
|
||||
managerUserOptions.value = []
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res: any = await fetchRegionStaffList(regionId, { page: 1, pageSize: 100 })
|
||||
const list = (res?.list || []) as Array<Record<string, any>>
|
||||
const leader = list.find((x) => x.isLeader) || list[0]
|
||||
if (!leader?.userId) {
|
||||
form.regionManagerUserId = ''
|
||||
return
|
||||
}
|
||||
const stub = makeUserStub(
|
||||
String(leader.userId),
|
||||
String(leader.nickName || leader.userName || '')
|
||||
)
|
||||
form.regionManagerUserId = stub.id
|
||||
if (!managerUserOptions.value.some((x) => x.id === stub.id)) {
|
||||
managerUserOptions.value = [stub, ...managerUserOptions.value]
|
||||
}
|
||||
} catch {
|
||||
/* 地区无人员配置时留空,可手工选择 */
|
||||
}
|
||||
}
|
||||
|
||||
function addTransfer() {
|
||||
@@ -319,7 +715,10 @@
|
||||
transferType: '',
|
||||
transferAmount: null,
|
||||
transferTime: '',
|
||||
payStatus: 'dfj'
|
||||
payStatus: 'dfj',
|
||||
remark: '',
|
||||
financeRemark: '',
|
||||
attachments: [] as TransferAttachment[]
|
||||
})
|
||||
}
|
||||
|
||||
@@ -327,8 +726,8 @@
|
||||
await loadTransferTypeDict()
|
||||
transferTypeOptions.value = getTransferTypeOptions()
|
||||
const [types, regions] = await Promise.all([
|
||||
fetchCellSampleTypeList({ page: 1, pageSize: 200 }),
|
||||
fetchCollectionRegionList({ page: 1, pageSize: 200 })
|
||||
fetchCellSampleTypeList({ page: 1, pageSize: 100 }),
|
||||
fetchCollectionRegionList({ page: 1, pageSize: 100 })
|
||||
])
|
||||
sampleTypeOptions.value = (types as any)?.list || (types as any) || []
|
||||
regionOptions.value = (regions as any)?.list || (regions as any) || []
|
||||
@@ -337,13 +736,13 @@
|
||||
async function loadHospitals() {
|
||||
hospitalOptions.value = []
|
||||
if (!form.regionId) return
|
||||
const res: any = await fetchHospitalList(form.regionId, { page: 1, pageSize: 200 })
|
||||
const res: any = await fetchHospitalList(form.regionId, { page: 1, pageSize: 100 })
|
||||
hospitalOptions.value = res?.list || []
|
||||
}
|
||||
|
||||
async function onRegionChange() {
|
||||
form.hospitalId = ''
|
||||
await loadHospitals()
|
||||
await Promise.all([loadHospitals(), applyDefaultRegionManager(form.regionId)])
|
||||
}
|
||||
|
||||
async function loadDetail() {
|
||||
@@ -357,14 +756,28 @@
|
||||
form.expectedDeliveryDate = String(data.expectedDeliveryDate || '')
|
||||
form.regionId = String(data.regionId || '')
|
||||
form.hospitalId = String(data.hospitalId || '')
|
||||
form.collectorUserId = String(data.collectorUserId || '')
|
||||
form.regionManagerUserId = String(data.regionManagerUserId || '')
|
||||
form.sampleCollectTime = String(data.sampleCollectTime || '')
|
||||
form.payMode = String(data.payMode || 'dj')
|
||||
form.contractAmount = data.contractAmount ?? null
|
||||
form.orderStatus = String(data.orderStatus || 'active')
|
||||
form.sampleTypeIds = Array.isArray(data.sampleTypeIds) ? [...data.sampleTypeIds] : []
|
||||
form.remark = String(data.remark || '')
|
||||
form.transfers = Array.isArray(data.transfers) ? [...data.transfers] : []
|
||||
form.transfers = Array.isArray(data.transfers)
|
||||
? data.transfers.map((t: Record<string, any>) => normalizeTransfer(t))
|
||||
: []
|
||||
form.confirmedDepositAmount = Number(data.confirmedDepositAmount || 0)
|
||||
if (form.collectorUserId) {
|
||||
collectorUserOptions.value = [
|
||||
makeUserStub(form.collectorUserId, String(data.collectorUserName || ''))
|
||||
]
|
||||
}
|
||||
if (form.regionManagerUserId) {
|
||||
managerUserOptions.value = [
|
||||
makeUserStub(form.regionManagerUserId, String(data.regionManagerUserName || ''))
|
||||
]
|
||||
}
|
||||
if (form.regionId) await loadHospitals()
|
||||
} finally {
|
||||
loading.value = false
|
||||
@@ -380,7 +793,9 @@
|
||||
await fetchUpdatePresignTransfer(String(tr.id), {
|
||||
transferType: tr.transferType,
|
||||
transferAmount: tr.transferAmount,
|
||||
transferTime: tr.transferTime || undefined
|
||||
transferTime: tr.transferTime || undefined,
|
||||
transferAttachments: attachmentsToUrls(tr.attachments) || undefined,
|
||||
remark: tr.remark || undefined
|
||||
})
|
||||
}
|
||||
await fetchSubmitPresignTransferFinance(String(tr.id))
|
||||
@@ -388,6 +803,18 @@
|
||||
ElMessage.success('已提交财务确认')
|
||||
}
|
||||
|
||||
async function withdrawTransfer(tr: Record<string, any>) {
|
||||
if (!tr.id) return
|
||||
await ElMessageBox.confirm(
|
||||
'确认撤回该笔意向金的财务确认申请?撤回后可修改并重新提交。',
|
||||
'撤回',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
await fetchWithdrawPresignTransfer(String(tr.id))
|
||||
tr.payStatus = 'dfj'
|
||||
ElMessage.success('已撤回')
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (!form.motherName.trim()) {
|
||||
ElMessage.warning('请填写母亲姓名')
|
||||
@@ -407,6 +834,8 @@
|
||||
regionId: form.regionId || undefined,
|
||||
hospitalId: form.hospitalId || undefined,
|
||||
sampleCollectTime: form.sampleCollectTime || undefined,
|
||||
collectorUserId: form.collectorUserId || undefined,
|
||||
regionManagerUserId: form.regionManagerUserId || undefined,
|
||||
payMode: form.payMode || undefined,
|
||||
contractAmount: form.contractAmount ?? undefined,
|
||||
orderStatus: form.orderStatus || 'active',
|
||||
@@ -420,13 +849,17 @@
|
||||
await fetchCreatePresignTransfer(props.orderId, {
|
||||
transferType: tr.transferType,
|
||||
transferAmount: tr.transferAmount,
|
||||
transferTime: tr.transferTime || undefined
|
||||
transferTime: tr.transferTime || undefined,
|
||||
transferAttachments: attachmentsToUrls(tr.attachments) || undefined,
|
||||
remark: tr.remark || undefined
|
||||
})
|
||||
} else if (tr.id && tr.payStatus === 'dfj') {
|
||||
await fetchUpdatePresignTransfer(String(tr.id), {
|
||||
transferType: tr.transferType,
|
||||
transferAmount: tr.transferAmount,
|
||||
transferTime: tr.transferTime || undefined
|
||||
transferTime: tr.transferTime || undefined,
|
||||
transferAttachments: attachmentsToUrls(tr.attachments) || undefined,
|
||||
remark: tr.remark || undefined
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -436,7 +869,9 @@
|
||||
.map((t) => ({
|
||||
transferType: t.transferType,
|
||||
transferAmount: t.transferAmount,
|
||||
transferTime: t.transferTime || undefined
|
||||
transferTime: t.transferTime || undefined,
|
||||
transferAttachments: attachmentsToUrls(t.attachments) || undefined,
|
||||
remark: t.remark || undefined
|
||||
}))
|
||||
await fetchCreatePresignOrder(payload)
|
||||
}
|
||||
@@ -459,16 +894,6 @@
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.form-grid-2 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px 12px;
|
||||
}
|
||||
|
||||
.form-col-span-2 {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
margin: 16px 0 8px;
|
||||
font-size: 13px;
|
||||
@@ -476,17 +901,114 @@
|
||||
color: var(--mint-text-secondary, #475569);
|
||||
}
|
||||
|
||||
.transfer-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.deposit-hint {
|
||||
margin-left: 12px;
|
||||
font-size: 12px;
|
||||
color: #047857;
|
||||
}
|
||||
|
||||
.transfer-card {
|
||||
margin-bottom: 10px;
|
||||
padding: 10px 12px;
|
||||
.transfer-table {
|
||||
width: 100%;
|
||||
|
||||
:deep(.el-date-editor) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.muted-dash {
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
}
|
||||
|
||||
.attachment-upload {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.attachment-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.upload-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 88px;
|
||||
height: 72px;
|
||||
cursor: pointer;
|
||||
border: 1px dashed #a7f3d0;
|
||||
border-radius: 8px;
|
||||
background: #ecfdf5;
|
||||
}
|
||||
|
||||
.upload-card-text {
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
color: #047857;
|
||||
}
|
||||
|
||||
.attachment-item {
|
||||
width: 88px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
|
||||
&.is-uploading {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 88px;
|
||||
height: 52px;
|
||||
background: #f8fafc;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-preview__icon {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.attachment-footer {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
.attachment-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
color: #94a3b8;
|
||||
|
||||
&:hover {
|
||||
color: #dc2626;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user