云
6 小时以前 fb4db4ae9b6a7f113ba049f6789e6916c0dcc276
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
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 { getBatchPage } from '#/api/mes/pro/batch';
import { can } from '#/mobile/access';
import { lookup, errorText, writeBlockReason, type TraceResult, type TraceObject } from '#/mobile/api';
import Scanner from './scanner.vue';
defineOptions({name:'MobileQuery'});
const route=useRoute(),key=route.path.split('/')[2]||'bags';
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 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'));
let appliedFilter='';
async function load(reset=false){
  if(busy.value)return;busy.value=true;error.value='';
  const nextPage=reset?1:page.value+1;
  const search=reset?filter.value.trim():appliedFilter;
  try{
    const params={pageNo:nextPage,pageSize:20,code:search||undefined};
    const r=await (key==='bags'?getBagCodePage(params):key==='pallets'?getPalletPage(params):getBatchPage(params));
    rows.value=reset?r.list:[...rows.value,...r.list];total.value=r.total;page.value=nextPage;
    appliedFilter=search;
  }catch(e){error.value=errorText(e);}finally{busy.value=false;}
}
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='';
  try{
    const r=await lookup(code);
    if(binding.value&&writeBlockReason(r))throw new Error(writeBlockReason(r));
    if(!binding.value){
      if(r.type!==expected)throw new Error('该码类型与当前查询页面不一致,请使用对应查询入口');
      detail.value=r;
    }else if(!target.value){
      if(r.type!==(key==='bags'?'pallet':'batch'))throw new Error(key==='bags'?'请先扫描目标托盘':'请先扫描目标批次');
      target.value=r;
    }else{
      if(r.type!==expected)throw new Error('请扫描'+(key==='bags'?'袋码':'托盘码'));
      if(key==='bags'){
        if(r.data.palletId)throw new Error('该袋码已绑定托盘,请核实后操作');
        if(r.data.batchId&&target.value.data.batchId&&r.data.batchId!==target.value.data.batchId)throw new Error('袋码与目标托盘批次不一致');
        if(bags.value.some(b=>b.data.id===r.data.id))throw new Error('本次已经采集该袋码');
        bags.value.push(r);
      }else{
        if(r.data.batchId)throw new Error('该托盘已绑定批次');
        detail.value=r;
      }
    }
  }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 clearPending(){pending.value=undefined;sessionStorage.removeItem(storageKey);}
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]:[];
  if(!ids.length)return;
  const operation={kind:key,ids,targetId:target.value.data.id};
  pending.value=operation;sessionStorage.setItem(storageKey,JSON.stringify(operation));busy.value=true;error.value='';
  try{
    if(key==='bags')await bindBagCodePallet({ids,palletId:operation.targetId});
    else await bindPalletBatch({ids,batchId:operation.targetId});
    clearPending();binding.value=false;bags.value=[];detail.value=undefined;notice.value='绑定成功,已更新对象的绑定关系。';
  }catch(e){error.value='绑定结果待确认:'+errorText(e)+'。请先核实,勿重复提交。';}
  finally{busy.value=false;}
}
async function verifyBind(){
  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;}
    else if(none){notice.value='已核实:对象仍未绑定。请重新查询对象后采集提交。';clearPending();binding.value=false;bags.value=[];detail.value=undefined;}
    else error.value='对象绑定结果不一致,请联系负责人核对。当前保留待确认操作。';
  }catch(e){error.value=errorText(e);}finally{busy.value=false;}
}
onBeforeRouteLeave(()=>!busy.value&&(!binding.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>
  <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"/>
    <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>
      <p v-else class="m-status">{{detail.data.inboundTime ? '已入库' : '未入库'}}<span v-if="detail.type === 'pallet'"> · 待入库 {{detail.data.pendingBagQuantity ?? '—'}} 袋</span></p>
      <p v-if="writeBlockReason(detail)" class="m-warning">{{writeBlockReason(detail)}}</p>
      <dl><div class="m-row"><dt>码值</dt><dd>{{detail.code}}</dd></div><div class="m-row"><dt>批次</dt><dd>{{detail.data.batchCode||detail.data.tempCode||'未绑定'}}</dd></div><div class="m-row"><dt>托盘</dt><dd>{{detail.data.palletCode||'—'}}</dd></div><div class="m-row"><dt>生产日期</dt><dd>{{detail.data.produceDate||'未录入'}}</dd></div><div class="m-row"><dt>产线</dt><dd>{{detail.data.lineName||'未录入'}}</dd></div></dl>
      <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">
      <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?'正在绑定…':'确认绑定'}}</button><button class="m-secondary" :disabled="busy" @click="binding=false;bags=[];target=undefined;detail=undefined">取消本次绑定</button></div>
    </template>
    <template v-else>
      <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>
      <p v-if="!rows.length&&!busy" class="m-empty">暂无匹配对象</p>
      <button v-for="row in rows" :key="row.id" class="m-feature" style="width:100%;background:transparent;border-top:0;border-left:0;border-right:0;text-align:left" :disabled="busy" @click="identify(row.code||row.tempCode||'')"><div><strong>{{row.itemName||row.code||row.tempCode}}</strong><small>{{row.code||row.tempCode}} · {{row.batchCode||'点击查看详情'}}</small></div></button>
      <button v-if="rows.length<total" class="m-secondary" :disabled="busy" @click="load()">{{busy?'正在加载…':'加载更多'}}</button>
    </template>
  </template>
</template>