fix(admin): expose container customs downloads
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { FileUp } from '@lucide/vue'
|
||||
import { Download, FileUp } from '@lucide/vue'
|
||||
import { ElMessage, ElMessageBox, type UploadFile } from 'element-plus'
|
||||
import { createPlatformExportTask } from '@/modules/downloads'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
@@ -9,6 +9,8 @@ import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
|
||||
import {
|
||||
completeAllBuyContainerCustoms,
|
||||
downloadContainerCustomsCompletion,
|
||||
downloadContainerCustomsDocument,
|
||||
getContainerCustoms,
|
||||
startContainerCustoms,
|
||||
uploadContainerCustomsCompletion,
|
||||
@@ -30,6 +32,7 @@ const loading = ref(false)
|
||||
const actionLoading = ref(false)
|
||||
const loadError = ref<ApiErrorDetails | null>(null)
|
||||
const actionError = ref<ApiErrorDetails | null>(null)
|
||||
const downloadingKey = ref('')
|
||||
const completionFiles = reactive<Record<string, File | null>>({})
|
||||
|
||||
const missingInbounds = computed(() => customs.value?.inbounds.filter((item) => item.taxMode === 'TAX_REFUND' && !item.covered) || [])
|
||||
@@ -44,6 +47,7 @@ function documentCustomers(document: ContainerCustomsDocument) {
|
||||
return [...new Set((customs.value?.inbounds || []).filter((item) => document.inboundIds.includes(item.inboundId)).map((item) => item.customerName))].join('、') || '—'
|
||||
}
|
||||
function selectCompletion(documentId: string, upload: UploadFile) {
|
||||
if (!props.canManage || customs.value?.status !== 'DECLARING') return
|
||||
completionFiles[documentId] = upload.raw || null
|
||||
actionError.value = null
|
||||
}
|
||||
@@ -69,6 +73,7 @@ async function start() {
|
||||
}
|
||||
|
||||
async function uploadCompletion(document: ContainerCustomsDocument) {
|
||||
if (!props.canManage || customs.value?.status !== 'DECLARING') return
|
||||
const selected = completionFiles[document.documentId]
|
||||
if (!selected) { ElMessage.warning('请选择对应完成文件'); return }
|
||||
const finalConfirmation = !document.completion && missingCompletionCount.value === 1
|
||||
@@ -88,6 +93,34 @@ async function uploadCompletion(document: ContainerCustomsDocument) {
|
||||
finally { actionLoading.value = false }
|
||||
}
|
||||
|
||||
function saveBlob(blob: Blob, fileName: string) {
|
||||
const url = URL.createObjectURL(blob)
|
||||
const anchor = document.createElement('a')
|
||||
anchor.href = url
|
||||
anchor.download = fileName
|
||||
anchor.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
async function downloadFile(document: ContainerCustomsDocument, kind: 'source' | 'completion') {
|
||||
const target = kind === 'source' ? document.source : document.completion
|
||||
if (!target) return
|
||||
const key = `${kind}:${document.documentId}`
|
||||
downloadingKey.value = key
|
||||
actionError.value = null
|
||||
try {
|
||||
const blob = kind === 'source'
|
||||
? await downloadContainerCustomsDocument(document.documentId)
|
||||
: await downloadContainerCustomsCompletion(document.documentId)
|
||||
saveBlob(blob, target.fileName)
|
||||
ElMessage.success(`${kind === 'source' ? '源资料' : '完成文件'}下载已开始`)
|
||||
} catch (error) {
|
||||
actionError.value = getApiErrorDetails(error, `${kind === 'source' ? '源资料' : '完成文件'}下载失败`)
|
||||
} finally {
|
||||
downloadingKey.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
async function completeAllBuy() {
|
||||
if (!customs.value || customs.value.status !== 'DECLARING' || !allBuy.value) return
|
||||
try { await ElMessageBox.confirm('确认全买单柜报关已经完成?报关完成不等同于国内离仓。', '完成全买单柜报关', { type: 'warning', confirmButtonText: '确认完成' }) }
|
||||
@@ -129,6 +162,7 @@ onMounted(load)
|
||||
<el-button v-if="canManage && allBuy && customs.status === 'DECLARING'" type="primary" :loading="actionLoading" @click="completeAllBuy">确认报关完成</el-button>
|
||||
</div>
|
||||
</header>
|
||||
<div v-if="actionError" class="action-error" role="alert"><span>{{ actionError.message }}</span><ApiErrorMeta :details="actionError" /></div>
|
||||
<LoadErrorState v-if="loadError" compact title="整柜报关加载失败" :message="loadError.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState>
|
||||
<div v-else-if="loading"><el-skeleton :rows="3" animated /></div>
|
||||
<template v-else-if="customs">
|
||||
@@ -139,9 +173,11 @@ onMounted(load)
|
||||
</div>
|
||||
<div v-if="customs.documents.length" class="compact-table-wrap">
|
||||
<h3>冻结资料与完成文件</h3>
|
||||
<table class="compact-table document-table"><thead><tr><th>操作</th><th>关联入仓码</th><th>版本</th><th>客户</th><th>完成情况</th></tr></thead><tbody>
|
||||
<table class="compact-table document-table"><thead><tr><th>操作</th><th>源资料</th><th>完成文件</th><th>关联入仓码</th><th>版本</th><th>客户</th><th>完成情况</th></tr></thead><tbody>
|
||||
<tr v-for="document in customs.documents" :key="document.documentId">
|
||||
<td><el-upload :auto-upload="false" :limit="1" :show-file-list="false" :on-change="(upload: UploadFile) => selectCompletion(document.documentId, upload)"><el-button size="small"><FileUp :size="14" />{{ document.completion ? '覆盖完成文件' : '上传完成文件' }}</el-button></el-upload><el-button size="small" type="primary" :disabled="!completionFiles[document.documentId]" :loading="actionLoading" @click="uploadCompletion(document)">提交</el-button></td>
|
||||
<td><div v-if="canManage && customs.status === 'DECLARING'" class="document-actions"><el-upload :auto-upload="false" :limit="1" :show-file-list="false" :on-change="(upload: UploadFile) => selectCompletion(document.documentId, upload)"><el-button size="small"><FileUp :size="14" />{{ document.completion ? '覆盖完成文件' : '上传完成文件' }}</el-button></el-upload><el-button size="small" type="primary" :disabled="!completionFiles[document.documentId]" :loading="actionLoading" @click="uploadCompletion(document)">提交</el-button></div><span v-else class="readonly-label">只读</span></td>
|
||||
<td><div class="file-cell"><span :title="document.source.fileName">{{ document.source.fileName }}</span><el-button link type="primary" :loading="downloadingKey === `source:${document.documentId}`" :aria-label="`下载源资料 ${document.source.fileName}`" @click="downloadFile(document, 'source')"><Download :size="14" />下载</el-button></div></td>
|
||||
<td><div v-if="document.completion" class="file-cell"><span :title="document.completion.fileName">{{ document.completion.fileName }}</span><el-button link type="primary" :loading="downloadingKey === `completion:${document.documentId}`" :aria-label="`下载完成文件 ${document.completion.fileName}`" @click="downloadFile(document, 'completion')"><Download :size="14" />下载</el-button></div><span v-else>未上传</span></td>
|
||||
<td>{{ document.inboundCodes.join('、') || '—' }}</td><td>资料 v{{ document.source.version }}<span v-if="document.completion"> / 完成 v{{ document.completion.version }}</span></td><td>{{ documentCustomers(document) }}</td><td>{{ document.completion ? '已上传完成文件' : '待上传' }}</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
@@ -152,5 +188,5 @@ onMounted(load)
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.container-customs{padding:12px 16px}.customs-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.customs-header h2,.compact-table-wrap h3{margin:0;font-size:15px}.customs-header p{margin:3px 0 0;color:var(--cc-muted);font-size:12px}.customs-actions{display:flex;align-items:center;gap:8px}.customs-metrics{display:flex;gap:20px;margin-top:10px;padding:8px 10px;background:var(--cc-surface-muted);font-size:12px}.customs-metrics strong{margin-left:4px;font-size:16px}.customs-metrics .danger strong{color:var(--el-color-danger)}.compact-table-wrap{margin-top:10px}.compact-table-wrap h3{margin-bottom:6px}.compact-table{width:100%;border-collapse:collapse;font-size:12px}.compact-table th,.compact-table td{padding:7px 9px;border:1px solid var(--cc-border);text-align:left;vertical-align:middle}.compact-table th{background:var(--cc-surface-muted);color:var(--cc-muted)}.document-table th:first-child{width:230px}.document-table td:first-child{display:flex;gap:6px}.departure-state{display:flex;align-items:center;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid var(--cc-border);font-size:13px}
|
||||
.container-customs{padding:12px 16px}.customs-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.customs-header h2,.compact-table-wrap h3{margin:0;font-size:15px}.customs-header p{margin:3px 0 0;color:var(--cc-muted);font-size:12px}.customs-actions{display:flex;align-items:center;gap:8px}.action-error{margin-top:8px;padding:8px 10px;border:1px solid var(--el-color-danger-light-5);border-radius:4px;background:var(--el-color-danger-light-9);color:var(--el-color-danger);font-size:12px}.action-error :deep(.api-error-meta){margin-top:5px}.customs-metrics{display:flex;gap:20px;margin-top:10px;padding:8px 10px;background:var(--cc-surface-muted);font-size:12px}.customs-metrics strong{margin-left:4px;font-size:16px}.customs-metrics .danger strong{color:var(--el-color-danger)}.compact-table-wrap{margin-top:10px}.compact-table-wrap h3{margin-bottom:6px}.compact-table{width:100%;border-collapse:collapse;font-size:12px}.compact-table th,.compact-table td{padding:7px 9px;border:1px solid var(--cc-border);text-align:left;vertical-align:middle}.compact-table th{background:var(--cc-surface-muted);color:var(--cc-muted)}.document-table th:first-child{width:230px}.document-actions,.file-cell{display:flex;align-items:center;gap:6px}.file-cell span{display:block;max-width:210px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.readonly-label{color:var(--cc-muted)}.departure-state{display:flex;align-items:center;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid var(--cc-border);font-size:13px}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user