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
@@ -1,9 +1,10 @@
<script setup lang="ts">
import InlineHelp from '@/shared/components/InlineHelp.vue'
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { useAuthStore } from '@/core/auth/auth'
import { getDispatch, getBatchDispatchCandidates, listDispatchCandidates, createDispatchDraft, createDispatchDraftContainer, listDispatchDrafts, lockDispatchDraftPallet, convertDispatchDraft, releaseDispatchDraft, dispatchVehicleSpecs, DispatchInputError, requireVersion, requireBatchVersions, validateCommandResult, validateCurrentDispatch, isDefiniteDispatchRejection, type BatchDispatchCandidate, type BatchDispatchCandidates, type Candidate, type DispatchDetail, type DispatchDraft, type ConvertDraft, type ConvertedDraft, type DraftSelection, type VersionCommand } from '@/modules/overseas-warehouse'
import { getDispatch, getBatchDispatchCandidates, listDispatchCandidates, createDispatchDraft, createDispatchDraftContainer, listDispatchDrafts, lockDispatchDraftPallet, convertDispatchDraft, releaseDispatchDraft, dispatchVehicleSpecs, DispatchInputError, requireVersion, requireBatchVersions, requireSelectionGroup, expandSelectionGroups, assertSelectionGroupVersions, selectionGroupBlockReason, validateCommandResult, validateCurrentDispatch, isDefiniteDispatchRejection, type BatchDispatchCandidate, type BatchDispatchCandidates, type Candidate, type DispatchDetail, type DispatchDraft, type ConvertDraft, type ConvertedDraft, type DraftSelection, type VersionCommand } from '@/modules/overseas-warehouse'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
@@ -37,7 +38,7 @@ function persist() { ensureOwner(); if (pending.value) sessionStorage.setItem(st
try { const saved = owner && sessionStorage.getItem(storageKey); if (saved) pending.value = JSON.parse(saved) as Preparation }
catch { corrupted.value = true; saveError.value = { message: '原排车请求记录损坏,请保留现场核对,不能另建排车单' } }
function reasons(row: BatchRow) { return row.blockReasons }
function physicalCount(values: BatchRow[]) { return new Set(values.map(row => row.native?.selection.warehousePalletId || row.native?.sourceKey || row.key)).size }
function physicalCount(values: BatchRow[]) { return new Set(values.flatMap(row => row.native?.selectionGroup.pallets.map(pallet => pallet.warehousePalletId || pallet.palletNo || row.key) || [])).size }
function detailPath(no: string) { return `/overseas-warehouse/dispatches/${encodeURIComponent(no)}` }
async function load() {
if (!readable.value || pending.value || corrupted.value) return
@@ -77,7 +78,7 @@ async function load() {
const sources = receiving[index]!.batch.items
const blocks = [...context.blockReasons, ...row.blockReasons]
if (!candidate || candidate.warehouseId !== warehouseId.value || !candidate.sourceVersion || candidate.selection.sourceVersion !== candidate.sourceVersion) blocks.push('原生实物来源、起运仓或版本待核实')
if (candidate) blocks.push(...candidate.blockReasons)
if (candidate) { const groupBlock = selectionGroupBlockReason(candidate); if (groupBlock) blocks.push(groupBlock) }
if (row.packageCount == null || row.packageCount <= 0 || candidate && candidate.availablePackages !== row.packageCount) blocks.push('批次箱数与本托真实可排量不一致,请核对分批或拆托')
if (!row.allocations.length || row.allocations.some(allocation => allocation.planPalletId !== row.planPalletId || allocation.customerId !== row.customerId || allocation.batchKey !== context.batchKey || !Number.isInteger(allocation.packageCount) || allocation.packageCount <= 0 || !sources.some(source => source.customerId === allocation.customerId && source.sourceKey === allocation.sourceKey && source.destinationKey === allocation.destinationKey && source.packageCount != null && source.packageCount >= allocation.packageCount))) blocks.push('候选不属于当前批次、客户和真实来源范围')
if (row.allocations.reduce((sum, allocation) => sum + allocation.packageCount, 0) !== row.packageCount) blocks.push('逐来源批次分配与实物箱数不一致')
@@ -131,7 +132,11 @@ async function executeStep() {
const expected = request.rows.find(row => row.native.selection.planPalletId === selectedSource.planPalletId && row.native.selection.sourceType === selectedSource.sourceType && row.native.sourceVersion === selectedSource.sourceVersion)
const source = expected && draft.sources.find(row => row.sourceKey === expected.native.sourceKey && row.customerId === expected.customerId)
if (!source || !draft.containers.find(row => row.draftContainerId === request.containerId)?.sourceKeys.includes(source.sourceKey)) throw new DispatchInputError('原来源未确认进入本装车组,请核对原请求')
request.lockedKeys.push(source.sourceKey)
const group = requireSelectionGroup(source)
assertSelectionGroupVersions([expected!.native], [source])
const boundKeys = draft.containers.find(row => row.draftContainerId === request.containerId)!.sourceKeys
if (group.sources.some(required => !boundKeys.includes(required.sourceKey))) throw new DispatchInputError('完整选货组尚未原子进入本装车组,请核对原请求')
request.lockedKeys.push(...group.sources.map(required => required.sourceKey))
}
request.draft = draft
}
@@ -163,7 +168,7 @@ async function save() {
else {
const cargo = [...new Set(request.rows.map(row => row.native.sourceKey))].map(key => request.draft!.sources.find(source => source.sourceKey === key))
if (cargo.some(source => !source || source.warehouseId !== request.warehouseId || !source.sourceVersion || source.selection.sourceVersion !== source.sourceVersion || request.rows.some(row => row.native.sourceKey === source.sourceKey && row.customerId !== source.customerId))) throw new DispatchInputError('草稿真实来源尚未完整恢复,请保留原请求核对')
step('CONVERT', { ...common(request.draft), groups: [{ draftContainerId: request.containerId, dispatch: { idempotencyKey: request.dispatchKey, warehouseId: request.warehouseId, vehicle: null, driver: null, plannedDepartureAt: null, estimatedArrivalAt: null, remark: null, cargo: cargo.map(source => source!.selection), batchAllocations: request.rows.flatMap(row => row.allocations) } }] } satisfies ConvertDraft)
step('CONVERT', { ...common(request.draft), groups: [{ draftContainerId: request.containerId, dispatch: { idempotencyKey: request.dispatchKey, warehouseId: request.warehouseId, vehicle: null, driver: null, plannedDepartureAt: null, estimatedArrivalAt: null, remark: null, cargo: expandSelectionGroups(cargo as Candidate[]), batchAllocations: request.rows.flatMap(row => row.allocations) } }] } satisfies ConvertDraft)
}
}
}
@@ -199,7 +204,7 @@ onBeforeUnmount(() => { generation++ })
</script>
<template>
<section class="work-form" aria-label="按预约批次创建整车排车">
<h3>{{ existing.length ? '沿用本批已有排车单' : '按批次选择同仓真实货物' }}</h3><p>同一张排车单可装同仓多个客户与目的仓,客户和预约归属逐来源保留。</p>
<h3>{{ existing.length ? '沿用本批已有排车单' : '按批次选择同仓真实货物' }} <InlineHelp title="排车说明"><p>同一张排车单可装同仓多个客户与目的仓,客户和预约归属逐来源保留。</p></InlineHelp></h3>
<p v-if="!readable">读取批次实物需要预约、尾程订单、入仓单和排车列表权限。</p>
<LoadErrorState v-if="error" title="批次可排实物读取失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
<p v-if="loading">正在核对批次、客户、起运仓与实物来源…</p>