优化
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormItemRule, type FormRules } from 'element-plus'
|
||||
import { AlertTriangle, Box, Eye, FilePlus2, MapPin, PackageCheck, Pencil, Plus, Send, Trash2 } from '@lucide/vue'
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormItemRule, type FormRules, type UploadRequestOptions } from 'element-plus'
|
||||
import { AlertTriangle, Box, Eye, FilePlus2, ImagePlus, MapPin, PackageCheck, Pencil, Plus, Send, Trash2 } from '@lucide/vue'
|
||||
import { FIRST_LEG_ORDER_STATUSES, FIRST_LEG_TRANSPORT_MODES, type FirstLegOrderStatus } from '@chongchong/shared'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/components/common/QueryToolbar.vue'
|
||||
import DataTable, { type DataTableColumn } from '@/components/common/DataTable.vue'
|
||||
@@ -13,7 +13,9 @@ import OrderProgressPanel from '@/modules/first-leg/components/OrderProgressPane
|
||||
import { getMerchantInboundBilling } from '@/modules/first-leg/api/inboundBilling'
|
||||
import {
|
||||
estimateMerchantFirstLegPrice,
|
||||
downloadMerchantProductImage,
|
||||
getMerchantPricingChannels,
|
||||
uploadMerchantProductImage,
|
||||
type MerchantPriceEstimate,
|
||||
type MerchantPriceEstimateRequest,
|
||||
type MerchantPricingChannel,
|
||||
@@ -23,20 +25,18 @@ import { useTenantAdministrationStore } from '@/modules/tenant-management/public
|
||||
import { useAuthStore } from '@/modules/auth/public'
|
||||
import { useUnsavedWorkRegistration } from '@/navigation/workspace-tabs/unsavedWork'
|
||||
import type { FirstLegPartyRole, FirstLegPartySnapshot } from '@/modules/first-leg/data/firstLegParties'
|
||||
import type { CargoBoxSizeType, FirstLegCargoItem, FirstLegOrder, FirstLegOrderDraft } from '@/modules/first-leg/types/firstLegOrder'
|
||||
import type { FirstLegCargoItem, FirstLegOrder, FirstLegOrderDraft } from '@/modules/first-leg/types/firstLegOrder'
|
||||
import type { MerchantInboundBilling } from '@/modules/first-leg/types/inboundBilling'
|
||||
import {
|
||||
canClientEditFirstLegOrder,
|
||||
applyCargoBoxSize,
|
||||
CARGO_BOX_SIZES,
|
||||
createEmptyCargoItem,
|
||||
getFirstLegStatusLabel,
|
||||
getFirstLegStatusTone,
|
||||
getFirstLegTransportModeLabel,
|
||||
summarizeFirstLegCargo,
|
||||
updateCargoBoxVolume,
|
||||
validateFirstLegOrderDraft,
|
||||
} from '@/modules/first-leg/utils/firstLegOrder'
|
||||
import { validateProductImageFile } from '@/modules/first-leg/utils/productImage'
|
||||
|
||||
const store = useFirstLegOrdersStore()
|
||||
const tenantStore = useTenantAdministrationStore()
|
||||
@@ -402,6 +402,8 @@ onBeforeUnmount(() => {
|
||||
channelRequestSequence += 1
|
||||
estimateRequestSequence += 1
|
||||
if (estimateTimer) clearTimeout(estimateTimer)
|
||||
releaseProductImagePreviews(form.items)
|
||||
if (detailOrder.value) releaseProductImagePreviews(detailOrder.value.items)
|
||||
})
|
||||
|
||||
function markEditorClean() {
|
||||
@@ -422,10 +424,65 @@ function cloneDraft(draft: FirstLegOrderDraft): FirstLegOrderDraft {
|
||||
}
|
||||
|
||||
function replaceForm(draft: FirstLegOrderDraft) {
|
||||
releaseProductImagePreviews(form.items)
|
||||
Object.assign(form, cloneDraft(draft))
|
||||
markEditorClean()
|
||||
}
|
||||
|
||||
function releaseProductImagePreviews(items: FirstLegCargoItem[]) {
|
||||
items.forEach((item) => {
|
||||
if (item.productImagePreviewUrl.startsWith('blob:')) URL.revokeObjectURL(item.productImagePreviewUrl)
|
||||
item.productImagePreviewUrl = ''
|
||||
})
|
||||
}
|
||||
|
||||
async function ensureProductImagePreview(item: FirstLegCargoItem) {
|
||||
if (!item.productImageFileId || item.productImagePreviewUrl || item.productImageUploading) return
|
||||
item.productImageUploading = true
|
||||
item.productImageError = ''
|
||||
try {
|
||||
const blob = await downloadMerchantProductImage(item.productImageFileId)
|
||||
item.productImagePreviewUrl = URL.createObjectURL(blob)
|
||||
} catch (error) {
|
||||
item.productImageError = error instanceof Error ? error.message : '产品图片加载失败'
|
||||
} finally {
|
||||
item.productImageUploading = false
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadCargoProductImage(item: FirstLegCargoItem, options: UploadRequestOptions) {
|
||||
const file = options.file
|
||||
const validationError = validateProductImageFile(file)
|
||||
if (validationError) {
|
||||
item.productImageError = validationError
|
||||
ElMessage.warning(validationError)
|
||||
throw new Error(validationError)
|
||||
}
|
||||
item.productImageUploading = true
|
||||
item.productImageError = ''
|
||||
try {
|
||||
const result = await uploadMerchantProductImage(file)
|
||||
if (item.productImagePreviewUrl.startsWith('blob:')) URL.revokeObjectURL(item.productImagePreviewUrl)
|
||||
item.productImageFileId = String(result.fileId)
|
||||
item.productImageName = result.fileName
|
||||
item.productImagePreviewUrl = URL.createObjectURL(file)
|
||||
} catch (error) {
|
||||
item.productImageError = error instanceof Error ? error.message : '产品图片上传失败'
|
||||
ElMessage.error(item.productImageError)
|
||||
throw error
|
||||
} finally {
|
||||
item.productImageUploading = false
|
||||
}
|
||||
}
|
||||
|
||||
function removeCargoProductImage(item: FirstLegCargoItem) {
|
||||
if (item.productImagePreviewUrl.startsWith('blob:')) URL.revokeObjectURL(item.productImagePreviewUrl)
|
||||
item.productImageFileId = ''
|
||||
item.productImageName = ''
|
||||
item.productImagePreviewUrl = ''
|
||||
item.productImageError = ''
|
||||
}
|
||||
|
||||
function draftFromOrder(order: FirstLegOrder): FirstLegOrderDraft {
|
||||
return {
|
||||
referenceNo: order.referenceNo,
|
||||
@@ -456,7 +513,12 @@ function draftFromOrder(order: FirstLegOrder): FirstLegOrderDraft {
|
||||
destinationType: order.destinationType,
|
||||
amazonWarehouseCode: order.amazonWarehouseCode,
|
||||
shipmentId: order.shipmentId,
|
||||
items: order.items.map((item) => ({ ...item })),
|
||||
items: order.items.map((item) => ({
|
||||
...item,
|
||||
productImagePreviewUrl: '',
|
||||
productImageUploading: false,
|
||||
productImageError: '',
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,6 +817,7 @@ async function openEdit(order: FirstLegOrder) {
|
||||
}
|
||||
editingId.value = detailedOrder.id
|
||||
replaceForm(draft)
|
||||
await Promise.all(form.items.map(ensureProductImagePreview))
|
||||
if (warehouseAvailable) void loadSelectedWarehouse(detailedOrder.receivingWarehouseId)
|
||||
detailOpen.value = false
|
||||
editorOpen.value = true
|
||||
@@ -773,7 +836,8 @@ async function openDetail(order: FirstLegOrder) {
|
||||
detailError.value = ''
|
||||
void loadInboundBilling(order)
|
||||
try {
|
||||
await store.loadDetail(order.id)
|
||||
const detailedOrder = await store.loadDetail(order.id)
|
||||
await Promise.all(detailedOrder.items.map(ensureProductImagePreview))
|
||||
} catch (error) {
|
||||
detailError.value = error instanceof Error ? error.message : '订单详情加载失败'
|
||||
ElMessage.error(detailError.value)
|
||||
@@ -873,10 +937,6 @@ function addCargoItem() {
|
||||
resetEditorValidation()
|
||||
}
|
||||
|
||||
function changeCargoBoxSize(item: FirstLegCargoItem, type: CargoBoxSizeType) {
|
||||
applyCargoBoxSize(item, type)
|
||||
}
|
||||
|
||||
function removeCargoItem(index: number) {
|
||||
if (form.items.length === 1) {
|
||||
ElMessage.warning('订单至少需要保留一条货物明细')
|
||||
@@ -1116,37 +1176,81 @@ async function searchOrders(model: QueryModel) {
|
||||
<el-form-item label="收件地址" prop="consigneePartySnapshot.address" required :rules="requiredTextRules('请输入本单实际收货地址')" class="prototype-form-row prototype-form-row--wide"><el-input v-model="form.consigneePartySnapshot.address" maxlength="300" placeholder="请输入收件详细地址" /></el-form-item>
|
||||
<el-form-item label="邮编" class="prototype-form-row prototype-form-row--short"><el-input v-model="form.consigneePartySnapshot.postalCode" maxlength="20" placeholder="请输入邮编" /></el-form-item>
|
||||
</template>
|
||||
<el-form-item v-if="form.destinationType === 'FBA'" label="货件编号" prop="shipmentId" required :rules="requiredTextRules('请输入 Shipment ID')" class="prototype-form-row prototype-form-row--compact"><el-input v-model="form.shipmentId" maxlength="64" placeholder="Shipment ID" /></el-form-item>
|
||||
<el-form-item label="跟踪编号" prop="referenceNo" :required="form.destinationType === 'FBA'" :rules="form.destinationType === 'FBA' ? requiredTextRules('FBA 订单请输入 Reference ID') : undefined" class="prototype-form-row prototype-form-row--compact"><el-input v-model="form.referenceNo" maxlength="100" :placeholder="form.destinationType === 'FBA' ? 'Reference ID' : '选填,贵司内部跟踪编号'" /></el-form-item>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="order-form-section prototype-section cargo-section">
|
||||
<div class="order-section-heading"><div><h3>货物明细</h3><p>先填写常用字段,申报和整柜信息可按需展开。</p></div><el-button @click="addCargoItem"><Plus :size="16" />添加货物</el-button></div>
|
||||
<div class="order-section-heading"><div><h3>货物明细</h3></div><el-button @click="addCargoItem"><Plus :size="16" />添加货物</el-button></div>
|
||||
<article v-for="(item, index) in form.items" :key="item.id" class="cargo-entry">
|
||||
<header><strong>货物 {{ index + 1 }}</strong><el-button link type="danger" :aria-label="`删除货物 ${index + 1}`" @click="removeCargoItem(index)"><Trash2 :size="16" />删除</el-button></header>
|
||||
<div class="cargo-primary-grid">
|
||||
<el-form-item label="SKU" :prop="`items.${index}.sku`" :required="form.destinationType === 'FBA'" :rules="form.destinationType === 'FBA' ? requiredTextRules('FBA 货物请输入客户 SKU') : undefined"><el-input v-model="item.sku" placeholder="SKU NO." /></el-form-item>
|
||||
<el-form-item label="中文品名" :prop="`items.${index}.cargoNameCn`" required :rules="requiredTextRules('请输入中文品名')"><el-input v-model="item.cargoNameCn" placeholder="例如:蓝牙音箱" /></el-form-item>
|
||||
<el-form-item label="英文品名"><el-input v-model="item.cargoNameEn" placeholder="Bluetooth speakers" /></el-form-item>
|
||||
<el-form-item v-if="form.destinationType === 'FBA'" label="UPC" :prop="`items.${index}.upc`" required :rules="requiredTextRules('FBA 货物请输入 UPC')"><el-input v-model="item.upc" maxlength="32" placeholder="UPC NO." /></el-form-item>
|
||||
<el-form-item label="包装类型" :prop="`items.${index}.packageType`" required :rules="requiredSelectRules('请选择包装类型')"><el-select v-model="item.packageType"><el-option label="纸箱" value="CARTON" /><el-option label="托盘" value="PALLET" /><el-option label="木箱" value="WOODEN_CASE" /></el-select></el-form-item>
|
||||
<el-form-item label="总数量" :prop="`items.${index}.pieces`" required :rules="positiveNumberRules('件数必须为大于 0 的整数', true)"><el-input-number v-model="item.pieces" :min="1" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="箱规" :prop="`items.${index}.boxSizeType`" required :rules="requiredSelectRules('请选择箱规')"><el-select v-model="item.boxSizeType" clearable placeholder="请选择固定箱规" @change="changeCargoBoxSize(item, $event as CargoBoxSizeType)"><el-option v-for="box in CARGO_BOX_SIZES" :key="box.code" :value="box.code" :label="`${box.label} · ${box.lengthCm} × ${box.widthCm} × ${box.heightCm} cm`" /></el-select></el-form-item>
|
||||
<el-form-item label="箱数"><el-input-number v-model="item.packageCount" :min="1" controls-position="right" @change="updateCargoBoxVolume(item)" /></el-form-item>
|
||||
<el-form-item label="总毛重 kg" :prop="`items.${index}.grossWeightKg`" required :rules="positiveNumberRules('毛重必须大于 0')"><el-input-number v-model="item.grossWeightKg" :min="0" :precision="2" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="总体积 m³" :prop="`items.${index}.volumeCbm`" required :rules="positiveNumberRules('体积必须大于 0')"><el-input-number v-model="item.volumeCbm" :min="0" :precision="3" controls-position="right" disabled /></el-form-item>
|
||||
</div>
|
||||
<el-collapse class="cargo-more"><el-collapse-item title="更多申报信息"><div class="cargo-secondary-grid">
|
||||
<el-form-item label="HS Code"><el-input v-model="item.hsCode" maxlength="20" placeholder="例如:8518210000" /></el-form-item>
|
||||
<el-form-item label="申报货值"><el-input-number v-model="item.declaredValue" :min="0" :precision="2" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="币种"><el-input v-model="item.currency" maxlength="3" placeholder="例如:USD" /></el-form-item>
|
||||
<el-form-item label="危险品"><el-switch v-model="item.dangerousGoods" /></el-form-item>
|
||||
<template v-if="form.transportMode === 'FCL'"><el-form-item label="柜型" :prop="`items.${index}.containerType`" required :rules="requiredSelectRules('请选择柜型')"><el-select v-model="item.containerType"><el-option v-for="type in ['20GP', '40GP', '40HQ', '45HQ']" :key="type" :label="type" :value="type" /></el-select></el-form-item><el-form-item label="柜量" :prop="`items.${index}.containerCount`" required :rules="positiveNumberRules('柜量必须为大于 0 的整数', true)"><el-input-number v-model="item.containerCount" :min="0" controls-position="right" /></el-form-item></template>
|
||||
<el-form-item label="货物备注" class="form-span-full"><el-input v-model="item.remark" maxlength="300" placeholder="选填,例如:注意防潮" /></el-form-item>
|
||||
</div></el-collapse-item></el-collapse>
|
||||
<section class="cargo-field-section">
|
||||
<h4>商品信息</h4>
|
||||
<div class="cargo-form-grid cargo-form-grid--basic">
|
||||
<el-form-item label="SKU NO." :prop="`items.${index}.sku`" :required="form.destinationType === 'FBA'" :rules="form.destinationType === 'FBA' ? requiredTextRules('FBA 货物请输入 SKU NO.') : undefined"><el-input v-model="item.sku" maxlength="64" placeholder="SKU NO." /></el-form-item>
|
||||
<el-form-item label="中文品名" :prop="`items.${index}.cargoNameCn`" required :rules="requiredTextRules('请输入中文品名')"><el-input v-model="item.cargoNameCn" maxlength="128" placeholder="例如:蓝牙音箱" /></el-form-item>
|
||||
<el-form-item label="英文品名 DESCRIPTION OF GOODS"><el-input v-model="item.cargoNameEn" maxlength="128" placeholder="Bluetooth speakers" /></el-form-item>
|
||||
<el-form-item label="图片 PRODUCT PICTURE" class="product-image-field">
|
||||
<div class="product-image-control">
|
||||
<div v-if="item.productImagePreviewUrl" class="product-image-preview"><img :src="item.productImagePreviewUrl" :alt="`${item.cargoNameCn || `货物 ${index + 1}`}产品图片`" /><el-button circle type="danger" size="small" :aria-label="`移除货物 ${index + 1} 产品图片`" @click="removeCargoProductImage(item)"><Trash2 :size="14" /></el-button></div>
|
||||
<div v-else class="product-image-placeholder"><ImagePlus :size="22" /><span>{{ item.productImageUploading ? '上传中...' : '暂无图片' }}</span></div>
|
||||
<el-upload :show-file-list="false" accept=".jpg,.jpeg,.png,image/jpeg,image/png" :disabled="item.productImageUploading" :http-request="(options: UploadRequestOptions) => uploadCargoProductImage(item, options)"><el-button :loading="item.productImageUploading"><ImagePlus :size="15" />{{ item.productImageFileId ? '替换图片' : '上传图片' }}</el-button></el-upload>
|
||||
<small>JPG、PNG,最大 5MB</small>
|
||||
<span v-if="item.productImageError" class="product-image-error" role="alert">{{ item.productImageError }}</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="UPC NO." :prop="`items.${index}.upc`" :required="form.destinationType === 'FBA'" :rules="form.destinationType === 'FBA' ? requiredTextRules('FBA 货物请输入 UPC NO.') : undefined"><el-input v-model="item.upc" maxlength="64" placeholder="UPC NO." /></el-form-item>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="cargo-field-section">
|
||||
<h4>箱规、数量与重量</h4>
|
||||
<div class="cargo-form-grid">
|
||||
<el-form-item label="包装类型" :prop="`items.${index}.packageType`" required :rules="requiredSelectRules('请选择包装类型')"><el-select v-model="item.packageType"><el-option label="纸箱" value="CARTON" /><el-option label="托盘" value="PALLET" /><el-option label="木箱" value="WOODEN_CASE" /></el-select></el-form-item>
|
||||
<el-form-item label="箱规长 cm"><el-input-number v-model="item.lengthCm" :min="0" :precision="2" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="箱规宽 cm"><el-input-number v-model="item.widthCm" :min="0" :precision="2" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="箱规高 cm"><el-input-number v-model="item.heightCm" :min="0" :precision="2" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="每箱数量 QTY/CTN" :prop="`items.${index}.unitsPerPackage`" required :rules="positiveNumberRules('每箱数量必须为大于 0 的整数', true)"><el-input-number v-model="item.unitsPerPackage" :min="1" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="箱数 CARTONS" :prop="`items.${index}.packageCount`" required :rules="positiveNumberRules('箱数必须为大于 0 的整数', true)"><el-input-number v-model="item.packageCount" :min="1" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="总毛重 T.K.GW" :prop="`items.${index}.grossWeightKg`" required :rules="positiveNumberRules('总毛重必须大于 0')"><el-input-number v-model="item.grossWeightKg" :min="0" :precision="3" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="总体积 T.CBM" :prop="`items.${index}.volumeCbm`" required :rules="positiveNumberRules('总体积必须大于 0')"><el-input-number v-model="item.volumeCbm" :min="0" :precision="4" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="总数量 T.QTY" :prop="`items.${index}.pieces`" required :rules="positiveNumberRules('总数量必须为大于 0 的整数', true)"><el-input-number v-model="item.pieces" :min="1" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="总净重 T.QNW"><el-input-number v-model="item.netWeightKg" :min="0" :precision="3" controls-position="right" /></el-form-item>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="cargo-field-section">
|
||||
<h4>报关与产品资料</h4>
|
||||
<div class="cargo-form-grid">
|
||||
<el-form-item label="报关 HS CODE"><el-input v-model="item.hsCode" maxlength="20" placeholder="报关 HS CODE" /></el-form-item>
|
||||
<el-form-item label="清关 HS CODE"><el-input v-model="item.customsHsCode" maxlength="20" placeholder="清关 HS CODE" /></el-form-item>
|
||||
<el-form-item label="清关申报单价 USD"><el-input-number v-model="item.unitDeclaredValue" :min="0" :precision="2" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="清关申报金额 USD"><el-input-number v-model="item.declaredValue" :min="0" :precision="2" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="中英文材质 MATERIAL"><el-input v-model="item.material" maxlength="128" placeholder="材质 / Material" /></el-form-item>
|
||||
<el-form-item label="中英文用途 USE FOR"><el-input v-model="item.usage" maxlength="128" placeholder="用途 / Use for" /></el-form-item>
|
||||
<el-form-item label="型号 MODEL"><el-input v-model="item.model" maxlength="64" placeholder="型号" /></el-form-item>
|
||||
<el-form-item label="品牌 LOGO"><el-input v-model="item.brand" maxlength="64" placeholder="品牌" /></el-form-item>
|
||||
<el-form-item label="产品规格 长×宽×高" class="product-dimensions-field"><div class="dimension-inputs"><el-input-number v-model="item.productLengthCm" :min="0" :precision="2" placeholder="长 cm" controls-position="right" /><span>×</span><el-input-number v-model="item.productWidthCm" :min="0" :precision="2" placeholder="宽 cm" controls-position="right" /><span>×</span><el-input-number v-model="item.productHeightCm" :min="0" :precision="2" placeholder="高 cm" controls-position="right" /></div></el-form-item>
|
||||
<el-form-item label="危险品"><el-switch v-model="item.dangerousGoods" /></el-form-item>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="cargo-field-section">
|
||||
<h4>货件与备注</h4>
|
||||
<div class="cargo-form-grid">
|
||||
<el-form-item label="货件编号 SHIPMENT ID" :prop="`items.${index}.shipmentId`" :required="form.destinationType === 'FBA'" :rules="form.destinationType === 'FBA' ? requiredTextRules('亚马逊货物请输入 Shipment ID') : undefined"><el-input v-model="item.shipmentId" maxlength="64" placeholder="SHIPMENT ID" /></el-form-item>
|
||||
<el-form-item label="跟踪编号 REFERENCE ID" :prop="`items.${index}.referenceId`" :required="form.destinationType === 'FBA'" :rules="form.destinationType === 'FBA' ? requiredTextRules('亚马逊货物请输入 Reference ID') : undefined"><el-input v-model="item.referenceId" maxlength="64" placeholder="REFERENCE ID" /></el-form-item>
|
||||
<el-form-item label="备注" class="cargo-remark-field"><el-input v-model="item.remark" maxlength="500" placeholder="选填,例如:注意防潮" /></el-form-item>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="form.transportMode === 'FCL'" class="cargo-field-section">
|
||||
<h4>整柜信息</h4>
|
||||
<div class="cargo-form-grid">
|
||||
<el-form-item label="箱型" :prop="`items.${index}.containerType`" required :rules="requiredSelectRules('请选择箱型')"><el-select v-model="item.containerType"><el-option v-for="type in ['20GP', '40GP', '40HQ']" :key="type" :label="type" :value="type" /></el-select></el-form-item>
|
||||
<el-form-item label="柜量" :prop="`items.${index}.containerCount`" required :rules="positiveNumberRules('柜量必须为大于 0 的整数', true)"><el-input-number v-model="item.containerCount" :min="1" controls-position="right" /></el-form-item>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
<p class="cargo-tip">FBA 货物必须填写 SKU、UPC、货件编号和跟踪编号;提交前请核对重量与体积。</p>
|
||||
<aside class="price-estimate" aria-live="polite">
|
||||
<div class="price-estimate__heading"><div><strong>参考运费</strong><span>仅供参考,最终以实际结算为准</span></div><el-button v-if="priceEstimateError" link type="primary" @click="loadPriceEstimate">重新估价</el-button></div>
|
||||
<div v-if="priceEstimateLoading" class="price-estimate__state">正在匹配当前生效价格...</div>
|
||||
@@ -1213,7 +1317,7 @@ async function searchOrders(model: QueryModel) {
|
||||
<div v-if="detailOrder.expressCompanyName || detailOrder.trackingNumber" class="express-information"><PackageCheck :size="20" /><div><span>快递公司</span><strong>{{ detailOrder.expressCompanyName || '待平台补充' }}</strong></div><div><span>快递单号</span><strong>{{ detailOrder.trackingNumber || '待平台补充' }}</strong></div></div>
|
||||
<div v-else class="express-empty"><PackageCheck :size="20" /><div><strong>平台尚未维护快递信息</strong><span>快递公司和快递单号由平台后台维护,客户端仅只读展示。</span></div></div>
|
||||
</section>
|
||||
<section class="detail-section"><h3>货物明细</h3><div v-if="detailSummary" class="cargo-summary"><div><Box :size="18" /><span>明细</span><strong>{{ detailOrder.items.length }} 项</strong></div><div><span>件数</span><strong>{{ detailSummary.pieces }}</strong></div><div><span>毛重</span><strong>{{ detailSummary.grossWeightKg.toLocaleString() }} kg</strong></div><div><span>体积</span><strong>{{ detailSummary.volumeCbm }} m³</strong></div></div><div v-if="detailOrder.items.length" class="cargo-detail-list"><article v-for="(item, index) in detailOrder.items" :key="item.id"><header><span>货物 {{ index + 1 }}</span><strong>{{ item.cargoNameCn || '未命名货物' }}</strong><small>{{ item.cargoNameEn || '' }}</small></header><dl><div><dt>SKU / UPC</dt><dd>{{ item.sku || '-' }} / {{ item.upc || '-' }}</dd></div><div><dt>HS 编码</dt><dd>{{ item.hsCode || '-' }}</dd></div><div><dt>包装 / 箱数</dt><dd>{{ packageTypeLabels[item.packageType] || item.packageType || '-' }} · {{ item.packageCount }} 箱</dd></div><div><dt>数量</dt><dd>{{ item.pieces }} 件</dd></div><div><dt>毛重</dt><dd>{{ item.grossWeightKg }} kg</dd></div><div><dt>体积</dt><dd>{{ item.volumeCbm }} m³</dd></div><div><dt>申报价值</dt><dd>{{ item.declaredValue ? `${item.currency} ${item.declaredValue.toLocaleString()}` : '-' }}</dd></div><div><dt>危险品</dt><dd>{{ item.dangerousGoods ? '是' : '否' }}</dd></div></dl><p v-if="item.remark">{{ item.remark }}</p></article></div><el-empty v-else description="暂无货物明细" :image-size="56" /></section>
|
||||
<section class="detail-section"><h3>货物明细</h3><div v-if="detailSummary" class="cargo-summary"><div><Box :size="18" /><span>明细</span><strong>{{ detailOrder.items.length }} 项</strong></div><div><span>件数</span><strong>{{ detailSummary.pieces }}</strong></div><div><span>毛重</span><strong>{{ detailSummary.grossWeightKg.toLocaleString() }} kg</strong></div><div><span>体积</span><strong>{{ detailSummary.volumeCbm }} m³</strong></div></div><div v-if="detailOrder.items.length" class="cargo-detail-list"><article v-for="(item, index) in detailOrder.items" :key="item.id"><header><span>货物 {{ index + 1 }}</span><strong>{{ item.cargoNameCn || '未命名货物' }}</strong><small>{{ item.cargoNameEn || '' }}</small></header><div v-if="item.productImageFileId" class="cargo-detail-image"><img v-if="item.productImagePreviewUrl" :src="item.productImagePreviewUrl" :alt="`${item.cargoNameCn || `货物 ${index + 1}`}产品图片`" /><span v-else-if="item.productImageUploading">图片加载中...</span><span v-else>{{ item.productImageError || '产品图片暂不可用' }}</span></div><dl><div><dt>SKU / UPC</dt><dd>{{ item.sku || '-' }} / {{ item.upc || '-' }}</dd></div><div><dt>HS 编码</dt><dd>{{ item.hsCode || '-' }}</dd></div><div><dt>包装 / 箱数</dt><dd>{{ packageTypeLabels[item.packageType] || item.packageType || '-' }} · {{ item.packageCount }} 箱</dd></div><div><dt>数量</dt><dd>{{ item.pieces }} 件</dd></div><div><dt>毛重</dt><dd>{{ item.grossWeightKg }} kg</dd></div><div><dt>体积</dt><dd>{{ item.volumeCbm }} m³</dd></div><div><dt>申报价值</dt><dd>{{ item.declaredValue ? `${item.currency} ${item.declaredValue.toLocaleString()}` : '-' }}</dd></div><div><dt>危险品</dt><dd>{{ item.dangerousGoods ? '是' : '否' }}</dd></div></dl><p v-if="item.remark">{{ item.remark }}</p></article></div><el-empty v-else description="暂无货物明细" :image-size="56" /></section>
|
||||
<OrderProgressPanel :order="detailOrder" />
|
||||
<InboundBillingPanel
|
||||
:data="inboundBilling"
|
||||
@@ -1274,7 +1378,7 @@ async function searchOrders(model: QueryModel) {
|
||||
.order-form-section + .order-form-section { margin-top: 24px; padding-top: 22px; border-top: 1px solid var(--border); }
|
||||
.order-form-section h3 { margin: 0 0 16px; color: var(--navy); font-size: 16px; }
|
||||
.order-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 18px; }
|
||||
.order-form-grid > *, .cargo-primary-grid > *, .cargo-secondary-grid > * { min-width: 0; }
|
||||
.order-form-grid > *, .cargo-form-grid > * { min-width: 0; }
|
||||
.order-form :deep(.el-select), .order-form :deep(.el-date-editor), .order-form :deep(.el-input-number) { width: 100%; }
|
||||
.form-span-full { grid-column: 1 / -1; }
|
||||
.prototype-section { padding: 2px 2px 0; }
|
||||
@@ -1324,12 +1428,23 @@ async function searchOrders(model: QueryModel) {
|
||||
.cargo-entry { padding: 16px; border: 1px solid var(--border); background: #fbfcfc; }
|
||||
.cargo-entry + .cargo-entry { margin-top: 12px; }
|
||||
.cargo-entry > header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
|
||||
.cargo-primary-grid { display: grid; grid-template-columns: repeat(5, minmax(130px, 1fr)); column-gap: 12px; }
|
||||
.cargo-secondary-grid { display: grid; grid-template-columns: repeat(4, minmax(130px, 1fr)); column-gap: 12px; padding-top: 6px; }
|
||||
.cargo-more { border-top: 1px solid var(--border); border-bottom: 0; }
|
||||
.cargo-more :deep(.el-collapse-item__header), .cargo-more :deep(.el-collapse-item__wrap) { background: transparent; }
|
||||
.cargo-more :deep(.el-collapse-item__content) { padding-bottom: 0; }
|
||||
.cargo-tip { margin: 12px 0 0; color: var(--muted); font-size: 11px; line-height: 1.55; }
|
||||
.cargo-field-section + .cargo-field-section { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
|
||||
.cargo-field-section h4 { margin: 0 0 12px; color: var(--navy); font-size: 12px; }
|
||||
.cargo-form-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: 12px; }
|
||||
.cargo-form-grid--basic { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
.product-image-field { grid-row: span 2; }
|
||||
.product-image-control { display: grid; grid-template-columns: 78px minmax(0, 1fr); align-items: center; gap: 7px 10px; width: 100%; }
|
||||
.product-image-preview, .product-image-placeholder { position: relative; display: flex; width: 78px; height: 78px; grid-row: 1 / span 3; align-items: center; justify-content: center; overflow: hidden; border: 1px dashed #cbd5da; background: #fff; color: var(--muted); }
|
||||
.product-image-preview img { width: 100%; height: 100%; object-fit: contain; }
|
||||
.product-image-preview :deep(.el-button) { position: absolute; top: 4px; right: 4px; width: 26px; height: 26px; }
|
||||
.product-image-placeholder { flex-direction: column; gap: 5px; font-size: 10px; }
|
||||
.product-image-control small { color: var(--muted); font-size: 10px; line-height: 1.4; }
|
||||
.product-image-error { grid-column: 2; overflow-wrap: anywhere; color: #b42318; font-size: 10px; line-height: 1.4; }
|
||||
.product-dimensions-field, .cargo-remark-field { grid-column: span 2; }
|
||||
.dimension-inputs { display: grid; width: 100%; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 6px; }
|
||||
.dimension-inputs > span { color: var(--muted); }
|
||||
.cargo-detail-image { display: flex; width: 112px; height: 112px; align-items: center; justify-content: center; margin-top: 12px; overflow: hidden; border: 1px solid var(--border); background: #fff; color: var(--muted); font-size: 10px; text-align: center; }
|
||||
.cargo-detail-image img { width: 100%; height: 100%; object-fit: contain; }
|
||||
.order-detail-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding-bottom: 20px; border-bottom: 1px solid var(--border); }
|
||||
.order-detail-heading span { color: var(--muted); font-size: 11px; }
|
||||
.order-detail-heading h2 { margin: 6px 0; font-size: 21px; }
|
||||
@@ -1409,7 +1524,9 @@ async function searchOrders(model: QueryModel) {
|
||||
.order-table-toolbar .el-button { width: 100%; min-height: 40px; }
|
||||
.order-eligibility { flex-wrap: wrap; }
|
||||
.order-eligibility .el-button { width: 100%; min-height: 44px; }
|
||||
.order-form-grid, .order-detail-grid, .cargo-primary-grid, .cargo-secondary-grid, .party-detail-grid { grid-template-columns: 1fr; }
|
||||
.order-form-grid, .order-detail-grid, .cargo-form-grid, .cargo-form-grid--basic, .party-detail-grid { grid-template-columns: 1fr; }
|
||||
.product-image-field { grid-row: auto; }
|
||||
.product-dimensions-field, .cargo-remark-field { grid-column: auto; }
|
||||
.fulfillment-summary { grid-template-columns: 1fr; }
|
||||
.fulfillment-summary > div, .fulfillment-summary > div:not(:nth-child(3n)) { border-right: 0; border-bottom: 1px solid var(--border); }
|
||||
.fulfillment-summary > div:last-child { border-bottom: 0; }
|
||||
|
||||
Reference in New Issue
Block a user