This commit is contained in:
2026-08-20 18:02:52 +08:00
parent 4ae5e7c672
commit 80947cd9ce
65 changed files with 1386 additions and 1083 deletions
@@ -1,58 +1,85 @@
<script setup lang="ts">
import { ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { Eye } from '@lucide/vue'
import { useRouter } from 'vue-router'
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import AdminPage from '@/shared/components/AdminPage.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
import ContractPendingNotice from '../components/ContractPendingNotice.vue'
import { listInboundPlans } from '../api/inboundPlans'
import type { InboundOrderListItem } from '../types'
const rows = ref<InboundOrderListItem[]>([])
const customers = ref<PlatformTenantResource[]>([])
const router = useRouter()
const page = ref(1)
const pageSize = ref(20)
const filters = ref<QueryModel>({ orderNo: '', inboundCode: '', status: '', warehouse: '', fbaWarehouse: '', appointmentStatus: '', dispatchStatus: '', inboundDate: [] })
const fields: QueryField[] = [
const total = ref(0)
const loading = ref(false)
const customersLoading = ref(false)
const loadError = ref<ApiErrorDetails | null>(null)
const customersError = ref<ApiErrorDetails | null>(null)
const filters = ref<QueryModel>({ customerId: '', orderNo: '', inboundCode: '', status: '', warehouse: '', fbaWarehouse: '', appointmentStatus: '', dispatchStatus: '', inboundDate: [] })
const warehouseOptions = computed(() => [...new Map(rows.value.filter((row) => row.warehouseId).map((row) => [row.warehouseId, row.warehouse])).entries()].map(([value, label]) => ({ label, value })))
const fbaWarehouseOptions = computed(() => [...new Set(rows.value.map((row) => row.fbaWarehouse).filter((value) => value !== '—'))].map((value) => ({ label: value, value })))
const fields = computed<QueryField[]>(() => [
{ key: 'customerId', type: 'select', label: '客户名称', placeholder: '请选择客户', width: 240, props: { filterable: true, clearable: true, loading: customersLoading.value }, options: customers.value.map((item) => ({ label: item.tenantName, value: String(item.id) })) },
{ key: 'orderNo', type: 'input', label: '入库单号', placeholder: '请输入入库单号' },
{ key: 'inboundCode', type: 'input', label: '入仓码', placeholder: '请输入入仓码' },
{ key: 'status', type: 'select', label: '状态', placeholder: '全部状态', options: ['待审核', '已审核', '已入库'].map((value) => ({ label: value, value })) },
{ key: 'warehouse', type: 'select', label: '海外仓', placeholder: '全部海外仓', options: [] },
{ key: 'fbaWarehouse', type: 'select', label: 'FBA仓库', placeholder: '全部FBA仓库', options: [] },
{ key: 'appointmentStatus', type: 'select', label: '预约状态', placeholder: '全部预约状态', options: ['未预约', '预约中', '预约成功'].map((value) => ({ label: value, value })) },
{ key: 'dispatchStatus', type: 'select', label: '排车状态', placeholder: '全部排车状态', options: ['未排车', '已排车'].map((value) => ({ label: value, value })) },
{ key: 'inboundDate', type: 'daterange', label: '入库时间' },
]
{ key: 'status', type: 'select', label: '状态', placeholder: '全部状态', options: [{ label: '待分配', value: 'PENDING_ASSIGNMENT' }, { label: '已完成', value: 'COMPLETED' }] },
{ key: 'warehouse', type: 'select', label: '海外仓', placeholder: '全部海外仓', options: warehouseOptions.value },
{ key: 'fbaWarehouse', type: 'select', label: 'FBA仓库', placeholder: '全部FBA仓库', options: fbaWarehouseOptions.value },
{ key: 'appointmentStatus', type: 'select', label: '预约状态', placeholder: '全部预约状态', options: [{ label: '待预约', value: 'PENDING' }, { label: '预约成功', value: 'SUCCESS' }, { label: '预约失败', value: 'FAILED' }, { label: '已取消', value: 'CANCELLED' }] },
{ key: 'dispatchStatus', type: 'select', label: '排车状态', placeholder: '接口未提供', options: [], props: { disabled: true } },
{ key: 'inboundDate', type: 'daterange', label: '创建时间' },
])
const columns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 120, fixed: 'left', slotName: 'actions' },
{ key: 'orderNo', prop: 'orderNo', label: '入库单号', minWidth: 170 },
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 140 },
{ key: 'type', prop: 'type', label: '类型', width: 100 },
{ key: 'warehouse', prop: 'warehouse', label: '海外仓', minWidth: 170 },
{ key: 'fbaWarehouse', prop: 'fbaWarehouse', label: 'FBA仓库', minWidth: 130 },
{ key: 'owner', prop: 'owner', label: '货主', minWidth: 150 },
{ key: 'cartons', prop: 'cartons', label: '总箱数', width: 90, align: 'right' },
{ key: 'volume', prop: 'volume', label: '计费体积(m³)', width: 125, align: 'right' },
{ key: 'pallets', prop: 'pallets', label: '总托盘数', width: 100, align: 'right' },
{ key: 'status', prop: 'status', label: '状态', width: 100 },
{ key: 'appointmentStatus', prop: 'appointmentStatus', label: 'FBA预约', width: 110 },
{ key: 'dispatchStatus', prop: 'dispatchStatus', label: '排车状态', width: 110 },
{ key: 'orderNo', prop: 'orderNo', label: '入库单号', minWidth: 170 }, { key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 140 },
{ key: 'type', prop: 'type', label: '类型', width: 100 }, { key: 'warehouse', prop: 'warehouse', label: '海外仓', minWidth: 170 },
{ key: 'fbaWarehouse', prop: 'fbaWarehouse', label: 'FBA仓库', minWidth: 130 }, { key: 'owner', prop: 'owner', label: '货主', minWidth: 150 },
{ key: 'cartons', prop: 'cartons', label: '总箱数', width: 90, align: 'right' }, { key: 'volume', prop: 'volume', label: '计费体积(m³)', width: 125, align: 'right' },
{ key: 'pallets', prop: 'pallets', label: '总托盘数', width: 100, align: 'right' }, { key: 'status', prop: 'status', label: '状态', width: 100 },
{ key: 'appointmentStatus', prop: 'appointmentStatus', label: 'FBA预约', width: 110 }, { key: 'dispatchStatus', prop: 'dispatchStatus', label: '排车状态', width: 110 },
]
function openDetail(source: object) {
const row = source as InboundOrderListItem
void router.push({ name: 'overseas-inbound-order-detail', params: { orderId: row.orderNo } })
function text(value: unknown) { return String(value ?? '').trim() || '—' }
function statusLabel(value: unknown) { const labels: Record<string, string> = { PENDING_ASSIGNMENT: '待分配', COMPLETED: '已完成' }; return labels[String(value || '')] || text(value) }
function appointmentLabel(value: unknown) { const labels: Record<string, string> = { PENDING: '待预约', SUCCESS: '预约成功', FAILED: '预约失败', CANCELLED: '已取消' }; return labels[String(value || '')] || text(value) }
function orderType(value: unknown) { const labels: Record<string, string> = { ONE_CLICK_DROPSHIP: '一件代发', FIRST_LEG: '头程', RETURN_INBOUND: '退货入库' }; return labels[String(value || '')] || text(value) }
async function loadCustomers() {
customersLoading.value = true; customersError.value = null
try { const records: PlatformTenantResource[] = []; let current = 1; let pages = 1; do { const result = await listPlatformTenants({ page: current, size: 200, status: 'ACTIVE' }); records.push(...result.records); pages = Math.max(1, result.pages); current += 1 } while (current <= pages); customers.value = records }
catch (error) { customersError.value = getApiErrorDetails(error, '客户列表加载失败,请重试') }
finally { customersLoading.value = false }
}
async function load(model: QueryModel = filters.value) {
Object.assign(filters.value, model)
const customerId = String(filters.value.customerId || '').trim()
if (!customerId) { rows.value = []; total.value = 0; loadError.value = null; return }
const range = Array.isArray(filters.value.inboundDate) ? filters.value.inboundDate.map(String) : []
loading.value = true; loadError.value = null
try {
const result = await listInboundPlans({ customerId, page: page.value, size: pageSize.value, planNo: String(filters.value.orderNo || '').trim() || undefined, inboundCode: String(filters.value.inboundCode || '').trim() || undefined, status: String(filters.value.status || '').trim() || undefined, overseasWarehouseId: String(filters.value.warehouse || '').trim() || undefined, fbaWarehouseCode: String(filters.value.fbaWarehouse || '').trim() || undefined, appointmentStatus: String(filters.value.appointmentStatus || '').trim() || undefined, createdFrom: range[0] ? `${range[0]}T00:00:00` : undefined, createdTo: range[1] ? `${range[1]}T23:59:59` : undefined })
rows.value = result.records.map((item) => ({ id: String(item.id), customerId, sourceOrderId: String(item.sourceOrderId), warehouseId: String(item.overseasWarehouseId || ''), destinationWarehouseCode: String(item.destinationWarehouseCode || ''), orderNo: text(item.planNo), inboundCode: text(item.inboundCode), type: orderType(item.orderType), warehouse: text(item.overseasWarehouseName || item.destinationWarehouseName), fbaWarehouse: text(item.fbaWarehouseCode), owner: text(item.customerName), cartons: Number(item.packageCount || 0), volume: Number(item.totalVolumeCbm || 0), pallets: Number(item.palletCount || 0), status: statusLabel(item.status), appointmentStatus: appointmentLabel(item.appointmentStatus), dispatchStatus: '—' }))
total.value = result.total
} catch (error) { rows.value = []; total.value = 0; loadError.value = getApiErrorDetails(error, '入库单加载失败,请重试') }
finally { loading.value = false }
}
function search(model: QueryModel) { page.value = 1; void load(model) }
function reset(model: QueryModel) { page.value = 1; void load(model) }
function openDetail(source: object) { const row = source as InboundOrderListItem; void router.push({ name: 'overseas-inbound-order-detail', params: { orderId: row.orderNo }, query: { customerId: row.customerId } }) }
onMounted(loadCustomers)
</script>
<template>
<AdminPage><ListPageLayout aria-label="海外仓入库单列表">
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" :fields="fields" ariaLabel="入库单筛选" preference-key="overseas-inbound-orders-query" @search="page = 1" /></template>
<template #state><ContractPendingNotice domain="入库单" /></template>
<section class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="0" preference-key="overseas-inbound-orders" row-key="orderNo" empty-text="海外区域入库单查询契约待接入">
<template #actions="{ row }"><el-button size="small" plain type="primary" @click="openDetail(row)"><Eye :size="15" />查看</el-button></template>
<template #pagination-left>共 0 条入库单</template>
</DataTable></section>
</ListPageLayout></AdminPage>
</template>
<template><AdminPage><ListPageLayout aria-label="海外仓入库单列表">
<template #toolbar><QueryToolbar v-model="filters" class="surface-panel" :fields="fields" :loading="loading" ariaLabel="入库单筛选" preference-key="overseas-inbound-orders-query" @search="search" @reset="reset" /></template>
<template v-if="customersError || loadError" #state><LoadErrorState title="入库单加载失败" :message="(customersError || loadError)?.message" :loading="customersLoading || loading" @retry="customersError ? loadCustomers() : load()"><template #details><ApiErrorMeta :details="(customersError || loadError)!" /></template></LoadErrorState></template>
<section v-if="!customersError && !loadError" class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" preference-key="overseas-inbound-orders" row-key="id" :empty-text="filters.customerId ? '暂无入库单' : '请先选择客户后查询'" @update:page="load()" @update:page-size="page = 1; load()">
<template #actions="{ row }"><el-button size="small" plain type="primary" @click="openDetail(row)"><Eye :size="15" />查看</el-button></template><template #pagination-left>共 {{ total }} 条入库单</template>
</DataTable></section>
</ListPageLayout></AdminPage></template>