Sfoglia il codice sorgente

Merge remote-tracking branch 'origin/testing' into testing

# Conflicts:
#	src/router/index.ts
#	src/views/index.vue
zengjun 8 mesi fa
parent
commit
f4056e982d

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

@@ -73,7 +73,7 @@ export function getOrderPacking(data:any) {
  * 装箱复核
  * @param data
  */
-export function packingReview(data:packingReviewType) {
+export function packingReview(data:any) {
   return request({
     url: '/api/wms/outbound/review/packingReview',
     method: 'post',

+ 14 - 0
src/api/picking/index.ts

@@ -96,6 +96,19 @@ export function setPickingDetail(data: any[]) {
   })
 }
 
+/**
+ * 结束攒单
+ * @param data
+ */
+export function deleteAndClearOrderUid(params: any) {
+  return request({
+    url: 'api/erp/order/deleteAndClearOrderUid',
+    method: 'post',
+    params
+  })
+}
+
+
 /**
  * 云打印
  * @param data
@@ -109,6 +122,7 @@ export function fluxPrint(data: any) {
 }
 
 
+
 /**
  * 大件拣货
  */

+ 24 - 0
src/api/processing/index.ts

@@ -0,0 +1,24 @@
+// @ts-ignore
+import request from '@/utils/request'
+/**
+ * 加工-获取加工类型
+ * @param params
+ */
+export function getProcessingTypeList() {
+  return request({
+    url: 'api/device/check/processing/typeList',
+    method: 'get',
+  })
+}
+/**
+ * 加工-创建加工单
+ * @param params
+ */
+export function createProcessing(data:any) {
+  return request({
+    url: 'api/device/check/processing/reference',
+    method: 'post',
+    data,
+  })
+}
+

+ 6 - 0
src/router/index.ts

@@ -115,6 +115,12 @@ const routes: RouteRecordRaw[] = [
     meta:{title:'退货登记'},
     component: () => import('@/views/returned/register/index.vue')
   },
+  {
+    path: '/processing',
+    name: 'Processing',
+    meta:{title:'加工登记'},
+    component: () => import('@/views/processing/register/index.vue')
+  },
 ];
 
 // 创建路由实例

+ 31 - 0
src/utils/android.ts

@@ -103,3 +103,34 @@ export function scanError(){
 
   }
 }
+
+export function openCamera(){
+  try {
+    // @ts-ignore
+    const path = window.android.openCamera()
+    console.log(path)
+    return path
+  }catch (e){
+
+  }
+}
+
+export function openGallery(){
+  try {
+    // @ts-ignore
+    const header = window.android.openGallery()
+    console.log(header)
+  }catch (e){
+
+  }
+}
+
+export function openImageEditor(imagePath: string){
+  try {
+    // @ts-ignore
+    const header = window.android.openImageEditor(imagePath)
+    console.log(header)
+  }catch (e){
+
+  }
+}

+ 5 - 1
src/utils/dataType.js

@@ -24,7 +24,11 @@ export function toMap(data, key, val=undefined, optional = {}) {
 
   return map;
 }
-
+//字符串分割数组
+export function toArray(code) {
+  // 使用正则表达式分割字符串,去除多余的空白字符或换行符
+  return code.split(/[,,|\n\r\s]+/).filter(Boolean); // 使用 filter 去除空元素
+}
 
  export function barcodeToUpperCase(code){
    if (/[a-zA-Z]/.test(code)) {

+ 9 - 0
src/utils/date.ts

@@ -31,3 +31,12 @@ export function getCurrentTime() {
   const seconds = String(now.getSeconds()).padStart(2, '0');
   return `${hours}:${minutes}:${seconds}`;
 };
+/*
+ *获取当前时间
+ */
+export function formatDate(date:any) {
+  const year = date.getFullYear(); // 获取年份
+  const month = String(date.getMonth() + 1).padStart(2, '0'); // 获取月份
+  const day = String(date.getDate()).padStart(2, '0'); // 获取日期
+  return `${year}-${month}-${day}`;
+}

+ 32 - 5
src/views/haikang/putaway/putaway/index.vue

@@ -75,7 +75,7 @@ import { onMounted, onUnmounted, ref } from 'vue'
 import { androidFocus, getHeader, goBack, playVoiceBin, scanError, scanSuccess } from '@/utils/android'
 import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
 import { useStore } from '@/store/modules/user'
-import { showNotify } from 'vant'
+import { showConfirmDialog, showNotify } from 'vant'
 import { barcodeToUpperCase, toMap } from '@/utils/dataType'
 import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
 import Box from '@/views/haikang/putaway/components/Box.vue'
@@ -254,8 +254,10 @@ const reset=()=>{
   equipmentBarcodeList.value=[]
   locationBarcodeBinList.value=[]
   locationScanBarcodeBinList.value=[]
+  locationDetailList.value=[]
   equipmentMap.value={}
   dataList.value=[]
+
 }
 const inputCheck=()=>{
   if(!workBinNo.value){
@@ -323,14 +325,39 @@ const setPutaway=async (type)=>{
     }
     goBackRef.value?.show(workBinNo.value,activeType,count.value,barcodeActive.value)
   }else if(type==1){
-    if(!barcodeActive.value.container){
+    if(locationDetailList.value.length==0){
       scanType.value = 1
-      tips.value = `请先扫描商品条码,查询待上架信息`
-      showNotify({ type: 'danger', duration: 3000, message: `请先扫描商品条码,查询待上架信息` })
+      tips.value = `请先扫描料箱编号`
+      showNotify({ type: 'danger', duration: 3000, message: `请先扫描料箱编号,查询待上架信息` })
       scanError()
       return false
     }
-    goBackRef.value?.show(workBinNo.value,'1','', {})
+    showConfirmDialog({
+      title: '料箱回库',
+      message: `${ workBinNo.value },是否执行料箱回库?`,
+    })
+      .then(() => {
+        showLoading()
+        const {container,asnCode}=locationDetailList.value[0]
+        boxReturn({warehouse,container,boxCode:workBinNo.value,externalCode:asnCode}).then(res=>{
+          closeLoading()
+          scanSuccess()
+          tips.value = `料箱回库成功,请继续扫描料箱编号`
+          showNotify({ type: 'success', duration: 3000, message: `料箱回库成功,请继续扫描料箱编号` })
+          reset()
+          barcodeActive.value={}
+          workBinNo.value=''
+          searchBarcode.value=''
+          searchLocation.value=''
+          scanType.value = 1
+        }).catch(err=>{
+          closeLoading()
+          scanError()
+          tips.value =err.message
+        })
+      })
+      .catch(() => {});
+    // goBackRef.value?.show(workBinNo.value,'1','', {})
   }
 }
 const setGoBack=async (item)=>{

+ 2 - 1
src/views/inbound/blindCollection/task/index.vue

@@ -6,6 +6,7 @@
       fixed
       placeholder
       @click-left="onClickLeft"
+      @click-right="onClickRight"
       z-index="100"
     >
       <template #left>
@@ -13,7 +14,7 @@
         <div style="color: #fff" >返回</div>
       </template>
       <template #right>
-        <div style="color: #fff" @click="onClickRight">提交任务</div>
+        <div style="color: #fff">提交任务</div>
       </template>
     </van-nav-bar>
     <div class="blind-task-list">

+ 2 - 0
src/views/inbound/takeDelivery/task/index.vue

@@ -699,6 +699,8 @@ const onConfirm = () => {
       scanSuccess()
       showNotify({ type: 'success', duration: 3000, message: `${searchBarcode.value}收货完成,请继续收货!`})
       setBarcode(taskNo.value, '2')
+      reset()
+      taskInfo.value={}
       scanType.value=2
       closeLoading()
     }).catch(err => {

+ 3 - 2
src/views/index.vue

@@ -15,6 +15,7 @@
     <div class="home" @click="onRouter('check-large')">复核-大件单</div>
     <div class="home" @click="onRouter('move-list')">调拨-移库</div>
     <div class="home" @click="onRouter('returned-register')">退货-登记</div>
+    <div class="home" @click="onRouter('processing')">加工登记</div>
   </div>
 </template>
 <script setup>
@@ -48,8 +49,8 @@ setTimeout(()=>{
     margin: 20px
   .home
     cursor: pointer
-    padding: 10px
-    font-size: 18px
+    padding: 5px
+    font-size: 16px
     text-decoration: underline
     color: #0077ff
   .name

+ 11 - 11
src/views/outbound/check/activity/index.vue

@@ -22,7 +22,7 @@
         <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"  @click.enter.prevent="endCheck()"  >
+                    autocomplete="off" type="number"  >
           <template #button>
             <div>KG</div>
           </template>
@@ -113,7 +113,6 @@
     <reverse-picking ref="reversePickingRef" :warehouse="warehouse" :reversePickingContainerNo="reversePickingContainerNo" @load-data="loadData" @reversePickingReset="reversePickingReset" />
     <!--    耗材-->
     <related-materia ref="relatedMateriaRef" @cut-barcode="cutBarcode" />
-
   </div>
 </template>
 <script setup>
@@ -123,7 +122,7 @@ 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 { closeToast, showConfirmDialog, showLoadingToast, showNotify } from 'vant'
 import { closeLoading, showLoading } from '@/utils/loading'
 import { fluxPrint } from '@/api/picking/index'
 import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
@@ -358,12 +357,16 @@ const packingRequests = async (startIndex = 0, lastNumber) => {
         warehouse: item.warehouseId,
         workStation: item.warehouseId,
         code: item.orderNo,
-        totalGrossWeight: totalWeight.value,
+        totalGrossWeight:Number(totalWeight.value),
         groupDetailList,
       };
-      showLoading()
+        showLoadingToast({
+          duration: 0,
+          forbidClick: true,
+          message: `进度:${startIndex+1}/${lastNumber}`,
+        });
       const res = await packingReview(data)
-      closeLoading()
+      // const res = await getPendingReviewTask(data)
       if (res) {
         if (res.data == '0000'){
           orderMap.value.cancelGroup[item.orderNo] = item.orderNo
@@ -378,11 +381,11 @@ const packingRequests = async (startIndex = 0, lastNumber) => {
     errorNumber.value+=1
     tips.value=error.message
     if(startIndex>0){
-      reversePickingRef.value.show(successNumber.value,orderMap.value.cancelGroup.length-successNumber.value,orderMap.value.cancelGroup,orderMap.value.freezeGroup)
+      reversePickingRef.value.show(successNumber.value,lastNumber-successNumber.value,orderMap.value.cancelGroup,orderMap.value.freezeGroup)
     }
     scanError()
   } finally {
-    closeLoading()
+    closeToast()
   }
 }
 //更多操作
@@ -606,7 +609,4 @@ window.onRefresh = loadData
         .task-table-bin tbody
           background: #cde7ff
 
-
-
-
 </style>

+ 52 - 13
src/views/outbound/picking/task/index.vue

@@ -81,15 +81,36 @@
       </div>
       <div class="order-list">
         <div v-if="model.type=='*'">
-          <van-cell v-for="(item,index) in taskDetailList" class="order-cell" center
-                    is-link value="获取" @click="onSubCreateTask(item)" >
-            <template #title>
-              <div style="display: flex;justify-content:space-evenly;">
-                <div style="width: 65%">{{item.carrierName}}</div>
-                <div style="flex: 1"><span :style="item.hours>=2?'color:#ee0a24;font-weight: 500':''">{{item.residualOrderQty}}单</span></div>
-              </div>
-            </template>
-          </van-cell>
+          <table border="1" style="width: 100%;border-collapse: collapse;text-align: center;table-layout: fixed;font-size: 14px" >
+            <thead>
+            <tr>
+              <th>承运商</th>
+              <th style="width: 60px">数量</th>
+              <th>操作</th>
+            </tr>
+            </thead>
+            <tbody>
+            <tr v-for="(item, rowIndex) in taskDetailList" :key="rowIndex">
+              <td style="word-wrap: break-word" >
+                <div>{{ item.carrierName }}</div>
+              </td>
+              <td style="word-wrap: break-word;"><span :style="item.hours>=2?'color:#ee0a24;font-weight: 500':''">{{item.residualOrderQty}}单</span></td>
+              <td>
+                <van-button type="danger" size="mini" @click="remove(item)" >结束攒单</van-button>
+                <van-button type="primary" size="mini" @click="onSubCreateTask(item)" >获取任务</van-button>
+              </td>
+            </tr>
+            </tbody>
+          </table>
+<!--          <van-cell v-for="(item,index) in taskDetailList" class="order-cell" center-->
+<!--                    is-link value="获取" @click="onSubCreateTask(item)" >-->
+<!--            <template #title>-->
+<!--              <div style="display: flex;justify-content:space-evenly;">-->
+<!--                <div style="width: 65%">{{item.carrierName}}</div>-->
+<!--                <div style="flex: 1"><span :style="item.hours>=2?'color:#ee0a24;font-weight: 500':''">{{item.residualOrderQty}}单</span></div>-->
+<!--              </div>-->
+<!--            </template>-->
+<!--          </van-cell>-->
         </div>
         <div v-else >
           <van-checkbox-group  v-model="checkedResult" @change="checkedResultChange" >
@@ -105,7 +126,7 @@
           </van-checkbox-group>
         </div>
       </div>
-      <div v-if="model.type=='*' && model.province"><van-notice-bar :background="'none'" style="font-size: 12px" :speed="20" :text="model.province" /></div>
+      <div v-if="model.type=='*' && model.province" style="padding: 0 15px"><van-notice-bar :background="'none'" style="font-size: 12px" :speed="20" :text="model.province" /></div>
     </van-dialog>
     <van-dialog v-model:show="createTaskTrueFalseBy"
                 :beforeClose="beforeClose"
@@ -172,10 +193,10 @@ import nodataUrl from '@/assets/nodata.png'
 import { useRoute, useRouter } from 'vue-router'
 import { getTaskList } from '@/views/outbound/picking/task/hooks/task'
 import { computed, defineAsyncComponent, nextTick, ref } from 'vue'
-import { showConfirmDialog, showDialog, showFailToast, showToast } from 'vant'
+import { showConfirmDialog, showDialog, showFailToast, showNotify, showToast } from 'vant'
 import { basicStore } from '@/store/modules/basic'
 import {
-  createPickingTask,
+  createPickingTask, deleteAndClearOrderUid,
   getBigPickingTaskDetail,
   getMixPickActivityOrders,
   getPickingTaskDetail,
@@ -454,6 +475,7 @@ const countRefLength = () => {
     }
   });
 };
+//获取任务
 const onSubCreateTask=(row)=>{
   subModel.value=row
   if(row.residualOrderQty<=60){
@@ -528,7 +550,24 @@ const route = useRoute()
 const onClickLeft = () => {
   goBack()
 };
-
+// 结束攒单
+const remove=(item)=>{
+  showConfirmDialog({
+    title: '温馨提示',
+    message:`您正在结束攒单是否继续`,
+    allowHtml:true,
+    keyboardEnabled:false
+  }).then(() => {
+    showLoading()
+    deleteAndClearOrderUid({ warehouse,uid:item.uid }).then(res => {
+      orderTrueFalseBy.value=false
+      showNotify({ type: 'success', duration: 3000, message: `结束攒单成功,正在刷新数据` })
+      loadData()
+    }).finally(()=>{
+      closeLoading()
+    })
+  }).catch(() => {})
+}
 //查看更多承运人
 const onMore= async (row)=>{
   const carrierArray = row.carrier.split(',').map(code => store.carrierMap[code] || code)

+ 63 - 923
src/views/piece/dashboard/index.vue

@@ -1,959 +1,99 @@
 <template>
-  <van-overlay :show="!isOnline">
-    <div class="wrapper">
-      <!-- 断网提示(固定在顶部) -->
-      <div v-if="!isOnline" class="network-alert offline">
-        ⚠️ 网络已断开,请检查您的网络连接!
-      </div>
-      <!-- 网络恢复提示 -->
-      <Transition name="fade">
-        <div v-if="showReconnected" class="network-alert online">
-          ✅ 网络已恢复
-        </div>
-      </Transition>
+  <div>
+    <button @click="_openCamera">打开相机</button>
+    <button @click="_openGallery">打开相册选择图片并返回路径</button>
+    <button @click="_openImageEditor">打开图片编辑器</button>
+
+    <!-- 显示图片的容器 -->
+    <div v-if="imageUrl" class="image-preview">
+      <img :src="imageUrl" alt="Selected Image" />
     </div>
-  </van-overlay>
-  <van-row class="container">
-    <van-col span="6" class="user-panel">
-      <div class="user-header">
-        <h3>用户信息</h3>
-      </div>
-
-      <div class="info-grid">
-<!--        <div class="info-item">-->
-<!--          <div class="info-label">用户ID</div>-->
-<!--          <div class="info-value">{{ userInfo.userId }}</div>-->
-<!--        </div>-->
-
-        <div class="info-item">
-          <div class="info-label">用户姓名</div>
-          <div class="info-value" :style="{ color: !userInfo.name ? 'red' : '#1e1f1f' }">{{ userInfo.name || '请登录' }}</div>
-        </div>
-
-        <div class="info-item">
-          <div class="info-label">MAC地址</div>
-          <div class="info-value">{{ mac }}</div>
-        </div>
-
-        <div class="info-item">
-          <div class="info-label">仓库</div>
-          <div class="info-value">{{ frogPosition.warehouse }}</div>
-        </div>
-
-        <div class="info-item">
-          <div class="info-label">位置编号</div>
-          <div class="info-value">{{ frogPosition.code }}</div>
-        </div>
-
-        <div class="info-item">
-          <div class="info-label">用途类型</div>
-          <div class="info-value">{{ frogPosition.type }}</div>
-        </div>
-      </div>
-
-      <div class="error-section">
-        <div class="error-display">
-          <div class="error-label" @click="logout">退出登录</div>
-          <!--          <div class="error-count">{{ errNum }}</div>-->
-        </div>
-      </div>
-
-    </van-col>
-    <van-col span="18" class="table-container">
-      <van-cell-group class="custom-cell-group">
-        <van-cell title="系统提示:" :value="message" :value-class="{'success-message': message && message.includes('扫描成功')}"/>
-      </van-cell-group>
-
-      <div class="waterfall-table">
-        <!-- 固定表头 -->
-        <div class="table-header">
-          <div class="col index">序号</div>
-          <div class="col time">时间</div>
-          <div class="col operator">操作人</div>
-          <div class="col content">操作内容</div>
-        </div>
-
-        <van-pull-refresh
-          ref="pullRefreshRef"
-          v-model="refreshing"
-          success-text="刷新成功"
-          @refresh="onRefresh"
-          class="table-body"
-        >
-          <!-- 表格主体 -->
-          <van-list
-            v-model:loading="loading"
-            :finished="finished"
-            finished-text="没有更多了"
-            @load="onLoad">
-            <div
-              v-for="(item, index) in list"
-              :key="item.id"
-              class="table-row"
-            >
-              <div class="col index">{{ index + 1 }}</div>
-              <div class="col time">{{ item.operationTime.replace(/^\d{4}-/, '') }}</div>
-              <div class="col operator">{{ item.operatorName }}</div>
-              <div class="col content">{{ item.deliveryNo }}</div>
-            </div>
-            <van-back-top @click="scrollTop"/>
-          </van-list>
-        </van-pull-refresh>
-      </div>
-    </van-col>
-  </van-row>
-  <van-button class="add" icon="setting-o" type="primary" @click="_openSetting" />
-  <van-sticky>
-    <van-dialog
-      v-model:show="setting.show"
-      title="设置"
-      class="setting-dialog"
-      show-cancel-button
-      :before-close="_verifyMac"
-      @confirm="_saveMac"
-      @closed="_closeSetting"
-      width="380px">
-
-      <van-notice-bar
-        wrapable
-        left-icon="volume-o"
-        :scrollable="false"
-        :text="hintMessage"
-      />
-
-      <van-cell-group inset>
-        <van-field
-          v-model="setting.mac"
-          label="MAC地址"
-          placeholder="请输入MAC地址"
-        />
-        <van-field
-          v-model="setting.password"
-          type="password"
-          label="密码"
-          placeholder="设置密码"
-        />
-      </van-cell-group>
-
-      <van-notice-bar color="#1989fa" background="#ecf9ff" @click="checkUpdate"
-                      style="font-size: 16px; padding-left: 10px" left-icon="info-o">
-        版本号:{{ versionName }} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(点击检查更新)
-      </van-notice-bar>
-    </van-dialog>
-  </van-sticky>
+  </div>
 </template>
 
 <script setup>
-import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
-import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
-import { scanSuccess, scanError } from '@/utils/android'
-import { getVersionName, checkUpdate, saveUserId, getUserId, scanRepeat, saveMacAddress, listErrRecords, reLoginTip,
-   readMacAddress, pageDelivery, addDelivery, isDeliveryNoExists, markAsPushed, getErrRecordsCount, removeUserId, scanErr } from '@/utils/androidPiece'
-import { showLoadingToast, showNotify } from 'vant'
-import { getUserIdByCert, getUserNameById } from '@/api/login/index'
-import { receive, getScanDriverInfo } from '@/api/scan/index'
-import { formatDateTime } from '@/utils/date'
-
-// 在输入对话框或界面中添加提示文本
-const hintMessage = ref(`请前往:设置 → 关于平板电脑 → 设备WLAN MAC 地址 长按复制并粘贴到此处。`);
-const mac = ref('')
-const userInfo = ref({
-  userId: '',
-  name: ''
-})
-const setting = ref({
-  show: false,
-  mac: '',
-  password: ''
-})
-const message = ref('')
-const list = ref([])
-const loading = ref(false)
-const finished = ref(false)
-const errNum = ref(0)
-const preDeliveryNo = ref('')
+import { ref } from 'vue'
 
-// 小青蛙位置属性
-const frogPosition = ref({
-  warehouse: '',
-  type:'',
-  code:''
-})
+const imagePath = ref('')
+const imageUrl = ref('')
 
-
-const isOnline = ref(navigator.onLine)
-// 更新网络状态
-const updateNetworkStatus = () => {
-  isOnline.value = navigator.onLine
-}
-
-// 退出登录
-const logout = () => {
-  if (!userInfo.value.userId) {
-    scanError()
-    showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '请先登录!' });
+const _openCamera = () => {
+  if (!window.android) {
+    alert('Android 接口不可用')
     return
   }
-  userInfo.value = {}
-  removeUserId()
-  scanSuccess()
-  showNotify({ type: 'success', style: 'font-size: 30px !important;height:50px', message: '已退出登录!' });
-}
-
-// 网络提示
-const showReconnected = ref(false)
-let reconnectTimer = null
-watch(isOnline, (newVal, oldVal) => {
-  // 当网络从离线变为在线时
-  if (newVal && !oldVal) {
-    scanSuccess()
-    showReconnected.value = true
-
-    // 3秒后自动隐藏恢复提示
-    reconnectTimer = setTimeout(() => {
-      showReconnected.value = false
-    }, 3000)
-  }
-
-  // 当网络从在线变为离线时,隐藏恢复提示
-  if (!newVal && oldVal) {
-    scanError()
-    showReconnected.value = false
-  }
-})
-
-onUnmounted(() => {
-  window.removeEventListener('online', updateNetworkStatus)
-  window.removeEventListener('offline', updateNetworkStatus)
-  closeListener()
-})
-
-onMounted(() => {
-  window.addEventListener('online', updateNetworkStatus)
-  window.addEventListener('offline', updateNetworkStatus)
-
-  versionName.value = getVersionName()
-  mac.value = readMacAddress()
-  // 获取用户信息
-  if (!userInfo.value.userId) {
-    const userId = getUserId()
-    if (userId) {
-      userInfo.value.userId = userId
-      getUserName(userId)
-    }
-  }
-  // 获取机器位置信息
-  if (!frogPosition.value.warehouse && mac.value) {
-    getFrogPosition()
-  }
-
-  if (mac.value) {
-    _openScan()
-  } else {
-    _openSetting()
-  }
-  // ip
-  // fetchIP()
-  // err数量
-  errNum.value = getErrNum()
-})
-
-const getFrogPosition = () => {
-  getScanDriverInfo(mac.value).then(res => {
-    if (res && res.data) {
-      frogPosition.value.warehouse = res.data.warehouseCode
-      frogPosition.value.type = res.data.type
-      frogPosition.value.code = res.data.code
-    }
-  })
-}
-
-const _openScan = () => {
-  openListener()
-  setTimeout(() => {
-    scanInit(debounceScan)
-  },300)
-}
-
-const pushing = ref(false)
-const pullRefreshRef = ref(null)
-const scanDebounceTimeout = ref(null)
-
-const debounceScan = (code) => {
-  // 清除之前的防抖计时器
-  if (scanDebounceTimeout.value) {
-    clearTimeout(scanDebounceTimeout.value)
-  }
-
-  // 设置新的防抖计时器
-  scanDebounceTimeout.value = setTimeout(() => {
-    _handlerScan(code)
-  }, 300) // 300ms 防抖时间
-}
-const _handlerScan = (code) => {
-  code = fixDuplicateText(code)
-  // 校验扫描的是否为登录二维码
-  const regex = /\{"sign":"([^"]*)",\s*"identity":(\d+)\}/;
-  if (code !== null && regex.test(code)) {
-    const match = regex.exec(code)
-    const sign = match[1]
-    const identity = match[2]
-    getUserIdByCert({sign: sign, identity: identity}).then(res => {
-      if (res && res.data) {
-        userInfo.value.userId = res.data
-        getUserName(res.data)
-      } else {
-        showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '登录失败!' });
-      }
-    })
-  } else {
-    // 检查长度是否在11到25个字符之间
-    if (typeof code !== 'string' || code.length < 11 || code.length > 25) {
-      console.log(code)
-      scanErr()
-      showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '请扫描正确的快递单号!' });
-      return
-    }
-    // 数字0-9,大写字母A-Z,部分特殊字符(空格、! " % & ' ( ) * + , - . / : ; < = > ? _)
-    const code128BPattern = /^[\x20-\x7F]+$/;
-    // 检查是否只包含有效字符
-    if (!code128BPattern.test(code)) {
-      console.log(code)
-      scanErr()
-      showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '请扫描正确的快递单号!' });
-      return
-    }
-    scrollTop()
-    // 校验是否已登录
-    if (!userInfo.value.userId) {
-      scanError()
-      showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '请先登录!' });
-      return
-    }
-    // 校验是否为空
-    if (!code) {
-      scanErr()
-      showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '请先扫描快递单号!' });
-      return
-    }
-
-    if (pushing.value) {
-      scanErr()
-      showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '正在提交中,请稍后扫描!' });
-      return
-    }
-    pushing.value =  true  // 请求状态锁
-
-    // 校验是否已扫描
-    if (list.value.some(item => item.code === code)) {
-      scanRepeat()
-      pushing.value = false
-      message.value = '该快递单已扫描!'
-      showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '该快递单已扫描!' });
-      return
-    }
-
-    const exists = isDeliveryNoExists(code)
-    if (!exists) {
-      scanRepeat()
-      pushing.value = false
-      message.value = '该快递单已扫描!'
-      showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '该快递单已扫描!' });
-      return
-    }
-
-    const toast = showLoadingToast({
-      message: '上传中...',
-      forbidClick: true,
-      duration: 0 // 禁止自动关闭
-    });
-    // 校验是否已存在
-    const currentTime = formatDateTime(new Date())
-    message.value = ''
-    const dto = {
-      deliveryNo: code,
-      machine: mac.value,
-      operator: userInfo.value.userId,
-      operationTime: currentTime,
-      operatorName: userInfo.value.name,
-      isPush: 1,
-      preDeliveryNo: preDeliveryNo.value,
-      version: versionName.value }
-
-    receive(dto).then(res => {
-      if (res && res.code === 200) {
-        const result = addDelivery(mac.value, code, userInfo.value.userId, userInfo.value.name)
-        if (result === -1) {
-          scanRepeat()
-          message.value = '该快递单已扫描!'
-          showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '该快递单已扫描!' });
-          return
-        }
-        if (result === -2) {
-          scanErr()
-          showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '添加失败,请联系开发人员!' });
-          return
-        }
-        preDeliveryNo.value = code
-        scanSuccess()
-        message.value = code + '扫描成功!'
-        list.value.unshift(dto)
-        showNotify({ type: 'success', style: 'font-size: 30px !important;height:50px', message: '扫描成功!' });
-      }
-    }).catch(err => {
-      if (err.code === 700) {
-        userInfo.value = {}
-        reLoginTip()
-      } else {
-        scanErr()
-      }
-      showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: `扫描失败!${err.message}` });
-    }).finally(() => {
-      toast.close();
-      pushing.value = false
-    })
-  }
-}
-
-const scrollTop = () => {
-  // 添加滚动到顶部的逻辑
-  nextTick(() => {
-    const el = pullRefreshRef.value?.$el;
-    if (el && el.scrollTo) {
-      el.scrollTo({ top: 0, behavior: 'smooth' });
-    } else if (el) {
-      // 回退方案
-      el.scrollTop = 0;
-    }
-  })
+  window.android.openCamera()
 }
