This commit is contained in:
2026-08-18 11:32:58 +08:00
parent 771ed6f059
commit eedaa2f080
8 changed files with 109 additions and 17 deletions
+48 -12
View File
@@ -4,7 +4,12 @@ import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { MapPin, Plus, Send, Trash2 } from '@lucide/vue'
import { useAuthStore } from '@/modules/auth/store/auth'
import { getMerchantPricingChannels, type MerchantPricingChannel } from '@/modules/first-leg/api/firstLeg'
import {
getMerchantInboundCodePolicy,
getMerchantPricingChannels,
type InboundCodePolicy,
type MerchantPricingChannel,
} from '@/modules/first-leg/api/firstLeg'
import { useFirstLegOrdersStore } from '@/modules/first-leg/store/firstLegOrders'
import type { FirstLegOrderDraft } from '@/modules/first-leg/types/firstLegOrder'
import { createEmptyCargoItem } from '@/modules/first-leg/utils/firstLegOrder'
@@ -19,6 +24,7 @@ interface QuickOrderProduct {
interface QuickOrderRow {
id: string
inboundCode: string
channelKey: string
destinationType: DestinationType
destinationWarehouseId: string
@@ -52,6 +58,7 @@ const store = useFirstLegOrdersStore()
const loading = ref(false)
const submitting = ref(false)
const loadError = ref('')
const inboundCodePolicy = ref<InboundCodePolicy | null>(null)
const channelOptions = ref<QuickChannelOption[]>([])
const destinationDialogOpen = ref(false)
const destinationLoading = ref(false)
@@ -85,6 +92,7 @@ function createProduct(): QuickOrderProduct {
function createRow(): QuickOrderRow {
return reactive({
id: `quick-order-${++rowSequence}`,
inboundCode: '',
channelKey: '',
destinationType: 'FBA',
destinationWarehouseId: '',
@@ -102,6 +110,7 @@ function createRow(): QuickOrderRow {
const rows = reactive<QuickOrderRow[]>([createRow()])
const hasChannels = computed(() => channelOptions.value.length > 0)
const manualInboundCodeRequired = computed(() => inboundCodePolicy.value?.mode === 'LEGACY_MANUAL')
const availableDestinationWarehouses = computed(() => destinationForm.type === 'PRIVATE'
? []
: store.warehouses.filter((warehouse) => warehouse.warehouseType === destinationForm.type))
@@ -163,6 +172,7 @@ function hasCompletePrivateContact(row: QuickOrderRow) {
function buildOrderDraft(row: QuickOrderRow, channel: QuickChannelOption): FirstLegOrderDraft {
const pricingTaxMode = channel.pricingTaxModes[0] ?? 'TAX_INCLUDED'
return {
inboundCode: row.inboundCode.trim() || undefined,
referenceNo: '',
transportMode: 'FCL',
businessType: 'EXPORT',
@@ -288,7 +298,11 @@ async function loadChannels() {
loading.value = true
loadError.value = ''
try {
await store.loadWarehouseType('DOMESTIC', true)
const [, policy] = await Promise.all([
store.loadWarehouseType('DOMESTIC', true),
getMerchantInboundCodePolicy(),
])
inboundCodePolicy.value = policy
const results = await Promise.allSettled(store.warehouses
.filter((warehouse) => warehouse.warehouseType === 'DOMESTIC')
.map(async (warehouse) => {
@@ -305,6 +319,17 @@ async function loadChannels() {
}
async function submitQuickOrder() {
if (!inboundCodePolicy.value) {
ElMessage.warning('入仓码规则尚未加载,请刷新页面后重试')
return
}
const missingInboundCodeIndex = manualInboundCodeRequired.value
? rows.findIndex((row) => !row.inboundCode.trim())
: -1
if (missingInboundCodeIndex >= 0) {
ElMessage.warning(`请填写第 ${missingInboundCodeIndex + 1} 行的旧系统入仓码`)
return
}
const incompleteIndex = rows.findIndex((row) => !row.channelKey || !row.destinationWarehouseName
|| row.products.some((product) => !product.sku.trim() || product.packageCount < 1))
if (incompleteIndex >= 0) {
@@ -330,7 +355,7 @@ async function submitQuickOrder() {
if (!activeBatchKey.value) activeBatchKey.value = createBatchKey()
const response = await store.saveDraftBatch(drafts, customerName, activeBatchKey.value)
const successes = response.items.flatMap((item): QuickDraftResult[] => {
if (!item.success || item.orderId == null || !item.inboundCode?.trim()) return []
if (!item.success || item.orderId == null) return []
const row = rows.find((candidate) => candidate.id === item.clientRowId)
if (!row) return []
return [{
@@ -338,10 +363,10 @@ async function submitQuickOrder() {
sku: row.products.map((product) => product.sku.trim()).join('、'),
draftId: String(item.orderId),
orderNo: item.orderNo ?? '',
inboundCode: item.inboundCode.trim(),
inboundCode: item.inboundCode?.trim() ?? '',
}]
})
const failures = response.items.filter((item) => !item.success || item.orderId == null || !item.inboundCode?.trim())
const failures = response.items.filter((item) => !item.success || item.orderId == null)
draftResults.splice(0, draftResults.length, ...successes)
const successfulIds = new Set(successes.map((item) => item.rowId))
for (let index = rows.length - 1; index >= 0; index -= 1) {
@@ -374,6 +399,7 @@ onMounted(loadChannels)
<header class="quick-order-intro">
<h2 id="quick-order-heading">客户下单</h2>
<p>只需输入渠道、目的仓、产品 SKU 和箱数,即可快速填写订单。</p>
<small v-if="manualInboundCodeRequired">当前全局使用旧系统入仓码,每张草稿都需要填写旧系统生成的入仓码。</small>
<small>每一行会直接生成一张独立草稿单,其他明细可进入草稿后继续编辑并提交。</small>
</header>
@@ -385,14 +411,23 @@ onMounted(loadChannels)
<table class="quick-order-table">
<thead>
<tr>
<th>选择渠道</th>
<th>目的仓</th>
<th>操作</th>
<th v-if="manualInboundCodeRequired" class="inbound-code-heading">旧系统入仓码</th>
<th class="channel-heading">选择渠道</th>
<th class="destination-heading">目的仓</th>
<th class="operation-heading">操作</th>
<th>产品信息:产品 SKU / 箱数</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, rowIndex) in rows" :key="row.id">
<td v-if="manualInboundCodeRequired" class="inbound-code-cell">
<el-input
v-model="row.inboundCode"
maxlength="40"
placeholder="请输入旧系统入仓码"
@input="row.inboundCode = String($event).trim().toUpperCase()"
/>
</td>
<td class="channel-cell">
<el-select v-model="row.channelKey" :disabled="!hasChannels" placeholder="请选择">
<el-option
@@ -476,7 +511,7 @@ onMounted(loadChannels)
<el-dialog v-model="resultDialogOpen" class="quick-result-dialog" title="草稿单生成结果" width="min(700px, 94vw)">
<div class="quick-draft-results">
<article v-for="result in draftResults" :key="result.rowId">
<div><strong>{{ result.orderNo || `草稿 ${result.draftId}` }}</strong><span>SKU {{ result.sku }}</span><span>入仓码 {{ result.inboundCode }}</span></div>
<div><strong>{{ result.orderNo || `草稿 ${result.draftId}` }}</strong><span>SKU {{ result.sku }}</span><span>{{ result.inboundCode ? `入仓码 ${result.inboundCode}` : '入仓码将在提交订单时生成' }}</span></div>
<el-button type="primary" plain @click="editDraft(result)">编辑明细</el-button>
</article>
</div>
@@ -494,11 +529,12 @@ onMounted(loadChannels)
.quick-order-table-wrap { min-height: 126px; overflow-x: auto; border: 1px solid #e5eaf2; border-radius: 8px; }
.quick-order-table { width: 100%; min-width: 900px; border-collapse: collapse; table-layout: fixed; }
.quick-order-table th { height: 42px; padding: 0 14px; background: #f4f5fb; color: #637fec; font-size: 12px; font-weight: 650; text-align: left; }
.quick-order-table th:nth-child(1) { width: 220px; }
.quick-order-table th:nth-child(2) { width: 145px; }
.quick-order-table th:nth-child(3) { width: 210px; }
.quick-order-table .inbound-code-heading, .quick-order-table .channel-heading { width: 220px; }
.quick-order-table .destination-heading { width: 145px; }
.quick-order-table .operation-heading { width: 210px; }
.quick-order-table td { padding: 12px 14px; border-top: 1px solid #edf0f5; vertical-align: middle; }
.channel-cell :deep(.el-select) { width: 100%; }
.inbound-code-cell :deep(.el-input) { width: 100%; }
.destination-options { display: flex; align-items: flex-start; flex-direction: column; gap: 4px; }
.destination-options :deep(.el-radio) { height: 20px; margin-right: 0; }
.destination-binding { display: flex; width: 100%; min-width: 0; align-items: center; gap: 5px; margin-top: 7px; padding: 0; border: 0; background: transparent; color: #637fec; cursor: pointer; font-size: 11px; text-align: left; }