Files
admin-web/src/modules/overseas-warehouse/pages/RelabelOrderDetailView.vue
T

96 lines
6.6 KiB
Vue
Raw Normal View History

2026-08-19 16:45:51 +08:00
<script setup lang="ts">
2026-08-20 09:10:36 +08:00
import { computed, onMounted, ref } from 'vue'
2026-08-19 16:45:51 +08:00
import { useRoute, useRouter } from 'vue-router'
2026-08-20 09:10:36 +08:00
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import { getRelabelOrder, type RelabelDetailDto } from '../api/relabelOrders'
import { formatOverseasDateTime } from '../lastMilePresentation'
2026-08-19 16:45:51 +08:00
import OverseasReadOnlyDetail from '../components/OverseasReadOnlyDetail.vue'
const route = useRoute()
const router = useRouter()
2026-08-20 09:10:36 +08:00
const orderId = computed(() => String(route.params.orderId || ''))
const detail = ref<RelabelDetailDto | null>(null)
const loading = ref(false)
const loadError = ref<ApiErrorDetails | null>(null)
2026-08-19 16:45:51 +08:00
const groups = [
{ title: '单据信息', fields: ['换标单号', '关联订单', '状态', '资金状态', '创建时间', '更新时间'] },
2026-08-19 16:45:51 +08:00
{ title: '商品信息', fields: ['商品SKU', '中文品名', '箱数', '预算总价'] },
{ title: '作业要求', fields: ['贴标要求', '上传文件', '备注'] },
{ title: '费用明细', fields: ['换标费总计', '资金异常说明'] },
2026-08-19 16:45:51 +08:00
]
2026-08-20 09:10:36 +08:00
function text(value: unknown) { return String(value ?? '').trim() || '—' }
function status(value: unknown) {
const labels: Record<string, string> = { PENDING_PROCESSING: '待处理', PROCESSING: '处理中', COMPLETED: '已完成', REJECTED: '已驳回', CANCELLED: '已取消' }
return labels[String(value || '')] || text(value)
}
function walletStatus(value: unknown) { return ({ FREEZE_PENDING: '冻结中', FROZEN: '已冻结', SETTLE_PENDING: '结算中', SETTLED: '已结算', RELEASE_PENDING: '释放中', RELEASED: '已释放', FREEZE_FAILED: '冻结失败', SETTLE_FAILED: '结算失败', RELEASE_FAILED: '释放失败' } as Record<string,string>)[String(value || '')] || text(value) }
2026-08-30 17:43:03 +08:00
const referenceValue = computed(() => text(detail.value?.application.relabelNo || '换标单号待补充'))
2026-08-20 09:10:36 +08:00
const fieldValues = computed<Record<string, string | number>>(() => {
const value = detail.value
const items = value?.items || []
return {
换标单号: text(value?.application.relabelNo),
关联订单: text(value?.application.lastlegOrderNo || value?.application.customerOrderNo),
状态: status(value?.application.status),
资金状态: walletStatus(value?.application.walletStatus),
2026-08-20 09:10:36 +08:00
创建时间: formatOverseasDateTime(value?.application.createdAt),
更新时间: value?.history.length ? formatOverseasDateTime(value.history[value.history.length - 1]?.createdAt) : '—',
商品SKU: items.map((item) => text(item.skuCode)).join('、') || '—',
中文品名: items.map((item) => text(item.productNameZh)).join('、') || '—',
箱数: Number(value?.application.totalPackageCount || 0),
预算总价: '—',
贴标要求: items.map((item) => text(item.labelRequirement)).join(';') || '—',
上传文件: items.map((item) => text(item.relabelFileName)).filter((item) => item !== '—').join('、') || '—',
备注: text(value?.remark),
换标费总计: value?.application.chargeAmount == null ? '—' : `${Number(value.application.chargeAmount).toFixed(2)} ${value.application.chargeCurrency || 'USD'}`,
资金异常说明: text(value?.application.walletErrorMessage),
2026-08-20 09:10:36 +08:00
}
})
async function load() {
2026-08-21 11:20:09 +08:00
if (!orderId.value) { loadError.value = { message: '详情缺少换标单号,请从换标单列表重新进入' }; return }
2026-08-20 09:10:36 +08:00
loading.value = true; loadError.value = null
2026-08-21 11:20:09 +08:00
try { detail.value = await getRelabelOrder(orderId.value) }
2026-08-20 09:10:36 +08:00
catch (error) { loadError.value = getApiErrorDetails(error, '换标单详情加载失败,请重试') }
finally { loading.value = false }
}
2026-08-19 16:45:51 +08:00
function back() { void router.push({ name: 'overseas-relabel-orders' }) }
2026-08-20 09:10:36 +08:00
onMounted(load)
2026-08-19 16:45:51 +08:00
</script>
2026-09-06 18:11:35 +08:00
<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">
<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-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>
<thead><tr><th>我方箱唛</th><th>FBA 箱唛</th><th>商品 SKU</th><th>中文品名</th><th>FBA 货件编号</th><th>换标新 SKU</th><th>目的仓</th><th>备注</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>{{ text(item.remark) }}</td></tr>
<tr v-if="!detail?.items.length"><td class="empty-cell" colspan="8">暂无箱唛换标信息</td></tr>
</tbody>
</table>
</div>
</section>
</template>
</OverseasReadOnlyDetail>
</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; }
.mark-code { color: #245b9e; font-weight: 600; }
.empty-cell { height: 96px; color: var(--cc-muted); text-align: center; }
</style>