yangg 3 viikkoa sitten
vanhempi
sitoutus
53197f69eb

+ 2 - 2
.env.development

@@ -5,8 +5,8 @@ ENV = 'development'
 #线下:http://192.168.100.187:8083
 # 本地环境接口地址 121.36.251.245
 
-#VITE_API_URL = 'http://192.168.100.121:8086'
-VITE_API_URL = 'http://1.94.168.85:8086'
+VITE_API_URL = 'http://192.168.100.121:8086'
+#VITE_API_URL = 'http://1.94.168.85:8086'
 # VITE_API_URL = 'https://backend.qicai321.com'
 VITE_API_WX_URL='https://api.weixin.qq.com/'
 VITE_API_URL_MINLONG='http://117.185.80.170:7861'

+ 337 - 50
src/views/system/Inventorycount/InventoryDialog/index.vue

@@ -5,22 +5,29 @@
           <el-input ref="deviceCodeInputRef" v-model="device_code" placeholder="设备编号(支持扫码枪/RFID批量录入,回车添加)" @keyup.enter="handleAddToBatch" :disabled="loading" />
           <el-input v-model="check_no" placeholder="盘点编号" @keyup.enter="handleAddToBatch" :disabled="true" />
           <el-button type="success" @click="handleAddToBatch" :disabled="loading">添加到待提交列表</el-button>
-          <el-button type="primary" @click="handleBatchSubmit" :loading="loading" :disabled="pendingDeviceCodes.length === 0">批量提交({{ pendingDeviceCodes.length }})</el-button>
+          <el-button type="info" :icon="Setting" @click="showReadRuleDialog = true" :disabled="loading">读取规则</el-button>
         </div>
 
         <!-- 待提交设备编号列表 -->
         <div v-if="pendingDeviceCodes.length > 0" class="pending-list" style="margin-top: 15px; margin-bottom: 15px;">
-          <div class="pending-title">待提交设备编号 ({{ pendingDeviceCodes.length }}个):</div>
-          <el-tag 
-            v-for="(code, index) in pendingDeviceCodes" 
-            :key="index" 
-            closable 
-            @close="removePendingCode(index)"
-            style="margin-right: 8px; margin-bottom: 8px;"
-          >
-            {{ code }}
-          </el-tag>
-          <el-button type="text" size="small" @click="clearPendingCodes" style="margin-left: 10px;">清空</el-button>
+          <div class="pending-header">
+            <div class="pending-title">待提交设备编号 ({{ pendingDeviceCodes.length }}个):</div>
+            <div class="pending-actions">
+              <el-button type="primary" @click="handleBatchSubmit" :loading="loading" size="small">批量提交({{ pendingDeviceCodes.length }})</el-button>
+              <el-button type="text" size="small" @click="clearPendingCodes">清空</el-button>
+            </div>
+          </div>
+          <div class="pending-tags">
+            <el-tag 
+              v-for="(code, index) in pendingDeviceCodes" 
+              :key="index" 
+              closable 
+              @close="removePendingCode(index)"
+              style="margin-right: 8px; margin-bottom: 8px;"
+            >
+              {{ code }}
+            </el-tag>
+          </div>
         </div>
     
         <el-table :data="deviceList"  v-loading="loading" style="margin-top: 20px">
@@ -42,12 +49,56 @@
         </div>
       </template>
     </el-dialog>
