Kaynağa Gözat

Merge branch '250711_check'

# Conflicts:
#	src/views/index.vue
zhaohuanhuan 8 ay önce
ebeveyn
işleme
2430307a2a

+ 54 - 1
src/api/check/index.ts

@@ -1,7 +1,7 @@
 // @ts-ignore
 import request from '@/utils/request'
 // @ts-ignore
-import { getInventoryListType, getInventoryType, movementReturnType } from '@/types/check'
+import { getInventoryListType, getInventoryType, getPendingReviewTaskType, movementReturnType, packingReviewType, reversePickingType} from '@/types/check'
 
 /**
  * 查询库存批次,并分组汇总
@@ -39,3 +39,56 @@ export function movementReturn(data:movementReturnType) {
   })
 }
 
+
+
+/**
+ * 获取待复核任务
+ * @param data
+ */
+export function getPendingReviewTask(data:getPendingReviewTaskType) {
+  return request({
+    url: '/api/wms/outbound/review/getPendingReviewTask',
+    method: 'post',
+    data:JSON.stringify(data),
+  })
+}
+
+
+
+/**
+ * 装箱复核
+ * @param data
+ */
+export function packingReview(data:packingReviewType) {
+  return request({
+    url: '/api/wms/outbound/review/packingReview',
+    method: 'post',
+    data:JSON.stringify(data),
+  })
+}
+
+/**
+ *  重置装箱复核
+ */
+export function resetCheckPacking(data:any) {
+  return request({
+    url: '/api/wms/outbound/review/resetCheckPacking',
+    method: 'post',
+    data
+  })
+}
+
+/**
+ * 返拣
+ * @param data
+ */
+export function reversePicking(data:reversePickingType) {
+  return request({
+    url: '/api/wms/outbound/review/reversePicking',
+    method: 'post',
+    data:JSON.stringify(data),
+  })
+}
+
+
+

+ 4 - 4
src/api/picking/index.ts

@@ -97,14 +97,14 @@ export function setPickingDetail(data: any[]) {
 }
 
 /**
- * 打印面单
+ * 打印
  * @param data
  */
-export function printExpressNo(params: any[]) {
+export function fluxPrint(data: any) {
   return request({
-    url: 'api/wms/order/express-print',
+    url: 'api/wms/fluxPrint/print',
     method: 'post',
-    params
+    data
   })
 }
 

+ 12 - 1
src/router/index.ts

@@ -79,7 +79,18 @@ const routes: RouteRecordRaw[] = [
     meta:{title:'海康-入库'},
     component: () => import('@/views/haikang/boxReturn/boxReturn/index.vue')
   },
-
+  {
+    path: '/check-activity',
+    name: 'CheckActivity',
+    meta:{title:'复核-活动单'},
+    component: () => import('@/views/outbound/check/activity/index.vue')
+  },
+  {
+    path: '/check-large',
+    name: 'CheckLarge',
+    meta:{title:'复核-大件单'},
+    component: () => import('@/views/outbound/check/large/index.vue')
+  },
 ];
 
 // 创建路由实例

+ 99 - 0
src/types/check.ts

@@ -41,3 +41,102 @@ export interface movementReturnType {
   fmLocation:string;
   toLocation:string;
 }
+/**
+ * 获取待复核任务
+ */
+export interface getPendingReviewTaskType {
+  /**
+   * 是否活动单
+   */
+  activityOrderFlag?: boolean;
+  /**
+   * 单号(订单号/快递单号/波次号/容器号)
+   */
+  code: string;
+  /**
+   * 仓库编号
+   */
+  warehouse: string;
+  [property: string]: any;
+}
+
+/**
+ * 装箱复核
+ */
+export interface packingReviewType {
+  /**
+   * 是否活动单
+   */
+  activityOrderFlag?: boolean;
+  /**
+   * 单号(订单号/快递单号/波次号/容器号)
+   */
+  code: string;
+  /**
+   * 分配明细IDS - 前端传入这个
+   */
+  groupDetailList: PackingReviewDetailsQO[];
+  /**
+   * 装箱复核模式 false 逐次 true 批量
+   */
+  packingReviewMode?: boolean;
+  /**
+   * 总毛重
+   */
+  totalGrossWeight?: number;
+  /**
+   * 仓库编号
+   */
+  warehouse: string;
+  /**
+   * 工作站
+   */
+  workStation: string;
+  [property: string]: any;
+}
+
+/**
+ * 装箱复核商品明细
+ *
+ * PackingReviewDetailsQO
+ */
+export interface PackingReviewDetailsQO {
+  /**
+   * 批次号
+   */
+  lotNum: string;
+  /**
+   * 待复核数量
+   */
+  qty: number;
+  /**
+   * IMEI码
+   */
+  secondSerialNoList?: string[];
+  /**
+   * 唯一码
+   */
+  serialNoList?: string[];
+  [property: string]: any;
+}
+
+/**
+ * 返拣
+ */
+export interface reversePickingType {
+  /**
+   * 单号(订单号/快递单号/波次号/容器号)
+   */
+  code: string;
+  /**
+   * 反拣容器号
+   */
+  reversePickingContainerNo: string;
+  /**
+   * 仓库编号
+   */
+  warehouse: string;
+  [property: string]: any;
+}
+
+

+ 4 - 1
src/views/index.vue

@@ -10,7 +10,10 @@
     <div class="home" @click="onRouter('take-delivery')">收货</div>
     <div class="home" @click="onRouter('hik-putaway-allocation')">海康上架-调度</div>
     <div class="home" @click="onRouter('hik-putaway')">海康上架</div>
-    <div class="home" @click="onRouter('hik-box-return')">海康-入库/解绑</div>
+    <div class="home" @click="onRouter('hik-box-return')">海康-入库</div>
+    <div class="home" @click="onRouter('check-activity')">复核-活动单</div>
+    <div class="home" @click="onRouter('check-large')">复核-大件单</div>
+
   </div>
 </template>
 <script setup>

+ 562 - 0
src/views/outbound/check/activity/index.vue

