优化
This commit is contained in:
@@ -55,7 +55,7 @@ const queryFields = computed<QueryField[]>(() => [
|
||||
])
|
||||
const columns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', width: 150, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'customerName', label: '客户', minWidth: 180 },
|
||||
{ key: 'customerName', label: '客户', minWidth: 180, slotName: 'customerName' },
|
||||
{ key: 'customerCode', label: '客户编号', width: 150 },
|
||||
{ key: 'contactName', label: '联系人', width: 120 },
|
||||
{ key: 'contactPhone', label: '联系电话', width: 140 },
|
||||
@@ -73,9 +73,9 @@ const ledgerQueryFields: QueryField[] = [
|
||||
{ key: 'occurredRange', type: 'daterange', label: '发生日期', width: 280 },
|
||||
]
|
||||
const ledgerColumns: DataTableColumn[] = [
|
||||
{ key: 'ledgerId', label: '流水号', minWidth: 160 }, { key: 'occurredAt', label: '发生时间', width: 170 }, { key: 'businessType', label: '业务类型', width: 130, slotName: 'businessType' },
|
||||
{ key: 'ledgerId', label: '流水号', minWidth: 160 }, { key: 'occurredAt', label: '发生时间', width: 170, slotName: 'occurredAt' }, { key: 'businessType', label: '业务类型', width: 130, slotName: 'businessType' },
|
||||
{ key: 'documentNo', label: '关联单据', minWidth: 170 }, { key: 'receivableChange', label: '应收变动(CNY)', width: 145 }, { key: 'payableChange', label: '应付变动(CNY)', width: 145 },
|
||||
{ key: 'outstandingReceivableAfter', label: '变动后未收(CNY)', width: 160 }, { key: 'operatorName', label: '操作人', width: 120 }, { key: 'remark', label: '备注', minWidth: 180 },
|
||||
{ key: 'outstandingReceivableAfter', label: '变动后未收(CNY)', width: 160 }, { key: 'operatorName', label: '操作人', width: 140, slotName: 'operatorName' }, { key: 'remark', label: '备注', minWidth: 180 },
|
||||
]
|
||||
const summary = computed(() => rows.value.reduce((result, row) => ({
|
||||
receivable: result.receivable + row.outstandingReceivable,
|
||||
@@ -147,14 +147,36 @@ async function saveTerms() {
|
||||
finally { saving.value = false }
|
||||
}
|
||||
function money(value: unknown) { return Number(value || 0).toFixed(2) }
|
||||
function customerName(customerId: string, snapshot?: string) {
|
||||
const masterName = customers.value.find((item) => String(item.id) === String(customerId))?.tenantName?.trim()
|
||||
const candidate = String(snapshot || '').trim()
|
||||
const placeholder = [String(customerId), `客户 ${customerId}`, `客户${customerId}`].includes(candidate)
|
||||
return masterName || (candidate && !placeholder ? candidate : '客户名称暂不可用')
|
||||
}
|
||||
function ledgerBusinessType(value: unknown) {
|
||||
const code = String(value || '').trim().toUpperCase()
|
||||
const known: Record<string, string> = { REC: '收款', RECEIPT: '收款', PAYMENT: '付款', COST: '成本', BILL: '对账单', RECONCILIATION: '对账单', SETTLEMENT: '结算', FEE_SOURCE: '费用来源', ADJUSTMENT: '账务调整' }
|
||||
return known[code] || financeBusinessTypeLabel(code)
|
||||
}
|
||||
function displayOperator(value: unknown) {
|
||||
const operator = String(value || '').trim()
|
||||
if (!operator) return '系统'
|
||||
return /^(?:operator-)?\d+$/i.test(operator) ? '操作人信息缺失' : operator
|
||||
}
|
||||
function formatDateTime(value: unknown) {
|
||||
const raw = String(value || '').trim()
|
||||
if (!raw) return '时间暂不可用'
|
||||
const date = new Date(raw)
|
||||
return Number.isNaN(date.getTime()) ? '时间暂不可用' : date.toLocaleString('zh-CN', { hour12: false })
|
||||
}
|
||||
function csvCell(value: unknown) { return `"${String(value ?? '').replaceAll('"', '""')}"` }
|
||||
function downloadCsv(fileName: string, headers: string[], data: unknown[][]) {
|
||||
const content = `\uFEFF${[headers, ...data].map((line) => line.map(csvCell).join(',')).join('\r\n')}`
|
||||
const url = URL.createObjectURL(new Blob([content], { type: 'text/csv;charset=utf-8' }))
|
||||
const link = document.createElement('a'); link.href = url; link.download = fileName; link.click(); URL.revokeObjectURL(url)
|
||||
}
|
||||
function exportAccounts() { downloadCsv('客户账户.csv', columns.slice(1).map((item) => String(item.label)), rows.value.map((row) => [row.customerName, row.customerCode, row.contactName, row.contactPhone, row.outstandingReceivable, row.pendingBilling, row.receivedInPeriod, row.outstandingPayable, row.status, row.lastChangedAt])) }
|
||||
function exportLedger() { downloadCsv(`${selected.value?.customerName || '客户'}-账户流水.csv`, ledgerColumns.map((item) => String(item.label)), ledger.value.map((row) => [row.ledgerId, row.occurredAt, financeBusinessTypeLabel(row.businessType), row.documentNo, row.receivableChange, row.payableChange, row.outstandingReceivableAfter, row.operatorName, row.remark])) }
|
||||
function exportAccounts() { downloadCsv('客户账户.csv', columns.slice(1).map((item) => String(item.label)), rows.value.map((row) => [customerName(row.customerId, row.customerName), row.customerCode, row.contactName, row.contactPhone, row.outstandingReceivable, row.pendingBilling, row.receivedInPeriod, row.outstandingPayable, row.status, row.lastChangedAt])) }
|
||||
function exportLedger() { downloadCsv(`${selected.value ? customerName(selected.value.customerId, selected.value.customerName) : '客户'}-账户流水.csv`, ledgerColumns.map((item) => String(item.label)), ledger.value.map((row) => [row.ledgerId, formatDateTime(row.occurredAt), ledgerBusinessType(row.businessType), row.documentNo, row.receivableChange, row.payableChange, row.outstandingReceivableAfter, displayOperator(row.operatorName), row.remark])) }
|
||||
async function closeDetail() {
|
||||
detailOpen.value = false; selected.value = null; editing.value = false; ledger.value = []
|
||||
if (route.name === 'finance-customer-account-detail') await router.push({ name: 'finance-customer-accounts' })
|
||||
@@ -174,7 +196,7 @@ async function restoreAccountDetail(customerId: string) {
|
||||
}
|
||||
onMounted(() => {
|
||||
const customerId = String(route.params.customerId || '')
|
||||
if (route.name === 'finance-customer-account-detail' && customerId) void restoreAccountDetail(customerId)
|
||||
if (route.name === 'finance-customer-account-detail' && customerId) { if (canListCustomers.value) void loadCustomers(); void restoreAccountDetail(customerId) }
|
||||
else { if (canListCustomers.value) void loadCustomers(); void load() }
|
||||
})
|
||||
watch(() => [route.name, String(route.params.customerId || '')], ([name, customerId], previous) => {
|
||||
@@ -194,17 +216,18 @@ watch(() => [route.name, String(route.params.customerId || '')], ([name, custome
|
||||
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" preference-key="finance-customer-accounts-table" aria-label="客户账户列表" row-key="customerId" empty-text="暂无客户账户" @update:page="load()" @update:page-size="page = 1; load()">
|
||||
<template #toolbar-actions><el-button :disabled="loading || !rows.length" @click="exportAccounts"><Download :size="16" />导出</el-button></template>
|
||||
<template #actions="{ row }"><div class="row-actions"><el-button link type="primary" @click="openAccount(row, false)"><Eye :size="15" />查看</el-button><el-button v-if="canUpdateTerms" link type="primary" @click="openAccount(row, true)"><Pencil :size="15" />账期</el-button></div></template>
|
||||
<template #customerName="{ row }">{{ customerName(row.customerId, row.customerName) }}</template>
|
||||
<template #status="{ row }"><el-tag :type="row.status === 'ACTIVE' ? 'success' : 'info'">{{ row.status === 'ACTIVE' ? '正常' : '停用' }}</el-tag></template>
|
||||
<template #pagination-left><span>汇总:未收 {{ money(summary.receivable) }} · 待出账 {{ money(summary.pending) }} · 本期已收 {{ money(summary.received) }} · 应付 {{ money(summary.payable) }} CNY</span></template>
|
||||
</DataTable>
|
||||
</section>
|
||||
</ListPageLayout>
|
||||
<AdminPage v-else as="div" class="finance-account-detail" spacing="normal">
|
||||
<div class="detail-heading"><el-button @click="closeDetail"><ArrowLeft :size="16" />返回客户账户</el-button><div><h2>{{ editing ? '维护客户账期' : '客户账户详情' }}</h2><p v-if="selected">{{ selected.customerName }} · {{ selected.customerCode }}</p></div></div>
|
||||
<div class="detail-heading"><el-button @click="closeDetail"><ArrowLeft :size="16" />返回客户账户</el-button><div><h2>{{ editing ? '维护客户账期' : '客户账户详情' }}</h2><p v-if="selected">{{ customerName(selected.customerId, selected.customerName) }} · {{ selected.customerCode }}</p></div></div>
|
||||
<LoadErrorState v-if="ledgerError" :title="editing ? '客户账期加载失败' : '账户流水加载失败'" :message="ledgerError.message" :loading="ledgerLoading" @retry="retryDrawer"><template #details><ApiErrorMeta :details="ledgerError" /></template></LoadErrorState>
|
||||
<template v-else-if="selected">
|
||||
<el-descriptions :column="2" border class="account-detail"><el-descriptions-item label="客户">{{ selected.customerName }}</el-descriptions-item><el-descriptions-item label="客户编号">{{ selected.customerCode }}</el-descriptions-item><el-descriptions-item label="待收">{{ money(selected.outstandingReceivable) }} CNY</el-descriptions-item><el-descriptions-item label="待开账">{{ money(selected.pendingBilling) }} CNY</el-descriptions-item><el-descriptions-item label="本期已收">{{ money(selected.receivedInPeriod) }} CNY</el-descriptions-item><el-descriptions-item label="待付">{{ money(selected.outstandingPayable) }} CNY</el-descriptions-item></el-descriptions>
|
||||
<FormSection v-if="!editing" title="账户流水" description="展示应收、应付及每次变动后的未收金额。"><QueryToolbar v-model="ledgerFilters" :fields="ledgerQueryFields" ariaLabel="账户流水筛选" preference-key="finance-account-ledger-query" @search="ledgerPage = 1; loadLedger" /><div class="detail-toolbar"><span>共 {{ ledgerTotal }} 条</span><el-button :disabled="!ledger.length" @click="exportLedger"><Download :size="16" />导出流水</el-button></div><DataTable v-model:page="ledgerPage" v-model:page-size="ledgerPageSize" :data="ledger" :columns="ledgerColumns" :loading="ledgerLoading" :total="ledgerTotal" row-key="ledgerId" empty-text="暂无账户流水" @update:page="loadLedger()" @update:page-size="ledgerPage = 1; loadLedger()"><template #businessType="{ row }">{{ financeBusinessTypeLabel(row.businessType) }}</template></DataTable></FormSection>
|
||||
<el-descriptions :column="2" border class="account-detail"><el-descriptions-item label="客户">{{ customerName(selected.customerId, selected.customerName) }}</el-descriptions-item><el-descriptions-item label="客户编号">{{ selected.customerCode }}</el-descriptions-item><el-descriptions-item label="待收">{{ money(selected.outstandingReceivable) }} CNY</el-descriptions-item><el-descriptions-item label="待开账">{{ money(selected.pendingBilling) }} CNY</el-descriptions-item><el-descriptions-item label="本期已收">{{ money(selected.receivedInPeriod) }} CNY</el-descriptions-item><el-descriptions-item label="待付">{{ money(selected.outstandingPayable) }} CNY</el-descriptions-item></el-descriptions>
|
||||
<FormSection v-if="!editing" title="账户流水" description="展示应收、应付及每次变动后的未收金额。"><QueryToolbar v-model="ledgerFilters" :fields="ledgerQueryFields" ariaLabel="账户流水筛选" preference-key="finance-account-ledger-query" @search="ledgerPage = 1; loadLedger" /><div class="detail-toolbar"><span>共 {{ ledgerTotal }} 条</span><el-button :disabled="!ledger.length" @click="exportLedger"><Download :size="16" />导出流水</el-button></div><DataTable v-model:page="ledgerPage" v-model:page-size="ledgerPageSize" :data="ledger" :columns="ledgerColumns" :loading="ledgerLoading" :total="ledgerTotal" row-key="ledgerId" empty-text="暂无账户流水" @update:page="loadLedger()" @update:page-size="ledgerPage = 1; loadLedger()"><template #occurredAt="{ row }">{{ formatDateTime(row.occurredAt) }}</template><template #businessType="{ row }">{{ ledgerBusinessType(row.businessType) }}</template><template #operatorName="{ row }">{{ displayOperator(row.operatorName) }}</template></DataTable></FormSection>
|
||||
<FormSection v-else title="账期设置" description="每个客户独立设置,默认 30 天。"><el-input-number v-model="creditDays" :min="0" :max="3650" :step="1" /><span class="term-unit">天</span></FormSection>
|
||||
</template>
|
||||
<div v-if="editing" class="detail-footer"><el-button @click="closeDetail">关闭</el-button><el-button v-if="canUpdateTerms" type="primary" :loading="saving" @click="saveTerms"><Save :size="16" />保存账期</el-button></div>
|
||||
|
||||
Reference in New Issue
Block a user