fix(admin): complete container loading workflows

This commit is contained in:
2026-08-11 11:15:44 +08:00
parent 76f5a1debf
commit 9b994040b8
13 changed files with 456 additions and 135 deletions
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import { Eye, Pencil } from '@lucide/vue'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
@@ -28,6 +29,7 @@ import {
} from '../api/platformLoadingLists'
const props = defineProps<{ mode: LoadingListMode }>()
const route = useRoute()
const access = useAccessPreviewStore()
const rows = ref<LoadingListSummary[]>([])
const loading = ref(false)
@@ -47,6 +49,7 @@ const editSaving = ref(false)
const editError = ref<ApiErrorDetails | null>(null)
const editFormRef = ref<FormInstance>()
const editForm = reactive({ containerNo: '', voyageNo: '', operationReason: '' })
const handledDeepLink = ref('')
const isPreload = computed(() => props.mode === 'preload')
const title = computed(() => isPreload.value ? '预装清单' : '装柜清单')
@@ -107,6 +110,14 @@ function formatNumber(value?: number | null, digits = 3) {
return Number(value).toLocaleString('zh-CN', { maximumFractionDigits: digits })
}
function statusLabel(status?: string | null) {
return status === 'LOADING' ? '装柜中' : status === 'COMPLETED' ? '装柜完成' : status || '未知状态'
}
function queryId(value: unknown) {
return typeof value === 'string' && /^[1-9]\d*$/.test(value) ? value : ''
}
function toQuery(): LoadingListQuery {
const range = Array.isArray(query.createdRange) ? query.createdRange.map(String) : []
return {
@@ -127,6 +138,7 @@ async function load() {
const result = isPreload.value ? await listPreloadPlans(request) : await listConfirmedLoadingLists(request)
rows.value = result.records
total.value = result.total
await openDeepLinkedDetail()
} catch (cause) {
error.value = getApiErrorDetails(cause, `${title.value}加载失败,请稍后重试`)
} finally {
@@ -134,6 +146,30 @@ async function load() {
}
}
async function openDeepLinkedDetail() {
const planId = queryId(route.query.planId)
const warehouseId = queryId(route.query.warehouseId)
if (!planId || !warehouseId || !canView.value) return
const key = `${props.mode}:${planId}:${warehouseId}`
if (handledDeepLink.value === key) return
handledDeepLink.value = key
selectedPlanId.value = planId
selectedWarehouseId.value = warehouseId
detail.value = null
detailError.value = null
detailOpen.value = true
await loadDetail()
const expectedStatus = isPreload.value ? 'LOADING' : 'COMPLETED'
const loadedDetail = detail.value as LoadingDetail | null
if (loadedDetail && loadedDetail.summary.status !== expectedStatus) {
detail.value = null
detailError.value = {
code: 'MODE_STATUS_MISMATCH',
message: `该计划不属于当前${title.value}状态,请从对应清单入口查看。`,
}
}
}
function search() {
if (page.value !== 1) page.value = 1
else void load()
@@ -141,7 +177,15 @@ function search() {
async function loadDetail() {
if (!selectedPlanId.value || !selectedWarehouseId.value || !canView.value) return
if (!canView.value) return
if (!selectedPlanId.value || !selectedWarehouseId.value) {
detail.value = null
detailError.value = {
message: '该清单缺少所属仓库信息,无法安全查询详情,请联系管理员核对数据。',
code: 'MISSING_WAREHOUSE_CONTEXT',
}
return
}
detailLoading.value = true
detailError.value = null
try {
@@ -204,13 +248,23 @@ async function saveTransport() {
ElMessage.success('运输信息已更新')
await load()
} catch (cause) {
editError.value = getApiErrorDetails(cause, '运输信息保存失败,请刷新详情后重试')
const details = getApiErrorDetails(cause, '运输信息保存失败,请刷新详情后重试')
if (details.code === 'A0409' || /版本|并发|已被其他人修改/.test(details.message)) {
await loadDetail()
editError.value = {
...details,
message: '运输信息已被其他人修改,系统已刷新最新版本;当前输入已保留,请核对后重新保存。',
}
} else {
editError.value = details
}
} finally {
editSaving.value = false
}
}
watch([page, pageSize], () => void load())
watch(() => [route.query.planId, route.query.warehouseId], () => void openDeepLinkedDetail())
onMounted(() => {
void load()
})
@@ -256,7 +310,7 @@ onMounted(() => {
</template>
<template #customers="{ row }">{{ (row as PreloadSummary).customers.map((item) => item.customerName || item.accountId).join('、') || '—' }}</template>
<template #vesselVoyage="{ row }">{{ (row as PreloadSummary).vesselName || '—' }} / {{ (row as PreloadSummary).voyageNo || '—' }}</template>
<template #status="{ row }"><StatusBadge :status="row.status" /><span class="status-label">{{ row.status === 'LOADING' ? '装柜中' : row.status === 'COMPLETED' ? '装柜完成' : row.status }}</span></template>
<template #status="{ row }"><StatusBadge :status="statusLabel(row.status)" /></template>
<template #grossWeight="{ row }">{{ formatNumber(row.totalGrossWeightKg) }}</template>
<template #netWeight="{ row }">{{ formatNumber((row as ConfirmedSummary).totalNetWeightKg) }}</template>
<template #volume="{ row }">{{ formatNumber(row.totalVolumeCbm) }}</template>
@@ -301,8 +355,6 @@ onMounted(() => {
<style scoped>
.loading-lists-page { display: grid; min-width: 0; gap: 16px; }
.table-panel { min-width: 0; overflow: hidden; border: 1px solid var(--cc-border); background: #fff; }
.status-label { color: var(--cc-muted); font-size: 12px; }
.status-label { margin-left: 6px; }
.transport-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; margin-top: 16px; }
.transport-form :deep(.el-form-item:last-child) { grid-column: 1 / -1; }
@media (max-width: 640px) { .transport-form { grid-template-columns: 1fr; } .transport-form :deep(.el-form-item:last-child) { grid-column: auto; } }