@@ -0,0 +1,562 @@
+<template>
+  <div class="container">
+    <van-nav-bar
+      title="复核-活动单" left-arrow fixed placeholder @click-left="goBack">
+      <template #left>
+        <van-icon name="arrow-left" size="25" />
+        <div style="color: #fff">返回</div>
+      </template>
+      <!--      <template #right>-->
+      <!--        <div class="nav-right" @click="onClickRight">提交任务</div>-->
+      <!--      </template>-->
+    </van-nav-bar>
+    <div class="activity">
+      <div class="wave-title">
+        <div><span style="font-size: 12px">波次/容器号:</span>{{ waveNo || '--' }}</div>
+        <div class="wave-tips">
+          <van-notice-bar :background="'none'" :speed="50" :text="tips" />
+        </div>
+        <van-button plain size="mini" type="primary" @click="_setWaveNo()">更换</van-button>
+      </div>
+      <div class="scan-barcode">
+        <van-field v-model.lazy="scanBarcode"  label-align="left" placeholder="请扫描商品条码/SKU" label="商品条码:"
+                   class="input-barcode" autocomplete="off" @keydown.enter="_handlerScan(scanBarcode)"  />
+        <van-field  v-model.lazy="totalWeight" ref="weightRef" label-align="left" required placeholder="请输入商品重量kg" label="重&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;量:"
+                    autocomplete="off" type="number"  >
+          <template #button>
+            <div>KG</div>
+          </template>
+        </van-field>
+<!--        :class="[totalWeight>0?'success-input-barcode':'error-input-barcode']"-->
+      </div>
+      <div class="order-detail">
+        <div class="picking-no">
+          <div class="picking-code"><van-icon name="stop-circle-o" /><span style="padding-left: 5px">{{ orderDetail.pickingDetailCode }}</span></div>
+          <div>{{ orderDetail.carrierName || '--' }}</div>
+        </div>
+        <div class="picking-container ">
+          <div class="container-item"><span style="color:#666">拣货容器号:</span><div style="flex:1"> <van-notice-bar :background="'none'" color="#000" :speed="50" :text="orderDetail.pickingDetailContainer" /></div></div>
+          <div class="container-item"><span style="color:#666">返拣容器:</span>{{ reversePickingContainerNo || '--' }}</div>
+          <div class="picking-order-count ">
+            <div>待复核单:
+              <span style="color: #333;font-weight: bold;font-size: 18px" v-if="orderMap.dataGroup">{{Object.keys(orderMap.dataGroup).length ||0}}</span>
+              <span v-else>0</span>
+            </div>
+            <div>取消单:
+              <span style="color: #333;font-weight: bold;font-size: 18px" v-if="orderMap.cancelGroup">{{Object.keys(orderMap.cancelGroup).length ||0}}</span>
+              <span v-else>0</span>
+            </div>
+            <div>冻结单:
+              <span style="color: #333;font-weight: bold;font-size: 18px" v-if="orderMap.freezeGroup">{{Object.keys(orderMap.freezeGroup).length ||0}}</span>
+              <span  v-else>0</span>
+            </div>
+          </div>
+        </div>
+        <div class="picking-button">
+          <div class="picking-button-item" @click="print('packing')">打印装箱清单</div>
+          <div class="picking-button-item" @click="print('express')">打印面单</div>
+          <div class="picking-button-item" @click="endCheck()" >结束复核</div>
+          <van-popover :actions="actions" placement="bottom-end" @select="onSelect" theme="dark">
+            <template #reference>
+              <div class="picking-button-item" style="padding: 0 5px">更多操作<van-icon name="play" /></div>
+            </template>
+          </van-popover>
+        </div>
+      </div>
+     <div class="order-list-box">
+       <van-divider style="margin: 0;padding: 5px 15px">订单明细</van-divider>
+       <div class="order-list">
+         <table class="task-table">
+           <thead>
+           <tr>
+             <th style="width: 40%">商品条码</th>
+             <th>数量</th>
+             <th>剩余</th>
+             <th v-if="isUniqueCode">标记</th>
+           </tr>
+           </thead>
+           <tbody>
+           <tr v-for="(item, index) in orderList" :key="index" v-if="orderList.length>0">
+             <td>{{ item.barcode }}</td>
+             <td>{{item.qty}}/{{item.qtyOrdered}}</td>
+             <td>{{ item.qty }}</td>
+             <td v-if="isUniqueCode">
+               <van-tag v-if="item.uniqueRegExp" type="warning" >唯一码</van-tag>
+               <van-tag v-if="item.imeiRegExp" type="primary" >IMEI码</van-tag>
+             </td>
+           </tr>
+           <tr v-else>
+             <td colspan="3">
+               <div>暂无数据</div>
+             </td>
+           </tr>
+           </tbody>
+         </table>
+       </div>
+     </div>
+    </div>
+    <!-- 条码输入组件 -->
+    <input-barcode :back="back" @setBarcode="setBarcode" ref="inputBarcodeRef" />
+    <!--    打印面单-->
+    <printer ref="printerRef" @onPrint="onPrint"  />
+    <!--    订单列表-->
+    <order-list-table ref="orderListRef" />
+    <!--    返拣-->
+    <reverse-picking ref="reversePickingRef" :warehouse="warehouse" :reversePickingContainerNo="reversePickingContainerNo" @load-data="loadData" />
+    <!--    耗材-->
+    <related-materia ref="relatedMateriaRef" @cut-barcode="cutBarcode" />
+
+  </div>
+</template>
+<script setup>
+import { onMounted, onUnmounted, ref } from 'vue'
+import { androidFocus, getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
+import { useStore } from '@/store/modules/user'
+import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
+import { getPendingReviewTask, packingReview } from '@/api/check/index'
+import { barcodeToUpperCase } from '@/utils/dataType.js'
+import { showConfirmDialog, showNotify } from 'vant'
+import { closeLoading, showLoading } from '@/utils/loading'
+import { fluxPrint } from '@/api/picking/index'
+import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
+import Printer from '@/components/Printer.vue'
+import orderListTable from '@/views/outbound/check/components/OrderListTable.vue'
+import ReversePicking from '@/views/outbound/check/components/ReversePicking.vue'
+import RelatedMateria from '@/views/outbound/check/components/RelatedMateria.vue'
+const store = useStore()
+try {
+  getHeader()
+  androidFocus()
+} catch (error) {
+}
+// 页面初始化
+onMounted(() => {
+  openListener()
+  scanInit(_handlerScan)
+  loadData()
+})
+const warehouse = store.warehouse
+//收货容器号
+const waveNo = ref('')
+const  isUniqueCode=ref(false)
+// 错误提示
+const tips = ref('')
+//强制返回
+const back = ref(true)
+const scanBarcode=ref('')
+const totalWeight=ref('')
+const containerNoMap={
+  'WH01':'FJ-WH01-20',
+  'WH02':'FJ-WH02-20',
+  'WH10':'FJ-WH10-01',
+  'WH99':'FJ-WH99-01',
+}
+//返拣容器
+const reversePickingContainerNo=containerNoMap[warehouse]
+// 订单明细
+const orderDetail=ref({})
+//订单详情
+const orderMap=ref({})
+const orderList=ref([])
+//匹配条码
+const matchBarcodeList=ref([])
+const actions = [
+  { text: '重新开始',value:'reset' },
+  { text: '打印波次商品',value:'goods' },
+  // { text: '打印装箱清单',value:'packing' },
+  { text: '取消单列表',value:'cancel' },
+  { text: '冻结单列表',value: 'freeze' },
+];
+// 扫描条码监听
+const relatedMateriaRef=ref(null)
+const _handlerScan = (code) => {
+  if (code) {
+    const barcode = [...new Set(
+      orderList.value
+        .flatMap(item => [item.barcode, item.barcode2, item.sku])
+        .filter(value => value !== null && value !== '' && value !== undefined)
+    )];
+    const checkBarcode = barcodeToUpperCase(code);
+    if (barcode.some(item => barcodeToUpperCase(item) === checkBarcode)) {
+      matchBarcodeList.value=orderList.value.filter(item=>((item.barcode===checkBarcode || item.sku===checkBarcode || item.barcode2===checkBarcode) && item.qty>0) )
+      if(matchBarcodeList.value.length>0){
+        const itemActive = matchBarcodeList.value[0]
+        if(itemActive.uniqueRegExp){
+          scanBarcode.value=''
+          showNotify({ type: 'warning', duration: 3000, message: `此活动单包含唯一码,请到PC复核`})
+          return
+        }
+        if(itemActive.imeiRegExp){
+          scanBarcode.value=''
+          showNotify({ type: 'warning', duration: 3000, message: `此活动单包含IMEI码,请到PC复核`})
+          return
+        }
+        scanBarcode.value=code
+        if(itemActive.relatedMaterial && itemActive.relatedMaterial.length>0){
+          relatedMateriaRef.value.show(itemActive)
+          return
+        }
+       cutBarcode(itemActive,1)
+     }else {
+        scanBarcode.value=''
+       scanError()
+       showNotify({ type: 'warning', duration: 3000, message: `商品条码${code},已全部扫描完成`})
+     }
+    }else {
+      scanBarcode.value=''
+      showNotify({ type: 'warning', duration: 3000, message: `商品条码${code},不匹配请重新扫描!`})
+      scanError()
+    }
+  }
+}
+//扣除商品数量
+const weightRef=ref(null)
+const cutBarcode = (itemActive, count) => {
+  if (itemActive.qty > 0) {
+    itemActive.qty -= count;
+    itemActive.qty = Math.max(itemActive.qty, 0)
+    itemActive.quantity+=count
+  }
+  // 计算所有商品的总数量
+  const allCount = orderList.value.reduce((sum, item) => sum + Math.max(Number(item.qty), 0), 0);
+  // 根据所有商品总数量判断是否继续扫描
+  if (allCount > 0) {
+    scanBarcode.value = '';
+    tips.value = '请继续扫描条码';
+    showNotify({ type: 'success', duration: 3000, message: '数量已扣除,请继续扫描条码' });
+  } else {
+    // 如果所有商品已扫描完毕,根据总重量判断提示信息
+    if (!totalWeight.value) {
+      tips.value = '请输入重量';
+      weightRef.value?.focus()
+    } else {
+      tips.value = '商品扫描完成,请点击结束复核';
+    }
+  }
+  scanSuccess();
+};
+//重新开始
+const reset=()=>{
+  showConfirmDialog({
+    title: '温馨提示',
+    message: '您正在进行重新开始操作,是否继续?',
+  }).then(() => {loadData()})
+}
+//打印面单
+const printerRef=ref(null)
+const printType=ref(null)
+const printTypeMap={
+  'express':{
+    templateCode: 'PRINT_WAYBILL'
+  },
+  'packing':{
+    templateCode: 'A3014_PACK_CARTON_PACKINGLIST',
+  },
+  'goods':{
+    templateCode: 'A3012_RF_WIFI_HDQD',
+  }
+}
+const print=(type)=>{
+  printType.value=type
+  printerRef.value?.show(warehouse)
+}
+const onPrint=(code)=>{
+  const printTemplate=printTypeMap[printType.value]
+  const data = {warehouse,code:orderDetail.value.waveNo,printServer: code.server, printName:code.printer,...printTemplate }
+  fluxPrint(data)
+    .then(res => {
+      scanSuccess()
+      showNotify({ type: 'success', duration: 3000, message: '打印已发起,请检查打印情况' });
+    })
+    .catch(err => {
+      scanError()
+      tips.value=err.message || '系统异常,请联系技术支持!'
+    }).finally(() => {
+      closeLoading()
+  })
+}
+//结束复核
+const endCheck=()=>{
+  const allCount = orderList.value.reduce((sum, item) => sum + Math.max(Number(item.qty), 0), 0);
+  if(allCount>0){
+    scanError()
+    tips.value = '商品未扫描完成,请先扫描商品';
+    showNotify({ type: 'warning', duration: 3000, message: '商品未扫描完成,请先扫描商品' });
+    return
+  }
+  if(Object.keys(orderMap.value.dataGroup)===0){
+      scanError()
+      tips.value = '暂无需装箱数据,请刷新数据';
+      showNotify({ type: 'warning', duration: 3000, message: '暂无需装箱数据,请刷新数据' });
+      return
+  }
+  if (!totalWeight.value) {
+    tips.value = '请输入重量';
+    scanError()
+    showNotify({ type: 'warning', duration: 3000, message: '请输入重量' });
+    weightRef.value?.focus()
+    return
+  }
+  const lastNumber=Object.keys(orderMap.value.dataGroup).length
+  packingRequests(0,lastNumber)
+
+}
+//装箱
+const reversePickingRef=ref(null)
+const packingRequests = async (startIndex = 0, lastNumber) => {
+  if(startIndex>=lastNumber){
+    reversePickingRef.value.show(startIndex,0,orderMap.value.cancelGroup)
+    return
+  }
+  try {
+      const getNonEmptyList = (list) => list.length ? list : undefined;
+      const key = Object.keys(orderMap.value.dataGroup)[startIndex];
+      const list = orderMap.value.dataGroup[key] || [];
+      const groupDetailList = list.map(items => {
+        return {
+          lotNum: items.lotNum,
+          qty: items.quantity,
+          serialNoList: getNonEmptyList(items.serialNoList),
+          secondSerialNoList: getNonEmptyList(items.secondSerialNoList),
+        };
+      });
+      const item = list[0] || {};
+      const data = {
+        warehouse: item.warehouseId,
+        workStation: item.warehouseId,
+        code: item.orderNo,
+        totalGrossWeight: totalWeight.value,
+        groupDetailList,
+      };
+      showLoading()
+      const res = await packingReview(data)
+      closeLoading()
+      if (res) {
+        await packingRequests(startIndex + 1, lastNumber)
+      }
+  } catch (error) {
+    tips.value=error.message
+    if(startIndex>0){
+      reversePickingRef.value.show(startIndex,lastNumber-startIndex,orderMap.value.cancelGroup)
+    }
+    scanError()
+  } finally {
+    closeLoading()
+  }
+}
+//更多操作
+const orderListRef=ref(null)
+const onSelect=(item)=>{
+  const handleGroup = (groupKey, emptyMessage) => {
+    const order = orderMap.value[groupKey];
+    if (Object.keys(order).length === 0) {
+      showNotify({ message: emptyMessage, duration: 5000, type: 'danger' });
+      return;
+    }
+    orderListRef.value?.show(item.value,order);
+  };
+  if (item.value == 'cancel') {
+    handleGroup('cancelGroup', '暂无取消单');
+  } else if (item.value == 'freeze') {
+    handleGroup('freezeGroup', '暂无冻结单');
+  }else if(item.value=='reset'){
+    reset()
+  }else if(item.value=='goods'){
+    print('goods')
+  }
+}
+// 设置波次号
+const inputBarcodeRef = ref(null)
+const setBarcode = (code) => {
+  const data = { warehouse, code, activityOrderFlag: true }
+  showLoading()
+  getPendingReviewTask(data).then(res => {
+    if (res.data.details.length == 0) {
+      scanError()
+      inputBarcodeRef.value?.show('', '请扫描波次/容器号', '暂未查询到待复核数据,请切换波次/容器号')
+    } else {
+      if(res.data.waveType!=='*'){
+        scanError()
+        inputBarcodeRef.value?.show('', '请扫描波次/容器号', '仅支持活动单复核')
+        return
+      }
+      scanSuccess()
+      tips.value = '请扫描商品条码'
+      waveNo.value = code
+      orderDetail.value = res.data
+      orderMap.value=getDataList(res.data.details)
+      if(orderMap.value.dataGroup && Object.keys(orderMap.value.dataGroup).length>0){
+        orderList.value=orderMap.value.dataGroup[Object.keys(orderMap.value.dataGroup)[0]]
+        isUniqueCode.value = orderList.value.some(item => item.uniqueRegExp || item.imeiRegExp );
+      }else {
+        orderList.value=[]
+      }
+      matchBarcodeList.value=[]
+      scanBarcode.value=''
+    }
+  }).catch(err => {
+    scanError()
+    inputBarcodeRef.value?.show('', '请扫描波次/容器号', err.message)
+  }).finally(f=>{
+    closeLoading()
+  })
+}
+//格式化订单
+const getDataList = (data) => {
+  const freezeList = [],cancelList = [],dataList = []
+  const groupedData = {
+    freezeGroup: {}, // 冻结订单
+    cancelGroup: {}, //取消订单
+    dataGroup: {} // 普通订单
+  };
+  const groupOrder = (order, group, groupKey, list) => {
+    list.push(order)
+    if (!group[groupKey]) {
+      group[groupKey] = [] // 如果该订单号的组不存在,创建一个新数组
+    }
+    group[groupKey].push(order) // 将订单按订单号分组
+  };
+  data.forEach(curr => {
+    curr.serialNoList = []
+    curr.secondSerialNoList = []
+    curr.quantity=0
+    if (curr.releaseStatus === 'H') {
+      groupOrder(curr, groupedData.freezeGroup, curr.orderNo, freezeList) // 处理冻结订单
+    } else if (curr.erpCancelFlag === 'Y') {
+      groupOrder(curr, groupedData.cancelGroup, curr.orderNo, cancelList) // 处理取消订单
+    } else {
+      groupOrder(curr, groupedData.dataGroup, curr.orderNo, dataList) // 处理普通订单
+    }
+  });
+  return groupedData
+};
+
+
+//切换波次
+const _setWaveNo = () => {
+  back.value = false
+  inputBarcodeRef.value?.show('', '请扫描波次/容器号', '')
+}
+// 数据刷新
+const loadData = () => {
+  if (!waveNo.value) {
+    inputBarcodeRef.value?.show('', '请扫描波次/容器号', '')
+    return
+  } else {
+    setBarcode(waveNo.value)
+  }
+}
+onUnmounted(() => {
+  closeListener()
+})
+
+window.onRefresh = loadData
+</script>
+
+<style scoped lang="sass">
+.container
+  background: #e9f4ff
+  .activity
+    .wave-title
+      display: flex
+      justify-content: space-between
+      padding: 8px 10px
+      .wave-tips
+        color: #ed6a0c
+        flex: 1
+    .scan-barcode
+      ::v-deep(.van-cell)
+        padding: 5px 15px 0 15px
+
+      ::v-deep(.van-field__control)
+        border-bottom: 2px solid #efefef
+        font-size: 16px
+
+      ::v-deep(.van-field__label)
+        width: unset
+        font-size: 16px
+      .input-barcode
+        ::v-deep(.van-field__control)
+          border-bottom: 2px solid #0077ff
+          z-index: 2
+      .success-input-barcode
+        ::v-deep(.van-field__control)
+          border-bottom: 2px solid #1ca600
+          z-index: 2
+      .error-input-barcode
+        ::v-deep(.van-field__control)
+          border-bottom: 2px solid #ff0000
+          z-index: 2
+    .order-detail
+      margin-top: 2px
+      background: #fff
+      font-size: 14px
+      .picking-no
+        display: flex
+        justify-content: space-between
+        margin: 0 15px
+        padding: 8px 0
+        border-bottom: 1px solid #eaeaeb
+        .picking-code
+          color: #419bff
+      .picking-container
+        padding: 0 15px
+        text-align: left
+        border-bottom: 1px solid #eaeaeb
+        .container-item
+          line-height: 30px
+          display: flex
+          align-items: center
+        .picking-order-count
+          display: flex
+          justify-content: space-between
+          line-height: 30px
+      .picking-button
+        display: flex
+        justify-content: space-evenly
+        align-items: center
+        .picking-button-item
+          flex: 1
+          color: #419bff
+          font-weight: bold
+          line-height: 35px
+          border-right: 1px solid #eaeaeb
+          box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1)
+        .picking-button-item:last-child
+          border-right: none
+    .order-list-box
+      background: #fff
+      margin-top: 2px
+      .order-list
+        width: 100%
+        overflow-y: auto
+        max-height: 60vh
+        .task-table, .task-table-bin, .task-table-box
+          width: 100%
+          table-layout: fixed
+          border-collapse: collapse
+          font-size: 15px
+
+        .task-table th, .task-table-bin th, .task-table td, .task-table-bin td, .task-table-box th, .task-table-box td
+          text-align: center
+          border: 1px solid #ccc
+          word-wrap: break-word
+          word-break: break-all
+
+        .task-table thead, .task-table-bin thead, .task-table-box thead
+          background-color: #3f8dff
+          position: sticky
+          top: 0
+          color: white
+          font-size: 15px
+
+        .task-table-bin thead
+          background-color: #3f8dff
+
+        .task-table-bin tbody
+          background: #cde7ff
+
+
+
+
+</style>

