This commit is contained in:
2026-10-20 11:33:39 +08:00
parent d2f2ecf57a
commit 41a498657c
33 changed files with 909 additions and 88 deletions
@@ -5,7 +5,7 @@ import { CheckCircle2, Eye, FileUp, Undo2 } from '@lucide/vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { overseasWarehousePermissions as permission } from '../permissions'
import { changeRelabelOrderStatus, confirmRelabelCompletion, getRelabelReviewTask, returnRelabelForRework } from '../api/relabelOrders'
import { changeRelabelOrderStatus, confirmRelabelCompletion, getRelabelReviewTask, returnRelabelForRework, getRelabelQuantities, type RelabelQuantity } from '../api/relabelOrders'
import { useRoute, useRouter } from 'vue-router'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
@@ -21,6 +21,11 @@ const route = useRoute()
const router = useRouter()
const orderId = computed(() => String(route.params.orderId || ''))
const detail = ref<RelabelDetailDto | null>(null)
const quantity = ref<RelabelQuantity | null>(null)
const currentPallets = ref<string[]>([])
const targetPallets = ref<string[]>([])
const sourceMissing = ref(false)
const quantityMessage = ref('')
const loading = ref(false)
const access = useAccessPreviewStore()
const returning = ref(false)
@@ -65,7 +70,7 @@ const previewTitle = ref('')
const previewOpen = ref(false)
const groups = [
{ title: '单据信息', fields: ['换标单号', '关联订单', '状态', '资金状态', '创建时间', '更新时间'] },
{ title: '商品信息', fields: ['商品SKU', '中文品名', '箱数', '预算总价'] },
{ title: '商品信息', fields: ['商品SKU', '中文品名', '箱数', '贴标件数', 'SKU种数', '当前关联托盘', '本轮目标托盘', '原托盘快照', '预算总价'] },
{ title: '作业要求', fields: ['贴标要求', '上传文件', '备注'] },
{ title: '费用明细', fields: ['换标费总计', '资金异常说明'] },
]
@@ -96,7 +101,12 @@ const fieldValues = computed<Record<string, string | number>>(() => {
更新时间: value?.history.length ? formatOverseasDateTime(value.history[value.history.length - 1]?.createdAt) : '—',
商品SKU: [...new Set(items.flatMap(relabelSkuCodes))].join('、') || '—',
中文品名: items.map((item) => text(item.productNameZh)).join('、') || '—',
箱数: Number(value?.application.totalPackageCount || 0),
箱数: quantity.value?.cartonCount ?? '未提供',
贴标件数: quantity.value?.labelPieceCount ?? '未提供/不适用',
SKU种数: quantity.value?.skuCount ?? '未提供',
当前关联托盘: currentPallets.value.length ? `${currentPallets.value.length} 托:${currentPallets.value.join('、')}${sourceMissing.value ? '(部分来源未提供)' : ''}` : '未提供',
本轮目标托盘: targetPallets.value.length ? `${targetPallets.value.length} 托:${targetPallets.value.join('、')}` : '未提供/不适用',
原托盘快照: '未提供(当前关联托盘不代表换标前快照)',
预算总价: '—',
贴标要求: items.map((item) => text(item.labelRequirement)).join(';') || '—',
上传文件: items.map((item) => text(item.relabelFileName)).filter((item) => item !== '—').join('、') || '—',
@@ -108,7 +118,24 @@ const fieldValues = computed<Record<string, string | number>>(() => {
async function load() {
if (!orderId.value) { loadError.value = { message: '详情缺少换标单号,请从换标单列表重新进入' }; return }
loading.value = true; loadError.value = null
try { detail.value = await getRelabelOrder(orderId.value) }
try {
detail.value = await getRelabelOrder(orderId.value)
quantity.value = null; quantityMessage.value = ''; currentPallets.value = []; targetPallets.value = []; sourceMissing.value = false
try {
const result = await getRelabelQuantities({ page: 1, size: 1, relabelNo: orderId.value })
quantity.value = result.orders[orderId.value] || null
if (quantity.value?.duplicateCarton) quantityMessage.value = '该单存在重复箱唛关联,箱数已按箱唛去重,贴标件数待核查。'
const task = await getRelabelReviewTask(orderId.value)
currentPallets.value = [...new Set((task.items || []).map(item => item.sourcePalletCode).filter((value): value is string => Boolean(value)))]
sourceMissing.value = (task.items || []).some(item => !item.sourcePalletCode)
const history = task.history || []
const lastRework = history.map(item => item.action).lastIndexOf('REWORK_ARCHIVE')
const targets = history.slice(lastRework + 1).filter(item => item.action === 'PALLET_BIND').flatMap(item => {
try { const parsed: { targetPallets?: string[] } = JSON.parse(item.detail || '{}'); return parsed.targetPallets || [] } catch { return [] }
})
targetPallets.value = [...new Set(targets)]
} catch { quantityMessage.value = '部分数量或托盘资料加载失败,未将缺失值显示为0。' }
}
catch (error) { loadError.value = getApiErrorDetails(error, '换标单详情加载失败,请重试') }
finally { loading.value = false }
}
@@ -145,6 +172,7 @@ async function previewCompletionPhoto(fileId: string) {
<section v-if="access.can(permission.relabelOrderStatus) && ['CUSTOMER_CONFIRM_PENDING', 'PROCESSING'].includes(detail?.application.status || '') && detail?.completionEvidence?.photoFileIds?.length" class="relabel-document-panel relabel-review-actions"><el-button v-if="detail?.application.status === 'CUSTOMER_CONFIRM_PENDING'" type="primary" :loading="confirming" @click="confirmCompletion"><CheckCircle2 :size="15" />确认换标完成</el-button><el-button type="warning" :loading="returning" @click="returnForRework"><Undo2 :size="15" />退回重新贴标</el-button></section>
<RelabelDocumentsPanel v-if="detail" :relabel-no="detail.application.relabelNo" :detail="detail" /><section class="relabel-document-panel" aria-label="PDA换标完工证据"><div class="relabel-mark-list__head"><h2>PDA 换标完工证据</h2><span>{{ detail?.completionEvidence?.reviewerName ? `复核人:${detail.completionEvidence.reviewerName}` : '等待仓库作业' }}</span></div><div v-if="detail?.completionEvidence?.photoFileIds?.length" class="document-row"><strong>共 {{ detail.completionEvidence.photoFileIds.length }} 张完工照片</strong><span class="document-actions density-actions"><el-button class="density-action" data-tone="neutral" v-for="(fileId, index) in detail.completionEvidence.photoFileIds" :key="fileId" size="small" @click="previewCompletionPhoto(fileId)"><Eye :size="14" />照片 {{ index + 1 }}</el-button></span></div><div v-else class="attachment-empty">暂未上传完工照片</div></section>
<section class="relabel-document-panel" aria-label="总换标文件"><div class="relabel-mark-list__head"><h2>总换标 PDF</h2><label class="file-picker"><FileUp :size="15" />{{ uploading ? '上传中' : (detail?.application.totalFileName ? '更换PDF' : '上传PDF') }}<input type="file" accept="application/pdf,.pdf" :disabled="uploading" @change="uploadPdf($event, 'TOTAL')" /></label></div><div v-if="detail?.application.totalFileId" class="document-row"><strong>{{ detail.application.totalFileName }}</strong><el-button class="density-action" data-tone="neutral" size="small" @click="previewPdf(detail.application.totalFileId, detail.application.totalFileName || '总换标PDF', 'TOTAL')"><Eye :size="14" />直接预览</el-button></div><div v-else class="attachment-empty">暂未上传总换标PDF</div></section>
<p v-if="quantityMessage" role="status">{{ quantityMessage }}</p>
<section class="relabel-mark-list" aria-label="箱唛换标信息">
<div class="relabel-mark-list__head"><h2>箱唛换标信息</h2><span>共 {{ detail?.items.length || 0 }} 条</span></div>
<div class="relabel-mark-list__scroll">