统一后台状态列顺序、详情布局与测量计费展示

This commit is contained in:
2026-10-28 10:32:33 +08:00
parent 8e97fd6a9f
commit e41e9e6d3f
120 changed files with 436 additions and 238 deletions
@@ -124,10 +124,10 @@ async function save() {
<section v-for="group in visibleGroups" :key="group.category" class="document-group density-dialog-section" data-tone="source" :data-category="group.category">
<header><h3 class="density-dialog-section__title">{{ group.label }}</h3><el-tag :type="group.files.length ? 'success' : 'info'">{{ group.files.length ? '已上传' : '待上传' }}</el-tag><span>{{ group.files.length }}/20</span><el-upload v-if="canEdit" :key="uploadKey" :auto-upload="false" :show-file-list="false" :disabled="busy || conflict || group.files.length >= 20" :on-change="(file: UploadFile) => upload(group.category, file)"><el-button class="density-action" data-tone="primary" :disabled="busy || conflict || group.files.length >= 20">上传</el-button></el-upload></header>
<p v-if="!group.files.length" class="density-dialog-note">尚未上传文件</p>
<table v-else class="density-table document-files"><thead><tr><th class="document-file-actions">操作</th><th>文件名称</th><th class="document-file-time">上传时间</th><th class="document-file-state">保存状态</th></tr></thead><tbody>
<table v-else class="density-table document-files"><thead><tr><th class="document-file-actions">操作</th><th>文件名称</th><th class="document-file-state">保存状态</th><th class="document-file-time">上传时间</th></tr></thead><tbody>
<tr v-for="file in group.files" :key="file.fileId" class="document-file">
<td><div class="density-actions"><el-button link :aria-label="'下载 ' + file.fileName" :disabled="downloading || !isSaved(group.category, file.fileId)" @click="download(group.category, file)">下载</el-button><template v-if="canEdit"><el-upload :key="uploadKey" :auto-upload="false" :show-file-list="false" :disabled="busy || conflict" :on-change="(uploadFile: UploadFile) => upload(group.category, uploadFile, file.fileId)"><el-button class="density-action" data-tone="primary" :disabled="busy || conflict">替换</el-button></el-upload><el-button class="density-action" data-tone="danger" :disabled="busy || conflict" @click="remove(group.category, file.fileId)">删除</el-button></template></div></td>
<td><LineClampText :content="file.fileName" /></td><td>{{ file.uploadedAt?.replace('T', ' ') || '—' }}</td><td>{{ isSaved(group.category, file.fileId) ? '已保存' : '待保存' }}</td>
<td><LineClampText :content="file.fileName" /></td><td>{{ isSaved(group.category, file.fileId) ? '已保存' : '待保存' }}</td><td>{{ file.uploadedAt?.replace('T', ' ') || '—' }}</td>
</tr>
</tbody></table>
</section>
@@ -48,6 +48,6 @@ onBeforeUnmount(() => { generation++ })
<p v-if="!deliveries.length" class="density-dialog-note">尚未生成派送单费用。</p>
<p v-else-if="!allowed" class="density-dialog-note">暂无派送单费用查看权限。</p>
<LoadErrorState v-else-if="error" title="费用读取失败" :message="error.message" compact @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
<table v-else class="density-table"><thead><tr><th>客户</th><th>派送单号</th><th>费用项目</th><th>币种</th><th>金额</th><th>费用状态</th></tr></thead><tbody><tr v-for="fee in fees" :key="`${fee.deliveryNo}-${fee.id}`"><td>{{ fee.customerName || '—' }}</td><td>{{ fee.deliveryNo }}</td><td>{{ labels[fee.feeType] || fee.feeType || '—' }}</td><td>{{ fee.currency || '—' }}</td><td>{{ fee.amount ?? '—' }}</td><td>{{ status(fee) }}</td></tr><tr v-if="!loading && !fees.length"><td colspan="6">暂无正式费用记录</td></tr></tbody></table>
<table v-else class="density-table"><thead><tr><th>客户</th><th>派送单号</th><th>费用状态</th><th>费用项目</th><th>币种</th><th>金额</th></tr></thead><tbody><tr v-for="fee in fees" :key="`${fee.deliveryNo}-${fee.id}`"><td>{{ fee.customerName || '—' }}</td><td>{{ fee.deliveryNo }}</td><td>{{ status(fee) }}</td><td>{{ labels[fee.feeType] || fee.feeType || '—' }}</td><td>{{ fee.currency || '—' }}</td><td>{{ fee.amount ?? '—' }}</td></tr><tr v-if="!loading && !fees.length"><td colspan="6">暂无正式费用记录</td></tr></tbody></table>
</div>
</template>
@@ -296,10 +296,10 @@ onBeforeUnmount(clearTruckPreviewUrls)
.pickup-detail-facts dd { display: flex; flex-wrap: wrap; gap: 4px 10px; }
.truck-evidence-panel { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; padding: 12px 14px; border: 1px solid var(--cc-border); border-radius: var(--cc-radius-md); background: var(--cc-surface); }
.truck-evidence-panel > h2 { flex: 0 0 100%; margin: 0; }
.truck-evidence-panel label { display: inline-flex; align-items: center; gap: 10px; color: var(--cc-text); font-size: 13px; }
.truck-evidence-panel label { display: inline-flex; align-items: center; gap: 10px; color: var(--cc-text); font-size: var(--admin-font-body); }
.truck-evidence-upload { padding: 8px 14px; border: 1px solid var(--el-color-primary); border-radius: var(--cc-radius-sm); color: var(--el-color-primary) !important; cursor: pointer; }
.truck-evidence-upload input[type='file'] { display: none; }
.truck-evidence-list { display: flex; flex-wrap: wrap; gap: 12px; color: var(--cc-muted); font-size: 12px; }
.truck-evidence-list { display: flex; flex-wrap: wrap; gap: 12px; color: var(--cc-muted); font-size: var(--admin-font-caption); }
.truck-evidence-item { width: 132px; margin: 0; }
.truck-evidence-item { position: relative; }
.truck-evidence-item .el-image { display: block; width: 132px; height: 92px; overflow: hidden; border: 1px solid var(--cc-border); border-radius: var(--cc-radius-sm); background: var(--cc-surface-muted); cursor: pointer; }
@@ -123,10 +123,10 @@ const gateText = computed(() => {
const allocationRows = computed(() => (detail.value?.allocations || []).map(row => ({ ...row, skuCode: row.skuCode && row.skuCode !== 'MIXED' ? row.skuCode : '—', movementStatus: ({ RESERVED: '已分配', SHIPPED: '已发运', RECEIVED: '目标已实收' } as Record<string, string>)[row.status] || '—' })))
const columns: DataTableColumn[] = [
{ key: 'inboundCode', prop: 'inboundCode', label: '来源入仓码', minWidth: 160 },
{ key: 'movementStatus', prop: 'movementStatus', label: '履约状态', width: 120, afterColumn: 'inboundCode' },
{ key: 'palletNo', prop: 'palletNo', label: '来源托盘码', minWidth: 180 },
{ key: 'skuCode', prop: 'skuCode', label: '实际 SKU', minWidth: 150 },
{ key: 'packageCount', prop: 'packageCount', label: '分配箱数', width: 100 },
{ key: 'movementStatus', prop: 'movementStatus', label: '履约状态', width: 120 },
]
async function load() {
const current = ++request
@@ -196,7 +196,7 @@ onBeforeUnmount(() => { generation++ })
.receipt-box-heading{display:flex;align-items:center;justify-content:flex-start;flex-wrap:wrap;gap:12px}
.receipt-discrepancy-reason{margin:0;white-space:pre-wrap;overflow-wrap:anywhere}
.transfer-receipt-panel{display:grid;gap:var(--admin-space-2)}
.receipt-aligned-table{width:100%;max-width:1490px;min-width:0}
.receipt-aligned-table{width:100%;min-width:0}
.receipt-difference{color:var(--el-color-danger);font-weight:600}
h3 small{margin-left:8px;color:var(--workbench-muted);font-size:var(--admin-font-caption);font-weight:400}
h3{margin:0;font-size:var(--admin-font-section)}
@@ -22,7 +22,7 @@ const access = useAccessPreviewStore(); const rows = ref<CargoDiscardSummary[]>(
/** 数量事实缺失时明确提示,不用零代替未记录。 */
function quantity(value: number | null | undefined, unit: string) { return value == null ? '未记录 / 不适用' : `${value} ${unit}` }
const fields: QueryField[] = [{ key: 'customerId', type: 'select', label: '客户', placeholder: '请选择客户', options: [] }, { key: 'discardNo', type: 'input', label: '废弃单号', placeholder: '废弃单号', width: 220 }, { key: 'inboundCode', type: 'input', label: '入仓码', placeholder: '请输入入仓码', width: 180 }, { key: 'status', type: 'select', label: '状态', placeholder: '全部状态', options: [{ label: '待审核', value: 'PENDING_REVIEW' }, { label: '已审核', value: 'APPROVED' }, { label: '待客户确认', value: 'PDA_COMPLETED' }, { label: '已完成', value: 'COMPLETED' }, { label: '已驳回', value: 'REJECTED' }, { label: '已取消', value: 'CANCELLED' }] }]
const columns: DataTableColumn[] = [{ key: 'actions', label: '操作', width: 140, fixed: 'left', slotName: 'actions' }, { key: 'discardNo', label: '废弃单号', minWidth: 180 }, { key: 'inboundCodes', label: '入仓码', minWidth: 180 }, { key: 'overseasWarehouseName', label: '仓库', minWidth: 140 }, { key: 'requestedPackages', label: '申请箱数', width: 90 }, { key: 'totalFee', label: '废弃费用', width: 120, formatter: (row: CargoDiscardSummary) => `${row.currency || 'USD'} ${row.totalFee ?? 0}` }, { key: 'createdBy', label: '申请人', width: 110 }, { key: 'createdAt', label: '创建时间', minWidth: 170 }, { key: 'status', label: '状态', width: 150, slotName: 'status' }]
const columns: DataTableColumn[] = [{ key: 'actions', label: '操作', width: 140, fixed: 'left', slotName: 'actions' }, { key: 'discardNo', label: '废弃单号', minWidth: 180 }, { key: 'status', label: '状态', width: 150, slotName: 'status', afterColumn: 'discardNo' }, { key: 'inboundCodes', label: '入仓码', minWidth: 180 }, { key: 'overseasWarehouseName', label: '仓库', minWidth: 140 }, { key: 'requestedPackages', label: '申请箱数', width: 90 }, { key: 'totalFee', label: '废弃费用', width: 120, formatter: (row: CargoDiscardSummary) => `${row.currency || 'USD'} ${row.totalFee ?? 0}` }, { key: 'createdBy', label: '申请人', width: 110 }, { key: 'createdAt', label: '创建时间', minWidth: 170 }]
const statusLabels: Record<string, string> = { PENDING_REVIEW: '待审核', APPROVED: '已审核,待 PDA', PDA_COMPLETED: 'PDA 已完成,待客户确认', COMPLETED: '已完成', REJECTED: '已驳回', CANCELLED: '已取消' }
const customerError = ref<ApiErrorDetails | null>(null); const listError = ref<ApiErrorDetails | null>(null)
const detailError = ref<ApiErrorDetails | null>(null); const reviewError = ref<ApiErrorDetails | null>(null)
@@ -25,10 +25,10 @@ const fields: QueryField[] = [
]
const columns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 180, fixed: 'left', slotName: 'actions' },
{ key: 'cartonNo', label: '箱号', minWidth: 180 }, { key: 'fbaWarehouse', label: '所属FBA仓', minWidth: 150 },
{ key: 'cartonNo', label: '箱号', minWidth: 180 },
{ key: 'status', label: '状态', width: 100, afterColumn: 'cartonNo' }, { key: 'fbaWarehouse', label: '所属FBA仓', minWidth: 150 },
{ key: 'sku', label: '商品SKU', minWidth: 150 }, { key: 'productName', label: '中文品名', minWidth: 180 },
{ key: 'quantity', label: '数量', width: 100, align: 'right' }, { key: 'weight', label: '重量(kg)', width: 110, align: 'right' },
{ key: 'status', label: '状态', width: 100 },
]
const dialogOpen = ref(false)
const dialogMode = ref<'create' | 'edit'>('create')
@@ -44,11 +44,11 @@ const fields: QueryField[] = [
const columns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 130, fixed: 'left', slotName: 'actions' },
{ key: 'deliveryNo', prop: 'deliveryNo', label: '派送单号', minWidth: 180, fixed: 'left' },
{ key: 'status', prop: 'status', label: '回访状态', width: 110, slotName: 'status', afterColumn: 'deliveryNo' },
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 180 },
{ key: 'contact', label: '联系人', minWidth: 170, formatter: (row: CustomerVisitRow) => [row.contactName, row.contactPhone].filter(Boolean).join(' ') || '—' },
{ key: 'deliveryCompletedAt', prop: 'deliveryCompletedAt', label: '完成时间', minWidth: 170, formatter: (row: CustomerVisitRow) => formatOverseasDateTime(row.deliveryCompletedAt) },
{ key: 'destination', prop: 'destination', label: '目的地', minWidth: 140 },
{ key: 'status', prop: 'status', label: '回访状态', width: 110, slotName: 'status' },
{ key: 'visitedAt', prop: 'visitedAt', label: '回访时间', minWidth: 170, formatter: (row: CustomerVisitRow) => formatOverseasDateTime(row.visitedAt) },
{ key: 'visitResult', prop: 'visitResult', label: '回访结果', minWidth: 130, formatter: (row: CustomerVisitRow) => resultLabels[row.visitResult || ''] || '—' },
{ key: 'attachments', label: '资料', minWidth: 110, formatter: (row: CustomerVisitRow) => row.attachments.length ? `${row.attachments.length} 份` : '—' },
@@ -113,8 +113,8 @@ watch([deliveryNo, customerId], () => { void load() }, { immediate: true })
<h2 v-if="detail?.fees.length" class="fulfillment-fees-heading">履约费用记录</h2>
<el-table v-if="detail?.fees.length" :data="detail.fees" border stripe empty-text="暂无履约费用">
<el-table-column type="index" label="序号" width="80" /><el-table-column label="应收/应付" width="120"><template #default="scope">{{ scope.row.feeDirection === 'PAYABLE' ? '应付' : scope.row.feeDirection === 'RECEIVABLE' ? '应收' : show(scope.row.feeDirection) }}</template></el-table-column>
<el-table-column label="费用类型" min-width="180"><template #default="scope">{{ feeLabels[scope.row.feeType] || scope.row.feeType }}</template></el-table-column><el-table-column prop="currency" label="币种" width="100" />
<el-table-column prop="amount" label="金额" min-width="140" align="right" /><el-table-column label="费用状态" min-width="165"><template #default="scope">{{ feeStatus(scope.row as DeliveryFeeRow) }}</template></el-table-column><el-table-column prop="remark" label="备注" min-width="240" />
<el-table-column label="费用类型" min-width="180"><template #default="scope">{{ feeLabels[scope.row.feeType] || scope.row.feeType }}</template></el-table-column><el-table-column label="费用状态" min-width="165"><template #default="scope">{{ feeStatus(scope.row as DeliveryFeeRow) }}</template></el-table-column>
<el-table-column prop="currency" label="币种" width="100" /><el-table-column prop="amount" label="金额" min-width="140" align="right" /><el-table-column prop="remark" label="备注" min-width="240" />
</el-table>
</section>
</el-tab-pane>
@@ -51,6 +51,7 @@ const fields = computed<QueryField[]>(() => [
const columns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 300, fixed: 'left', slotName: 'actions' },
{ key: 'deliveryNo', prop: 'deliveryNo', label: '派送单号', minWidth: 230, fixed: 'left' },
{ key: 'status', prop: 'status', label: '订单状态', width: 110, slotName: 'status', afterColumn: 'deliveryNo' },
{ key: 'documents', label: '所需资料', minWidth: 380, slotName: 'documents' },
{ key: 'bolNo', prop: 'bolNo', label: 'BOL号', minWidth: 170 },
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 160 },
@@ -58,7 +59,6 @@ const columns: DataTableColumn[] = [
{ key: 'overseasWarehouseName', prop: 'overseasWarehouseName', label: '仓库', minWidth: 150 },
{ key: 'dispatchNo', prop: 'dispatchNo', label: '排车单号', minWidth: 170 },
{ key: 'waybillNo', prop: 'waybillNo', label: '运单号', minWidth: 150 },
{ key: 'status', prop: 'status', label: '订单状态', width: 110, slotName: 'status' },
{ key: 'deliveryMethod', prop: 'deliveryMethod', label: '派送方式', width: 120, formatter: (row: DeliveryOrderRow) => deliveryMethodLabel(row.deliveryMethod) },
{ key: 'carrierName', prop: 'carrierName', label: '派送人', minWidth: 150 },
{ key: 'vehiclePlate', prop: 'vehiclePlate', label: '车牌', minWidth: 120 },
@@ -75,18 +75,18 @@ const columns = computed<DataTableColumn[]>(() => [
{ key: 'actions', label: '操作', width: rows.value.some(row => row.taskType === 'PICKUP') ? 330 : 220, fixed: 'left', slotName: 'actions' },
{ key: 'taskType', label: '排车类型', width: 115, slotName: 'taskType' },
{ key: 'dispatchNo', prop: 'dispatchNo', label: '排车单号', minWidth: 190 },
{ key: 'vehicleStatus', label: '车辆状态', minWidth: 110, slotName: 'vehicleStatus', afterColumn: 'dispatchNo' }, { key: 'stage', label: '作业阶段', minWidth: 110, slotName: 'stage', afterColumn: 'dispatchNo' },
{ key: 'createdAt', label: '创建时间', minWidth: 170, slotName: 'createdAt' },
{ key: 'vehicle', label: '车牌号', minWidth: 130, slotName: 'vehicle' }, { key: 'driver', label: '司机', minWidth: 100, slotName: 'driver' },
{ key: 'driverPhone', label: '联系电话', minWidth: 140, slotName: 'driverPhone' },
{ key: 'appointmentNo', label: '预约号', minWidth: 160, slotName: 'appointmentNo' },
{ key: 'appointmentAt', label: '确认送达时间', minWidth: 170, slotName: 'appointmentAt' },
{ key: 'shipmentNumbers', label: '货件号', minWidth: 180, slotName: 'shipmentNumbers' },
{ key: 'vehicleStatus', label: '车辆状态', minWidth: 110, slotName: 'vehicleStatus' },
{ key: 'warehouseName', prop: 'warehouseName', label: '起运仓', minWidth: 160 },
...(showSections.value && section.value === 'VEHICLE_INFO' ? [{ key: 'returnReason', label: '退回原因', minWidth: 240, slotName: 'returnReason' }] : []),
...(showSections.value && section.value !== 'VEHICLE_INFO' ? [{ key: 'review', label: section.value === 'DISPATCH_REVIEW' ? '调度审核' : '理货员审核', minWidth: 210, slotName: 'review' }] : []),
...(showSections.value && section.value !== 'VEHICLE_INFO' ? [{ key: 'review', label: section.value === 'DISPATCH_REVIEW' ? '调度审核' : '理货员审核', minWidth: 210, slotName: 'review', afterColumn: 'dispatchNo' }] : []),
{ key: 'counts', label: '托数/箱数/已拣', minWidth: 160, slotName: 'counts' }, { key: 'planned', label: '计划发车', minWidth: 165, slotName: 'planned' },
{ key: 'departure', label: '实际发车', minWidth: 165, slotName: 'departure' }, { key: 'stage', label: '作业阶段', minWidth: 110, slotName: 'stage' },
{ key: 'departure', label: '实际发车', minWidth: 165, slotName: 'departure' },
...(stage.value === 'DELIVERING' ? [{ key: 'routeMap', label: '路线', width: 100, slotName: 'routeMap' }] : []),
])
const preferenceKey = computed(() => `overseas-dispatches-${stage.value}-${section.value}`)
@@ -1,9 +1,11 @@
import ElementPlus from 'element-plus'
import { computed } from 'vue'
import { flushPromises, mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createExceptionOrder, listExceptionOrders, resolveExceptionRelation, getExceptionDetail } from '../api/exceptionOrders'
import ExceptionOrdersView from './ExceptionOrdersView.vue'
import { commonPhase5Stubs } from './phase5TestStubs'
import { UI_PREFERENCE_SCOPE, preferenceStorageKey } from '@/shared/utils/uiLayoutPreference'
const canPermission = vi.hoisted(() => vi.fn((_code: string) => false))
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: canPermission }) }))
@@ -13,6 +15,7 @@ function mountView() { return mount(ExceptionOrdersView, { global: { plugins: [E
describe('ExceptionOrdersView permissions and creation', () => {
beforeEach(() => {
localStorage.clear()
canPermission.mockReset().mockReturnValue(false)
vi.mocked(getExceptionDetail).mockReset().mockResolvedValue({ order: { id: '19', exceptionNo: 'EXC-1', customerId: '100', customerName: '示例客户',
exceptionType: 'DAMAGED', status: 'PROCESSING', reason: '包装破损', handlerName: '1000001', version: 1, allowedActions: ['COMPLETE_EXCEPTION'], exceptionAt: '', updatedAt: '' }, returnSources: [], returnReceipts: [] })
@@ -21,6 +24,30 @@ describe('ExceptionOrdersView permissions and creation', () => {
vi.mocked(createExceptionOrder).mockReset().mockResolvedValue({ id: '19', exceptionNo: 'EXC-1', status: 'PENDING' })
})
it('places the processing status after the exception number when old column settings exist', async () => {
localStorage.setItem(preferenceStorageKey('table', 'exception-user', 'overseas-exception-orders'), JSON.stringify({
version: 1,
order: ['actions', 'exceptionNo', 'related', 'customerName', 'reason', 'status'],
hidden: ['reason'],
widths: { status: 135 },
}))
const wrapper = mount(ExceptionOrdersView, { global: {
plugins: [ElementPlus],
provide: { [UI_PREFERENCE_SCOPE as symbol]: computed(() => 'exception-user') },
stubs: {
...commonPhase5Stubs,
DataTable: false,
ElTable: { template: '<div><slot /></div>' },
ElTableColumn: { props: ['label', 'width'], template: '<span class="column-title">{{ label }}</span>' },
},
} })
await flushPromises()
expect(wrapper.findAll('.column-title').slice(0, 3).map(column => column.text()))
.toEqual(['操作', '异常订单号', '处理状态'])
expect(wrapper.findAll('.column-title').some(column => column.text() === '异常原因')).toBe(false)
wrapper.unmount()
})
it('hides creation without the independent permission', async () => {
const wrapper = mountView()
await flushPromises()
@@ -31,6 +31,7 @@ const fields: QueryField[] = [
const columns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 100, fixed: 'left', slotName: 'actions' },
{ key: 'exceptionNo', prop: 'exceptionNo', label: '异常订单号', minWidth: 180, fixed: 'left' },
{ key: 'status', label: '处理状态', width: 100, slotName: 'status', afterColumn: 'exceptionNo' },
{ key: 'related', label: '关联订单号', minWidth: 230, slotName: 'related' },
{ key: 'exceptionType', label: '异常类型', width: 100, formatter: (row: ExceptionOrderRow) => typeLabels[row.exceptionType] || row.exceptionType },
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 160 },
@@ -39,7 +40,6 @@ const columns: DataTableColumn[] = [
{ key: 'skuCode', prop: 'skuCode', label: 'SKU', minWidth: 120 },
{ key: 'packageCount', prop: 'packageCount', label: '箱数', width: 80 },
{ key: 'exceptionAt', label: '异常时间', minWidth: 170, formatter: (row: ExceptionOrderRow) => formatOverseasDateTime(row.exceptionAt) },
{ key: 'status', label: '处理状态', width: 100, slotName: 'status' },
{ key: 'handlingAction', prop: 'handlingAction', label: '处理动作', minWidth: 140 },
{ key: 'returnLogisticsNo', prop: 'returnLogisticsNo', label: '退回物流单号', minWidth: 160 },
{ key: 'handlerName', prop: 'handlerName', label: '处理人', minWidth: 110, slotName: 'handlerName' },
@@ -50,11 +50,11 @@ const palletColumns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 100, fixed: 'left', slotName: 'actions' },
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 150 },
{ key: 'palletNo', prop: 'palletNo', label: '托盘号', minWidth: 150 },
{ key: 'putawayStatus', label: '上架状态', width: 110, prop: 'putawayStatus', afterColumn: 'palletNo' },
{ key: 'palletOwner', prop: 'palletOwner', label: '托盘所属计划', minWidth: 190 },
{ key: 'packageCount', prop: 'packageCount', label: '对应箱数', width: 110, align: 'right' },
{ key: 'location', label: '当前库位', minWidth: 150, slotName: 'location' },
{ key: 'locationType', label: '库位类型', width: 120, prop: 'locationType' },
{ key: 'putawayStatus', label: '上架状态', width: 110, prop: 'putawayStatus' },
]
const palletRows = computed(() => (detail.value?.allocations || []).map((allocation, index) => {
const line = detail.value?.lines.find(item => item.id === allocation.lineId)
@@ -81,6 +81,10 @@ const columns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 300, fixed: 'left', slotName: 'actions' },
{ key: 'selection', type: 'selection', width: 48, fixed: 'left', selectable: selectableRow },
{ key: 'orderNo', prop: 'orderNo', label: '入库单号', minWidth: 170 },
{ key: 'status', prop: 'status', label: '状态', width: 100, afterColumn: 'orderNo' },
{ key: 'appointmentStatus', prop: 'appointmentStatus', label: 'FBA预约', width: 110, afterColumn: 'orderNo' },
{ key: 'dispatchStatus', prop: 'dispatchStatus', label: '排车状态', width: 110, afterColumn: 'orderNo' },
{ key: 'assigned', prop: 'assigned', label: '分配', width: 82, align: 'center', afterColumn: 'orderNo' },
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 140 },
{ key: 'lxCode', prop: 'lxCode', label: 'LX代码', minWidth: 150 },
{ key: 'type', prop: 'type', label: '类型', width: 100 },
@@ -93,12 +97,8 @@ const columns: DataTableColumn[] = [
{ key: 'netWeight', prop: 'netWeight', label: '货物净重(kg)', width: 120, align: 'right' },
{ key: 'volume', prop: 'volume', label: '计费体积(m³)', width: 125, align: 'right' },
{ key: 'pallets', prop: 'pallets', label: '总托盘数', width: 100, align: 'right' },
{ key: 'assigned', prop: 'assigned', label: '分配', width: 82, align: 'center' },
{ key: 'mergeAllowed', prop: 'mergeAllowed', label: '合托', width: 96, align: 'center' },
{ key: 'urgent', prop: 'urgent', label: '加急', width: 82, align: 'center' },
{ key: 'status', prop: 'status', label: '状态', width: 100 },
{ key: 'appointmentStatus', prop: 'appointmentStatus', label: 'FBA预约', width: 110 },
{ key: 'dispatchStatus', prop: 'dispatchStatus', label: '排车状态', width: 110 },
{ key: 'createdAt', prop: 'createdAt', label: '创建时间', minWidth: 170 },
]
@@ -24,7 +24,7 @@ const productFields = ['客户', '海外仓', 'SKU编码', '产品名称', '更
const locationRows = computed(() => positions.value.map((item) => ({ id: String(item.id), location: text(item.warehouseLocationCode), status: text(item.status), palletNo: text(item.palletNo), cartons: Number(item.totalPackages || 0), quantity: Number(item.totalPackages || 0), available: Number(item.availablePackages || 0), inboundDate: formatOverseasDateTime(item.updatedAt), ageDays: '—' })))
const palletRows = computed(() => positions.value.map((item) => ({ id: String(item.id), palletNo: text(item.palletNo), cartons: Number(item.totalPackages || 0), cartonRange: '—', location: text(item.warehouseLocationCode), quantity: Number(item.totalPackages || 0) })))
const ledgerRows = computed(() => ledger.value.map((item) => ({ id: String(item.id), occurredAt: formatOverseasDateTime(item.occurredAt), businessType: inventoryBusinessTypeLabel(item.businessType), businessOrderNo: text(item.businessOrderNo), inboundOrderNo: text(item.inboundOrderNo), outboundOrderNo: text(item.outboundOrderNo), location: text(item.warehouseLocationCode), inboundQuantity: item.deltaPackages > 0 ? item.deltaPackages : 0, outboundQuantity: item.deltaPackages < 0 ? Math.abs(item.deltaPackages) : 0, balance: Number(item.afterPackages || 0), operator: '—', remark: text(item.remark) })))
const locationColumns: DataTableColumn[] = [{ key: 'location', prop: 'location', label: '库位', minWidth: 130 }, { key: 'status', prop: 'status', label: '库存状态', width: 120 }, { key: 'palletNo', prop: 'palletNo', label: '托盘号', minWidth: 150 }, { key: 'cartons', prop: 'cartons', label: '箱数', width: 90, align: 'right' }, { key: 'quantity', prop: 'quantity', label: '库存数量', width: 110, align: 'right' }, { key: 'available', prop: 'available', label: '可用数量', width: 110, align: 'right' }, { key: 'inboundDate', prop: 'inboundDate', label: '最近更新', width: 160 }, { key: 'ageDays', prop: 'ageDays', label: '库龄(天)', width: 100, align: 'right' }]
const locationColumns: DataTableColumn[] = [{ key: 'location', prop: 'location', label: '库位', minWidth: 130 }, { key: 'palletNo', prop: 'palletNo', label: '托盘号', minWidth: 150 }, { key: 'status', prop: 'status', label: '库存状态', width: 120, afterColumn: 'palletNo' }, { key: 'cartons', prop: 'cartons', label: '箱数', width: 90, align: 'right' }, { key: 'quantity', prop: 'quantity', label: '库存数量', width: 110, align: 'right' }, { key: 'available', prop: 'available', label: '可用数量', width: 110, align: 'right' }, { key: 'inboundDate', prop: 'inboundDate', label: '最近更新', width: 160 }, { key: 'ageDays', prop: 'ageDays', label: '库龄(天)', width: 100, align: 'right' }]
const palletColumns: DataTableColumn[] = [{ key: 'palletNo', prop: 'palletNo', label: '托盘号', minWidth: 150 }, { key: 'cartons', prop: 'cartons', label: '关联箱数', width: 110, align: 'right' }, { key: 'cartonRange', prop: 'cartonRange', label: '箱号范围', minWidth: 200 }, { key: 'location', prop: 'location', label: '库位', minWidth: 130 }, { key: 'quantity', prop: 'quantity', label: '库存数量', width: 110, align: 'right' }]
const ledgerColumns: DataTableColumn[] = [{ key: 'occurredAt', prop: 'occurredAt', label: '发生时间', minWidth: 160 }, { key: 'businessType', prop: 'businessType', label: '业务类型', width: 120 }, { key: 'businessOrderNo', prop: 'businessOrderNo', label: '业务订单号', minWidth: 170 }, { key: 'inboundOrderNo', prop: 'inboundOrderNo', label: '入库单号', minWidth: 160 }, { key: 'outboundOrderNo', prop: 'outboundOrderNo', label: '出库单号', minWidth: 160 }, { key: 'location', prop: 'location', label: '库位', minWidth: 120 }, { key: 'inboundQuantity', prop: 'inboundQuantity', label: '入库数量', width: 110, align: 'right' }, { key: 'outboundQuantity', prop: 'outboundQuantity', label: '出库数量', width: 110, align: 'right' }, { key: 'balance', prop: 'balance', label: '结存', width: 100, align: 'right' }, { key: 'operator', prop: 'operator', label: '操作人', minWidth: 120 }, { key: 'remark', prop: 'remark', label: '备注', minWidth: 180 }]
async function load() { if (!inventoryId.value) { loadError.value = { message: '详情缺少库存记录编号,请从库存列表重新进入' }; return }; loading.value = true; loadError.value = null; try { const inboundOrderNo = String(route.query.inboundOrderNo || ''); const inboundCode = String(route.query.inboundCode || ''); const skuCode = String(route.query.skuCode || ''); const overseasWarehouseId = String(route.query.overseasWarehouseId || ''); const result = await listInventoryBalances({ page: 1, size: 200, inboundOrderNo: inboundOrderNo || undefined, inboundCode: inboundCode || undefined, skuCode: skuCode || undefined, overseasWarehouseId: overseasWarehouseId || undefined }); balance.value = result.records.find((item) => String(item.id) === inventoryId.value) || null; if (!balance.value) throw new Error('Inventory balance not found'); const [positionRows, ledgerPage] = await Promise.all([listInventoryPositions(inventoryId.value), listInventoryLedger({ page: 1, size: 200, inboundOrderNo: balance.value.inboundOrderNo || undefined, inboundCode: balance.value.inboundCode || undefined })]); positions.value = positionRows; ledger.value = ledgerPage.records } catch (error) { loadError.value = getApiErrorDetails(error, '库存详情加载失败,请重试') } finally { loading.value = false } }
@@ -243,9 +243,9 @@ const cargoColumns = computed<DataTableColumn[]>(() => isReturnOrder.value ? [
{ key: 'quantity', prop: 'quantity', label: '件数', width: 100, align: 'right' }, { key: 'weight', prop: 'weight', label: '重量(kg)', width: 120, align: 'right' },
{ key: 'volume', prop: 'volume', label: '体积(m³)', width: 120, align: 'right' },
])
const documentColumns: DataTableColumn[] = [
{ key: 'status', prop: 'status', label: '状态', minWidth: 130 }, { key: 'quantity', prop: 'quantity', label: '换标数量(箱/件)', minWidth: 150, formatter: (row: { quantity?: number; quantityUnit?: string }) => `${row.quantity ?? '—'}${row.quantityUnit || '件'}` },
const documentColumns: DataTableColumn[] = [ { key: 'quantity', prop: 'quantity', label: '换标数量(箱/件)', minWidth: 150, formatter: (row: { quantity?: number; quantityUnit?: string }) => `${row.quantity ?? '—'}${row.quantityUnit || '件'}` },
{ key: 'type', prop: 'type', label: '单据类型', minWidth: 150 }, { key: 'number', prop: 'number', label: '单据编号', minWidth: 200 },
{ key: 'status', prop: 'status', label: '状态', minWidth: 130, afterColumn: 'number' },
{ key: 'createdAt', prop: 'createdAt', label: '生成时间', minWidth: 170 }, { key: 'quote', prop: 'quote', label: '报价', minWidth: 150 },
]
const logColumns: DataTableColumn[] = [
@@ -293,6 +293,11 @@ const columns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 330, fixed: 'left', slotName: 'actions' },
{ key: 'selection', type: 'selection', width: 48, fixed: 'left' },
{ key: 'orderNo', prop: 'orderNo', label: '尾程单号', minWidth: 170, slotName: 'orderNo' },
{ key: 'dispatchStatus', prop: 'dispatchStatus', label: '排车状态', width: 110, afterColumn: 'orderNo' },
{ key: 'relabelStatus', slotName: 'relabelStatus', label: '换标子状态', minWidth: 180, afterColumn: 'orderNo' },
{ key: 'status', prop: 'status', label: '当前状态', width: 110, afterColumn: 'orderNo' },
{ key: 'abnormal', prop: 'abnormal', label: '是否异常', width: 100, afterColumn: 'orderNo' },
{ key: 'held', prop: 'held', label: '是否暂扣', width: 100, afterColumn: 'orderNo' },
{ key: 'businessSource', prop: 'businessSource', label: '业务来源', minWidth: 220 },
{ key: 'businessOrderNo', prop: 'businessOrderNo', label: '关联商业单号', minWidth: 230, slotName: 'businessOrderNo' },
{ key: 'externalBookingNo', prop: 'externalBookingNo', label: '外接预订号', minWidth: 220 },
@@ -308,8 +313,6 @@ const columns: DataTableColumn[] = [
{ key: 'transferWarehouse', prop: 'transferWarehouse', label: '转仓', minWidth: 140 },
{ key: 'fbaWarehouse', prop: 'fbaWarehouse', label: 'FBA仓库', minWidth: 130 },
{ key: 'containerNo', prop: 'containerNo', label: '集装箱号', minWidth: 150 },
{ key: 'abnormal', prop: 'abnormal', label: '是否异常', width: 100 },
{ key: 'held', prop: 'held', label: '是否暂扣', width: 100 },
{ key: 'pallets', prop: 'pallets', label: '托数(退货实收)', width: 130, align: 'right', slotName: 'receiptPallets' },
{ key: 'cartons', prop: 'cartons', label: '箱数(退货实收)', width: 130, align: 'right', slotName: 'receiptBoxes' },
{ key: 'returnParcels', label: '退货实收包裹', width: 120, align: 'right', slotName: 'returnParcels' },
@@ -326,12 +329,9 @@ const columns: DataTableColumn[] = [
{ key: 'portAta', prop: 'portAta', label: '实际到港时间', minWidth: 150 },
{ key: 'warehouseEta', prop: 'warehouseEta', label: '预计到仓', minWidth: 140 },
{ key: 'inboundOrderNo', prop: 'inboundOrderNo', label: '入库单号', minWidth: 150 },
{ key: 'dispatchStatus', prop: 'dispatchStatus', label: '排车状态', width: 110 },
{ key: 'outboundOrderNo', prop: 'outboundOrderNo', label: '出库单号', minWidth: 150 },
{ key: 'dispatchOrderNo', prop: 'dispatchOrderNo', label: '派送单号', minWidth: 150 },
{ key: 'relabelOrderNo', slotName: 'relabelOrderNo', label: '换标单号', minWidth: 220 },
{ key: 'relabelStatus', slotName: 'relabelStatus', label: '换标子状态', minWidth: 180 },
{ key: 'status', prop: 'status', label: '当前状态', width: 110 },
{ key: 'createdAt', prop: 'createdAt', label: '创建时间', minWidth: 170 },
{ key: 'internalRemark', prop: 'internalRemark', label: '备注(仅自己)', minWidth: 160 },
{ key: 'customerRemark', prop: 'customerRemark', label: '备注(客户)', minWidth: 160 },
@@ -339,10 +339,10 @@ const columns: DataTableColumn[] = [
]
const containerDetailColumns: DataTableColumn[] = [
{ key: 'orderNo', prop: 'orderNo', label: '尾程单号', minWidth: 170 },
{ key: 'orderStatus', prop: 'orderStatus', label: '订单状态', width: 130, slotName: 'orderStatus', afterColumn: 'orderNo' },
{ key: 'planStatus', prop: 'planStatus', label: '计划状态', width: 130, slotName: 'planStatus', afterColumn: 'orderNo' },
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 150 },
{ key: 'orderStatus', prop: 'orderStatus', label: '订单状态', width: 130, slotName: 'orderStatus' },
{ key: 'planNo', prop: 'planNo', label: '独立入库计划', minWidth: 170 },
{ key: 'planStatus', prop: 'planStatus', label: '计划状态', width: 130, slotName: 'planStatus' },
{ key: 'plannedArrivalAt', prop: 'plannedArrivalAt', label: '计划到仓时间', minWidth: 170, slotName: 'plannedArrivalAt' },
]
@@ -33,7 +33,7 @@ const queryFields = computed<QueryField[]>(() => [
{ key: 'locationType', type: 'select', label: '类型', width: 150, options: [{ label: '普通库位', value: 'NORMAL' }, { label: 'A0共享溢出位', value: 'SHARED_OVERFLOW' }, { label: '虚拟收货位', value: 'VIRTUAL' }] },
{ key: 'status', type: 'select', label: '状态', width: 140, options: [{ label: '可用', value: 'ACTIVE' }, { label: '空闲', value: 'IDLE' }, { label: '已占用', value: 'OCCUPIED' }, { label: '已停用', value: 'INACTIVE' }] },
])
const columns: DataTableColumn[] = [{ key: 'actions', label: '操作', width: 220, fixed: 'left', slotName: 'actions' }, { key: 'locationCode', prop: 'locationCode', label: '库位编码', minWidth: 180 }, { key: 'warehouse', label: '海外仓', minWidth: 190, slotName: 'warehouse' }, { key: 'locationType', label: '类型', width: 130, slotName: 'locationType' }, { key: 'status', label: '状态', width: 110, slotName: 'status' }, { key: 'occupiedPalletCount', prop: 'occupiedPalletCount', label: '托盘数', width: 100, align: 'right' }, { key: 'remark', prop: 'remark', label: '备注', minWidth: 180 }]
const columns: DataTableColumn[] = [{ key: 'actions', label: '操作', width: 220, fixed: 'left', slotName: 'actions' }, { key: 'locationCode', prop: 'locationCode', label: '库位编码', minWidth: 180 }, { key: 'status', label: '状态', width: 110, slotName: 'status', afterColumn: 'locationCode' }, { key: 'warehouse', label: '海外仓', minWidth: 190, slotName: 'warehouse' }, { key: 'locationType', label: '类型', width: 130, slotName: 'locationType' }, { key: 'occupiedPalletCount', prop: 'occupiedPalletCount', label: '托盘数', width: 100, align: 'right' }, { key: 'remark', prop: 'remark', label: '备注', minWidth: 180 }]
const typeLabels: Record<OverseasLocationType, string> = { VIRTUAL: '虚拟收货位', NORMAL: '普通库位', SHARED_OVERFLOW: 'A0共享溢出位' }; const statusLabels: Record<OverseasLocationStatus, string> = { ACTIVE: '可用', IDLE: '空闲', OCCUPIED: '已占用', INACTIVE: '已停用', DISABLED: '已停用' }
const selectedWarehouse = computed(() => warehouses.value.find(item => String(item.id) === String(form.warehouseId)))
const warehouseNameById = computed(() => new Map(warehouses.value.map(item => [String(item.id), item.warehouseName])))
@@ -46,12 +46,12 @@ const editForm = reactive({ skuCode: '', lengthCm: 0, widthCm: 0, heightCm: 0, w
const columns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 190, fixed: 'left', slotName: 'actions' },
{ key: 'sourceCode', label: '上传标识', minWidth: 180, showOverflowTooltip: true, slotName: 'sourceCode' },
{ key: 'syncStatus', label: '同步状态', width: 120, slotName: 'syncStatus', afterColumn: 'sourceCode' },
{ key: 'kind', label: '类型', width: 110, slotName: 'kind' },
{ key: 'inboundCode', prop: 'inboundCode', label: '命中入仓码', minWidth: 150, showOverflowTooltip: true },
{ key: 'image', label: '测量图片', width: 110, slotName: 'image' },
{ key: 'dimensions', label: '长 × 宽 × 高(cm)', width: 170, slotName: 'dimensions' },
{ key: 'weightKg', prop: 'weightKg', label: '重量(kg)', width: 110 },
{ key: 'syncStatus', label: '同步状态', width: 120, slotName: 'syncStatus' },
{ key: 'match', label: '领星 SKU', minWidth: 150, slotName: 'match' },
{ key: 'measuredAt', label: '测量时间', width: 170, slotName: 'measuredAt' },
{ key: 'failure', label: '失败原因', minWidth: 220, slotName: 'failure' },
@@ -246,8 +246,8 @@ onMounted(() => { void Promise.all([loadConfig(), load()]) })
<div class="connection-result__sku">查询 SKU:<strong>{{ testResult.skuCode }}</strong></div>
<el-table :data="testResult.warehouses" border>
<el-table-column prop="whCode" label="仓库" width="90" />
<el-table-column label="结果" width="110"><template #default="{ row }"><el-tag :type="row.matched ? 'success' : 'danger'">{{ row.matched ? '已命中' : '未命中' }}</el-tag></template></el-table-column>
<el-table-column label="维护状态" width="110"><template #default="{ row }">{{ row.matched ? (row.maintained ? '已维护' : '待维护') : '—' }}</template></el-table-column>
<el-table-column label="结果" width="110"><template #default="{ row }"><el-tag :type="row.matched ? 'success' : 'danger'">{{ row.matched ? '已命中' : '未命中' }}</el-tag></template></el-table-column>
<el-table-column label="领星数据" min-width="220"><template #default="{ row }">{{ row.matched ? `${row.length ?? 0} × ${row.width ?? 0} × ${row.height ?? 0} cm / ${row.weight ?? 0} kg` : row.message }}</template></el-table-column>
</el-table>
</div>
@@ -27,6 +27,7 @@ const warehouseName = ref('')
const statusLabels: Record<string, string> = { WAIT_PICK: '待拣货', WAIT_OUTBOUND: '待出库', PICKING: '拣货中', PICKED: '已拣货', OUTBOUNDED: '已出库', CANCELLED: '已取消' }
const palletColumns: DataTableColumn[] = [
{ key: 'palletNo', label: '托盘码', width: 220 },
{ key: 'status', label: '状态', width: 110, afterColumn: 'palletNo' },
{ key: 'inboundCode', label: '入仓码', width: 190 },
{ key: 'destinationWarehouseCode', label: '目的仓编码', width: 150 },
{ key: 'destinationWarehouseName', label: '目的仓名称', minWidth: 180 },
@@ -37,7 +38,6 @@ const palletColumns: DataTableColumn[] = [
{ key: 'volumeWeightKg', label: '材积重(kg)', width: 130, align: 'right' },
{ key: 'volumeCbm', label: '体积(m³)', width: 120, align: 'right' },
{ key: 'pickedPackageCount', label: '已拣箱数', width: 110, align: 'right' },
{ key: 'status', label: '状态', width: 110 },
]
function show(value: unknown) { const text = String(value ?? '').trim(); return text || '—' }
// A warehouse ID is only a join key; never render it as a warehouse name.
@@ -103,7 +103,7 @@ const allPageSelected = computed(() => pagedDropshipCandidates.value.length > 0
const somePageSelected = computed(() => selectedPageCount.value > 0 && !allPageSelected.value)
const selectedDropshipCustomerCount = computed(() => new Set(dropshipCandidates.value.filter(item => selectedDropshipKeys.value.has(dropshipCandidateKey(item))).map(item => item.customerId).filter(Boolean)).size)
const oneClickMarks = computed(() => typeof cartonMarks.value === 'string' ? cartonMarks.value.split(/[,\n,]+/).map(value => value.trim()).filter(Boolean) : [])
const columns: DataTableColumn[] = [{ key: 'actions', label: '操作', width: 180, fixed: 'left', slotName: 'actions' }, { key: 'outboundNo', prop: 'outboundNo', label: '出库单号', minWidth: 180 }, { key: 'outboundType', prop: 'outboundType', label: '出库类型', width: 150, slotName: 'outboundType' }, { key: 'lxCodes', prop: 'lxCodes', label: 'LX代码', minWidth: 180 }, { key: 'dispatchNo', prop: 'dispatchNo', label: '关联排车单', minWidth: 160 }, { key: 'status', prop: 'status', label: '状态', width: 130, slotName: 'status' }, { key: 'palletCount', prop: 'palletCount', label: '托盘数', width: 100 }, { key: 'packageCount', prop: 'packageCount', label: '箱数', width: 100 }, { key: 'grossWeightKg', prop: 'grossWeightKg', label: '毛重(kg)', width: 120, align: 'right' }, { key: 'volumeWeightKg', prop: 'volumeWeightKg', label: '材积重(kg)', width: 130, align: 'right' }, { key: 'volumeCbm', prop: 'volumeCbm', label: '体积(m³)', width: 120, align: 'right' }, { key: 'createdAt', prop: 'createdAt', label: '创建时间', minWidth: 180 }]
const columns: DataTableColumn[] = [{ key: 'actions', label: '操作', width: 180, fixed: 'left', slotName: 'actions' }, { key: 'outboundNo', prop: 'outboundNo', label: '出库单号', minWidth: 180 }, { key: 'status', prop: 'status', label: '状态', width: 130, slotName: 'status', afterColumn: 'outboundNo' }, { key: 'outboundType', prop: 'outboundType', label: '出库类型', width: 150, slotName: 'outboundType' }, { key: 'lxCodes', prop: 'lxCodes', label: 'LX代码', minWidth: 180 }, { key: 'dispatchNo', prop: 'dispatchNo', label: '关联排车单', minWidth: 160 }, { key: 'palletCount', prop: 'palletCount', label: '托盘数', width: 100 }, { key: 'packageCount', prop: 'packageCount', label: '箱数', width: 100 }, { key: 'grossWeightKg', prop: 'grossWeightKg', label: '毛重(kg)', width: 120, align: 'right' }, { key: 'volumeWeightKg', prop: 'volumeWeightKg', label: '材积重(kg)', width: 130, align: 'right' }, { key: 'volumeCbm', prop: 'volumeCbm', label: '体积(m³)', width: 120, align: 'right' }, { key: 'createdAt', prop: 'createdAt', label: '创建时间', minWidth: 180 }]
const fields: QueryField[] = [{ key: 'documentNo', type: 'input', label: '出库单号', placeholder: '请输入出库单号', width: 220 }, { key: 'lxCode', type: 'input', label: 'LX代码', placeholder: '请输入领星入库单代码', width: 220 }, { key: 'warehouseId', type: 'input', label: '目的仓', placeholder: '请输入目的仓', width: 220 }, { key: 'status', type: 'select', label: '状态', placeholder: '全部状态', width: 160, options: [{ label: '全部', value: '' }, { label: '待拣货', value: 'WAIT_PICK' }, { label: '拣货中', value: 'PICKING' }, { label: '已拣货', value: 'PICKED' }, { label: '待出库', value: 'WAIT_OUTBOUND' }, { label: '已出库', value: 'OUTBOUNDED' }, { label: '已取消', value: 'CANCELLED' }] }, { key: 'outboundType', type: 'select', label: '出库类型', placeholder: '全部出库类型', width: 170, options: [{ label: '全部', value: '' }, { label: '仓储转运出库', value: 'WAREHOUSE_TRANSFER_OUTBOUND' }, { label: '一件代发出库', value: 'ONE_CLICK_DROPSHIP' }, { label: '退货出库', value: 'RETURN_OUTBOUND' }, { label: '派送出库', value: 'DISPATCH_DELIVERY' }, { label: '废弃出库', value: 'DISCARD_OUTBOUND' }, { label: '贴标出库', value: 'RELABEL_OUTBOUND' }] }]
async function load(model: QueryModel = filters.value) { Object.assign(filters.value, model); loading.value = true; error.value = null; try { const result = await listOutbounds({ page: page.value, size: pageSize.value, documentNo: String(filters.value.documentNo || '').trim() || undefined, lxCode: String(filters.value.lxCode || '').trim() || undefined, warehouseId: String(filters.value.warehouseId || '').trim() || undefined, status: String(filters.value.status || '').trim() || undefined, outboundType: String(filters.value.outboundType || '').trim() || undefined }); rows.value = result.records.map((row) => ({ ...row, createdAt: formatOverseasDateTime(row.createdAt) })); total.value = result.total } catch (cause) { error.value = getApiErrorDetails(cause, '出库单列表加载失败') } finally { loading.value = false } }
@@ -45,10 +45,10 @@ const fields = computed<QueryField[]>(() => [
])
const columns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 180, fixed: 'left', slotName: 'actions' },
{ key: 'code', prop: 'code', label: '托盘编码', minWidth: 160 }, { key: 'type', prop: 'type', label: '托盘类型', minWidth: 130 },
{ key: 'code', prop: 'code', label: '托盘编码', minWidth: 160 },
{ key: 'status', prop: 'status', label: '状态', width: 100, afterColumn: 'code' }, { key: 'type', prop: 'type', label: '托盘类型', minWidth: 130 },
{ key: 'warehouse', prop: 'warehouse', label: '仓库', minWidth: 180 }, { key: 'volume', prop: 'volume', label: '容积(m³)', width: 110, align: 'right' },
{ key: 'purchasePrice', prop: 'purchasePrice', label: '采购价格', width: 120, align: 'right' }, { key: 'fbaWarehouse', prop: 'fbaWarehouse', label: '所属FBA仓', minWidth: 140 },
{ key: 'status', prop: 'status', label: '状态', width: 100 }, { key: 'remark', prop: 'remark', label: '备注', minWidth: 180 },
{ key: 'purchasePrice', prop: 'purchasePrice', label: '采购价格', width: 120, align: 'right' }, { key: 'fbaWarehouse', prop: 'fbaWarehouse', label: '所属FBA仓', minWidth: 140 }, { key: 'remark', prop: 'remark', label: '备注', minWidth: 180 },
]
const dialogOpen = ref(false)
const dialogMode = ref<'create' | 'edit'>('create')
@@ -49,6 +49,8 @@ const columns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 180, fixed: 'left', slotName: 'actions' },
{ key: 'selection', type: 'selection', width: 48, fixed: 'left' },
{ key: 'orderNo', label: '换标单号', minWidth: 170 },
{ key: 'walletStatus', label: '资金状态', width: 120, afterColumn: 'orderNo' },
{ key: 'status', label: '状态', width: 110, afterColumn: 'orderNo' },
{ key: 'sku', label: '商品SKU', minWidth: 150 },
{ key: 'productName', label: '中文品名', minWidth: 180 },
{ key: 'cartons', label: '箱数', width: 90, align: 'right' },
@@ -56,10 +58,8 @@ const columns: DataTableColumn[] = [
{ key: 'skuCount', label: 'SKU种数', width: 100, align: 'right' },
{ key: 'budgetTotal', label: '预算总价', width: 120, align: 'right' },
{ key: 'relabelCost', label: '换标费用', width: 120, align: 'right' },
{ key: 'walletStatus', label: '资金状态', width: 120 },
{ key: 'labelRequirement', label: '贴标要求', minWidth: 150 },
{ key: 'fileName', label: '上传文件', minWidth: 160 },
{ key: 'status', label: '状态', width: 110 },
{ key: 'createdAt', label: '创建时间', minWidth: 170 },
]
const dialogOpen = ref(false)
@@ -533,6 +533,7 @@ const cartonColumns: DataTableColumn[] = [
slotName: "carton-0",
},
{ key: "cartonMark", label: "箱号", prop: "cartonMark", minWidth: 220 },
{ key: "carton-9", label: "状态", width: 100, slotName: "carton-9", afterColumn: 'cartonMark' },
{ key: "carton-2", label: "客户", minWidth: 180, slotName: "carton-2" },
{ key: "carton-3", label: "海外仓", minWidth: 180, slotName: "carton-3" },
{
@@ -548,7 +549,6 @@ const cartonColumns: DataTableColumn[] = [
minWidth: 180,
},
{ key: "carton-6", label: "目的仓", minWidth: 180, slotName: "carton-6" },
{ key: "carton-9", label: "状态", width: 100, slotName: "carton-9" },
{ key: "skuTypes", label: "SKU种类", prop: "skuTypes", align: "right" },
{
key: "totalQuantity",
@@ -569,6 +569,7 @@ const planColumns: DataTableColumn[] = [
slotName: "plan-actions",
},
{ key: "planNo", label: "计划号", prop: "planNo", minWidth: 230 },
{ key: "status", label: "状态", minWidth: 110, slotName: "plan-status", afterColumn: 'planNo' },
{ key: "customerId", label: "客户", prop: "customerId", minWidth: 180, slotName: "plan-customer" },
{
key: "returnNos",
@@ -577,7 +578,6 @@ const planColumns: DataTableColumn[] = [
slotName: "plan-return-nos",
},
{ key: "mode", label: "合箱方式", minWidth: 110, slotName: "plan-mode" },
{ key: "status", label: "状态", minWidth: 110, slotName: "plan-status" },
{ key: "createdAt", label: "创建时间", prop: "createdAt", minWidth: 180, slotName: "plan-created-at" },
];
function formatPlanCreatedAt(value: unknown) {