75 lines
6.7 KiB
Vue
75 lines
6.7 KiB
Vue
<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>
|