+ 194 - 0
src/views/outbound/check/components/BatchPacking.vue

@@ -0,0 +1,194 @@
+<template>
+  <van-dialog v-model:show="countTrueFalseBy"
+              title="批量装箱"
+              :close-on-click-modal="false"
+              :show-cancel-button="false"
+              :show-confirm-button="false"
+              :keyboardEnabled="false"
+  >
+    <div class="scan-barcode">
+      <van-field ref="countRef" type="number" v-model.lazy="count" label-align="left" placeholder="请输入每箱数量"
+                 label="数量:"
+                 class="input-barcode" autocomplete="off" @keydown.enter="confirmPacking"
+                 :max="packingList.length>0?packingList[0].quantity:100" />
+    </div>
+    <div class="materia-list">
+      <table class="task-table">
+        <thead>
+        <tr>
+          <th>条码</th>
+          <th>名称</th>
+          <th style="width: 60px">数量</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr v-for="(item, index) in packingList" :key="index" v-if="packingList.length>0">
+          <td>{{ item.barcode }}</td>
+          <td>
+            <van-text-ellipsis :content="item.skuName" />
+          </td>
+          <td>{{ item.quantity }}</td>
+        </tr>
+        <tr v-else>
+          <td colspan="3">
+            <div>暂无数据</div>
+          </td>
+        </tr>
+        </tbody>
+      </table>
+    </div>
+  </van-dialog>
+</template>
+<script setup>
+import { ref } from 'vue'
+import { showNotify } from 'vant'
+import { scanError, scanSuccess } from '@/utils/android'
+import { closeLoading, showLoading } from '@/utils/loading'
+import { packingReview } from '@/api/check/index'
+
+const countTrueFalseBy = ref(false)
+const packingList = ref([])
+const orderDetail = ref({})
+const count = ref('')
+const countRef = ref(null)
+const show = (list, detail) => {
+  count.value = ''
+  packingList.value=list.filter(item => item.isPacking && item.status=='60' )
+  orderDetail.value = detail
+  countTrueFalseBy.value = true
+  setTimeout(() => {
+    countRef.value?.focus()
+  })
+}
+const confirmPacking = () => {
+  const packingBarcode = packingList.value[0]
+  const dataList = createPackingList(packingBarcode, count.value)
+  recursiveRequest(0, dataList, packingBarcode)
+}
+const emit = defineEmits(['loadData', 'print'])
+const recursiveRequest = async (index, dataList, item) => {
+  if (index >= dataList.length) {
+    countTrueFalseBy.value = false
+    emit('loadData')
+    scanSuccess()
+    return
+  }
+  try {
+    const data = {
+      warehouse: item.warehouseId,
+      workStation: item.warehouseId,
+      code: item.orderNo,
+      packingReviewMode: true,
+      groupDetailList: [dataList[index]],
+    }
+    showLoading()
+    const res = await packingReview(data)
+    if (res.data && res.data != '0000') {
+      showNotify({ type: 'success', duration: 3000, message: res.data + '装箱成功' })
+      await recursiveRequest(index + 1, dataList, item)
+      // 是否需要打印面单
+      if (res.data.includes('#')) {
+        showNotify({ type: 'success', duration: 3000, message: res.data + '已设置不获取新面单' })
+      } else if (res.data == item.deliveryNo) {
+        showNotify({ type: 'success', duration: 3000, message: res.data + '本单为初始单号,不打印面单' })
+      } else {
+        emit('print', 'PRINT_WAYBILL', res.data)
+      }
+      closeLoading()
+    } else {
+      emit('loadData')
+      //取消单
+    }
+  } catch (error) {
+    closeLoading()
+    if (index != 0) {
+      emit('loadData')
+      this.batchPackingTrueFalseBy = false
+    }
+    scanError()
+    showNotify({ type: 'danger', duration: 3000, message: `第 ${index} 箱,装箱失败` })
+    console.error(`请求第 ${index} 箱失败:`, error)
+  }
+}
+//创建装箱数组
+const createPackingList = (item, count) => {
+  const result = []
+  let remainingQuantity = item.quantity
+  let serialNoIndex = 0  //提取的序列号位置
+  // 生成对象,直到剩余扫描数量小于等于 0
+  while (remainingQuantity > 0) {
+    const qty = Math.min(remainingQuantity, count)
+    result.push({
+      lotNum: item.lotNum,
+      qty: qty,
+    })
+    // 更新剩余数量
+    remainingQuantity -= qty
+    // 更新数量,下次从序列号中取出不同的部分
+    serialNoIndex += qty
+  }
+  return result
+}
+defineExpose({ show })
+</script>
+
+<style scoped lang="sass">
+.scan-barcode
+  ::v-deep(.van-cell)
+    padding: 5px 15px 0 15px
+
+  ::v-deep(.van-field__control)
+    border-bottom: 2px solid #efefef
+    font-size: 16px
+
+  ::v-deep(.van-field__label)
+    width: unset
+    font-size: 16px
+
+  .input-barcode
+    ::v-deep(.van-field__control)
+      border-bottom: 2px solid #0077ff
+      z-index: 2
+
+  .success-input-barcode
+    ::v-deep(.van-field__control)
+      border-bottom: 2px solid #1ca600
+      z-index: 2
+
+  .error-input-barcode
+    ::v-deep(.van-field__control)
+      border-bottom: 2px solid #ff0000
+      z-index: 2
+
+.materia-list
+  width: 100%
+  overflow-y: auto
+  max-height: 60vh
+  padding: 10px 0
+
+  .task-table, .task-table-bin, .task-table-box
+    width: 100%
+    table-layout: fixed
+    border-collapse: collapse
+    font-size: 15px
+
+  .task-table th, .task-table-bin th, .task-table td, .task-table-bin td, .task-table-box th, .task-table-box td
+    text-align: center
+    border: 1px solid #ccc
+    word-wrap: break-word
+    word-break: break-all
+
+  .task-table thead, .task-table-bin thead, .task-table-box thead
+    background-color: #3f8dff
+    position: sticky
+    top: 0
+    color: white
+    font-size: 15px
+
+  .task-table-bin thead
+    background-color: #3f8dff
+
+  .task-table-bin tbody
+    background: #cde7ff
+
+</style>

