Files
admin-web/src/modules/appointment-calendar/AppointmentCargoPanel.vue
T

91 lines
5.5 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import QueryToolbar, { type QueryModel, type QueryField } from '@/shared/components/QueryToolbar.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import RowActions from '@/shared/components/RowActions.vue'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import { listPlanningCargo, resolvePlanningSource, type ResolvedPlanningSource, type PlanningCargo } from './planningApi'
import AppointmentFollowupDialog from './AppointmentFollowupDialog.vue'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
const router = useRouter()
const access = useAccessPreviewStore()
const following = ref<ResolvedPlanningSource | null>(null)
const followupError = ref<ApiErrorDetails | null>(null)
let followupRequest = 0
const query = ref<QueryModel>({ keyword: '' })
const fields: QueryField[] = [{ key: 'keyword', type: 'input', label: '货物', placeholder: '入仓码、柜号、客户名称', width: 300 }]
const page = ref(1), size = ref(20), total = ref(0)
const rows = ref<PlanningCargo[]>([])
const loading = ref(false)
const error = ref<ApiErrorDetails | null>(null)
let request = 0
let controller: AbortController | undefined
const columns: DataTableColumn[] = [
{ key: 'actions', label: '操作', slot: 'actions', width: 150, fixed: 'left' },
{ prop: 'inboundCode', label: '入仓码', minWidth: 180 },
{ prop: 'customerName', label: '客户', minWidth: 150 },
{ prop: 'containerNo', label: '柜号', minWidth: 150 },
{ prop: 'originWarehouseName', label: '接仓点', minWidth: 140 },
{ prop: 'destinationWarehouseCode', label: '目的FBA仓', width: 110 },
{ prop: 'packages', label: '预报箱数', width: 100 },
{ prop: 'receivedPackages', label: '实收箱数', width: 100 },
{ prop: 'estimatedArrivalAt', label: '来源预计到港', minWidth: 170 },
{ prop: 'estimatedWarehouseAt', label: '已保存预计到仓', minWidth: 170 },
{ key: 'progress', label: '当前事项', slot: 'progress', minWidth: 170 },
]
async function load(reset = false) {
if (reset) page.value = 1
const current = ++request
controller?.abort(); controller = new AbortController()
loading.value = true; error.value = null
try {
const result = await listPlanningCargo({ page: page.value, size: size.value, keyword: String(query.value.keyword || '').trim() || undefined }, controller.signal)
if (request !== current) return
rows.value = result.records; total.value = result.total
} catch (cause) {
if (request === current) { error.value = getApiErrorDetails(cause, '已开船货物加载失败'); rows.value = []; total.value = 0 }
} finally { if (request === current) loading.value = false }
}
function source(row: PlanningCargo) {
return router.push({ name: 'overseas-last-mile-order-detail', params: { orderId: row.orderId }, query: { customerId: row.customerId } })
}
async function openFollowup(row: PlanningCargo) {
const request = ++followupRequest
followupError.value = null
try {
const result = await resolvePlanningSource({ sourceKey: row.sourceKey, customerId: row.customerId, destinationKey: `FBA:${row.destinationWarehouseCode}` })
if (request === followupRequest) following.value = result
} catch (cause) { if (request === followupRequest) followupError.value = getApiErrorDetails(cause, '来源核对失败') }
}
onMounted(() => load())
onBeforeUnmount(() => { ++request; ++followupRequest; controller?.abort() })
</script>
<template>
<DataTable v-model:page="page" v-model:page-size="size" appearance="integrated" :columns="columns" :data="rows" :total="total" :loading="loading" :results-visible="!error" row-key="sourceKey" @update:page="load()" @update:page-size="load(true)">
<template #filters><QueryToolbar v-model="query" ariaLabel="在途货物查询" :fields="fields" :loading="loading" @search="load(true)" /></template>
<template #notices>
<LoadErrorState v-if="followupError" title="来源核对失败" :message="followupError.message" :show-retry="false" />
<LoadErrorState v-if="error" title="已开船货物加载失败" :message="error.message" :loading="loading" @retry="load()"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
<p v-else class="cargo-note">来源预计时间供筹划核对,确认预约前请核对最新船期与货物备妥情况。</p>
</template>
<template #actions="{ row }"><RowActions :actions="[{ key: 'followup', label: '跟进' }, { key: 'source', label: '查看来源' }]" text-actions @action="key => key === 'followup' ? openFollowup(row as PlanningCargo) : source(row as PlanningCargo)" /></template>
<template #progress="{ row }">
<span v-if="row.detained">暂扣,待处理</span>
<span v-else-if="row.mixedDestinations">含多个目的地,待核对</span>
<span v-else-if="row.hasActiveAppointment">已有预约</span>
<span v-else>{{ row.hasInboundPlan ? '已建入仓计划,待安排' : '尚未建入仓计划' }}</span>
</template>
</DataTable>
<AppointmentFollowupDialog v-if="following" :source="following.source" :coverage="following.coverage" :can-write="access.can('platform:appointment:followup')" @close="following = null" />
</template>
<style scoped>
.cargo-note { margin: var(--admin-space-3) 0; color: var(--el-text-color-secondary); font-size: var(--admin-font-caption); }
</style>