This commit is contained in:
2026-09-14 20:03:27 +08:00
parent 66f49b97e5
commit 349e8f60df
44 changed files with 658 additions and 459 deletions
@@ -1,6 +1,7 @@
<script setup lang="ts">
import '@/styles/adminDensity.css'
import { computed, onMounted, ref, onBeforeUnmount } from 'vue'
import { Eye, FileUp } from '@lucide/vue'
import { FileUp } from '@lucide/vue'
import { ElMessage } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
@@ -87,18 +88,18 @@ async function previewCompletionPhoto(fileId: string) {
</script>
<template>
<OverseasReadOnlyDetail title="换标单详情" reference-label="换标单号" :reference-value="referenceValue" back-label="返回换标单" domain="换标单详情" :groups="groups" :field-values="fieldValues" :pending="false" :status-text="detail ? status(detail.application.status) : '加载中'" :loading="loading" :show-content="!loadError" @back="back">
<OverseasReadOnlyDetail class="admin-density" dense title="换标单详情" reference-label="换标单号" :reference-value="referenceValue" back-label="返回换标单" domain="换标单详情" :groups="groups" :field-values="fieldValues" :pending="false" :status-text="detail ? status(detail.application.status) : '加载中'" :loading="loading" :show-content="!loadError" @back="back">
<template #state><LoadErrorState v-if="loadError" title="换标单详情加载失败" :message="loadError.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState></template>
<template #content-after>
<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"><el-button v-for="(fileId, index) in detail.completionEvidence.photoFileIds" :key="fileId" size="small" plain @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 size="small" plain @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>
<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)">照片 {{ 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')">直接预览</el-button></div><div v-else class="attachment-empty">暂未上传总换标PDF</div></section>
<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">
<table>
<table class="density-table">
<thead><tr><th>我方箱唛</th><th>FBA 箱唛</th><th>商品 SKU</th><th>中文品名</th><th>FBA 货件编号</th><th>换标新 SKU</th><th>目的仓</th><th>明细PDF</th></tr></thead>
<tbody>
<tr v-for="item in detail?.items || []" :key="item.id"><td class="mark-code">{{ text(item.cartonMark) }}</td><td class="mark-code">{{ text(item.fbaCartonMark) }}</td><td>{{ text(item.skuCode) }}</td><td>{{ text(item.productNameZh) }}</td><td>{{ text(item.shipmentNo) }}</td><td>{{ text(item.newSkuCode) }}</td><td>{{ text(item.destinationWarehouseId) }}</td><td><span v-if="item.relabelFileName" class="document-actions"><strong>{{ item.relabelFileName }}</strong><el-button size="small" link type="primary" @click="previewPdf(item.relabelFileId, item.relabelFileName, 'ITEM', item.id)"><Eye :size="14" />预览</el-button></span><label v-else class="inline-file-picker"><FileUp :size="14" />上传<input type="file" accept="application/pdf,.pdf" :disabled="uploading" @change="uploadPdf($event, 'ITEM', item.id)" /></label></td></tr>
<tr v-for="item in detail?.items || []" :key="item.id"><td class="mark-code">{{ text(item.cartonMark) }}</td><td class="mark-code">{{ text(item.fbaCartonMark) }}</td><td>{{ text(item.skuCode) }}</td><td>{{ text(item.productNameZh) }}</td><td>{{ text(item.shipmentNo) }}</td><td>{{ text(item.newSkuCode) }}</td><td>{{ text(item.destinationWarehouseId) }}</td><td><span v-if="item.relabelFileName" class="document-actions density-actions"><strong>{{ item.relabelFileName }}</strong><el-button class="density-action" data-tone="neutral" size="small" @click="previewPdf(item.relabelFileId, item.relabelFileName, 'ITEM', item.id)">预览</el-button></span><label v-else class="inline-file-picker"><FileUp :size="14" />上传<input type="file" accept="application/pdf,.pdf" :disabled="uploading" @change="uploadPdf($event, 'ITEM', item.id)" /></label></td></tr>
<tr v-if="!detail?.items.length"><td class="empty-cell" colspan="8">暂无箱唛换标信息</td></tr>
</tbody>
</table>
@@ -106,27 +107,25 @@ async function previewCompletionPhoto(fileId: string) {
</section>
</template>
</OverseasReadOnlyDetail>
<AppDialog v-if="previewUrl" v-model="previewOpen" :title="previewTitle" width="min(900px, calc(100vw - 28px))" :show-footer="false"><div class="pdf-preview"><iframe :src="previewUrl" :title="`${previewTitle}预览`" /></div></AppDialog>
<AppDialog class="admin-density-dialog" v-if="previewUrl" v-model="previewOpen" :title="previewTitle" width="min(900px, calc(100vw - 28px))" :show-footer="false"><div class="pdf-preview"><iframe :src="previewUrl" :title="`${previewTitle}预览`" /></div></AppDialog>
</template>
<style scoped>
.relabel-mark-list { min-width: 0; grid-column: 1 / -1; padding: 18px; }
.relabel-mark-list__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.relabel-mark-list__head h2 { margin: 0; color: var(--cc-text); font-size: 15px; }
.relabel-mark-list__head span { color: var(--cc-muted); font-size: 12px; }
.relabel-mark-list__scroll { overflow-x: auto; border: 1px solid var(--cc-border); border-radius: 4px; }
table { width: 100%; min-width: 1120px; border-collapse: collapse; font-size: 13px; }
th { height: 42px; padding: 0 12px; border-bottom: 1px solid var(--cc-border); background: #f8fafc; color: var(--cc-muted); font-size: 12px; font-weight: 600; text-align: left; white-space: nowrap; }
td { min-height: 46px; padding: 12px; border-bottom: 1px solid var(--cc-border); color: var(--cc-text); overflow-wrap: anywhere; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover td { background: #fafcff; }
.relabel-mark-list { min-width: 0; grid-column: 1 / -1; padding: var(--admin-space-5); }
.relabel-mark-list__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--admin-space-5); }
.relabel-mark-list__head h2 { margin: 0; color: var(--cc-text); font-size: var(--admin-font-section); }
.relabel-mark-list__head span { color: var(--cc-muted); font-size: var(--admin-font-caption); }
.relabel-mark-list__scroll { overflow-x: auto; border: 1px solid var(--cc-border); border-radius: var(--admin-radius); }
table { min-width: 1120px; }
.mark-code { color: #245b9e; font-weight: 600; }
.relabel-document-panel { grid-column: 1 / -1; padding: 18px; border-bottom: 1px solid var(--cc-border); }
.document-row { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:12px; border:1px solid var(--cc-border); border-radius:6px; }
.file-picker, .inline-file-picker { display:inline-flex; align-items:center; gap:5px; color:var(--el-color-primary); cursor:pointer; }
.relabel-document-panel { grid-column: 1 / -1; padding: var(--admin-space-5); border-bottom: 1px solid var(--cc-border); }
.document-row { display:flex; align-items:center; justify-content:space-between; gap:var(--admin-space-5); padding:var(--admin-space-5); border:1px solid var(--cc-border); border-radius:var(--admin-radius); }
.file-picker, .inline-file-picker { display:inline-flex; align-items:center; gap:var(--admin-space-2); color:var(--el-color-primary); cursor:pointer; }
.file-picker input, .inline-file-picker input { display:none; }
.document-actions { display:inline-flex; align-items:center; gap:8px; }
.pdf-preview { overflow:hidden; border:1px solid var(--cc-border); border-radius:6px; background:#f5f7fa; }
.pdf-preview { overflow:hidden; border:1px solid var(--cc-border); border-radius:var(--admin-radius); background:#f5f7fa; }
.pdf-preview iframe { display:block; width:100%; height:min(72vh,760px); border:0; background:#fff; }
.empty-cell { height: 96px; color: var(--cc-muted); text-align: center; }
.empty-cell { height: var(--admin-empty-height); color: var(--cc-muted); text-align: center; }
.document-row strong { min-width: 0; overflow-wrap: anywhere; }
</style>