diff --git a/src/views/order/platformStoreOrder/CreateLogisticsModal.vue b/src/views/order/platformStoreOrder/CreateLogisticsModal.vue index f495c7f..3b7c7f6 100644 --- a/src/views/order/platformStoreOrder/CreateLogisticsModal.vue +++ b/src/views/order/platformStoreOrder/CreateLogisticsModal.vue @@ -3,7 +3,7 @@ import { computed, ref, watch } from 'vue' import { useI18n } from 'vue-i18n' import { useRouter } from 'vue-router' import { Modal } from '@arco-design/web-vue' -import { IconPlus, IconDelete, IconQuestionCircle } from '@arco-design/web-vue/es/icon' +import { IconPlus, IconDelete, IconQuestionCircle, IconCopy } from '@arco-design/web-vue/es/icon' import { batchCreatePlatformStoreFulfill, batchCreatePlatformStoreFulfillWms, @@ -16,6 +16,7 @@ import { } from '@/api/order' import { getSenderAddressList } from '@/api/order/senderAddress' import { getExportQualificationList } from '@/api/customer/exportQualification' +import { getSalesProductList } from '@/api/channel/salesProduct' import { getDictListAll } from '@/api/system/dict' import { resolveOmsAssetUrl } from '@/api/oms/common' import SlidingTabs from '@/components/SlidingTabs/index.vue' @@ -23,6 +24,19 @@ import ExportCustoms9610GuideModal from '@/views/order/placeOrder/components/Exp import { $baseMessage } from '@/utils' import { normalizeCargoAttributeCode } from '@/utils/cargoAttribute' import { normalizeListRow, pickField } from '@/utils/warehouseListMapper' +import { + formatWarehouseShipAddress, + formatWarehouseShipRecipient, +} from '@/utils/warehouseShipAddress' + +type WarehouseOption = { + label: string + value: number + recipient?: string + contactPhone?: string + shippingAddress?: string + remark?: string +} import type { CreateLogisticsMode } from './constants' @@ -92,7 +106,7 @@ const props = defineProps<{ mode: CreateLogisticsMode storeOrderIds: number[] productOptions: Array<{ label: string; value: string }> - warehouseOptions: Array<{ label: string; value: number }> + warehouseOptions: WarehouseOption[] defaultProductCode?: string defaultWarehouseId?: number customerId?: number @@ -141,6 +155,7 @@ const platformCode = ref() const inboundMethod = ref<'selfShip' | 'doorPickup'>('selfShip') const senderAddressId = ref() const expectPickupTime = ref() +const carrierService = ref() const agreementChecked = ref(false) const agreementModalVisible = ref(false) const exportCustomsGuideVisible = ref(false) @@ -150,11 +165,70 @@ const exportCustomsModeOptions = ref([]) const ecommercePlatformOptions = ref([]) const exportQualificationOptions = ref([]) const pickupAddressOptions = ref>([]) +const domesticPickupProductOptions = ref([]) +const pickupAddressLoading = ref(false) function goToExportQualificationManage() { router.push({ path: '/user/exportQualification' }) } +const warehouseShipAddressList = computed(() => props.warehouseOptions || []) +const warehousesWithShipAddress = computed(() => + warehouseShipAddressList.value.filter((opt) => warehouseHasAddress(opt)), +) + +function warehouseHasAddress(opt?: WarehouseOption) { + if (!opt) return false + return Boolean(opt.recipient || opt.contactPhone || opt.shippingAddress) +} + +function displayWarehouseRecipient(opt?: WarehouseOption) { + return formatWarehouseShipRecipient(opt?.recipient, '') +} + +function displayWarehouseShippingAddress(opt?: WarehouseOption) { + return formatWarehouseShipAddress(opt?.shippingAddress, '') +} + +function buildWarehouseShipCopyText(opt?: WarehouseOption) { + if (!opt) return '' + const lines = [ + opt.label || '', + `收件人:${displayWarehouseRecipient(opt)}`, + `电话:${opt.contactPhone || '-'}`, + `地址:${displayWarehouseShippingAddress(opt)}`, + ] + const clientNo = String(activeDraft.value?.clientNo ?? '').trim() + if (clientNo) lines.push(`客户单号:${clientNo}`) + return lines.filter(Boolean).join('\n') +} + +async function copyWarehouseShipAddress(opt?: WarehouseOption) { + const text = buildWarehouseShipCopyText(opt) + if (!text.trim()) { + $baseMessage('该仓库暂未配置收货地址', 'warning') + return + } + try { + await navigator.clipboard.writeText(text) + $baseMessage('仓库寄件信息已复制', 'success') + } catch { + const area = document.createElement('textarea') + area.value = text + area.style.position = 'fixed' + area.style.left = '-9999px' + document.body.appendChild(area) + area.select() + try { + document.execCommand('copy') + $baseMessage('仓库寄件信息已复制', 'success') + } catch { + $baseMessage('复制失败', 'error') + } + document.body.removeChild(area) + } +} + const modalVisible = computed({ get: () => props.visible, set: (v: boolean) => emit('update:visible', v), @@ -347,6 +421,7 @@ async function loadExportQualifications() { async function loadPickupAddresses() { const customerId = resolveCandidatesCustomerId() + pickupAddressLoading.value = true try { const res = await getSenderAddressList({ page: 1, @@ -366,6 +441,38 @@ async function loadPickupAddresses() { .filter((x): x is { label: string; value: number } => !!x) } catch { pickupAddressOptions.value = [] + } finally { + pickupAddressLoading.value = false + } +} + +async function loadDomesticPickupProducts() { + try { + const [groupRows, productRes] = await Promise.all([ + getDictListAll({ dictType: 'sales_product_group' }), + getSalesProductList({ page: 1, pageSize: 200, isActive: 2 }), + ]) + const domesticPickupGroupCodes = new Set( + (groupRows || []) + .filter((d: any) => String(d?.dictName ?? d?.DictName ?? '').includes('国内揽收')) + .map((d: any) => String(d?.dictCode ?? d?.DictCode ?? '').trim()) + .filter(Boolean), + ) + const items = (productRes as any)?.items || [] + domesticPickupProductOptions.value = items + .filter((row: any) => { + const groupCode = String(row?.productGroup ?? row?.ProductGroup ?? '').trim() + return domesticPickupGroupCodes.size > 0 && domesticPickupGroupCodes.has(groupCode) + }) + .map((row: any) => { + const code = String(row?.productCode ?? row?.ProductCode ?? '').trim() + const name = String(row?.productName ?? row?.ProductName ?? '').trim() + if (!code) return null + return { value: code, label: name ? `${name} (${code})` : code } + }) + .filter((x: SelectOption | null): x is SelectOption => !!x) + } catch { + domesticPickupProductOptions.value = [] } } @@ -389,6 +496,10 @@ function buildParcelBasicPayload() { inboundMethod: inboundMethod.value, senderAddressId: inboundMethod.value === 'doorPickup' ? senderAddressId.value : undefined, expectPickupTime: inboundMethod.value === 'doorPickup' ? expectPickupTime.value : undefined, + carrierService: + inboundMethod.value === 'doorPickup' && String(carrierService.value || '').trim() + ? String(carrierService.value).trim() + : undefined, } } @@ -874,6 +985,7 @@ watch( inboundMethod.value = 'selfShip' senderAddressId.value = undefined expectPickupTime.value = undefined + carrierService.value = undefined agreementChecked.value = false await loadBasicDicts() await loadDrafts() @@ -883,8 +995,11 @@ watch( const est = first.lines.reduce((s, x) => s + Math.max(1, Number(x.quantity) || 0) * 0.01, 0) actualWeight.value = String(Number(est.toFixed(3)) || 0.01) } - await loadExportQualifications() - await loadPickupAddresses() + await Promise.all([ + loadExportQualifications(), + loadPickupAddresses(), + loadDomesticPickupProducts(), + ]) if (isFulfill.value) await loadCandidates() }, { immediate: true }, @@ -909,6 +1024,7 @@ watch(needExportQualification, (need) => { watch(inboundMethod, (method) => { if (!props.visible || method !== 'doorPickup') return void loadPickupAddresses() + if (!domesticPickupProductOptions.value.length) void loadDomesticPickupProducts() }) @@ -1117,11 +1233,106 @@ watch(inboundMethod, (method) => { size="small" class="inbound-method-radios" > - 自寄到仓 + + + 自寄到仓 + + + + + + 上门揽收 - + @@ -1129,12 +1340,24 @@ watch(inboundMethod, (method) => { @@ -1708,6 +1950,14 @@ watch(inboundMethod, (method) => { grid-column: 1 / -1; } +.inbound-pickup-batch-hint { + margin: -4px 0 8px; + grid-column: 1 / -1; + font-size: 12px; + line-height: 1.45; + color: var(--color-text-3, #86909c); +} + .inbound-method-radios { display: inline-flex; flex-wrap: wrap; @@ -2474,3 +2724,136 @@ watch(inboundMethod, (method) => { } + + diff --git a/src/views/order/platformStoreOrder/index.vue b/src/views/order/platformStoreOrder/index.vue index 5d274a7..c1ee30f 100644 --- a/src/views/order/platformStoreOrder/index.vue +++ b/src/views/order/platformStoreOrder/index.vue @@ -82,7 +82,15 @@ const fulfillStatus = ref(resolveFulfillStatusFromQuery()) const platformCode = ref(resolvePlatformFromQuery()) const keyword = ref('') const summary = ref>({}) -const warehouseOptions = ref>([]) +type WarehouseOption = { + label: string + value: number + recipient?: string + contactPhone?: string + shippingAddress?: string + remark?: string +} +const warehouseOptions = ref([]) const warehouseNameMap = computed(() => buildWarehouseNameMap(warehouseOptions.value)) const productOptions = ref>([]) const wmsProductOptions = ref>([]) @@ -728,8 +736,12 @@ onMounted(async () => { const warehouseTask = getWarehouseList({ page: 1, pageSize: 200 }) .then(({ items }) => { warehouseOptions.value = (items || []).map((w: any) => ({ - label: w.warehouseName || `#${w.id}`, - value: Number(w.id), + label: w.warehouseName || w.WarehouseName || `#${w.id ?? w.Id}`, + value: Number(w.id ?? w.Id), + recipient: String(w.recipient ?? w.Recipient ?? '').trim() || undefined, + contactPhone: String(w.contactPhone ?? w.ContactPhone ?? '').trim() || undefined, + shippingAddress: String(w.shippingAddress ?? w.ShippingAddress ?? '').trim() || undefined, + remark: String(w.remark ?? w.Remark ?? '').trim() || undefined, })) if (warehouseOptions.value.length) matchWarehouseId.value = warehouseOptions.value[0].value })