diff --git a/src/modules/container-loading/pages/ContainersView.vue b/src/modules/container-loading/pages/ContainersView.vue index 6315ddda..d1e35f71 100644 --- a/src/modules/container-loading/pages/ContainersView.vue +++ b/src/modules/container-loading/pages/ContainersView.vue @@ -286,7 +286,8 @@ function optional(value: string | null) { return value?.trim() || null } function statusText(status: ContainerPlanStatus) { return ({ WAIT_LOADING: '待装柜', EDITING: '预装柜中', LOADING: '拣货中', COMPLETED: '装柜完成' })[status] } function documentStatusText(status: ContainerDocumentStatus | null | undefined) { return status ? ({ MISSING: '未上传', UPLOADED: '已上传', COMPLETED: '已完成' })[status] : '—' } function formatTime(value: string | null | undefined) { return value?.replace('T', ' ').slice(0, 16) || '—' } -function defaultArrivalDate(row: ContainerWorkbenchRow) { +function defaultArrivalDate(source: object) { + const row = source as ContainerWorkbenchRow const arrival = arrivalTimes.value[row.id] const projected = projectWarehouseArrival(row.actualFacts?.arrivedDestinationPortAt || row.actualPortArrivalAt || row.estimatedPortArrivalAt, arrival ? { ...arrival, plannedAt: arrival.estimatedWarehouseArrivalAt } : undefined) diff --git a/src/modules/overseas-warehouse/PROTOTYPE_MATRIX.md b/src/modules/overseas-warehouse/PROTOTYPE_MATRIX.md index ddfcb7b2..b5569ede 100644 --- a/src/modules/overseas-warehouse/PROTOTYPE_MATRIX.md +++ b/src/modules/overseas-warehouse/PROTOTYPE_MATRIX.md @@ -1,5 +1,17 @@ # 海外仓原型核对矩阵 +## 2026-10-21 智能排车区域入口替代仓库搜索 + +用户明确删除查询区海外仓选择,仅保留“全部/洛杉矶/纽约”。界面移除顶部仓库框,区域筛选直接提交服务端;候选 warehouseId 不传时跨仓数据库分页,首托确定草稿仓并继续同仓安排,已有排车/预约仍显式指定仓库。空单模式在排车设置中保留起运仓,避免无来源时猜测仓库。正式业务边界维护于《排车列表统一作业改造契约》的区域入口确认段。 + +自动检查:前端7文件134项中旧区域断言调整后,相关3文件57项复验通过,其余77项结果复用;排车样式合同通过,应用类型检查通过。后端3个精确测试类10项通过,覆盖区域HTTP参数、服务校验、Mapper统计/分页范围及OpenAPI;未运行全量回归。未部署、未访问测试环境数据;需Admin与Lastleg同步发布,视觉与真实HTTP待验收。 + +## 2026-10-21 智能排车全屏空间利用 + +依据用户截图 `codex-clipboard-df3ac2ac-0fa1-4e30-98e6-e508074c0d66.png` 与 `codex-clipboard-4833c2b8-d6a3-4e34-bb5c-0099a3fc8fcf.png`:区域按钮并入待排订单标题行,移除中栏订单号 65% 宽度上限;全屏新建选货模式沿弹框可用空间分配三栏高度,待排表格取消原 390px/40vh 上限,分页位于下方,三栏各自按内容滚动。退出全屏、空单与已有排车沿用各自布局。计划时间及备注迁移至列表尚处于用户要求的梳理阶段,本轮未删除其填写区;现有列表“维护车辆信息”已支持三字段,计划发车审核前补齐。 + +自动检查:受影响布局、选货及排车样式合同共 41 项通过,69 项无关样式用例未执行,未跑全量;先前样式合同整文件运行另发现 DispatchesView 的取货车辆弹框缺少密度类,两项失败与本轮改动无关,未扩散修改。差异检查通过;全屏实际高度及滚动待人工截图确认。 + ## 2026-10-21 智能排车查询与三栏统一对齐 用户确认保留蓝白配色,按最初第二张原型恢复区块与控件位置;最新截图 `codex-clipboard-40f3fcdc-8093-4fd1-8b0e-bd1abb147b3e.png` 确认已有排车查询散落、三栏错位,上轮不构成视觉验收通过。 diff --git a/src/modules/overseas-warehouse/api/dispatchVehicleDocuments.ts b/src/modules/overseas-warehouse/api/dispatchVehicleDocuments.ts new file mode 100644 index 00000000..76be4b29 --- /dev/null +++ b/src/modules/overseas-warehouse/api/dispatchVehicleDocuments.ts @@ -0,0 +1,25 @@ +import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' + +export type FuelSlot = 'FUEL_START' | 'FUEL_END' +export interface VehiclePhoto { slot: FuelSlot; fileId: string; fileName: string; contentType: string; savedAt: string | null } +export interface VehicleDocuments { dispatchNo: string; recordVersion: string; editable: boolean; photos: VehiclePhoto[] } +export interface BindVehiclePhoto { recordVersion: string; idempotencyKey: string; fileId: string } +const path = (no: string) => `/platform/v1/last-leg/dispatches/${encodeURIComponent(no)}/vehicle-documents` +export async function getVehicleDocuments(no: string) { + return unwrapApiResponse(await http.get, ApiEnvelope>(path(no))) +} +export async function uploadVehiclePhoto(no: string, slot: FuelSlot, file: File, key: string) { + const form = new FormData(); form.append('file', file); form.append('idempotencyKey', key) + return unwrapApiResponse(await http.post, ApiEnvelope>(`${path(no)}/${slot}/uploads`, form)) +} +export async function recoverVehiclePhoto(no: string, key: string) { + return unwrapApiResponse(await http.get, ApiEnvelope>(`${path(no)}/uploads/${encodeURIComponent(key)}`)) +} +export async function bindVehiclePhoto(no: string, slot: FuelSlot, body: BindVehiclePhoto) { + return unwrapApiResponse(await http.put, ApiEnvelope>(`${path(no)}/${slot}`, body)) +} +export async function readVehiclePhoto(no: string, photo: VehiclePhoto) { + const blob = await http.get(`${path(no)}/${photo.slot}/files/${encodeURIComponent(photo.fileId)}/content`, { responseType: 'blob' }) + if (!(blob instanceof Blob) || !['image/jpeg', 'image/png'].includes(blob.type) || !blob.size) throw new Error('图片读取失败,请重新核对') + return blob +} diff --git a/src/modules/overseas-warehouse/components/DeliveryDocumentsDialog.vue b/src/modules/overseas-warehouse/components/DeliveryDocumentsDialog.vue index 447c17ae..53876535 100644 --- a/src/modules/overseas-warehouse/components/DeliveryDocumentsDialog.vue +++ b/src/modules/overseas-warehouse/components/DeliveryDocumentsDialog.vue @@ -12,7 +12,7 @@ import { useAccessPreviewStore } from '@/core/access/accessPreview' import { overseasWarehousePermissions as permission } from '../permissions' import { deliveryDocumentCategories, getDeliveryDocuments, saveDeliveryDocuments, uploadDeliveryDocument, downloadDeliveryDocument, type DeliveryDocuments, type DeliveryDocumentCategory, type DeliveryDocumentFile } from '../api/deliveryDocuments' -const props = defineProps<{ deliveryNo: string; customerId: string }>() +const props = defineProps<{ deliveryNo: string; customerId: string; embedded?: boolean; categories?: DeliveryDocumentCategory[] }>() const open = defineModel({ default: false }) const emit = defineEmits<{ saved: [] }>() const access = useAccessPreviewStore() @@ -29,6 +29,9 @@ const conflict = ref(false) const canEdit = computed(() => access.can(permission.deliveryDocuments) && Boolean(snapshot.value?.allowedActions.includes('MANAGE_DOCUMENTS'))) const busy = computed(() => saving.value || uploading.value) const uploadedCount = computed(() => draft.value.filter(item => item.files.length > 0).length) +const visibleGroups = computed(() => draft.value.filter(item => !props.categories || props.categories.includes(item.category))) +const dirty = computed(() => Boolean(snapshot.value) && (bolNo.value !== (snapshot.value?.bolNo || '') || draft.value.some(group => JSON.stringify(group.files.map(file => file.fileId)) !== JSON.stringify(snapshot.value?.categories.find(item => item.category === group.category)?.files.map(file => file.fileId) || [])))) +defineExpose({ busy, dirty, canEdit, loading, conflict, save }) let sequence = 0 function isCurrent(current: number, deliveryNo: string, customerId: string) { return current === sequence && open.value && props.deliveryNo === deliveryNo && props.customerId === customerId } let attempt: { body: string; key: string } | null = null @@ -97,23 +100,27 @@ async function save() { saving.value = true; error.value = null const current = sequence; const { deliveryNo, customerId } = props try { - await saveDeliveryDocuments(deliveryNo, customerId, { ...body, idempotencyKey: attempt.key }) + const result = await saveDeliveryDocuments(deliveryNo, customerId, { ...body, idempotencyKey: attempt.key }) if (!isCurrent(current, deliveryNo, customerId)) return const hasReceipt = body.documents.some(item => item.category === 'RECEIPT' && item.fileIds.length > 0) - open.value = false; emit('saved'); ElMessage.success(hasReceipt ? '签收文件已保存,派送单已自动完成' : '派送资料已保存') + if (props.embedded) { + snapshot.value = result; bolNo.value = result.bolNo || ''; attempt = null + draft.value = deliveryDocumentCategories.map(item => ({ ...item, files: (result.categories.find(group => group.category === item.category)?.files || []).map(file => ({ ...file })) })) + } else open.value = false + emit('saved'); ElMessage.success(hasReceipt ? '签收文件已保存,派送单已自动完成' : '派送资料已保存') } catch (cause) { if (isCurrent(current, deliveryNo, customerId)) { error.value = getApiErrorDetails(cause, '派送资料保存失败'); conflict.value = error.value.code === 'A0409' } } finally { if (current === sequence) saving.value = false } } diff --git a/src/modules/overseas-warehouse/components/SmartDispatchCargoBoard.vue b/src/modules/overseas-warehouse/components/SmartDispatchCargoBoard.vue index d4abd85d..42adb377 100644 --- a/src/modules/overseas-warehouse/components/SmartDispatchCargoBoard.vue +++ b/src/modules/overseas-warehouse/components/SmartDispatchCargoBoard.vue @@ -11,6 +11,7 @@ const props = defineProps<{ capacity: number disabled: boolean warehouseSelected: boolean + addVehicleDisabled?: boolean page: number size: number total: number @@ -161,7 +162,7 @@ watch(() => [props.disabled, props.warehouseSelected, props.draft, props.activeC