Files
pda-overseas-uniapp/src/components/ExceptionReturnWork.vue
T
2026-10-30 17:59:57 +08:00

75 lines
6.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<scroll-view class="work-scroll" scroll-y><view class="portal-content work-content">
<view v-if="error" class="work-card"><text role="alert">{{ error }}</text><button :disabled="busy" @tap="refresh">刷新任务</button></view>
<template v-if="!current">
<view class="row-head"><text class="card-title">异常退货入库</text><button :disabled="busy" @tap="refresh">刷新</button></view>
<text v-if="!busy && !tasks.length" class="empty-copy">当前仓没有待验收异常退货任务</text>
<view v-for="task in tasks" :key="task.exceptionNo" class="work-card">
<text class="business-code">{{ task.exceptionNo }}</text>
<text class="meta-line">车牌号:{{ task.vehiclePlate || '未登记' }}</text>
<text class="meta-line">客户:{{ task.customerName || '—' }} · 待收 {{ task.packageCount - task.receivedPackages }} 箱</text>
<button class="primary-button" :disabled="busy" @tap="start(task)">开始作业</button>
</view>
</template>
<template v-else>
<view class="work-card"><text class="business-code">{{ current.order.exceptionNo }}</text><text class="meta-line">车牌号:{{ selectedTask.vehiclePlate || '未登记' }}</text><button :disabled="busy" @tap="leave">返回任务列表</button></view>
<view v-for="(item,index) in current.returnSources" :key="`${item.outboundPalletItemId}:${item.inventoryPositionId}:${item.returnCartonId}`" class="work-card">
<text class="card-title">{{ item.skuCode || '货物' }}</text><text class="meta-line">原托盘 / 箱唛:{{ item.cartonMark || item.palletNo || '—' }}</text>
<text class="meta-line">剩余可收 {{ item.maxReturnPackages }} 箱</text>
<input v-model="quantities[index]" type="number" placeholder="本批实收箱数" :disabled="busy" />
</view>
<view class="work-card"><text class="card-title">上架位置</text><input v-model="palletCode" placeholder="目标托盘号" :disabled="busy" /><input v-model="locationCode" placeholder="目标库位号" :disabled="busy" /></view>
<view class="work-card"><text class="card-title">实收照片</text><text v-for="photo in photos" :key="photo.fileId" class="meta-line">{{ photo.fileName }}</text><button :disabled="busy || photos.length >= 20" @tap="photo">拍照 / 选择照片</button></view>
<button class="primary-button" :disabled="busy || !ready" @tap="receive">{{ busy ? '处理中…' : '确认本批入库' }}</button>
</template>
<text v-if="message" class="meta-line" role="status">{{ message }}</text>
</view></scroll-view>
</template>
<script>
import { listExceptionReturns, startExceptionReturn, uploadExceptionReturnEvidence, receiveExceptionReturn } from '@/api/exception-returns.js'
export default {
data() { return { tasks: [], current: null, selectedTask: null, quantities: [], palletCode: '', locationCode: '', photos: [], busy: false, error: '', message: '', revision: 0 } },
computed: {
ready() { return this.current && this.palletCode.trim() && this.locationCode.trim() && this.photos.length && this.quantities.some(value => Number(value) > 0) },
},
mounted() { this.refresh() },
beforeUnmount() { this.revision++ },
methods: {
// 页面切换后不再用迟到响应覆盖另一张作业单。
async run(action) { if (this.busy) return; const revision = this.revision; this.busy = true; this.error = ''; try { await action(() => revision === this.revision) } catch (error) { if (revision === this.revision) this.error = error.message || '操作失败,请刷新核对实际进度' } finally { if (revision === this.revision) this.busy = false } },
async refresh() { await this.run(async valid => { const tasks = await listExceptionReturns(); if (valid()) this.tasks = tasks }) },
async start(task) { await this.run(async valid => { const value = await startExceptionReturn(task.exceptionNo); if (valid()) { this.current = value; this.selectedTask = task; this.quantities = []; this.photos = []; this.palletCode = ''; this.locationCode = ''; this.message = '' } }) },
async leave() { if (this.busy) return; this.current = null; this.photos = []; await this.refresh() },
async photo() { await this.run(async valid => { const result = await new Promise((resolve,reject) => uni.chooseImage({ count: 1, sourceType: ['camera','album'], success: resolve, fail: reject })); const uploaded = await uploadExceptionReturnEvidence(this.current.order.exceptionNo,result.tempFilePaths[0]); if (valid()) this.photos.push(uploaded) }) },
async receive() {
if (!this.ready) return
await this.run(async valid => {
const items = this.current.returnSources.flatMap((item,index) => {
const packages = Number(this.quantities[index] || 0)
if (!Number.isSafeInteger(packages) || packages < 0 || packages > item.maxReturnPackages || item.returnCartonId && packages > 1) throw new Error('实收箱数必须为整数且不超过剩余可收数量')
return packages ? [{ outboundPalletItemId: item.outboundPalletItemId, inventoryPositionId: item.inventoryPositionId, returnCartonId: item.returnCartonId, packages }] : []
})
const number = this.current.order.exceptionNo
const result = await receiveExceptionReturn(number,{ expectedVersion: this.current.order.version, idempotencyKey: `exception-return-${Date.now()}-${Math.random().toString(36).slice(2)}`, palletCode: this.palletCode.trim(), locationCode: this.locationCode.trim(), items, evidenceFileIds: this.photos.map(photo => photo.fileId) })
if (!valid()) return
this.quantities = []; this.photos = []; this.message = `本批已入库 ${result.restoredPackages} 箱${result.status === 'COMPLETED' ? ',异常处理完成' : ',还有货物待收'}`
if (result.status === 'COMPLETED') { this.current = null; this.tasks = await listExceptionReturns() }
else this.current = await startExceptionReturn(number)
})
// 结果不明时重新读取权威版本,禁止使用旧快照重复累计数量。
if (this.error && this.current) {
try { this.current = await startExceptionReturn(this.current.order.exceptionNo); this.quantities = [] }
catch (_) { this.current = null; this.tasks = []; }
}
},
},
}
</script>
<style scoped>
.work-card { margin: 12px 0; padding: 14px; border: 1px solid #dfe7ef; border-radius: 12px; background: #fff; }
.business-code,.meta-line,.card-title { display: block; margin-bottom: 10px; }
.business-code,.card-title { font-weight: 700; }.meta-line { color: #61758b; font-size: 13px; }
input { height: 44px; margin: 8px 0; padding: 0 10px; border: 1px solid #cbd7e4; border-radius: 8px; }
button { margin-top: 10px; font-size: 14px; }.primary-button { color: #fff; background: #1668dc; }.primary-button[disabled] { background: #a2b2c8; }
</style>