From a917216c3bf2a6b6d00d9ec4866c6711ff468634 Mon Sep 17 00:00:00 2001
From: zzh <1368735060@qq.com>
Date: Tue, 20 Oct 2026 11:23:15 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9B=E5=BB=BA=E8=BF=90=E5=8D=95/?=
=?UTF-8?q?=E4=BB=A3=E5=8F=91=E8=BF=90=E5=8D=95=E5=BC=B9=E7=AA=97=E8=A1=A5?=
=?UTF-8?q?=E9=BD=90=E5=9F=BA=E6=9C=AC=E4=BF=A1=E6=81=AF=E4=B8=8E=E4=B8=8B?=
=?UTF-8?q?=E5=8D=95=E5=8D=8F=E8=AE=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Cursor
---
src/locale/en-US.ts | 2 +
src/locale/zh-CN.ts | 1 +
.../CreateLogisticsModal.vue | 526 ++++++++++++++++--
3 files changed, 474 insertions(+), 55 deletions(-)
diff --git a/src/locale/en-US.ts b/src/locale/en-US.ts
index c40dcb7..fe7a24a 100644
--- a/src/locale/en-US.ts
+++ b/src/locale/en-US.ts
@@ -1058,6 +1058,8 @@ export default {
'order.platformStoreOrder.createLogistics.tip': 'Review recipient and line items for each order, then confirm the shipping product before submitting.',
'order.platformStoreOrder.createLogistics.product': 'Shipping Product',
'order.platformStoreOrder.createLogistics.productRequired': 'Please select a shipping product',
+ 'order.platformStoreOrder.createLogistics.basicRequired':
+ 'Please complete waybill basic info (warehouse, cargo type, weight, dimensions, inbound method, etc.)',
'order.platformStoreOrder.createLogistics.orderList': 'Selected Orders',
'order.platformStoreOrder.createLogistics.receiverTitle': 'Recipient Check',
'order.platformStoreOrder.createLogistics.linesTitle': 'Line Items',
diff --git a/src/locale/zh-CN.ts b/src/locale/zh-CN.ts
index 5478774..67e1dba 100644
--- a/src/locale/zh-CN.ts
+++ b/src/locale/zh-CN.ts
@@ -1047,6 +1047,7 @@ export default {
'order.platformStoreOrder.createLogistics.tip': '请逐单核对收件人与商品明细,确认运输方式后再提交,避免下错单。',
'order.platformStoreOrder.createLogistics.product': '运输方式',
'order.platformStoreOrder.createLogistics.productRequired': '请选择运输方式',
+ 'order.platformStoreOrder.createLogistics.basicRequired': '请完整填写运单基本信息(仓库、货物属性、实重、长宽高、到仓方式等)',
'order.platformStoreOrder.createLogistics.orderList': '待创建清单',
'order.platformStoreOrder.createLogistics.receiverTitle': '收件人校对',
'order.platformStoreOrder.createLogistics.linesTitle': '商品明细',
diff --git a/src/views/order/platformStoreOrder/CreateLogisticsModal.vue b/src/views/order/platformStoreOrder/CreateLogisticsModal.vue
index e659513..699e6b4 100644
--- a/src/views/order/platformStoreOrder/CreateLogisticsModal.vue
+++ b/src/views/order/platformStoreOrder/CreateLogisticsModal.vue
@@ -13,13 +13,33 @@ import {
postOrderStockBindInventoryCandidates,
postOrderStockBindParcelCandidates,
} from '@/api/order'
+import { getSenderAddressList } from '@/api/order/senderAddress'
+import { getExportQualificationList } from '@/api/customer/exportQualification'
+import { getDictListAll } from '@/api/system/dict'
import { resolveOmsAssetUrl } from '@/api/oms/common'
import SlidingTabs from '@/components/SlidingTabs/index.vue'
import { $baseMessage } from '@/utils'
+import { normalizeCargoAttributeCode } from '@/utils/cargoAttribute'
import { normalizeListRow, pickField } from '@/utils/warehouseListMapper'
import type { CreateLogisticsMode } from './constants'
+type SelectOption = { label: string; value: string }
+type QualOption = { id: number; label: string; isDefault: boolean }
+
+const SAMPLE_EXPORT_CUSTOMS_MODE_CODE = '9610Simple'
+const EXPORT_CUSTOMS_MODE_9610_SUMMARY = '9610Summary'
+const AGREEMENT_TEXT = `《物流下单用户协议》及《物流服务赔偿协议》
+
+一、服务说明
+您确认通过本系统创建的运单/代发订单,将按所选运输方式、仓库与件重尺计费并履约。
+
+二、责任与理赔
+请以正式协议文本为准;勾选即视为已阅读并同意全部条款。
+
+三、勾选确认
+本人已仔细阅读并充分理解全部条款,自愿接受本协议所有约定。`
+
export type { CreateLogisticsMode }
export type BindDraftItem = {
@@ -43,6 +63,8 @@ type OrderDraft = {
customerId?: number
warehouseId?: number
platformOrderNo: string
+ /** 可编辑客户单号,默认平台订单号 */
+ clientNo: string
orderCountry: string
recipientName: string
recipientPhone: string
@@ -103,6 +125,28 @@ const batchMatchDeclaration = ref(false)
const batchQtyPerOrder = ref(1)
let searchTimer: ReturnType | null = null
+/** 运单基本信息(对齐下单页) */
+const cargoAttribute = ref('general')
+const actualWeight = ref(undefined)
+const quantity = ref(1)
+const lengthCm = ref(10)
+const widthCm = ref(10)
+const heightCm = ref(10)
+const exportCustomsMode = ref(SAMPLE_EXPORT_CUSTOMS_MODE_CODE)
+const exportQualificationId = ref()
+const platformCode = ref()
+const inboundMethod = ref<'selfShip' | 'doorPickup'>('selfShip')
+const senderAddressId = ref()
+const expectPickupTime = ref()
+const agreementChecked = ref(false)
+const agreementModalVisible = ref(false)
+const dictLoading = ref(false)
+const cargoAttributeOptions = ref([])
+const exportCustomsModeOptions = ref([])
+const ecommercePlatformOptions = ref([])
+const exportQualificationOptions = ref([])
+const pickupAddressOptions = ref>([])
+
const modalVisible = computed({
get: () => props.visible,
set: (v: boolean) => emit('update:visible', v),
@@ -167,17 +211,172 @@ const warehouseLabel = computed(() => {
return opt?.label || '-'
})
-const canSubmit = computed(() => {
+const needExportQualification = computed(
+ () =>
+ String(exportCustomsMode.value ?? '').trim().toLowerCase() ===
+ EXPORT_CUSTOMS_MODE_9610_SUMMARY.toLowerCase(),
+)
+
+const basicInfoReady = computed(() => {
if (!productCode.value) return false
+ if (!warehouseId.value) return false
+ if (!String(cargoAttribute.value || '').trim()) return false
+ if (!(Number(actualWeight.value) > 0)) return false
+ if (!(Number(quantity.value) > 0)) return false
+ if (!(Number(lengthCm.value) > 0) || !(Number(widthCm.value) > 0) || !(Number(heightCm.value) > 0)) {
+ return false
+ }
+ if (needExportQualification.value) {
+ if (!(Number(exportQualificationId.value) > 0)) return false
+ if (!String(platformCode.value || '').trim()) return false
+ }
+ if (inboundMethod.value === 'doorPickup') {
+ if (!(Number(senderAddressId.value) > 0)) return false
+ if (!expectPickupTime.value) return false
+ }
+ if (drafts.value.some((d) => !String(d.clientNo || '').trim())) return false
+ return true
+})
+
+const canSubmit = computed(() => {
+ if (!basicInfoReady.value) return false
+ if (!agreementChecked.value) return false
if (drafts.value.length === 0) return false
if (drafts.value.some((d) => d.receiverMissing || d.linesMissing)) return false
if (isFulfill.value) {
- if (!warehouseId.value) return false
if (drafts.value.some((d) => d.bindItems.length === 0)) return false
}
return true
})
+function resolveExportCustomsMode9610Simple() {
+ const opts = exportCustomsModeOptions.value
+ const byCode = opts.find(
+ (o) => o.value === SAMPLE_EXPORT_CUSTOMS_MODE_CODE || o.value.toLowerCase() === '9610simple',
+ )
+ if (byCode) return byCode.value
+ const byLabel = opts.find((o) => /9610.*简易|简易.*9610/i.test(o.label))
+ if (byLabel) return byLabel.value
+ return SAMPLE_EXPORT_CUSTOMS_MODE_CODE
+}
+
+function mapDictOptions(rows: any[]): SelectOption[] {
+ return (rows || [])
+ .map((row) => {
+ const value = String(row?.dictCode ?? row?.DictCode ?? '').trim()
+ const label = String(row?.dictName ?? row?.DictName ?? value).trim()
+ if (!value) return null
+ return { label: label || value, value }
+ })
+ .filter((x): x is SelectOption => !!x)
+}
+
+async function loadBasicDicts() {
+ dictLoading.value = true
+ try {
+ const [cargoRows, exportRows, platformRows] = await Promise.all([
+ getDictListAll({ dictType: 'cargo_attribute' }),
+ getDictListAll({ dictType: 'export_customs_mode' }),
+ getDictListAll({ dictType: 'e-commerce_platform' }),
+ ])
+ cargoAttributeOptions.value = mapDictOptions(cargoRows as any[])
+ exportCustomsModeOptions.value = mapDictOptions(exportRows as any[])
+ ecommercePlatformOptions.value = mapDictOptions(platformRows as any[])
+ if (!cargoAttribute.value && cargoAttributeOptions.value[0]) {
+ cargoAttribute.value = normalizeCargoAttributeCode(cargoAttributeOptions.value[0].value)
+ } else {
+ cargoAttribute.value = normalizeCargoAttributeCode(cargoAttribute.value)
+ }
+ exportCustomsMode.value = resolveExportCustomsMode9610Simple()
+ } catch {
+ cargoAttributeOptions.value = [
+ { label: '普货', value: 'general' },
+ { label: '带电', value: 'electronics' },
+ { label: '敏感', value: 'sensitive' },
+ ]
+ exportCustomsModeOptions.value = [
+ { label: '9610 简易申报', value: SAMPLE_EXPORT_CUSTOMS_MODE_CODE },
+ { label: '9610 汇总申报', value: EXPORT_CUSTOMS_MODE_9610_SUMMARY },
+ ]
+ } finally {
+ dictLoading.value = false
+ }
+}
+
+async function loadExportQualifications() {
+ const customerId = resolveCandidatesCustomerId()
+ try {
+ const res = await getExportQualificationList({
+ page: 1,
+ pageSize: 100,
+ customerId,
+ })
+ exportQualificationOptions.value = (res.items || [])
+ .map((item: Record) => {
+ const id = Number(pickField(item, 'id', 'Id') ?? 0)
+ if (!id) return null
+ const companyName = String(pickField(item, 'companyName', 'CompanyName') ?? '').trim()
+ const creditCode = String(pickField(item, 'socialCreditCode', 'SocialCreditCode') ?? '').trim()
+ const isDefault = Boolean(pickField(item, 'isDefault', 'IsDefault'))
+ const base = companyName || creditCode || `#${id}`
+ return { id, label: isDefault ? `${base}(默认)` : base, isDefault }
+ })
+ .filter((x): x is QualOption => !!x)
+ if (!exportQualificationId.value) {
+ const def =
+ exportQualificationOptions.value.find((x) => x.isDefault) || exportQualificationOptions.value[0]
+ exportQualificationId.value = def?.id
+ }
+ } catch {
+ exportQualificationOptions.value = []
+ }
+}
+
+async function loadPickupAddresses() {
+ const customerId = resolveCandidatesCustomerId()
+ try {
+ const res = await getSenderAddressList({
+ page: 1,
+ pageSize: 200,
+ addressType: 'pickup',
+ customerId,
+ })
+ pickupAddressOptions.value = (res.items || [])
+ .map((item: Record) => {
+ const id = Number(pickField(item, 'id', 'Id') ?? 0)
+ if (!id) return null
+ const name = String(pickField(item, 'contactName', 'ContactName', 'name', 'Name') ?? '').trim()
+ const phone = String(pickField(item, 'phone', 'Phone') ?? '').trim()
+ const label = [name, phone].filter(Boolean).join(' · ') || `#${id}`
+ return { label, value: id }
+ })
+ .filter((x): x is { label: string; value: number } => !!x)
+ } catch {
+ pickupAddressOptions.value = []
+ }
+}
+
+function openAgreementModal() {
+ agreementModalVisible.value = true
+}
+
+function buildParcelBasicPayload() {
+ return {
+ cargoAttribute: normalizeCargoAttributeCode(cargoAttribute.value),
+ actualWeight: Number(actualWeight.value),
+ quantity: Number(quantity.value),
+ length: Number(lengthCm.value),
+ width: Number(widthCm.value),
+ height: Number(heightCm.value),
+ exportCustomsMode: String(exportCustomsMode.value || '').trim() || resolveExportCustomsMode9610Simple(),
+ exportQualificationId: needExportQualification.value ? exportQualificationId.value : undefined,
+ platformCode: needExportQualification.value ? platformCode.value : undefined,
+ inboundMethod: inboundMethod.value,
+ senderAddressId: inboundMethod.value === 'doorPickup' ? senderAddressId.value : undefined,
+ expectPickupTime: inboundMethod.value === 'doorPickup' ? expectPickupTime.value : undefined,
+ }
+}
+
function close() {
emit('update:visible', false)
}
@@ -208,11 +407,13 @@ function mapDetail(raw: Record): OrderDraft {
const shipLines = lines
const customerIdRaw = Number(pickField(row, 'customerId', 'CustomerId') ?? 0)
const warehouseIdRaw = Number(pickField(row, 'warehouseId', 'WarehouseId') ?? 0)
+ const platformOrderNo = String(pickField(row, 'platformOrderNo', 'PlatformOrderNo') ?? '')
return {
storeOrderId: Number(pickField(row, 'id', 'Id')),
customerId: Number.isFinite(customerIdRaw) && customerIdRaw > 0 ? customerIdRaw : undefined,
warehouseId: Number.isFinite(warehouseIdRaw) && warehouseIdRaw > 0 ? warehouseIdRaw : undefined,
- platformOrderNo: String(pickField(row, 'platformOrderNo', 'PlatformOrderNo') ?? ''),
+ platformOrderNo,
+ clientNo: platformOrderNo,
orderCountry: String(pickField(row, 'orderCountry', 'OrderCountry') ?? ''),
recipientName,
recipientPhone: String(pickField(row, 'recipientPhone', 'RecipientPhone') ?? ''),
@@ -525,6 +726,10 @@ function buildBindPayload(items: BindDraftItem[]) {
async function handleSubmit(): Promise {
if (!canSubmit.value) {
+ if (!agreementChecked.value) {
+ $baseMessage(t('order.placeOrder.agreement.required'), 'warning')
+ return false
+ }
if (!productCode.value) {
$baseMessage(
isFulfillWms.value
@@ -534,10 +739,14 @@ async function handleSubmit(): Promise {
)
return false
}
- if (isFulfill.value && !warehouseId.value) {
+ if (!warehouseId.value) {
$baseMessage(t('order.platformStoreOrder.message.warehouseRequired'), 'warning')
return false
}
+ if (!basicInfoReady.value) {
+ $baseMessage(t('order.platformStoreOrder.createLogistics.basicRequired'), 'warning')
+ return false
+ }
const bad = drafts.value.find(
(d) => d.receiverMissing || d.linesMissing || (isFulfill.value && d.bindItems.length === 0),
)
@@ -579,12 +788,14 @@ async function handleSubmit(): Promise {
try {
const items = drafts.value.map((d) => ({
storeOrderId: d.storeOrderId,
+ clientNo: String(d.clientNo || '').trim() || undefined,
bindItems: isFulfill.value ? buildBindPayload(d.bindItems) : undefined,
}))
const body = {
productCode: productCode.value,
warehouseId: warehouseId.value,
customerId: props.customerId,
+ parcelBasic: buildParcelBasicPayload(),
items,
}
const result = isFulfillWms.value
@@ -636,7 +847,29 @@ watch(
batchMode.value = props.storeOrderIds.length > 1
batchMatchDeclaration.value = false
batchQtyPerOrder.value = 1
+ cargoAttribute.value = 'general'
+ actualWeight.value = undefined
+ quantity.value = 1
+ lengthCm.value = 10
+ widthCm.value = 10
+ heightCm.value = 10
+ exportCustomsMode.value = SAMPLE_EXPORT_CUSTOMS_MODE_CODE
+ exportQualificationId.value = undefined
+ platformCode.value = undefined
+ inboundMethod.value = 'selfShip'
+ senderAddressId.value = undefined
+ expectPickupTime.value = undefined
+ agreementChecked.value = false
+ await loadBasicDicts()
await loadDrafts()
+ // 实重默认按订单申报行估算(取首单)
+ const first = drafts.value[0]
+ if (first && !(Number(actualWeight.value) > 0)) {
+ const est = first.lines.reduce((s, x) => s + Math.max(1, Number(x.quantity) || 0) * 0.01, 0)
+ actualWeight.value = Number(est.toFixed(3)) || 0.01
+ }
+ await loadExportQualifications()
+ await loadPickupAddresses()
if (isFulfill.value) await loadCandidates()
},
{ immediate: true },
@@ -652,14 +885,24 @@ watch(leftTab, () => {
if (!props.visible || !isFulfill.value) return
void loadCandidates()
})
+
+watch(needExportQualification, (need) => {
+ if (!props.visible || !need) return
+ void loadExportQualifications()
+})
+
+watch(inboundMethod, (method) => {
+ if (!props.visible || method !== 'doorPickup') return
+ void loadPickupAddresses()
+})
@@ -686,49 +933,146 @@ watch(leftTab, () => {
}}
-
-
-
+
+
+ {{ t('order.placeOrder.single.stepBasic') }}
+
{{
- isFulfillWms
- ? t('order.platformStoreOrder.createFulfillWms.product')
- : t('order.platformStoreOrder.createLogistics.product')
+ t('order.platformStoreOrder.createLogistics.readyMeta', {
+ total: drafts.length,
+ ready: readyCount,
+ })
}}
-
+
+
+
+
+ {{
+ isFulfillWms
+ ? t('order.platformStoreOrder.createFulfillWms.product')
+ : t('order.platformStoreOrder.createLogistics.product')
+ }}
+ *
+
+
+
+
+
+
+
+
+
+
+
+
+
+
到仓方式 *
+
+ 自寄到仓
+ 上门揽收
+
+
+
+
+
+
-
-
- {{ t('warehouse.common.warehouse') }}
- *
+
+
+
+ {{ t('order.placeOrder.agreement.label') }}
-
-
- {{
- t('order.platformStoreOrder.createLogistics.readyMeta', {
- total: drafts.length,
- ready: readyCount,
- })
- }}
-
-
+
@@ -1067,6 +1411,16 @@ watch(leftTab, () => {
+
+
+ {{ AGREEMENT_TEXT }}
+