优化
This commit is contained in:
@@ -2,7 +2,12 @@
|
||||
<view v-if="user" class="page-shell portal-page work-page">
|
||||
<PortalHeader title="退货合箱" :user="user" fallback-url="/pages/module/module?key=inbound" />
|
||||
<scroll-view class="work-scroll" scroll-y><view class="portal-content work-content">
|
||||
<view v-if="!result" class="work-card"><text class="card-title">添加退货单</text><ScanTargetInput name="returnNo" v-model="searchCode" label="退货单号" placeholder="扫描或输入完整退货单号" :active="true" @submit="search" /><button class="primary" :disabled="!searchCode.trim() || busy" @tap="search(searchCode)">{{ busy ? '查询中…' : '查询可合箱库存' }}</button></view>
|
||||
<template v-if="!result && !activePlan">
|
||||
<view v-if="plans.length" class="work-card"><view class="row-head"><text class="card-title">待执行合箱计划</text><button class="link-button" :disabled="busy" @tap="loadPlans">刷新</button></view><button v-for="plan in plans" :key="plan.planNo" class="task-card candidate-card" @tap="openPlan(plan)"><view class="row-head"><view><text class="business-code">{{ plan.planNo }}</text><text class="meta-line">{{ plan.mode === 'CUSTOMER_INSTRUCTION' ? '客户合箱指令' : '仓库自主合箱' }} · {{ plan.returnNos.length }} 个订单</text></view><text class="select-link">开始 ›</text></view><text class="meta-line">SKU {{ plan.skuLines.length }} 种 · 计划 {{ plan.boxes.length }} 箱</text></button></view>
|
||||
<view class="work-card"><text class="card-title">无计划时自主合箱</text><ScanTargetInput name="returnNo" v-model="searchCode" label="退货单号" placeholder="扫描或输入完整退货单号" :active="true" @submit="search" /><button class="primary" :disabled="!searchCode.trim() || busy" @tap="search(searchCode)">{{ busy ? '查询中…' : '查询可合箱库存' }}</button></view>
|
||||
</template>
|
||||
|
||||
<template v-if="activePlan && !result"><view class="work-card"><view class="row-head"><view><text class="card-title">{{ activePlan.planNo }}</text><text class="meta-line">{{ activePlan.mode === 'CUSTOMER_INSTRUCTION' ? '客户合箱指令' : '仓库自主合箱' }} · 来源订单 {{ activePlan.returnNos.length }} 个</text></view><button class="link-button" @tap="closePlan">返回列表</button></view><text class="hint">计划未配置箱明细时,系统已按单 SKU 一箱生成默认方案。可以合并为混箱,也可以拆成多个单箱。</text></view><view v-for="(box, index) in planBoxes" :key="index" class="work-card"><view class="row-head"><text class="card-title">第 {{ index + 1 }} 箱</text><button class="link-button" :disabled="planBoxes.length <= 1" @tap="removePlanBox(index)">删除箱</button></view><view v-for="(item, itemIndex) in box.items" :key="itemIndex" class="quantity-row"><input class="number-input plan-sku-input" v-model="item.skuCode" placeholder="SKU" /><input class="number-input" type="number" v-model="item.quantity" placeholder="数量" /><button class="link-button" @tap="removePlanItem(box, itemIndex)">移除</button></view><button class="secondary" @tap="addPlanItem(box)">添加 SKU 到本箱</button><view class="dimension-grid"><label v-for="field in dimensionFields" :key="field.key"><text>{{ field.label }}</text><input class="number-input" type="digit" v-model="box[field.key]" :placeholder="field.placeholder" /></label></view></view><button class="secondary" @tap="addPlanBox">添加箱</button><button class="primary" :disabled="!canCompletePlan || busy" @tap="completePlan">{{ busy ? '生成中…' : '确认合箱并生成箱唛' }}</button></template>
|
||||
|
||||
<view v-if="candidates !== null && !result" class="section-gap"><view v-if="!candidates.length" class="empty-box"><text class="empty-copy">没有匹配的已完成退货单或可用库存</text></view><view v-else class="task-list"><button v-for="item in candidates" :key="item.returnNo" class="task-card candidate-card" @tap="addOrder(item)"><view class="row-head"><view><text class="business-code">{{ item.returnNo }}</text><text class="meta-line">客户参考号:{{ item.customerReferenceNo || '—' }}</text></view><text class="select-link">添加 ›</text></view></button></view></view>
|
||||
|
||||
@@ -13,7 +18,7 @@
|
||||
<button class="primary" :disabled="!canMerge || busy" @tap="mergeCartons">{{ busy ? '合箱中…' : `确认合箱(${mergeQuantity} 件)` }}</button>
|
||||
</template>
|
||||
|
||||
<view v-if="result" class="success-box result-card"><text class="success-title">退货合箱成功</text><text class="result-carton">{{ result.label.cartonMark || result.cartonMark }}</text><text class="meta-line">来源退货单:{{ result.label.sourceReturnNo || '—' }}</text><text class="meta-line">箱规:{{ sizeText(result.label) }}</text><view class="item-list section-gap"><view v-for="item in result.label.items" :key="item.skuCode" class="item-card"><view class="row-head"><text class="business-code">{{ item.skuCode }}</text><text class="remaining-copy">{{ item.quantity }} 件</text></view><text class="meta-line">{{ item.productNameZh || '商品名称未填写' }}</text></view></view><button class="secondary" @tap="copyCartonMark">复制新箱号</button><button class="primary" @tap="nextPacking">继续合箱</button></view>
|
||||
<view v-if="result" class="success-box result-card"><text class="success-title">退货合箱成功,共 {{ (result.labels || [result.label]).length }} 箱</text><view v-for="label in (result.labels || [result.label])" :key="label.cartonMark" class="item-card"><text class="result-carton">{{ label.cartonMark }}</text><text class="meta-line">来源退货单:{{ label.sourceReturnNo || '—' }}</text><text class="meta-line">箱规:{{ sizeText(label) }}</text><view class="item-list section-gap"><view v-for="item in label.items" :key="item.skuCode" class="row-head"><text class="business-code">{{ item.skuCode }}</text><text class="remaining-copy">{{ item.quantity }} 件</text></view></view></view><button class="secondary" @tap="copyCartonMark">复制首个箱号</button><button class="primary" @tap="nextPacking">继续合箱</button></view>
|
||||
<view v-if="error" class="error-box" role="alert"><text class="error-title">{{ error.message }}</text><text v-if="error.code">错误码:{{ error.code }}</text><text v-if="error.requestId">requestId:{{ error.requestId }}</text><text v-if="error.traceId">traceId:{{ error.traceId }}</text><button v-if="selectedOrders.length" class="secondary" :disabled="busy" @tap="refreshSelected">重新读取可用库存</button></view>
|
||||
</view></scroll-view>
|
||||
</view>
|
||||
@@ -22,7 +27,7 @@
|
||||
<script>
|
||||
import PortalHeader from '@/components/PortalHeader.vue'
|
||||
import ScanTargetInput from '@/components/ScanTargetInput.vue'
|
||||
import { findPackingOrders, mergeReturnCartons } from '@/api/return-packing.js'
|
||||
import { findPackingOrders, listReturnPackingPlans, mergeReturnCartons, mergeReturnPackingPlan } from '@/api/return-packing.js'
|
||||
import { session, requireSession } from '@/stores/session.js'
|
||||
import { releaseWarehouseKey, resetWarehouseDomain, restoreWarehouseWork, updateWarehouseWork, warehouseIdempotencyKey, warehouseWork } from '@/stores/warehouse-work.js'
|
||||
import { startScanBroadcast } from '@/utils/scan-broadcast.js'
|
||||
@@ -30,7 +35,7 @@
|
||||
|
||||
export default {
|
||||
components: { PortalHeader, ScanTargetInput },
|
||||
data() { return { user: session.currentUser, candidates: null, previews: {}, result: null, busy: false, error: null, dimensionFields: [{ key: 'length', label: '长 cm', placeholder: '长' }, { key: 'width', label: '宽 cm', placeholder: '宽' }, { key: 'height', label: '高 cm', placeholder: '高' }, { key: 'weight', label: '重量 kg', placeholder: '重量' }] } },
|
||||
data() { return { user: session.currentUser, candidates: null, previews: {}, plans: [], activePlan: null, planBoxes: [], result: null, busy: false, error: null, dimensionFields: [{ key: 'length', label: '长 cm', placeholder: '长' }, { key: 'width', label: '宽 cm', placeholder: '宽' }, { key: 'height', label: '高 cm', placeholder: '高' }, { key: 'weight', label: '重量 kg', placeholder: '重量' }] } },
|
||||
computed: {
|
||||
packing() { return warehouseWork.returnPacking },
|
||||
searchCode: { get() { return this.packing.searchCode }, set(value) { updateWarehouseWork('returnPacking', { searchCode: value }) } },
|
||||
@@ -38,11 +43,20 @@
|
||||
availableSkus() { const result = new Map(); for (const order of this.selectedOrders) for (const item of this.previews[order.returnNo]?.skus || []) { const current = result.get(item.skuCode) || { ...item, availableQuantity: 0 }; current.availableQuantity += Number(item.availableQuantity || 0); result.set(item.skuCode, current) } return [...result.values()] },
|
||||
mergeQuantity() { return Object.values(this.packing.quantities || {}).reduce((total, value) => total + (Number(value) || 0), 0) },
|
||||
validDimensions() { return this.dimensionFields.every((field) => Number(this.packing[field.key]) > 0) },
|
||||
canMerge() { return this.selectedOrders.length > 0 && this.mergeQuantity > 0 && this.validDimensions && this.availableSkus.every((item) => this.quantity(item.skuCode) <= item.availableQuantity) }
|
||||
canMerge() { return this.selectedOrders.length > 0 && this.mergeQuantity > 0 && this.validDimensions && this.availableSkus.every((item) => this.quantity(item.skuCode) <= item.availableQuantity) },
|
||||
canCompletePlan() { return this.planBoxes.length > 0 && this.planBoxes.every((box) => box.items.some((item) => String(item.skuCode || '').trim() && Number(item.quantity) > 0) && this.dimensionFields.every((field) => Number(box[field.key]) > 0)) }
|
||||
},
|
||||
onShow() { if (!requireSession()) return; this.user = session.currentUser; restoreWarehouseWork(`${this.user.id}:${this.user.warehouseId}`); this.startScanner(); if (this.selectedOrders.length && !Object.keys(this.previews).length) this.refreshSelected() },
|
||||
onShow() { if (!requireSession()) return; this.user = session.currentUser; restoreWarehouseWork(`${this.user.id}:${this.user.warehouseId}`); this.startScanner(); this.loadPlans(); if (this.selectedOrders.length && !Object.keys(this.previews).length) this.refreshSelected() },
|
||||
onHide() { this.stopScanner() }, onUnload() { this.stopScanner() }, beforeUnmount() { this.stopScanner() },
|
||||
methods: {
|
||||
async loadPlans() { try { this.plans = await listReturnPackingPlans() } catch (error) { this.plans = []; this.setError(error, '合箱计划加载失败') } },
|
||||
openPlan(plan) { this.activePlan = plan; this.planBoxes = (plan.boxes || []).map((box) => ({ ...box, items: (box.items || []).map((item) => ({ ...item })) })) },
|
||||
closePlan() { this.activePlan = null; this.planBoxes = [] },
|
||||
addPlanBox() { this.planBoxes.push({ boxNo: this.planBoxes.length + 1, items: [{ skuCode: '', quantity: 0 }], length: 0, width: 0, height: 0, weight: 0 }) },
|
||||
removePlanBox(index) { if (this.planBoxes.length > 1) this.planBoxes.splice(index, 1) },
|
||||
addPlanItem(box) { box.items.push({ skuCode: '', quantity: 0 }) },
|
||||
removePlanItem(box, index) { if (box.items.length > 1) box.items.splice(index, 1) },
|
||||
async completePlan() { if (!this.canCompletePlan || this.busy || !this.activePlan) return; const payload = { boxes: this.planBoxes.map((box, index) => ({ boxNo: index + 1, items: box.items.filter((item) => String(item.skuCode || '').trim() && Number(item.quantity) > 0).map((item) => ({ skuCode: String(item.skuCode).trim(), quantity: Number(item.quantity) })), length: Number(box.length), width: Number(box.width), height: Number(box.height), weight: Number(box.weight) })), idempotencyKey: warehouseIdempotencyKey('returnPackingPlan', 'complete', { planNo: this.activePlan.planNo, boxes: this.planBoxes }).value }; await this.run(async () => { this.result = await mergeReturnPackingPlan(this.activePlan.planNo, payload); this.activePlan = null; this.planBoxes = []; await this.loadPlans(); playSuccessSound() }, '合箱计划执行失败') },
|
||||
startScanner() { this.stopScanner(); this._stopScanner = startScanBroadcast({ onScan: (value) => { if (!this.busy) { this.searchCode = value; this.search(value) } }, onError: (error) => this.setError(error, '扫码广播注册失败') }) },
|
||||
stopScanner() { if (this._stopScanner) this._stopScanner(); this._stopScanner = null },
|
||||
async search(raw) { const value = String(raw?.detail?.value ?? raw ?? this.searchCode).trim(); if (!value || this.busy) return; this.searchCode = value; await this.run(async () => { const preview = await findPackingOrders(value); this.candidates = preview.orders.filter((item) => !this.selectedOrders.some((selected) => selected.returnNo === item.returnNo)); if (this.candidates.length) playSuccessSound() }, '查询可合箱退货单失败') },
|
||||
|
||||
Reference in New Issue
Block a user