style(admin): unify dialog layouts and dense readonly presentation
This commit is contained in:
@@ -9,6 +9,7 @@ import { listAllPlatformWarehouses, type PlatformWarehouseSummaryResource } from
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import InlineHelp from '@/shared/components/InlineHelp.vue'
|
||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
||||
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
||||
@@ -77,7 +78,7 @@ async function submitPalletMerge(required: boolean) {
|
||||
try {
|
||||
await ElMessageBox.confirm(required
|
||||
? `将勾选的 ${selection.length} 票订单标记为同一合托组?所选订单须属于同一客户、同一目的仓,PDA 将提示现场合托。`
|
||||
: `取消勾选的 ${selection.length} 票订单的合托标记和 PDA 提示?`, label,
|
||||
: `取消勾选的 ${selection.length} 票订单的合托标记和 PDA 提示?`, `${label} · ${selection.length} 票订单`,
|
||||
{ customClass: 'admin-density-confirm', confirmButtonText: `确认${label}`, cancelButtonText: '返回', type: 'warning' })
|
||||
} catch { return }
|
||||
actionLoading.value = true
|
||||
@@ -910,12 +911,12 @@ async function reviewDiscard(row: LastMileOrderListItem) {
|
||||
let decision: 'APPROVE' | 'REJECT' = 'APPROVE'
|
||||
let remark: string | undefined
|
||||
try {
|
||||
await ElMessageBox.confirm(`请核对废弃申请 ${row.orderNo},确认审核结论。`, '废弃申请审核', {
|
||||
await ElMessageBox.confirm(`请核对废弃申请 ${row.orderNo},确认审核结论。`, `废弃申请审核 · ${row.orderNo || '—'}`, {
|
||||
customClass: 'admin-density-confirm', confirmButtonText: '审核通过', cancelButtonText: '驳回', distinguishCancelAndClose: true,
|
||||
})
|
||||
} catch (action) {
|
||||
if (action !== 'cancel') throw action
|
||||
const { value } = await ElMessageBox.prompt('请填写驳回原因', '驳回废弃申请', {
|
||||
const { value } = await ElMessageBox.prompt('请填写驳回原因', `驳回废弃申请 · ${row.orderNo || '—'}`, {
|
||||
customClass: 'admin-density-confirm', inputType: 'textarea',
|
||||
inputValidator: (input) => Boolean(input?.trim()) && input.trim().length <= 1000 || '请填写 1 至 1000 字驳回原因',
|
||||
confirmButtonText: '确认驳回', cancelButtonText: '取消',
|
||||
@@ -938,14 +939,14 @@ async function reviewReturn(source: object) {
|
||||
const returnNo = row.sourceOrderNo
|
||||
if (!returnNo || !row.customerId) return
|
||||
try {
|
||||
await ElMessageBox.confirm('审核通过后,仓库才可以签收这张退货预报。', '退货预报审核', { customClass: 'admin-density-confirm', confirmButtonText: '审核通过', cancelButtonText: '驳回', distinguishCancelAndClose: true })
|
||||
await ElMessageBox.confirm('审核通过后,仓库才可以签收这张退货预报。', `退货预报审核 · ${returnNo}`, { customClass: 'admin-density-confirm', confirmButtonText: '审核通过', cancelButtonText: '驳回', distinguishCancelAndClose: true })
|
||||
await reviewReturnOrder(returnNo, row.customerId, 'APPROVE')
|
||||
ElMessage.success('退货预报已审核通过')
|
||||
await load()
|
||||
} catch (error) {
|
||||
if (error === 'cancel') {
|
||||
try {
|
||||
const { value } = await ElMessageBox.prompt('请填写驳回原因', '驳回退货预报', { customClass: 'admin-density-confirm', inputType: 'textarea', inputValidator: (input) => Boolean(input?.trim()) || '请填写驳回原因', confirmButtonText: '确认驳回', cancelButtonText: '取消' })
|
||||
const { value } = await ElMessageBox.prompt('请填写驳回原因', `驳回退货预报 · ${returnNo}`, { customClass: 'admin-density-confirm', inputType: 'textarea', inputValidator: (input) => Boolean(input?.trim()) || '请填写驳回原因', confirmButtonText: '确认驳回', cancelButtonText: '取消' })
|
||||
await reviewReturnOrder(returnNo, row.customerId, 'REJECT', value)
|
||||
ElMessage.success('退货预报已驳回')
|
||||
await load()
|
||||
@@ -957,14 +958,14 @@ async function reviewDirectRelabel(source: object) {
|
||||
const row = source as LastMileOrderListItem
|
||||
if (!row.id || !row.customerId) return
|
||||
try {
|
||||
await ElMessageBox.confirm('审核通过后,贴标单将进入海外仓换标作业。', '贴标单审核', { customClass: 'admin-density-confirm', confirmButtonText: '审核通过', cancelButtonText: '驳回', distinguishCancelAndClose: true })
|
||||
await ElMessageBox.confirm('审核通过后,贴标单将进入海外仓换标作业。', `贴标单审核 · ${row.orderNo || '—'}`, { customClass: 'admin-density-confirm', confirmButtonText: '审核通过', cancelButtonText: '驳回', distinguishCancelAndClose: true })
|
||||
await reviewDirectRelabelOrder(row.id, row.customerId, 'APPROVE')
|
||||
ElMessage.success('贴标单已审核通过')
|
||||
await load()
|
||||
} catch (error) {
|
||||
if (error === 'cancel') {
|
||||
try {
|
||||
const { value } = await ElMessageBox.prompt('请填写驳回原因,客户可在贴标单详情查看并修改后重新提交。', '驳回贴标单', { customClass: 'admin-density-confirm', inputType: 'textarea', inputValidator: (input) => Boolean(input?.trim()) || '请填写驳回原因', confirmButtonText: '确认驳回', cancelButtonText: '取消' })
|
||||
const { value } = await ElMessageBox.prompt('请填写驳回原因,客户可在贴标单详情查看并修改后重新提交。', `驳回贴标单 · ${row.orderNo || '—'}`, { customClass: 'admin-density-confirm', inputType: 'textarea', inputValidator: (input) => Boolean(input?.trim()) || '请填写驳回原因', confirmButtonText: '确认驳回', cancelButtonText: '取消' })
|
||||
await reviewDirectRelabelOrder(row.id, row.customerId, 'REJECT', value)
|
||||
ElMessage.success('贴标单已驳回')
|
||||
await load()
|
||||
@@ -1023,7 +1024,7 @@ async function confirmRelabel(row: { relabelOrderNo?: string }) {
|
||||
const relabelNo = row.relabelOrderNo
|
||||
if (!relabelNo) return
|
||||
try {
|
||||
await ElMessageBox.confirm('确认仓库上传的换标结果无误并继续后续流程吗?', '换标完成确认', { customClass: 'admin-density-confirm', confirmButtonText: '确认并继续', cancelButtonText: '取消' })
|
||||
await ElMessageBox.confirm('确认仓库上传的换标结果无误并继续后续流程吗?', `换标完成确认 · ${relabelNo}`, { customClass: 'admin-density-confirm', confirmButtonText: '确认并继续', cancelButtonText: '取消' })
|
||||
await confirmRelabelCompletion(relabelNo)
|
||||
ElMessage.success('已确认换标完成')
|
||||
await load()
|
||||
@@ -1146,68 +1147,42 @@ onMounted(() => { void load() })
|
||||
</DataTable>
|
||||
</ListPageLayout></AdminPage>
|
||||
<WarehouseTransferReviewDialog v-if="transferReviewTarget" v-model="transferReviewOpen" :customer-id="transferReviewTarget.customerId" :customer-name="transferReviewTarget.owner" :transfer-no="transferReviewTarget.orderNo" @reviewed="load()" />
|
||||
<AppDialog class="admin-density-dialog" v-model="transferOpen" title="转仓" confirm-text="确认转仓" :loading="transferWarehouseLoading" :confirm-loading="actionLoading" :confirm-disabled="!transferForm.warehouseId || Boolean(transferWarehouseError)" @confirm="submitTransfer">
|
||||
<LoadErrorState v-if="transferWarehouseError" title="FBA 仓候选加载失败" :message="transferWarehouseError.message" compact @retry="loadTransferWarehouseOptions">
|
||||
<template #details><ApiErrorMeta :details="transferWarehouseError" /></template>
|
||||
</LoadErrorState>
|
||||
<el-form class="density-form" v-else label-position="top">
|
||||
<el-form-item label="目的 FBA 仓" required>
|
||||
<el-select v-model="transferForm.warehouseId" filterable clearable placeholder="请选择目的 FBA 仓" @update:model-value="selectTransferWarehouse">
|
||||
<el-option v-for="warehouse in transferWarehouseOptions" :key="warehouse.warehouseId" :label="warehouse.label" :value="warehouse.warehouseId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<AppDialog class="admin-density-dialog" v-model="transferOpen" :title="`转仓 · ${selectedRows.length} 票订单`" fullscreenable confirm-text="确认转仓" :loading="transferWarehouseLoading" :confirm-loading="actionLoading" :confirm-disabled="!transferForm.warehouseId || Boolean(transferWarehouseError)" @confirm="submitTransfer">
|
||||
<LoadErrorState v-if="transferWarehouseError" title="FBA 仓候选加载失败" :message="transferWarehouseError.message" compact @retry="loadTransferWarehouseOptions"><template #details><ApiErrorMeta :details="transferWarehouseError" /></template></LoadErrorState>
|
||||
<section v-else 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="110px" :disabled="actionLoading"><table class="density-table density-table--editable"><tbody>
|
||||
<tr><td><el-form-item label="目的 FBA 仓" required><el-select v-model="transferForm.warehouseId" filterable clearable placeholder="请选择目的 FBA 仓" @update:model-value="selectTransferWarehouse"><el-option v-for="warehouse in transferWarehouseOptions" :key="warehouse.warehouseId" :label="warehouse.label" :value="warehouse.warehouseId" /></el-select></el-form-item></td></tr>
|
||||
<tr><td><el-form-item label="备注"><el-input v-model="transferForm.remark" type="textarea" maxlength="500" placeholder="请输入转仓备注(选填)" /></el-form-item></td></tr>
|
||||
</tbody></table></el-form>
|
||||
<el-alert v-if="!transferWarehouseLoading && !transferWarehouseOptions.length" title="暂无可用 FBA 仓候选" type="info" :closable="false" show-icon />
|
||||
<el-form-item label="备注"><el-input v-model="transferForm.remark" type="textarea" maxlength="500" placeholder="请输入转仓备注(选填)" /></el-form-item>
|
||||
</el-form>
|
||||
</section>
|
||||
</AppDialog>
|
||||
<AppDialog class="admin-density-dialog" v-model="containerConvertOpen" title="按柜生成入库计划预览" width="min(1240px, calc(100vw - 80px))" :confirm-text="containerConfirmText" :loading="containerOptionsLoading || containerPreviewLoading" :confirm-loading="actionLoading" :confirm-disabled="containerConfirmDisabled" @confirm="confirmContainerInboundPlans">
|
||||
<p class="container-convert-hint">先读取服务端全柜预览,确认分组、托数和阻断原因;确认后整柜只提交一次生成命令。</p>
|
||||
<LoadErrorState v-if="containerLoadError" title="待分配柜加载失败" :message="containerLoadError.message" compact @retry="loadContainerOptions()">
|
||||
<template #details><ApiErrorMeta :details="containerLoadError" /></template>
|
||||
</LoadErrorState>
|
||||
<el-form class="density-form" v-else label-position="top">
|
||||
<el-form-item label="选择待分配柜" required>
|
||||
<el-select v-model="selectedContainerBookingId" filterable remote clearable :remote-method="loadContainerOptions" :loading="containerOptionsLoading" :disabled="actionLoading" placeholder="输入柜号搜索并选择" @change="loadContainerPreview">
|
||||
<el-option v-for="container in containerOptions" :key="container.sourceContainerBookingId" :value="container.sourceContainerBookingId" :label="`${container.containerNo || '柜号缺失'} · ${container.totalOrderCount} 票`" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-alert v-if="containerPreviewError" class="container-convert-alert" type="error" :closable="false" show-icon :title="containerPreviewError" />
|
||||
<div v-if="containerPreview" class="container-preview">
|
||||
<div class="container-preview__context">
|
||||
<div class="container-preview__identity text-ellipsis" :title="containerPreview.containerNo || '未录入货柜号'">{{ containerPreview.containerNo || '未录入货柜号' }}</div>
|
||||
<div class="container-preview__warehouse text-ellipsis" :title="[containerPreview.overseasWarehouseCode, containerPreview.overseasWarehouseName].filter(Boolean).join(' / ') || '未关联海外仓'">{{ [containerPreview.overseasWarehouseCode, containerPreview.overseasWarehouseName].filter(Boolean).join(' / ') || '未关联海外仓' }}</div>
|
||||
</div>
|
||||
<div v-if="!containerIsResourceRecovery" class="container-preview__metrics">
|
||||
<div class="container-preview__metric"><span>头程已有托盘码</span><strong>{{ containerPreview.sourcePalletCount }}</strong></div>
|
||||
<div class="container-preview__metric container-preview__metric--primary"><span>海外仓补充托盘码</span><strong>{{ containerPreview.supplementPalletCount }}</strong></div>
|
||||
<div class="container-preview__metric"><span>最终生成托盘</span><strong>{{ containerPreview.pendingPalletCount }}</strong></div>
|
||||
<div class="container-preview__metric"><span>本次处理订单</span><strong>{{ containerPreview.pendingOrderCount }}</strong></div>
|
||||
</div>
|
||||
<div v-else class="container-preview__metrics">
|
||||
<div class="container-preview__metric"><span>已保存计划托盘</span><strong>{{ containerPreview.totalPlannedPalletCount }}</strong></div>
|
||||
<div class="container-preview__metric container-preview__metric--primary"><span>资源确认</span><strong>{{ containerConversionResult?.resourceRecovered ? '已确认' : '待确认' }}</strong></div>
|
||||
</div>
|
||||
<el-alert v-if="containerPreview.blockedOrderCount > 0" class="container-convert-alert" type="error" :closable="false" show-icon title="存在阻断订单,暂不可确认生成;请按下方原因处理后重新预览。" />
|
||||
<el-alert v-else-if="containerPreview.resourceRetry && !containerConversionResult" class="container-convert-alert" type="warning" :closable="false" show-icon title="本柜计划和托盘已保存,资源仍待确认。请重试原操作完成资源确认。" />
|
||||
<el-alert v-else-if="!containerConversionResult" class="container-convert-alert" type="success" :closable="false" show-icon :title="`预览已就绪:头程已有 ${containerPreview.sourcePalletCount} 个托盘码,海外仓补充 ${containerPreview.supplementPalletCount} 个,最终生成 ${containerPreview.pendingPalletCount} 个托盘。`" />
|
||||
<section v-if="containerConversionResult" class="container-conversion-result">
|
||||
<header><strong>{{ containerConversionResult.resourceRecovered ? '资源确认完成' : '生成完成' }}</strong><span v-if="containerConversionResult.resourceRecovered">已确认 {{ containerConversionResult.plans.length }} 个已有计划</span><span v-else>新建 {{ containerConversionResult.newPlanCount }} 个 / 已有 {{ containerConversionResult.existingPlanCount }} 个</span></header>
|
||||
<div class="container-conversion-result__plans">
|
||||
<article v-for="plan in containerConversionResult.plans" :key="plan.planNo" class="container-conversion-result__plan">
|
||||
<div><strong class="text-ellipsis" :title="plan.planNo">{{ plan.planNo }}</strong><el-tag size="small" :type="containerConversionResult.existingPlanNos.has(plan.planNo) ? 'info' : 'success'">{{ containerConversionResult.existingPlanNos.has(plan.planNo) ? '已有计划' : '本次新建' }}</el-tag></div>
|
||||
<p v-if="plan.pallets.length"><span v-for="pallet in plan.pallets" :key="pallet.id" class="container-conversion-result__pallet" :title="pallet.palletNo || '托盘码暂未返回'">{{ pallet.palletNo || '—' }}</span></p>
|
||||
<p v-else class="container-conversion-result__empty">暂无托盘码</p>
|
||||
</article>
|
||||
</div>
|
||||
<AppDialog class="admin-density-dialog" v-model="containerConvertOpen" :title="`按柜生成入库计划预览${containerPreview?.containerNo ? ' · ' + containerPreview.containerNo : ''}`" width="min(1240px, calc(100vw - 80px))" fullscreenable :confirm-text="containerConfirmText" :loading="containerOptionsLoading || containerPreviewLoading" :confirm-loading="actionLoading" :confirm-disabled="containerConfirmDisabled" @confirm="confirmContainerInboundPlans">
|
||||
<div class="density-dialog-sections">
|
||||
<section class="density-dialog-section" data-tone="work" aria-label="选择待分配柜"><h3 class="density-dialog-section__title">选择待分配柜 <InlineHelp title="按柜生成说明">先读取服务端全柜预览,确认分组、托数和阻断原因;确认后整柜只提交一次生成命令。</InlineHelp></h3>
|
||||
<LoadErrorState v-if="containerLoadError" title="待分配柜加载失败" :message="containerLoadError.message" compact @retry="loadContainerOptions()"><template #details><ApiErrorMeta :details="containerLoadError" /></template></LoadErrorState>
|
||||
<el-form v-else class="density-form" label-position="left" label-width="110px" :disabled="actionLoading"><table class="density-table density-table--editable"><tbody><tr><td><el-form-item label="待分配柜" required><el-select v-model="selectedContainerBookingId" filterable remote clearable :remote-method="loadContainerOptions" :loading="containerOptionsLoading" :disabled="actionLoading" placeholder="输入柜号搜索并选择" @change="loadContainerPreview"><el-option v-for="container in containerOptions" :key="container.sourceContainerBookingId" :value="container.sourceContainerBookingId" :label="`${container.containerNo || '柜号缺失'} · ${container.totalOrderCount} 票`" /></el-select></el-form-item></td></tr></tbody></table></el-form>
|
||||
</section>
|
||||
<div class="container-preview__scroll">
|
||||
<section v-for="group in containerPreview.groups" :key="`${group.destinationType}-${group.destinationWarehouseId || group.destinationWarehouseCode || 'default'}`" class="container-preview__group">
|
||||
<header class="container-preview__group-header">
|
||||
<strong class="text-ellipsis" :title="previewGroupTitle(group)">{{ previewGroupTitle(group) }}:{{ containerIsResourceRecovery ? `已有 ${group.plannedPalletCount} 托` : `最终 ${group.pendingPalletCount} 托` }}</strong>
|
||||
<span v-if="containerIsResourceRecovery">已保存计划托数 {{ group.plannedPalletCount }}</span>
|
||||
<span v-else>头程已有 {{ group.sourcePalletCount }} 托 / 海外仓补充 {{ group.supplementPalletCount }} 托 / 本次处理 {{ group.pendingOrderCount }} 票</span>
|
||||
</header>
|
||||
<el-alert v-if="containerPreviewError" type="error" :closable="false" show-icon :title="containerPreviewError" />
|
||||
<div v-if="containerPreview" class="container-preview density-dialog-sections">
|
||||
<section class="density-dialog-section" data-tone="summary" aria-label="全柜预览汇总"><h3 class="density-dialog-section__title">全柜预览</h3>
|
||||
<table class="density-table last-mile-dialog-facts"><tbody>
|
||||
<tr><th scope="row">柜号</th><td><strong class="container-preview__identity" :title="containerPreview.containerNo || '未录入货柜号'">{{ containerPreview.containerNo || '未录入货柜号' }}</strong></td><th scope="row">海外接柜仓</th><td>{{ [containerPreview.overseasWarehouseCode, containerPreview.overseasWarehouseName].filter(Boolean).join(' / ') || '未关联海外仓' }}</td></tr>
|
||||
<template v-if="!containerIsResourceRecovery"><tr><th scope="row">头程已有托盘码</th><td>{{ containerPreview.sourcePalletCount }}</td><th scope="row">海外仓补充托盘码</th><td>{{ containerPreview.supplementPalletCount }}</td></tr><tr><th scope="row">最终生成托盘</th><td>{{ containerPreview.pendingPalletCount }}</td><th scope="row">本次处理订单</th><td>{{ containerPreview.pendingOrderCount }}</td></tr></template>
|
||||
<tr v-else><th scope="row">已保存计划托盘</th><td>{{ containerPreview.totalPlannedPalletCount }}</td><th scope="row">资源确认</th><td>{{ containerConversionResult?.resourceRecovered ? '已确认' : '待确认' }}</td></tr>
|
||||
</tbody></table>
|
||||
</section>
|
||||
<el-alert v-if="containerPreview.blockedOrderCount > 0" type="error" :closable="false" show-icon title="存在阻断订单,暂不可确认生成;请按下方原因处理后重新预览。" />
|
||||
<el-alert v-else-if="containerPreview.resourceRetry && !containerConversionResult" type="warning" :closable="false" show-icon title="本柜计划和托盘已保存,资源仍待确认。请重试原操作完成资源确认。" />
|
||||
<el-alert v-else-if="!containerConversionResult" type="success" :closable="false" show-icon :title="`预览已就绪:头程已有 ${containerPreview.sourcePalletCount} 个托盘码,海外仓补充 ${containerPreview.supplementPalletCount} 个,最终生成 ${containerPreview.pendingPalletCount} 个托盘。`" />
|
||||
<section v-if="containerConversionResult" class="density-dialog-section container-conversion-result" data-tone="summary" aria-label="生成结果">
|
||||
<h3 class="density-dialog-section__title">{{ containerConversionResult.resourceRecovered ? '资源确认完成' : '生成完成' }}</h3>
|
||||
<p class="density-dialog-note"><span v-if="containerConversionResult.resourceRecovered">已确认 {{ containerConversionResult.plans.length }} 个已有计划</span><span v-else>新建 {{ containerConversionResult.newPlanCount }} 个 / 已有 {{ containerConversionResult.existingPlanCount }} 个</span></p>
|
||||
<table class="density-table"><thead><tr><th>计划号</th><th>状态</th><th>托盘码</th></tr></thead><tbody class="container-conversion-result__plans"><tr v-for="plan in containerConversionResult.plans" :key="plan.planNo" class="container-conversion-result__plan"><td>{{ plan.planNo }}</td><td><el-tag size="small" :type="containerConversionResult.existingPlanNos.has(plan.planNo) ? 'info' : 'success'">{{ containerConversionResult.existingPlanNos.has(plan.planNo) ? '已有计划' : '本次新建' }}</el-tag></td><td><template v-if="plan.pallets.length"><span v-for="(pallet, index) in plan.pallets" :key="pallet.id" class="container-conversion-result__pallet">{{ index ? '、' : '' }}{{ pallet.palletNo || '—' }}</span></template><span v-else>暂无托盘码</span></td></tr></tbody></table>
|
||||
</section>
|
||||
<section v-for="group in containerPreview.groups" :key="`${group.destinationType}-${group.destinationWarehouseId || group.destinationWarehouseCode || 'default'}`" class="density-dialog-section" data-tone="source" aria-label="目的地分组明细">
|
||||
<h3 class="density-dialog-section__title">{{ previewGroupTitle(group) }}:{{ containerIsResourceRecovery ? `已有 ${group.plannedPalletCount} 托` : `最终 ${group.pendingPalletCount} 托` }}</h3>
|
||||
<p class="density-dialog-note"><span v-if="containerIsResourceRecovery">已保存计划托数 {{ group.plannedPalletCount }}</span><span v-else>头程已有 {{ group.sourcePalletCount }} 托 / 海外仓补充 {{ group.supplementPalletCount }} 托 / 本次处理 {{ group.pendingOrderCount }} 票</span></p>
|
||||
<el-table :data="group.orders" size="small" border>
|
||||
<el-table-column label="入仓码" min-width="148" show-overflow-tooltip><template #default="{ row }">{{ row.inboundCode || '—' }}</template></el-table-column>
|
||||
<el-table-column prop="businessOrderNo" label="订单号" min-width="150" show-overflow-tooltip />
|
||||
@@ -1265,67 +1240,55 @@ onMounted(() => { void load() })
|
||||
</LoadErrorState>
|
||||
</el-form>
|
||||
</AppDialog>
|
||||
<AppDialog class="admin-density-dialog" v-model="inboundPlanOpen" title="入库计划" width="min(720px, calc(100vw - 28px))" confirm-text="保存并生成入库单" :loading="inboundPlanPreviewLoading" :confirm-loading="actionLoading" :confirm-disabled="inboundPlanSubmitDisabled" @confirm="submitInboundPlan">
|
||||
<AppDialog class="admin-density-dialog" v-model="inboundPlanOpen" :title="`入库计划 · ${actionTarget?.orderNo || '—'}`" width="min(720px, calc(100vw - 28px))" fullscreenable confirm-text="保存并生成入库单" :loading="inboundPlanPreviewLoading" :confirm-loading="actionLoading" :confirm-disabled="inboundPlanSubmitDisabled" @confirm="submitInboundPlan">
|
||||
<el-alert title="保存后生成入库单;清关完成后入库计划不可更改。" type="info" :closable="false" show-icon />
|
||||
<el-alert v-if="inboundPlanPreviewError" class="container-convert-alert" :title="inboundPlanPreviewError" type="error" :closable="false" show-icon />
|
||||
<el-form class="command-form density-form" label-position="top">
|
||||
<div class="command-form__grid"><el-form-item label="预计到仓时间"><el-input :model-value="inboundPlanExpectedArrival" disabled placeholder="当前柜预计到仓时间" /><small>取整柜预计到仓时间,同柜所有入库计划保持一致;已生成计划后锁定。</small><el-button v-if="access.can('platform:firstleg:terminal-drayage:list') && actionTarget?.containerNo" link type="primary" @click="router.push({ name: 'overseas-container-workbench', query: { keyword: actionTarget.containerNo } })">前往货柜维护预计到仓</el-button></el-form-item><el-form-item label="计划入库仓"><el-input :model-value="inboundPlanWarehouseLabel" disabled placeholder="当前柜接柜仓" /><small>接柜仓按柜统一;如需变更,请在柜子工作台执行“变更接柜仓”。</small></el-form-item><el-form-item label="入仓码"><el-input :model-value="inboundPlanForm.inboundCode" disabled placeholder="订单冻结入仓码" /></el-form-item><el-form-item label="托数" required><el-input-number v-model="inboundPlanForm.palletCount" :min="inboundPlanMinimumPalletCount" :max="inboundPlanIsDropship ? inboundPlanMinimumPalletCount : 999" :disabled="inboundPlanIsDropship" controls-position="right" placeholder="请输入托数" /><small v-if="inboundPlanIsDropship">一件代发固定按头程已封托托盘数整体入库,不可增减。</small><small v-else>按柜级预览至少 {{ inboundPlanMinimumPalletCount }} 托,可增加,不能减少。</small></el-form-item><el-form-item label="托盘类型"><el-select v-model="inboundPlanForm.palletType" placeholder="请选择托盘类型"><el-option label="标准托盘" value="STANDARD" /><el-option label="木托盘" value="WOOD" /><el-option label="纸托盘" value="PAPER" /></el-select></el-form-item><el-form-item label="是否分配"><el-switch v-model="inboundPlanForm.assigned" inline-prompt active-text="是" inactive-text="否" /></el-form-item><el-form-item label="允许合托"><el-switch v-model="inboundPlanForm.mergeRequired" inline-prompt active-text="是" inactive-text="否" /><small>仅作为现场可选提示,不强制工人合托。</small></el-form-item><el-form-item label="是否加急"><el-switch v-model="inboundPlanForm.urgent" inline-prompt active-text="是" inactive-text="否" /></el-form-item></div>
|
||||
<el-form-item label="备注"><el-input v-model="inboundPlanForm.remark" type="textarea" :rows="3" maxlength="1000" show-word-limit placeholder="请输入备注(选填)" /></el-form-item>
|
||||
<el-alert v-if="inboundPlanPreviewError" :title="inboundPlanPreviewError" type="error" :closable="false" show-icon />
|
||||
<el-form class="command-form density-form density-dialog-sections" label-position="left" label-width="110px" :disabled="actionLoading">
|
||||
<section class="density-dialog-section" data-tone="source" aria-label="入库来源"><h3 class="density-dialog-section__title">入库来源 <InlineHelp title="入库来源说明">取整柜预计到仓时间,同柜所有入库计划保持一致;已生成计划后锁定。接柜仓按柜统一;如需变更,请在柜子工作台执行“变更接柜仓”。</InlineHelp></h3>
|
||||
<table class="density-table last-mile-dialog-facts"><tbody>
|
||||
<tr><th scope="row">预计到仓时间</th><td>{{ inboundPlanExpectedArrival }} <el-button v-if="access.can('platform:firstleg:terminal-drayage:list') && actionTarget?.containerNo" link type="primary" @click="router.push({ name: 'overseas-container-workbench', query: { keyword: actionTarget.containerNo } })">前往货柜维护预计到仓</el-button></td></tr>
|
||||
<tr><th scope="row">计划入库仓</th><td>{{ inboundPlanWarehouseLabel || '—' }}</td></tr><tr><th scope="row">入仓码</th><td>{{ inboundPlanForm.inboundCode || '—' }}</td></tr>
|
||||
</tbody></table>
|
||||
</section>
|
||||
<section class="density-dialog-section" data-tone="work" aria-label="入库计划登记"><h3 class="density-dialog-section__title">计划登记</h3>
|
||||
<table class="density-table density-table--editable last-mile-command-table"><tbody>
|
||||
<tr><td><el-form-item label="托数" required><span v-if="inboundPlanIsDropship" class="frozen-pallet-count">{{ inboundPlanForm.palletCount }}</span><el-input-number v-else v-model="inboundPlanForm.palletCount" :min="inboundPlanMinimumPalletCount" :max="999" controls-position="right" placeholder="请输入托数" /><span class="density-dialog-note" v-if="inboundPlanIsDropship">一件代发固定按头程已封托托盘数整体入库,不可增减。</span><span class="density-dialog-note" v-else>按柜级预览至少 {{ inboundPlanMinimumPalletCount }} 托,可增加,不能减少。</span></el-form-item></td><td><el-form-item label="托盘类型"><el-select v-model="inboundPlanForm.palletType" placeholder="请选择托盘类型"><el-option label="标准托盘" value="STANDARD" /><el-option label="木托盘" value="WOOD" /><el-option label="纸托盘" value="PAPER" /></el-select></el-form-item></td></tr>
|
||||
<tr><td><el-form-item label="是否分配"><el-switch v-model="inboundPlanForm.assigned" inline-prompt active-text="是" inactive-text="否" /></el-form-item></td><td><el-form-item label="允许合托"><el-switch v-model="inboundPlanForm.mergeRequired" inline-prompt active-text="是" inactive-text="否" /><InlineHelp title="合托提示说明">仅作为现场可选提示,不强制工人合托。</InlineHelp></el-form-item></td></tr>
|
||||
<tr><td colspan="2"><el-form-item label="是否加急"><el-switch v-model="inboundPlanForm.urgent" inline-prompt active-text="是" inactive-text="否" /></el-form-item></td></tr>
|
||||
<tr><td colspan="2"><el-form-item label="备注"><el-input v-model="inboundPlanForm.remark" type="textarea" :rows="3" maxlength="1000" show-word-limit placeholder="请输入备注(选填)" /></el-form-item></td></tr>
|
||||
</tbody></table>
|
||||
</section>
|
||||
</el-form>
|
||||
</AppDialog>
|
||||
<AppDialog class="admin-density-dialog" v-model="holdOpen" :title="holdForm.held ? '暂扣订单' : '解除暂扣'" width="min(620px, calc(100vw - 28px))" confirm-text="保存" :confirm-loading="actionLoading" @confirm="submitHold">
|
||||
<AppDialog class="admin-density-dialog" v-model="holdOpen" :title="`${holdForm.held ? '暂扣订单' : '解除暂扣'} · ${actionTarget?.orderNo || '—'}`" width="min(620px, calc(100vw - 28px))" fullscreenable confirm-text="保存" :confirm-loading="actionLoading" @confirm="submitHold">
|
||||
<el-alert :title="holdForm.held ? '暂扣后不可创建出库计划;解除暂扣后恢复出库计划操作。' : '解除暂扣后恢复该订单的出库计划操作。'" type="warning" :closable="false" show-icon />
|
||||
<el-form class="command-form density-form" label-position="top"><div class="command-form__grid"><el-form-item label="暂扣状态" required><el-radio-group v-model="holdForm.held"><el-radio :value="true">暂扣</el-radio><el-radio :value="false">解除暂扣</el-radio></el-radio-group></el-form-item><el-form-item label="是否计费" required><el-radio-group v-model="holdForm.chargeable"><el-radio :value="true">是</el-radio><el-radio :value="false">否</el-radio></el-radio-group></el-form-item></div><el-form-item label="变更原因" required><el-input v-model="holdForm.reason" type="textarea" :rows="4" maxlength="500" show-word-limit placeholder="请输入暂扣或解除暂扣原因" /></el-form-item></el-form>
|
||||
<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="100px" :disabled="actionLoading"><table class="density-table density-table--editable"><tbody>
|
||||
<tr><td><el-form-item label="暂扣状态" required><el-radio-group v-model="holdForm.held"><el-radio :value="true">暂扣</el-radio><el-radio :value="false">解除暂扣</el-radio></el-radio-group></el-form-item></td></tr>
|
||||
<tr><td><el-form-item label="是否计费" required><el-radio-group v-model="holdForm.chargeable"><el-radio :value="true">是</el-radio><el-radio :value="false">否</el-radio></el-radio-group></el-form-item></td></tr>
|
||||
<tr><td><el-form-item label="变更原因" required><el-input v-model="holdForm.reason" type="textarea" :rows="4" maxlength="500" show-word-limit placeholder="请输入暂扣或解除暂扣原因" /></el-form-item></td></tr>
|
||||
</tbody></table></el-form></section>
|
||||
</AppDialog>
|
||||
<AppDialog class="admin-density-dialog" v-model="manualNodesOpen" title="修改节点" width="min(700px, calc(100vw - 28px))" confirm-text="保存" :loading="manualNodesLoading" :confirm-loading="actionLoading" @confirm="submitManualNodes">
|
||||
<div class="manual-node-order"><span>尾程单号</span><strong>{{ actionTarget?.orderNo || '—' }}</strong></div>
|
||||
<AppDialog class="admin-density-dialog" v-model="manualNodesOpen" :title="`修改节点 · ${actionTarget?.orderNo || '—'}`" width="min(700px, calc(100vw - 28px))" fullscreenable confirm-text="保存" :loading="manualNodesLoading" :confirm-loading="actionLoading" @confirm="submitManualNodes">
|
||||
<el-alert title="保存后该订单只显示人工节点,不再跟随柜级节点;请至少填写一个节点时间。" type="warning" :closable="false" show-icon />
|
||||
<div class="manual-node-table"><div class="manual-node-table__head"><span>节点</span><span>节点时间</span></div><div v-for="node in manualNodeDefinitions" :key="node.code" class="manual-node-table__row"><span>{{ node.label }}</span><el-date-picker v-model="manualNodeValues[node.code]" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="年/月/日 --:--" /></div></div>
|
||||
<section class="density-dialog-section" data-tone="work" aria-label="人工节点登记"><h3 class="density-dialog-section__title">人工节点</h3><el-form class="density-form" :disabled="actionLoading"><table class="density-table density-table--editable manual-nodes-editor"><thead><tr><th>节点</th><th>节点时间</th></tr></thead><tbody><tr v-for="node in manualNodeDefinitions" :key="node.code"><th scope="row">{{ node.label }}</th><td><el-date-picker v-model="manualNodeValues[node.code]" :aria-label="`${node.label}时间`" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="年/月/日 --:--" /></td></tr></tbody></table></el-form></section>
|
||||
</AppDialog>
|
||||
<AppDialog class="admin-density-dialog" v-model="reworkOpen" title="换标退回重做" width="min(620px, calc(100vw - 28px))" confirm-text="确认退回重做" :confirm-loading="reworkSaving" :confirm-disabled="!reworkForm.relabelNo || !reworkForm.remark.trim()" @confirm="submitReworkRelabel">
|
||||
<el-form class="density-form" label-position="top">
|
||||
<el-form-item label="换标单" required><el-select v-model="reworkForm.relabelNo" :disabled="reworkSaving" placeholder="请选择需要退回的换标单"><el-option v-for="application in reworkOptions" :key="application.relabelNo" :label="application.relabelNo" :value="application.relabelNo" /></el-select></el-form-item>
|
||||
<el-form-item label="退回原因" required><el-input v-model="reworkForm.remark" :disabled="reworkSaving" type="textarea" :rows="4" maxlength="1000" show-word-limit placeholder="填写退回原因,仓库将重新执行换标并上传照片" /></el-form-item>
|
||||
</el-form>
|
||||
<AppDialog class="admin-density-dialog" v-model="reworkOpen" :title="`换标退回重做 · ${reworkForm.relabelNo || '—'}`" width="min(620px, calc(100vw - 28px))" fullscreenable confirm-text="确认退回重做" :confirm-loading="reworkSaving" :confirm-disabled="!reworkForm.relabelNo || !reworkForm.remark.trim()" @confirm="submitReworkRelabel">
|
||||
<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="100px" :disabled="reworkSaving"><table class="density-table density-table--editable"><tbody>
|
||||
<tr><td><el-form-item label="换标单" required><el-select v-model="reworkForm.relabelNo" :disabled="reworkSaving" placeholder="请选择需要退回的换标单"><el-option v-for="application in reworkOptions" :key="application.relabelNo" :label="application.relabelNo" :value="application.relabelNo" /></el-select></el-form-item></td></tr>
|
||||
<tr><td><el-form-item label="退回原因" required><el-input v-model="reworkForm.remark" :disabled="reworkSaving" type="textarea" :rows="4" maxlength="1000" show-word-limit placeholder="填写退回原因,仓库将重新执行换标并上传照片" /></el-form-item></td></tr>
|
||||
</tbody></table></el-form></section>
|
||||
</AppDialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.last-mile-dialog-facts, .last-mile-command-table, .manual-nodes-editor { table-layout: fixed; }
|
||||
.last-mile-dialog-facts th { width: var(--admin-label-width); }
|
||||
.last-mile-command-table td:not([colspan]) { width: 50%; }
|
||||
.manual-nodes-editor th { width: 180px; }
|
||||
.relabel-suborder { min-height: 30px; line-height: 30px; white-space: nowrap; }
|
||||
.last-mile-toolbar { min-width: 0; }
|
||||
.row-actions { white-space: nowrap; }
|
||||
.command-form { margin-top: var(--admin-space-5); }
|
||||
.command-form__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--admin-space-5); }
|
||||
.command-form :deep(.el-select), .command-form :deep(.el-date-editor), .command-form :deep(.el-input-number) { width: 100%; }
|
||||
.manual-node-order { display: flex; justify-content: space-between; gap: var(--admin-space-5); margin-bottom: var(--admin-space-5); padding: var(--admin-space-5) var(--admin-space-5); border: 1px solid var(--cc-border); border-radius: var(--admin-radius); background: var(--cc-surface); }
|
||||
.manual-node-table { max-height: 420px; margin-top: var(--admin-space-5); overflow: auto; border: 1px solid var(--cc-border); border-radius: var(--admin-radius); }
|
||||
.manual-node-table__head, .manual-node-table__row { display: grid; grid-template-columns: minmax(0, 42fr) minmax(0, 58fr); align-items: center; gap: var(--admin-space-5); padding: var(--admin-space-4) var(--admin-space-5); }
|
||||
.manual-node-table__head { position: sticky; top: 0; z-index: 1; background: var(--cc-surface); color: var(--cc-muted); font-size: var(--admin-font-body); font-weight: 600; }
|
||||
.manual-node-table__row { border-top: 1px solid var(--cc-border); }
|
||||
.manual-node-table__row :deep(.el-date-editor) { width: 100%; }
|
||||
|
||||
.container-convert-hint { margin: 0 0 var(--admin-space-5); color: var(--cc-muted); line-height: 1.6; }
|
||||
.container-convert-alert { margin-top: var(--admin-space-5); }
|
||||
.container-preview { margin-top: var(--admin-space-5); }
|
||||
.container-preview__context, .container-preview__group-header { display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-5); }
|
||||
.container-preview__identity { color: var(--cc-text); font-size: var(--admin-font-title); font-weight: 700; }
|
||||
.container-preview__warehouse, .container-preview__group-header span { color: var(--cc-muted); font-size: var(--admin-font-body); }
|
||||
.container-preview__metric span { color: var(--cc-muted); font-size: var(--admin-font-body); }
|
||||
.container-preview__metric--primary { border-color: var(--cc-primary); background: #f3f6ff; }
|
||||
.container-preview__metric--primary strong { color: var(--cc-primary); }
|
||||
.container-preview__metric--danger strong { color: var(--el-color-danger); }
|
||||
.container-preview__scroll { max-height: 48vh; margin-top: var(--admin-space-5); padding-right: var(--admin-space-2); overflow-y: auto; }
|
||||
.container-preview__group + .container-preview__group { margin-top: var(--admin-space-5); }
|
||||
.container-preview__group-header { min-height: 42px; padding: 0 var(--admin-space-5); border: 1px solid var(--cc-border); border-bottom: 0; border-radius: 8px 8px 0 0; background: var(--cc-surface); }
|
||||
.container-preview__group-header strong { min-width: 0; color: var(--cc-text); }
|
||||
.container-conversion-result { margin-top: var(--admin-space-5); padding: var(--admin-space-5); border: 1px solid #86efac; border-radius: var(--admin-radius); background: #f0fdf4; }
|
||||
.container-conversion-result > header { display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-5); color: #166534; }
|
||||
.container-conversion-result__plans { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--admin-space-3); margin-top: var(--admin-space-5); }
|
||||
.container-conversion-result__plan { min-width: 0; padding: var(--admin-space-4) var(--admin-space-5); border: 1px solid #bbf7d0; border-radius: var(--admin-radius); background: #fff; }
|
||||
.container-conversion-result__plan > div { display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-4); }
|
||||
.container-conversion-result__plan p { display: flex; flex-wrap: wrap; gap: var(--admin-space-2); margin: var(--admin-space-3) 0 0; }
|
||||
.container-conversion-result__pallet { max-width: 100%; padding: 3px 7px; overflow: hidden; border-radius: var(--admin-radius); background: #dcfce7; color: #166534; font-size: var(--admin-font-caption); text-overflow: ellipsis; white-space: nowrap; }
|
||||
.container-conversion-result__empty { color: var(--cc-muted); font-size: var(--admin-font-caption); }
|
||||
.text-ellipsis { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.container-detail-summary { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--admin-space-3) var(--admin-space-5); margin-bottom: var(--admin-space-5); padding: var(--admin-space-5) var(--admin-space-5); border: 1px solid var(--cc-border); border-radius: var(--admin-radius); background: var(--cc-surface); font-size: var(--admin-font-body); }
|
||||
.container-detail-summary > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
@@ -1356,10 +1319,6 @@ onMounted(() => { void load() })
|
||||
.container-workbench__pagination { display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-5); margin-top: var(--admin-space-5); padding-top: var(--admin-space-5); border-top: 1px solid var(--cc-border); color: var(--cc-muted); font-size: var(--admin-font-body); }
|
||||
@media (max-width: 1380px) { .container-workbench__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||
|
||||
.container-preview__metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; margin: var(--admin-space-5) 0; border: 1px solid var(--cc-border); }
|
||||
.container-preview__metric { display: flex; min-height: var(--admin-row-height); padding: var(--admin-space-5) var(--admin-space-5); border: 0; border-radius: 0; background: var(--admin-surface); flex-direction: row; justify-content: flex-start; align-items: baseline; gap: var(--admin-space-3); border-right: 1px solid var(--cc-border); }
|
||||
.container-preview__metric:last-child { border-right: 0; }
|
||||
.container-preview__metric strong { margin-top: var(--admin-space-1); color: var(--cc-text); font-size: var(--admin-font-body); line-height: 20px; margin: 0; }
|
||||
.package-difference--nonzero { color: var(--el-color-danger); font-weight: 600; }
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user