@@ -0,0 +1,34 @@
< script setup lang = "ts" >
import { computed , onMounted , reactive , ref , watch } from 'vue'
import { ElMessage , type FormInstance , type FormRules } from 'element-plus'
import { Eye , Plus , Send } from '@lucide/vue'
import QueryToolbar , { type QueryField , type QueryModel } from '@/shared/components/QueryToolbar.vue'
import DataTable from '@/shared/components/DataTable.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import AdminPage from '@/shared/components/AdminPage.vue'
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
import StatusBadge from '@/shared/components/StatusBadge.vue'
import { getApiErrorDetails , type ApiErrorDetails } from '@/shared/api/http'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { createCustomsCase , getCustomsCase , listCustomsCases , submitCustomsCase } from '../api/platformCustoms'
import type { CustomsCaseCreateRequest , CustomsCaseDetail , CustomsCaseResource } from '../types'
const access = useAccessPreviewStore ( ) ; const rows = ref < CustomsCaseResource [ ] > ( [ ] ) ; const loading = ref ( false ) ; const error = ref < ApiErrorDetails | null > ( null ) ; const page = ref ( 1 ) ; const pageSize = ref ( 20 ) ; const total = ref ( 0 )
const filters = reactive < QueryModel > ( { orderId : '' , inboundId : '' , status : '' } ) ; const fields : QueryField [ ] = [ { key : 'orderId' , type : 'input' , label : '订单 ID' , width : 170 } , { key : 'inboundId' , type : 'input' , label : '入库单 ID' , width : 170 } , { key : 'status' , type : 'select' , label : '状态' , options : [ 'DRAFT' , 'COLLECTING' , 'READY' , 'SUBMITTED' , 'RELEASED' , 'REJECTED' ] . map ( ( value ) => ( { label : value , value } ) ) } ]
const columns = [ { key : 'actions' , label : '操作' , fixed : 'left' , width : 175 , slotName : 'actions' , hideable : false , movable : false } , { prop : 'customsNo' , label : '报关编号' , minWidth : 145 } , { prop : 'orderId' , label : '订单 ID' , minWidth : 155 } , { prop : 'inboundId' , label : '入库单 ID' , minWidth : 155 } , { prop : 'checklistCode' , label : '资料清单' , minWidth : 125 } , { prop : 'checklistVersionNo' , label : '版本' , width : 90 } , { prop : 'status' , label : '状态' , width : 110 , slotName : 'status' } , { prop : 'updatedAt' , label : '更新时间' , minWidth : 160 } ]
async function load ( ) { loading . value = true ; error . value = null ; try { const result = await listCustomsCases ( { page : page . value , size : pageSize . value , orderId : String ( filters . orderId || '' ) || undefined , inboundId : String ( filters . inboundId || '' ) || undefined , status : String ( filters . status || '' ) || undefined } ) ; rows . value = result . records ; total . value = result . total } catch ( cause ) { error . value = getApiErrorDetails ( cause ) } finally { loading . value = false } }
function search ( ) { if ( page . value === 1 ) void load ( ) ; else page . value = 1 }
const createOpen = ref ( false ) ; const createSaving = ref ( false ) ; const createError = ref < ApiErrorDetails | null > ( null ) ; const formRef = ref < FormInstance > ( ) ; const createForm = reactive < CustomsCaseCreateRequest > ( { customerId : '' , orderId : '' , inboundId : '' , containerBookingId : null , checklistVersionId : '' , remark : '' , expectedVersion : 0 } ) ; const rules : FormRules = { customerId : [ { required : true , message : '请输入客户 ID' , trigger : 'blur' } ] , orderId : [ { required : true , message : '请输入订单 ID' , trigger : 'blur' } ] , inboundId : [ { required : true , message : '请输入入库单 ID' , trigger : 'blur' } ] , checklistVersionId : [ { required : true , message : '请输入资料清单版本 ID' , trigger : 'blur' } ] }
function openCreate ( ) { Object . assign ( createForm , { customerId : '' , orderId : '' , inboundId : '' , containerBookingId : null , checklistVersionId : '' , remark : '' , expectedVersion : 0 } ) ; createError . value = null ; createOpen . value = true }
async function create ( ) { if ( ! await formRef . value ? . validate ( ) . catch ( ( ) => false ) ) return ; createSaving . value = true ; createError . value = null ; try { await createCustomsCase ( { ... createForm } ) ; ElMessage . success ( '报关协同已创建' ) ; createOpen . value = false ; await load ( ) } catch ( cause ) { createError . value = getApiErrorDetails ( cause ) } finally { createSaving . value = false } }
const detailOpen = ref ( false ) ; const detail = ref < CustomsCaseDetail | null > ( null ) ; const detailLoading = ref ( false ) ; const actionError = ref < ApiErrorDetails | null > ( null ) ; const submitForm = reactive ( { customsAgentName : '' , externalReference : '' , note : '' } ) ; const canSubmit = computed ( ( ) => detail . value && access . can ( 'platform:firstleg:customs:submit' ) && ! [ 'SUBMITTED' , 'RELEASED' ] . includes ( detail . value . customsCase . status ) )
async function openDetail ( id : string ) { detailOpen . value = true ; detailLoading . value = true ; actionError . value = null ; try { detail . value = await getCustomsCase ( id ) } catch ( cause ) { actionError . value = getApiErrorDetails ( cause ) } finally { detailLoading . value = false } }
async function submit ( ) { if ( ! detail . value || ! submitForm . customsAgentName . trim ( ) ) return ; actionError . value = null ; try { await submitCustomsCase ( detail . value . customsCase . id , { ... submitForm , expectedVersion : detail . value . customsCase . version } ) ; ElMessage . success ( '已登记线下提交批次' ) ; detail . value = await getCustomsCase ( detail . value . customsCase . id ) } catch ( cause ) { actionError . value = getApiErrorDetails ( cause ) ; if ( actionError . value . code === 'A0409' ) detail . value = await getCustomsCase ( detail . value . customsCase . id ) } }
watch ( [ page , pageSize ] , ( ) => void load ( ) ) ; onMounted ( load )
< / script >
< template > < AdminPage as = "div" > < ListPageLayout aria -label = " 报关协同列表 " > < template # toolbar > < QueryToolbar v-model = "filters" class="surface-panel" ariaLabel="报关协同筛选" :fields="fields" @search="search" /></template><template v-if="error" #state><LoadErrorState title="报关协同加载失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState></template><section v-if="!error" class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" row-key="id" preference-key="customs-cases" empty-text="暂无报关协同记录"><template #toolbar-actions><el-button v-if="access.can('platform:firstleg:customs:manage')" type="primary" @click="openCreate"><Plus :size="16" />新增报关</el-button></template><template #actions="{ row }"><el-button v-if="access.can('platform:firstleg:customs:view')" link type="primary" @click="openDetail(row.id)"><Eye :size="15" />查看</el-button></template><template #status="{ row }"><StatusBadge :status="row.status" / > < / template > < / DataTable > < / section > < / ListPageLayout >
< AppDialog v-model = "createOpen" title="新增报关协同" width="720px" :confirm-loading="createSaving" @confirm="create"><el-alert v-if="createError" type="error" :closable="false"><ApiErrorMeta :details="createError" /></el-alert><el-form ref="formRef" :model="createForm" :rules="rules" label-position="top"><el-row :gutter="16"><el-col :xs="24" :sm="12"><el-form-item label="客户 ID" prop="customerId"><el-input v-model="createForm.customerId" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="订单 ID" prop="orderId"><el-input v-model="createForm.orderId" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="入库单 ID" prop="inboundId"><el-input v-model="createForm.inboundId" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="柜次 ID"><el-input v-model="createForm.containerBookingId" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="资料清单版本 ID" prop="checklistVersionId"><el-input v-model="createForm.checklistVersionId" /></el-form-item></el-col><el-col :xs="24"><el-form-item label="备注"><el-input v-model="createForm.remark" type="textarea" / > < / el-form-item > < / el-col > < / el-row > < / el-form > < / AppDialog >
< AppDialog v-model = "detailOpen" title="报关协同详情" width="900px" :show-confirm="false"><LoadErrorState v-if="actionError && !detail" title="详情加载失败" :message="actionError.message" :loading="detailLoading" :show-retry="false"><template #details><ApiErrorMeta :details="actionError" /></template></LoadErrorState><template v-if="detail"><el-alert v-if="actionError" type="error" :closable="false"><ApiErrorMeta :details="actionError" /></el-alert><el-descriptions :column="2" border><el-descriptions-item label="报关编号">{{ detail.customsCase.customsNo }}</el-descriptions-item><el-descriptions-item label="状态"><StatusBadge :status="detail.customsCase.status" /></el-descriptions-item><el-descriptions-item label="资料清单">{{ detail.customsCase.checklistCode }} / {{ detail.customsCase.checklistVersionNo }}</el-descriptions-item><el-descriptions-item label="资料/回执">{{ detail.documents.length }} / {{ detail.receipts.length }}</el-descriptions-item></el-descriptions><el-form v-if="canSubmit" label-position="top" class="submit-form"><el-row :gutter="12"><el-col :xs="24" :sm="12"><el-form-item label="报关代理"><el-input v-model="submitForm.customsAgentName" /></el-form-item></el-col><el-col :xs="24" :sm="12"><el-form-item label="外部参考号"><el-input v-model="submitForm.externalReference" /></el-form-item></el-col><el-col :xs="24"><el-form-item label="提交备注"><el-input v-model="submitForm.note" /></el-form-item></el-col></el-row><el-button type="primary" @click="submit"><Send :size="16" />登记线下提交</el-button></el-form><h3>状态历史</h3><el-timeline><el-timeline-item v-for="entry in detail.history" :key="entry.id" :timestamp="entry.createdAt" > {{ entry.action }} : {{ entry.fromStatus | | ' — ' }} → {{ entry.toStatus | | ' — ' }} < small > { { entry . note || '' } } < / small > < / el-timeline-item > < / el-timeline > < / template > < / AppDialog > < / AdminPage > < / template >
< style scoped > . submit - form { margin - top : 18 px } . submit - form + . el - button { margin - bottom : 12 px } h3 { margin : 20 px 0 12 px } small { display : block ; color : var ( -- cc - muted ) } < / style >