优化样式

This commit is contained in:
2026-10-19 14:30:52 +08:00
parent 7e68d978dd
commit f7dd39c7ed
5 changed files with 136 additions and 33 deletions
@@ -31,6 +31,14 @@ function harness(kind = 'disposal', overrides = {}) {
return { ...state, props, calls, events, revoked }
}
test('仓储转运客户确认测量弹窗不展示箱子明细和托盘尺寸', () => {
assert.doesNotMatch(source, /<el-table :data="measurement\.cartons\?\.filter\(carton => carton\.status === 'COUNTED'\)/)
assert.doesNotMatch(source, /<el-table-column label="尺寸\(cm\)"/)
assert.doesNotMatch(source, /箱数、尺寸、重量/)
assert.match(source, /<el-table-column prop="grossWeightKg" label="毛重\(kg\)"/)
assert.match(source, /<el-table-column prop="volumeCbm" label="体积\(m³\)"/)
})
for (const kind of ['disposal', 'labeling', 'transfer']) {
test(`${kind}: opening loads photos without confirming; only reviewed evidence can be submitted`, async () => {
const state = harness(kind)
@@ -96,21 +96,15 @@ onBeforeUnmount(clear)
<template>
<el-dialog :model-value="modelValue" :title="title" width="760px" :close-on-click-modal="false" :close-on-press-escape="!busy" :show-close="!busy" @update:model-value="!busy && emit('update:modelValue', $event)">
<p>单号:{{ documentNo }}</p>
<p>{{ kind === 'transfer' ? '请核对 PDA 回传的箱数、尺寸、重量、打托数据及现场照片,确认无误后再提交。' : '请核对以下 PDA 作业照片,确认作业结果无误后再提交。' }}</p>
<p>{{ kind === 'transfer' ? '请核对 PDA 回传的箱数、重量、打托数据及现场照片,确认无误后再提交。' : '请核对以下 PDA 作业照片,确认作业结果无误后再提交。' }}</p>
<template v-if="kind === 'transfer' && measurement">
<p>实际签收箱数:{{ measurement.cartons?.filter(carton => carton.status === 'COUNTED').length ?? 0 }} 箱;托盘数:{{ measurement.pallets?.length ?? 0 }} 托</p>
<el-table :data="measurement.pallets || []" max-height="220" size="small" border empty-text="暂无 PDA 打托测量数据">
<el-table-column label="托盘号" min-width="180"><template #default="{ row }">{{ row.palletNo || `第${row.sequenceNo || '—'}托` }}</template></el-table-column>
<el-table-column prop="cartonCount" label="箱数" width="65" />
<el-table-column label="尺寸(cm)" min-width="130"><template #default="{ row }">{{ row.lengthCm ?? '—' }} × {{ row.widthCm ?? '—' }} × {{ row.heightCm ?? '—' }}</template></el-table-column>
<el-table-column prop="grossWeightKg" label="毛重(kg)" width="95" />
<el-table-column prop="volumeCbm" label="体积(m³)" width="95" />
</el-table>
<el-table :data="measurement.cartons?.filter(carton => carton.status === 'COUNTED') || []" max-height="220" size="small" border empty-text="暂无已签收箱子">
<el-table-column prop="cartonMark" label="箱唛" min-width="200" />
<el-table-column label="尺寸(cm)" min-width="180"><template #default="{ row }">{{ row.length ?? '—' }} × {{ row.width ?? '—' }} × {{ row.height ?? '—' }}</template></el-table-column>
<el-table-column prop="weight" label="重量(kg)" width="110" />
</el-table>
</template>
<p v-if="loading">正在读取作业照片…</p>
<el-alert v-if="error" :title="error" type="error" :closable="false" />
@@ -334,6 +334,9 @@ const relabelCandidatesLoading = ref(false)
const transferInventoryCandidates = ref<RelabelInventoryCandidate[]>([])
const selectedTransferInventory = ref('')
const inventoryTransfer = computed(() => props.service === 'transfer' && Boolean(selectedTransferInventory.value))
// 来源库存发往FBA时默认贴标,私人仓和外来货保留原有选择。
const transferRelabelRequired = computed(() => inventoryTransfer.value && transferDestinationType.value === 'FBA')
watch(transferRelabelRequired, (required) => { if (required) form.relabel = true }, { flush: 'sync' })
const transferSourceCartons = ref<TransferSourceCarton[]>([])
const transferSourceLoading = ref(false)
const transferSourceError = ref('')
@@ -348,6 +351,8 @@ async function loadTransferSourceCartons() {
if (selectedTransferInventory.value.startsWith('RETURN_ORDER:')) {
const returnNo = selectedTransferInventory.value.slice('RETURN_ORDER:'.length)
transferSourceCartons.value = transferInventoryCandidates.value
.filter(item => !['COMPLETED', 'PARTIALLY_COMPLETED'].includes(item.relabelStatus || '')
&& Number(item.relabelledQuantity || item.relabelledPackages || 0) === 0)
.filter(item => (item.sourceType === 'RETURN_CARTON' || item.sourceType === 'RETURN_PARCEL')
&& String(item.inboundOrderNo || '').trim() === returnNo && Number(item.cartonAvailablePackages || 0) > 0)
.filter(item => Boolean(item.cartonMark))
@@ -1174,6 +1179,7 @@ function printForecastCartons() {
}
async function submit(action: 'draft' | 'submit' = 'submit') {
if (saving.value) return
if (transferRelabelRequired.value) form.relabel = true
const primaryWarehouse = props.service === 'relabel' ? form.sourceWarehouse : form.warehouse
const relabelRows = labelingMode.value === 'carton' ? cartonDetailRows.value : skuRelabelRows.value
const invalidCargo = props.service === 'disposal'
@@ -1473,7 +1479,7 @@ async function loadTransferInventoryCandidates(warehouseId: string) {
relabelCandidatesLoading.value = true
try {
// 共用接口包含退货历史及占用中的箱子,转运只展示当前可整箱使用的来源。
transferInventoryCandidates.value = (await listRelabelInventoryCandidates(warehouseId)).filter(item =>
transferInventoryCandidates.value = (await listRelabelInventoryCandidates(warehouseId)).filter(isUnlabelledTransferCandidate).filter(item =>
item.sourceType === 'RETURN_CARTON' || item.sourceType === 'RETURN_PARCEL'
? Number(item.cartonAvailablePackages || 0) > 0
: Number(item.availablePackages || 0) > 0)
@@ -1542,6 +1548,12 @@ const transferInventoryOptions = computed(() => {
}
return [...grouped.values()]
})
/** 已完成或部分完成换标的退货实物箱不再作为原箱来源;普通来源由逐箱接口过滤。 */
function isUnlabelledTransferCandidate(item: RelabelInventoryCandidate) {
if (item.sourceType !== 'RETURN_CARTON' && item.sourceType !== 'RETURN_PARCEL') return true
return !['COMPLETED', 'PARTIALLY_COMPLETED'].includes(item.relabelStatus || '')
&& inventoryQuantity(item.relabelledQuantity || item.relabelledPackages) === 0
}
async function selectTransferInventory(value: string) {
selectedTransferInventory.value = value || ''
selectedTransferSourceOrderIds.value = []
@@ -1730,7 +1742,7 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
<label v-if="props.service === 'labeling'"><span>贴标要求</span><el-input v-model="form.requirement" placeholder="请输入贴标要求" /></label>
<label v-else-if="props.service === 'relabel' || (props.service === 'transfer' && form.deliveryMethod === 'CUSTOMER_DELIVERY')"><span>物流跟踪号</span><el-input v-model="form.trackingNo" placeholder="请输入物流跟踪号(选填)" /></label>
<label class="warehouse-span-2"><span>备注</span><el-input v-model="form.remark" type="textarea" :rows="2" placeholder="选填" /></label>
<div v-if="props.service === 'transfer'" class="warehouse-choice-field warehouse-choice-field--relabel warehouse-span-2"><span><i>*</i>是否贴标</span><el-radio-group v-model="form.relabel"><el-radio-button :value="false">不需要贴标</el-radio-button><el-radio-button :value="true">需要贴标</el-radio-button></el-radio-group><small>{{ form.relabel ? '已开启贴标,请在右侧填写贴标资料' : 'FBA 仓和私人仓均可选择是否贴标' }}</small></div>
<div v-if="props.service === 'transfer'" class="warehouse-choice-field warehouse-choice-field--relabel warehouse-span-2"><span><i>*</i>是否贴标</span><el-radio-group v-model="form.relabel"><el-radio-button v-if="!transferRelabelRequired" :value="false">不需要贴标</el-radio-button><el-radio-button :value="true">需要贴标</el-radio-button></el-radio-group><small>{{ form.relabel ? '已开启贴标,请在右侧填写贴标资料' : 'FBA 仓和私人仓均可选择是否贴标' }}</small></div>
</div>
</div>
<div class="warehouse-card warehouse-form-section warehouse-transfer-cargo-card">
@@ -1805,7 +1817,7 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
<el-alert v-if="transferSourceError" :title="transferSourceError" type="error" :closable="false"><el-button link @click="loadTransferSourceCartons">重新加载</el-button></el-alert>
<div class="warehouse-table-scroll"><table class="warehouse-edit-table"><thead><tr><th>箱号</th><th>商品 SKU</th><th>中文品名</th><th>箱内数量(件)</th><th>原目的仓</th></tr></thead><tbody>
<tr v-for="box in transferSourceCartons" :key="box.cartonMark"><td>{{ box.cartonMark }}</td><td>{{ box.items.map(item => item.skuCode || '—').join(' / ') }}</td><td>{{ box.productNameZh || '—' }}</td><td>{{ box.quantity ?? '未记录' }}</td><td>{{ [box.destinationWarehouseCode, box.destinationWarehouseName].filter(Boolean).join(' · ') || '未指定' }}</td></tr>
<tr v-if="!transferSourceLoading && !transferSourceCartons.length"><td colspan="5"><div class="warehouse-empty">{{ transferSourceError ? '加载失败,请重试' : '原库存单据未记录逐箱明细,不虚构箱号;按原库存汇总转运' }}</div></td></tr>
<tr v-if="!transferSourceLoading && !transferSourceCartons.length"><td colspan="5"><div class="warehouse-empty">{{ transferSourceError ? '加载失败,请重试' : '当前来源单暂无未换标的箱明细' }}</div></td></tr>
</tbody></table></div>
<div v-if="!transferSourceLoading && !transferSourceError && !transferSourceCartons.length" class="warehouse-table-scroll"><table class="warehouse-edit-table"><thead><tr><th>商品 SKU</th><th>中文品名</th><th>库存箱数</th><th>原目的仓</th></tr></thead><tbody><tr v-for="row in cargoRows" :key="row.id"><td>{{ row.sku || '—' }}</td><td>{{ row.name || '—' }}</td><td>{{ row.expectedPackageCount }}</td><td>{{ transferInventoryCandidates.filter(item => item.inboundCode === selectedTransferInventory && item.skuCode === row.sku).map(item => item.destinationFbaWarehouseCode || item.destinationFbaWarehouseName).filter(Boolean).join(' / ') || '未指定' }}</td></tr></tbody></table></div>
</section>
@@ -2,6 +2,7 @@ import test from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { transpileModule, ScriptTarget } from 'typescript'
import { computed, ref, reactive, watch } from 'vue'
const view = readFileSync(new URL('./WarehouseServiceView.vue', import.meta.url), 'utf8')
@@ -18,6 +19,8 @@ test('退货箱明细与来源下拉使用同一份逐箱库存,不请求普
cartonSkuItems: [{ skuCode: 'A', quantity: 1 }] },
{ sourceType: 'RETURN_CARTON', inboundOrderNo: 'OTHER', cartonMark: 'FOREIGN', cartonAvailablePackages: 1 },
{ sourceType: 'RETURN_CARTON', inboundOrderNo: 'RET-1', cartonMark: 'OCCUPIED', cartonAvailablePackages: 0 },
{ sourceType: 'RETURN_CARTON', inboundOrderNo: 'RET-1', cartonMark: 'LABELLED', cartonAvailablePackages: 1, relabelStatus: 'COMPLETED' },
{ sourceType: 'RETURN_CARTON', inboundOrderNo: 'RET-1', cartonMark: 'PARTIAL', cartonAvailablePackages: 1, relabelledQuantity: 2 },
] }, getTransferSourceCartons: () => { throw Error('不应调用普通来源箱查询') } }
const load = new Function(...Object.keys(ctx), 'let transferSourceRequest=0;' + code + ';return loadTransferSourceCartons')(...Object.values(ctx))
await load()
@@ -28,6 +31,27 @@ test('退货箱明细与来源下拉使用同一份逐箱库存,不请求普
assert.equal(ctx.transferSourceLoading.value, false)
})
test('来源库存发往FBA默认开启贴标,清除来源或切换私人仓后恢复选项', () => {
const source = ref(''), destination = ref('NONE'), form = reactive({ relabel: false })
const inventoryTransfer = computed(() => Boolean(source.value))
const body = view.slice(view.indexOf('const transferRelabelRequired ='), view.indexOf('const transferSourceCartons ='))
const required = new Function('computed', 'watch', 'inventoryTransfer', 'transferDestinationType', 'form', body + ';return transferRelabelRequired')(
computed, watch, inventoryTransfer, destination, form)
destination.value = 'FBA'
assert.equal(form.relabel, false)
source.value = 'IN-1'
assert.equal(required.value, true)
assert.equal(form.relabel, true)
destination.value = 'PRIVATE'
assert.equal(required.value, false)
form.relabel = false
destination.value = 'FBA'
assert.equal(form.relabel, true)
source.value = ''
assert.equal(required.value, false)
assert.match(view, /<el-radio-button v-if="!transferRelabelRequired" :value="false">不需要贴标/)
})
function setup() {
const body = view.slice(view.indexOf('async function selectTransferInventory('), view.indexOf('function selectTransferInboundMethod('))
const code = transpileModule(body, { compilerOptions: { target: ScriptTarget.ES2022 } }).outputText