front/src/composables/useFulfillBindLookup.ts
zzh 70d59a0ea9 feat: 接入 IDE 官网与地区地图,并同步运营/财务/订单前端改动
新增 IDE 官网静态资源与 D3 分区域地图布局,构建时复制至 dist/site;同步应收应付、代发打包、订单详情等页面优化。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-24 19:24:05 +08:00

218 lines
7.0 KiB
TypeScript

import { ref, reactive, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { $baseMessage } from '@/utils'
import { resolveOmsAssetUrl } from '@/api/oms/common'
import {
printLabelLookup,
type PrintLabelBindItem,
type PrintLabelLookupCandidate,
type PrintLabelLookupResult,
} from '@/api/operation'
import { useOperationScanFocus } from '@/composables/useOperationScanFocus'
export function normalizeFulfillBindLookup(raw: any): PrintLabelLookupResult {
return {
matched: raw?.matched ?? raw?.Matched,
matchedNumberType: raw?.matchedNumberType ?? raw?.MatchedNumberType,
warnings: raw?.warnings ?? raw?.Warnings ?? [],
orderId: raw?.orderId ?? raw?.OrderId,
serviceNo: raw?.serviceNo ?? raw?.ServiceNo,
clientNo: raw?.clientNo ?? raw?.ClientNo,
trackNo: raw?.trackNo ?? raw?.TrackNo,
productCode: raw?.productCode ?? raw?.ProductCode,
salesProductName: raw?.salesProductName ?? raw?.SalesProductName,
destinationCountry: raw?.destinationCountry ?? raw?.DestinationCountry,
destinationCountryName: raw?.destinationCountryName ?? raw?.DestinationCountryName,
orderStatus: raw?.orderStatus ?? raw?.OrderStatus,
orderStatusName: raw?.orderStatusName ?? raw?.OrderStatusName,
warehouseId: raw?.warehouseId ?? raw?.WarehouseId,
warehouseName: raw?.warehouseName ?? raw?.WarehouseName,
bindStatus: raw?.bindStatus ?? raw?.BindStatus ?? 'none',
bindQtyTotal: raw?.bindQtyTotal ?? raw?.BindQtyTotal ?? 0,
bindLineCount: raw?.bindLineCount ?? raw?.BindLineCount ?? 0,
bindItems: (raw?.bindItems ?? raw?.BindItems ?? []).map((item: any) => ({
bindType: item.bindType ?? item.BindType,
skuCode: item.skuCode ?? item.SkuCode,
skuName: item.skuName ?? item.SkuName,
trackingNo: item.trackingNo ?? item.TrackingNo,
productName: item.productName ?? item.ProductName,
attributeText: item.attributeText ?? item.AttributeText,
imageUrl: item.imageUrl ?? item.ImageUrl,
binCode: item.binCode ?? item.BinCode,
bindQty: item.bindQty ?? item.BindQty,
status: item.status ?? item.Status,
})),
candidates: (raw?.candidates ?? raw?.Candidates ?? []).map((c: any) => ({
orderId: c.orderId ?? c.OrderId,
serviceNo: c.serviceNo ?? c.ServiceNo,
clientNo: c.clientNo ?? c.ClientNo,
trackNo: c.trackNo ?? c.TrackNo,
orderStatusName: c.orderStatusName ?? c.OrderStatusName,
bindQtyTotal: c.bindQtyTotal ?? c.BindQtyTotal,
matchedHint: c.matchedHint ?? c.MatchedHint,
})),
}
}
export function useFulfillBindLookup(pageKey: string, defaultNumberType = 'auto') {
const { t } = useI18n()
const scanInputRef = ref<{ focus?: () => void } | null>(null)
const { keepScanFocus, maybeRefocusScan } = useOperationScanFocus(pageKey, scanInputRef)
const formData = reactive({
scanNumber: '',
numberType: defaultNumberType,
})
const lookingUp = ref(false)
const lookup = ref<PrintLabelLookupResult | null>(null)
const candidates = ref<PrintLabelLookupCandidate[]>([])
const bindItems = computed(() => lookup.value?.bindItems || [])
const hasNoneBindWarning = computed(() => (lookup.value?.warnings || []).includes('noneBind'))
const canConfirmPick = computed(
() => Boolean(lookup.value?.matched && lookup.value?.bindStatus === 'bound'),
)
const matchedTypeLabel = computed(() => {
const type = lookup.value?.matchedNumberType || ''
const key = `operation.printLabel.numberType.${type === 'customerorder' ? 'customerOrder' : type}`
const text = t(key)
return text === key ? type : text
})
const bindStatusLabel = computed(() => {
const code = lookup.value?.bindStatus || 'none'
const key = `order.myOrders.stockBind.status.${code}`
const text = t(key)
return text === key ? code : text
})
const bindStatusColor = computed(() => {
const code = lookup.value?.bindStatus
if (code === 'deducted') return 'red'
if (code === 'bound') return 'green'
return 'gray'
})
function itemImage(item: PrintLabelBindItem) {
return resolveOmsAssetUrl(item.imageUrl || '') || ''
}
function bindPrimaryName(item: PrintLabelBindItem) {
if (item.bindType === 'parcelItem') {
return item.productName || item.skuName || item.trackingNo || '-'
}
return item.skuName || item.skuCode || '-'
}
function bindCode(item: PrintLabelBindItem) {
return item.skuCode || ''
}
async function handleLookup(silent = false) {
const number = formData.scanNumber.trim()
if (!number) {
if (!silent) {
$baseMessage(t('operation.fulfillPack.message.numberRequired'), 'warning')
}
maybeRefocusScan()
return false
}
lookingUp.value = true
lookup.value = null
candidates.value = []
try {
const raw = await printLabelLookup({
number,
numberType: formData.numberType || 'auto',
})
const data = normalizeFulfillBindLookup(raw)
if (data.candidates?.length && !data.matched) {
candidates.value = data.candidates
lookup.value = null
if (!silent) {
$baseMessage(t('operation.printLabel.preview.multiMatch'), 'warning')
}
return false
}
lookup.value = data
candidates.value = []
if (formData.numberType === 'auto' && data.matchedNumberType) {
const mt = data.matchedNumberType
formData.numberType =
mt === 'customerorder' ? 'customerOrder' : mt === 'auto' ? 'auto' : mt
}
if (!silent) {
$baseMessage(t('operation.fulfillPack.message.lookupSuccess'), 'success')
}
return true
} catch (e: any) {
lookup.value = null
candidates.value = []
if (!silent) {
$baseMessage(e?.message || t('operation.fulfillPack.message.lookupFailed'), 'error')
}
return false
} finally {
lookingUp.value = false
maybeRefocusScan()
}
}
async function selectCandidate(c: PrintLabelLookupCandidate) {
formData.scanNumber = c.serviceNo || c.clientNo || String(c.orderId)
formData.numberType = c.serviceNo ? 'waybill' : 'customerOrder'
candidates.value = []
await handleLookup()
}
async function lookupByWaybill(serviceNo: string) {
formData.scanNumber = serviceNo
formData.numberType = 'waybill'
return handleLookup(true)
}
async function lookupByDomesticTracking(trackingNo: string) {
formData.scanNumber = trackingNo
formData.numberType = 'parcel'
return handleLookup(true)
}
function handleReset() {
formData.scanNumber = ''
formData.numberType = defaultNumberType
lookup.value = null
candidates.value = []
maybeRefocusScan()
}
async function onScanEnter() {
await handleLookup()
}
return {
scanInputRef,
keepScanFocus,
formData,
lookingUp,
lookup,
candidates,
bindItems,
hasNoneBindWarning,
canConfirmPick,
matchedTypeLabel,
bindStatusLabel,
bindStatusColor,
itemImage,
bindPrimaryName,
bindCode,
handleLookup,
selectCandidate,
lookupByWaybill,
lookupByDomesticTracking,
handleReset,
onScanEnter,
}
}