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
@@ -2333,34 +2333,59 @@ onBeforeUnmount(() => {
releaseMeasurementPreview()
releaseCargoImagePreviews()
})
function orderDialogTitle(action: string, inboundCode?: string | null) {
return inboundCode ? `${action} · ${inboundCode}` : action
}
</script>
<template>
<AdminPage>
<section class="business-page unified-orders-page admin-density">
<AppDialog class="admin-density-dialog" v-model="feeConfirmOpen" title="编辑计费数据" confirm-text="确认并发送" :confirm-loading="confirmFeeSending || feeConfirmReferenceLoading" width="min(920px, 94vw)" :teleported="false" @confirm="executeConfirmAndSendFee">
<AppDialog class="admin-density-dialog" v-model="feeConfirmOpen" :title="orderDialogTitle('编辑计费数据', detail?.summary.inboundCode)" confirm-text="确认并发送" :confirm-loading="confirmFeeSending || feeConfirmReferenceLoading" width="min(920px, 94vw)" fullscreenable :teleported="false" @confirm="executeConfirmAndSendFee">
<el-alert class="fee-confirm-price-alert" title="价格表单价仅作为参考;本次对客单价可人工修改,客户将一次确认计费量、单价和费用合计。" type="info" :closable="false" show-icon />
<el-form label-position="top" class="fee-confirm-form">
<el-form-item label="入仓码"><el-input :model-value="detail?.summary.inboundCode || '—'" disabled /></el-form-item>
<el-form-item label="渠道"><el-input :model-value="detail?.summary.channelName || detail?.summary.channelCode || '—'" disabled /></el-form-item>
<el-form-item label="计费毛重"><div class="fee-confirm-number-wrap"><el-input-number v-model="feeConfirmWeight" :min="0" :precision="3" :controls="false" class="fee-confirm-number" placeholder="请输入计费毛重" /><span class="fee-confirm-unit">kg</span></div></el-form-item>
<el-form-item label="计费体积"><div class="fee-confirm-number-wrap"><el-input-number v-model="feeConfirmVolume" :min="0" :precision="6" :controls="false" class="fee-confirm-number" placeholder="请输入计费体积" /><span class="fee-confirm-unit">m³</span></div></el-form-item>
<el-form-item label="价格表参考单价"><div class="fee-confirm-number-wrap"><el-input-number :model-value="feeConfirmPriceMissing || feeConfirmReference?.unitPrice == null ? undefined : Number(feeConfirmReference.unitPrice)" :precision="4" :controls="false" disabled class="fee-confirm-number" :placeholder="feeConfirmPriceMissing ? '未命中' : '正在读取参考单价'" /><span class="fee-confirm-unit">{{ feeConfirmPriceUnit }}</span></div></el-form-item>
<el-form-item label="本次对客单价"><div class="fee-confirm-number-wrap"><el-input-number v-model="feeConfirmUnitPrice" :min="0" :precision="4" :controls="false" :disabled="feeConfirmReferenceLoading" class="fee-confirm-number" placeholder="请输入对客单价" /><span class="fee-confirm-unit">{{ feeConfirmPriceUnit }}</span></div></el-form-item>
<el-form-item v-if="feeConfirmPriceChanged" :label="feeConfirmPriceMissing ? '人工定价原因' : '单价调整原因'" class="fee-confirm-price-reason"><el-input v-model="feeConfirmPriceReason" type="textarea" :rows="2" maxlength="500" show-word-limit placeholder="请说明本次人工定价的原因" /></el-form-item>
</el-form>
<dl v-if="feeConfirmReference" class="fee-confirm-price-summary">
<div><dt>参考计费量</dt><dd>{{ formatNumber(Number(feeConfirmReference.chargeableQuantity), feeConfirmReference.roundingScale) }} {{ feeConfirmReference.chargeUnit }}</dd></div>
<div><dt>按当前单价预计合计</dt><dd>{{ feeConfirmEstimatedTotal == null ? '—' : feeConfirmMoney(feeConfirmEstimatedTotal, feeConfirmReference.currency) }}</dd></div>
</dl>
<h4 class="fee-confirm-reference-title">参考数据</h4>
<el-table :data="feeConfirmRows" border size="small">
<el-table-column prop="source" label="数据来源" />
<el-table-column prop="count" label="件数" />
<el-table-column label="总毛重(T.K.GW)" align="right"><template #default="{ row }">{{ row.weight == null ? '—' : formatNumber(row.weight, 2) }}</template></el-table-column>
<el-table-column label="总体积(m³)"><template #default="{ row }">{{ row.volume == null ? '—' : formatNumber(row.volume, 4) }}</template></el-table-column>
<el-table-column label="总材积重"><template #default="{ row }">{{ row.volume == null ? '—' : formatNumber(Number(row.volume) * 167, 2) }}</template></el-table-column>
</el-table>
<section class="density-dialog-section" data-tone="work">
<h3 class="density-dialog-section__title">本次计费数据 <small>毛重、体积和对客单价可修改</small></h3>
<table class="density-table density-table--editable fee-confirm-table" aria-label="本次计费数据">
<colgroup><col class="fee-confirm-label-column" /><col /><col class="fee-confirm-label-column" /><col /></colgroup>
<tbody>
<tr>
<th scope="row">入仓码</th><td>{{ detail?.summary.inboundCode || '—' }}</td>
<th scope="row">渠道</th><td>{{ detail?.summary.channelName || detail?.summary.channelCode || '—' }}</td>
</tr>
<tr>
<th scope="row"><label for="fee-confirm-weight">计费毛重</label></th>
<td><div class="density-table-editor"><el-input-number id="fee-confirm-weight" v-model="feeConfirmWeight" :min="0" :precision="3" :controls="false" placeholder="请输入计费毛重" /><span>kg</span></div></td>
<th scope="row"><label for="fee-confirm-volume">计费体积</label></th>
<td><div class="density-table-editor"><el-input-number id="fee-confirm-volume" v-model="feeConfirmVolume" :min="0" :precision="6" :controls="false" placeholder="请输入计费体积" /><span>m³</span></div></td>
</tr>
<tr>
<th scope="row">价格表参考单价</th>
<td>{{ feeConfirmPriceMissing ? '未命中' : feeConfirmReference?.unitPrice == null ? '正在读取参考单价' : `${formatNumber(Number(feeConfirmReference.unitPrice), 4)} ${feeConfirmPriceUnit}` }}</td>
<th scope="row"><label for="fee-confirm-unit-price">本次对客单价</label></th>
<td><div class="density-table-editor"><el-input-number id="fee-confirm-unit-price" v-model="feeConfirmUnitPrice" :min="0" :precision="4" :controls="false" :disabled="feeConfirmReferenceLoading" placeholder="请输入对客单价" /><span>{{ feeConfirmPriceUnit }}</span></div></td>
</tr>
<tr v-if="feeConfirmReference">
<th scope="row">参考计费量</th><td>{{ formatNumber(Number(feeConfirmReference.chargeableQuantity), feeConfirmReference.roundingScale) }} {{ feeConfirmReference.chargeUnit }}</td>
<th scope="row">按当前单价预计合计</th><td><strong>{{ feeConfirmEstimatedTotal == null ? '—' : feeConfirmMoney(feeConfirmEstimatedTotal, feeConfirmReference.currency) }}</strong></td>
</tr>
<tr v-if="feeConfirmPriceChanged">
<th scope="row"><label for="fee-confirm-price-reason">{{ feeConfirmPriceMissing ? '人工定价原因' : '单价调整原因' }}</label></th>
<td colspan="3"><el-input id="fee-confirm-price-reason" v-model="feeConfirmPriceReason" type="textarea" :rows="2" maxlength="500" show-word-limit placeholder="请说明本次人工定价的原因" /></td>
</tr>
</tbody>
</table>
</section>
<section class="density-dialog-section" data-tone="source">
<h3 class="density-dialog-section__title">参考数据</h3>
<el-table :data="feeConfirmRows" border size="small">
<el-table-column prop="source" label="数据来源" />
<el-table-column prop="count" label="件数" />
<el-table-column label="总毛重(T.K.GW)" align="right"><template #default="{ row }">{{ row.weight == null ? '—' : formatNumber(row.weight, 2) }}</template></el-table-column>
<el-table-column label="总体积(m³)"><template #default="{ row }">{{ row.volume == null ? '—' : formatNumber(row.volume, 4) }}</template></el-table-column>
<el-table-column label="总材积重"><template #default="{ row }">{{ row.volume == null ? '—' : formatNumber(Number(row.volume) * 167, 2) }}</template></el-table-column>
</el-table>
</section>
</AppDialog>
<template v-if="isDetailRoute">
<DetailPageLayout ariaLabel="统一订单详情">
@@ -2643,7 +2668,7 @@ onBeforeUnmount(() => {
<div class="measurement-total"><strong>合计</strong><span>箱数:{{ measurementTotals(actualMeasurementRows).count ?? '—' }}</span><span>总毛重:{{ formatNumber(measurementTotals(actualMeasurementRows).gross) }}</span><span>总体积:{{ formatNumber(measurementTotals(actualMeasurementRows).volume, 6) }}</span><span>总材积重:{{ formatNumber(measurementTotals(actualMeasurementRows).volumetric) }}</span><span>重量体积:{{ formatNumber(measurementTotals(actualMeasurementRows).weightVolume, 6) }}</span></div>
<section class="weight-evidence" aria-label="称重图片与服务端确认重量">
<div class="weight-evidence__heading">
<div><h4>称重图片与确认重量</h4><p>称重图片独立展示;重量值直接采用服务端确认结果。</p></div>
<div><h4>称重图片与确认重量</h4></div>
<dl><dt>实际总毛重</dt><dd>{{ formatNumber(measurementDetail?.actualGrossWeightKg) }} kg</dd></dl>
</div>
<div v-if="measurementDetail?.weightResults.length" class="weight-evidence-grid">
@@ -2846,17 +2871,23 @@ onBeforeUnmount(() => {
</ListPageLayout>
<AppDialog class="admin-density-dialog" v-model="legacyImportOpen" title="导入旧系统订单" width="min(760px, calc(100vw - 28px))" confirm-text="开始导入" :confirm-loading="legacyImportSaving" :confirm-disabled="!legacyImportFile || legacyImportSaving || !!legacyImportResult" destroy-on-close @confirm="submitLegacyImport">
<div class="legacy-import-content">
<dl class="legacy-import-facts">
<div><dt>固定归属客户</dt><dd>IT测试公司</dd></div>
<div><dt>导入结果状态</dt><dd>待审核</dd></div>
</dl>
<el-upload :auto-upload="false" :limit="1" accept=".xlsx" :on-change="selectLegacyImportFile">
<el-button><FileUp :size="16" />选择 Excel 文件</el-button>
<template #tip><div class="el-upload__tip">仅支持旧系统“订单统计导出” .xlsx 文件,最多 200 条、5MB;入仓码保持不变。</div></template>
</el-upload>
<div class="legacy-import-content density-dialog-sections">
<section class="density-dialog-section" data-tone="source">
<h3 class="density-dialog-section__title">导入规则</h3>
<dl class="legacy-import-facts">
<div><dt>固定归属客户</dt><dd>IT测试公司</dd></div>
<div><dt>导入结果状态</dt><dd>待审核</dd></div>
</dl>
</section>
<section class="density-dialog-section" data-tone="work">
<h3 class="density-dialog-section__title">导入文件</h3>
<el-upload :auto-upload="false" :limit="1" accept=".xlsx" :on-change="selectLegacyImportFile">
<el-button><FileUp :size="16" />选择 Excel 文件</el-button>
<template #tip><div class="el-upload__tip">仅支持旧系统“订单统计导出” .xlsx 文件,最多 200 条、5MB;入仓码保持不变。</div></template>
</el-upload>
</section>
<LoadErrorState v-if="legacyImportError" compact title="旧系统订单导入失败" :message="legacyImportError.message" :show-retry="false"><template #details><ApiErrorMeta :details="legacyImportError" /></template></LoadErrorState>
<div v-if="legacyImportResult" class="legacy-import-result">
<div v-if="legacyImportResult" class="legacy-import-result density-dialog-section" data-tone="summary">
<p>共 {{ legacyImportResult.total }} 条,成功 {{ legacyImportResult.succeeded }} 条,失败 {{ legacyImportResult.failed }} 条。</p>
<el-table v-if="legacyImportResult.failed" :data="legacyImportResult.items.filter((item) => !item.success)" border stripe max-height="280">
<el-table-column prop="excelRow" label="Excel 行" width="90" />
@@ -2867,25 +2898,34 @@ onBeforeUnmount(() => {
</div>
</AppDialog>
<SmartLoadingDialog v-model="smartLoadingOpen" />
<AppDialog class="admin-density-dialog" v-model="batchApprovalOpen" title="批量审核订单" confirm-text="确认批量审核" confirm-type="success" width="min(1100px, calc(100vw - 28px))" :loading="batchApprovalLoading" :confirm-loading="batchApprovalSaving" :confirm-disabled="batchApprovalLoading || !batchApprovalSelectedRows.length" destroy-on-close @confirm="batchApproveOrders">
<AppDialog class="admin-density-dialog" v-model="batchApprovalOpen" title="批量审核订单" confirm-text="确认批量审核" confirm-type="success" width="min(1100px, calc(100vw - 28px))" fullscreenable :loading="batchApprovalLoading" :confirm-loading="batchApprovalSaving" :confirm-disabled="batchApprovalLoading || !batchApprovalSelectedRows.length" destroy-on-close @confirm="batchApproveOrders">
<el-alert title="请逐票核对入仓码、客户、中文品名、渠道和目的仓;审核通过后订单进入待测量。" type="info" :closable="false" show-icon />
<LoadErrorState v-if="batchApprovalError" compact title="批量审核预览加载失败" :message="batchApprovalError.message" :loading="batchApprovalLoading" @retry="openBatchApproval"><template #details><ApiErrorMeta :details="batchApprovalError" /></template></LoadErrorState>
<template v-else>
<div class="batch-approval-summary"><span>预览 {{ batchApprovalPreviewRows.length }} 票</span><strong>已勾选 {{ batchApprovalSelectedRows.length }} 票</strong></div>
<el-table ref="batchApprovalTableRef" class="batch-approval-table" :data="batchApprovalPreviewRows" row-key="orderId" border stripe max-height="520" empty-text="暂无可审核订单" @selection-change="handleBatchApprovalSelection">
<el-table-column type="selection" width="48" fixed="left" :selectable="(row: UnifiedOrderBatchApprovalPreviewItem) => row.canApprove" />
<el-table-column label="入仓码" min-width="160" show-overflow-tooltip><template #default="{ row }"><strong>{{ row.inboundCode || '—' }}</strong></template></el-table-column>
<el-table-column label="客户" min-width="150" show-overflow-tooltip><template #default="{ row }">{{ row.customerName || '—' }}</template></el-table-column>
<el-table-column label="中文品名" min-width="220" show-overflow-tooltip><template #default="{ row }">{{ row.chineseGoodsNames.length ? row.chineseGoodsNames.join('、') : '—' }}</template></el-table-column>
<el-table-column label="渠道" min-width="160" show-overflow-tooltip><template #default="{ row }">{{ row.channelName || row.channelCode || '—' }}</template></el-table-column>
<el-table-column label="目的仓" min-width="180" show-overflow-tooltip><template #default="{ row }">{{ row.destinationWarehouseName || row.destinationWarehouseCode || '—' }}</template></el-table-column>
<el-table-column label="校验结果" min-width="180" show-overflow-tooltip><template #default="{ row }"><el-tag :type="row.canApprove ? 'success' : 'danger'" effect="light">{{ row.canApprove ? '可审核' : row.blockReason || '不可审核' }}</el-tag></template></el-table-column>
</el-table>
<section class="density-dialog-section" data-tone="summary">
<h3 class="density-dialog-section__title">本次审核范围</h3>
<div class="batch-approval-summary"><span>预览 {{ batchApprovalPreviewRows.length }} 票</span><strong>已勾选 {{ batchApprovalSelectedRows.length }} 票</strong></div>
</section>
<section class="density-dialog-section" data-tone="source">
<h3 class="density-dialog-section__title">待审核订单</h3>
<el-table ref="batchApprovalTableRef" class="batch-approval-table" :data="batchApprovalPreviewRows" row-key="orderId" border stripe max-height="520" empty-text="暂无可审核订单" @selection-change="handleBatchApprovalSelection">
<el-table-column type="selection" width="48" fixed="left" :selectable="(row: UnifiedOrderBatchApprovalPreviewItem) => row.canApprove" />
<el-table-column label="入仓码" min-width="160" show-overflow-tooltip><template #default="{ row }"><strong>{{ row.inboundCode || '—' }}</strong></template></el-table-column>
<el-table-column label="客户" min-width="150" show-overflow-tooltip><template #default="{ row }">{{ row.customerName || '—' }}</template></el-table-column>
<el-table-column label="中文品名" min-width="220" show-overflow-tooltip><template #default="{ row }">{{ row.chineseGoodsNames.length ? row.chineseGoodsNames.join('、') : '—' }}</template></el-table-column>
<el-table-column label="渠道" min-width="160" show-overflow-tooltip><template #default="{ row }">{{ row.channelName || row.channelCode || '—' }}</template></el-table-column>
<el-table-column label="目的仓" min-width="180" show-overflow-tooltip><template #default="{ row }">{{ row.destinationWarehouseName || row.destinationWarehouseCode || '—' }}</template></el-table-column>
<el-table-column label="校验结果" min-width="180" show-overflow-tooltip><template #default="{ row }"><el-tag :type="row.canApprove ? 'success' : 'danger'" effect="light">{{ row.canApprove ? '可审核' : row.blockReason || '不可审核' }}</el-tag></template></el-table-column>
</el-table>
</section>
</template>
</AppDialog>
<AppDialog class="admin-density-dialog" v-model="coLoadCreateOpen" title="合柜报关" confirm-text="确认建组" :confirm-loading="coLoadSaving" :confirm-disabled="!canCreateCoLoadGroup" @confirm="createCoLoadGroup">
<el-alert type="warning" :closable="false" show-icon :title="`所选 ${selectedOrders.length} 票须属于同一国内仓、同一渠道,并全量进入同一柜;目的仓或目的港可以不同。资格由服务端按完整入仓片段校验。`" />
<div class="coload-selection"><span v-for="row in selectedOrders" :key="String(row.orderId)">{{ row.inboundCode || '待生成入仓码' }}</span></div>
<section class="density-dialog-section" data-tone="source">
<h3 class="density-dialog-section__title">所选入仓码</h3>
<div class="coload-selection"><span v-for="row in selectedOrders" :key="String(row.orderId)">{{ row.inboundCode || '待生成入仓码' }}</span></div>
</section>
<div v-if="coLoadError" class="coload-error" role="alert"><span>{{ coLoadError.message }}</span><ApiErrorMeta :details="coLoadError" /></div>
</AppDialog>
<AppDialog class="admin-density-dialog" v-model="coLoadDetailOpen" title="同柜组成员" :show-confirm="false" cancel-text="关闭" width="min(720px, 94vw)" :loading="coLoadDetailLoading" :confirm-loading="coLoadCancelSaving">
@@ -2893,12 +2933,15 @@ onBeforeUnmount(() => {
<template v-else-if="coLoadDetailSummary">
<div class="coload-detail-summary"><el-tag size="small" :type="coLoadDetailSummary.status === 'ACTIVE' ? 'warning' : 'info'">{{ coLoadGroupSummary(coLoadDetailSummary) }}</el-tag><span>{{ coLoadDetailSummary.memberCount }} 个成员</span></div>
<el-alert v-if="coLoadDetailHasMissingInboundCodes" type="error" :closable="false" show-icon title="同柜组成员入仓码尚未返回,请刷新后重试" />
<div v-else-if="coLoadDetailMembers.length" class="coload-detail-scroll">
<table class="coload-detail-table">
<thead><tr><th>入仓码</th></tr></thead>
<tbody><tr v-for="member in coLoadDetailMembers" :key="member.inboundCode"><td><strong :title="member.inboundCode">{{ member.inboundCode }}</strong></td></tr></tbody>
</table>
</div>
<section v-else-if="coLoadDetailMembers.length" class="density-dialog-section" data-tone="source">
<h3 class="density-dialog-section__title">同柜成员</h3>
<div class="coload-detail-scroll">
<table class="coload-detail-table">
<thead><tr><th>入仓码</th></tr></thead>
<tbody><tr v-for="member in coLoadDetailMembers" :key="member.inboundCode"><td><strong :title="member.inboundCode">{{ member.inboundCode }}</strong></td></tr></tbody>
</table>
</div>
</section>
<el-empty v-else description="暂无同柜组成员" :image-size="56" />
<div v-if="coLoadCancelError" class="coload-error" role="alert"><span>{{ coLoadCancelError.message }}</span><ApiErrorMeta :details="coLoadCancelError" /></div>
</template>
@@ -2915,7 +2958,7 @@ onBeforeUnmount(() => {
/>
</AppDrawer>
<AppDialog class="admin-density-dialog" v-model="noteOpen" title="编辑订单备注" width="min(680px, calc(100vw - 28px))" confirm-text="保存备注" :confirm-loading="noteSaving" @confirm="saveNote">
<AppDialog class="admin-density-dialog" v-model="noteOpen" :title="orderDialogTitle('编辑订单备注', noteOrder?.inboundCode)" width="min(680px, calc(100vw - 28px))" confirm-text="保存备注" :confirm-loading="noteSaving" @confirm="saveNote">
<el-form label-position="top" class="order-remarks-form">
<el-form-item label="客户备注(只读)"><el-input :model-value="noteOrder?.customerRemark || '—'" disabled /></el-form-item>
<el-form-item label="内部备注"><el-input v-model="noteForm.internalRemark" type="textarea" :rows="3" maxlength="1000" show-word-limit placeholder="可填写、修改或清空内部备注" /></el-form-item>
@@ -2924,19 +2967,22 @@ onBeforeUnmount(() => {
</el-form>
</AppDialog>
<AppDialog class="admin-density-dialog" v-model="holdDialogOpen" :title="holdAction === 'HOLD' ? '确认扣货' : '确认解除扣货'" :confirm-text="holdAction === 'HOLD' ? '确认扣货' : '确认解除'" :confirm-type="holdAction === 'HOLD' ? 'danger' : 'primary'" :confirm-loading="holdSaving" :confirm-disabled="!holdReason.trim() || holdReason.trim().length > 1000" width="min(600px, calc(100vw - 28px))" destroy-on-close @confirm="submitHoldAction">
<AppDialog class="admin-density-dialog" v-model="holdDialogOpen" :title="orderDialogTitle(holdAction === 'HOLD' ? '确认扣货' : '确认解除扣货', detail?.summary.inboundCode)" :confirm-text="holdAction === 'HOLD' ? '确认扣货' : '确认解除'" :confirm-type="holdAction === 'HOLD' ? 'danger' : 'primary'" :confirm-loading="holdSaving" :confirm-disabled="!holdReason.trim() || holdReason.trim().length > 1000" width="min(600px, calc(100vw - 28px))" destroy-on-close @confirm="submitHoldAction">
<el-alert :title="holdAction === 'HOLD' ? '扣货后服务端将阻止后续实物履约操作。' : '解除后是否可继续履约仍以服务端状态为准。'" :type="holdAction === 'HOLD' ? 'warning' : 'info'" :closable="false" show-icon />
<el-form label-position="top" class="hold-command-form"><el-form-item :label="holdAction === 'HOLD' ? '扣货原因' : '解除原因'" required><el-input v-model="holdReason" type="textarea" :rows="4" maxlength="1000" show-word-limit :placeholder="holdAction === 'HOLD' ? '请输入扣货原因' : '请输入解除扣货原因'" aria-label="扣货操作原因" /></el-form-item><el-form-item label="关联文件(选填)"><el-upload :key="holdEvidenceUploadKey" :auto-upload="false" :limit="1" accept=".txt,.pdf,.jpg,.jpeg,.png,text/plain,application/pdf,image/jpeg,image/png" :disabled="holdSaving" :on-change="selectHoldEvidenceFile" :on-remove="removeHoldEvidenceFile"><el-button :disabled="holdSaving"><FileUp :size="15" />选择文件</el-button><template #tip><div class="el-upload__tip">仅支持 TXT、PDF、JPG、PNG,单文件不超过 10MB;服务端将校验文件真实内容。</div></template></el-upload></el-form-item><LoadErrorState v-if="holdEvidenceError" compact title="关联文件处理失败" :message="holdEvidenceError.message" :show-retry="false"><template #details><ApiErrorMeta :details="holdEvidenceError" /></template></LoadErrorState></el-form>
</AppDialog>
<AppDialog class="admin-density-dialog" v-model="approvalOpen" title="审核订单" confirm-text="审核通过" confirm-type="success" :confirm-loading="approvalSaving" destroy-on-close @confirm="approveOrder" @cancel="closeApproval">
<dl v-if="approvalOrder" class="approval-facts">
<div><dt>入仓码</dt><dd>{{ approvalOrder.inboundCode || '—' }}</dd></div>
<div><dt>客户</dt><dd>{{ customerName(approvalOrder) }}</dd></div>
<div><dt>渠道</dt><dd>{{ approvalOrder.channelName || '—' }}</dd></div>
<div><dt>目的仓</dt><dd>{{ approvalOrder.destinationWarehouseName || '—' }}</dd></div>
<div><dt>总箱数</dt><dd>{{ approvalOrder.totalPackageCount ?? '—' }} 箱</dd></div>
</dl>
<AppDialog class="admin-density-dialog" v-model="approvalOpen" :title="orderDialogTitle('审核订单', approvalOrder?.inboundCode)" confirm-text="审核通过" confirm-type="success" :confirm-loading="approvalSaving" destroy-on-close @confirm="approveOrder" @cancel="closeApproval">
<section v-if="approvalOrder" class="density-dialog-section" data-tone="summary">
<h3 class="density-dialog-section__title">订单审核资料</h3>
<dl class="approval-facts">
<div><dt>入仓码</dt><dd>{{ approvalOrder.inboundCode || '—' }}</dd></div>
<div><dt>客户</dt><dd>{{ customerName(approvalOrder) }}</dd></div>
<div><dt>渠道</dt><dd>{{ approvalOrder.channelName || '—' }}</dd></div>
<div><dt>目的仓</dt><dd>{{ approvalOrder.destinationWarehouseName || '—' }}</dd></div>
<div><dt>总箱数</dt><dd>{{ approvalOrder.totalPackageCount ?? '—' }} 箱</dd></div>
</dl>
</section>
<p class="approval-hint">确认订单资料无误后审核通过,订单将进入待测量阶段。</p>
</AppDialog>
@@ -2952,6 +2998,7 @@ onBeforeUnmount(() => {
v-model="measurementPreviewOpen"
:title="measurementPreviewTitle"
width="min(920px, calc(100vw - 28px))"
fullscreenable
:loading="measurementPreviewLoading"
:show-footer="false"
destroy-on-close
@@ -3018,7 +3065,7 @@ onBeforeUnmount(() => {
.table-card :deep(.el-table__row.is-coload-group-row > .el-table__cell) { background: #fff6df; color: #7a4b00; }
.table-card :deep(.el-table__row.is-coload-group-row:hover > .el-table__cell) { background: #ffedbd; }
.table-card :deep(.el-table__row.is-coload-group-row > .el-table__cell:first-child) { box-shadow: inset 3px 0 0 #e6a23c; }
.coload-selection { display: flex; flex-wrap: wrap; gap: var(--admin-space-2, 6px); margin-top: var(--admin-space-5, 12px); }
.coload-selection { display: flex; flex-wrap: wrap; gap: var(--admin-space-2, 6px); }
.coload-selection > span { padding: var(--admin-space-1, 4px) 7px; border: 1px solid var(--cc-border); border-radius: var(--admin-radius, 4px); background: var(--cc-surface-muted); font-size: var(--admin-font-caption, 12px); }
.coload-detail-summary { display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-5, 12px); margin-bottom: var(--admin-space-4, 10px); }
.coload-detail-scroll { overflow-x: auto; border: 1px solid var(--cc-border); border-radius: var(--admin-radius, 4px); }
@@ -3035,9 +3082,8 @@ onBeforeUnmount(() => {
.subsequent-fulfillment { margin-top: var(--admin-space-5, 12px); padding-top: var(--admin-space-4, 10px); border-top: 1px solid var(--cc-border); }
.subsequent-fulfillment h3 { margin: 0 0 var(--admin-space-3, 8px); font-size: var(--admin-font-section, 14px); }
.fulfillment-facts { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 0; border-top: 1px solid var(--cc-border); border-left: 1px solid var(--cc-border); overflow: hidden; }
.batch-approval-summary { display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-6, 16px); margin: var(--admin-space-5, 12px) 0 var(--admin-space-3, 8px); color: var(--cc-muted); font-size: var(--admin-font-body, 13px); white-space: nowrap; }
.batch-approval-summary { display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-6, 16px); margin: 0; color: var(--cc-muted); font-size: var(--admin-font-body, 13px); white-space: nowrap; }
.batch-approval-summary strong { color: var(--cc-text); }
.batch-approval-table :deep(.cell) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fulfillment-facts > div { display: flex; align-items: baseline; gap: var(--admin-space-4, 10px); min-width: 0; padding: var(--admin-space-3, 8px) var(--admin-space-4, 10px); border-right: 1px solid var(--cc-border); border-bottom: 1px solid var(--cc-border); background: var(--admin-surface, #fff); }
.fulfillment-facts > div.wide { grid-column: span 2; }
.fulfillment-facts dt { flex: 0 0 auto; color: var(--cc-muted); font-size: var(--admin-font-caption, 12px); }
@@ -3075,20 +3121,14 @@ onBeforeUnmount(() => {
.panel-title > svg { color: var(--cc-primary); }
.measurement-title-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--admin-space-3, 8px); }
.measurement-title-actions :deep(.el-button) { margin-left: 0; }
.fee-confirm-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 22px; }
.fee-confirm-price-alert { margin-bottom: 14px; }
.fee-confirm-number-wrap { position: relative; width: 100%; }
.fee-confirm-number { width: 100%; }
.fee-confirm-number :deep(.el-input__wrapper) { padding-right: 92px; }
.fee-confirm-unit { position: absolute; top: 50%; right: 12px; z-index: 1; transform: translateY(-50%); color: var(--cc-muted); pointer-events: none; }
.fee-confirm-reference-title { margin: var(--admin-space-3, 8px) 0 var(--admin-space-5, 12px); color: var(--cc-text); font-size: var(--admin-font-section, 14px); }
.fee-confirm-price-reason { grid-column: 1 / -1; }
.fee-confirm-price-summary { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 0 0 var(--admin-space-6, 16px); border: 1px solid var(--cc-border); background: var(--admin-label-bg, #f8fafc); }
.fee-confirm-price-summary > div { min-width: 0; padding: var(--admin-space-4, 10px) var(--admin-space-5, 12px); }
.fee-confirm-price-summary > div + div { border-left: 1px solid var(--cc-border); }
.fee-confirm-price-summary dt { color: var(--cc-muted); font-size: var(--admin-font-caption, 11px); }
.fee-confirm-price-summary dd { margin: var(--admin-space-1, 4px) 0 0; color: var(--cc-text); font-size: var(--admin-font-body, 13px); font-weight: 650; }
@media (max-width: 680px) { .fee-confirm-form { grid-template-columns: 1fr; } .fee-confirm-unit { right: 12px; } }
.fee-confirm-price-alert { margin-bottom: var(--admin-space-5); }
.fee-confirm-table { table-layout: fixed; }
.fee-confirm-label-column { width: 18%; }
@media (max-width: 680px) {
.fee-confirm-table colgroup { display: none; }
.fee-confirm-table tr { display: grid; grid-template-columns: 36% minmax(0, 1fr); }
.fee-confirm-table :is(th, td) { min-width: 0; }
}
.fee-section-title { margin: var(--admin-space-5, 12px) 0 var(--admin-space-3, 8px); color: #344054; font-size: var(--admin-font-section, 14px); line-height: 1.4; }
.fee-overview { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); margin: 0; border: 1px solid var(--cc-border); }
.fee-overview > div { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--admin-space-1, 4px) var(--admin-space-5, 12px); padding: var(--admin-space-3, 8px) var(--admin-space-4, 10px); }
@@ -3143,7 +3183,6 @@ onBeforeUnmount(() => {
.hold-summary dt { flex: 0 0 auto; margin: 0; color: var(--cc-muted); font-size: var(--admin-font-caption, 12px); }
.hold-summary dd { margin: 0; color: var(--cc-text); font-size: var(--admin-font-body, 13px); overflow-wrap: anywhere; }
.hold-actions { display: flex; flex: 0 0 auto; gap: var(--admin-space-3, 8px); }
.legacy-import-content { display: grid; gap: var(--admin-space-5, 12px); }
.legacy-import-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); margin: 0; border: 1px solid var(--cc-border); }
.legacy-import-facts > div { display: grid; grid-template-columns: 120px minmax(0, 1fr); align-items: center; padding-block: var(--admin-space-2, 6px); }
.legacy-import-facts dt { padding: 8px 10px; color: var(--cc-text-muted); }