Files
shanxia2.0/web-admin/src/views/cell-storage/payment-confirm/index.vue
T
2026-05-17 12:31:11 +08:00

445 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 缴费确认(财务) -->
<!-- eslint-disable vue/no-v-model-argument -->
<template>
<div class="page-container">
<header class="page-head">
<h1>缴费确认</h1>
<p class="page-desc">
待确认:处理「缴费确认中」明细;已确认:查看财务确认收款记录及当时填写的备注与附件。
</p>
</header>
<div class="region-tabs region-tab-strip">
<div class="tab-button-group region-tab-group">
<el-button
v-for="tab in tabs"
:key="tab.key"
class="region-tab-btn"
:type="activeTab === tab.key ? 'primary' : 'default'"
@click="switchTab(tab.key)"
>
<span class="region-tab-inner">
<span class="region-tab-name">{{ tab.label }}</span>
</span>
</el-button>
</div>
</div>
<section class="panel">
<div class="panel-top">
<h2 class="title-bar-heading">缴费确认列表</h2>
</div>
<div class="list-body list-wrap payment-confirm-table-wrap">
<el-table class="list-table" :data="tableData" v-loading="loading" stripe row-key="id">
<el-table-column
type="index"
label="序号"
width="68"
align="center"
header-align="center"
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
/>
<el-table-column prop="protocolNo" label="协议号" min-width="130" align="center" header-align="center" />
<el-table-column prop="motherName" label="客户姓名" min-width="100" align="center" header-align="center">
<template #default="{ row }">{{ row.motherName || '-' }}</template>
</el-table-column>
<el-table-column prop="motherIdCard" label="身份证号" min-width="170" align="center" header-align="center">
<template #default="{ row }">{{ maskIdCard(row.motherIdCard) }}</template>
</el-table-column>
<el-table-column prop="newbornName" label="新生儿姓名" min-width="110" align="center" header-align="center">
<template #default="{ row }">{{ row.newbornName || '-' }}</template>
</el-table-column>
<el-table-column
prop="newbornIdCard"
label="新生儿身份证号"
min-width="170"
align="center"
header-align="center"
>
<template #default="{ row }">{{ maskIdCard(row.newbornIdCard) }}</template>
</el-table-column>
<el-table-column prop="protocolStatus" label="协议状态" width="100" align="center" header-align="center">
<template #default="{ row }">
{{ protocolStatusLabel(String(row.protocolStatus || '')) }}
</template>
</el-table-column>
<el-table-column prop="payMode" label="缴费方式" width="100" align="center" header-align="center">
<template #default="{ row }">{{ payModeLabel(String(row.payMode || '')) }}</template>
</el-table-column>
<el-table-column prop="installmentNo" label="期数" width="80" align="center" header-align="center">
<template #default="{ row }">{{ row.installmentNo ?? '-' }}</template>
</el-table-column>
<el-table-column prop="installmentDueDate" label="应缴费时间" width="120" align="center" header-align="center">
<template #default="{ row }">{{ formatPaymentDueDate(row.installmentDueDate) }}</template>
</el-table-column>
<el-table-column prop="dueAmount" label="应缴金额(元)" width="120" align="center" header-align="center">
<template #default="{ row }">{{ formatPaymentAmount(row.dueAmount) }}</template>
</el-table-column>
<el-table-column prop="transferAmount" label="实缴金额(元)" width="120" align="center" header-align="center">
<template #default="{ row }">{{ formatPaymentAmount(row.transferAmount) }}</template>
</el-table-column>
<el-table-column prop="transferType" label="转账方式" width="110" align="center" header-align="center">
<template #default="{ row }">{{ transferTypeLabel(String(row.transferType || '')) }}</template>
</el-table-column>
<el-table-column prop="transferTime" label="转账时间" min-width="170" align="center" header-align="center">
<template #default="{ row }">{{ row.transferTime || '-' }}</template>
</el-table-column>
<el-table-column prop="payStatus" label="状态" width="110" align="center" header-align="center">
<template #default>
{{ activeTab === 'pending' ? '缴费确认中' : '已缴费' }}
</template>
</el-table-column>
<el-table-column
v-if="activeTab === 'confirmed'"
prop="updateTime"
label="确认时间"
min-width="170"
align="center"
header-align="center"
/>
<el-table-column
v-if="activeTab === 'confirmed'"
prop="financeRemark"
label="财务备注"
min-width="120"
align="center"
header-align="center"
show-overflow-tooltip
>
<template #default="{ row }">{{ row.financeRemark || '-' }}</template>
</el-table-column>
<el-table-column
v-if="activeTab === 'pending'"
prop="createTime"
label="创建时间"
min-width="170"
align="center"
header-align="center"
/>
<el-table-column label="操作" width="140" fixed="right" align="center" header-align="center">
<template #default="{ row }">
<el-button
v-if="activeTab === 'pending'"
class="btn-op-item primary"
@click="openConfirm(row)"
>
确认收款
</el-button>
<el-button v-else class="btn-op-item" @click="openViewConfirm(row)">查看确认信息</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="dialogVisible"
title="确认收款"
width="900px"
class="dialog-form-layout payment-confirm-dialog"
align-center
destroy-on-close
:close-on-click-modal="false"
>
<ProtocolPaymentDetailReadonly
:detail="confirmRow"
title="缴费明细(请核对)"
@preview="handleAttachmentPreview"
@download="handleAttachmentDownload"
/>
<div class="form-section-divider" />
<h4 class="detail-section-title">财务确认</h4>
<el-form label-position="top" class="biz-dialog-form">
<ProtocolFinanceConfirmFields
v-model:finance-remark="financeRemark"
v-model:attachments="attachments"
upload-folder="protocol-finance-receipt"
/>
</el-form>
<template #footer>
<el-button class="btn-ghost" @click="dialogVisible = false">
<el-icon><Close /></el-icon>
取消
</el-button>
<el-button class="btn-main" type="primary" :loading="submitLoading" @click="submitConfirm">
<el-icon><Check /></el-icon>
确认
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="viewDialogVisible"
title="确认收款信息"
width="900px"
class="dialog-form-layout payment-confirm-dialog"
align-center
destroy-on-close
>
<ProtocolPaymentDetailReadonly
:detail="viewRow"
title="缴费明细"
@preview="handleAttachmentPreview"
@download="handleAttachmentDownload"
/>
<div class="form-section-divider" />
<h4 class="detail-section-title">财务确认信息</h4>
<el-form label-position="top" class="biz-dialog-form view-confirm-form">
<div class="form-grid-2">
<el-form-item label="确认时间" class="form-col-span-2">
<span class="view-field-text">{{ viewRow?.updateTime || '-' }}</span>
</el-form-item>
<el-form-item label="财务备注" class="form-col-span-2">
<span class="view-field-text view-field-text--multiline">{{
viewRow?.financeRemark || '—'
}}</span>
</el-form-item>
<el-form-item label="财务确认附件" class="form-col-span-2">
<ProtocolAttachmentReadonly
:files="viewFinanceAttachmentFiles"
@preview="handleAttachmentPreview"
@download="handleAttachmentDownload"
/>
</el-form-item>
</div>
</el-form>
<template #footer>
<el-button class="btn-ghost" @click="viewDialogVisible = false">
<el-icon><Close /></el-icon>
关闭
</el-button>
</template>
</el-dialog>
</section>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { Close, Check } from '@element-plus/icons-vue'
import {
fetchReceiptConfirmList,
fetchReceiptConfirmedList,
fetchReceiptConfirmSubmit
} from '@/api/collection-protocol'
import ProtocolFinanceConfirmFields from '@/components/business/protocol-finance-confirm-fields/index.vue'
import ProtocolPaymentDetailReadonly from '@/components/business/protocol-payment-detail-readonly/index.vue'
import ProtocolAttachmentReadonly from '../protocol/ProtocolAttachmentReadonly.vue'
import {
financeAttachmentsToUrlString,
type FinanceAttachmentItem
} from '@/components/business/protocol-finance-confirm-fields/finance-attachments'
import { maskIdCard } from '@/utils/mask-sensitive'
import {
formatPaymentAmount,
formatPaymentDueDate,
payModeLabel,
protocolStatusLabel,
transferTypeLabel
} from '@/utils/protocol-payment-labels'
defineOptions({ name: 'CellStoragePaymentConfirm' })
const tabs = [
{ key: 'pending', label: '待确认' },
{ key: 'confirmed', label: '已确认' }
]
const activeTab = ref('pending')
const tableData = ref<Record<string, unknown>[]>([])
const loading = ref(false)
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
const dialogVisible = ref(false)
const viewDialogVisible = ref(false)
const submitLoading = ref(false)
const financeRemark = ref('')
const attachments = ref<FinanceAttachmentItem[]>([])
const currentDetailId = ref<string | null>(null)
const confirmRow = ref<Record<string, unknown> | null>(null)
const viewRow = ref<Record<string, unknown> | null>(null)
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
const viewFinanceAttachmentFiles = computed(() => {
const row = viewRow.value
if (!row) return []
if (Array.isArray(row.financeConfirmAttachmentFiles)) {
return row.financeConfirmAttachmentFiles as Array<Record<string, unknown>>
}
const urls = String(row.financeConfirmAttachments || '')
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 }
})
.filter(Boolean) as Array<Record<string, unknown>>
})
function fullFileUrl(url: string) {
if (!url) return ''
if (/^https?:\/\//i.test(url)) return url
return minioBaseUrl ? `${minioBaseUrl}/${url.replace(/^\//, '')}` : url
}
function handleAttachmentPreview(file: Record<string, unknown>) {
const url = String(file.fileUrl || '')
if (!url) return
window.open(fullFileUrl(url), '_blank')
}
function handleAttachmentDownload(file: Record<string, unknown>) {
const url = String(file.fileUrl || '')
if (!url) return
const a = document.createElement('a')
a.href = fullFileUrl(url)
a.download = String(file.fileName || 'download')
a.target = '_blank'
a.rel = 'noopener'
a.click()
}
async function loadData() {
loading.value = true
try {
const params = { page: pagination.page, pageSize: pagination.pageSize }
const res =
activeTab.value === 'pending'
? await fetchReceiptConfirmList(params)
: await fetchReceiptConfirmedList(params)
tableData.value = res.list || []
pagination.total = res.total ?? 0
} finally {
loading.value = false
}
}
function switchTab(key: string) {
if (activeTab.value === key) return
activeTab.value = key
pagination.page = 1
loadData()
}
function openConfirm(row: Record<string, unknown>) {
currentDetailId.value = String(row.id)
confirmRow.value = row
financeRemark.value = ''
attachments.value = []
dialogVisible.value = true
}
function openViewConfirm(row: Record<string, unknown>) {
viewRow.value = row
viewDialogVisible.value = true
}
async function submitConfirm() {
if (!currentDetailId.value) return
if (attachments.value.some((f) => f._uploading)) {
ElMessage.warning('附件上传中,请稍候')
return
}
submitLoading.value = true
try {
await fetchReceiptConfirmSubmit(currentDetailId.value, {
financeConfirmAttachments: financeAttachmentsToUrlString(attachments.value) || undefined,
financeRemark: financeRemark.value.trim() || undefined
})
dialogVisible.value = false
loadData()
} finally {
submitLoading.value = false
}
}
onMounted(() => {
loadData()
})
</script>
<style scoped lang="scss">
.region-tabs {
margin-bottom: 16px;
}
.region-tab-btn {
position: relative;
min-width: 108px;
padding: 0 12px;
:deep(.el-icon) {
display: none;
}
}
.region-tab-btn.el-button > :deep(span) {
position: static !important;
display: inline-flex;
width: 100%;
justify-content: center;
}
.region-tab-inner {
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
}
.payment-confirm-table-wrap {
overflow-x: auto;
}
.payment-confirm-dialog {
:deep(.el-dialog__body) {
padding-top: 16px;
}
}
.form-section-divider {
height: 1px;
margin: 20px 0;
background: var(--mint-border, #e2e8f0);
}
.detail-section-title {
margin: 0 0 16px;
font-size: 14px;
font-weight: 600;
color: var(--mint-text, #334155);
line-height: 1.4;
}
.view-confirm-form :deep(.el-form-item) {
margin-bottom: 0;
}
.view-field-text {
display: block;
font-size: 14px;
color: var(--mint-text, #334155);
line-height: 1.5;
word-break: break-word;
&--multiline {
white-space: pre-wrap;
}
}
</style>