-function fixDuplicateText(input) {
-  // 改进版正则表达式(支持任意字符且精确匹配完全重复)
-  const regex = /^(.+?)\1$/; // 非贪婪模式防止误匹配
-  const match = input.match(regex);
-  // 返回处理逻辑
-  return match ? match[1] : input;
-}
-
-
-// 在外部定义一个防抖函数映射表,用于跟踪每个单据的防抖状态
-const debounceMap = new Map();
-
-// 防抖函数封装
-const debounce = (func, wait = 500) => {
-  let timer;
-  return (...args) => {
-    clearTimeout(timer);
-    timer = setTimeout(() => {
-      func.apply(this, args);
-    }, wait);
-  };
-};
-
-// 修改后的推送函数
-const _rePush = (item) => {
-  if (!item || item.isPush === 1) return;
-  let status = false
-  showLoadingToast({
-    message: '上传中...',
-    forbidClick: true,
-    closeToast: status
-  });
-  // 初始化当前单据的防抖状态
-  if (!debounceMap.has(item.deliveryNo)) {
-    debounceMap.set(item.deliveryNo, {
-      isPushing: false,  // 请求状态锁
-      debouncedFn: debounce(() => {
-        const entry = debounceMap.get(item.deliveryNo);
-        if (!entry || entry.isPushing) return;
-
-        entry.isPushing = true;  // 加锁
-
-        const dto = {
-          deliveryNo: item.deliveryNo,
-          machine: item.machine,
-          operator: item.operator,
-          operationTime: item.operationTime
-        };
-
-        receive(dto).then(res => {
-          const result = markAsPushed(item.deliveryNo);
-          entry.isPushing = false;  // 请求完成解锁
-          item.isPush = 1;
-          scanSuccess();
-          status = true
-          errNum.value -= 1
-          showNotify({
-            type: 'success',
-            style: 'font-size: 30px !important;height:50px',
-            message: '推送成功!'
-          });
-        }).catch(err => {
-          entry.isPushing = false;  // 请求失败解锁
-          scanError();
-          status = true
-          showNotify({
-            type: 'danger',
-            style: 'font-size: 30px !important;height:50px',
-            message: `推单失败!${err.message}`
-          });
-        });
-      }, 500)  // 500ms防抖时间
-    });
-  }
-
-  // 执行防抖函数
-  const { debouncedFn } = debounceMap.get(item.deliveryNo);
-  debouncedFn();
-};
-
-const getUserName = (userId) => {
-  getUserNameById(userId, mac.value).then(res => {
-    if (res && res.data) {
-      saveUserId(userInfo.value.userId)
-      userInfo.value.name = res.data
-      scanSuccess()
-      showNotify({ type: 'success', style: 'font-size: 30px !important;height:50px', message: '登录成功!' });
-    }
-  })
-}
-
-const versionName = ref('')
-const _openSetting = () => {
-  closeListener()
-  setting.value.show = true
-  versionName.value = getVersionName()
-}
-
 
