From 12dc5dfd82ac88980623a0b0dce24eafa12a325d Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 21 Sep 2026 23:19:17 +0800 Subject: [PATCH 01/36] feat: add mojia sku sync admin page --- src/core/navigation/adminNavigation.ts | 1 + .../overseas-warehouse/api/mojiaSkuSync.ts | 67 +++++++ .../pages/MojiaSkuSyncView.vue | 167 ++++++++++++++++++ src/modules/overseas-warehouse/permissions.ts | 3 + src/modules/overseas-warehouse/routes.ts | 6 + 5 files changed, 244 insertions(+) create mode 100644 src/modules/overseas-warehouse/api/mojiaSkuSync.ts create mode 100644 src/modules/overseas-warehouse/pages/MojiaSkuSyncView.vue diff --git a/src/core/navigation/adminNavigation.ts b/src/core/navigation/adminNavigation.ts index 34c738c8..63f677a5 100644 --- a/src/core/navigation/adminNavigation.ts +++ b/src/core/navigation/adminNavigation.ts @@ -170,6 +170,7 @@ export const adminNavigation: AdminNavigationNode[] = [ { key: 'fba-carrier-rates', title: '船期价格表', icon: CircleDollarSign, path: '/first-leg-pricing/carrier-rates', permission: 'firstleg:fba-pricing:rate:view' }, { key: 'vehicle-list', title: '车辆列表', icon: Truck, path: '/overseas-master-data/vehicles', permission: 'platform:overseas-master-data:vehicle:list' }, { key: 'download-center', title: '下载中心', icon: Download, path: '/downloads', permission: 'platform:download:list' }, + { key: 'mojia-sku-sync', title: '一件代发 SKU 同步', icon: Database, path: '/general/mojia-sku-sync', permission: 'platform:mojia-sku-sync:list' }, ], }, { diff --git a/src/modules/overseas-warehouse/api/mojiaSkuSync.ts b/src/modules/overseas-warehouse/api/mojiaSkuSync.ts new file mode 100644 index 00000000..81aa0ef2 --- /dev/null +++ b/src/modules/overseas-warehouse/api/mojiaSkuSync.ts @@ -0,0 +1,67 @@ +import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' + +export type MojiaSkuSyncStatus = 'PENDING' | 'SYNCING' | 'SUCCESS' | 'FAILED' + +export interface MojiaSkuSyncRecord { + id: string + skuCode: string + lengthCm: number + widthCm: number + heightCm: number + weightKg: number + imageFileId?: string | null + imageUrl?: string | null + measuredAt?: string | null + receivedAt?: string | null + syncStatus: MojiaSkuSyncStatus + syncFailureCode?: string | null + syncFailureReason?: string | null + lingxingSkuId?: string | null + revision: number + updatedAt?: string | null +} + +export interface MojiaSkuSyncPage { + records: MojiaSkuSyncRecord[] + page: number + size: number + total: number + pages: number +} + +export interface MojiaSkuSyncQuery { + page: number + size: number + skuCode?: string + syncStatus?: MojiaSkuSyncStatus +} + +const basePath = '/platform/v1/firstleg/mojia-sku-sync' + +export async function listMojiaSkuSync(query: MojiaSkuSyncQuery) { + const response = await http.get, ApiEnvelope>( + basePath, { params: query }, + ) + return unwrapApiResponse(response) +} + +export async function updateMojiaSkuSync(id: string, data: { + skuCode: string + lengthCm: number + widthCm: number + heightCm: number + weightKg: number + revision: number +}) { + const response = await http.put, ApiEnvelope>( + `${basePath}/${encodeURIComponent(id)}`, data, + ) + return unwrapApiResponse(response) +} + +export async function retryMojiaSkuSync(id: string, revision: number) { + const response = await http.post, ApiEnvelope>( + `${basePath}/${encodeURIComponent(id)}/retry`, { revision }, + ) + return unwrapApiResponse(response) +} diff --git a/src/modules/overseas-warehouse/pages/MojiaSkuSyncView.vue b/src/modules/overseas-warehouse/pages/MojiaSkuSyncView.vue new file mode 100644 index 00000000..44140a25 --- /dev/null +++ b/src/modules/overseas-warehouse/pages/MojiaSkuSyncView.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/src/modules/overseas-warehouse/permissions.ts b/src/modules/overseas-warehouse/permissions.ts index 4879b9e0..e73a8a7c 100644 --- a/src/modules/overseas-warehouse/permissions.ts +++ b/src/modules/overseas-warehouse/permissions.ts @@ -49,4 +49,7 @@ export const overseasWarehousePermissions = { productList: 'platform:overseas-warehouse:product:list', productCreate: 'platform:overseas-warehouse:product:create', productEdit: 'platform:overseas-warehouse:product:edit', + mojiaSkuSyncList: 'platform:mojia-sku-sync:list', + mojiaSkuSyncEdit: 'platform:mojia-sku-sync:edit', + mojiaSkuSyncRetry: 'platform:mojia-sku-sync:retry', } as const diff --git a/src/modules/overseas-warehouse/routes.ts b/src/modules/overseas-warehouse/routes.ts index 4394c76d..7155dc5a 100644 --- a/src/modules/overseas-warehouse/routes.ts +++ b/src/modules/overseas-warehouse/routes.ts @@ -130,4 +130,10 @@ export const overseasWarehouseRoutes = [ component: () => import('./pages/PalletDetailView.vue'), meta: { title: '托盘详情', description: '查看托盘基础信息与使用信息', permission: permission.palletList, showPageHeading: false }, }, + { + path: 'general/mojia-sku-sync', + name: 'mojia-sku-sync', + component: () => import('./pages/MojiaSkuSyncView.vue'), + meta: { title: '一件代发 SKU 同步', description: '查看墨家测量数据并同步领星 SKU 尺寸重量', permission: permission.mojiaSkuSyncList }, + }, ] satisfies RouteRecordRaw[] From 3cb8b5b70effaca2939268334867176b480e3fc3 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 21 Sep 2026 23:32:12 +0800 Subject: [PATCH 02/36] fix: keep mojia sku sync statuses to three states --- src/modules/overseas-warehouse/api/mojiaSkuSync.ts | 2 +- src/modules/overseas-warehouse/pages/MojiaSkuSyncView.vue | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/overseas-warehouse/api/mojiaSkuSync.ts b/src/modules/overseas-warehouse/api/mojiaSkuSync.ts index 81aa0ef2..e9e68004 100644 --- a/src/modules/overseas-warehouse/api/mojiaSkuSync.ts +++ b/src/modules/overseas-warehouse/api/mojiaSkuSync.ts @@ -1,6 +1,6 @@ import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' -export type MojiaSkuSyncStatus = 'PENDING' | 'SYNCING' | 'SUCCESS' | 'FAILED' +export type MojiaSkuSyncStatus = 'PENDING' | 'SUCCESS' | 'FAILED' export interface MojiaSkuSyncRecord { id: string diff --git a/src/modules/overseas-warehouse/pages/MojiaSkuSyncView.vue b/src/modules/overseas-warehouse/pages/MojiaSkuSyncView.vue index 44140a25..025d1417 100644 --- a/src/modules/overseas-warehouse/pages/MojiaSkuSyncView.vue +++ b/src/modules/overseas-warehouse/pages/MojiaSkuSyncView.vue @@ -47,10 +47,10 @@ const columns: DataTableColumn[] = [ ] const statusLabel: Record = { - PENDING: '待同步', SYNCING: '同步中', SUCCESS: '同步成功', FAILED: '同步失败', + PENDING: '待同步', SUCCESS: '同步成功', FAILED: '同步失败', } const statusType: Record = { - PENDING: 'warning', SYNCING: 'info', SUCCESS: 'success', FAILED: 'danger', + PENDING: 'warning', SUCCESS: 'success', FAILED: 'danger', } async function load() { From 2e2f3ed2d2ac139cb5b777a291b057295a7e2ca0 Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Tue, 22 Sep 2026 08:47:43 +0800 Subject: [PATCH 03/36] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../overseas-warehouse/api/lastMileOrders.ts | 1 + .../WarehouseTransferReviewDialog.test.ts | 96 +++++++++++++++++++ .../WarehouseTransferReviewDialog.vue | 11 ++- .../lastMilePresentation.test.ts | 13 +++ .../lastMilePresentation.ts | 8 +- .../pages/LastMileOrderDetailView.test.ts | 26 +++++ .../pages/LastMileOrderDetailView.vue | 12 ++- .../pages/LastMileOrdersView.vue | 7 +- src/modules/overseas-warehouse/types.ts | 1 + 9 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 src/modules/overseas-warehouse/components/WarehouseTransferReviewDialog.test.ts diff --git a/src/modules/overseas-warehouse/api/lastMileOrders.ts b/src/modules/overseas-warehouse/api/lastMileOrders.ts index c13e50c0..76c1161e 100644 --- a/src/modules/overseas-warehouse/api/lastMileOrders.ts +++ b/src/modules/overseas-warehouse/api/lastMileOrders.ts @@ -87,6 +87,7 @@ export interface LastMileOrderSummaryDto { deliveryOrderNo?: string | null outboundOrderNo?: string | null relabelOrderNo?: string | null + relabelApplications?: Array<{ relabelNo: string; status: string; quantity: number; createdAt: string }> relabelStatus?: string | null palletMergeFlag?: string | null /** Optional grouping facts returned by newer last-leg services. */ diff --git a/src/modules/overseas-warehouse/components/WarehouseTransferReviewDialog.test.ts b/src/modules/overseas-warehouse/components/WarehouseTransferReviewDialog.test.ts new file mode 100644 index 00000000..4a6c6991 --- /dev/null +++ b/src/modules/overseas-warehouse/components/WarehouseTransferReviewDialog.test.ts @@ -0,0 +1,96 @@ +import { flushPromises, mount } from '@vue/test-utils' +import { defineComponent, h } from 'vue' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import WarehouseTransferReviewDialog from './WarehouseTransferReviewDialog.vue' +import { getWarehouseTransfer, reviewWarehouseTransfer } from '../api/warehouseTransfers' + +vi.mock('../api/warehouseTransfers', async importOriginal => ({ + ...await importOriginal(), + getWarehouseTransfer: vi.fn(), + reviewWarehouseTransfer: vi.fn(), +})) + +const AppDialogStub = defineComponent({ + name: 'AppDialog', + props: { modelValue: Boolean }, + emits: ['confirm', 'update:modelValue'], + setup(props, { slots }) { + return () => props.modelValue ? h('section', { class: 'dialog' }, slots.default?.()) : null + }, +}) + +const DataTableStub = defineComponent({ + name: 'DataTable', + props: { data: { type: Array, default: () => [] } }, + setup(props) { + return () => h('div', { class: 'data-table' }, JSON.stringify(props.data)) + }, +}) + +function detail(transferNo: string) { + return { + id: `id-${transferNo}`, + transferNo, + packageCount: 2, + needRelabel: false, + status: 'PENDING_REVIEW', + items: [{ id: 'item-1', skuCode: 'SKU-1', productNameZh: '测试货物', packageCount: 2 }], + cartons: [], + pallets: [], + relabelBindings: [], + } +} + +function mountDialog(props: { modelValue: boolean; customerId: string; customerName: string; transferNo: string }) { + return mount(WarehouseTransferReviewDialog, { + props, + global: { + stubs: { + AppDialog: AppDialogStub, + DataTable: DataTableStub, + LoadErrorState: true, + ApiErrorMeta: true, + ElForm: { template: '
' }, + ElFormItem: { template: '' }, + ElRadioGroup: { template: '
' }, + ElRadioButton: { template: '' }, + ElInput: { template: '