This commit is contained in:
2026-08-17 19:13:25 +08:00
parent 28f2cd4cc5
commit d618683467
22 changed files with 263 additions and 50 deletions
@@ -82,6 +82,14 @@ describe('FinanceCustomerAccountsView delivery', () => {
expect(page).toContain('white-space: nowrap')
})
it('keeps account detail and term editing readable across the 1920 workspace', () => {
expect(page).toContain('width: min(100%, 1920px)')
expect(page).toContain('@media (min-width: 1920px)')
expect(page).toContain('.finance-account-detail__layout :deep(.detail-page-layout__content)')
expect(page).toContain('padding: 0 14px 10px')
expect(page).not.toContain('max-width: 520px')
})
it('shows only derived CNY summaries and no wallet actions', () => {
for (const marker of ['未收', '待出账', '本期已收', '应付', 'CNY']) expect(page).toContain(marker)
for (const forbidden of ['手动充值', '钱包', '外币', '发票', '利润']) expect(page).not.toContain(forbidden)
@@ -1,3 +1,5 @@
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import ElementPlus from 'element-plus'
import { createPinia, setActivePinia } from 'pinia'
import { createMemoryHistory, createRouter } from 'vue-router'
@@ -189,6 +191,15 @@ describe('FinanceCustomerArapView G2 operations', () => {
expect(wrapper.find('.arap-information').text()).not.toContain('T00:00:00')
})
it('keeps the fee-source detail readable across the 1920 workspace', () => {
const page = readFileSync(resolve(process.cwd(), 'src/modules/finance/pages/FinanceCustomerArapView.vue'), 'utf8')
expect(page).toContain('width: min(100%, 1920px)')
expect(page).toContain('@media (min-width: 1920px)')
expect(page).toContain('grid-template-columns: minmax(0, 1.35fr) minmax(320px, 0.65fr)')
expect(page).toContain('.finance-arap-detail__layout :deep(.detail-page-layout__content)')
expect(page).not.toContain('max-width: 520px')
})
it('resolves blank detail customer names and keeps source labels business-facing', async () => {
vi.mocked(getFinanceFeeSource).mockResolvedValueOnce({ ...detail, customerName: '', sourceType: 'NEW_SOURCE' })
const wrapper = await mountView(['platform:finance:customer-arap:list'], '/finance/customer-arap/42')
@@ -1,3 +1,5 @@
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import ElementPlus from 'element-plus'
import { createPinia, setActivePinia } from 'pinia'
import { createMemoryHistory, createRouter } from 'vue-router'
@@ -99,12 +101,20 @@ describe('FinanceCustomerStatementsView', () => {
expect(document.querySelector('[aria-label="客户对账单列表"]')).toBeNull()
expect(document.querySelector('[aria-label="所选对账单联动明细"]')).toBeNull()
expect(document.querySelector('[aria-label="对账单摘要"]')?.textContent).toContain('200.00 CNY')
expect(document.querySelectorAll('.statement-summary-grid .statement-summary-item')).toHaveLength(11)
expect(document.querySelector('.statement-detail-head')?.textContent).toContain('REC-501')
expect(document.querySelector('[aria-label="客户对账单账单明细"]')).toBeTruthy()
expect(document.querySelector('.detail-table-scroll [aria-label="客户对账单账单明细"]')).toBeTruthy()
expect(document.querySelector('.el-drawer')).toBeNull()
})
it('uses the prototype-style wide summary grid at 1920px', () => {
const source = readFileSync(resolve(process.cwd(), 'src/modules/finance/pages/FinanceCustomerStatementsView.vue'), 'utf8')
expect(source).toContain('grid-template-columns: repeat(auto-fit, minmax(220px, 1fr))')
expect(source).toContain('grid-template-columns: repeat(5, minmax(0, 1fr))')
expect(source).not.toContain('<el-descriptions :column="3" border>')
})
it('keeps the payment history in the desktop detail layout without duplicating a card container', async () => {
const { wrapper } = await mountPage()
const vm = wrapper.vm as unknown as { openDetail(row: FinanceStatementReadModel): Promise<void>; activeDetailTab: 'lines' | 'payments' }
@@ -456,19 +456,19 @@ watch(detailRouteId, (statementId) => {
<el-tag :type="financeStatusTone(detail.status)">{{ financeStatusLabel(detail.status) }}</el-tag>
</div>
<FormSection class="statement-summary-section" title="对账单概要" aria-label="对账单摘要">
<el-descriptions :column="3" border>
<el-descriptions-item label="总应收金额">{{ detail.totalAmount.toFixed(2) }} CNY</el-descriptions-item>
<el-descriptions-item label="已收金额">{{ detail.paidAmount.toFixed(2) }} CNY</el-descriptions-item>
<el-descriptions-item label="未收金额"><strong class="statement-outstanding">{{ detail.outstandingAmount.toFixed(2) }} CNY</strong></el-descriptions-item>
<el-descriptions-item label="账期">{{ detail.creditDays }} 天</el-descriptions-item>
<el-descriptions-item label="货币">{{ detail.currency }}</el-descriptions-item>
<el-descriptions-item label="是否结清">{{ detail.settled ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="创建人">{{ operatorDisplayName(detail.createdByName) }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ formatDateTime(detail.createdAt) }}</el-descriptions-item>
<el-descriptions-item label="更新人">{{ operatorDisplayName(detail.updatedByName) }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ formatDateTime(detail.updatedAt) }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ detail.remark || '—' }}</el-descriptions-item>
</el-descriptions>
<div class="statement-summary-grid">
<div class="statement-summary-item"><span>总应收金额</span><strong>{{ detail.totalAmount.toFixed(2) }} CNY</strong></div>
<div class="statement-summary-item"><span>已收金额</span><strong>{{ detail.paidAmount.toFixed(2) }} CNY</strong></div>
<div class="statement-summary-item"><span>未收金额</span><strong class="statement-outstanding">{{ detail.outstandingAmount.toFixed(2) }} CNY</strong></div>
<div class="statement-summary-item"><span>账期</span><strong>{{ detail.creditDays }} 天</strong></div>
<div class="statement-summary-item"><span>货币</span><strong>{{ detail.currency }}</strong></div>
<div class="statement-summary-item"><span>是否结清</span><strong>{{ detail.settled ? '是' : '否' }}</strong></div>
<div class="statement-summary-item"><span>创建人</span><strong>{{ operatorDisplayName(detail.createdByName) }}</strong></div>
<div class="statement-summary-item"><span>创建时间</span><strong>{{ formatDateTime(detail.createdAt) }}</strong></div>
<div class="statement-summary-item"><span>更新人</span><strong>{{ operatorDisplayName(detail.updatedByName) }}</strong></div>
<div class="statement-summary-item"><span>更新时间</span><strong>{{ formatDateTime(detail.updatedAt) }}</strong></div>
<div class="statement-summary-item statement-summary-item--remark"><span>备注</span><strong>{{ detail.remark || '—' }}</strong></div>
</div>
</FormSection>
</template>
<el-tabs v-model="activeDetailTab" class="statement-detail-tabs">
@@ -566,6 +566,12 @@ watch(detailRouteId, (statementId) => {
.statement-detail-head h2 { margin: 2px 0 0; font-size: 16px; line-height: 1.4; overflow-wrap: anywhere; }
.statement-detail-head p { margin: 5px 0 0; color: var(--cc-text-secondary); font-size: 12px; line-height: 1.6; overflow-wrap: anywhere; }
.statement-summary-section { padding: 10px 16px; border-bottom: 1px solid var(--cc-border-subtle); }
.statement-summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px 24px; }
.statement-summary-item { min-width: 0; }
.statement-summary-item span, .statement-summary-item strong { display: block; overflow-wrap: anywhere; }
.statement-summary-item span { margin-bottom: 3px; color: var(--cc-text-secondary); font-size: 12px; }
.statement-summary-item strong { color: var(--cc-text-primary); font-size: 14px; line-height: 1.45; }
.statement-summary-item--remark { grid-column: span 2; }
.statement-outstanding { color: var(--el-color-warning-dark-2); }
.statement-detail-tabs { min-width: 0; }
.statement-detail-table { min-width: 0; }
@@ -601,6 +607,15 @@ watch(detailRouteId, (statementId) => {
padding: 8px 14px;
}
.statement-summary-grid {
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 10px 24px;
}
.statement-summary-item--remark {
grid-column: span 2;
}
.statement-detail-tabs :deep(.el-tabs__header) {
padding: 0 14px;
}
@@ -624,5 +639,7 @@ watch(detailRouteId, (statementId) => {
@media (max-width: 640px) {
.statement-detail-page__header { align-items: stretch; flex-direction: column; }
.statement-summary-grid { grid-template-columns: 1fr; }
.statement-summary-item--remark { grid-column: auto; }
}
</style>
@@ -9,4 +9,19 @@ const settlementId = computed(() => String(route.params.settlementId || ''))
function openDetail(id: string) { void router.push({ name: 'finance-settlement-payable-detail', params: { settlementId: id } }) }
function back() { void router.push({ name: 'finance-settlement-payables' }) }
</script>
<template><FinanceFormalSettlementPage kind="payable" :settlement-id="settlementId" @open-detail="openDetail" @back="back" /></template>
<template><FinanceFormalSettlementPage class="finance-settlement-route finance-settlement-route--payable" kind="payable" :settlement-id="settlementId" @open-detail="openDetail" @back="back" /></template>
<style scoped>
@media (min-width: 1920px) {
.finance-settlement-route :deep(.settlement-detail-page) { width: 100%; max-width: none; }
.finance-settlement-route :deep(.settlement-detail-info .el-descriptions__table > tbody) {
display: grid;
grid-template-columns: minmax(110px, .35fr) minmax(0, 1fr) minmax(110px, .35fr) minmax(0, 1fr);
column-gap: 20px;
row-gap: 10px;
}
.finance-settlement-route :deep(.settlement-detail-info .el-descriptions__table > tbody > tr) { display: contents; }
.finance-settlement-route :deep(.settlement-detail-info .el-descriptions__label) { min-width: 0; white-space: nowrap; }
.finance-settlement-route :deep(.settlement-detail-info .el-descriptions__content) { min-width: 0; }
}
</style>
@@ -9,4 +9,19 @@ const settlementId = computed(() => String(route.params.settlementId || ''))
function openDetail(id: string) { void router.push({ name: 'finance-settlement-receivable-detail', params: { settlementId: id } }) }
function back() { void router.push({ name: 'finance-settlement-receivables' }) }
</script>
<template><FinanceFormalSettlementPage kind="receivable" :settlement-id="settlementId" @open-detail="openDetail" @back="back" /></template>
<template><FinanceFormalSettlementPage class="finance-settlement-route finance-settlement-route--receivable" kind="receivable" :settlement-id="settlementId" @open-detail="openDetail" @back="back" /></template>
<style scoped>
@media (min-width: 1920px) {
.finance-settlement-route :deep(.settlement-detail-page) { width: 100%; max-width: none; }
.finance-settlement-route :deep(.settlement-detail-info .el-descriptions__table > tbody) {
display: grid;
grid-template-columns: minmax(110px, .35fr) minmax(0, 1fr) minmax(110px, .35fr) minmax(0, 1fr);
column-gap: 20px;
row-gap: 10px;
}
.finance-settlement-route :deep(.settlement-detail-info .el-descriptions__table > tbody > tr) { display: contents; }
.finance-settlement-route :deep(.settlement-detail-info .el-descriptions__label) { min-width: 0; white-space: nowrap; }
.finance-settlement-route :deep(.settlement-detail-info .el-descriptions__content) { min-width: 0; }
}
</style>
@@ -0,0 +1,19 @@
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
const receivableSource = readFileSync(resolve(process.cwd(), 'src/modules/finance/pages/FinanceSettlementReceivablesView.vue'), 'utf8')
const payableSource = readFileSync(resolve(process.cwd(), 'src/modules/finance/pages/FinanceSettlementPayablesView.vue'), 'utf8')
describe('Finance settlement route 1920px layout', () => {
it.each([
['应收', receivableSource, 'finance-settlement-route--receivable'],
['应付', payableSource, 'finance-settlement-route--payable'],
])('uses the full detail workspace and a readable two-field information grid for %s', (_label, source, routeClass) => {
expect(source).toContain(routeClass)
expect(source).toContain('@media (min-width: 1920px)')
expect(source).toContain('.finance-settlement-route :deep(.settlement-detail-page) { width: 100%; max-width: none; }')
expect(source).toContain('grid-template-columns: minmax(110px, .35fr) minmax(0, 1fr) minmax(110px, .35fr) minmax(0, 1fr)')
expect(source).toContain('> tbody > tr) { display: contents; }')
})
})