This commit is contained in:
2026-11-05 11:56:04 +08:00
parent c1706685f5
commit 48602becd1
3 changed files with 31 additions and 18 deletions
+4
View File
@@ -28,6 +28,10 @@
<script>
import { listExceptionReturns, startExceptionReturn, uploadExceptionReturnEvidence, receiveExceptionReturn } from '@/api/exception-returns.js'
export default {
emits: ['working-change'],
watch: {
current(value) { this.$emit('working-change', Boolean(value)) },
},
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) },
+8 -9
View File
@@ -1,11 +1,11 @@
<template>
<view v-if="user" class="page-shell portal-page work-page receipt-page" :class="{ 'pda-task-list receipt-lookup-page': receiptDomain === 'customer' && !current && !result }">
<PortalHeader title="退货入库" :user="user" />
<view class="receipt-domain-tabs" role="tablist" aria-label="退货入库类型">
<view v-if="!current && !result && !exceptionWorking" class="receipt-domain-tabs" role="tablist" aria-label="退货入库类型">
<button :class="['receipt-domain-tab', { active: receiptDomain === 'customer' }]" role="tab" :aria-selected="receiptDomain === 'customer'" :disabled="busy" @tap="switchDomain('customer')">客户退货入库</button>
<button :class="['receipt-domain-tab', { active: receiptDomain === 'exception' }]" role="tab" :aria-selected="receiptDomain === 'exception'" :disabled="busy" @tap="switchDomain('exception')">异常退货入库</button>
</view>
<ExceptionReturnWork v-if="receiptDomain === 'exception'" />
<ExceptionReturnWork v-if="receiptDomain === 'exception'" @working-change="exceptionWorking = $event" />
<scroll-view v-if="receiptDomain === 'customer'" class="work-scroll" scroll-y><view class="portal-content work-content">
<view v-if="!current && !result" class="work-card receipt-lookup-card">
<text class="card-title">扫描 SKU 69 码</text>
@@ -56,10 +56,8 @@
</view>
</template>
<view v-if="unitType === 'PALLET'" class="item-list receipt-sku-list"><view v-for="item in selectedItems" :key="item.skuCode" class="item-card"><view class="row-head"><view><text class="business-code">{{ item.skuCode }}</text><text class="meta-line">{{ item.productNameZh || '商品名称未填写' }}</text></view><text class="remaining-copy">剩余 {{ remaining(item) }}</text></view><view class="sku-quantity-fields"><view class="quantity-row"><text>数量</text><input class="number-input" type="number" :value="quantity(item.skuCode)" placeholder="0" @input="setQuantity(item, $event.detail.value)" /></view><view v-if="unitType === 'PALLET'" class="quantity-row"><text>SKU 箱数</text><input class="number-input" type="number" :value="boxCount(item.skuCode)" placeholder="0" @input="setBoxCount(item, $event.detail.value)" /></view></view></view></view></view>
<view v-if="unitType === 'PALLET'" class="summary-bar"><view><text class="summary-label">{{ unitType === 'PARCEL' ? '本次合计' : '本托合计' }}</text><text class="summary-value">{{ receiptQuantity }}</text></view><button class="primary submit-button" :disabled="!canConfirm || busy" @tap="confirmReceipt">{{ busy ? '提交中…' : unitType === 'PARCEL' ? '确认包裹签收' : '确认签收并入托' }}</button></view>
<view class="summary-bar"><view><text class="summary-label">{{ unitType === 'PALLET' ? '本托合计' : '本次合计' }}</text><text class="summary-value">{{ receiptQuantity }}</text></view><view class="receipt-action-buttons"><button class="primary submit-button" :disabled="busy || !canConfirm || (unitType !== 'PALLET' && !allBoxRowsConfirmed)" @tap="confirmReceipt">{{ busy ? '提交中…' : '确认签收' }}</button><button class="secondary manual-complete" :disabled="busy" @tap="manualComplete">缺货完结</button></view></view>
<view class="work-card"><view class="row-head"><view><text class="card-title">退货打托照片</text></view><text class="status-pill">{{ photoFileIds.length }} / 20 张</text></view><button class="secondary" :disabled="busy || photoFileIds.length >= 20" @tap="choosePhoto()">拍照或从相册选择</button><view v-if="photoFileIds.length" class="photo-grid"><view v-for="(fileId,index) in photoFileIds" :key="fileId" class="photo-tile"><image class="photo-thumbnail" :src="photoUrls[fileId]" mode="aspectFill" @tap="previewPhoto(fileId)" /><text class="photo-name">{{ photoNames[index] || '退货打托照片' }}</text><button class="link-button" :disabled="busy" @tap="removePhoto(index)">移除</button></view></view></view>
<view v-if="unitType !== 'PALLET'" class="summary-bar"><view><text class="summary-label">本次合计</text><text class="summary-value">{{ receiptQuantity }}</text></view><button class="primary submit-button" :disabled="busy || !canConfirm || !allBoxRowsConfirmed || !photoFileIds.length" @tap="confirmReceipt">{{ busy ? '提交中…' : '确认签收入库' }}</button></view>
<button v-if="current.status === 'PARTIALLY_RECEIVED'" class="secondary manual-complete" :disabled="busy" @tap="manualComplete">缺货手动完结</button>
<view v-if="current.completedPallets?.length" class="work-card"><text class="card-title">本单已完成打托</text><view class="item-list"><view v-for="pallet in current.completedPallets" :key="pallet.palletCode" class="item-card"><text class="business-code">{{ pallet.palletCode }}</text><text class="meta-line">{{ pallet.skuCode }} · {{ pallet.quantity }} 件</text><text class="meta-line">签收批次:{{ pallet.receiptBatchNo }}</text></view></view></view>
</template>
@@ -82,7 +80,7 @@
export default {
components: { PortalHeader, ScanTargetInput, ExceptionReturnWork },
data() { return { receiptDomain: 'customer', user: session.currentUser, current: null, candidates: null, result: null, receiptResults: [], selectedSku: '', palletCheck: null, newPalletHtml: '', unitType: 'PALLET', unitCode: '', boxRows: [{ items: {}, length: '', width: '', height: '', weight: '', originalPackaging: true, confirmed: false }], palletLength: '', palletWidth: '', palletHeight: '', palletWeight: '', unitOptions: [{ value: 'PALLET', label: '托盘' }, { value: 'BOX', label: '箱子' }, { value: 'PARCEL', label: '包裹' }], photoFileIds: [], photoNames: [], photoUrls: {}, activeTarget: 'lookup', busy: false, error: null } },
data() { return { receiptDomain: 'customer', exceptionWorking: false, user: session.currentUser, current: null, candidates: null, result: null, receiptResults: [], selectedSku: '', palletCheck: null, newPalletHtml: '', unitType: 'PALLET', unitCode: '', boxRows: [{ items: {}, length: '', width: '', height: '', weight: '', originalPackaging: true, confirmed: false }], palletLength: '', palletWidth: '', palletHeight: '', palletWeight: '', unitOptions: [{ value: 'PALLET', label: '托盘' }, { value: 'BOX', label: '箱子' }, { value: 'PARCEL', label: '包裹' }], photoFileIds: [], photoNames: [], photoUrls: {}, activeTarget: 'lookup', busy: false, error: null } },
computed: {
entryMode: { get() { return warehouseWork.returnReceipt.entryMode }, set(value) { updateWarehouseWork('returnReceipt', { entryMode: value }) } },
lookupCode: { get() { return warehouseWork.returnReceipt.lookupCode }, set(value) { updateWarehouseWork('returnReceipt', { lookupCode: value }) } },
@@ -143,7 +141,7 @@
printPallet() { if (!this.newPalletHtml) return; /* #ifdef H5 */ const view = window.open('', '_blank'); if (view) { view.document.write(this.newPalletHtml); view.document.close(); view.focus(); view.print() } else this.setError(new Error('请允许打开标签打印窗口'), '打印失败'); /* #endif */ /* #ifndef H5 */ this.setError(new Error('当前设备未配置打印服务'), '打印失败'); /* #endif */ },
printReturnCarton(label) { if (!label?.printableHtml) return; /* #ifdef H5 */ const view = window.open('', '_blank'); if (view) { view.document.write(label.printableHtml); view.document.close(); view.focus(); view.print() } else this.setError(new Error('请允许打开标签打印窗口'), '打印失败'); /* #endif */ /* #ifndef H5 */ this.setError(new Error('当前设备未配置打印服务'), '打印失败'); /* #endif */ },
acceptTracking(raw) { this.trackingNo = String(raw?.detail?.value ?? raw ?? '').trim(); playSuccessSound() },
async confirmReceipt() { if (this.busy) return; const totalBoxCount = this.palletBoxCount; if (this.unitType === 'PALLET' && totalBoxCount <= 0) { this.setError(new Error('请先填写各 SKU 的箱数'), '托盘签收校验失败'); return } if (!this.canConfirm || this.busy || (this.unitType !== 'PALLET' && !this.allBoxRowsConfirmed)) return; if (this.unitType !== 'PALLET' && !this.photoFileIds.length) { this.setError(new Error('请先拍照或上传签收照片,再确认签收入库'), '签收校验失败'); return } const willComplete = this.current.expectedItems.every((item) => (this.unitType !== 'PALLET' ? this.boxRows.reduce((sum, box) => sum + (Number(box.items[item.skuCode]) || 0), 0) : this.quantity(item.skuCode)) >= this.remaining(item)); if (willComplete && !this.photoFileIds.length) { const modal = await new Promise((resolve) => uni.showModal({ title: '缺少退货打托照片', content: '本次签收将完成整单入库,请先拍照并上传后再确认打托。', confirmText: '去拍照', cancelText: '取消', success: resolve })); if (modal?.confirm) this.error = { message: '请先拍照并上传退货打托照片后再确认' }; return }
async confirmReceipt() { if (this.busy) return; const totalBoxCount = this.palletBoxCount; if (this.unitType === 'PALLET' && totalBoxCount <= 0) { this.setError(new Error('请先填写各 SKU 的箱数'), '托盘签收校验失败'); return } if (!this.canConfirm || this.busy || (this.unitType !== 'PALLET' && !this.allBoxRowsConfirmed)) return; const willComplete = this.current.expectedItems.every((item) => (this.unitType !== 'PALLET' ? this.boxRows.reduce((sum, box) => sum + (Number(box.items[item.skuCode]) || 0), 0) : this.quantity(item.skuCode)) >= this.remaining(item)); if (!this.photoFileIds.length && (this.unitType !== 'PALLET' || willComplete)) { uni.showModal({ title: '请先拍照', content: '请先拍照或从相册上传签收照片,再点击确认签收。', showCancel: false, confirmText: '知道了' }); return }
await this.run(async () => {
const rows = this.unitType !== 'PALLET' ? this.boxRows : [null]
let expectedReceivedQuantity = this.current.expectedItems.reduce((sum, item) => sum + Number(item.receivedQuantity || 0), 0)
@@ -254,13 +252,14 @@
.receipt-page :deep(.portal-header-shell){flex-shrink:0}
.receipt-page>.work-scroll{flex:1;height:0;min-height:0}
.receipt-page .work-content{padding-bottom:calc(24px + env(safe-area-inset-bottom))}
.receipt-page .summary-bar{position:static}
.receipt-page .summary-bar{position:static;margin-bottom:10px}
.receipt-domain-tabs{display:flex;flex-shrink:0;gap:4px;margin:10px 14px 0;padding:4px;border:1px solid #d8e4f5;border-radius:10px;background:#e8eff9}
.receipt-domain-tab{box-sizing:border-box;flex:1;min-width:0;min-height:40px;margin:0;padding:0 8px;border-radius:7px;color:#52677f;background:transparent;font-size:14px;line-height:40px}
.receipt-domain-tab::after{border:0}
.receipt-domain-tab.active{color:#135ee8;background:#fff;font-weight:700;box-shadow:0 1px 5px rgba(28,69,121,.1)}
.receipt-domain-tab[disabled]{opacity:.6}
.manual-complete{margin-top:12px}
.summary-bar>.receipt-action-buttons{display:flex;flex:1;min-width:0;gap:8px}
.receipt-action-buttons>.primary,.receipt-action-buttons>.secondary{flex:1;min-width:0;width:auto;margin:0;padding:0 6px;white-space:nowrap}
.receipt-lookup-page{background:#f1f5fd}
.receipt-lookup-page .work-content{padding-top:14px}