优化
This commit is contained in:
@@ -177,6 +177,11 @@ export interface MerchantUnifiedOrderQuery {
|
|||||||
|
|
||||||
const unifiedOrderBaseUrl = '/merchant/v1/orders'
|
const unifiedOrderBaseUrl = '/merchant/v1/orders'
|
||||||
|
|
||||||
|
export type OrderQueue = 'all' | 'fee' | 'customs' | 'draft' | 'loaded' | 'declared' | 'cleared' | 'scheduled' | 'delivering' | 'delivered'
|
||||||
|
export type OrderProgressStage = 'none' | 'loaded' | 'declared' | 'cleared' | 'scheduled' | 'delivering' | 'delivered'
|
||||||
|
export interface MerchantOrderProgress { orderId: string | number; stage: OrderProgressStage; partial: boolean }
|
||||||
|
export type OrderQueueCounts = Record<OrderQueue, number>
|
||||||
|
|
||||||
export interface MerchantOrderWorkbenchQuery {
|
export interface MerchantOrderWorkbenchQuery {
|
||||||
cargoKeyword?: string
|
cargoKeyword?: string
|
||||||
receivingWarehouseId?: string
|
receivingWarehouseId?: string
|
||||||
@@ -186,14 +191,15 @@ export interface MerchantOrderWorkbenchQuery {
|
|||||||
mainStatus?: string
|
mainStatus?: string
|
||||||
createdFrom?: string
|
createdFrom?: string
|
||||||
createdTo?: string
|
createdTo?: string
|
||||||
queue?: 'all' | 'fee' | 'customs' | 'draft'
|
queue?: OrderQueue
|
||||||
page?: number
|
page?: number
|
||||||
size?: number
|
size?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MerchantOrderWorkbench {
|
export interface MerchantOrderWorkbench {
|
||||||
page: PageResult<MerchantUnifiedOrderRow>
|
page: PageResult<MerchantUnifiedOrderRow>
|
||||||
queues: { all: number; fee: number; customs: number; draft: number }
|
queues: OrderQueueCounts
|
||||||
|
progress: MerchantOrderProgress[]
|
||||||
channelNames: string[]
|
channelNames: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -236,5 +236,5 @@ test('keeps failed order loads distinct from empty results', () => {
|
|||||||
assert.match(storeSource, /const loadError = ref\(''\)/)
|
assert.match(storeSource, /const loadError = ref\(''\)/)
|
||||||
assert.match(storeSource, /orderResult\.reason instanceof Error/)
|
assert.match(storeSource, /orderResult\.reason instanceof Error/)
|
||||||
assert.match(storeSource, /warehouseLoadError/)
|
assert.match(storeSource, /warehouseLoadError/)
|
||||||
assert.match(storeSource, /return \{ orders, orderDetails, total, queueCounts, queryChannelNames, warehouses, loading, loadedFromApi, loadError, warehouseLoadError/)
|
assert.match(storeSource, /return \{ orders, orderDetails, total, queueCounts, orderProgress, queryChannelNames, warehouses, loading, loadedFromApi, loadError, warehouseLoadError/)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import {
|
|||||||
getMerchantOrderWorkbench,
|
getMerchantOrderWorkbench,
|
||||||
type MerchantUnifiedOrderDetail,
|
type MerchantUnifiedOrderDetail,
|
||||||
type MerchantOrderWorkbenchQuery,
|
type MerchantOrderWorkbenchQuery,
|
||||||
|
type MerchantOrderProgress,
|
||||||
|
type OrderQueueCounts,
|
||||||
type MerchantUnifiedOrderRow,
|
type MerchantUnifiedOrderRow,
|
||||||
} from '@/modules/first-leg/api/unifiedOrders'
|
} from '@/modules/first-leg/api/unifiedOrders'
|
||||||
import type { MerchantTenantStatus } from '@/modules/tenant-management/public'
|
import type { MerchantTenantStatus } from '@/modules/tenant-management/public'
|
||||||
@@ -396,7 +398,8 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
|
|||||||
const orders = ref<FirstLegOrder[]>([])
|
const orders = ref<FirstLegOrder[]>([])
|
||||||
const orderDetails = ref<Record<string, FirstLegOrder>>({})
|
const orderDetails = ref<Record<string, FirstLegOrder>>({})
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const queueCounts = ref({ all: 0, fee: 0, customs: 0, draft: 0 })
|
const queueCounts = ref<OrderQueueCounts>({ all: 0, fee: 0, customs: 0, draft: 0, loaded: 0, declared: 0, cleared: 0, scheduled: 0, delivering: 0, delivered: 0 })
|
||||||
|
const orderProgress = ref<Record<string, MerchantOrderProgress>>({})
|
||||||
const queryChannelNames = ref<string[]>([])
|
const queryChannelNames = ref<string[]>([])
|
||||||
let listRequest = 0
|
let listRequest = 0
|
||||||
const pendingSubmissions = new Map<string, { key: string; payload: { expectedVersion: number } }>()
|
const pendingSubmissions = new Map<string, { key: string; payload: { expectedVersion: number } }>()
|
||||||
@@ -451,7 +454,8 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
|
|||||||
loadError.value = ''
|
loadError.value = ''
|
||||||
orders.value = []
|
orders.value = []
|
||||||
total.value = 0
|
total.value = 0
|
||||||
queueCounts.value = { all: 0, fee: 0, customs: 0, draft: 0 }
|
queueCounts.value = { all: 0, fee: 0, customs: 0, draft: 0, loaded: 0, declared: 0, cleared: 0, scheduled: 0, delivering: 0, delivered: 0 }
|
||||||
|
orderProgress.value = {}
|
||||||
const [orderResult, warehouseResult] = await Promise.allSettled([
|
const [orderResult, warehouseResult] = await Promise.allSettled([
|
||||||
getMerchantOrderWorkbench(query), loadWarehouseType('DOMESTIC'),
|
getMerchantOrderWorkbench(query), loadWarehouseType('DOMESTIC'),
|
||||||
])
|
])
|
||||||
@@ -466,14 +470,19 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
|
|||||||
|| Number(result.page.size) !== size || Number(result.page.pages) !== pages
|
|| Number(result.page.size) !== size || Number(result.page.pages) !== pages
|
||||||
|| result.page.records.length !== Math.min(size, Math.max(0, count - (expectedPage - 1) * size))
|
|| result.page.records.length !== Math.min(size, Math.max(0, count - (expectedPage - 1) * size))
|
||||||
|| new Set(result.page.records.map(row => String(row.orderId))).size !== result.page.records.length
|
|| new Set(result.page.records.map(row => String(row.orderId))).size !== result.page.records.length
|
||||||
|| ['all', 'fee', 'customs', 'draft'].some(key => { const value = result.queues[key as keyof typeof result.queues]; return !Number.isSafeInteger(Number(value)) || Number(value) < 0 })
|
|| ['all', 'fee', 'customs', 'draft', 'loaded', 'declared', 'cleared', 'scheduled', 'delivering', 'delivered'].some(key => { const value = result.queues[key as keyof typeof result.queues]; return !Number.isSafeInteger(Number(value)) || Number(value) < 0 })
|
||||||
|| Number(result.queues[query.queue ?? 'all']) !== count
|
|| Number(result.queues[query.queue ?? 'all']) !== count
|
||||||
|
|| !Array.isArray(result.progress) || result.progress.length !== result.page.records.length
|
||||||
|
|| new Set(result.progress.map(p => String(p.orderId))).size !== result.progress.length
|
||||||
|
|| result.progress.some(p => !result.page.records.some(row => String(row.orderId) === String(p.orderId))
|
||||||
|
|| !['none', 'loaded', 'declared', 'cleared', 'scheduled', 'delivering', 'delivered'].includes(p.stage) || typeof p.partial !== 'boolean')
|
||||||
|| !Array.isArray(result.channelNames)) {
|
|| !Array.isArray(result.channelNames)) {
|
||||||
loadError.value = '订单分页数据不完整,请重新加载'
|
loadError.value = '订单分页数据不完整,请重新加载'
|
||||||
} else {
|
} else {
|
||||||
orders.value = result.page.records.map(item => mapUnifiedOrderRow(item, warehouses.value))
|
orders.value = result.page.records.map(item => mapUnifiedOrderRow(item, warehouses.value))
|
||||||
total.value = count
|
total.value = count
|
||||||
queueCounts.value = { all: Number(result.queues.all), fee: Number(result.queues.fee), customs: Number(result.queues.customs), draft: Number(result.queues.draft) }
|
queueCounts.value = Object.fromEntries(Object.entries(result.queues).map(([key, value]) => [key, Number(value)])) as OrderQueueCounts
|
||||||
|
orderProgress.value = Object.fromEntries(result.progress.map(p => [String(p.orderId), p]))
|
||||||
queryChannelNames.value = result.channelNames
|
queryChannelNames.value = result.channelNames
|
||||||
}
|
}
|
||||||
} catch { loadError.value = '订单分页数据不完整,请重新加载' }
|
} catch { loadError.value = '订单分页数据不完整,请重新加载' }
|
||||||
@@ -629,5 +638,5 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
|
|||||||
return loadOrderAggregate(id)
|
return loadOrderAggregate(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { orders, orderDetails, total, queueCounts, queryChannelNames, warehouses, loading, loadedFromApi, loadError, warehouseLoadError, getById, load, loadDetail, loadDraftDetail, loadWarehouseDetail, loadWarehouseType, beginDraftEdit, saveDraft, prepareCancellation, cancelOrder, saveDraftBatch, submitSaved, hasPendingSubmission, isSubmissionRetry, beginNewSubmissionEdit, saveDraftForSubmission }
|
return { orders, orderDetails, total, queueCounts, orderProgress, queryChannelNames, warehouses, loading, loadedFromApi, loadError, warehouseLoadError, getById, load, loadDetail, loadDraftDetail, loadWarehouseDetail, loadWarehouseType, beginDraftEdit, saveDraft, prepareCancellation, cancelOrder, saveDraftBatch, submitSaved, hasPendingSubmission, isSubmissionRetry, beginNewSubmissionEdit, saveDraftForSubmission }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ const row = (id, mainStatus = 'LOADED') => ({ orderId: String(id), orderNo: `FL-
|
|||||||
function result(page = 1, size = 10, total = 215) {
|
function result(page = 1, size = 10, total = 215) {
|
||||||
return {
|
return {
|
||||||
page: { page, size, total, pages: Math.ceil(total / size), records: Array.from({ length: Math.min(size, Math.max(0, total - (page - 1) * size)) }, (_, i) => row((page - 1) * size + i + 1)) },
|
page: { page, size, total, pages: Math.ceil(total / size), records: Array.from({ length: Math.min(size, Math.max(0, total - (page - 1) * size)) }, (_, i) => row((page - 1) * size + i + 1)) },
|
||||||
queues: { all: total, fee: 2, customs: 1, draft: 3 }, channelNames: ['AIR', 'SEA'],
|
queues: { all: total, fee: 2, customs: 1, draft: 3, loaded: 0, declared: 0, cleared: 0, scheduled: 0, delivering: 0, delivered: 0 }, channelNames: ['AIR', 'SEA'],
|
||||||
|
progress: Array.from({ length: Math.min(size, Math.max(0, total - (page - 1) * size)) }, (_, i) => ({ orderId: String((page - 1) * size + i + 1), stage: 'loaded', partial: false })),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function deferred() { let resolve, reject; const promise = new Promise((a, b) => { resolve = a; reject = b }); return { promise, resolve, reject } }
|
function deferred() { let resolve, reject; const promise = new Promise((a, b) => { resolve = a; reject = b }); return { promise, resolve, reject } }
|
||||||
@@ -44,7 +45,7 @@ test('loads beyond 200 with server totals and forwards every filter using one re
|
|||||||
assert.equal(h.store.orders[0].id, '201')
|
assert.equal(h.store.orders[0].id, '201')
|
||||||
assert.equal(h.store.total, 215)
|
assert.equal(h.store.total, 215)
|
||||||
assert.deepEqual(h.calls, [query])
|
assert.deepEqual(h.calls, [query])
|
||||||
assert.deepEqual(h.store.queueCounts, { all: 215, fee: 2, customs: 1, draft: 3 })
|
assert.deepEqual(h.store.queueCounts, { all: 215, fee: 2, customs: 1, draft: 3, loaded: 0, declared: 0, cleared: 0, scheduled: 0, delivering: 0, delivered: 0 })
|
||||||
assert.deepEqual(h.store.queryChannelNames, ['AIR', 'SEA'])
|
assert.deepEqual(h.store.queryChannelNames, ['AIR', 'SEA'])
|
||||||
await h.store.load({ page: 22, size: 10 })
|
await h.store.load({ page: 22, size: 10 })
|
||||||
assert.deepEqual(h.store.orders.map(item => item.id), ['211', '212', '213', '214', '215'])
|
assert.deepEqual(h.store.orders.map(item => item.id), ['211', '212', '213', '214', '215'])
|
||||||
@@ -109,9 +110,24 @@ test('draft and cancelled rows retain authoritative versions; off-page detail ca
|
|||||||
|
|
||||||
test('queue total is checked against the selected queue rather than all orders', async t => {
|
test('queue total is checked against the selected queue rather than all orders', async t => {
|
||||||
const response = result(1, 10, 2)
|
const response = result(1, 10, 2)
|
||||||
response.queues = { all: 215, fee: 2, customs: 1, draft: 3 }
|
response.queues = { all: 215, fee: 2, customs: 1, draft: 3, loaded: 0, declared: 0, cleared: 0, scheduled: 0, delivering: 0, delivered: 0 }
|
||||||
const h = harness(t, () => response)
|
const h = harness(t, () => response)
|
||||||
await h.store.load({ queue: 'fee' })
|
await h.store.load({ queue: 'fee' })
|
||||||
assert.equal(h.store.total, 2); assert.equal(h.store.queueCounts.all, 215)
|
assert.equal(h.store.total, 2); assert.equal(h.store.queueCounts.all, 215)
|
||||||
assert.equal(h.store.loadError, '')
|
assert.equal(h.store.loadError, '')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
test('retains server progress and rejects missing or foreign progress without inferring milestones', async t => {
|
||||||
|
let response = result()
|
||||||
|
response.progress[0] = { orderId: '1', stage: 'scheduled', partial: true }
|
||||||
|
const h = harness(t, () => response)
|
||||||
|
await h.store.load()
|
||||||
|
assert.deepEqual(h.store.orderProgress['1'], response.progress[0])
|
||||||
|
response = result(); response.progress[0].orderId = 'foreign'
|
||||||
|
await h.store.load()
|
||||||
|
assert.match(h.store.loadError, /分页数据不完整/)
|
||||||
|
assert.deepEqual(h.store.orderProgress, {})
|
||||||
|
response = result(); delete response.queues.cleared
|
||||||
|
await h.store.load(); assert.match(h.store.loadError, /分页数据不完整/)
|
||||||
|
})
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import test from 'node:test'
|
||||||
|
import { readFileSync } from 'node:fs'
|
||||||
|
import ts from 'typescript'
|
||||||
|
const source = readFileSync(new URL('./orderProgress.ts', import.meta.url), 'utf8')
|
||||||
|
const code = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS } }).outputText
|
||||||
|
const exports = {}
|
||||||
|
new Function('exports', code)(exports)
|
||||||
|
test('prototype queues preserve order and distinguish partial progress from completion', () => {
|
||||||
|
assert.deepEqual(exports.ORDER_QUEUES.slice(0, 7).map(q => q.key), ['all','fee','loaded','declared','cleared','scheduled','delivering'])
|
||||||
|
assert.equal(exports.orderProgressLabel({ stage: 'scheduled', partial: true }), '部分已排车')
|
||||||
|
assert.equal(exports.orderProgressLabel({ stage: 'cleared', partial: false }), '已清关')
|
||||||
|
assert.equal(exports.orderProgressLabel({ stage: 'none', partial: false }), '—')
|
||||||
|
assert.equal(exports.orderProgressLabel(), '—')
|
||||||
|
})
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import type { MerchantOrderProgress, OrderQueue } from '../api/unifiedOrders'
|
||||||
|
|
||||||
|
export const ORDER_QUEUES: ReadonlyArray<{ key: OrderQueue; label: string; todo?: boolean }> = [
|
||||||
|
{ key: 'all', label: '全部' },
|
||||||
|
{ key: 'fee', label: '确认计费重', todo: true },
|
||||||
|
{ key: 'loaded', label: '装柜完成' },
|
||||||
|
{ key: 'declared', label: '已报关' },
|
||||||
|
{ key: 'cleared', label: '已清关' },
|
||||||
|
{ key: 'scheduled', label: '已排车' },
|
||||||
|
{ key: 'delivering', label: '派送中' },
|
||||||
|
{ key: 'delivered', label: '派送完成' },
|
||||||
|
{ key: 'customs', label: '上传资料', todo: true },
|
||||||
|
{ key: 'draft', label: '草稿' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export function orderProgressLabel(progress?: MerchantOrderProgress) {
|
||||||
|
if (!progress || progress.stage === 'none') return '—'
|
||||||
|
const label = ORDER_QUEUES.find(q => q.key === progress.stage)?.label ?? '—'
|
||||||
|
return `${progress.partial ? '部分' : ''}${label}`
|
||||||
|
}
|
||||||
@@ -176,7 +176,7 @@ test('exports every visible order field with normalized dates', () => {
|
|||||||
test('matches the prototype with selectable dense order-list columns', () => {
|
test('matches the prototype with selectable dense order-list columns', () => {
|
||||||
assert.match(source, /aria-label="设置订单列表显示列"/)
|
assert.match(source, /aria-label="设置订单列表显示列"/)
|
||||||
assert.match(source, /显示字段筛选/)
|
assert.match(source, /显示字段筛选/)
|
||||||
assert.match(source, /const defaultVisibleColumnKeys = \['status', 'channel', 'transportMode', 'pickupMethod', 'receivingWarehouse', 'createdAt'\]/)
|
assert.match(source, /const defaultVisibleColumnKeys = \['status', 'progress', 'channel', 'transportMode', 'pickupMethod', 'receivingWarehouse', 'createdAt'\]/)
|
||||||
assert.match(source, /key: 'inboundCode', label: '入仓码', width: 230, fixed: 'left'/)
|
assert.match(source, /key: 'inboundCode', label: '入仓码', width: 230, fixed: 'left'/)
|
||||||
assert.match(source, /<SlidersHorizontal :size="18"/)
|
assert.match(source, /<SlidersHorizontal :size="18"/)
|
||||||
assert.match(source, /<el-checkbox-group v-model="visibleColumnKeys"/)
|
assert.match(source, /<el-checkbox-group v-model="visibleColumnKeys"/)
|
||||||
@@ -272,7 +272,7 @@ test('keeps all progress stages and business summary usable on mobile', () => {
|
|||||||
test('keeps the first viewport compact and moves create into the table toolbar', () => {
|
test('keeps the first viewport compact and moves create into the table toolbar', () => {
|
||||||
const tableToolbarSource = source.slice(source.indexOf('#toolbar-actions'), source.indexOf('class="order-eligibility is-pending"'))
|
const tableToolbarSource = source.slice(source.indexOf('#toolbar-actions'), source.indexOf('class="order-eligibility is-pending"'))
|
||||||
assert.match(source, /#toolbar-actions/)
|
assert.match(source, /#toolbar-actions/)
|
||||||
assert.match(source, /class="order-queue-tabs"[\s\S]*确认计费重[\s\S]*上传资料[\s\S]*草稿/)
|
assert.match(source, /class="order-queue-tabs"[\s\S]*v-for="queue in ORDER_QUEUES"[\s\S]*orderQueueCounts\[queue.key\]/)
|
||||||
assert.match(source, /activeOrderQueue\.value === 'draft'[\s\S]*order\.status === 'DRAFT'/)
|
assert.match(source, /activeOrderQueue\.value === 'draft'[\s\S]*order\.status === 'DRAFT'/)
|
||||||
assert.doesNotMatch(source, /共 \{\{ filteredOrders\.length \}\} 条/)
|
assert.doesNotMatch(source, /共 \{\{ filteredOrders\.length \}\} 条/)
|
||||||
assert.match(source, /#toolbar-actions[\s\S]*order:first-leg:create[\s\S]*创建订单[\s\S]*class="order-eligibility is-pending"/)
|
assert.match(source, /#toolbar-actions[\s\S]*order:first-leg:create[\s\S]*创建订单[\s\S]*class="order-eligibility is-pending"/)
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ import {
|
|||||||
summarizeFirstLegCargo,
|
summarizeFirstLegCargo,
|
||||||
validateFirstLegOrderDraft,
|
validateFirstLegOrderDraft,
|
||||||
} from '@/modules/first-leg/utils/firstLegOrder'
|
} from '@/modules/first-leg/utils/firstLegOrder'
|
||||||
|
import { ORDER_QUEUES, orderProgressLabel } from '../utils/orderProgress'
|
||||||
|
import type { OrderQueue } from '../api/unifiedOrders'
|
||||||
import { validateProductImageFile } from '@/modules/first-leg/utils/productImage'
|
import { validateProductImageFile } from '@/modules/first-leg/utils/productImage'
|
||||||
import { createPriceEstimateBreakdown } from '@/modules/first-leg/utils/priceEstimate'
|
import { createPriceEstimateBreakdown } from '@/modules/first-leg/utils/priceEstimate'
|
||||||
|
|
||||||
@@ -168,6 +170,7 @@ const fixedOrderColumns: DataTableColumn[] = [
|
|||||||
]
|
]
|
||||||
const selectableOrderColumns: DataTableColumn[] = [
|
const selectableOrderColumns: DataTableColumn[] = [
|
||||||
{ key: 'status', label: '当前订单状态', width: 126, align: 'center', headerAlign: 'center', slotName: 'status' },
|
{ key: 'status', label: '当前订单状态', width: 126, align: 'center', headerAlign: 'center', slotName: 'status' },
|
||||||
|
{ key: 'progress', label: '履约进度', width: 142, align: 'center', headerAlign: 'center', slotName: 'progress' },
|
||||||
{ key: 'channel', label: '渠道', width: 150, align: 'left', headerAlign: 'left', showOverflowTooltip: true, slotName: 'channel' },
|
{ key: 'channel', label: '渠道', width: 150, align: 'left', headerAlign: 'left', showOverflowTooltip: true, slotName: 'channel' },
|
||||||
{ key: 'transportMode', label: '运输方式', width: 104, align: 'center', headerAlign: 'center', slotName: 'transportMode' },
|
{ key: 'transportMode', label: '运输方式', width: 104, align: 'center', headerAlign: 'center', slotName: 'transportMode' },
|
||||||
{ key: 'pickupMethod', label: '交货方式', width: 112, align: 'center', headerAlign: 'center', slotName: 'pickupMethod' },
|
{ key: 'pickupMethod', label: '交货方式', width: 112, align: 'center', headerAlign: 'center', slotName: 'pickupMethod' },
|
||||||
@@ -198,7 +201,7 @@ const selectableOrderColumns: DataTableColumn[] = [
|
|||||||
{ key: 'updatedAt', label: '更新时间', width: 164, align: 'center', headerAlign: 'center', slotName: 'updatedAt' },
|
{ key: 'updatedAt', label: '更新时间', width: 164, align: 'center', headerAlign: 'center', slotName: 'updatedAt' },
|
||||||
]
|
]
|
||||||
const allSelectableColumnKeys = selectableOrderColumns.map((column) => String(column.key))
|
const allSelectableColumnKeys = selectableOrderColumns.map((column) => String(column.key))
|
||||||
const defaultVisibleColumnKeys = ['status', 'channel', 'transportMode', 'pickupMethod', 'receivingWarehouse', 'createdAt']
|
const defaultVisibleColumnKeys = ['status', 'progress', 'channel', 'transportMode', 'pickupMethod', 'receivingWarehouse', 'createdAt']
|
||||||
const visibleColumnKeys = ref<string[]>([...defaultVisibleColumnKeys])
|
const visibleColumnKeys = ref<string[]>([...defaultVisibleColumnKeys])
|
||||||
const orderedColumnKeys = ref<string[]>([...allSelectableColumnKeys])
|
const orderedColumnKeys = ref<string[]>([...allSelectableColumnKeys])
|
||||||
const draggedColumnKey = ref('')
|
const draggedColumnKey = ref('')
|
||||||
@@ -243,7 +246,6 @@ const selectedOrders = ref<FirstLegOrder[]>([])
|
|||||||
const selectedOrderCount = computed(() => selectedOrders.value.length)
|
const selectedOrderCount = computed(() => selectedOrders.value.length)
|
||||||
const selectedDraftOrders = computed(() => selectedOrders.value.filter((order) => order.status === 'DRAFT'))
|
const selectedDraftOrders = computed(() => selectedOrders.value.filter((order) => order.status === 'DRAFT'))
|
||||||
const orderTableVersion = ref(0)
|
const orderTableVersion = ref(0)
|
||||||
type OrderQueue = 'all' | 'fee' | 'customs' | 'draft'
|
|
||||||
const activeOrderQueue = ref<OrderQueue>('all')
|
const activeOrderQueue = ref<OrderQueue>('all')
|
||||||
const editorOpen = ref(false)
|
const editorOpen = ref(false)
|
||||||
const orderFormRef = ref<FormInstance>()
|
const orderFormRef = ref<FormInstance>()
|
||||||
@@ -638,6 +640,7 @@ const emptyDescription = computed(() => {
|
|||||||
if (activeOrderQueue.value === 'customs') return '暂无需要上传报关资料的订单'
|
if (activeOrderQueue.value === 'customs') return '暂无需要上传报关资料的订单'
|
||||||
if (activeOrderQueue.value === 'fee') return '暂无需要确认计费重的订单'
|
if (activeOrderQueue.value === 'fee') return '暂无需要确认计费重的订单'
|
||||||
if (activeOrderQueue.value === 'draft') return '暂无草稿订单'
|
if (activeOrderQueue.value === 'draft') return '暂无草稿订单'
|
||||||
|
if (activeOrderQueue.value !== 'all') return `暂无${ORDER_QUEUES.find(q => q.key === activeOrderQueue.value)?.label ?? ''}的订单`
|
||||||
return '还没有订单'
|
return '还没有订单'
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1812,6 +1815,7 @@ function exportOrderInformation() {
|
|||||||
displayListValue(order.destinationWarehouseName),
|
displayListValue(order.destinationWarehouseName),
|
||||||
destinationTypeLabels[order.destinationType],
|
destinationTypeLabels[order.destinationType],
|
||||||
getFirstLegStatusLabel(order.status),
|
getFirstLegStatusLabel(order.status),
|
||||||
|
orderProgressLabel(store.orderProgress[order.id]),
|
||||||
formatListMetric(orderPackageCount(order)),
|
formatListMetric(orderPackageCount(order)),
|
||||||
displayListValue(order.expressCompanyName),
|
displayListValue(order.expressCompanyName),
|
||||||
displayListValue(order.trackingNumber),
|
displayListValue(order.trackingNumber),
|
||||||
@@ -1830,7 +1834,7 @@ function exportOrderInformation() {
|
|||||||
formatExportDateTime(order.createdAt),
|
formatExportDateTime(order.createdAt),
|
||||||
])
|
])
|
||||||
downloadCsv('订单信息.csv', [[
|
downloadCsv('订单信息.csv', [[
|
||||||
'入仓码', '渠道', '运输方式', '交货方式', '预约提货时间', '目的仓', '目的仓类型', '当前订单状态', '箱数', '快递公司', '快递单号', 'SKUS',
|
'入仓码', '渠道', '运输方式', '交货方式', '预约提货时间', '目的仓', '目的仓类型', '当前订单状态', '履约进度', '箱数', '快递公司', '快递单号', 'SKUS',
|
||||||
'总毛重', '总净重', '总体积', '总数量', '计费单价', '计费体积', '计费重量', '备注',
|
'总毛重', '总净重', '总体积', '总数量', '计费单价', '计费体积', '计费重量', '备注',
|
||||||
'货件编号', '跟踪编号', '国内仓入库时间', '创建时间',
|
'货件编号', '跟踪编号', '国内仓入库时间', '创建时间',
|
||||||
], ...rows])
|
], ...rows])
|
||||||
@@ -2106,10 +2110,12 @@ async function searchOrders(model: QueryModel) {
|
|||||||
<DataTable :page="tablePage" :page-size="tablePageSize" @update:page="changeOrderPage" @update:page-size="changeOrderPageSize" :data="pagedOrders" :columns="visibleOrderColumns" :key="orderTableVersion" :loading="store.loading" :total="store.total" :page-sizes="[10, 20, 50]" row-key="id" empty-text="暂无订单" class="prototype-order-table" :row-actions="getOrderRowActions" @selection-change="handleOrderSelectionChange">
|
<DataTable :page="tablePage" :page-size="tablePageSize" @update:page="changeOrderPage" @update:page-size="changeOrderPageSize" :data="pagedOrders" :columns="visibleOrderColumns" :key="orderTableVersion" :loading="store.loading" :total="store.total" :page-sizes="[10, 20, 50]" row-key="id" empty-text="暂无订单" class="prototype-order-table" :row-actions="getOrderRowActions" @selection-change="handleOrderSelectionChange">
|
||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
<nav class="order-queue-tabs" aria-label="订单事项分类">
|
<nav class="order-queue-tabs" aria-label="订单事项分类">
|
||||||
<button type="button" :class="{ active: activeOrderQueue === 'all' }" @click="selectOrderQueue('all')">全部</button>
|
<button v-for="queue in ORDER_QUEUES" :key="queue.key" type="button"
|
||||||
<button type="button" :class="{ active: activeOrderQueue === 'fee' }" @click="selectOrderQueue('fee')">确认计费重<span v-if="orderQueueCounts.fee" class="queue-count">{{ orderQueueCounts.fee }}</span></button>
|
:class="{ active: activeOrderQueue === queue.key }" :aria-pressed="activeOrderQueue === queue.key"
|
||||||
<button type="button" :class="{ active: activeOrderQueue === 'customs' }" @click="selectOrderQueue('customs')">上传资料<span v-if="orderQueueCounts.customs" class="queue-count">{{ orderQueueCounts.customs }}</span></button>
|
@click="selectOrderQueue(queue.key)">
|
||||||
<button type="button" :class="{ active: activeOrderQueue === 'draft' }" @click="selectOrderQueue('draft')">草稿<span v-if="orderQueueCounts.draft" class="queue-count">{{ orderQueueCounts.draft }}</span></button>
|
{{ queue.label }}<span v-if="queue.key !== 'all' && orderQueueCounts[queue.key]"
|
||||||
|
:class="queue.todo ? 'queue-count' : 'queue-stage-count'">{{ queue.todo ? orderQueueCounts[queue.key] : `(${orderQueueCounts[queue.key]})` }}</span>
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
<span v-if="selectedOrderCount">已选 {{ selectedOrderCount }} 条</span>
|
<span v-if="selectedOrderCount">已选 {{ selectedOrderCount }} 条</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -2185,6 +2191,7 @@ async function searchOrders(model: QueryModel) {
|
|||||||
<template #receivingWarehouse="{ row }"><span class="table-ellipsis" :title="displayListValue((row as FirstLegOrder).receivingWarehouseName)">{{ displayListValue((row as FirstLegOrder).receivingWarehouseName) }}</span></template>
|
<template #receivingWarehouse="{ row }"><span class="table-ellipsis" :title="displayListValue((row as FirstLegOrder).receivingWarehouseName)">{{ displayListValue((row as FirstLegOrder).receivingWarehouseName) }}</span></template>
|
||||||
<template #destinationWarehouse="{ row }"><span class="table-ellipsis" :title="displayListValue((row as FirstLegOrder).destinationWarehouseName)">{{ displayListValue((row as FirstLegOrder).destinationWarehouseName) }}</span></template>
|
<template #destinationWarehouse="{ row }"><span class="table-ellipsis" :title="displayListValue((row as FirstLegOrder).destinationWarehouseName)">{{ displayListValue((row as FirstLegOrder).destinationWarehouseName) }}</span></template>
|
||||||
<template #destinationType="{ row }">{{ destinationTypeLabels[(row as FirstLegOrder).destinationType] }}</template>
|
<template #destinationType="{ row }">{{ destinationTypeLabels[(row as FirstLegOrder).destinationType] }}</template>
|
||||||
|
<template #progress="{ row }">{{ orderProgressLabel(store.orderProgress[row.id]) }}</template>
|
||||||
<template #status="{ row }"><span class="client-status" :data-tone="getFirstLegStatusTone((row as FirstLegOrder).status)">{{ getFirstLegStatusLabel((row as FirstLegOrder).status) }}</span></template>
|
<template #status="{ row }"><span class="client-status" :data-tone="getFirstLegStatusTone((row as FirstLegOrder).status)">{{ getFirstLegStatusLabel((row as FirstLegOrder).status) }}</span></template>
|
||||||
<template #packageCount="{ row }">{{ formatListMetric(orderPackageCount(row as FirstLegOrder)) }}</template>
|
<template #packageCount="{ row }">{{ formatListMetric(orderPackageCount(row as FirstLegOrder)) }}</template>
|
||||||
<template #expressCompany="{ row }"><span class="table-ellipsis" :title="displayListValue((row as FirstLegOrder).expressCompanyName)">{{ displayListValue((row as FirstLegOrder).expressCompanyName) }}</span></template>
|
<template #expressCompany="{ row }"><span class="table-ellipsis" :title="displayListValue((row as FirstLegOrder).expressCompanyName)">{{ displayListValue((row as FirstLegOrder).expressCompanyName) }}</span></template>
|
||||||
@@ -2570,7 +2577,12 @@ async function searchOrders(model: QueryModel) {
|
|||||||
.supporting-data-error span { overflow-wrap: anywhere; color: var(--muted); font-size: 11px; line-height: 1.55; }
|
.supporting-data-error span { overflow-wrap: anywhere; color: var(--muted); font-size: 11px; line-height: 1.55; }
|
||||||
.order-row-actions, .editor-actions { display: flex; justify-content: flex-end; gap: 8px; }
|
.order-row-actions, .editor-actions { display: flex; justify-content: flex-end; gap: 8px; }
|
||||||
.order-table-card { padding: 0; overflow: hidden; border: 1px solid var(--client-border); border-radius: var(--client-radius); box-shadow: none; }
|
.order-table-card { padding: 0; overflow: hidden; border: 1px solid var(--client-border); border-radius: var(--client-radius); box-shadow: none; }
|
||||||
.order-queue-tabs { display: flex; align-items: center; gap: 4px; }
|
.prototype-order-table :deep(.data-table__toolbar) { flex-wrap: nowrap; }
|
||||||
|
.prototype-order-table :deep(.data-table__toolbar-heading) { min-width: 0; flex: 1; flex-wrap: nowrap; }
|
||||||
|
.prototype-order-table :deep(.data-table__toolbar-actions) { flex-shrink: 0; }
|
||||||
|
.order-queue-tabs { display: flex; min-width: 0; align-items: center; gap: 4px; overflow-x: auto; padding: 7px 0 10px; scrollbar-width: thin; }
|
||||||
|
.order-queue-tabs button { flex-shrink: 0; white-space: nowrap; }
|
||||||
|
.queue-stage-count { margin-left: 3px; }
|
||||||
.order-queue-tabs button { position: relative; min-height: 34px; padding: 0 12px; border: 1px solid transparent; border-radius: 7px; background: transparent; color: #667085; cursor: pointer; font: inherit; font-weight: 600; }
|
.order-queue-tabs button { position: relative; min-height: 34px; padding: 0 12px; border: 1px solid transparent; border-radius: 7px; background: transparent; color: #667085; cursor: pointer; font: inherit; font-weight: 600; }
|
||||||
.order-queue-tabs button:hover { color: var(--client-action); }
|
.order-queue-tabs button:hover { color: var(--client-action); }
|
||||||
.order-queue-tabs button.active { border-color: #cfe0ff; background: #f3f7ff; color: var(--client-action); }
|
.order-queue-tabs button.active { border-color: #cfe0ff; background: #f3f7ff; color: var(--client-action); }
|
||||||
|
|||||||
Reference in New Issue
Block a user