+ 134 - 0
src/views/outbound/check/components/CheckPacking.vue

@@ -0,0 +1,134 @@
+<template>
+  <van-dialog v-model:show="packingTrueFalseBy"
+              title="装箱"
+              close-on-click-overlay
+              :keyboard-enabled="false"
+                show-cancel-button
+              :beforeClose="beforeClose">
+    <div class="packing-list">
+      <table class="task-table">
+        <thead>
+        <tr>
+          <th>条码</th>
+          <th>名称</th>
+          <th style="width: 60px">数量</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr v-for="(item, index) in packingList" :key="index" v-if="packingList.length>0">
+          <td>{{ item.barcode }}</td>
+          <td><van-text-ellipsis :content="item.skuName" /></td>
+          <td>{{ item.quantity }}</td>
+        </tr>
+        <tr v-else>
+          <td colspan="3">
+            <div>暂无数据</div>
+          </td>
+        </tr>
+        </tbody>
+      </table>
+    </div>
+  </van-dialog>
+</template>
+<script setup>
+import { ref } from 'vue'
+import { packingReview } from '@/api/check/index'
+import { closeLoading, showLoading } from '@/utils/loading'
+import { showNotify } from 'vant'
+import { scanError, scanSuccess } from '@/utils/android'
+const packingTrueFalseBy = ref(false)
+const packingList = ref([])
+const orderDetail=ref({})
+const show = (list,detail) => {
+  packingList.value=list.filter(item => item.isPacking && item.status=='60' )
+  orderDetail.value=detail
+  packingTrueFalseBy.value = true
+}
+const beforeClose = (action) =>
+  new Promise(async (resolve) => {
+    if (action === 'confirm') {
+      setPacking()
+      resolve(true)
+    }
+    resolve(true)
+  })
+const emit = defineEmits(['print','cancelOrder','resetPackingStatus'])
+const setPacking = () => {
+  const groupDetailList = packingList.value.map(items => {
+    return {
+      lotNum: items.lotNum,
+      qty: items.quantity,
+    };
+  });
+  const data = {
+    warehouse: orderDetail.value.warehouseId,
+    workStation:orderDetail.value.warehouseId,
+    code: orderDetail.value.orderNo,
+    packingReviewMode:true,
+    groupDetailList,
+  };
+  showLoading()
+  packingReview(data).then(res => {
+    if(res.data=='0000'){
+      scanError()
+      // 订单取消
+     emit('cancelOrder',orderDetail.value,'erp')
+    }else {
+      showNotify({ type: 'success', duration: 3000, message: res.data + '装箱成功'})
+      scanSuccess()
+      emit('resetPackingStatus')
+      // packingList.value.forEach((item) => {
+      //   item.quantity=0
+      //   delete item.isPacking
+      // })
+      const qty=groupDetailList.reduce((sum, item) => sum + Number(item.qty), 0)
+      const packingItem={ orderNo:orderDetail.value.orderNo, traceId:res.data, qty }
+      orderDetail.value.orderPacking.unshift(packingItem)
+      // 是否需要打印面单
+      if (res.data.includes('#') ) {
+        showNotify({ type: 'success', duration: 3000, message: res.data + '已设置不获取新面单'})
+      } else if(res.data==orderDetail.value.deliveryNo ){
+        showNotify({ type: 'success', duration: 3000, message: res.data + '本单为初始单号,不打印面单'})
+      } else {
+        emit('print','PRINT_WAYBILL',res.data)
+      }
+    }
+  }).finally(f=>{
+    closeLoading()
+  })
+}
+defineExpose({ show })
+</script>
+
+<style scoped lang="sass">
+.packing-list
+  width: 100%
+  overflow-y: auto
+  max-height: 60vh
+  padding: 10px 0
+  .task-table, .task-table-bin, .task-table-box
+    width: 100%
+    table-layout: fixed
+    border-collapse: collapse
+    font-size: 15px
+
+  .task-table th, .task-table-bin th, .task-table td, .task-table-bin td, .task-table-box th, .task-table-box td
+    text-align: center
+    border: 1px solid #ccc
+    word-wrap: break-word
+    word-break: break-all
+
+  .task-table thead, .task-table-bin thead, .task-table-box thead
+    background-color: #3f8dff
+    position: sticky
+    top: 0
+    color: white
+    font-size: 15px
+
+  .task-table-bin thead
+    background-color: #3f8dff
+
+  .task-table-bin tbody
+    background: #cde7ff
+
+</style>

+ 55 - 0
src/views/outbound/check/components/OrderListTable.vue

@@ -0,0 +1,55 @@
+<template>
+  <van-dialog v-model:show="orderTrueFalseBy"
+              :title="title"
+              close-on-click-overlay
+              :show-cancel-button="false"
+              :show-confirm-button="false">
+    <div class="order-list">
+      <div class="list-item">
+        <div class="barcode">条码</div>
+        <div class="barcode">商品名称</div>
+        <div class="number" >数量</div>
+      </div>
+      <div v-for="(value,key) in orderMap">
+        <div v-for="item in value" class="list-item van-hairline--bottom">
+          <div class="barcode"> {{item.barcode}}</div>
+          <div class="barcode"> {{item.skuName}}</div>
+          <div class="number" > {{item.qtyOrdered}}</div>
+        </div>
+      </div>
+
+    </div>
+  </van-dialog>
+</template>
+<script setup>
+import {ref } from 'vue'
+const orderTrueFalseBy = ref(false)
+const title=ref('取消单')
+const orderMap=ref([])
+const show=(type,order)=>{
+  const typeMap={
+    cancel:'取消单列表',
+    freeze:'冻结单列表',
+  }
+  orderTrueFalseBy.value=true
+  title.value=typeMap[type]
+  orderMap.value=order
+}
+defineExpose({show})
+</script>
+
+<style scoped lang="sass">
+.order-list
+  width: 100%
+  overflow-y: auto
+  max-height: 60vh
+  .list-item
+    display: flex
+    font-size: 14px
+    line-height: 30px
+    .barcode
+      flex: 1
+    .number
+      width: 80px
+
+</style>

+ 123 - 0
src/views/outbound/check/components/PackingList.vue

