From 2f9c84cf0e0c03a8e3bf07d3924ccdf791070b77 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 16 十月 2026 17:45:21 +0800
Subject: [PATCH] feat(mobile): 添加移动端自动托盘生成功能和追溯定位功能
---
src/views/mobile/query.vue | 82 +++++++++++++++++++++++++++++++++-------
1 files changed, 67 insertions(+), 15 deletions(-)
diff --git a/src/views/mobile/query.vue b/src/views/mobile/query.vue
index 207fee3..e6a831e 100644
--- a/src/views/mobile/query.vue
+++ b/src/views/mobile/query.vue
@@ -3,7 +3,7 @@
import { onBeforeRouteLeave, useRoute } from 'vue-router';
import { useUserStore } from '@vben/stores';
import { bindBagCodePallet, getBagCode, getBagCodePage } from '#/api/mes/pro/bagCode';
-import { bindPalletBatch, getPallet, getPalletPage } from '#/api/mes/pro/pallet';
+import { batchCreatePallet, bindPalletBatch, getPallet, getPalletPage, preGeneratePallet } from '#/api/mes/pro/pallet';
import { getBatchPage } from '#/api/mes/pro/batch';
import { can } from '#/mobile/access';
import { lookup, errorText, writeBlockReason, type TraceResult, type TraceObject } from '#/mobile/api';
@@ -13,10 +13,12 @@
const expected={bags:'bag',pallets:'pallet',batches:'batch'}[key];
const busy=ref(false),error=ref(''),notice=ref(''),detail=ref<TraceResult>(),rows=ref<TraceObject[]>([]),page=ref(1),total=ref(0),filter=ref('');
const binding=ref(false),target=ref<TraceResult>(),bags=ref<TraceResult[]>([]);
-type PendingBind={kind:string;ids:number[];targetId:number};
+const autoPacking=ref(false),palletSize=ref<48|100>(48),thresholdPrompt=ref(false),thresholdPassed=ref(false);
+type PendingBind={kind:string;ids:number[];targetId:number;stage?:'creating'|'binding';palletCode?:string};
const pending=ref<PendingBind>();
const storageKey='mom-mobile-bind-'+useUserStore().userInfo?.id+'-'+key;
const canBind=computed(()=>key==='bags'?can('mes:pro-bag-code:update')&&can('mes:pro-pallet:query'):key==='pallets'&&can('mes:pro-pallet:update')&&can('mes:pro-batch:query'));
+const canAutoPack=computed(()=>key==='bags'&&can('mes:pro-bag-code:update')&&can('mes:pro-pallet:create')&&can('mes:pro-pallet:query'));
let appliedFilter='';
async function load(reset=false){
if(busy.value)return;busy.value=true;error.value='';
@@ -31,10 +33,22 @@
}
onMounted(()=>{try{pending.value=JSON.parse(sessionStorage.getItem(storageKey)||'null')||undefined;}catch{sessionStorage.removeItem(storageKey);}load(true);});
async function identify(code:string){
- if(busy.value||pending.value)return;busy.value=true;error.value='';notice.value='';
+ if(busy.value||pending.value||thresholdPrompt.value)return;busy.value=true;error.value='';notice.value='';
try{
const r=await lookup(code);
- if(binding.value&&writeBlockReason(r))throw new Error(writeBlockReason(r));
+ if((binding.value||autoPacking.value)&&writeBlockReason(r))throw new Error(writeBlockReason(r));
+ if(autoPacking.value){
+ if(r.type!=='bag')throw new Error('璇锋壂鎻忚鐮�');
+ if(r.data.palletId)throw new Error('璇ヨ鐮佸凡缁戝畾鎵樼洏锛岃鏍稿疄鍚庢搷浣�');
+ if(!r.data.itemId)throw new Error('璇ヨ鐮佺己灏戝搧绉嶏紝鏃犳硶缁戝畾鎵樼洏');
+ if(bags.value.some(b=>b.data.id===r.data.id))throw new Error('鏈宸茬粡閲囬泦璇ヨ鐮�');
+ const first=bags.value[0];
+ if(first&&first.data.itemId!==r.data.itemId)throw new Error('琚嬬爜鍝佺涓庢湰娆℃墭鐩樹笉涓�鑷�');
+ if(first&&first.data.batchId!==r.data.batchId)throw new Error('琚嬬爜鎵�灞炴壒娆′笌鏈鎵樼洏涓嶄竴鑷�');
+ bags.value.push(r);
+ if(bags.value.length===palletSize.value&&!thresholdPassed.value)thresholdPrompt.value=true;
+ return;
+ }
if(!binding.value){
if(r.type!==expected)throw new Error('璇ョ爜绫诲瀷涓庡綋鍓嶆煡璇㈤〉闈笉涓�鑷达紝璇蜂娇鐢ㄥ搴旀煡璇㈠叆鍙�');
detail.value=r;
@@ -56,7 +70,34 @@
}catch(e){error.value=errorText(e);}finally{busy.value=false;}
}
function startBind(){binding.value=true;detail.value=undefined;target.value=undefined;bags.value=[];notice.value='';error.value='';}
+function startAutoPack(){autoPacking.value=true;binding.value=false;detail.value=undefined;target.value=undefined;bags.value=[];thresholdPrompt.value=false;thresholdPassed.value=false;notice.value='';error.value='';}
+function stopAutoPack(){autoPacking.value=false;bags.value=[];thresholdPrompt.value=false;thresholdPassed.value=false;}
+function removeBag(index:number){bags.value.splice(index,1);if(bags.value.length<palletSize.value){thresholdPrompt.value=false;thresholdPassed.value=false;}}
function clearPending(){pending.value=undefined;sessionStorage.removeItem(storageKey);}
+async function createAndBindPallet(){
+ if(busy.value||pending.value||bags.value.length<palletSize.value)return;
+ const ids=bags.value.map(b=>b.data.id!);
+ const operation:PendingBind={kind:'auto-bags',ids,targetId:0,stage:'creating'};
+ pending.value=operation;sessionStorage.setItem(storageKey,JSON.stringify(operation));busy.value=true;error.value='';thresholdPrompt.value=false;
+ try{
+ const batchId=bags.value[0]?.data.batchId;
+ if(batchId){
+ const [id]=await batchCreatePallet({batchId,count:1,capacity:palletSize.value});
+ if(!id)throw new Error('鏈彇寰楁柊鎵樼洏缂栧彿');
+ operation.targetId=id;operation.stage='binding';sessionStorage.setItem(storageKey,JSON.stringify(operation));
+ const created=await getPallet(id);
+ operation.palletCode=created.code||created.tempCode||String(id);
+ }else{
+ const [created]=await preGeneratePallet({count:1,capacity:palletSize.value});
+ if(!created?.id)throw new Error('鏈彇寰楁柊鎵樼洏缂栧彿');
+ operation.targetId=created.id;operation.palletCode=created.tempCode;operation.stage='binding';
+ }
+ sessionStorage.setItem(storageKey,JSON.stringify(operation));
+ await bindBagCodePallet({ids,palletId:operation.targetId});
+ clearPending();stopAutoPack();notice.value=`宸茬敓鎴愬苟缁戝畾鎵樼洏鐮� ${operation.palletCode}锛屽叡 ${ids.length} 琚嬨�俙;
+ }catch(e){error.value=operation.targetId?`鎵樼洏鐮� ${operation.palletCode} 鐨勭粦瀹氱粨鏋滃緟纭锛�${errorText(e)}銆傝鍏堟牳瀹烇紝鍕块噸澶嶇敓鎴愩�俙:`鐢熸垚缁撴灉寰呯‘璁わ細${errorText(e)}銆傝鍦ㄦ墭鐩樺垪琛ㄦ牳瀹烇紝鍕块噸澶嶇敓鎴愩�俙;}
+ finally{busy.value=false;}
+}
async function submitBind(){
if(busy.value||!target.value?.data.id)return;
const ids=key==='bags'?bags.value.map(b=>b.data.id!):detail.value?.data.id?[detail.value.data.id]:[];
@@ -74,26 +115,35 @@
if(!pending.value||busy.value)return;busy.value=true;error.value='';
try{
const p=pending.value;
- const states=await Promise.all(p.ids.map(id=>p.kind==='bags'?getBagCode(id):getPallet(id)));
- const all=states.every(s=>p.kind==='bags'?('palletId'in s&&s.palletId===p.targetId):s.batchId===p.targetId);
- const none=states.every(s=>p.kind==='bags'?(!('palletId'in s)||!s.palletId):!s.batchId);
- if(all){notice.value='宸叉牳瀹烇細鎵�鏈夊璞$粦瀹氭垚鍔熴��';clearPending();binding.value=false;bags.value=[];detail.value=undefined;}
+ if(p.kind==='auto-bags'&&!p.targetId){error.value='灏氭湭鍙栧緱鎵樼洏缂栧彿锛岃鍦ㄦ墭鐩樺垪琛ㄦ牳瀹炵敓鎴愮粨鏋滃悗鑱旂郴璐熻矗浜哄鐞嗐��';return;}
+ const states=await Promise.all(p.ids.map(id=>p.kind==='bags'||p.kind==='auto-bags'?getBagCode(id):getPallet(id)));
+ const bagOperation=p.kind==='bags'||p.kind==='auto-bags';
+ const all=states.every(s=>bagOperation?('palletId'in s&&s.palletId===p.targetId):s.batchId===p.targetId);
+ const none=states.every(s=>bagOperation?(!('palletId'in s)||!s.palletId):!s.batchId);
+ if(all){notice.value=p.kind==='auto-bags'?`宸叉牳瀹烇細鎵樼洏鐮� ${p.palletCode} 宸茬粦瀹� ${p.ids.length} 琚嬨�俙:'宸叉牳瀹烇細鎵�鏈夊璞$粦瀹氭垚鍔熴��';clearPending();binding.value=false;stopAutoPack();detail.value=undefined;}
+ else if(none&&p.kind==='auto-bags'){await bindBagCodePallet({ids:p.ids,palletId:p.targetId});notice.value=`宸插畬鎴愭墭鐩樼爜 ${p.palletCode} 鐨勮鐮佺粦瀹氥�俙;clearPending();stopAutoPack();}
else if(none){notice.value='宸叉牳瀹烇細瀵硅薄浠嶆湭缁戝畾銆傝閲嶆柊鏌ヨ瀵硅薄鍚庨噰闆嗘彁浜ゃ��';clearPending();binding.value=false;bags.value=[];detail.value=undefined;}
else error.value='瀵硅薄缁戝畾缁撴灉涓嶄竴鑷达紝璇疯仈绯昏礋璐d汉鏍稿銆傚綋鍓嶄繚鐣欏緟纭鎿嶄綔銆�';
}catch(e){error.value=errorText(e);}finally{busy.value=false;}
}
-onBeforeRouteLeave(()=>!busy.value&&(!binding.value||!!pending.value||window.confirm('绂诲紑灏嗘斁寮冩湰娆℃湭鎻愪氦鐨勭粦瀹氭竻鍗曪紝鏄惁缁х画锛�')));
+onBeforeRouteLeave(()=>!busy.value&&(!(binding.value||autoPacking.value)||!!pending.value||window.confirm('绂诲紑灏嗘斁寮冩湰娆℃湭鎻愪氦鐨勭粦瀹氭竻鍗曪紝鏄惁缁х画锛�')));
</script>
<template>
<p v-if="notice" class="m-success" role="status">{{notice}}</p>
<p v-if="error" class="m-error" role="alert">{{error}}</p>
- <div v-if="pending" class="m-panel"><h2 class="m-title">缁戝畾缁撴灉寰呯‘璁�</h2><p>鏈 {{pending.ids.length}} 涓璞★紝鐩爣缂栧彿 {{pending.targetId}}銆�</p><button class="m-primary" :disabled="busy" @click="verifyBind">鏍稿疄缁戝畾缁撴灉</button></div>
+ <div v-if="pending" class="m-panel"><h2 class="m-title">{{pending.stage==='creating'?'鐢熸垚缁撴灉寰呯‘璁�':'缁戝畾缁撴灉寰呯‘璁�'}}</h2><p>鏈 {{pending.ids.length}} 涓璞�<span v-if="pending.targetId">锛屾墭鐩樼爜 {{pending.palletCode||pending.targetId}}</span>銆�</p><button class="m-primary" :disabled="busy||!pending.targetId" @click="verifyBind">鏍稿疄缁戝畾缁撴灉</button><p v-if="!pending.targetId" class="m-warning">鐢熸垚璇锋眰鐨勭粨鏋滃皻涓嶆槑纭紝璇峰湪鎵樼洏鍒楄〃鏍稿疄骞惰仈绯昏礋璐d汉澶勭悊銆�</p></div>
<template v-else>
<template v-if="binding">
<h2 class="m-title">{{!target?(key==='bags'?'鍏堟壂鎻忕洰鏍囨墭鐩�':'鍏堟壂鎻忕洰鏍囨壒娆�'):(key==='bags'?'閫愯閲囬泦':'鎵弿寰呯粦瀹氭墭鐩�')}}</h2>
<div v-if="target" class="m-panel"><p>鐩爣锛歿{target.code}}</p><p class="m-muted">{{target.data.itemName||'鏈綍鍏ュ搧绉�'}} 路 {{target.data.batchCode||'鏈粦瀹氭壒娆�'}}</p></div>
</template>
- <Scanner :disabled="busy" @code="identify"/>
+ <template v-if="autoPacking"><h2 class="m-title">鎵弿琚嬬爜鐢熸垚鎵樼洏</h2><p class="m-muted">{{palletSize}} 琚嬪瀷 路 宸查噰闆� {{bags.length}} 琚�<span v-if="bags.length<palletSize">锛岃繕闇� {{palletSize-bags.length}} 琚�</span>銆傜敓鎴愬悗灏嗚嚜鍔ㄧ粦瀹氭湰娆¢噰闆嗙殑琚嬬爜銆�</p></template>
+ <Scanner :disabled="busy||thresholdPrompt" :continuous="autoPacking" @code="identify"/>
+ <section v-if="autoPacking&&thresholdPrompt" class="m-panel" role="alert" aria-labelledby="pallet-threshold-title" aria-describedby="pallet-threshold-description">
+ <h2 id="pallet-threshold-title" class="m-title">宸叉壂鎻� {{palletSize}} 琚�</h2>
+ <p id="pallet-threshold-description">鐜板湪鐢熸垚骞剁粦瀹氭墭鐩樼爜锛屾垨缁х画鎵弿鏇村琚嬬爜鍔犲叆鏈墭鐩樸��</p>
+ <div class="m-action-bar"><button class="m-primary" :disabled="busy" @click="createAndBindPallet">鐢熸垚骞剁粦瀹氭墭鐩樼爜</button><button class="m-secondary" :disabled="busy" @click="thresholdPrompt=false;thresholdPassed=true">缁х画鎵爜缁戝畾</button></div>
+ </section>
<div v-if="detail&&!binding" class="m-panel">
<h2 class="m-title">{{detail.data.itemName||'瀵硅薄璇︽儏'}}</h2>
<p v-if="detail.type === 'batch'" class="m-status">{{detail.data.status === 30 ? '宸插畬鎴�' : detail.data.status === 20 ? '鐢熶骇涓�' : detail.data.status === 40 ? '宸蹭綔搴�' : '鑽夌'}} 路 宸插叆搴� {{detail.data.inboundBagQuantity || 0}} 琚�</p>
@@ -103,13 +153,15 @@
<dl v-if="detail.type==='bag'"><div class="m-row"><dt>杩芥函鐮�</dt><dd>{{detail.data.code}}</dd></div><div class="m-row"><dt>涓氬姟鐮�</dt><dd>{{detail.data.businessCode||'鏈敓鎴�'}}</dd></div></dl>
<dl v-else><div class="m-row"><dt>涓氬姟鐮�</dt><dd>{{detail.data.businessCode||'鏈敓鎴�'}}</dd></div></dl>
</div>
- <template v-if="binding">
+ <template v-if="binding||autoPacking">
<p v-if="key==='bags'" class="m-muted">宸查噰闆� {{bags.length}} 琚嬶紝灏氭湭鎻愪氦缁戝畾</p>
- <article v-for="(bag,i) in bags" :key="bag.data.id" class="m-record"><p>{{bag.code}}</p><button class="m-detail-toggle" :disabled="busy" @click="bags.splice(i,1)">绉婚櫎</button></article>
- <p v-if="key==='pallets'&&detail">寰呯粦瀹氭墭鐩橈細{{detail.code}}</p>
- <div class="m-action-bar"><button class="m-primary" :disabled="busy||!target||(key==='bags'?!bags.length:!detail)" @click="submitBind">{{busy?'姝e湪缁戝畾鈥�':'纭缁戝畾'}}</button><button class="m-secondary" :disabled="busy" @click="binding=false;bags=[];target=undefined;detail=undefined">鍙栨秷鏈缁戝畾</button></div>
+ <article v-for="(bag,i) in bags" :key="bag.data.id" class="m-record"><p>{{bag.code}}</p><button class="m-detail-toggle" :disabled="busy" @click="removeBag(i)">绉婚櫎</button></article>
+ <p v-if="binding&&key==='pallets'&&detail">寰呯粦瀹氭墭鐩橈細{{detail.code}}</p>
+ <div v-if="autoPacking&&!thresholdPrompt" class="m-action-bar"><button class="m-primary" :disabled="busy||bags.length<palletSize" @click="createAndBindPallet">{{busy?'姝e湪鐢熸垚鈥�':'鐢熸垚骞剁粦瀹氭墭鐩樼爜'}}</button><button class="m-secondary" :disabled="busy" @click="stopAutoPack">鍙栨秷鏈鎵爜</button></div>
+ <div v-else-if="binding" class="m-action-bar"><button class="m-primary" :disabled="busy||!target||(key==='bags'?!bags.length:!detail)" @click="submitBind">{{busy?'姝e湪缁戝畾鈥�':'纭缁戝畾'}}</button><button class="m-secondary" :disabled="busy" @click="binding=false;bags=[];target=undefined;detail=undefined">鍙栨秷鏈缁戝畾</button></div>
</template>
<template v-else>
+ <div v-if="canAutoPack" class="m-panel"><h2 class="m-title">鎵弿琚嬬爜鑷姩鐢熸垚鎵樼洏</h2><label class="m-field"><span>鎵樼洏鐮佺被鍨�</span><select v-model.number="palletSize" :disabled="busy"><option :value="48">48 琚�</option><option :value="100">100 琚�</option></select></label><button class="m-primary" :disabled="busy" @click="startAutoPack">寮�濮嬫壂鐮佺敓鎴愭墭鐩�</button></div>
<button v-if="canBind" class="m-primary" :disabled="busy" @click="startBind">{{key==='bags'?'寮�濮嬭鐮佺粦瀹氭墭鐩�':'寮�濮嬫墭鐩樼粦瀹氭壒娆�'}}</button>
<h2 class="m-section-title">瀵硅薄鍒楄〃</h2>
<form class="m-stack" @submit.prevent="load(true)"><label class="m-field"><span>鎸夌爜鍊兼煡璇�</span><input v-model="filter" placeholder="杈撳叆鐮佸��" /></label><button class="m-secondary" :disabled="busy">鏌ヨ鍒楄〃</button></form>
--
Gitblit v1.9.3