From 18732f2761a0f5d1d2b9a455fe7c69f0b11ae27d Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期五, 16 十月 2026 18:07:17 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_新疆_塔河种业' into dev_新疆_塔河种业

---
 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