@@ -0,0 +1,123 @@
+<template>
+  <van-dialog v-model:show="listTrueFalseBy"
+              :show-cancel-button="true"
+              :show-confirm-button="true"
+              :close-on-click-overlay="false"
+              title="装箱列表"
+              confirm-button-text="重置装箱"
+              :beforeClose="onBuildTask"
+  >
+    <div class="order-list">
+      <van-checkbox  class="order-checkbox" v-model="isCheckAll" :indeterminate="isIndeterminate"  @change="checkAllChange">全选</van-checkbox>
+        <van-checkbox-group  v-model="checkedResult" @change="checkedChange" >
+          <van-checkbox class="order-item" v-for="(item,index) in packingList" :key="index" :name="item">
+            <div  style="display: flex;flex: 1;flex-direction: column" >
+              <div class="order-item-detail">
+                <div>{{item.traceId}}</div>
+                <div  style="width: 30px">{{item.qty}}<span style="font-size: 12px">件</span></div>
+              </div>
+            </div>
+          </van-checkbox>
+        </van-checkbox-group>
+      </div>
+  </van-dialog>
+</template>
+<script setup>
+import { ref } from 'vue'
+import { showConfirmDialog, showNotify, showToast } from 'vant'
+import { useRouter } from 'vue-router'
+import { resetCheckPacking } from '@/api/check/index'
+import { closeLoading, showLoading } from '@/utils/loading'
+import { scanError, scanSuccess } from '@/utils/android'
+const router = useRouter()
+const listTrueFalseBy =ref(false)
+const packingList = ref([])
+const orderDetail=ref({})
+const show = (list,detail) => {
+  packingList.value=list
+  orderDetail.value=detail
+  listTrueFalseBy.value=true
+  isCheckAll.value=false
+  isIndeterminate.value=false
+  checkedResult.value=[]
+}
+// 全选
+const isCheckAll=ref(false)
+//不确定状态
+const isIndeterminate=ref(false)
+//选中数据
+const checkedResult = ref([])
+
+const checkAllChange = (val) => {
+  checkedResult.value = val ? packingList.value : []
+  isIndeterminate.value = false
+}
+const checkedChange = (value) => {
+  const checkedCount = value.length
+  isCheckAll.value = checkedCount === packingList.value.length
+  isIndeterminate.value = checkedCount > 0 && checkedCount < packingList.value.length
+}
+//确认
+const onBuildTask=(action)=>
+  new Promise((resolve) => {
+    if (action === 'confirm') {
+      if (checkedResult.value.length === 0 ) {
+        showToast('请选择数据')
+        return resolve(false)
+      }
+      showConfirmDialog({
+        title: '温馨提示',
+        message: '是否重置装箱?',
+        })
+        .then(() => {
+          resetPacking()
+        }).catch(() => {})
+    }
+    resolve(true)
+  })
+const emit = defineEmits(['loadData'])
+const resetPacking=()=>{
+  const traceIdList = checkedResult.value.map(item => item.traceId);
+  const data = {
+    warehouseId: orderDetail.value.warehouseId,
+    orderNo:  orderDetail.value.orderNo,
+    traceIdList
+  }
+  showLoading()
+  resetCheckPacking(data).then(res => {
+    scanSuccess()
+    showNotify({ type: 'success', duration: 3000, message: `重置成功,正在刷新数据!` })
+    emit('loadData')
+  }).catch(err=>{
+    scanError()
+  }).finally(() => {
+    closeLoading()
+  })
+}
+
+defineExpose({show})
+</script>
+
+<style scoped lang="sass">
+.order-list
+
+  padding: 5px 10px
+  min-height: 20vh
+  max-height: 50vh
+  overflow: scroll
+  .order-checkbox
+    font-size: 14px
+    padding-bottom: 5px
+  .order-item
+    padding: 5px 0
+    :deep(.van-checkbox__label)
+      display: flex
+      flex: 1
+    .order-item-detail
+      font-size: 14px
+      display: flex
+      flex: 1
+      align-items: center
+      justify-content: space-between
+
+</style>

+ 163 - 0
src/views/outbound/check/components/RelatedMateria.vue

@@ -0,0 +1,163 @@
+<template>
+  <van-dialog v-model:show="materiaTrueFalseBy"
+              title="耗材扫描"
+              :close-on-click-modal="false"
+              :show-cancel-button="false"
+              :show-confirm-button="false"
+              :keyboardEnabled="false"
+
+  >
+    <div class="scan-barcode">
+      <van-field ref="scanBarcodeRef" v-model.lazy="scanBarcode"  label-align="left" placeholder="请扫描商品条码/SKU" label="商品条码:"
+                 class="input-barcode" autocomplete="off" @keydown.enter="_handlerScan(scanBarcode)"  />
+    </div>
+    <div class="materia-list">
+      <table class="task-table">
+        <thead>
+        <tr>
+          <th style="width: 50%">耗材条码</th>
+          <th>数量</th>
+          <th>剩余</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr v-for="(item, index) in materiaList" :key="index" v-if="materiaList.length>0">
+          <td>{{ item.barCode }}</td>
+          <td>{{item.qty}}/{{item.quantity}}</td>
+          <td>{{ item.qty }}</td>
+        </tr>
+        <tr v-else>
+          <td colspan="3">
+            <div>暂无数据</div>
+          </td>
+        </tr>
+        </tbody>
+      </table>
+    </div>
+  </van-dialog>
+</template>
+<script setup>
+import {ref } from 'vue'
+import { barcodeToUpperCase } from '@/utils/dataType'
+import { showNotify } from 'vant'
+import { scanError, scanSuccess } from '@/utils/android'
+const materiaTrueFalseBy = ref(false)
+const scanBarcode=ref('')
+const orderActive=ref({})
+const materiaList=ref([])
+const scanBarcodeRef=ref(null)
+const show=(order)=>{
+  scanBarcode.value=''
+  order.relatedMaterial.forEach((item)=>{
+    item.quantity=item.qty
+  })
+  orderActive.value=order
+  materiaList.value=JSON.parse(JSON.stringify(order.relatedMaterial))
+  materiaTrueFalseBy.value=true
+  setTimeout(()=>{
+    scanBarcodeRef.value?.focus()
+  })
+}
+const _handlerScan = (code) => {
+  if (code) {
+    const barcode = [...new Set(
+      materiaList.value
+        .flatMap(item => [item.barCode, item.sku])
+        .filter(value => value !== null && value !== '' && value !== undefined)
+    )];
+    const checkBarcode = barcodeToUpperCase(code);
+    if (barcode.some(item => barcodeToUpperCase(item) === checkBarcode)) {
+     const matchBarcodeList=materiaList.value.filter(item=>((item.barCode===checkBarcode || item.sku===checkBarcode) && item.qty>0) )
+      if(matchBarcodeList.length>0){
+        const itemActive = matchBarcodeList[0]
+        cutMateria(itemActive,1)
+      }else {
+        scanBarcode.value=''
+        scanError()
+        showNotify({ type: 'danger', duration: 3000, message: `耗材条码${code},已全部扫描完成`})
+      }
+    }else {
+      scanBarcode.value=''
+      showNotify({ type: 'danger', duration: 3000, message: `耗材条码${code},不匹配请重新扫描!`})
+      scanError()
+    }
+  }
+}
+const emit = defineEmits(['cutBarcode'])
+const cutMateria = (itemActive, count) => {
+  if (itemActive.qty > 0) {
+    itemActive.qty -= count;
+    itemActive.qty = Math.max(itemActive.qty, 0)
+  }
+  // 计算所有商品的总数量
+  const allCount = materiaList.value.reduce((sum, item) => sum + Math.max(Number(item.qty), 0), 0);
+  // 根据所有商品总数量判断是否继续扫描
+  if (allCount > 0) {
+    scanBarcode.value = '';
+    showNotify({ type: 'success', duration: 3000, message: '数量已扣除,请继续扫描条码' });
+    scanSuccess();
+  } else {
+    emit('cutBarcode',orderActive.value,1)
+    showNotify({ type: 'success', duration: 3000, message: '耗材扫描完成' })
+    materiaTrueFalseBy.value=false
+  }
+
+};
+defineExpose({show})
+</script>
+
+<style scoped lang="sass">
+.scan-barcode
+  ::v-deep(.van-cell)
+    padding: 5px 15px 0 15px
+
+  ::v-deep(.van-field__control)
+    border-bottom: 2px solid #efefef
+    font-size: 16px
+
+  ::v-deep(.van-field__label)
+    width: unset
+    font-size: 16px
+  .input-barcode
+    ::v-deep(.van-field__control)
+      border-bottom: 2px solid #0077ff
+      z-index: 2
+  .success-input-barcode
+    ::v-deep(.van-field__control)
+      border-bottom: 2px solid #1ca600
+      z-index: 2
+  .error-input-barcode
+    ::v-deep(.van-field__control)
+      border-bottom: 2px solid #ff0000
+      z-index: 2
+.materia-list
+  width: 100%
+  overflow-y: auto
+  max-height: 60vh
+  padding: 10px 0
+  .task-table, .task-table-bin, .task-table-box
+    width: 100%
+    table-layout: fixed
+    border-collapse: collapse
+    font-size: 15px
+
+  .task-table th, .task-table-bin th, .task-table td, .task-table-bin td, .task-table-box th, .task-table-box td
+    text-align: center
+    border: 1px solid #ccc
+    word-wrap: break-word
+    word-break: break-all
+
+  .task-table thead, .task-table-bin thead, .task-table-box thead
+    background-color: #3f8dff
+    position: sticky
+    top: 0
+    color: white
+    font-size: 15px
+
+  .task-table-bin thead
+    background-color: #3f8dff
+
+  .task-table-bin tbody
+    background: #cde7ff
+
+</style>

+ 84 - 0
src/views/outbound/check/components/ReversePicking.vue