-// 校验mac地址是否已配置
-const _verifyMac = (done) => {
-  if (!mac.value) {
-    setting.value.show = true
-    return false
+const _openGallery = () => {
+  if (!window.android) {
+    alert('Android 接口不可用')
+    return
   }
-  return true
-}
-
-// 关闭设置
-const _closeSetting = () => {
-  _openScan()
+  window.android.openGallery()
 }
 
-const _saveMac = () => {
-  if(!isValidMacAddress(setting.value.mac)) {
-    showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '请输入正确的MAC地址!' });
-    return
-  }
-  if (!isValidPassword(setting.value.password)) {
-    showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '请输入正确的密码!' });
+const _openImageEditor = () => {
+  if (!window.android) {
+    alert('Android 接口不可用')
     return
   }
-  if(!saveMacAddress(setting.value.mac)) {
-    showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: 'MAC保存失败!' });
+  if (!imagePath.value) {
+    alert('请先选择图片')
     return
   }
-  showNotify({ type: 'success', style: 'font-size: 30px !important;height:50px', message: 'MAC保存成功!' });
-  mac.value = setting.value.mac
-  getFrogPosition()
-}
-
-function isValidMacAddress(mac) {
-  const regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
-  return mac !== null && regex.test(mac);
-}
-function isValidPassword(password) {
-  return password === '123456';
+  window.android.openImageEditor(imagePath.value)
 }
 
