style(admin): unify dialog layouts and dense readonly presentation
This commit is contained in:
@@ -79,6 +79,7 @@ const createForm = ref({
|
||||
const paymentOpen = ref(false)
|
||||
const paymentBusy = ref(false)
|
||||
const paymentFile = ref<File | null>(null)
|
||||
const paymentUploadKey = ref(0)
|
||||
const paymentForm = ref({ transactionDate: new Date().toISOString().slice(0, 10), amount: 0, method: 'BANK_TRANSFER' as 'BANK_TRANSFER' | 'CASH' | 'OTHER', referenceNo: '', remark: '' })
|
||||
const receiptOpen = ref(false)
|
||||
const receiptBusy = ref(false)
|
||||
@@ -288,6 +289,10 @@ async function openCreate() {
|
||||
} catch (cause) { error.value = getApiErrorDetails(cause, '结算来源加载失败') }
|
||||
}
|
||||
|
||||
function reportCreateSourceError(reason: unknown) {
|
||||
if (createOpen.value) error.value = getApiErrorDetails(reason, '结算来源加载失败')
|
||||
}
|
||||
|
||||
function resetCreateDependentFields() {
|
||||
createForm.value.reconciliationStatementId = ''
|
||||
createForm.value.supplierId = ''
|
||||
@@ -329,21 +334,23 @@ async function refreshCreateDependencies(customerId?: string) {
|
||||
}
|
||||
|
||||
watch(() => createForm.value.customerId, (customerId) => {
|
||||
if (createOpen.value) void refreshCreateDependencies(customerId)
|
||||
if (createOpen.value) void refreshCreateDependencies(customerId).catch(reportCreateSourceError)
|
||||
})
|
||||
watch(() => createForm.value.supplierId, () => {
|
||||
if (!createOpen.value) return
|
||||
createForm.value.feeSourceIds = []
|
||||
feeOptions.value = []
|
||||
if (createForm.value.customerId) void refreshCreateDependencies(createForm.value.customerId)
|
||||
if (createForm.value.customerId) void refreshCreateDependencies(createForm.value.customerId).catch(reportCreateSourceError)
|
||||
})
|
||||
|
||||
async function submitCreate() {
|
||||
if (createBusy.value) return
|
||||
const customerId = String(createForm.value.customerId || '')
|
||||
if (!customerId) return ElMessage.warning('请先选择客户')
|
||||
if (props.kind === 'receivable' && !createForm.value.reconciliationStatementId) return ElMessage.warning('请选择已出账对账单')
|
||||
if (props.kind === 'payable' && (!createForm.value.supplierId || !createForm.value.dueDate || !createForm.value.feeSourceIds.length)) return ElMessage.warning('请完整填写供应商、到期日和费用来源')
|
||||
createBusy.value = true
|
||||
error.value = null
|
||||
try {
|
||||
if (props.kind === 'receivable') await createReceivableSettlement({ reconciliationStatementId: createForm.value.reconciliationStatementId, currency: 'CNY' })
|
||||
else await createPayableSettlement({ customerId, supplierId: createForm.value.supplierId, feeSourceIds: createForm.value.feeSourceIds, dueDate: createForm.value.dueDate, currency: 'CNY' })
|
||||
@@ -359,6 +366,8 @@ function openPayment(row: unknown) {
|
||||
selectedRow.value = settlement
|
||||
detail.value = { id: settlement.id, settlementNo: settlement.settlementNo, customerId: settlement.customerId, direction: 'PAYABLE', amount: settlement.payableAmount, paidAmount: settlement.paidAmount, outstandingAmount: settlement.outstandingAmount, currency: 'CNY', status: settlement.status, version: settlement.version, lines: [], transactions: [], revisions: [] }
|
||||
paymentFile.value = null
|
||||
paymentUploadKey.value += 1
|
||||
error.value = null
|
||||
paymentForm.value = { transactionDate: new Date().toISOString().slice(0, 10), amount: settlement.outstandingAmount, method: 'BANK_TRANSFER', referenceNo: '', remark: '' }
|
||||
paymentOpen.value = true
|
||||
}
|
||||
@@ -381,6 +390,7 @@ async function openReceipt(row: unknown) {
|
||||
if (!current.reconciliationStatementId) throw new Error('应收结算单缺少对账单关联')
|
||||
detail.value = current
|
||||
receiptFile.value = null
|
||||
receiptUploadKey.value += 1
|
||||
receiptForm.value = { receivedDate: new Date().toISOString().slice(0, 10), amount: current.outstandingAmount, paymentMethod: 'BANK_TRANSFER', remark: '' }
|
||||
receiptOpen.value = true
|
||||
} catch (cause) { receiptError.value = getApiErrorDetails(cause, '应收结算单加载失败,无法登记收款') }
|
||||
@@ -406,6 +416,7 @@ function selectReceiptFile(file: UploadFile) {
|
||||
}
|
||||
|
||||
async function submitReceipt() {
|
||||
if (receiptBusy.value) return
|
||||
const current = detail.value
|
||||
if (!current?.reconciliationStatementId || !receiptFile.value || receiptForm.value.amount <= 0) {
|
||||
receiptValidationError.value = '请填写收款金额并选择收款凭证'
|
||||
@@ -425,8 +436,10 @@ async function submitReceipt() {
|
||||
}
|
||||
|
||||
async function submitPayment() {
|
||||
if (paymentBusy.value) return
|
||||
if (!detail.value || !paymentFile.value || paymentForm.value.amount <= 0) return ElMessage.warning('请填写付款金额并选择付款凭证')
|
||||
paymentBusy.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const voucher = await uploadFinanceVoucher(detail.value.customerId, paymentFile.value)
|
||||
await createFinanceSettlementPayment(detail.value.id, { ...paymentForm.value, currency: 'CNY', voucherFileIds: [voucher.fileId], expectedVersion: detail.value.version })
|
||||
@@ -439,7 +452,7 @@ async function submitPayment() {
|
||||
}
|
||||
|
||||
async function performAction(row: SettlementRow, action: 'VOID' | 'REOPEN') {
|
||||
const reason = await ElMessageBox.prompt('请输入操作原因', action === 'VOID' ? `作废${title.value}` : `重开${title.value}`, {
|
||||
const reason = await ElMessageBox.prompt('请输入操作原因', `${action === 'VOID' ? '作废' : '重开'}${title.value} · ${row.settlementNo || '—'}`, {
|
||||
customClass: 'admin-density-confirm',
|
||||
inputType: 'textarea', inputPattern: /\S+/, inputErrorMessage: '操作原因不能为空', confirmButtonText: '确认', cancelButtonText: '取消',
|
||||
}).then((result) => result.value.trim()).catch(() => null)
|
||||
@@ -507,6 +520,7 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<AdminPage as="div" class="finance-settlement-page admin-density" spacing="normal">
|
||||
<OperationErrorToast v-if="createOpen || paymentOpen" :details="error" />
|
||||
<OperationErrorToast :message="receiptValidationError" />
|
||||
<OperationErrorToast :details="receiptError" />
|
||||
<el-result v-if="!canView" icon="warning" :title="`无权访问${title}`" sub-title="请联系管理员开通结算单查看权限" />
|
||||
@@ -640,25 +654,63 @@ onMounted(() => {
|
||||
</DataTable></section>
|
||||
</ListPageLayout>
|
||||
|
||||
<AppDialog class="admin-density-dialog" v-model="createOpen" :title="`生成${title}`" width="min(600px, 94vw)" :confirm-loading="createBusy" @confirm="submitCreate">
|
||||
<el-form class="density-form" label-position="top">
|
||||
<el-form-item label="客户"><el-select v-model="createForm.customerId" filterable placeholder="请选择客户"><el-option v-for="item in activeCustomers" :key="String(item.id)" :label="item.tenantName" :value="String(item.id)" /></el-select></el-form-item>
|
||||
<el-form-item v-if="kind === 'receivable'" label="已出账对账单"><el-select v-model="createForm.reconciliationStatementId" filterable><el-option v-for="item in statementOptions" :key="item.id" :label="`${item.billNo} · ${item.outstandingAmount} CNY`" :value="item.id" /></el-select></el-form-item>
|
||||
<template v-else><el-form-item label="供应商"><el-select v-model="createForm.supplierId" filterable placeholder="请选择供应商"><el-option v-for="item in supplierOptions" :key="String(item.id)" :label="`${item.name} · ${categoryLabels[item.category]}`" :value="String(item.id)" /></el-select></el-form-item><el-form-item label="到期日"><el-date-picker v-model="createForm.dueDate" type="date" value-format="YYYY-MM-DD" /></el-form-item><el-form-item label="应付费用来源"><el-select v-model="createForm.feeSourceIds" multiple filterable><el-option v-for="item in feeOptions" :key="item.feeSourceId!" :label="`${item.feeItemName} · ${item.amount} CNY`" :value="item.feeSourceId!" /></el-select></el-form-item></template>
|
||||
</el-form>
|
||||
<AppDialog class="admin-density-dialog" v-model="createOpen" :title="`生成${title}`" width="min(600px, 94vw)" fullscreenable :confirm-loading="createBusy" @confirm="submitCreate">
|
||||
<section class="density-dialog-section" data-tone="work" aria-label="结算单生成设置"><h3 class="density-dialog-section__title">生成设置</h3>
|
||||
<el-form class="density-form" label-position="left" label-width="120px" :disabled="createBusy"><table class="density-table density-table--editable"><tbody>
|
||||
<tr><td><el-form-item label="客户"><el-select v-model="createForm.customerId" filterable placeholder="请选择客户"><el-option v-for="item in activeCustomers" :key="String(item.id)" :label="item.tenantName" :value="String(item.id)" /></el-select></el-form-item></td></tr>
|
||||
<tr><td><el-form-item label="币种"><span>CNY</span></el-form-item></td></tr>
|
||||
<tr v-if="kind === 'receivable'"><td><el-form-item label="已出账对账单"><el-select v-model="createForm.reconciliationStatementId" filterable><el-option v-for="item in statementOptions" :key="item.id" :label="`${item.billNo} · ${item.outstandingAmount} CNY`" :value="item.id" /></el-select></el-form-item></td></tr>
|
||||
<template v-else>
|
||||
<tr><td><el-form-item label="供应商"><el-select v-model="createForm.supplierId" filterable placeholder="请选择供应商"><el-option v-for="item in supplierOptions" :key="String(item.id)" :label="`${item.name} · ${categoryLabels[item.category]}`" :value="String(item.id)" /></el-select></el-form-item></td></tr>
|
||||
<tr><td><el-form-item label="到期日"><el-date-picker v-model="createForm.dueDate" type="date" value-format="YYYY-MM-DD" /></el-form-item></td></tr>
|
||||
<tr><td><el-form-item label="应付费用来源"><el-select v-model="createForm.feeSourceIds" multiple filterable><el-option v-for="item in feeOptions" :key="item.feeSourceId!" :label="`${item.feeItemName} · ${item.amount} CNY`" :value="item.feeSourceId!" /></el-select></el-form-item></td></tr>
|
||||
</template>
|
||||
</tbody></table></el-form>
|
||||
</section>
|
||||
</AppDialog>
|
||||
|
||||
<AppDialog class="admin-density-dialog" v-model="paymentOpen" title="登记付款" width="min(760px, 94vw)" :confirm-loading="paymentBusy" @confirm="submitPayment">
|
||||
<dl v-if="detail" class="density-detail-overview partial-summary"><div><dt>结算单号</dt><dd>{{ detail.settlementNo }}</dd></div><div><dt>供应商</dt><dd>{{ detailPartyName() }}</dd></div><div><dt>应付金额</dt><dd>{{ money(detail.amount) }}</dd></div><div><dt>已付金额</dt><dd class="is-success">{{ money(detail.paidAmount) }}</dd></div><div><dt>当前余额</dt><dd class="is-danger">{{ money(detail.outstandingAmount) }}</dd></div></dl>
|
||||
<el-form class="settlement-operation-form density-form" label-position="top"><el-form-item label="付款日期"><el-date-picker v-model="paymentForm.transactionDate" type="date" value-format="YYYY-MM-DD" /></el-form-item><el-form-item label="本次付款金额(CNY)"><el-input-number v-model="paymentForm.amount" :min="0.01" :max="detail?.outstandingAmount || 0.01" :precision="2" placeholder="请输入付款金额" /></el-form-item><el-form-item label="付款方式"><el-select v-model="paymentForm.method"><el-option label="银行转账" value="BANK_TRANSFER" /><el-option label="现金" value="CASH" /><el-option label="其他" value="OTHER" /></el-select></el-form-item><el-form-item label="银行流水号/凭证号"><el-input v-model="paymentForm.referenceNo" placeholder="请输入付款流水号" /></el-form-item><el-form-item label="付款凭证"><el-upload :auto-upload="false" :limit="1" accept="image/*" :on-change="selectPaymentFile"><el-button>选择图片</el-button></el-upload></el-form-item><el-form-item label="备注"><el-input v-model="paymentForm.remark" type="textarea" placeholder="请输入付款备注(选填)" /></el-form-item></el-form>
|
||||
<div v-if="detail" class="remaining-preview"><span>登记后剩余余额</span><strong>{{ remainingAfter(paymentForm.amount) }}</strong></div>
|
||||
<AppDialog class="admin-density-dialog" v-model="paymentOpen" :title="`登记付款 · ${detail?.settlementNo || '—'}`" width="min(760px, 94vw)" fullscreenable :confirm-loading="paymentBusy" @confirm="submitPayment">
|
||||
<div class="density-dialog-sections">
|
||||
<section v-if="detail" class="density-dialog-section partial-summary" data-tone="summary" aria-label="当前结算资料"><h3 class="density-dialog-section__title">当前结算(CNY)</h3>
|
||||
<table class="density-table settlement-dialog-facts"><tbody>
|
||||
<tr><th scope="row">结算单号</th><td>{{ detail.settlementNo || '—' }}</td><th scope="row">供应商</th><td>{{ detailPartyName() }}</td></tr>
|
||||
<tr><th scope="row">应付金额</th><td>{{ money(detail.amount) }}</td><th scope="row">已付金额</th><td class="is-success">{{ money(detail.paidAmount) }}</td></tr>
|
||||
<tr><th scope="row">当前余额</th><td colspan="3" class="is-danger">{{ money(detail.outstandingAmount) }}</td></tr>
|
||||
</tbody></table>
|
||||
</section>
|
||||
<section class="density-dialog-section" data-tone="work" aria-label="付款登记"><h3 class="density-dialog-section__title">付款登记</h3>
|
||||
<el-form class="settlement-operation-form density-form" label-position="left" label-width="140px" :disabled="paymentBusy"><table class="density-table density-table--editable settlement-operation-table"><tbody>
|
||||
<tr><td><el-form-item label="付款日期"><el-date-picker v-model="paymentForm.transactionDate" type="date" value-format="YYYY-MM-DD" /></el-form-item></td><td><el-form-item label="本次付款金额(CNY)"><el-input-number v-model="paymentForm.amount" :min="0.01" :max="detail?.outstandingAmount || 0.01" :precision="2" placeholder="请输入付款金额" /></el-form-item></td></tr>
|
||||
<tr><td><el-form-item label="付款方式"><el-select v-model="paymentForm.method"><el-option label="银行转账" value="BANK_TRANSFER" /><el-option label="现金" value="CASH" /><el-option label="其他" value="OTHER" /></el-select></el-form-item></td><td><el-form-item label="银行流水号/凭证号"><el-input v-model="paymentForm.referenceNo" placeholder="请输入付款流水号" /></el-form-item></td></tr>
|
||||
<tr><td colspan="2"><el-form-item label="付款凭证"><el-upload :key="paymentUploadKey" :disabled="paymentBusy" :auto-upload="false" :limit="1" accept="image/*" :on-change="selectPaymentFile"><el-button>选择图片</el-button></el-upload></el-form-item></td></tr>
|
||||
<tr><td colspan="2"><el-form-item label="备注"><el-input v-model="paymentForm.remark" type="textarea" placeholder="请输入付款备注(选填)" /></el-form-item></td></tr>
|
||||
<tr><td colspan="2"><el-form-item label="登记后剩余余额"><strong>{{ remainingAfter(paymentForm.amount) }}</strong></el-form-item></td></tr>
|
||||
</tbody></table></el-form>
|
||||
</section>
|
||||
</div>
|
||||
</AppDialog>
|
||||
|
||||
<AppDialog class="admin-density-dialog" v-model="receiptOpen" title="登记收款" width="min(760px, 94vw)" :confirm-loading="receiptBusy" @confirm="submitReceipt">
|
||||
<dl v-if="detail" class="density-detail-overview partial-summary"><div><dt>结算单号</dt><dd>{{ detail.settlementNo }}</dd></div><div><dt>客户公司</dt><dd>{{ detailPartyName() }}</dd></div><div><dt>应收金额</dt><dd>{{ money(detail.amount) }}</dd></div><div><dt>已收金额</dt><dd class="is-success">{{ money(detail.paidAmount) }}</dd></div><div><dt>当前余额</dt><dd class="is-danger">{{ money(detail.outstandingAmount) }}</dd></div></dl>
|
||||
<el-form class="settlement-operation-form density-form" label-position="top"><el-form-item label="收款日期"><el-date-picker v-model="receiptForm.receivedDate" type="date" value-format="YYYY-MM-DD" /></el-form-item><el-form-item label="本次收款金额(CNY)"><el-input-number v-model="receiptForm.amount" :min="0.01" :max="detail?.outstandingAmount || 0.01" :precision="2" placeholder="请输入收款金额" /></el-form-item><el-form-item label="收款方式"><el-select v-model="receiptForm.paymentMethod"><el-option label="银行转账" value="BANK_TRANSFER" /><el-option label="现金" value="CASH" /><el-option label="其他" value="OTHER" /></el-select></el-form-item><el-form-item label="收款凭证"><el-upload :key="receiptUploadKey" :auto-upload="false" :limit="1" accept="image/*" :on-change="selectReceiptFile"><el-button>选择图片</el-button></el-upload></el-form-item><el-form-item label="备注"><el-input v-model="receiptForm.remark" type="textarea" placeholder="请输入收款备注(选填)" /></el-form-item></el-form>
|
||||
<div v-if="detail" class="remaining-preview"><span>登记后剩余余额</span><strong>{{ remainingAfter(receiptForm.amount) }}</strong></div>
|
||||
<AppDialog class="admin-density-dialog" v-model="receiptOpen" :title="`登记收款 · ${detail?.settlementNo || '—'}`" width="min(760px, 94vw)" fullscreenable :confirm-loading="receiptBusy" @confirm="submitReceipt">
|
||||
<div class="density-dialog-sections">
|
||||
<section v-if="detail" class="density-dialog-section partial-summary" data-tone="summary" aria-label="当前结算资料"><h3 class="density-dialog-section__title">当前结算(CNY)</h3>
|
||||
<table class="density-table settlement-dialog-facts"><tbody>
|
||||
<tr><th scope="row">结算单号</th><td>{{ detail.settlementNo || '—' }}</td><th scope="row">客户公司</th><td>{{ detailPartyName() }}</td></tr>
|
||||
<tr><th scope="row">应收金额</th><td>{{ money(detail.amount) }}</td><th scope="row">已收金额</th><td class="is-success">{{ money(detail.paidAmount) }}</td></tr>
|
||||
<tr><th scope="row">当前余额</th><td colspan="3" class="is-danger">{{ money(detail.outstandingAmount) }}</td></tr>
|
||||
</tbody></table>
|
||||
</section>
|
||||
<section class="density-dialog-section" data-tone="work" aria-label="收款登记"><h3 class="density-dialog-section__title">收款登记</h3>
|
||||
<el-form class="settlement-operation-form density-form" label-position="left" label-width="140px" :disabled="receiptBusy"><table class="density-table density-table--editable settlement-operation-table"><tbody>
|
||||
<tr><td><el-form-item label="收款日期"><el-date-picker v-model="receiptForm.receivedDate" type="date" value-format="YYYY-MM-DD" /></el-form-item></td><td><el-form-item label="本次收款金额(CNY)"><el-input-number v-model="receiptForm.amount" :min="0.01" :max="detail?.outstandingAmount || 0.01" :precision="2" placeholder="请输入收款金额" /></el-form-item></td></tr>
|
||||
<tr><td colspan="2"><el-form-item label="收款方式"><el-select v-model="receiptForm.paymentMethod"><el-option label="银行转账" value="BANK_TRANSFER" /><el-option label="现金" value="CASH" /><el-option label="其他" value="OTHER" /></el-select></el-form-item></td></tr>
|
||||
<tr><td colspan="2"><el-form-item label="收款凭证"><el-upload :key="receiptUploadKey" :disabled="receiptBusy" :auto-upload="false" :limit="1" accept="image/*" :on-change="selectReceiptFile"><el-button>选择图片</el-button></el-upload></el-form-item></td></tr>
|
||||
<tr><td colspan="2"><el-form-item label="备注"><el-input v-model="receiptForm.remark" type="textarea" placeholder="请输入收款备注(选填)" /></el-form-item></td></tr>
|
||||
<tr><td colspan="2"><el-form-item label="登记后剩余余额"><strong>{{ remainingAfter(receiptForm.amount) }}</strong></el-form-item></td></tr>
|
||||
</tbody></table></el-form>
|
||||
</section>
|
||||
</div>
|
||||
</AppDialog>
|
||||
|
||||
</AdminPage>
|
||||
</template>
|
||||
|
||||
@@ -677,11 +729,9 @@ onMounted(() => {
|
||||
:deep(.settlement-detail-info .el-descriptions__table) { table-layout: fixed; }
|
||||
:deep(.settlement-detail-info .el-descriptions__content) { min-width: 0; overflow-wrap: anywhere; }
|
||||
:deep(.detail-table-scroll .el-table) { min-width: 920px; }
|
||||
.settlement-operation-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--admin-space-5); row-gap: 0; }
|
||||
.settlement-operation-form :deep(.el-form-item:last-child) { grid-column: 1 / -1; }
|
||||
.settlement-operation-form :deep(.el-input-number) { width: 100%; }
|
||||
.remaining-preview { display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-4); padding: var(--admin-space-3) var(--admin-space-5); border-radius: var(--admin-radius); color: var(--cc-text-secondary, #64748b); background: var(--el-fill-color-light); }
|
||||
.remaining-preview strong { color: #b45309; font-size: var(--admin-font-section); }
|
||||
.settlement-dialog-facts, .settlement-operation-table { table-layout: fixed; }
|
||||
.settlement-dialog-facts th { width: var(--admin-label-width); }
|
||||
.settlement-operation-table td:not([colspan]) { width: 50%; }
|
||||
.operation-error { margin-bottom: var(--admin-space-5); }
|
||||
.operation-error-meta { margin: -var(--admin-space-2) 0 var(--admin-space-5); }
|
||||
:deep(.el-select), :deep(.el-date-editor) { width: 100%; }
|
||||
@@ -707,18 +757,13 @@ onMounted(() => {
|
||||
margin-bottom: var(--admin-space-3);
|
||||
}
|
||||
|
||||
.settlement-operation-form {
|
||||
column-gap: var(--admin-space-5);
|
||||
}
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.settlement-detail-summary, .partial-summary, .settlement-operation-form { grid-template-columns: 1fr; }
|
||||
.settlement-detail-summary { grid-template-columns: 1fr; }
|
||||
.settlement-detail-summary { gap: 10px; padding: 12px 16px; }
|
||||
.settlement-detail-summary .summary-item { padding: 0; border-left: 0; }
|
||||
.settlement-detail-content { padding: 0 16px 14px; }
|
||||
.settlement-detail-state { margin: 14px; }
|
||||
.partial-summary { gap: 10px; padding: 12px 10px; }
|
||||
.partial-summary div { padding: 0; border-left: 0; }
|
||||
.settlement-summary { align-items: flex-start; flex-direction: column; gap: 6px; }
|
||||
.settlement-summary .summary-item + .summary-item { margin-left: 0; padding-left: 0; border-left: 0; }
|
||||
.settlement-list-actions { width: 100%; justify-content: flex-start; }
|
||||
|
||||
Reference in New Issue
Block a user