+
+    <!-- 读取规则设置对话框 -->
+    <el-dialog v-model="showReadRuleDialog" title="设置读取规则" width="600px">
+      <el-form :model="readRuleConfig" label-width="140px">
+        <el-form-item label="RFID标签长度">
+          <el-input-number v-model="readRuleConfig.rfidLength" :min="1" :max="100" style="width: 100%" />
+          <div class="form-tip">RFID标签的固定长度(字符数)</div>
+        </el-form-item>
+        <el-form-item label="RFID标签前缀">
+          <el-input v-model="readRuleConfig.rfidPrefix" placeholder="例如:E280" style="width: 100%" />
+          <div class="form-tip">RFID标签的前缀(可选,留空则不限制前缀)</div>
+        </el-form-item>
+        <el-form-item label="分隔符">
+          <el-select v-model="readRuleConfig.separator" placeholder="选择分隔符" style="width: 100%">
+            <el-option label="空格" value=" " />
+            <el-option label="逗号" value="," />
+            <el-option label="分号" value=";" />
+            <el-option label="制表符" value="\t" />
+            <el-option label="回车换行" value="\n" />
+            <el-option label="自定义" value="custom" />
+          </el-select>
+          <el-input 
+            v-if="readRuleConfig.separator === 'custom'" 
+            v-model="readRuleConfig.customSeparator" 
+            placeholder="请输入自定义分隔符" 
+            style="width: 100%; margin-top: 10px;" 
+          />
+          <div class="form-tip">用于分割多个设备编号的分隔符</div>
+        </el-form-item>
+        <el-form-item label="自动分割粘连">
+          <el-switch v-model="readRuleConfig.autoSplitSticky" />
+          <div class="form-tip">是否自动分割粘连在一起的RFID标签</div>
+        </el-form-item>
+        <el-form-item label="允许重复添加">
+          <el-switch v-model="readRuleConfig.allowDuplicate" />
+          <div class="form-tip">是否允许添加重复的设备编号</div>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="resetReadRule">重置默认</el-button>
+        <el-button type="primary" @click="saveReadRule">保存</el-button>
+      </template>
+    </el-dialog>
   </template>
   
   <script setup lang="ts" >
   import { ref, nextTick } from 'vue';
   import * as api from '../api';
   import { ElMessage } from 'element-plus';
+  import { Setting } from '@element-plus/icons-vue';
   const emit = defineEmits(['completed']);
 
   const visible = ref(false);
@@ -59,11 +110,42 @@
   const deviceCodeInputRef = ref<any>(null);
   // 待提交的设备编号数组
   const pendingDeviceCodes = ref<string[]>([]);
+  // 标记是否是正常完成/取消盘点关闭(避免重复调用取消接口)
+  const isNormalClose = ref(false);
+  // 读取规则设置对话框显示状态
+  const showReadRuleDialog = ref(false);
+  // 读取规则配置
+  const readRuleConfig = ref({
+    rfidLength: 24, // RFID标签长度
+    rfidPrefix: 'E280', // RFID标签前缀
+    separator: ' ', // 分隔符
+    customSeparator: '', // 自定义分隔符
+    autoSplitSticky: true, // 自动分割粘连
+    allowDuplicate: true, // 允许重复添加
+  });
 //   const btflag=ref(true);
   
-  const setCheckNo = (val: string) => {
-    console.log("val::::::",val)
-    check_no.value = val;
+  const setCheckNo = async (checkNo: string, id?: number | null) => {
+    console.log("checkNo::::::", checkNo, "id::::::", id)
+    check_no.value = checkNo;
+    
+    // 如果传递了 id,直接使用
+    if (id !== undefined && id !== null) {
+      currentRecordId.value = id;
+    } else {
+      // 如果没有传递 id,通过 check_no 查询记录获取 id
+      try {
+        const reslist = await api.GetPermission();
+        if (reslist?.code === 2000) {
+          const record = findCheckRecord(checkNo, reslist.data);
+          currentRecordId.value = record?.id ?? null;
+          console.log("通过 check_no 查询到的 id::", currentRecordId.value);
+        }
+      } catch (error) {
+        console.error('查询盘点记录失败:', error);
+        currentRecordId.value = null;
+      }
+    }
 };
   
   // 聚焦设备编号输入框
@@ -106,12 +188,41 @@
     }, 100);
   };
 
+  // 重置读取规则为默认值
+  const resetReadRule = () => {
+    readRuleConfig.value = {
+      rfidLength: 24,
+      rfidPrefix: 'E280',
+      separator: ' ',
+      customSeparator: '',
+      autoSplitSticky: true,
+      allowDuplicate: true,
+    };
+    ElMessage.success('已重置为默认规则');
+  };
+
+  // 保存读取规则
+  const saveReadRule = () => {
+    // 验证配置
+    if (readRuleConfig.value.rfidLength < 1) {
+      ElMessage.warning('RFID标签长度必须大于0');
+      return;
+    }
+    if (readRuleConfig.value.separator === 'custom' && !readRuleConfig.value.customSeparator) {
+      ElMessage.warning('请设置自定义分隔符');
+      return;
+    }
+    showReadRuleDialog.value = false;
+    ElMessage.success('读取规则已保存');
+  };
+
   const onOpen = () => {
     device_code.value = '';
     // check_no.value = '';
     deviceList.value = [];
     currentRecordId.value = null;
     pendingDeviceCodes.value = [];
+    isNormalClose.value = false; // 重置标志
   };
 
   // 对话框完全打开后聚焦输入框
