79 lines
6.8 KiB
Vue
79 lines
6.8 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted, ref } from 'vue'
|
|
import { FileText, RefreshCcw } from '@lucide/vue'
|
|
import { ElMessage } from 'element-plus'
|
|
import { useFirstLegOrdersStore } from '../public'
|
|
import { getMerchantCustomsForOrder, type MerchantCustomsContainer } from '../api/customs'
|
|
import CustomsDocumentsDialog from '../components/CustomsDocumentsDialog.vue'
|
|
import type { FirstLegOrder } from '../types/firstLegOrder'
|
|
|
|
const store = useFirstLegOrdersStore()
|
|
const loading = ref(false)
|
|
const errorMessage = ref('')
|
|
const keyword = ref('')
|
|
const records = ref<Array<{ order: FirstLegOrder; detail: MerchantCustomsContainer }>>([])
|
|
const dialogOpen = ref(false)
|
|
const selectedOrderId = ref('')
|
|
const selectedDetail = ref<MerchantCustomsContainer | null>(null)
|
|
|
|
const filteredRecords = computed(() => {
|
|
const query = keyword.value.trim().toLowerCase()
|
|
if (!query) return records.value
|
|
return records.value.filter(({ order, detail }) => [order.orderNo, order.businessSummary.inboundCode, detail.containerNo, detail.cabinetNo].some((value) => String(value ?? '').toLowerCase().includes(query)))
|
|
})
|
|
|
|
function statusLabel(detail: MerchantCustomsContainer) {
|
|
return ({ WAITING_CUSTOMER_DOCUMENTS: '待上传资料', READY_TO_START: '待报关', DECLARING: '报关中', COMPLETED: '报关完成' } as Record<string, string>)[detail.status] ?? detail.status
|
|
}
|
|
function statusType(detail: MerchantCustomsContainer) {
|
|
return detail.status === 'COMPLETED' ? 'success' : detail.status === 'DECLARING' ? 'primary' : 'warning'
|
|
}
|
|
async function load() {
|
|
loading.value = true; errorMessage.value = ''
|
|
try {
|
|
await store.load()
|
|
const candidates = store.orders.filter((order) => order.status === 'LOADED' && order.taxMode === 'TAX_REFUND')
|
|
const results = await Promise.allSettled(candidates.map(async (order) => ({ order, detail: await getMerchantCustomsForOrder(order.id) })))
|
|
records.value = results.flatMap((result) => result.status === 'fulfilled' && result.value.detail.inbounds.some((item) => item.orderId === result.value.order.id && item.taxMode === 'TAX_REFUND') ? [result.value] : [])
|
|
} catch (error) {
|
|
records.value = []
|
|
errorMessage.value = error instanceof Error ? error.message : '报关资料加载失败,请稍后重试'
|
|
} finally { loading.value = false }
|
|
}
|
|
function openDetail(item: unknown) {
|
|
const value = item as { order: FirstLegOrder; detail: MerchantCustomsContainer }
|
|
selectedOrderId.value = value.order.id; selectedDetail.value = value.detail; dialogOpen.value = true
|
|
}
|
|
function updateDetail(value: MerchantCustomsContainer) {
|
|
const found = records.value.find((item) => item.order.id === selectedOrderId.value)
|
|
if (found) found.detail = value
|
|
}
|
|
onMounted(() => { void load() })
|
|
</script>
|
|
|
|
<template>
|
|
<div class="customs-page">
|
|
<section class="customs-hero"><div><span class="eyebrow">CUSTOMS DOCUMENTS</span><h1>报关资料</h1><p>查看装柜后退税订单的资料清单、文件版本与报关行回执。</p></div><el-button :loading="loading" @click="load"><RefreshCcw :size="15" />刷新</el-button></section>
|
|
<section class="customs-toolbar"><el-input v-model="keyword" clearable placeholder="搜索订单号、入仓码或柜号" /><span>共 {{ filteredRecords.length }} 个资料包</span></section>
|
|
<section class="customs-card">
|
|
<el-skeleton v-if="loading" :rows="5" animated />
|
|
<div v-else-if="errorMessage" class="state-error" role="alert"><p>{{ errorMessage }}</p><el-button @click="load">重试</el-button></div>
|
|
<el-empty v-else-if="!filteredRecords.length" description="暂无待处理的报关资料" />
|
|
<el-table v-else :data="filteredRecords" row-key="order.id" stripe>
|
|
<el-table-column label="操作" width="120" fixed="left"><template #default="{ row }"><el-button link type="primary" @click="openDetail(row)"><FileText :size="15" />查看资料</el-button></template></el-table-column>
|
|
<el-table-column label="订单号" min-width="160"><template #default="{ row }">{{ row.order.orderNo || '—' }}</template></el-table-column>
|
|
<el-table-column label="入仓码" min-width="150"><template #default="{ row }">{{ row.order.businessSummary.inboundCode || '—' }}</template></el-table-column>
|
|
<el-table-column label="柜号" min-width="140"><template #default="{ row }">{{ row.detail.containerNo || row.detail.cabinetNo || '—' }}</template></el-table-column>
|
|
<el-table-column label="实际箱数" width="100" prop="detail.actualBoxCount" />
|
|
<el-table-column label="资料覆盖" width="110"><template #default="{ row }">{{ row.detail.coveredTaxRefundCount }} / {{ row.detail.taxRefundCount }}</template></el-table-column>
|
|
<el-table-column label="状态" width="120"><template #default="{ row }"><el-tag :type="statusType(row.detail)">{{ statusLabel(row.detail) }}</el-tag></template></el-table-column>
|
|
</el-table>
|
|
</section>
|
|
<CustomsDocumentsDialog v-model="dialogOpen" :order-id="selectedOrderId" :can-edit="selectedDetail?.status !== 'COMPLETED'" :initial-detail="selectedDetail" @updated="updateDetail" />
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.customs-page { display:flex; flex-direction:column; gap:16px; }.customs-hero { display:flex; justify-content:space-between; align-items:flex-start; padding:0 2px 4px; }.eyebrow { color:#5b82c9; font-size:11px; font-weight:750; letter-spacing:.14em; }.customs-hero h1 { margin:6px 0 5px; color:#1f2f46; font-size:26px; line-height:1.25; letter-spacing:-.025em; }.customs-hero p { margin:0; color:#7d8da2; font-size:13px; }.customs-hero .el-button { margin-top:3px; border-color:#d9e3ef; border-radius:9px; background:#fff; color:#61738b; box-shadow:0 3px 8px rgba(26,55,91,.04); }.customs-toolbar { display:flex; min-height:82px; align-items:center; gap:16px; padding:16px 18px; border:1px solid #dce4ef; border-radius:16px; background:#fff; box-shadow:0 5px 16px rgba(31,56,88,.045); }.customs-toolbar .el-input { width:min(520px, 100%); }.customs-toolbar :deep(.el-input__wrapper) { min-height:42px; border-radius:10px; box-shadow:0 0 0 1px #d9e3ef inset; }.customs-toolbar span { color:#7d8da2; font-size:12px; }.customs-card { min-height:320px; padding:4px; overflow:hidden; border:1px solid #dce4ef; border-radius:16px; background:#fff; box-shadow:0 5px 16px rgba(31,56,88,.045); }.customs-card :deep(.el-table) { --el-table-header-bg-color:#f4f7fb; --el-table-row-hover-bg-color:#eaf2ff; --el-table-border-color:#edf1f6; color:#53647a; }.customs-card :deep(.el-table th.el-table__cell) { padding:13px 16px; color:#607089; font-size:12px; font-weight:750; }.customs-card :deep(.el-table td.el-table__cell) { height:58px; padding:13px 16px; font-size:13px; }.customs-card :deep(.el-table__row:nth-child(even)) { background:#f8fbff; }.state-error { display:flex; min-height:260px; flex-direction:column; align-items:center; justify-content:center; gap:10px; color:#b42318; }.state-error p { margin:0; }
|
|
</style>
|