优化
This commit is contained in:
@@ -292,6 +292,18 @@
|
||||
uni.redirectTo({ url: `/pages/relabel/relabel?op=relabelExecute&task=${encodeURIComponent(active.relabelNo)}` })
|
||||
} finally { this.busy = false }
|
||||
},
|
||||
async confirmStartTask(item) {
|
||||
if (this.busy) return false
|
||||
this.busy = true
|
||||
try {
|
||||
return await new Promise((resolve) => uni.showModal({
|
||||
title: '开始贴标作业',
|
||||
content: `是否开始贴标作业?\n贴标申请:${item.relabelNo}`,
|
||||
confirmText: '是', cancelText: '否',
|
||||
success: (result) => resolve(result.confirm), fail: () => resolve(false)
|
||||
}))
|
||||
} finally { this.busy = false }
|
||||
},
|
||||
async openExecution(item) {
|
||||
if (this.busy || this.stage === 'review') return
|
||||
if (this.stage === 'execute') {
|
||||
@@ -300,8 +312,10 @@
|
||||
}
|
||||
const active = this.candidates.find((candidate) => candidate.taskStatus === 'PROCESSING' && candidate.claimedByCurrentOperator === true)
|
||||
if (active && active.relabelNo !== item.relabelNo) return this.promptActiveTask(active)
|
||||
this.taskCode = item.relabelNo
|
||||
if (item.taskStatus === 'PROCESSING' && item.claimedByCurrentOperator) return uni.redirectTo({ url: `/pages/relabel/relabel?op=relabelExecute&task=${encodeURIComponent(item.relabelNo)}` })
|
||||
const confirmed = await this.confirmStartTask(item)
|
||||
if (!confirmed) return
|
||||
this.taskCode = item.relabelNo
|
||||
const payload = { relabelNo: item.relabelNo }; const key = warehouseIdempotencyKey('relabel', 'claim', payload); let claimed = false
|
||||
await this.run('claim', async () => { this.applyCurrent(await claimRelabelTask(item.relabelNo, { idempotencyKey: key.value })); releaseWarehouseKey('relabel', key.signature); claimed = true }, true, key)
|
||||
if (claimed) uni.redirectTo({ url: `/pages/relabel/relabel?op=relabelExecute&task=${encodeURIComponent(item.relabelNo)}` })
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
<template>
|
||||
<view v-if="user" class="page-shell portal-page transfer-page">
|
||||
<PortalHeader title="仓储转运测量" :user="user" fallback-url="/pages/module/module?key=inbound" />
|
||||
<PortalHeader :title="entryMode === 'list' ? '仓储转运测量列表' : '转运测量作业'" :user="user" fallback-url="/pages/module/module?key=inbound" />
|
||||
<scroll-view scroll-y class="transfer-scroll"><view class="portal-content transfer-content">
|
||||
<view class="transfer-heading">
|
||||
<view><text class="transfer-title">托盘测量</text><text class="transfer-subtitle">按实际托盘分别记录箱规、重量和箱数</text></view>
|
||||
<view><text class="transfer-title">{{ entryMode === 'list' ? '仓储转运测量列表' : '转运测量作业' }}</text><text class="transfer-subtitle">{{ entryMode === 'list' ? '选择待测转运单后进入作业详情' : '按实际托盘分别记录箱规、重量和箱数' }}</text></view>
|
||||
<button class="refresh-button" :disabled="loading" aria-label="刷新待测转运单" @tap="loadCandidates"><uni-icons type="refresh" color="#135ee8" size="18" /><text>{{ loading ? '加载中' : '刷新' }}</text></button>
|
||||
</view>
|
||||
|
||||
<template v-if="!selected && entryMode === 'list'">
|
||||
<view class="section-heading"><text>待测转运单</text><text>{{ candidates.length }} 单</text></view>
|
||||
<view v-if="!candidates.length && !loading" class="transfer-empty"><text class="empty-title">暂无待测量任务</text><text>新任务进入后可点击右上角刷新</text></view>
|
||||
<view class="candidate-list">
|
||||
<button v-for="item in candidates" :key="item.transferNo" class="transfer-card" :class="{ selected: selected?.transferNo === item.transferNo }" @tap="select(item)">
|
||||
<view class="transfer-card-head"><text class="transfer-no">{{ item.transferNo }}</text><text class="status-pill">{{ statusLabel(item.status) }}</text></view>
|
||||
<view class="transfer-card-head"><text class="transfer-no">{{ item.transferNo }}</text><text class="status-pill">{{ statusLabel(item.status, item.transferNo) }}</text></view>
|
||||
<view class="task-metrics"><view><text>计划箱数</text><strong>{{ item.packageCount || 0 }}</strong></view><view><text>{{ item.destinationType === 'PRIVATE' ? '目的仓' : 'FBA 目的仓' }}</text><strong>{{ destinationLabel(item) }}</strong></view><view><text>换标</text><strong>{{ item.needRelabel ? '需要' : '不需要' }}</strong></view></view>
|
||||
<text class="transfer-meta">入仓码 {{ item.inboundCode || '—' }}</text>
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
<view v-if="!selected && entryMode === 'work' && !detailLoading" class="transfer-empty"><text class="empty-title">暂无进行中的转运测量作业</text><text>请先从“仓储转运测量列表”选择待测转运单。</text></view>
|
||||
|
||||
<view v-if="detailLoading" class="transfer-empty"><text class="empty-title">正在读取转运明细</text><text>请稍候,不要重复选择</text></view>
|
||||
<template v-if="selected && !detailLoading">
|
||||
<view class="operation-detail-heading"><button class="back-to-list" @tap="backToCandidates">‹ 返回待测列表</button><view><text class="operation-detail-title">作业详情</text><text class="operation-detail-subtitle">进入后完成当前转运单的测量、打托和拍照</text></view></view>
|
||||
<view v-if="isInventoryTransfer" class="transfer-success inventory-transfer-panel"><text class="feedback-title">库存转运 · 无需测量</text><text>该单来源于仓库现有库存,不需要重复测量或上传现场照片。请进入托盘作业,按来源托盘执行整托转运、拆托重打,或与相同目的仓合托。</text><button class="transfer-primary" @tap="openPalletOperations">进入托盘作业</button></view>
|
||||
<template v-if="!isInventoryTransfer">
|
||||
<view class="order-summary">
|
||||
@@ -81,6 +85,7 @@
|
||||
import PortalHeader from '@/components/PortalHeader.vue'
|
||||
import ScanTargetInput from '@/components/ScanTargetInput.vue'
|
||||
import { session, requireSession } from '@/stores/session.js'
|
||||
import { restoreWarehouseWork, resetWarehouseDomain, updateWarehouseWork, warehouseWork } from '@/stores/warehouse-work.js'
|
||||
import { startScanBroadcast } from '@/utils/scan-broadcast.js'
|
||||
import { bindWarehouseTransferPallet, deleteWarehouseTransferPallet, downloadWarehouseTransferMeasurementPhoto, downloadWarehouseTransferPalletLabel, generateWarehouseTransferPallet, getWarehouseTransferDetail, listWarehouseTransferCandidates, listWarehouseTransferMeasurementPhotos, palletizeWarehouseTransfer, saveWarehouseTransferPalletDraft, uploadWarehouseTransferMeasurementPhoto } from '@/api/warehouse-transfers.js'
|
||||
|
||||
@@ -96,7 +101,7 @@ const restoredPallet = (value, index) => ({
|
||||
|
||||
export default {
|
||||
components: { PortalHeader, ScanTargetInput },
|
||||
data() { return { user: session.currentUser, candidates: [], selected: null, pallets: [newPallet()], activePalletKey: '', photoFileIds: [], photoUrls: {}, photoNames: [], completedPallets: [], completedTransferNo: '', completedDestination: '', completedDestinationType: '', loading: false, detailLoading: false, submitting: false, uploadingPhoto: false, openingLabel: false, error: null, success: false, pendingKey: '' } },
|
||||
data() { return { user: session.currentUser, entryMode: 'work', pendingTaskNo: '', candidates: [], selected: null, pallets: [newPallet()], activePalletKey: '', photoFileIds: [], photoNames: [], photoUrls: {}, completedPallets: [], completedTransferNo: '', completedDestination: '', completedDestinationType: '', loading: false, detailLoading: false, submitting: false, uploadingPhoto: false, openingLabel: false, error: null, success: false, pendingKey: '' } },
|
||||
computed: {
|
||||
completedDestinationLabel() { return this.destinationLabel({ fbaWarehouseCode: this.completedDestination, destinationType: this.completedDestinationType }) },
|
||||
totalCartons() { return this.pallets.reduce((sum, pallet) => sum + Math.max(0, Number(pallet.cartonCount) || 0), 0) },
|
||||
@@ -121,7 +126,8 @@ export default {
|
||||
watch: {
|
||||
pallets: { deep: true, handler() { if (!this.submitting) this.pendingKey = '' } }
|
||||
},
|
||||
onShow() { if (requireSession()) { this.user = session.currentUser; this.startScanner(); this.loadCandidates() } },
|
||||
onShow() { if (requireSession()) { this.user = session.currentUser; restoreWarehouseWork(`${this.user.id}:${this.user.warehouseId}`); this.startScanner(); this.loadCandidates().then(() => { const activeNo = String(warehouseWork.warehouseTransfer.currentNo || ''); const taskNo = this.pendingTaskNo || activeNo; if (this.entryMode === 'work' && taskNo) { const active = this.candidates.find((item) => String(item.transferNo) === taskNo); if (active) { this.pendingTaskNo = ''; void this.select(active) } } }) } },
|
||||
onLoad(options) { this.entryMode = options?.mode === 'list' ? 'list' : 'work'; this.pendingTaskNo = String(options?.task || '') },
|
||||
onHide() { this.stopScanner() },
|
||||
beforeUnmount() { this.stopScanner() },
|
||||
methods: {
|
||||
@@ -130,7 +136,7 @@ export default {
|
||||
startScanner() { this.stopScanner(); this._stopScanner = startScanBroadcast({ onScan: (value) => this.handlePalletScan(value), onError: (error) => { this.error = error } }) },
|
||||
stopScanner() { if (this._stopScanner) this._stopScanner(); this._stopScanner = null },
|
||||
handlePalletScan(value) { const pallet = this.pallets.find((item) => item.key === this.activePalletKey && item.generated && !item.bound); if (!pallet) return; pallet.bindCode = String(value || '').trim(); this.bindPallet(pallet) },
|
||||
statusLabel(status) { return ({ WAIT_PDA_RECEIVING: '待测量', APPROVED: '待测量', PARTIALLY_RECEIVED: '测量中' })[status] || '待测量' },
|
||||
statusLabel(status, transferNo = '') { if (String(status) === 'WAIT_PDA_RECEIVING' && String(warehouseWork.warehouseTransfer.currentNo) === String(transferNo)) return '测量中'; return ({ WAIT_PDA_RECEIVING: '待测量', APPROVED: '待测量', PARTIALLY_RECEIVED: '测量中' })[status] || '待测量' },
|
||||
rawPalletVolume(pallet) { const values = [pallet.lengthCm, pallet.widthCm, pallet.heightCm].map(Number); return values.every((value) => value > 0) ? values[0] * values[1] * values[2] / 1000000 : 0 },
|
||||
palletVolume(pallet) { return this.rawPalletVolume(pallet).toFixed(3) },
|
||||
palletReady(pallet) { return Number.isInteger(Number(pallet.cartonCount)) && [pallet.cartonCount, pallet.lengthCm, pallet.widthCm, pallet.heightCm, pallet.grossWeightKg].every((value) => Number(value) > 0) },
|
||||
@@ -157,14 +163,29 @@ export default {
|
||||
catch (error) { this.error = error; pallet.busy = false }
|
||||
},
|
||||
async printDraftPallet(pallet) { if (!pallet.labelDownloadPath) { this.error = new Error('服务端未返回托盘标签下载地址'); return }; await this.openLabelPdf(() => downloadWarehouseTransferPalletLabel(pallet.labelDownloadPath)) },
|
||||
async loadCandidates() { this.loading = true; this.error = null; try { this.candidates = await listWarehouseTransferCandidates() } catch (e) { this.error = e } finally { this.loading = false } },
|
||||
async loadCandidates() { this.loading = true; this.error = null; try { const activeNo = String(warehouseWork.warehouseTransfer.currentNo || ''); this.candidates = (await listWarehouseTransferCandidates()).map((item) => String(item.transferNo) === activeNo && item.status === 'WAIT_PDA_RECEIVING' ? { ...item, status: 'PARTIALLY_RECEIVED' } : item) } catch (e) { this.error = e } finally { this.loading = false } },
|
||||
async select(item) {
|
||||
if (this.detailLoading || this.submitting) return
|
||||
const requestedNo = String(item?.transferNo || '')
|
||||
const activeNo = String(warehouseWork.warehouseTransfer.currentNo || '')
|
||||
if (!activeNo) {
|
||||
const result = await new Promise((resolve) => uni.showModal({ title: '开始测量', content: `确认开始测量转运单 ${requestedNo} 吗?开始后本设备需先完成该单,才能选择下一单。`, confirmText: '确认开始', cancelText: '取消', success: resolve, fail: () => resolve({ confirm: false }) }))
|
||||
if (!result.confirm) return
|
||||
}
|
||||
if (activeNo && activeNo !== requestedNo) {
|
||||
const result = await new Promise((resolve) => uni.showModal({ title: '存在未完结的测量单', content: `转运单 ${activeNo} 尚未测量完成,是否继续进入?`, confirmText: '去继续测量', cancelText: '取消', success: resolve, fail: () => resolve({ confirm: false }) }))
|
||||
if (!result.confirm) return
|
||||
item = this.candidates.find((candidate) => String(candidate.transferNo) === activeNo) || item
|
||||
}
|
||||
updateWarehouseWork('warehouseTransfer', { currentNo: String(item.transferNo) })
|
||||
this.candidates = this.candidates.map((candidate) => String(candidate.transferNo) === String(item.transferNo) ? { ...candidate, status: 'PARTIALLY_RECEIVED' } : candidate)
|
||||
if (this.entryMode === 'list') { uni.navigateTo({ url: `/pages/warehouse-transfer/warehouse-transfer?mode=work&task=${encodeURIComponent(String(item.transferNo))}` }); return }
|
||||
this.detailLoading = true; this.error = null; this.success = false; this.completedPallets = []; this.completedTransferNo = ''; this.completedDestination = ''; this.completedDestinationType = ''
|
||||
try { this.selected = await getWarehouseTransferDetail(item.transferNo); this.pendingKey = ''; this.pallets = this.selected.pallets?.length ? this.selected.pallets.map(restoredPallet) : [newPallet()]; await this.restorePhotos(item.transferNo, this.selected.measurementPhotos) }
|
||||
catch (e) { this.selected = null; this.error = e }
|
||||
finally { this.detailLoading = false }
|
||||
},
|
||||
backToCandidates() { if (this.submitting || this.detailLoading) return; this.entryMode = 'list'; this.selected = null; this.error = null; this.pallets = [newPallet()]; this.photoFileIds = []; this.photoUrls = {}; this.photoNames = [] },
|
||||
addPallet() { this.pendingKey = ''; this.pallets.push(newPallet()) },
|
||||
async removePallet(index) { const pallet = this.pallets[index]; if (!pallet || pallet.generated || pallet.busy) return; pallet.busy = true; try { if (pallet.draftId) await deleteWarehouseTransferPallet(this.selected.transferNo, pallet.draftId); this.pallets.splice(index, 1); this.pendingKey = '' } catch (error) { pallet.busy = false; this.error = error } },
|
||||
async restorePhotos(transferNo, knownPhotos) {
|
||||
@@ -203,7 +224,7 @@ export default {
|
||||
try {
|
||||
if (!this.pendingKey) this.pendingKey = `pda-transfer-${this.selected.transferNo}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
|
||||
const payload = { idempotencyKey: this.pendingKey, photoFileIds: [...this.photoFileIds] }
|
||||
const transferNo = this.selected.transferNo; const result = await palletizeWarehouseTransfer(transferNo, payload); this.completedTransferNo = transferNo; this.completedDestination = result?.fbaWarehouseCode || this.selected.fbaWarehouseCode || ''; this.completedDestinationType = result?.destinationType || this.selected.destinationType || ''; this.completedPallets = (result?.pallets || []).filter((pallet) => pallet?.palletNo); this.success = true; this.selected = null; this.pendingKey = ''; this.pallets = [newPallet()]; this.photoFileIds = []; this.photoUrls = {}; this.photoNames = []; await this.loadCandidates()
|
||||
const transferNo = this.selected.transferNo; const result = await palletizeWarehouseTransfer(transferNo, payload); this.completedTransferNo = transferNo; this.completedDestination = result?.fbaWarehouseCode || this.selected.fbaWarehouseCode || ''; this.completedDestinationType = result?.destinationType || this.selected.destinationType || ''; this.completedPallets = (result?.pallets || []).filter((pallet) => pallet?.palletNo); this.success = true; resetWarehouseDomain('warehouseTransfer'); this.selected = null; this.pendingKey = ''; this.pallets = [newPallet()]; this.photoFileIds = []; this.photoUrls = {}; this.photoNames = []; await this.loadCandidates()
|
||||
} catch (e) { this.error = e } finally { this.submitting = false }
|
||||
},
|
||||
async printPallet(pallet) { if (!pallet?.labelDownloadPath) { this.error = new Error('服务端未返回托盘标签下载地址,请刷新后重试'); return }; await this.openLabelPdf(() => downloadWarehouseTransferPalletLabel(pallet.labelDownloadPath)) },
|
||||
@@ -241,6 +262,7 @@ export default {
|
||||
.take-photo-button[disabled]{color:#9aa7b5;background:#e7ecf2;opacity:1}
|
||||
.print-card{box-sizing:border-box;margin-bottom:11px;padding:14px;border:1px solid #b9d3f4;border-radius:13px;background:#f8fbff}.print-row button{width:auto;margin:0;padding:0 10px;color:#135ee8;border:1px solid #9fc0ef;border-radius:8px;background:#fff}.print-row{display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-top:1px solid #dce8f5;color:#728196;font-size:9px}.print-code{display:block;color:#17314f;font-size:13px;font-weight:850}
|
||||
.generate-button{width:100%;margin-top:12px;color:#fff;background:#135ee8}.generated-card{display:flex;align-items:center;justify-content:space-between;margin-top:12px;padding:10px;border-radius:9px;background:#edf5ff}.generated-card button,.bind-panel>button{width:auto;margin:0;color:#135ee8;background:#fff}.bind-panel{margin-top:10px}.bound-state{margin-top:10px;padding:9px;border-radius:8px;color:#08705f;font-weight:800;background:#dff5ed}
|
||||
.operation-detail-heading{display:flex;align-items:flex-start;gap:10px;margin:0 2px 12px;padding:2px 0}.back-to-list{width:auto;height:30px;margin:0;padding:0 9px;border:1px solid #c8d8ec;border-radius:8px;color:#135ee8;font-size:10px;line-height:30px;background:#f8fbff}.operation-detail-heading>view{min-width:0}.operation-detail-title{display:block;color:#243951;font-size:15px;font-weight:850}.operation-detail-subtitle{display:block;margin-top:3px;color:#8794a4;font-size:9px;line-height:14px}
|
||||
.collapse-button{width:30px;height:28px;margin:0;padding:0;color:#135ee8;font-size:16px;line-height:28px;background:#edf5ff}
|
||||
.receipt-comparison{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;margin-bottom:9px}.receipt-comparison view{padding:9px;border-radius:9px;text-align:center;background:#fff}.receipt-comparison strong,.receipt-comparison text{display:block}.receipt-comparison strong{color:#17314f;font-size:15px}.receipt-comparison text{margin-top:2px;color:#8390a1;font-size:8px}.receipt-comparison .difference{border:1px solid #e3a044;background:#fff4df}.receipt-comparison .difference strong,.receipt-comparison .difference text{color:#a35b0c;font-weight:800}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user