@@ -0,0 +1,84 @@
+<template>
+  <van-dialog v-model:show="packingTrueFalseBy"
+              title="装箱结果"
+              close-on-click-overlay
+              :beforeClose="beforeClose">
+    <div class="order-list">
+      <div class="order-number">
+        <span>已成功:{{ successCount }}单</span>
+        <span v-if="errorCount > 0">,已失败:{{ errorCount }}单</span>
+        <span v-if="cancelCount > 0">,已取消:{{ cancelCount }}单</span>
+      </div>
+      <div v-if="cancelCount > 0" class="cancel-list">
+        <div>取消单: <span v-for="(value, key) in cancelMap" :key="key">{{ key }},</span>请进行返拣!!!</div>
+      </div>
+    </div>
+  </van-dialog>
+</template>
+<script setup>
+import { ref } from 'vue'
+import { reversePicking } from '@/api/check/index'
+import { closeLoading, showLoading } from '@/utils/loading'
+import { showNotify } from 'vant'
+
+const packingTrueFalseBy = ref(false)
+const cancelMap = ref({})
+const successCount = ref(0)
+const errorCount = ref(0)
+const props = defineProps({
+  warehouse: String,
+  reversePickingContainerNo: String,
+})
+const cancelCount=ref(0)
+const show = (successNumber, errorNumber, cancelOrder) => {
+  successCount.value = successNumber
+  errorCount.value = errorNumber
+  cancelMap.value = cancelOrder
+  cancelCount.value= Object.keys(cancelMap.value).length;
+  packingTrueFalseBy.value = true
+}
+
+const beforeClose = (action) =>
+  new Promise(async (resolve) => {
+    if (action === 'confirm') {
+      if (Object.keys(cancelMap.value).length > 0) {
+        _reversePicking()
+        return
+      }
+      emit('loadData')
+      resolve(true)
+    }
+    resolve(true)
+  })
+const emit = defineEmits(['loadData'])
+const _reversePicking = () => {
+  showLoading()
+  const data = {
+    warehouse: props.warehouse,
+    reversePickingContainerNo: props.reversePickingContainerNo,
+    code: Object.keys(cancelMap.value),
+  }
+  reversePicking(data).then(res => {
+    showNotify({ type: 'success', duration: 3000, message: `${data.code},已进行返拣,请放置返拣容器中!` })
+    emit('loadData')
+  }).finally(() => {
+    closeLoading()
+  })
+}
+defineExpose({ show })
+</script>
+
+<style scoped lang="sass">
+.order-list
+  width: 100%
+  overflow-y: auto
+  max-height: 60vh
+
+  .order-number
+    line-height: 30px
+    font-size: 14px
+
+  .cancel-list
+    padding: 10px 20px
+
+</style>

+ 738 - 0
src/views/outbound/check/large/index.vue

