优化
This commit is contained in:
@@ -69,11 +69,11 @@ describe('unified orders API contract', () => {
|
||||
|
||||
it('normalizes C02 row IDs and keeps inboundId for measurement routing', async () => {
|
||||
vi.mocked(http.get).mockResolvedValue({ data: {
|
||||
records: [{ orderId: '90001', tenantId: '208', inboundId: '77', inboundVersion: 3, mainStatus: 'WAIT_MEASURE', recommendedPalletType: 'STANDARD', locationCodes: 'A0-01, B2-03', customerRemark: '客户备注', latestInternalNote: '最新内部备注', measurement: null, preload: null, loading: null, delivery: null, transport: null }],
|
||||
records: [{ orderId: '90001', tenantId: '208', inboundId: '77', inboundVersion: 3, mainStatus: 'WAIT_MEASURE', recommendedPalletType: 'STANDARD', locationCodes: 'A0-01, B2-03', customerRemark: '客户备注', latestInternalNote: '最新内部备注', relation: { role: 'PARENT', rootOrderId: 90001, parentOrderId: null, splitSequence: null, childCount: '2' }, measurement: null, preload: null, loading: null, delivery: null, transport: null }],
|
||||
page: 1, size: 20, total: 1, pages: 1,
|
||||
} })
|
||||
await expect(listPlatformUnifiedOrders({ page: 1, size: 20 })).resolves.toMatchObject({
|
||||
records: [{ orderId: '90001', tenantId: '208', inboundId: '77', inboundVersion: 3, recommendedPalletType: 'STANDARD', locationCodes: 'A0-01, B2-03', customerRemark: '客户备注', latestInternalNote: '最新内部备注' }],
|
||||
records: [{ orderId: '90001', tenantId: '208', inboundId: '77', inboundVersion: 3, recommendedPalletType: 'STANDARD', locationCodes: 'A0-01, B2-03', customerRemark: '客户备注', latestInternalNote: '最新内部备注', relation: { role: 'PARENT', rootOrderId: '90001', childCount: 2 } }],
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -63,12 +63,21 @@ export interface UnifiedOrderTransportSummary {
|
||||
portArrivalAt: string | null
|
||||
}
|
||||
|
||||
export interface UnifiedOrderRelationSummary {
|
||||
role: 'PARENT' | 'CHILD'
|
||||
rootOrderId: number | string | null
|
||||
parentOrderId: number | string | null
|
||||
splitSequence: number | null
|
||||
childCount: number
|
||||
}
|
||||
|
||||
export interface UnifiedOrderResource {
|
||||
orderId: number | string
|
||||
tenantId: number | string
|
||||
orderNo: string | null
|
||||
mainStatus: UnifiedOrderMainStatus
|
||||
nextAction: string | null
|
||||
relation: UnifiedOrderRelationSummary | null
|
||||
internalOrderStatus: string | null
|
||||
customerId: number | string | null
|
||||
customerName: string | null
|
||||
@@ -319,6 +328,13 @@ function normalizeRow(raw: UnifiedOrderResource): UnifiedOrderResource {
|
||||
destinationWarehouseId: id(raw.destinationWarehouseId),
|
||||
inboundId: id(raw.inboundId),
|
||||
inboundVersion: nullableInteger(raw.inboundVersion),
|
||||
relation: raw.relation ? {
|
||||
...raw.relation,
|
||||
rootOrderId: id(raw.relation.rootOrderId),
|
||||
parentOrderId: id(raw.relation.parentOrderId),
|
||||
splitSequence: nullableInteger(raw.relation.splitSequence),
|
||||
childCount: integer(raw.relation.childCount, 0),
|
||||
} : null,
|
||||
totalPackageCount: nullableInteger(raw.totalPackageCount),
|
||||
totalPalletCount: nullableInteger(raw.totalPalletCount),
|
||||
recommendedPalletType: raw.recommendedPalletType ?? null,
|
||||
|
||||
@@ -162,9 +162,13 @@ describe('UnifiedOrdersView contract', () => {
|
||||
expect(source).not.toContain("value.replaceAll('_', ' ')")
|
||||
})
|
||||
|
||||
it('exposes the single-remainder fulfillment aggregate in row expansion and detail', () => {
|
||||
expect(source).toContain("{ key: 'fulfillment', type: 'expand', width: 52, fixed: 'left'")
|
||||
expect(source).toContain('<template #fulfillment="{ row }">')
|
||||
it('shows parent-child markers only for related rows and keeps the aggregate in detail', () => {
|
||||
expect(source).not.toContain("type: 'expand'")
|
||||
expect(source).toContain('v-if="(row as UnifiedOrderResource).relation"')
|
||||
expect(source).toContain('父单 · ${row.relation.childCount} 个子单')
|
||||
expect(source).toContain('子单 · 第 ${row.relation.splitSequence} 次')
|
||||
expect(source).toContain('title="父子订单关联"')
|
||||
expect(source).toContain('@click="openOrderRelation(row as UnifiedOrderResource)"')
|
||||
expect(source).toContain("{ key: 'fulfillment', label: '拆分履约', icon: ListChecks }")
|
||||
expect(source).toContain("activeDetailTab === 'fulfillment'")
|
||||
expect(source).toContain('根单、已完成分片与当前余量')
|
||||
@@ -184,7 +188,6 @@ describe('UnifiedOrdersView contract', () => {
|
||||
expect(source).toContain('aria-label="订单详情模块"')
|
||||
expect(source).toContain("{ key: 'basic', label: '基本信息'")
|
||||
expect(source).toContain("{ key: 'measurement', label: '测量数据'")
|
||||
expect(source).not.toContain('<AppDrawer')
|
||||
expect(source).not.toContain('detailOpen')
|
||||
expect(source).toContain('watch(detailRouteKey,')
|
||||
expect(source).toContain('requestKey !== detailRouteKey.value')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, reactive, ref, watch, type Component } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ArrowLeft, Box, ChevronDown, ChevronUp, CircleCheck, Eye, History, ListChecks, MoreHorizontal, PackageOpen, Ruler, SlidersHorizontal, StickyNote, WalletCards } from '@lucide/vue'
|
||||
import { ArrowLeft, Box, ChevronDown, ChevronUp, CircleCheck, Eye, GitBranch, History, ListChecks, MoreHorizontal, PackageOpen, Ruler, SlidersHorizontal, StickyNote, WalletCards } from '@lucide/vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import {
|
||||
getPlatformUnifiedOrder,
|
||||
@@ -23,6 +23,7 @@ import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import AppDrawer from '@/shared/components/AppDrawer.vue'
|
||||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
||||
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
|
||||
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
||||
@@ -97,6 +98,8 @@ const noteOpen = ref(false)
|
||||
const noteSaving = ref(false)
|
||||
const noteContent = ref('')
|
||||
const noteOrder = ref<UnifiedOrderResource | null>(null)
|
||||
const relationOpen = ref(false)
|
||||
const relationOrder = ref<UnifiedOrderResource | null>(null)
|
||||
const feeDetails = ref<UnifiedOrderFeeDetailResource[]>([])
|
||||
const feeLoading = ref(false)
|
||||
const feeLoadedKey = ref('')
|
||||
@@ -197,8 +200,7 @@ const queryFields = computed(() => showAdvancedFilters.value
|
||||
|
||||
const columns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', width: 300, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'fulfillment', type: 'expand', width: 52, fixed: 'left', slotName: 'fulfillment', hideable: false, movable: false },
|
||||
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 140, slotName: 'field' },
|
||||
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 190, slotName: 'inboundCode' },
|
||||
{ key: 'internalOrderStatus', prop: 'internalOrderStatus', label: '订单状态', minWidth: 130, slotName: 'orderStatus' },
|
||||
{ key: 'measurementReviewStatus', prop: 'measurementReviewStatus', label: '测量数据审核状态', minWidth: 170, slotName: 'measurementReviewStatus' },
|
||||
{ key: 'expectedArrivalAt', prop: 'expectedArrivalAt', label: '预计到达时间', minWidth: 150, slotName: 'field' },
|
||||
@@ -303,6 +305,19 @@ function customerName(row: UnifiedOrderResource) {
|
||||
return row.customerName?.trim() || customers.value.find((item) => String(item.id) === String(row.tenantId))?.tenantName || `客户 ${row.tenantId}`
|
||||
}
|
||||
|
||||
function orderRelationLabel(row: UnifiedOrderResource) {
|
||||
if (row.relation?.role === 'PARENT') return `父单 · ${row.relation.childCount} 个子单`
|
||||
if (row.relation?.role === 'CHILD' && row.relation.splitSequence !== null) {
|
||||
return `子单 · 第 ${row.relation.splitSequence} 次`
|
||||
}
|
||||
return '子单'
|
||||
}
|
||||
|
||||
function openOrderRelation(row: UnifiedOrderResource) {
|
||||
relationOrder.value = row
|
||||
relationOpen.value = true
|
||||
}
|
||||
|
||||
function formatDate(value: string | null | undefined) {
|
||||
return value ? value.replace('T', ' ').slice(0, 16) : '—'
|
||||
}
|
||||
@@ -1074,12 +1089,18 @@ onMounted(() => {
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<template #fulfillment="{ row }">
|
||||
<OrderFulfillmentTreePanel
|
||||
:tenant-id="String((row as UnifiedOrderResource).tenantId)"
|
||||
:order-id="String((row as UnifiedOrderResource).orderId)"
|
||||
compact
|
||||
/>
|
||||
<template #inboundCode="{ row }">
|
||||
<div class="inbound-relation-cell">
|
||||
<span>{{ (row as UnifiedOrderResource).inboundCode || '—' }}</span>
|
||||
<el-button
|
||||
v-if="(row as UnifiedOrderResource).relation"
|
||||
class="order-relation-link"
|
||||
link
|
||||
type="primary"
|
||||
:aria-label="`查看${orderRelationLabel(row as UnifiedOrderResource)}关联`"
|
||||
@click="openOrderRelation(row as UnifiedOrderResource)"
|
||||
><GitBranch :size="14" />{{ orderRelationLabel(row as UnifiedOrderResource) }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #orderStatus="{ row }"><StatusBadge :status="orderStatusLabel((row as UnifiedOrderResource).internalOrderStatus || (row as UnifiedOrderResource).mainStatus)" /></template>
|
||||
<template #measurementReviewStatus="{ row }"><StatusBadge :status="measurementStatusLabel((row as UnifiedOrderResource).measurement?.reviewStatus)" /></template>
|
||||
@@ -1100,6 +1121,14 @@ onMounted(() => {
|
||||
</ListPageLayout>
|
||||
</template>
|
||||
|
||||
<AppDrawer v-model="relationOpen" title="父子订单关联" size="min(860px, 94vw)" :show-footer="false" destroy-on-close>
|
||||
<OrderFulfillmentTreePanel
|
||||
v-if="relationOrder"
|
||||
:tenant-id="String(relationOrder.tenantId)"
|
||||
:order-id="String(relationOrder.orderId)"
|
||||
/>
|
||||
</AppDrawer>
|
||||
|
||||
<AppDialog v-model="noteOpen" title="添加内部备注" confirm-text="保存备注" :confirm-loading="noteSaving" @confirm="saveNote">
|
||||
<el-input v-model="noteContent" type="textarea" :rows="5" maxlength="2000" show-word-limit placeholder="请输入仅平台内部可见的备注" aria-label="内部备注内容" />
|
||||
</AppDialog>
|
||||
@@ -1130,6 +1159,10 @@ onMounted(() => {
|
||||
.table-card { overflow: hidden; }
|
||||
.row-actions { display: flex; align-items: center; gap: 4px; white-space: nowrap; }
|
||||
.row-actions :deep(.el-button) { margin-left: 0; }
|
||||
.inbound-relation-cell { display: grid; min-width: 0; gap: 5px; }
|
||||
.inbound-relation-cell > span { overflow-wrap: anywhere; }
|
||||
.order-relation-link { width: fit-content; max-width: 100%; height: auto; justify-content: flex-start; padding: 0; font-size: 12px; }
|
||||
.order-relation-link :deep(span) { min-width: 0; white-space: normal; text-align: left; }
|
||||
.primary-cell { display: grid; min-width: 0; gap: 4px; }
|
||||
.primary-cell strong { overflow-wrap: anywhere; }
|
||||
.primary-cell span { color: var(--cc-muted); font-size: 12px; overflow-wrap: anywhere; }
|
||||
|
||||
Reference in New Issue
Block a user