style(admin): unify dialog layouts and dense readonly presentation

This commit is contained in:
2026-10-20 10:11:31 +08:00
parent 7ff7c9914f
commit 3267679758
214 changed files with 10931 additions and 2654 deletions
@@ -7,11 +7,12 @@ import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { getApiErrorDetails, getApiErrorMessage, type ApiErrorDetails } from '@/shared/api/http'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import { containerDialogTitle } from '../containerDialogTitle'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import { listContainerFeeSupplierOptions, uploadContainerFeePaymentVoucher, type ContainerFeePaymentMethod, type ContainerFeeSettlementMode, type ContainerFeeSupplierOption } from '@/modules/loading-lists'
import { adjustOverseasContainerFee, createOverseasContainerFee, listOverseasContainerCustomers, listOverseasContainerFees, reviewOverseasContainerFee, submitOverseasContainerFee, voidOverseasContainerFee, type OverseasContainerCustomer, type OverseasContainerFeeDirection, type OverseasContainerFeeItem, type OverseasContainerFeeSaveRequest, type OverseasContainerFeeSource } from '../api/overseasContainerFees'
const props = withDefaults(defineProps<{ title?: string; containerBookingId: string; containerNo: string | null; readonly?: boolean }>(), { title: '目的港及海外段费用(尾程)', readonly: false })
const props = withDefaults(defineProps<{ title?: string; containerBookingId: string; cabinetNo?: string | null; containerNo: string | null; readonly?: boolean }>(), { title: '目的港及海外段费用(尾程)', readonly: false })
const access = useAccessPreviewStore()
const rows = ref<OverseasContainerFeeSource[]>([])
const customers = ref<OverseasContainerCustomer[]>([])
@@ -146,17 +147,17 @@ async function uploadVoucher(event: Event) {
}
}
async function submit(row: OverseasContainerFeeSource) {
await ElMessageBox.confirm(`确认提交“${row.feeItemName}”审核?`, '提交费用', { customClass: 'admin-density-confirm', type: 'warning' })
await ElMessageBox.confirm(`确认提交“${row.feeItemName}”审核?`, containerDialogTitle('提交费用', props), { customClass: 'admin-density-confirm', type: 'warning' })
await mutate(() => submitOverseasContainerFee(row.sourceFeeId, key('container-fee-submit')), '费用已提交审核')
}
async function review(row: OverseasContainerFeeSource, decision: 'APPROVE' | 'REJECT') {
let reason: string | null = null
if (decision === 'REJECT') { const result = await ElMessageBox.prompt('请输入驳回原因', '驳回费用', { customClass: 'admin-density-confirm', inputPattern: /\S+/, inputErrorMessage: '请输入驳回原因', type: 'warning' }); reason = result.value.trim() }
else await ElMessageBox.confirm(`确认审核通过“${row.feeItemName}”?`, '审核费用', { customClass: 'admin-density-confirm', type: 'warning' })
if (decision === 'REJECT') { const result = await ElMessageBox.prompt('请输入驳回原因', containerDialogTitle('驳回费用', props), { customClass: 'admin-density-confirm', inputPattern: /\S+/, inputErrorMessage: '请输入驳回原因', type: 'warning' }); reason = result.value.trim() }
else await ElMessageBox.confirm(`确认审核通过“${row.feeItemName}”?`, containerDialogTitle('审核费用', props), { customClass: 'admin-density-confirm', type: 'warning' })
await mutate(() => reviewOverseasContainerFee(row.sourceFeeId, decision, reason, key('container-fee-review')), decision === 'APPROVE' ? '费用已审核通过' : '费用已驳回')
}
async function voidFee(row: OverseasContainerFeeSource) {
const result = await ElMessageBox.prompt('请输入作废原因', '作废费用', { customClass: 'admin-density-confirm', inputPattern: /\S+/, inputErrorMessage: '请输入作废原因', type: 'warning' })
const result = await ElMessageBox.prompt('请输入作废原因', containerDialogTitle('作废费用', props), { customClass: 'admin-density-confirm', inputPattern: /\S+/, inputErrorMessage: '请输入作废原因', type: 'warning' })
await mutate(() => voidOverseasContainerFee(row.sourceFeeId, row.sourceVersion, result.value.trim(), key('container-fee-void')), '作废申请已提交,请查看财务确认状态')
}
onMounted(load)
@@ -182,13 +183,24 @@ onMounted(load)
<el-table-column label="作废确认" min-width="130"><template #default="{ row }">{{ voidLabels[row.voidStatus] || '—' }}</template></el-table-column>
<el-table-column prop="reviewReason" label="审核说明" min-width="180" show-overflow-tooltip><template #default="{ row }">{{ row.reviewReason || '—' }}</template></el-table-column>
</el-table>
<AppDialog class="admin-density-dialog" v-model="dialogOpen" :title="editing ? '调整尾程费用' : '新增尾程费用'" width="min(620px, calc(100vw - 28px))" confirm-text="保存" :confirm-loading="actionLoading" :confirm-disabled="voucherUploading" @confirm="save">
<el-form class="density-form" label-position="top"><div class="fee-form-grid"><el-form-item label="客户" required><el-select v-model="form.customerId" :disabled="Boolean(editing) || voucherUploading" placeholder="请选择柜内客户"><el-option v-for="customer in customers" :key="customer.customerId" :label="customer.customerName || '未命名客户'" :value="customer.customerId" /></el-select></el-form-item><el-form-item label="费用项目" required><el-select v-model="form.feeItemCode"><el-option v-for="item in feeItems" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item><el-form-item label="费用方向" required><el-select v-model="form.direction"><el-option label="应收" value="RECEIVABLE" /><el-option label="应付" value="PAYABLE" /></el-select></el-form-item><el-form-item label="业务日期" required><el-date-picker v-model="form.businessDate" type="date" value-format="YYYY-MM-DD" /></el-form-item><el-form-item label="金额(CNY)" required><el-input-number v-model="form.amount" :min="0.01" :precision="2" :step="10" placeholder="请输入费用金额" /></el-form-item><template v-if="form.direction === 'PAYABLE'"><el-form-item label="供应商" required><el-select v-model="form.supplierId" filterable placeholder="请选择有效供应商"><el-option v-for="supplier in suppliers" :key="supplier.id" :label="supplier.supplierName" :value="supplier.id" /></el-select></el-form-item><el-form-item label="结算方式" required><el-select v-model="form.settlementMode"><el-option label="账期结算" value="ON_ACCOUNT" /><el-option label="直接已付" value="DIRECT_PAID" /></el-select></el-form-item><template v-if="form.settlementMode === 'DIRECT_PAID'"><el-form-item label="付款时间" required><el-date-picker v-model="form.paidAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" /></el-form-item><el-form-item label="付款方式" required><el-select v-model="form.paymentMethod"><el-option label="线上支付" value="ONLINE" /><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="form.paymentReferenceNo" maxlength="128" placeholder="请输入付款流水号(可选)" /></el-form-item><el-form-item label="付款凭证"><div class="voucher-list"><input ref="voucherInput" type="file" accept="image/*" hidden @change="uploadVoucher" /><el-button :loading="voucherUploading" :disabled="!form.customerId || actionLoading" @click="voucherInput?.click()">{{ form.paymentVoucherFileIds.length ? '重新上传凭证' : '上传付款凭证' }}</el-button><div v-for="(fileId, index) in form.paymentVoucherFileIds" :key="fileId" class="voucher-item"><el-button link type="primary" @click="previewVoucher(form.customerId, fileId)">{{ voucherNames[fileId] || `付款凭证 ${index + 1}` }}</el-button><el-button link type="danger" :disabled="voucherUploading || actionLoading" @click="form.paymentVoucherFileIds = form.paymentVoucherFileIds.filter((id) => id !== fileId)">移除</el-button></div></div></el-form-item></template></template></div></el-form>
<AppDialog class="admin-density-dialog" v-model="dialogOpen" :title="containerDialogTitle(editing ? '调整尾程费用' : '新增尾程费用', props)" width="min(920px, calc(100vw - 28px))" fullscreenable confirm-text="保存" :confirm-loading="actionLoading" :confirm-disabled="voucherUploading" @confirm="save">
<section class="density-dialog-section" data-tone="work">
<el-form class="density-form" label-position="left">
<table class="density-table density-table--editable fee-editor-table" aria-label="尾程费用填写"><tbody>
<tr><td><el-form-item label-position="left" label-width="110px" label="客户" required><span v-if="editing">{{ customerName(form.customerId) || editing.customerName || '—' }}</span><el-select v-else v-model="form.customerId" :disabled="voucherUploading" placeholder="请选择柜内客户"><el-option v-for="customer in customers" :key="customer.customerId" :label="customer.customerName || '未命名客户'" :value="customer.customerId" /></el-select></el-form-item></td><td><el-form-item label-position="left" label-width="110px" label="费用项目" required><el-select v-model="form.feeItemCode"><el-option v-for="item in feeItems" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item></td></tr>
<tr><td><el-form-item label-position="left" label-width="110px" label="费用方向" required><el-select v-model="form.direction"><el-option label="应收" value="RECEIVABLE" /><el-option label="应付" value="PAYABLE" /></el-select></el-form-item></td><td><el-form-item label-position="left" label-width="110px" label="业务日期" required><el-date-picker v-model="form.businessDate" type="date" value-format="YYYY-MM-DD" /></el-form-item></td></tr>
<tr><td :colspan="form.direction === 'PAYABLE' ? 1 : 2"><el-form-item label-position="left" label-width="110px" label="金额(CNY)" required><el-input-number v-model="form.amount" :min="0.01" :precision="2" :step="10" placeholder="请输入费用金额" /></el-form-item></td><td v-if="form.direction === 'PAYABLE'"><el-form-item label-position="left" label-width="110px" label="供应商" required><el-select v-model="form.supplierId" filterable placeholder="请选择有效供应商"><el-option v-for="supplier in suppliers" :key="supplier.id" :label="supplier.supplierName" :value="supplier.id" /></el-select></el-form-item></td></tr>
<tr v-if="form.direction === 'PAYABLE'"><td :colspan="form.settlementMode === 'DIRECT_PAID' ? 1 : 2"><el-form-item label-position="left" label-width="110px" label="结算方式" required><el-select v-model="form.settlementMode"><el-option label="账期结算" value="ON_ACCOUNT" /><el-option label="直接已付" value="DIRECT_PAID" /></el-select></el-form-item></td><td v-if="form.settlementMode === 'DIRECT_PAID'"><el-form-item label-position="left" label-width="110px" label="付款时间" required><el-date-picker v-model="form.paidAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" /></el-form-item></td></tr>
<tr v-if="form.direction === 'PAYABLE' && form.settlementMode === 'DIRECT_PAID'"><td><el-form-item label-position="left" label-width="110px" label="付款方式" required><el-select v-model="form.paymentMethod"><el-option label="线上支付" value="ONLINE" /><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-position="left" label-width="110px" label="付款流水号"><el-input v-model="form.paymentReferenceNo" maxlength="128" placeholder="请输入付款流水号(可选)" /></el-form-item></td></tr>
<tr v-if="form.direction === 'PAYABLE' && form.settlementMode === 'DIRECT_PAID'"><td :colspan="2"><el-form-item label-position="left" label-width="110px" label="付款凭证"><div class="voucher-list"><input ref="voucherInput" type="file" accept="image/*" hidden @change="uploadVoucher" /><el-button :loading="voucherUploading" :disabled="!form.customerId || actionLoading" @click="voucherInput?.click()">{{ form.paymentVoucherFileIds.length ? '重新上传凭证' : '上传付款凭证' }}</el-button><div v-for="(fileId, index) in form.paymentVoucherFileIds" :key="fileId" class="voucher-item"><el-button link type="primary" @click="previewVoucher(form.customerId, fileId)">{{ voucherNames[fileId] || `付款凭证 ${index + 1}` }}</el-button><el-button link type="danger" :disabled="voucherUploading || actionLoading" @click="form.paymentVoucherFileIds = form.paymentVoucherFileIds.filter((id) => id !== fileId)">移除</el-button></div></div></el-form-item></td></tr>
</tbody></table>
</el-form>
</section>
</AppDialog>
<AppDialog class="admin-density-dialog" v-model="previewOpen" :title="previewName" width="min(900px, calc(100vw - 28px))" :show-confirm="false" cancel-text="关闭" append-to-body>
<AppDialog class="admin-density-dialog last-leg-voucher-dialog" v-model="previewOpen" :title="containerDialogTitle('付款凭证', props)" :fullscreenable="Boolean(previewUrl) && previewIsImage" width="min(900px, calc(100vw - 28px))" :show-confirm="false" cancel-text="关闭" append-to-body>
<p v-if="previewLoading">正在读取付款凭证…</p>
<div v-else-if="previewError"><el-alert type="error" :closable="false" :title="previewError.message" /><ApiErrorMeta :details="previewError" /></div>
<div v-else-if="previewUrl" class="voucher-preview"><img v-if="previewIsImage" :src="previewUrl" :alt="previewName" /><p v-else>此文件暂不支持预览,请下载后查看。</p><a :href="previewUrl" :download="previewName">下载付款凭证</a></div>
<div v-else-if="previewUrl" class="density-dialog-section voucher-preview" data-tone="source"><p class="density-dialog-note">{{ previewName }}</p><img v-if="previewIsImage" :src="previewUrl" :alt="previewName" /><p v-else>此文件暂不支持预览,请下载后查看。</p><a :href="previewUrl" :download="previewName">下载付款凭证</a></div>
</AppDialog>
</div>
</template>
@@ -201,10 +213,13 @@ onMounted(load)
.voucher-list { display: flex; flex-direction: column; align-items: flex-start; gap: var(--admin-space-3); min-width: 0; }
.voucher-item { display: flex; gap: var(--admin-space-3); max-width: 100%; }
.voucher-list :deep(.el-button) { white-space: normal; overflow-wrap: anywhere; margin-left: 0; }
.voucher-preview { display: grid; gap: var(--admin-space-5); }
.voucher-preview { display: grid; gap: var(--admin-space-5); min-width: 0; overflow-wrap: anywhere; }
.voucher-preview img { max-width: 100%; max-height: 65vh; object-fit: contain; margin: auto; }
.row-actions { white-space: nowrap; }
.fee-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--admin-space-5); }
.fee-form-grid :deep(.el-select), .fee-form-grid :deep(.el-date-editor), .fee-form-grid :deep(.el-input-number) { width: 100%; }
@media (max-width: 760px) { .last-leg-fees__header { align-items: flex-start; flex-direction: column; } .fee-form-grid { grid-template-columns: 1fr; } }
.fee-editor-table { table-layout: fixed; }
@media (max-width: 760px) { .last-leg-fees__header { align-items: flex-start; flex-direction: column; } .fee-editor-table, .fee-editor-table tbody, .fee-editor-table tr, .fee-editor-table td { display: block; width: 100%; } }
</style>
<style>
.last-leg-voucher-dialog.is-fullscreen .voucher-preview img { max-height: 80vh; }
</style>