-// 网络状态和ip
-const ipAddress = ref(null)
-
-// 获取 IP 地址
-const fetchIP = async () => {
-  const RTCPeerConnection = window.RTCPeerConnection;
-  if (RTCPeerConnection) {
-    const pc = new RTCPeerConnection({ iceServers: [] });
-    pc.createDataChannel('');
-    pc.createOffer()
-      .then(sdp => pc.setLocalDescription(sdp))
-      .catch(console.error);
-    pc.onicecandidate = e => {
-      if (!e.candidate) return;
-      const ip = /([0-9]{1,3}(\.[0-9]{1,3}){3})/.exec(e.candidate.candidate)?.[1];
-      if (ip) ipAddress.value = ip;
-    };
+// 这个函数应该由 Android 调用
+window.handleImageResult = (path) => {
+  imagePath.value = path
+  // 如果是文件路径,直接使用
+  if (path.startsWith('file://') || path.startsWith('/storage')) {
+    imageUrl.value = path
+  }
+  // 如果是 base64,直接使用
+  else if (path.startsWith('data:image')) {
+    imageUrl.value = path
   }
 }
 
-const getErrNum = () => {
-  return getErrRecordsCount()
+// 处理 Blob 数据
+window.handleImageBlob = (base64) => {
+  const blob = base64ToBlob(base64, 'image/jpeg')
+  imageUrl.value = URL.createObjectURL(blob)
 }
 
-const page = ref(1)
-const size = ref(10)
-// 模拟数据加载
-const onLoad = () => {
-  if (refreshing.value) {
-    list.value = [];
-    refreshing.value = false;
-  }
-  const result = pageDelivery(page.value, size.value)
-  if (result.size < size.value) {
-    finished.value = true;
-  } else {
-    page.value += 1
-  }
-  list.value.push(...result)
-  loading.value = false;
-};
-const refreshing = ref(false);
-const onRefresh = () => {
-  // 清空列表数据
-  finished.value = false;
-
-  // 重新加载数据
-  // 将 loading 设置为 true,表示处于加载状态
-  loading.value = true;
-  page.value = 1
-  onLoad();
-};
-const isOnePush = ref(false)
-const _one_click_push = () => {
-  isOnePush.value = true
-  const result = listErrRecords()
-  if (result && result != '[]') {
-    const records = JSON.parse(result);
-    const promises = []; // 收集所有请求的Promise
-
-    records.forEach(item => {
-      // 初始化当前单据的防抖状态
-      if (!debounceMap.has(item.deliveryNo)) {
-        const debounceEntry = {
-          isPushing: false,
-          debouncedFn: debounce(() => {
-            const entry = debounceMap.get(item.deliveryNo);
-            if (!entry || entry.isPushing) return;
-
-            entry.isPushing = true;  // 加锁
-
-            const dto = {
-              deliveryNo: item.deliveryNo,
-              machine: item.machine,
-              operator: item.operator,
-              operationTime: item.operationTime
-            };
-
-            // 将请求包装成Promise并收集
-            const promise = receive(dto)
-              .then(res => {
-                const result = markAsPushed(item.deliveryNo);
-                list.value.filter(entry => entry.deliveryNo === item.deliveryNo).forEach(entry => {
-                  entry.isPush = 1;
-                })
-                errNum.value -= 1
-              })
-              .finally(() => {
-                entry.isPushing = false; // 无论成功失败都解锁
-              });
-
-            promises.push(promise); // 添加到Promise数组
-            return promise;
-          }, 500)
-        };
-        debounceMap.set(item.deliveryNo, debounceEntry);
-      }
-
-      // 立即执行防抖函数并收集Promise
-      const promise = debounceMap.get(item.deliveryNo).debouncedFn();
-      if (promise) promises.push(promise);
-    });
-
-    // 所有请求完成后重置状态
-    Promise.allSettled(promises)
-      .then(_ => {
-        scanSuccess();
-        showNotify({ type: 'success', style: 'font-size: 30px !important;height:50px', message: '数据已推送!' });
-      })
-      .catch(_ => {
-        scanError();
-        showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '存在数据推送失败!' });
-      }).finally(() => {
-      isOnePush.value = false;
-    });
-  } else {
-    showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '暂无异常数据!' });
-    isOnePush.value = false; // 没有数据时立即重置
+// 将 base64 转换为 Blob
+const base64ToBlob = (base64, mimeType) => {
+  const byteCharacters = atob(base64.split(',')[1])
+  const byteNumbers = new Array(byteCharacters.length)
+  for (let i = 0; i < byteCharacters.length; i++) {
+    byteNumbers[i] = byteCharacters.charCodeAt(i)
   }
+  const byteArray = new Uint8Array(byteNumbers)
+  return new Blob([byteArray], { type: mimeType })
 }
 </script>
 
 <style scoped lang="sass">
-*
-  margin: 0
-  padding: 0
-  box-sizing: border-box
-
-
-body
-  font-family: "PingFang SC", "Microsoft YaHei", sans-serif
-  background: #f5f7fa
-  color: #333
-  overflow: hidden
-  height: 100vh
-
-.container
-  height: 100vh
-  display: flex
-  overflow: hidden
-  font-size: large
-  background: #fff
-
-span
-  font-size: 7px
-
-.table-container
-  display: flex
-  flex-direction: column
-  height: 100%
-  overflow: hidden
-  background: #fff
-
-.waterfall-table
-  position: relative
-  flex: 1
-  display: flex
-  flex-direction: column
-  overflow: hidden
-  height: 100%
-
-.table-header
-  font-size: 8px
-  display: flex
-  background: #f2f3f5
-  font-weight: bold
-  z-index: 0
-  box-shadow: 0 2px 4px rgba(0,0,0,0.05)
-  flex-shrink: 0  // 防止表头被压缩
-
-.table-body
-  flex: 1
-  overflow-y: auto
-  position: relative
-  padding-top: 0  // 移除之前的padding-top
-
-.table-row
-  font-size: 10px
-  height: 21px
-  display: flex
-  box-sizing: border-box
-
-.col
-  padding: 8px 4px
-  overflow: hidden
-  text-overflow: ellipsis
-  box-sizing: border-box
-
-/* 列宽设置 - 确保表头和表体使用相同的宽度 */
-.index
-  flex: 0 0 30px
-  width: 30px
-  text-align: center
-
-.time
-  flex: 0 0 75px
-  width: 75px
-
-.operator
-  flex: 0 0 55px
-  width: 55px
-
-.content
-  flex: 1
-  min-width: 0 /* 允许内容换行 */
-  white-space: normal /* 允许换行 */
-
-.status
-  flex: 0 0 40px
-  width: 40px
-
-/* 确保表体中的单元格与表头对齐 */
-:deep(.van-list)
-  .van-cell
-    padding: 0
-    width: 100%
-    box-sizing: border-box
-  .van-cell__title, .van-cell__value
-    flex: none
-
-.add
-  position: fixed
-  bottom: 10px
-  left: 10px
-  width: 20px
-  height: 20px
-  border-radius: 50%
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15)
-
-:deep(.custom-cell-group)
-  height: auto  // 移除固定高度
-  display: flex
-  align-items: center
-  min-height: 24px  // 确保有足够高度
-  .van-cell
-    display: flex
-    align-items: center
-    height: 100%
-  .van-cell__title
-    flex: 0 0 30px !important
-    font-size: 10px
-    line-height: 24px
-    max-width: 70px
-    min-width: 70px
-  .van-cell__value
-    flex: 1
-    line-height: 24px
-    color: red
-    font-size: 13px
-    text-align: left
-  .success-message
-    font-size: 12px
-    color: #2ed573 !important
-
-.van-notice-bar
-  background-color: #f8f9fa
-  margin: 5px 0 5px 0
-  border: 1px solid #eaeaea
-
-.van-field
-  padding: 5px 16px
-  transition: border-color 0.3s
-
-:deep(.custom-notify)
-  font-size: 100px
-
-.network-alert
-  position: fixed
-  top: 0
-  left: 0
-  right: 0
-  padding: 15px
-  text-align: center
-  font-weight: bold
-  z-index: 9999
-  animation: slideIn 0.3s ease-out
-
-
-@keyframes slideIn
-  from
-    opacity: 0
-    transform: translateY(-100%)
-  to
-    opacity: 1
-    transform: translateY(0)
-
-.network-alert.offline
-  background-color: #ff4757
-  color: white
-
-.network-alert.online
-  background-color: #2ed573
+button
+  margin: 10px
+  padding: 8px 16px
+  background: #42b983
   color: white