@@ -0,0 +1,738 @@
+<template>
+  <div class="container">
+    <van-nav-bar
+      title="复核-大件单" left-arrow fixed placeholder @click-left="goBack">
+      <template #left>
+        <van-icon name="arrow-left" size="25" />
+        <div style="color: #fff">返回</div>
+      </template>
+      <!--      <template #right>-->
+      <!--        <div class="nav-right" @click="onClickRight">提交任务</div>-->
+      <!--      </template>-->
+    </van-nav-bar>
+    <div class="large">
+      <div class="large-title">
+        <!--        <div><span style="font-size: 12px">订单号:</span>{{ orderNo || '&#45;&#45;' }}</div>-->
+        <div class="large-tips">
+          <van-notice-bar :background="'none'" :speed="50" :text="tips" />
+        </div>
+        <van-button plain size="mini" type="primary" @click="_setOrderNo()">更换订单/快递</van-button>
+      </div>
+      <div class="scan-barcode">
+        <van-field v-model.lazy="scanBarcode" label-align="left" placeholder="请扫描商品条码/SKU" label="商品条码:"
+                  ref="barcodeNumberRef" class="input-barcode" autocomplete="off" @keydown.enter="_handlerScan(scanBarcode)" />
+      </div>
+      <div class="order-detail">
+        <div class="picking-no">
+          <div class="picking-code">
+            <van-icon name="stop-circle-o" color="#419bff" />
+            <span style="padding-left: 5px">单号:{{ orderDetail.orderNo }}</span></div>
+          <div>{{ orderDetail.carrierName || '--' }}</div>
+        </div>
+        <div class="picking-container ">
+          <div style="display: flex;padding-top: 5px"  @click="setPrinter()">
+            <div>打印机:</div>
+            <div v-if="printer">{{printer.printer}}</div>
+            <div  style="text-decoration: underline;color: #0077ff">设置打印机<van-icon name="edit" color="#0077ff"/> </div>
+          </div>
+          <div class="picking-order-count ">
+            <div>产品数量:
+              <span>{{scanOrderBarcodeCount}}/{{ orderBarcodeCount }}</span>
+            </div>
+            <div>耗材数量:
+              <span>{{ scanMaterialCount }}/{{ materialCount }}</span>
+            </div>
+            <div>已装箱数:
+              <span v-if="orderDetail.orderPacking && orderDetail.orderPacking.length>0" style="text-decoration: underline;color: #0077ff" @click="resetPacking" >{{ orderDetail.orderPacking.length }}<van-icon name="arrow-double-right" color="#0077ff" /></span>
+              <span v-else>0</span>
+            </div>
+          </div>
+        </div>
+        <div class="picking-button">
+          <div class="picking-button-item" @click="reset()">重置</div>
+          <div class="picking-button-item" @click="print('A3001_SO_PACKINGLIST',orderDetail.orderNo)">总清单</div>
+          <div class="picking-button-item"  @click="setPacking('batch')">批量分箱</div>
+          <div class="picking-button-item" @click="setPacking('single')">装箱</div>
+        </div>
+      </div>
+      <div class="order-list-box">
+        <van-divider style="margin: 0;padding: 5px 15px">
+          <template #default>
+            <div style="display: flex;align-items: center">订单明细(<div style="background: #E6A23C;height: 10px;width: 10px;margin:0 5px"></div><div>待装箱商品)</div></div>
+          </template>
+        </van-divider>
+        <div class="order-list">
+          <table class="task-table">
+            <thead>
+            <tr>
+              <th style="width: 20px">#</th>
+              <th style="width: 40%">商品条码</th>
+              <th>数量</th>
+              <th>剩余</th>
+              <th>状态</th>
+              <th v-if="isUniqueCode">标记</th>
+            </tr>
+            </thead>
+            <tbody>
+            <tr v-for="(item, index) in orderList" :key="index" v-if="orderList.length>0" :style="rowStyle(item)" >
+              <td>{{ index + 1 }}</td>
+              <td>{{ item.barcode }} <van-tag type="success" v-if="item.universalCode">万用</van-tag></td>
+              <td>
+                <span v-if="item.qtyOrdered" :style="item.qty!=item.oldQuantity && item.oldQuantity!=0 && item.qty!=0?'color:#b40a1e':''">{{ item.qty }}/{{ item.qtyOrdered }}</span>
+                <span v-else><van-tag type="warning">耗材</van-tag></span>
+              </td>
+              <td>{{ item.qty }}</td>
+              <td>{{ statusMap[item.status] || '' }}</td>
+              <td v-if="isUniqueCode">
+                <van-tag v-if="item.uniqueRegExp" type="warning">唯一码</van-tag>
+                <van-tag v-if="item.imeiRegExp" type="primary">IMEI码</van-tag>
+              </td>
+            </tr>
+            <tr v-else>
+              <td colspan="5">
+                <div>暂无数据</div>
+              </td>
+            </tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+    <!-- 条码输入组件 -->
+    <input-barcode :back="back" @setBarcode="setBarcode" ref="inputBarcodeRef" />
+    <!--    打印面单-->
+    <printer ref="printerRef" @onPrint="onPrint" />
+    <!--    批量扫描-->
+    <van-dialog v-model:show="barcodeNumberTrueFalseBy" title="批量扫描" show-cancel-button  :beforeClose="beforeClose" :keyboardEnabled="false"  >
+      <van-field v-model="barcodeNumber" autocomplete="off"  center border label="数量:" placeholder="请输入数量"
+                 type="digit" name="pattern" ref="barcodeNumberRef" class="count-input"  @keydown.enter="onSubmitCount"
+                 label-width="70px" label-align="center" :rules="[{ pattern, message: '请输入正确数量' }]">
+      </van-field>
+      <van-row :gutter="[5, 5]" style="margin: 10px 20px;font-size: 14px;text-align: left;color:#333" v-for="item in matchBarcodeList">
+        <van-col span="12">条码:{{item.barcode}}</van-col>
+        <van-col span="12">数量:{{item.qty}}</van-col>
+      </van-row>
+    </van-dialog>
+    <!--    装箱-->
+    <check-packing ref="checkPackingRef" @cancelOrder="cancelOrder"  @resetPackingStatus="resetPackingStatus"  @print="print"/>
+    <!--    批量装箱-->
+    <batch-packing ref="batchPackingRef"   @print="print" @loadData="loadData" />
+    <!--    重置装箱-->
+    <packing-list ref="packingListRef"  @loadData="loadData" />
+  </div>
+</template>
+<script setup >
+import { onMounted, onUnmounted, computed, ref } from 'vue'
+import { androidFocus, getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
+import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
+import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
+import { useStore } from '@/store/modules/user'
+import { closeLoading, showLoading } from '@/utils/loading'
+import { getPendingReviewTask, reversePicking } from '@/api/check'
+import { barcodeToUpperCase } from '@/utils/dataType'
+import { showConfirmDialog, showDialog, showNotify, showToast } from 'vant'
+import { fluxPrint } from '@/api/picking'
+import Printer from '@/components/Printer.vue'
+import CheckPacking from '@/views/outbound/check/components/CheckPacking.vue'
+import BatchPacking from '@/views/outbound/check/components/BatchPacking.vue'
+import PackingList from '@/views/outbound/check/components/PackingList.vue'
+// 设置波次号
+const store = useStore()
+try {
+  getHeader()
+  androidFocus()
+} catch (error) {
+}
+// 页面初始化
+onMounted(() => {
+  openListener()
+  scanInit(_handlerScan)
+  loadData()
+})
+const warehouse = store.warehouse
+//SOZ25070900018
+const orderNo = ref('')
+// 错误提示
+const tips = ref('请扫描订单/快递单号')
+//强制返回
+const back = ref(true)
+const scanBarcode = ref('')
+const inputBarcodeRef = ref(null)
+// 订单明细
+const orderDetail = ref({})
+const isUniqueCode = ref(false)
+const statusMap = {
+  '00': '创建',
+  '20': '预配',
+  '30': '部分分配',
+  '40': '已分配',
+  '50': '待拣货',
+  '60': '已拣货',
+  '61': '分拣完成',
+  '90': '订单取消',
+}
+//订单列表
+const dataList = ref([])
+const orderList = computed(() => {
+  return dataList.value
+    .filter(item => item.qty !== 0 || item.isPacking)
+    .sort((a, b) => {
+      const statusPriority = {
+        '60': 0,
+        '600': 1,
+        '50': 2,
+        '40': 3,
+        '30': 4,
+        '20': 5,
+        '00': 6,
+      }
+      if (statusPriority[a.status] !== statusPriority[b.status]) {
+        return statusPriority[a.status] - statusPriority[b.status]
+      }
+      return 0
+    })
+})
+//订单产品总数
+const orderBarcodeCount = computed(() => {
+  return dataList.value.reduce((sum, item) => {
+    if (item.qtyOrdered != null) {
+      return sum + Number(item.oldQty)
+    }
+    return sum
+  }, 0)
+})
+//扫描订单产品总数
+const scanOrderBarcodeCount = computed(() => {
+  return dataList.value.reduce((sum, item) => {
+    if (item.qtyOrdered != null) {
+      return sum + Number(item.oldQuantity)
+    }
+    return sum
+  }, 0)
+})
+//耗材总数
+const materialCount = computed(() => {
+  return dataList.value.reduce((sum, item) => {
+    if (item.qtyOrdered == null) {
+      return sum + Number(item.oldQty)
+    }
+    return sum
+  }, 0)
+})
+const scanMaterialCount = computed(() => {
+  return dataList.value.reduce((sum, item) => {
+    if (item.qtyOrdered == null) {
+      return sum + Number(item.quantity)
+    }
+    return sum
+  }, 0)
+})
+
+//匹配条码
+const matchBarcodeList = ref([])
+//扣除数量
+const barcodeNumberTrueFalseBy = ref(false)
+const barcodeNumberRef = ref(null)
+const barcodeNumber=ref('')
+const pattern=/^[0-9]\d*$/
+const _handlerScan = async (code) => {
+  if (code) {
+    if (isUniqueCode.value) {
+      scanError()
+      scanBarcode.value = ''
+      showNotify({ type: 'warning', duration: 3000, message: `此单包含唯一码/IMEI码,请到PC复核` })
+      return
+    }
+    const barcode = [...new Set(
+      orderList.value
+        .filter(item => (item.status == '60' || item.status == '600'))
+        .flatMap(item => [item.barcode, item.barcode2, item.sku, item.universalCode])
+        .filter(value => value !== null && value !== '' && value !== undefined),
+    )]
+    const checkBarcode = barcodeToUpperCase(code)
+    if (barcode.some(item => barcodeToUpperCase(item) === checkBarcode)) {
+      dataList.value = await barcodeMatching(checkBarcode)
+      matchBarcodeList.value = dataList.value.filter(item => (item.barcode === checkBarcode || item.sku === checkBarcode || item.barcode2 === checkBarcode || item.universalCode == checkBarcode) && (item.status == '60' || item.status == '600') && item.qty > 0)
+      if (matchBarcodeList.value.length > 0) {
+        scanBarcode.value = code
+        const allCount = matchBarcodeList.value.reduce((sum, item) => sum + Number(item.qty), 0)
+        if (allCount > 1) {
+          barcodeNumberTrueFalseBy.value = true
+          setTimeout(() => {
+            barcodeNumber.value=''
+            barcodeNumberRef.value?.focus()
+          },300)
+          return
+        }
+        cutBarcode([matchBarcodeList.value[0]], 1)
+      } else {
+        scanBarcode.value = ''
+        scanError()
+        tips.value = `商品条码${code},已全部扫描完成`
+        showNotify({ type: 'warning', duration: 3000, message: `商品条码${code},已全部扫描完成` })
+      }
+    } else {
+      scanBarcode.value = ''
+      tips.value = `商品条码${code},不匹配请重新扫描!`
+      showNotify({ type: 'warning', duration: 3000, message: `商品条码${code},不匹配请重新扫描!` })
+      scanError()
+    }
+  }
+}
+const onSubmitCount=()=>{
+  if(!barcodeNumber.value){
+    tips.value='请输入数量'
+    showToast({duration:5000,message:'请输入数量'})
+    return
+  }
+  const allCount = matchBarcodeList.value.reduce((sum, item) => sum + Number(item.qty), 0)
+  if(Number(barcodeNumber.value)>allCount){
+    const message='数量不能大于最大数量'+allCount
+    tips.value=message
+    showToast({duration:5000,message})
+    return
+  }
+  barcodeNumberTrueFalseBy.value=false
+  cutBarcode(matchBarcodeList.value, barcodeNumber.value)
+}
+const  beforeClose= (action) =>
+  new Promise((resolve) => {
+    if(action==='confirm'){
+      if(!barcodeNumber.value){
+        tips.value='请输入数量'
+        showToast({duration:5000,message:'请输入数量'})
+        return resolve(false)
+      }
+      const allCount = matchBarcodeList.value.reduce((sum, item) => sum + Number(item.qty), 0)
+      if(Number(barcodeNumber.value)>allCount){
+        const message='数量不能大于最大数量'+allCount
+        tips.value=message
+        showToast({duration:5000,message})
+        return resolve(false)
+      }
+      resolve(true)
+      cutBarcode(matchBarcodeList.value, barcodeNumber.value)
+    }else {
+      tips.value='您已取消扣减扫描商品,请重新扫描'
+      scanBarcode.value=''
+      matchBarcodeList.value=[]
+      resolve(true)
+    }
+
+  });
+const cutBarcode = (list, count) => { //扣减数量
+  let remainingCount = count  // 剩余的扣减数量
+  // 更新扫描数据的逻辑
+  const updateData = (item) => {
+    const itemIndex = dataList.value.findIndex(data => data.lotNum === item.lotNum)
+    const deductedAmount = item.originalDetailAmount - item.qty // 扣减的数量
+    if (itemIndex !== -1) {
+      dataList.value[itemIndex] = {
+        ...dataList.value[itemIndex],
+        quantity: dataList.value[itemIndex].quantity + deductedAmount, // 更新实际扣减的数量
+        oldQuantity:dataList.value[itemIndex].oldQuantity + deductedAmount, // 更新实际扣减的数量
+      }
+    } else {
+      item.quantity = deductedAmount
+      item.oldQuantity=deductedAmount
+      dataList.value.push(item)
+    }
+  }
+  list.forEach(item => {
+    if (remainingCount <= 0) return  // 如果剩余数量为0,停止扣减
+    item.originalDetailAmount = item.qty // 保存原始数量
+    if (item.qty > 0) {
+      // 如果剩余数量大于当前商品的数量,则直接扣除当前商品的数量
+      if (remainingCount >= item.qty) {
+        remainingCount -= item.qty
+        item.qty = 0
+      } else {
+        item.qty -= remainingCount  // 扣减剩余数量
+        remainingCount = 0
+      }
+      item.isPacking=true
+    }
+    if (item.qty !== item.originalDetailAmount) {
+      updateData(item)  // 更新扫描数据
+      scanBarcode.value = ''
+      tips.value = '请继续扫描商品'
+      scanSuccess()
+    }
+  })
+  const materiaList=orderList.value.filter(item=>item.status=='600' && item.qty>0)
+  const endOrder=orderList.value.filter(item=>item.status=='60' && item.qty>0)
+  if(materiaList.length ==0 &&endOrder.length==0){
+    setPacking('single')
+    return
+  }
+}
+// 进行装箱
+const checkPackingRef=ref(null)
+const currPackingList=ref([])
+const batchPackingRef=ref(null)
+const setPacking=(type)=>{
+  if(!printer.value){
+    scanError()
+    showNotify({ type: 'warning', duration: 3000, message: '请先设置打印机' })
+    return
+  }
+  // 获取装箱的商品列表
+   currPackingList.value = orderList.value.filter(item => item.isPacking)
+  if (currPackingList.value.length === 0) {
+    scanError()
+    showNotify({ type: 'warning', duration: 3000, message: '暂无未装箱数据' })
+    return
+  }
+  const materiaList = orderList.value.filter(item => item.status == '600' && item.qty > 0)
+  if (materiaList.length > 0) {
+    const checkBarcode = new Set(currPackingList.value.flatMap(item => item.relatedMaterial.map(material => material.barCode)))
+    const packingBarcodes = new Set(materiaList.map(item => item.barcode))
+    // 判断是否有装箱商品条形码与需要扫描的物料条形码重复
+    const hasCommonBarcode = [...packingBarcodes].some(barcode => checkBarcode.has(barcode))
+    if (hasCommonBarcode) {
+      scanError();
+      showNotify({ type: 'warning', duration: 3000, message: `装箱商品包含耗材${[...checkBarcode].join(',')},请先扫描耗材` })
+      return
+    }
+  }
+  if (type === 'single') { // 普通装箱
+    checkPackingRef.value?.show(currPackingList.value, orderDetail.value)
+  } else { // 批量装箱
+    const list =orderList.value.filter(item => item.isPacking && item.status=='60')
+    if (list.length > 1) {
+      scanError()
+      showNotify({ type: 'warning', duration: 3000, message: '不支持多个商品装箱' })
+      return
+    }
+    batchPackingRef.value?.show(currPackingList.value, orderDetail.value)
+  }
+}
+//重置装箱状态
+const resetPackingStatus=()=>{
+  currPackingList.value.forEach((item) => {
+    item.quantity=0
+    delete item.isPacking
+  })
+}
+//重置装箱
+const packingListRef=ref(null)
+const resetPacking=()=>{
+  packingListRef.value?.show(orderDetail.value.orderPacking,orderDetail.value)
+}
+
+//重新开始
+const reset=()=>{
+  showConfirmDialog({
+    title: '温馨提示',
+    message: '您正在进行重新开始操作,是否继续?',
+  }).then(() => {loadData()})
+}
+//打印清单
+const printerRef=ref(null)
+//设置打印机
+const setPrinter=()=>{
+  printerRef.value?.show(warehouse)
+}
+const printer=ref(null)
+const print=(templateCode,code)=>{
+  if(!printer.value){
+    scanError()
+    showNotify({ type: 'warning', duration: 3000, message: '请先设置打印机' });
+    return
+  }
+  const data = {warehouse,code,printServer: printer.value.server, printName:printer.value.printer,templateCode }
+  showLoading()
+  fluxPrint(data)
+    .then(res => {
+      scanSuccess()
+      showNotify({ type: 'success', duration: 3000, message: '打印已发起,请检查打印情况' });
+    })
+    .catch(err => {
+      scanError()
+      tips.value=err.message || '系统异常,请联系技术支持!'
+    }).finally(() => {
+    closeLoading()
+  })
+
+}
+const onPrint=(code)=>{
+  printer.value=code
+}
+//设置订单号
+const setBarcode = (code) => {
+  const data = { warehouse, code, activityOrderFlag: true }
+  showLoading()
+  getPendingReviewTask(data).then(res => {
+    if (res.data.details.length == 0) {
+      scanError()
+      inputBarcodeRef.value?.show('', '请扫描订单/快递单号', '暂未查询到待复核数据,请切换单号')
+    } else {
+      if(res.data.releaseStatus=='H' || res.data.status=='90' ||res.data.erpCancelFlag=='Y'){
+        scanError()
+        cancelOrder(res.data)
+        return
+      }
+      if(res.data.waveType!=='M'){
+        scanError()
+        inputBarcodeRef.value?.show('', '请扫描订单/快递单号', '仅支持大件单复核','')
+        return
+      }
+      orderNo.value = code
+      orderDetail.value = res.data
+      tips.value = '请扫描商品条码'
+      scanSuccess()
+      // 处理每个详情项
+      res.data.details.forEach((item) => {
+        item.quantity = 0
+        item.oldQuantity = 0
+        item.oldQty = item.qty
+        if (item.relatedMaterial == null) {
+          item.relatedMaterial = []
+        }
+        if (item.universalBarcode) {
+          item.universalCode = '#@@@@@@#'
+        }
+      })
+      const relatedMaterialList = getRelatedMaterial(res.data.details)
+      res.data.details.push(...relatedMaterialList)
+      dataList.value = res.data.details
+      isUniqueCode.value = dataList.value.some(item => item.uniqueRegExp || item.imeiRegExp)
+      scanBarcode.value = ''
+    }
+  }).catch(err => {
+    scanError()
+    inputBarcodeRef.value?.show('', '请扫描订单/快递单号', err.message)
+  }).finally(f => {
+    closeLoading()
+  })
+}
+const containerNoMap={
+  'WH01':'FJ-WH01-20',
+  'WH02':'FJ-WH02-20',
+  'WH10':'FJ-WH10-01',
+  'WH99':'FJ-WH99-01',
+}
+//返拣容器
+const cancelOrder=(item,type)=>{
+  const orderDetailStatus = orderList.value.find(item => (item.status != '60' && item.status != '600'));
+  if(item.status=='90'){
+    showDialog({ title: '温馨提示', message: '已取消, 暂停发货', }).then(() => {
+      inputBarcodeRef.value?.show('', '请扫描订单/快递单号', '上一单为冻结单,请重新扫描单号')
+    });
+  }else if(item.releaseStatus=='H'){
+    showDialog({ title: '温馨提示', message: '已冻结, 暂停发货', }).then(() => {
+      inputBarcodeRef.value?.show('', '请扫描订单/快递单号', '上一单为冻结单,请重新扫描单号')
+    });
+  }else  if(orderDetailStatus && item.erpCancelFlag=='Y' ){
+    showDialog({ title: '温馨提示', message: '此取消单包含《待拣货》商品,请将所有商品拣货后返拣', }).then(() => {});
+  } else  if(item.erpCancelFlag=='Y' || type=='erp'){
+    showConfirmDialog({ title: '温馨提示', message: '订单为取消单,请进入还库流程!!!', })
+      .then(() => {
+        _reversePicking()
+      })
+  }
+}
+const _reversePicking = () => {
+  showLoading()
+  const data = {
+    warehouse,
+    reversePickingContainerNo: containerNoMap[warehouse],
+    code: orderDetail.value.orderNo,
+  }
+  reversePicking(data).then(res => {
+    showNotify({ type: 'success', duration: 5000, message: `${data.code},已进行返拣,请放置《${data.reversePickingContainerNo}》返拣容器中!` })
+    loadData()
+  }).finally(() => {
+    closeLoading()
+  })
+}
+
+const rowStyle=( row )=>{
+  if(row.isPacking){
+    return { background: '#E6A23C'}
+  }
+  if( row.status=='600'){
+    return { background: '#fff8d9'}
+  }
+  if(row.status!='60' ){
+    return { background: '#b3b3b3'}
+  }
+
+  return ''
+}
+//条码匹配放到前边
+const barcodeMatching = (checkBarcode) => {
+  return dataList.value.reduce((list, item) => {
+    if (item.status === '50') {
+      list.push(item)
+      return list
+    }
+    const itemBarcode = barcodeToUpperCase(item.barcode)
+    const isMatchingBarcode = itemBarcode === checkBarcode || checkBarcode === item.sku || checkBarcode === item.barcodeOne
+    if (isMatchingBarcode) {
+      list.unshift(item) // 匹配条形码的项放到顶部
+    } else {
+      list.push(item) // 不匹配的项放到末尾
+    }
+    return list
+  }, [])
+}
+// 格式化耗材
+const getRelatedMaterial = (data) => {
+  const materialMap = {}
+  let allocationIdCounter = 0
+  data.forEach(item => {
+    if (item.status == '60' && item.qty>0) {
+      item.relatedMaterial.forEach((material, index) => {
+        material.oldQty =  material.qty * item.qty
+        material.qty = material.qty * item.qty
+        material.barcode = material.barCode
+        material.barcode2 = material.barCode
+        material.qtyOrdered = null
+        material.skuName = material.skuDescr
+        material.quantity = 0
+        material.status = '600'
+        material.lotNum = allocationIdCounter++
+        material.relatedMaterial = []
+        if (materialMap[material.barcode]) {
+          materialMap[material.barcode].qty += material.qty
+        } else {
+          materialMap[material.barcode] = { ...material }
+        }
+      })
+    }
+  })
+  return Object.values(materialMap)
+}
+
+//切换波次
+const _setOrderNo = () => {
+  back.value = false
+  inputBarcodeRef.value?.show('', '请扫描订单/快递单号', '')
+}
+// 数据刷新
+const loadData = () => {
+  if (!orderNo.value) {
+    inputBarcodeRef.value?.show('', '请扫描订单/快递单号', '')
+    return
+  } else {
+    setBarcode(orderNo.value)
+  }
+}
+onUnmounted(() => {
+  closeListener()
+})
+
+// window.onRefresh = loadData
+</script>
+
+<style scoped lang="sass">
+.container
+  background: #e9f4ff
+
+  .large
+    .large-title
+      display: flex
+      justify-content: space-between
+      padding: 8px 10px
+
+      .large-tips
+        color: #ed6a0c
+        flex: 1
+
+    .scan-barcode
+      ::v-deep(.van-cell)
+        padding: 5px 15px 0 15px
+
+      ::v-deep(.van-field__control)
+        border-bottom: 2px solid #efefef
+        font-size: 16px
+
+      ::v-deep(.van-field__label)
+        width: unset
+        font-size: 16px
+
+      .input-barcode
+        ::v-deep(.van-field__control)
+          border-bottom: 2px solid #0077ff
+          z-index: 2
+
+    .order-detail
+      margin-top: 2px
+      background: #fff
+      font-size: 14px
+
+      .picking-no
+        display: flex
+        justify-content: space-between
+        margin: 0 15px
+        padding: 8px 0
+        border-bottom: 1px solid #eaeaeb
+      .picking-container
+        padding: 0 15px
+        text-align: left
+        border-bottom: 1px solid #eaeaeb
+
+        .container-item
+          line-height: 30px
+          display: flex
+          align-items: center
+
+        .picking-order-count
+          display: flex
+          justify-content: space-between
+          line-height: 30px
+
+      .picking-button
+        display: flex
+        justify-content: space-evenly
+        align-items: center
+
+        .picking-button-item
+          flex: 1
+          color: #419bff
+          font-weight: bold
+          line-height: 35px
+          border-right: 1px solid #eaeaeb
+          box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1)
+
+        .picking-button-item:last-child
+          border-right: none
+
+    .order-list-box
+      background: #fff
+      margin-top: 2px
+
+      .order-list
+        width: 100%
+        overflow-y: auto
+        max-height: 60vh
+
+        .task-table, .task-table-bin, .task-table-box
+          width: 100%
+          table-layout: fixed
+          border-collapse: collapse
+          font-size: 15px
+
+        .task-table th, .task-table-bin th, .task-table td, .task-table-bin td, .task-table-box th, .task-table-box td
+          text-align: center
+          border: 1px solid #ccc
+          word-wrap: break-word
+          word-break: break-all
+
+        .task-table thead, .task-table-bin thead, .task-table-box thead
+          background-color: #3f8dff
+          position: sticky
+          top: 0
+          color: white
+          font-size: 15px
+
+        .task-table-bin thead
+          background-color: #3f8dff
+
+        .task-table-bin tbody
+          background: #cde7ff
+  .count-input
+    border-bottom: 2px solid #0077ff
+
+</style>

+ 3 - 3
src/views/outbound/picking/components/expressPrint.vue

@@ -22,7 +22,7 @@ import { ref } from 'vue'
 import { showNotify, showToast } from 'vant'
 import Printer from '@/components/Printer.vue'
 import { closeLoading, showLoading } from '@/utils/loading'
-import { printExpressNo } from '@/api/picking'
+import { fluxPrint } from '@/api/picking'
 const props = defineProps({
   warehouse:String
 });
@@ -85,9 +85,9 @@ const printRequests = async (startIndex = 0, printNum) => {
 }
 const _printMode = (code) => {
   return new Promise((resolve, reject) => {
-    const params = { warehouse:props.warehouse,code, server: server.value, printer: printer.value }
+    const params = { warehouse:props.warehouse,code, printServer: server.value, printName: printer.value, templateCode:'PRINT_WAYBILL', }
     showLoading()
-    printExpressNo(params)
+    fluxPrint(params)
       .then(res => resolve(res))
       .catch(err => reject('打印失败', err))
   })