@@ -160,22 +271,42 @@
   const splitRFIDStickyCodes = (input: string): string[] => {
     if (!input) return [];
 
-    // 方法1: 使用正则表达式匹配RFID标签(E280开头,24字符)
-    // 匹配以E280开头的24字符标签(E280 + 20个十六进制字符)
-    const rfidPattern = /E280[0-9A-F]{20}/gi;
-    const matches = input.match(rfidPattern);
+    const { rfidLength, rfidPrefix, autoSplitSticky } = readRuleConfig.value;
     
-    // 如果匹配到多个RFID标签,说明是粘连的,返回分割后的数组
-    if (matches && matches.length > 1) {
-      return matches;
+    // 如果未启用自动分割粘连,直接返回
+    if (!autoSplitSticky) {
+      return [input];
     }
 
-    // 方法2: 如果长度是24的倍数且大于24,按固定长度分割
-    // 这种情况适用于粘连的RFID标签(无论是否以E280开头)
-    if (input.length > 24 && input.length % 24 === 0) {
+    // 方法1: 如果配置了前缀,使用正则表达式匹配RFID标签
+    if (rfidPrefix && rfidPrefix.trim()) {
+      const prefixLength = rfidPrefix.length;
+      const remainingLength = rfidLength - prefixLength;
+      if (remainingLength > 0) {
+        try {
+          // 转义前缀中的特殊字符,避免正则表达式错误
+          const escapedPrefix = rfidPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+          // 构建正则表达式:前缀 + 剩余长度的十六进制字符
+          const rfidPattern = new RegExp(`${escapedPrefix}[0-9A-F]{${remainingLength}}`, 'gi');
+          const matches = input.match(rfidPattern);
+          
+          // 如果匹配到多个RFID标签,说明是粘连的,返回分割后的数组
+          if (matches && matches.length > 1) {
+            return matches;
+          }
+        } catch (error) {
+          console.warn('RFID前缀正则表达式构建失败:', error);
+          // 如果正则构建失败,继续使用固定长度分割方法
+        }
+      }
+    }
+
+    // 方法2: 如果长度是配置长度的倍数且大于配置长度,按固定长度分割
+    // 这种情况适用于粘连的RFID标签(无论是否有前缀)
+    if (input.length > rfidLength && input.length % rfidLength === 0) {
       const codes: string[] = [];
-      for (let i = 0; i < input.length; i += 24) {
-        const code = input.substring(i, i + 24);
+      for (let i = 0; i < input.length; i += rfidLength) {
+        const code = input.substring(i, i + rfidLength);
         if (code.trim()) {
           codes.push(code);
         }
@@ -192,7 +323,7 @@
 
   /**
    * 分割RFID粘连的设备编号
-   * 支持空格分隔和粘连两种情况
+   * 支持分隔符分隔和粘连两种情况
    * RFID标签通常以特定前缀开头(如E280),且有固定长度(24字符)
    * @param input 输入的设备编号字符串
    * @returns 分割后的设备编号数组
@@ -201,28 +332,90 @@
     const trimmed = input.trim();
     if (!trimmed) return [];
 
-    // 方法0: 优先处理空格分隔的情况
-    // 如果输入中包含空格,先按空格分割,然后对每个部分处理粘连
-    if (trimmed.includes(' ')) {
-      const parts = trimmed.split(/\s+/).filter(part => part.trim()); // 按空格分割,过滤空字符串
-      const result: string[] = [];
+    const { separator, customSeparator } = readRuleConfig.value;
+    const actualSeparator = separator === 'custom' ? customSeparator : separator;
+
+    console.log('splitRFIDCodes - 输入:', trimmed, '分隔符配置:', separator, '实际分隔符:', actualSeparator, '分隔符类型:', typeof actualSeparator, '长度:', actualSeparator?.length);
+
+    // 方法0: 优先处理分隔符分隔的情况
+    // 首先检查输入中是否包含空格(无论配置如何,空格都应该被识别)
+    const hasSpace = /\s/.test(trimmed);
+    
+    if (hasSpace) {
+      // 如果输入包含空格,优先按空格分割
+      const parts = trimmed.split(/\s+/).filter(part => part.trim());
+      console.log('检测到空格,分割结果:', parts);
       
-      // 对每个部分处理可能的粘连情况
-      for (const part of parts) {
-        const splitCodes = splitRFIDStickyCodes(part.trim());
-        result.push(...splitCodes);
+      if (parts.length > 1) {
+        const result: string[] = [];
+        // 对每个部分处理可能的粘连情况
+        for (const part of parts) {
+          const trimmedPart = part.trim();
+          if (trimmedPart) {
+            const splitCodes = splitRFIDStickyCodes(trimmedPart);
+            result.push(...splitCodes);
+          }
+        }
+        console.log('空格分割最终结果:', result);
+        return result.length > 0 ? result : [trimmed];
       }
-      
-      // 如果分割出多个,返回结果
-      if (result.length > 1) {
-        return result;
+    }
+
+    // 如果配置了分隔符且不是空格,按配置的分隔符处理
+    if (actualSeparator && actualSeparator.trim() !== '' && actualSeparator !== ' ') {
+      let parts: string[] = [];
+      let hasSeparator = false;
+
+      // 根据分隔符类型进行分割
+      if (actualSeparator === '\n') {
+        // 回车换行分隔符
+        hasSeparator = trimmed.includes('\n');
+        if (hasSeparator) {
+          parts = trimmed.split(/\n+/).filter(part => part.trim());
+        }
+      } else if (actualSeparator === '\t') {
+        // 制表符分隔符
+        hasSeparator = trimmed.includes('\t');
+        if (hasSeparator) {
+          parts = trimmed.split(/\t+/).filter(part => part.trim());
+        }
+      } else {
+        // 其他分隔符
+        hasSeparator = trimmed.includes(actualSeparator);
+        if (hasSeparator) {
+          try {
+            // 转义特殊字符,匹配一个或多个连续的分隔符
+            const escapedSeparator = actualSeparator.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+            const separatorPattern = new RegExp(escapedSeparator + '+', 'g');
+            parts = trimmed.split(separatorPattern).filter(part => part.trim());
+          } catch (error) {
+            console.warn('分隔符正则表达式构建失败,使用字符串分割:', error);
+            // 回退到简单的字符串分割
+            parts = trimmed.split(actualSeparator).filter(part => part.trim());
+          }
+        }
+      }
+
+      // 如果检测到分隔符并成功分割,处理每个部分
+      if (hasSeparator && parts.length > 0) {
+        const result: string[] = [];
+        // 对每个部分处理可能的粘连情况
+        for (const part of parts) {
+          const trimmedPart = part.trim();
+          if (trimmedPart) {
+            const splitCodes = splitRFIDStickyCodes(trimmedPart);
+            result.push(...splitCodes);
+          }
+        }
+        console.log('配置分隔符分割最终结果:', result);
+        return result.length > 0 ? result : [trimmed];
       }
-      // 如果只有一个结果,继续后续处理
-      return result.length > 0 ? result : [trimmed];
     }
 
-    // 如果没有空格,直接处理粘连情况
-    return splitRFIDStickyCodes(trimmed);
+    // 如果没有分隔符或分割失败,直接处理粘连情况
+    const stickyResult = splitRFIDStickyCodes(trimmed);
+    console.log('粘连处理结果:', stickyResult);
+    return stickyResult;
   };
 
   // 添加到待提交列表(不立即提交)
@@ -240,18 +433,37 @@
 
     // 自动分割RFID粘连的设备编号
     const codes = splitRFIDCodes(input);
+    console.log('分割结果:', codes, '输入:', input, '配置:', readRuleConfig.value);
     
     if (codes.length === 0) {
       ElMessage.warning('未识别到有效的设备编号');
       return;
     }
 
-    // 批量添加到待提交列表(允许重复)
-    pendingDeviceCodes.value.push(...codes);
+    // 根据配置决定是否去重
+    let codesToAdd = codes;
+    if (!readRuleConfig.value.allowDuplicate) {
+      // 过滤掉已存在的设备编号
+      codesToAdd = codes.filter(code => !pendingDeviceCodes.value.includes(code));
+      if (codesToAdd.length < codes.length) {
+        const duplicateCount = codes.length - codesToAdd.length;
+        ElMessage.warning(`已过滤 ${duplicateCount} 个重复的设备编号`);
+      }
+    }
+
+    if (codesToAdd.length === 0) {
+      ElMessage.warning('没有可添加的设备编号(可能全部重复)');
+      return;
+    }
+
+    // 批量添加到待提交列表
+    pendingDeviceCodes.value.push(...codesToAdd);
     
     // 显示提示信息
     if (codes.length > 1) {
-      ElMessage.success(`已自动分割并添加 ${codes.length} 个设备编号`);
+      ElMessage.success(`已自动分割并添加 ${codesToAdd.length} 个设备编号`);
+    } else {
+      ElMessage.success('已添加到待提交列表');
     }
     
     device_code.value = '';
@@ -475,6 +687,8 @@
     const res=await api.CompleteScan({ check_no: check_no.value });
     if (res?.code===2000) {
         ElMessage.success('完成盘点!');
+        // 标记为正常关闭,避免 onClose 中再次调用取消接口
+        isNormalClose.value = true;
         visible.value = false;
         emit('completed');
     }
@@ -485,6 +699,8 @@
     const res=await api.CancelScan(currentRecordId.value);
     if (res?.code===2000) {
         ElMessage.success('取消盘点成功!');
+        // 标记为正常关闭,避免 onClose 中再次调用取消接口
+        isNormalClose.value = true;
         visible.value = false;
         emit('completed');
     }
@@ -525,9 +741,57 @@
   
   defineExpose({ visible,setCheckNo});
 
-  const onClose = () => {
+  const onClose = async () => {
+    // 如果是正常完成/取消盘点关闭,不调用取消接口,直接清理状态
+    if (isNormalClose.value) {
+      isNormalClose.value = false;
+      // 清理状态
+      check_no.value = '';
+      pendingDeviceCodes.value = [];
+      deviceList.value = [];
+      currentRecordId.value = null;
+      return;
+    }
+    
+    // 用户直接关闭对话框时,调用取消盘点接口
+    console.log("currentRecordId.value::",currentRecordId.value, "check_no.value::", check_no.value)
+    
+    let recordId = currentRecordId.value;
+    
+    // 如果 currentRecordId 为 null,但 check_no 存在,尝试通过 check_no 查询获取 id
+    if (!recordId && check_no.value) {
+      try {
+        const reslist = await api.GetPermission();
+        if (reslist?.code === 2000) {
+          const record = findCheckRecord(check_no.value, reslist.data);
+          recordId = record?.id ?? null;
+          console.log("通过 check_no 查询到的 id::", recordId);
+        }
+      } catch (error) {
+        console.error('查询盘点记录失败:', error);
+      }
+    }
+    
+    // 如果存在盘点记录ID,调用取消盘点接口
+    if (recordId) {
+      try {
+        const res = await api.CancelScan(recordId);
+        if (res?.code === 2000) {
+          ElMessage.success('取消盘点成功!');
+        } else {
+          ElMessage.warning(res?.msg || '取消盘点失败');
+        }
+      } catch (error) {
+        console.error('取消盘点失败:', error);
+        ElMessage.error('取消盘点失败');
+      }
+    }
+    
+    // 清理状态
     check_no.value = '';
     pendingDeviceCodes.value = [];
+    deviceList.value = [];
+    currentRecordId.value = null;
   };
 
 
@@ -548,11 +812,34 @@
   border: 1px solid #e4e7ed;
 }
 
+.pending-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
 .pending-title {
   font-size: 14px;
   font-weight: 500;
   color: #606266;
-  margin-bottom: 8px;
+}
+
+.pending-actions {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+}
+
+.pending-tags {
+  margin-top: 8px;
+}
+
+.form-tip {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 4px;
+  line-height: 1.4;
 }
 </style>
   

+ 5 - 5
src/views/system/Inventorycount/crud.tsx

@@ -52,11 +52,11 @@ export const createCrudOptions = function ({ crudExpose , inventoryDialog, inven
 						text: '开始盘点',
 						// icon: 'el-icon-document',
 						click: async () => {
-						const res =await api.StartScan({ remark: '例行盘点' });
-						if (res?.code === 2000) {
-							inventoryDialog.value.setCheckNo(res.data.check_no); 
-							inventoryDialog.value.visible = true; 
-						}
+					const res =await api.StartScan({ remark: '例行盘点' });
+					if (res?.code === 2000) {
+						await inventoryDialog.value.setCheckNo(res.data.check_no, res.data.id); 
+						inventoryDialog.value.visible = true; 
+					}
 					},
 					},