445 lines
15 KiB
Vue
445 lines
15 KiB
Vue
<!-- 缴费确认(财务) -->
|
||
<!-- 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>
|