From aa189146a0a2bc2c9d5809b8fc4ede0555cbce5a Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Thu, 24 Sep 2026 10:11:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/DeliveryOrderCreateView.vue | 3 ++- .../api/warehouseServices.ts | 5 +++-- .../views/ReturnOrdersView.vue | 3 ++- .../views/WarehouseServiceView.vue | 7 ++++--- src/utils/idempotencyKey.ts | 18 ++++++++++++++++++ 5 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 src/utils/idempotencyKey.ts diff --git a/src/modules/order-management/views/DeliveryOrderCreateView.vue b/src/modules/order-management/views/DeliveryOrderCreateView.vue index 4a41b89..0468eb7 100644 --- a/src/modules/order-management/views/DeliveryOrderCreateView.vue +++ b/src/modules/order-management/views/DeliveryOrderCreateView.vue @@ -5,6 +5,7 @@ import { ArrowLeft, Plus, Trash2 } from '@lucide/vue' import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus' import { useAuthStore } from '@/modules/auth/public' import { useUnsavedWorkRegistration } from '@/navigation/workspace-tabs/unsavedWork' +import { createIdempotencyKey } from '@/utils/idempotencyKey' import { createDeliveryOrder, type DeliveryOrderFee, type DeliveryTransferPayload } from '../api/deliveryOrders' type DeliveryCreateForm = Omit @@ -112,7 +113,7 @@ async function submit() { submitError.value = '' const valid = await formRef.value?.validate().catch(() => false) if (!valid || !validateFees()) return - if (!pendingIdempotencyKey) pendingIdempotencyKey = crypto.randomUUID() + if (!pendingIdempotencyKey) pendingIdempotencyKey = createIdempotencyKey() submitting.value = true try { const created = await createDeliveryOrder(requestPayload()) diff --git a/src/modules/overseas-warehouse/api/warehouseServices.ts b/src/modules/overseas-warehouse/api/warehouseServices.ts index 708b70f..f52dce0 100644 --- a/src/modules/overseas-warehouse/api/warehouseServices.ts +++ b/src/modules/overseas-warehouse/api/warehouseServices.ts @@ -1,4 +1,5 @@ import { overseasApiRequest, overseasBlobRequest } from '@/api/overseasHttp' +import { createIdempotencyKey } from '@/utils/idempotencyKey' export type RelabelDeliveryMethod = 'TRUCK' | 'EXPRESS' | 'TRUCK_PICKUP' | 'EXPRESS_PICKUP' @@ -78,7 +79,7 @@ export function getRelabel(relabelNo: string) { return overseasApiRequest({ method: 'GET', url: `/merchant/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}` }) } export function confirmTransferMeasurement(transferNo: string, remark?: string) { - return overseasApiRequest>({ method: 'POST', url: `/merchant/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}/confirm-measurement`, data: { idempotencyKey: crypto.randomUUID(), remark } }) + return overseasApiRequest>({ method: 'POST', url: `/merchant/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}/confirm-measurement`, data: { idempotencyKey: createIdempotencyKey(), remark } }) } export interface TransferCartonLabel { transferNo: string; cartons: Array<{ cartonMark: string; customerCartonMark?: string; cartonIndex: number; totalCartons: number; status: string }>; printableHtml: string } export function getTransferCartonLabel(transferNo: string) { @@ -109,7 +110,7 @@ export function uploadRelabelFile(relabelNo: string, scope: 'CARTON_TOTAL' | 'SK return overseasApiRequest({ method: 'POST', url: `/merchant/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}/files`, params: { scope, itemId }, data, headers: { 'Content-Type': 'multipart/form-data' } }) } export function confirmRelabelCompletion(relabelNo: string, remark?: string) { - return overseasApiRequest({ method: 'POST', url: `/merchant/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}/confirm-completion`, data: { idempotencyKey: crypto.randomUUID(), remark } }) + return overseasApiRequest({ method: 'POST', url: `/merchant/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}/confirm-completion`, data: { idempotencyKey: createIdempotencyKey(), remark } }) } export function downloadRelabelCompletionPhoto(relabelNo: string, fileId: string) { return overseasBlobRequest({ method: 'GET', url: `/merchant/v1/last-leg/relabel-applications/${encodeURIComponent(relabelNo)}/completion-photos/${encodeURIComponent(fileId)}/download` }) diff --git a/src/modules/overseas-warehouse/views/ReturnOrdersView.vue b/src/modules/overseas-warehouse/views/ReturnOrdersView.vue index e4241a5..33e42b5 100644 --- a/src/modules/overseas-warehouse/views/ReturnOrdersView.vue +++ b/src/modules/overseas-warehouse/views/ReturnOrdersView.vue @@ -8,6 +8,7 @@ import DataTable, { type DataTableColumn } from '@/components/common/DataTable.v import type { TableAction } from '@/components/common/tableActions' import { useAuthStore } from '@/modules/auth/public' import { getMerchantWarehouses, type MerchantWarehouse } from '@/modules/first-leg/public' +import { createIdempotencyKey } from '@/utils/idempotencyKey' import { createReturnOrder, downloadReturnPhoto, getReturnOrder, listReturnOrders, updateReturnOrder, type ReturnOrderDetail, type ReturnOrderSummary, type ReturnReceipt } from '../api/returnOrders' const props = withDefaults(defineProps<{ createPage?: boolean; detailPage?: boolean; returnNo?: string }>(), { createPage: false, detailPage: false, returnNo: '' }) @@ -185,7 +186,7 @@ async function submit() { if (duplicateSkuIds.value.size) { ElMessage.warning('同一退货单不能重复填写相同 SKU'); return } creating.value = true try { - const payload = { overseasWarehouseId: form.overseasWarehouseId, warehouseContactName: form.warehouseContactName.trim(), warehouseContactPhone: form.warehouseContactPhone.trim(), customerReferenceNo: form.customerReferenceNo || undefined, remark: form.remark || undefined, needRelabel: form.needRelabel, idempotencyKey: crypto.randomUUID(), items: items.map(item => ({ ...item, productNameZh: item.productNameZh || undefined })) } + const payload = { overseasWarehouseId: form.overseasWarehouseId, warehouseContactName: form.warehouseContactName.trim(), warehouseContactPhone: form.warehouseContactPhone.trim(), customerReferenceNo: form.customerReferenceNo || undefined, remark: form.remark || undefined, needRelabel: form.needRelabel, idempotencyKey: createIdempotencyKey(), items: items.map(item => ({ ...item, productNameZh: item.productNameZh || undefined })) } const detail = editingReturnNo.value ? await updateReturnOrder(editingReturnNo.value, payload) : await createReturnOrder(payload) ElMessage.success(editingReturnNo.value ? '退货预报已更新' : '退货预报已创建'); showCreate.value = false; editingReturnNo.value = ''; resetForm(); await router.push({ name: 'overseas-return-relabel-detail', params: { returnNo: detail.order.returnNo } }) } finally { creating.value = false } diff --git a/src/modules/overseas-warehouse/views/WarehouseServiceView.vue b/src/modules/overseas-warehouse/views/WarehouseServiceView.vue index 21e8a63..b0dde44 100644 --- a/src/modules/overseas-warehouse/views/WarehouseServiceView.vue +++ b/src/modules/overseas-warehouse/views/WarehouseServiceView.vue @@ -25,6 +25,7 @@ import { type MerchantWarehouseDetail, } from '@/modules/first-leg/api/firstLeg' import { loadAllWarehouseRecords } from '@/modules/first-leg/utils/warehousePagination' +import { createIdempotencyKey } from '@/utils/idempotencyKey' import { createReturnRelabel, uploadReturnRelabelFile } from '../api/returnOrders' type ServiceType = 'labeling' | 'transfer' | 'disposal' | 'relabel' @@ -914,7 +915,7 @@ async function submitReturnRelabel() { destinationFbaWarehouseId: String(destination.id), destinationFbaWarehouseCode: destination.warehouseCode, cartonRemark: form.remark.trim() || undefined, - idempotencyKey: crypto.randomUUID(), + idempotencyKey: createIdempotencyKey(), }) await uploadReturnRelabelFile(created.relabelNo, fileScope, selectedFile) ElMessage.success(`贴标申请 ${created.relabelNo} 已提交,等待审核`) @@ -1002,7 +1003,7 @@ async function submit(action: 'draft' | 'submit' = 'submit') { } saving.value = true try { - const idempotencyKey = crypto.randomUUID() + const idempotencyKey = createIdempotencyKey() if (props.service === 'labeling') { const sourceInboundOrders = [...new Set(cargoRows.value.map((item) => item.sourceInboundOrderNo || item.sourceInboundCode).filter((value): value is string => Boolean(value)))].join('、').slice(0, 128) const items = labelingMode.value === 'carton' @@ -1260,7 +1261,7 @@ async function resubmitRejectedRelabel() { selectedRelabelDetail.value = await resubmitRelabel(application.relabelNo, { labelDeadline: application.labelDeadline || '', contactName: application.contactName || '', contactPhone: application.contactPhone || '', remark: value?.trim() || undefined, - idempotencyKey: crypto.randomUUID(), + idempotencyKey: createIdempotencyKey(), }) ElMessage.success('贴标单已重新提交审核') } catch (error) { diff --git a/src/utils/idempotencyKey.ts b/src/utils/idempotencyKey.ts new file mode 100644 index 0000000..1d5caea --- /dev/null +++ b/src/utils/idempotencyKey.ts @@ -0,0 +1,18 @@ +export function createIdempotencyKey(prefix = 'client') { + const nativeUuid = globalThis.crypto?.randomUUID + if (typeof nativeUuid === 'function') return nativeUuid.call(globalThis.crypto) + + const bytes = new Uint8Array(16) + if (typeof globalThis.crypto?.getRandomValues === 'function') { + globalThis.crypto.getRandomValues(bytes) + } else { + for (let index = 0; index < bytes.length; index += 1) { + bytes[index] = Math.floor(Math.random() * 256) + } + } + bytes[6] = (bytes[6] & 0x0f) | 0x40 + bytes[8] = (bytes[8] & 0x3f) | 0x80 + const hex = Array.from(bytes, value => value.toString(16).padStart(2, '0')).join('') + const uuid = `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}` + return prefix ? `${prefix}-${uuid}` : uuid +}