This commit is contained in:
2026-09-11 17:27:33 +08:00
parent 88a1601814
commit 22fcaba78d
3 changed files with 44 additions and 11 deletions
+10
View File
@@ -59,3 +59,13 @@ export function downloadDropshipPalletLabel(taskId, warehouseId, palletId) {
return response.tempFilePath
})
}
export function downloadAllDropshipPalletLabels(taskId, warehouseId) {
const baseUrl = String(session.apiBase || '/api/platform/v1').replace(/\/$/, '')
const url = `${baseUrl}${ROOT}/tasks/${encodeURIComponent(taskId)}/pallets/print?warehouseId=${encodeURIComponent(stableId(warehouseId))}`
return apiDownload({ url }).then((response) => {
const contentType = response.header?.['Content-Type'] || response.header?.['content-type'] || ''
if (response.statusCode < 200 || response.statusCode >= 300 || !response.tempFilePath || (contentType && !/^application\/pdf/i.test(contentType))) throw new Error('全部托盘码打印文件生成失败')
return response.tempFilePath
})
}
@@ -54,14 +54,13 @@
</view>
<view class="section-card">
<view class="section-heading"><view><text class="section-title">2. 托盘码</text><text class="section-desc">打开 A4 托盘信息单,装完后封托</text></view><button class="text-button" :disabled="loading" @tap="loadTask">刷新</button></view>
<view v-if="!task.pallets || !task.pallets.length" class="empty">暂无托盘</view>
<view v-for="pallet in task.pallets" :key="pallet.palletId" class="pallet-card">
<view class="pallet-head"><text class="pallet-code">{{ pallet.palletNo }}</text><text class="pallet-status" :class="pallet.status === 'SEALED' ? 'sealed' : ''">{{ pallet.status === 'SEALED' ? '已封托' : '待封托' }}</text></view>
<text class="pallet-meta">{{ pallet.cartonCount || 0 }} 箱 · {{ pallet.skuCount || 0 }} SKU · {{ pallet.weightKg || 0 }} kg · {{ pallet.volumeCbm || 0 }} m³</text>
<button v-if="pallet.status === 'OPEN'" class="secondary-button" :disabled="submitting" @tap="selectPallet(pallet.palletNo)">{{ currentPallet?.palletId === pallet.palletId ? '当前托盘' : '设为当前托盘' }}</button>
<button class="secondary-button" :disabled="printing" @tap="printPallet(pallet)">{{ printing ? '正在生成 A4 文件…' : '打开 A4 托盘单' }}</button>
<button v-if="pallet.status === 'OPEN'" class="primary-button" :disabled="!pallet.cartonCount || submitting" @tap="sealPallet(pallet)">封托</button>
<view class="section-heading"><view><text class="section-title">2. 托盘操作</text><text class="section-desc">直接扫描托盘码,只显示当前操作的托盘</text></view><button class="text-button" :disabled="loading" @tap="loadTask">刷新</button></view>
<view class="batch-print-row"><text>已生成 {{ task.generatedPalletCount || 0 }} 个托盘码</text><button class="text-button batch-print" :disabled="printing || !task.generatedPalletCount" @tap="printAllPallets">打开全部 A4 托盘单</button></view>
<view v-if="currentPalletDetails" class="pallet-card">
<view class="pallet-head"><text class="pallet-code">{{ currentPalletDetails.palletNo }}</text><text class="pallet-status" :class="currentPalletDetails.status === 'SEALED' ? 'sealed' : ''">{{ currentPalletDetails.status === 'SEALED' ? '已封托' : '待封托' }}</text></view>
<text class="pallet-meta">{{ currentPalletDetails.cartonCount || 0 }} 箱 · {{ currentPalletDetails.skuCount || 0 }} SKU · {{ currentPalletDetails.weightKg || 0 }} kg · {{ currentPalletDetails.volumeCbm || 0 }} m³</text>
<button class="secondary-button" :disabled="printing" @tap="printPallet(currentPalletDetails)">{{ printing ? '正在生成 A4 文件…' : '打开当前托盘 A4 单' }}</button>
<button v-if="currentPalletDetails.status === 'OPEN'" class="primary-button" :disabled="!currentPalletDetails.cartonCount || submitting" @tap="sealPallet(currentPalletDetails)">封托</button>
</view>
</view>
</template>
@@ -72,7 +71,7 @@
<script>
import PortalHeader from '@/components/PortalHeader.vue'
import ScanTargetInput from '@/components/ScanTargetInput.vue'
import { bindDropshipPendingCartons, claimDropshipPalletizationTask, countDropshipCarton, downloadDropshipPalletLabel, getDropshipPalletizationTask, resolveDropshipCarton, sealDropshipPallet } from '@/api/pda-dropship.js'
import { bindDropshipPendingCartons, claimDropshipPalletizationTask, countDropshipCarton, downloadAllDropshipPalletLabels, downloadDropshipPalletLabel, getDropshipPalletizationTask, resolveDropshipCarton, sealDropshipPallet } from '@/api/pda-dropship.js'
import { session, requireSession } from '@/stores/session.js'
import { formatApiError } from '@/utils/user-facing.js'
import { startScanBroadcast } from '@/utils/scan-broadcast.js'
@@ -86,7 +85,8 @@
palletSetupMessage() { return this.task?.palletSetupMessage || `预计 ${this.task?.expectedPalletCount || 0} 托,当前仅生成 ${this.task?.generatedPalletCount || 0} 个托盘码,请联系后台处理后刷新` },
canResolveCarton() { return Boolean(this.palletSetupReady && this.task?.taskStatus === 'CLAIMED' && String(this.boxCode || '').trim() && !this.loading && !this.submitting) },
canCount() { return Boolean(this.carton?.canCount && this.skuCounts.length && this.skuCounts.every((line) => Number.isInteger(Number(line.actualQuantity)) && Number(line.actualQuantity) >= 0 && (!this.lineDiffers(line) || String(line.discrepancyRemark || '').trim()))) },
currentPallet() { return (this.task?.pallets || []).find((item) => item.palletNo === this.selectedPalletNo && item.status === 'OPEN') || (this.selectedPalletNo ? { palletNo: this.selectedPalletNo, status: 'OPEN' } : null) }
currentPalletDetails() { return (this.task?.pallets || []).find((item) => item.palletNo === this.selectedPalletNo) || null },
currentPallet() { return this.currentPalletDetails?.status === 'OPEN' ? this.currentPalletDetails : (this.selectedPalletNo ? { palletNo: this.selectedPalletNo, status: 'OPEN' } : null) }
},
onLoad(options) { this.taskId = String(options.taskId || ''); this.warehouseId = String(options.warehouseId || '') },
onShow() { if (!requireSession()) return; this.user = session.currentUser; this.startScanner(); this.loadTask() },
@@ -144,6 +144,21 @@
} catch (error) { this.error = formatApiError(error, '托盘码打印失败') }
finally { this.printing = false }
},
async printAllPallets() {
if (this.printing) return
this.printing = true; this.resetMessages()
try {
const filePath = await downloadAllDropshipPalletLabels(this.taskId, this.warehouseId)
// #ifdef H5
const link = document.createElement('a'); link.href = filePath; link.download = `${this.task.inboundCode}-全部托盘码.pdf`; link.click()
// #endif
// #ifndef H5
await new Promise((resolve, reject) => uni.openDocument({ filePath, fileType: 'pdf', showMenu: true, success: resolve, fail: reject }))
// #endif
this.notice = `已打开 ${this.task.generatedPalletCount || 0} 个托盘码的 A4 文件`
} catch (error) { this.error = formatApiError(error, '全部托盘码打印失败') }
finally { this.printing = false }
},
async runMutation(action, message) {
if (this.submitting) return null
this.submitting = true; this.resetMessages()
@@ -181,5 +196,6 @@
.quantity-stepper { display: grid; grid-template-columns: 38px 56px 38px; overflow: hidden; border: 1px solid #b8c6d8; border-radius: 8px; background: #fff; }.quantity-stepper button,.quantity-stepper input { box-sizing: border-box; height: 40px; margin: 0; border: 0; border-radius: 0; text-align: center; background: #fff; }.quantity-stepper button { color: #155eef; font-size: 20px; line-height: 40px; }.quantity-stepper input { width: 56px; border-right: 1px solid #dce4ed; border-left: 1px solid #dce4ed; color: #12233f; font-size: 15px; }.difference-field { grid-column: 1 / -1; margin-top: 0; }.pallet-hint { display: block; margin-top: 10px; color: #155eef; font-size: 11px; text-align: center; }
.field { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 10px; color: #344054; font-size: 12px; }.field input { box-sizing: border-box; width: 120px; height: 42px; padding: 0 10px; border: 1px solid #cfd8e3; border-radius: 8px; background: #fff; }.field.stacked { align-items: stretch; flex-direction: column; gap: 6px; }.field textarea { box-sizing: border-box; width: 100%; height: 70px; padding: 9px; border: 1px solid #cfd8e3; border-radius: 8px; background: #fff; }
.pallet-code { color: #12233f; font-size: 19px; font-weight: 700; overflow-wrap: anywhere; }.text-button { min-width: 58px; height: 40px; border: 0; color: #155eef; font-size: 14px; line-height: 40px; background: transparent; }.empty { padding: 18px 0 8px; color: #98a2b3; font-size: 14px; text-align: center; }
.batch-print-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 7px 0; border-top: 1px solid #edf1f5; color: #667085; font-size: 13px; }.batch-print { min-width: 148px; padding: 0 4px; font-weight: 700; }
.palletization-page :deep(.scan-target-value) { font-size: 17px; }
</style>
+8 -1
View File
@@ -22,6 +22,8 @@ test('dropship palletization adapter uses only the dedicated production PDA cont
'/pallets/bind-pending', '/seal', '/print'
]) assert.match(api, new RegExp(route.replaceAll('/', '\\/')))
assert.match(api, /apiDownload/)
assert.match(api, /downloadAllDropshipPalletLabels/)
assert.match(api, /pallets\/print\?warehouseId=/)
assert.match(api, /application\\\/pdf/)
assert.match(api, /session\.apiBase \|\| '\/api\/platform\/v1'/)
assert.match(api, /tasks\/pending\?\$\{params\.join\('&'\)\}/)
@@ -43,7 +45,7 @@ test('palletization UI accepts carton and pre-generated pallet scans in either o
assert.match(tasks, /\.search-card\s*\{[^}]*display:\s*flex;[^}]*box-shadow:\s*none;/)
assert.match(tasks, /\.task-list\s*\{[^}]*overflow:\s*hidden;[^}]*border-radius:\s*7px;/)
assert.match(tasks, /\.task-card\s*\{[^}]*margin:\s*0;[^}]*border-radius:\s*0;[^}]*box-shadow:\s*none;/)
for (const text of ['一件代发入库', '领取并开始入库', '扫箱清点', '读取 SKU', '确认清点', '扫托盘', '可选', '差异说明', '托盘码', '打开 A4 托盘单', '封托']) assert.match(work, new RegExp(text))
for (const text of ['一件代发入库', '领取并开始入库', '扫箱清点', '读取 SKU', '确认清点', '扫托盘', '可选', '差异说明', '托盘码', '打开全部 A4 托盘单', '封托']) assert.match(work, new RegExp(text))
for (const text of ['sku-label', 'sku-code', 'quantity-stepper', 'adjustQuantity']) assert.match(work, new RegExp(text))
assert.match(work, /减少 \$\{line\.skuCode\} 清点数量/)
assert.match(work, /增加 \$\{line\.skuCode\} 清点数量/)
@@ -67,7 +69,12 @@ test('palletization UI accepts carton and pre-generated pallet scans in either o
assert.match(work, /继续装其他入仓单/)
assert.match(work, /selectedPalletNo/)
assert.match(work, /已选择仓库开放托盘/)
assert.match(work, /直接扫描托盘码,只显示当前操作的托盘/)
assert.match(work, /打开全部 A4 托盘单/)
assert.match(work, /currentPalletDetails/)
assert.doesNotMatch(work, /v-for="pallet in task\.pallets"/)
assert.match(work, /downloadDropshipPalletLabel/)
assert.match(work, /downloadAllDropshipPalletLabels/)
assert.match(work, /formatApiError/)
assert.doesNotMatch(work, /Number\(this\.warehouseId\)/)
assert.doesNotMatch(work, /taskId\s*\}\}|warehouseId\s*\}\}|inboundId\s*\}\}/)