-  top: 0px
-
-.fade-leave-active
-  transition: opacity 0.5s ease
-
-.fade-leave-to
-  opacity: 0
-
-/* 左侧用户信息面板样式优化 */
-.user-panel
-  background-color: #f5f7fa
-  padding: 10px 10px
-  height: 100%
-  display: flex
-  flex-direction: column
-
-.user-header
-  text-align: center
-  margin-bottom: 6px
-  position: relative
-  padding-bottom: 6px
-
-.user-header h3
-  color: #3498db
-  font-weight: 500
-  font-size: 10px
-
-.user-header::after
-  content: ''
-  position: absolute
-  bottom: 0
-  left: 0
-  right: 0
-  height: 2px
-  background: linear-gradient(to right, transparent, #3498db, transparent)
-
-.info-grid
-  display: grid
-  grid-template-columns: 1fr
-  padding-bottom: 5px
-  gap: 0px
-  flex: 1
-
-.info-item
-  display: flex
-  flex-direction: column
-
-.info-label
-  font-size: 7px
-  color: #7f8c8d
-  margin-bottom: 1px
-  display: flex
-  align-items: center
-
-.info-label::before
-  content: ''
-  display: inline-block
-  width: 3px
-  height: 10px
-  background-color: #3498db
-  margin-right: 3px
-  border-radius: 2px
-
-.info-value
-  font-size: 8px
-  font-weight: 400
-  color: #1e1f1f
-  padding-left: 8px
-
-.error-section
-  padding-bottom: 25px
+  border: none
+  border-radius: 4px
+  cursor: pointer
 
-.error-display
-  background: #fff5f5
-  padding: 3px 4px
-  border-radius: 3px
-  box-shadow: 0 2px 8px rgba(0,0,0,0.1)
 
-.error-label
-  font-size: 8px
-  color: #778181
+.image-preview
+  margin-top: 20px
+  img
+    max-width: 100%
+    max-height: 400px
+    border: 1px solid #ddd
+    border-radius: 4px
 
-.error-count
-  font-size: 10px
-  font-weight: bold
-  color: #ff4757
 
-.retry-btn
-  width: 45px
-  height: 18px
-  font-size: 7px
-  font-weight: bold
-  border-radius: 4px
-  box-shadow: 0 4px 10px rgba(231, 76, 60, 0.3)
-  transition: all 0.3s
-  margin-top: 5px
-  margin-left: 28px
 </style>

+ 181 - 0
src/views/processing/register/components/addRegisterType.vue

@@ -0,0 +1,181 @@
+<template>
+  <div class="container-no-container">
+    <van-dialog v-model:show="typeTrueFalseBy"
+                :beforeClose="beforeClose"
+                title="登记类型/数量"
+                show-cancel-button>
+      <van-field name="checkboxGroup" label="加工类型" required label-width="70px">
+        <template #input>
+          <van-checkbox-group v-model="type" direction="horizontal">
+            <div v-for="(item,index) in props.processingTypeList" :key="item.code" style="margin-bottom: 8px">
+              <van-checkbox :name="item.code" shape="square"
+                            v-if=" item.code !== 'PROFESSIONAL' && item.code !== 'DEEP' &&item.code !== 'FINE'"
+              >{{ item.name }}
+              </van-checkbox>
+            </div>
+          </van-checkbox-group>
+        </template>
+      </van-field>
+      <van-field name="radio" label="质检类型" label-width="70px" :required="type.includes('ALL_CHECK')">
+        <template #input>
+          <van-radio-group v-model="checkType" direction="horizontal" label-width="70px">
+            <div v-for="(item,index) in props.processingTypeList" :key="item.code" style="margin-bottom: 8px">
+              <van-radio v-if="item.code == 'PROFESSIONAL' || item.code == 'DEEP' || item.code == 'FINE'"
+                         :name="item.code">{{ item.name }}
+              </van-radio>
+            </div>
+          </van-radio-group>
+        </template>
+      </van-field>
+      <van-field v-model="count" type="digit" ref="countRef" @keydown.enter="onKeydown" clearable autocomplete="off"
+                 label="加工数量" placeholder="请输入加工数量" required />
+    </van-dialog>
+  </div>
+</template>
+<script setup lang="ts">
+import { ref } from 'vue'
+import { showToast } from 'vant'
+import { scanError } from '@/utils/android'
+const typeTrueFalseBy = ref(false)
+const type = ref([])
+const count = ref('')
+const countRef = ref(null)
+const checkType = ref('')
+// 接收父组件传递的数据
+const props = defineProps({
+  processingTypeList: Array
+})
+const typeList=ref([])
+const show = async (list) => {
+  typeTrueFalseBy.value = true
+  type.value = []
+  count.value = ''
+  checkType.value = ''
+  typeList.value=list
+}
+//输入拣货容器号
+const emit = defineEmits(['setProcessingType'])
+const validateData = () => {
+  // 验证加工类型
+  if (type.value.length === 0) {
+    showToast('请先选择加工类型')
+    scanError()
+    return false
+  }
+  // 验证质检类型
+  if (type.value.includes('ALL_CHECK') && !checkType.value) {
+    showToast('选择的加工类型包含质检,请选择质检类型')
+    scanError()
+    return false
+  }
+  // 验证加工数量
+  if (count.value <= 0) {
+    showToast('请输入加工数量')
+    scanError()
+    countRef.value?.focus()
+    return false
+  }
+  return true
+}
+const getType=()=>{
+  let operate = [...type.value]
+  operate = operate.filter(item => item !== 'ALL_CHECK')
+  if (checkType.value) {
+    operate.push(checkType.value)
+  }
+  return  { operate, expectAmount: count.value }
+}
+const beforeClose = (action) => new Promise(async (resolve) => {
+  if (action === 'confirm') {
+    if (!validateData()) {
+      return resolve(false)
+    }
+    const data=getType()
+    const isExist = typeList.value.some(item =>
+      item.operate.length === data.operate.length &&
+      item.operate.every(op => data.operate.includes(op)) &&
+      data.operate.every(op => item.operate.includes(op))
+    );
+    if(isExist){
+      showToast('选择的加工类型已存在,请检查')
+      scanError()
+      return resolve(false)
+    }
+    emit('setProcessingType', data)
+    resolve(true)
+  } else {
+    resolve(true)
+  }
+})
+const onKeydown = () => {
+  if (!validateData()) {
+    return
+  }
+  const data=getType()
+  const isExist = typeList.value.some(item =>
+    item.operate.length === data.operate.length && // 长度必须相同
+    item.operate.every(op => data.operate.includes(op)) && // 所有元素都在 data.operate 中
+    data.operate.every(op => item.operate.includes(op)) // 所有元素都在 item.operate 中
+  );
+  if(isExist){
+    showToast('选择的加工类型已存在,请检查')
+    scanError()
+    return
+  }
+  typeTrueFalseBy.value = false
+  emit('setProcessingType', data)
+}
+
+defineExpose({ show })
+</script>
+<style scoped lang="sass">
+.container-no-container
+  .code-input
+    font-size: 22px
+    font-weight: bold
+    border-bottom: 2px solid #0077ff
+    margin-top: 10px
+
+  .completion
+    text-align: right
+    font-size: 12px
+    padding: 5px 20px
+    cursor: pointer
+    text-decoration: underline
+
+  .container-list
+    max-height: 100px
+    font-size: 13px
+    font-weight: bold
+    display: flex
+    padding: 5px 20px
+    justify-content: space-between
+    flex-wrap: wrap
+    overflow: auto
+
+    .container-item
+      width: 50%
+      display: flex
+      justify-items: center
+      align-items: center
+      padding: 2px 0
+      cursor: pointer
+      text-decoration: underline
+
+      .container-item-line
+        width: 5px
+        height: 5px
+        background: #0077ff
+        border-radius: 50%
+        margin-right: 5px
+
+      .container-item-no
+        flex: 1
+        text-align: left
+
+.tips
+  font-size: 14px
+  color: #ff4141
+  line-height: 20px
+
+</style>

+ 204 - 0
src/views/processing/register/index.vue

@@ -0,0 +1,204 @@
+<template>
+  <div class="register">
+    <van-nav-bar
+      title="加工建单" left-arrow fixed placeholder @click-left="goBack" @click-right="onConfirm()">
+      <template #left>
+        <van-icon name="arrow-left" size="25" />
+        <div style="color: #fff">返回</div>
+      </template>
+      <template #right>
+        <div style="color: #fff">提交</div>
+      </template>
+    </van-nav-bar>
+    <van-field v-model="date" type="text" label="登记日期" placeholder="请选择日期"
+               readonly is-link input-align="right" @click="dateTrueFalseBy=true" required
+    />
+    <van-field v-model="ownerCode" type="text" label="登记货主" placeholder="请选择货主" readonly
+               is-link @click="ownerRef.show(1)" input-align="right" required
+    />
+    <van-field v-model="code" type="textarea" label="单据信息" placeholder="请填写快递单号/收货任务号,多个用逗号/空格/换行符进行分割"
+               show-word-limit maxlength="500" rows="2" autosize required
+    />
+    <van-field v-model="note" type="textarea" label="&nbsp;&nbsp;备注信息" placeholder="请输入备注信息"
+               show-word-limit  maxlength="500" rows="1" autosize
+    />
+    <div class="register-content">
+      <div class="add-type">
+        <van-button type="primary" round size="small" @click="addType">添加类型</van-button>
+      </div>
+      <div class="type-box">
+        <div class="type-title">类型表一览</div>
+        <div class="type-list">
+          <div class="type-item">
+            <div class="type-item-item">类型</div>
+            <div class="type-item-operate">数量</div>
+            <div class="type-item-operate">操作</div>
+          </div>
+          <div  class="type-item" v-for="(item,index) in typeList" v-if="typeList.length>0">
+            <div  class="type-item-item">{{getOperate(item.operate)}}</div>
+            <div class="type-item-operate">{{item.expectAmount}}</div>
+            <div class="type-item-delete" @click="del(item,index)" >删除</div>
+          </div>
+          <div v-else>
+            <van-empty :image="<string>nodataUrl" image-size="140" >
+              <van-button round type="primary" class="bottom-button" size="small" @click="addType">添加</van-button>
+            </van-empty>
+          </div>
+        </div>
+      </div>
+    </div>
+    <owner ref="ownerRef" @onOwner="onOwner" />
+    <van-calendar first-day-of-week="1" v-model:show="dateTrueFalseBy" @confirm="dateConfirm" />
+    <add-register-type ref="addRegisterTypeRef"
+                       :processingTypeList="processingTypeList"
+                       @setProcessingType="setProcessingType"  />
+  </div>
+</template>
+<script setup lang="ts">
+import { ref } from 'vue'
+import { getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
+import { showConfirmDialog, showNotify, showToast } from 'vant'
+import { useStore } from '@/store/modules/user'
+import { closeLoading, showLoading } from '@/utils/loading'
+import Owner from '@/components/Owner.vue'
+import { formatDate } from '@/utils/date'
+import { useRouter } from 'vue-router'
+import nodataUrl from '@/assets/nodata.png'
+import AddRegisterType from '@/views/processing/register/components/addRegisterType.vue'
+import { createProcessing, getProcessingTypeList } from '@/api/processing'
+import { toArray, toMap } from '@/utils/dataType'
+const router = useRouter()
+try {
+  getHeader()
+}catch (error) {
+  router.push('/login')
+}
+const store = useStore()
+const warehouse = store.warehouse
+const ownerRef = ref(null)
+const ownerItem = ref({})
+const ownerCode = ref('')
+const date = ref(formatDate(new Date()))
+const note = ref('')
+const code=ref('')
+const processingTypeList=ref([])
+const processingTypeMap=ref({})
+//添加类型
+const addRegisterTypeRef=ref(null)
+const typeList = ref([])
+const dateTrueFalseBy = ref(false)
+//选择货主
+const onOwner = (item) => {
+  ownerItem.value = item
+  ownerCode.value = item.name
+}
+//选择日期
+const dateConfirm = (e) => {
+  date.value = formatDate(e)
+  dateTrueFalseBy.value = false
+}
+//提交登记
+const onConfirm = () => {
+  if(!ownerCode.value){
+    showNotify({ type: 'warning', duration: 3000, message: '请选择货主' })
+    scanError()
+    return
+  }
+  if(typeList.value.length==0){
+    showNotify({ type: 'warning', duration: 3000, message: '请先添加单据类型' })
+    scanError()
+    return
+  }
+  if(!code.value){
+    showNotify({ type: 'warning', duration: 3000, message: '请先添加单据信息' })
+    scanError()
+    return
+  }
+  showConfirmDialog({
+    title: '温馨提示', message:'是否确认建单',keyboardEnabled:false })
+    .then(() => {
+    _createProcessing()
+    }).catch(() => {})
+}
+const _createProcessing=()=>{
+  const data={owner:ownerItem.value.id,ownerCode:ownerItem.value.code,note:note.value,warehouse,typeList:typeList.value,noList:toArray(code.value)}
+  showLoading()
+  createProcessing(data).then(res=>{
+    closeLoading()
+    scanSuccess()
+    showNotify({ type: 'success', duration: 3000, message: '建单成功' })
+    reset()
+  }).catch(err=>{
+    scanError()
+    closeLoading()
+  })
+}
+const reset=()=>{
+  code.value=""
+  typeList.value=[]
+  note.value=""
+  ownerCode.value=''
+}
+//添加类型
+const addType=()=>{
+  addRegisterTypeRef.value?.show(typeList.value)
+}
+const setProcessingType=(data)=>{
+  typeList.value.push(data)
+}
+//转换类型名称
+const getOperate=(operate)=>{
+  const result = operate.map(item => processingTypeMap.value[item] || item)
+  return result.join(',')
+}
+//获取类型
+const _getProcessingTypeList=()=>{
+  getProcessingTypeList().then(res=>{
+    processingTypeList.value=res.data
+    processingTypeMap.value=toMap(res.data,'code','name')
+  })
+}
+const del=(item,index)=>{
+  typeList.value.splice(index,1)
+}
+_getProcessingTypeList()
+</script>
+<style scoped lang="sass">
+.register
+  .register-content
+    background: #fff
+    padding: 10px
+    font-size: 14px
+    .add-type
+      text-align: right
+    .type-box
+      text-align: left
+      .type-title
+        line-height: 30px
+        color: #999
+      .type-list
+        border: 1px solid #e9e9e9
+        border-radius: 6px
+        padding: 5px 10px
+        .type-item
+          display: flex
+          align-items: center
+          border-bottom: 1px solid #e9e9e9
+
+          padding: 3px 0
+          .type-item-item
+            flex: 1
+          .type-item-operate
+            width: 70px
+            text-align: center
+          .type-item-delete
+            width: 70px
+            text-align: center
+            color: #ff4141
+            text-decoration: underline
+        .type-item:last-child
+          border-bottom: none
+
+
+
+</style>