Merge branch 'release/test' of http://8.163.30.128:10082/html/client-web into dev

This commit is contained in:
2026-08-26 11:11:29 +08:00
11 changed files with 52 additions and 22 deletions
+1
View File
@@ -1,5 +1,6 @@
import { createApp } from 'vue'
import { h } from 'vue'
import 'dayjs/locale/zh-cn'
import { createPinia } from 'pinia'
import { ElConfigProvider } from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
@@ -129,7 +129,9 @@ export interface MerchantUnifiedOrderDetail {
export interface MerchantUnifiedOrderQuery {
keyword?: string
fbaCode?: string
orderNo?: string
inboundCode?: string
mainStatus?: ApiUnifiedOrderMainStatus
receivingWarehouseId?: number | string
destinationWarehouseId?: number | string
@@ -100,6 +100,7 @@ test('routes supported filters to C02 without sending DRAFT as a main status', (
assert.match(storeSource, /interface ClientOrderReadQuery extends MerchantUnifiedOrderQuery/)
assert.match(storeSource, /if \(query\.draftOnly\)/)
assert.match(storeSource, /const unifiedQuery: MerchantUnifiedOrderQuery/)
assert.match(storeSource, /inboundCode: query\.inboundCode/)
assert.match(storeSource, /getMerchantUnifiedOrders\(unifiedQuery\)/)
})
@@ -435,6 +435,7 @@ type OrderListReadResult =
| { source: 'unified'; records: MerchantUnifiedOrderRow[]; drafts: MerchantFirstLegOrderRow[] }
interface ClientOrderReadQuery extends MerchantUnifiedOrderQuery {
fbaCode?: string
draftOnly?: boolean
includeDrafts?: boolean
}
@@ -460,8 +461,10 @@ async function getOrdersForClient(query: ClientOrderReadQuery = {}): Promise<Ord
mainStatus: query.mainStatus,
receivingWarehouseId: query.receivingWarehouseId,
destinationWarehouseId: query.destinationWarehouseId,
inboundCode: query.inboundCode,
createdFrom: query.createdFrom,
createdTo: query.createdTo,
fbaCode: query.fbaCode,
page: query.page,
size: query.size,
}
@@ -553,6 +556,7 @@ export const useFirstLegOrdersStore = defineStore('client-first-leg-orders', ()
loadError.value = orderResult.reason instanceof Error ? orderResult.reason.message : '订单数据加载失败'
}
loading.value = false
return orders.value.length
}
function upsertOrder(mapped: FirstLegOrder) {
@@ -122,8 +122,8 @@ test('requires FBA warehouse and cargo-level shipment, reference, SKU and UPC fi
assert.ok(errors.includes('FBA 订单请填写 Amazon 仓库编码'))
assert.ok(errors.includes('第 1 条货物缺少客户 SKU'))
assert.ok(errors.includes('第 1 条货物缺少 UPC'))
assert.ok(errors.includes('第 1 条货物缺少 Shipment ID'))
assert.ok(errors.includes('第 1 条货物缺少 Reference ID'))
assert.ok(errors.includes('第 1 条货物缺少货件编号'))
assert.ok(errors.includes('第 1 条货物缺少跟踪编号'))
})
test('requires public destination ids while keeping private addresses detached from public warehouses', () => {
+2 -2
View File
@@ -123,8 +123,8 @@ export function validateFirstLegOrderDraft(draft: FirstLegOrderDraft) {
if (!item.packageType.trim()) errors.push(`${line}缺少包装类型`)
if (draft.destinationType === 'FBA' && !item.sku.trim()) errors.push(`${line}缺少客户 SKU`)
if (draft.destinationType === 'FBA' && !item.upc.trim()) errors.push(`${line}缺少 UPC`)
if (draft.destinationType === 'FBA' && !item.shipmentId.trim()) errors.push(`${line}缺少 Shipment ID`)
if (draft.destinationType === 'FBA' && !item.referenceId.trim()) errors.push(`${line}缺少 Reference ID`)
if (draft.destinationType === 'FBA' && !item.shipmentId.trim()) errors.push(`${line}缺少货件编号`)
if (draft.destinationType === 'FBA' && !item.referenceId.trim()) errors.push(`${line}缺少跟踪编号`)
if (!Number.isInteger(item.pieces) || item.pieces <= 0) errors.push(`${line}件数必须为正整数`)
if (!Number.isInteger(item.packageCount) || item.packageCount <= 0) errors.push(`${line}箱数必须为正整数`)
if (!Number.isInteger(item.unitsPerPackage) || item.unitsPerPackage <= 0) errors.push(`${line}每箱数量必须为正整数`)
@@ -1206,6 +1206,15 @@ function displayListValue(value: string | number | null | undefined) {
return value === '' || value == null ? '-' : String(value)
}
function orderMatchesCargoKeyword(order: FirstLegOrder, keyword: string) {
const normalized = keyword.toLowerCase()
return [
order.businessSummary.inboundCode,
...order.skuNos,
...order.items.map((item) => item.sku),
].some((value) => String(value ?? '').toLowerCase().includes(normalized))
}
function uniqueItemValues(order: FirstLegOrder, key: 'sku' | 'shipmentId' | 'referenceId') {
if (key === 'sku' && order.skuNos.length) return [...new Set(order.skuNos)].join('、')
const values = order.items.map((item) => item[key]?.trim()).filter((value): value is string => Boolean(value))
@@ -1534,8 +1543,9 @@ async function searchOrders(model: QueryModel) {
const orderNo = typeof model.orderNo === 'string' ? model.orderNo.trim() : ''
const cargoKeyword = typeof model.cargoKeyword === 'string' ? model.cargoKeyword.trim() : ''
const range = Array.isArray(createdRange) ? createdRange.filter((value): value is string => typeof value === 'string') : []
await store.load({
keyword: cargoKeyword || undefined,
const query = {
keyword: undefined,
inboundCode: cargoKeyword || undefined,
orderNo: orderNo || undefined,
receivingWarehouseId: typeof model.receivingWarehouseId === 'string' && model.receivingWarehouseId ? model.receivingWarehouseId : undefined,
mainStatus: statusValue && statusValue !== 'DRAFT' ? statusValue : undefined,
@@ -1543,7 +1553,12 @@ async function searchOrders(model: QueryModel) {
includeDrafts: !statusValue,
createdFrom: range[0] ? `${range[0]}T00:00:00` : undefined,
createdTo: range[1] ? `${range[1]}T23:59:59` : undefined,
})
}
await store.load(query)
const cargoMatched = cargoKeyword && store.orders.some((order) => order.status !== 'DRAFT' && orderMatchesCargoKeyword(order, cargoKeyword))
if (cargoKeyword && !cargoMatched && statusValue !== 'DRAFT') {
await store.load({ ...query, keyword: undefined, inboundCode: undefined, fbaCode: cargoKeyword })
}
selectedOrders.value = []
orderTableVersion.value += 1
} catch {
@@ -1755,9 +1770,9 @@ async function searchOrders(model: QueryModel) {
<table class="cargo-edit-table">
<thead><tr>
<th class="cargo-check-cell"><el-checkbox :model-value="allCargoSelected" :indeterminate="someCargoSelected" aria-label="全选货物" @change="toggleAllCargoItems" /></th><th class="cargo-index-cell">#</th>
<th>SKU NO.<small>FBA必填</small></th><th>中文品名</th><th class="cargo-image-column">图片</th><th>英文品名</th><th>报关 HS CODE</th><th>清关 HS CODE</th><th>包装类型</th>
<th>SKU NO.<small>SKU必填</small></th><th>中文品名</th><th class="cargo-image-column">图片</th><th>英文品名</th><th>报关 HS CODE</th><th>清关 HS CODE</th><th>包装类型</th>
<th class="cargo-number-column">箱规长 cm</th><th class="cargo-number-column">箱规宽 cm</th><th class="cargo-number-column">箱规高 cm</th><th class="cargo-number-column">每箱数量</th><th class="cargo-number-column">箱数</th><th class="cargo-number-column">总毛重</th><th class="cargo-number-column">总体积</th><th class="cargo-number-column">总数量</th><th class="cargo-number-column">总净重</th>
<th>清关申报单价 USD</th><th>清关申报金额 USD</th><th>材质</th><th>用途</th><th>型号</th><th>品牌</th><th class="cargo-dimensions-column">产品规格 长×宽×高</th><th>危险品</th><th>货件编号<small>FBA必填</small></th><th>跟踪编号<small>FBA必填</small></th><th>UPC NO.<small>FBA必填</small></th><th>备注</th><th class="cargo-operation-column">操作</th>
<th>清关申报单价 USD</th><th>清关申报金额 USD</th><th>材质</th><th>用途</th><th>型号</th><th>品牌</th><th class="cargo-dimensions-column">产品规格 长×宽×高</th><th>危险品</th><th>货件编号<small>货件编号必填</small></th><th>跟踪编号<small>跟踪编号必填</small></th><th>UPC NO.<small>UPC必填</small></th><th>备注</th><th class="cargo-operation-column">操作</th>
</tr></thead>
<tbody><tr v-for="(item, index) in form.items" :key="item.id">
<td class="cargo-check-cell"><el-checkbox :model-value="selectedCargoIds.includes(item.id)" :aria-label="`选择货物 ${index + 1}`" @change="toggleCargoItem(item.id, $event)" /></td><td class="cargo-index-cell">{{ index + 1 }}</td>
@@ -1770,11 +1785,11 @@ async function searchOrders(model: QueryModel) {
<td><el-form-item :prop="`items.${index}.unitsPerPackage`" required :rules="positiveNumberRules('每箱数量必须大于 0', true)"><el-input-number v-model="item.unitsPerPackage" :min="1" :controls="false" /></el-form-item></td><td><el-form-item :prop="`items.${index}.packageCount`" required :rules="positiveNumberRules('箱数必须大于 0', true)"><el-input-number v-model="item.packageCount" :min="1" :controls="false" /></el-form-item></td><td><el-form-item :prop="`items.${index}.grossWeightKg`" required :rules="positiveNumberRules('总毛重必须大于 0')"><el-input-number v-model="item.grossWeightKg" :min="0" :precision="3" :controls="false" /></el-form-item></td><td><el-form-item :prop="`items.${index}.volumeCbm`" required :rules="positiveNumberRules('总体积必须大于 0')"><el-input-number v-model="item.volumeCbm" :min="0" :precision="4" :controls="false" /></el-form-item></td><td><el-form-item :prop="`items.${index}.pieces`" required :rules="positiveNumberRules('总数量必须大于 0', true)"><el-input-number v-model="item.pieces" :min="1" :controls="false" /></el-form-item></td><td><el-input-number v-model="item.netWeightKg" :min="0" :precision="3" :controls="false" /></td>
<td><el-input-number v-model="item.unitDeclaredValue" :min="0" :precision="2" :controls="false" /></td><td><el-input-number v-model="item.declaredValue" :min="0" :precision="2" :controls="false" /></td><td><el-input v-model="item.material" maxlength="128" placeholder="材质" /></td><td><el-input v-model="item.usage" maxlength="128" placeholder="用途" /></td><td><el-input v-model="item.model" maxlength="64" placeholder="型号" /></td><td><el-input v-model="item.brand" maxlength="64" placeholder="品牌" /></td>
<td class="cargo-dimensions-column"><div class="cargo-table-dimensions"><el-input-number v-model="item.productLengthCm" :min="0" :precision="2" :controls="false" /><span>×</span><el-input-number v-model="item.productWidthCm" :min="0" :precision="2" :controls="false" /><span>×</span><el-input-number v-model="item.productHeightCm" :min="0" :precision="2" :controls="false" /></div></td><td class="cargo-danger-cell"><el-switch v-model="item.dangerousGoods" /></td>
<td><el-form-item :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></td><td><el-form-item :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></td><td><el-form-item :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></td><td><el-input v-model="item.remark" maxlength="500" placeholder="备注" /></td><td class="cargo-operation-column"><el-button link type="danger" :aria-label="`删除货物 ${index + 1}`" @click="removeCargoItem(index)"><Trash2 :size="15" />删除</el-button></td>
<td><el-form-item :prop="`items.${index}.shipmentId`" :required="form.destinationType === 'FBA'" :rules="form.destinationType === 'FBA' ? requiredTextRules('FBA 货物请输入货件编号') : undefined"><el-input v-model="item.shipmentId" maxlength="64" placeholder="货件编号" /></el-form-item></td><td><el-form-item :prop="`items.${index}.referenceId`" :required="form.destinationType === 'FBA'" :rules="form.destinationType === 'FBA' ? requiredTextRules('FBA 货物请输入跟踪编号') : undefined"><el-input v-model="item.referenceId" maxlength="64" placeholder="跟踪编号" /></el-form-item></td><td><el-form-item :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></td><td><el-input v-model="item.remark" maxlength="500" placeholder="备注" /></td><td class="cargo-operation-column"><el-button link type="danger" :aria-label="`删除货物 ${index + 1}`" @click="removeCargoItem(index)"><Trash2 :size="15" />删除</el-button></td>
</tr></tbody>
</table>
</div>
<div class="cargo-table-hint"><span>FBA 货物的 SKU NO.、UPC NO. 为必填项</span><i></i><span>亚马逊货物的货件编号和跟踪编号为必填项</span></div>
<div class="cargo-table-hint"><span>SKU NO.、货件编号、跟踪编号、UPC NO. 均为必填项</span></div>
</section>
<section class="order-form-section prototype-section cost-estimate-section" aria-label="费用预估">
@@ -36,7 +36,7 @@ export interface MerchantQualification {
tenantId: number | string
qualificationType: MerchantQualificationType
certificateNo: string | null
documentUrl: string
documentUrl?: string | null
validTo: string | null
status: QualificationStatus
reviewComment: string | null
@@ -135,20 +135,23 @@ export const useTenantAdministrationStore = defineStore('tenant-administration',
registeredAddress: profile.registeredAddress ?? '',
auditStatus: profile.status,
}
qualifications.value = qualificationRecords.map((item) => ({
id: String(item.id),
type: ({
qualifications.value = qualificationRecords.map((item) => {
const documentUrl = typeof item.documentUrl === 'string' ? item.documentUrl : ''
return {
id: String(item.id),
type: ({
BUSINESS_LICENSE: '营业执照',
LEGAL_PERSON_ID: '法人身份证',
FREIGHT_FORWARDER_CERTIFICATE: '货代资质证书',
BANK_ACCOUNT_LICENSE: '银行开户许可证',
} as const)[item.qualificationType],
fileName: item.documentUrl.split('/').pop() || '资质文件',
status: normalizeQualificationStatus(item.status),
expiresAt: item.validTo || '长期有效',
updatedAt: '-',
documentUrl: item.documentUrl,
}))
} as const)[item.qualificationType] || '企业资质',
fileName: documentUrl.split('/').pop() || '资质文件',
status: normalizeQualificationStatus(item.status),
expiresAt: item.validTo || '长期有效',
updatedAt: '-',
documentUrl,
}
})
}
function mapMerchantRole(role: MerchantRole): TenantRole {