Ver Fonte

扫描枪入库修改

695593266@qq.com há 1 mês atrás
pai
commit
2be97badf2

+ 58 - 0
src/utils/crypto.js

@@ -0,0 +1,58 @@
+/**
+ * 条码/二维码内容编码工具(Base64Url)
+ *
+ * 设计:
+ * - 生成端用 Base64Url 对原文编码,并加 `~` 前缀标识;
+ * - 扫码端识别前缀后 Base64Url 解码还原原文;
+ * - 无前缀的历史/明文码直接透传,保证向后兼容;
+ * - 仅防止肉眼直接读取,不是真正的加密。
+ */
+
+const ENCODE_PREFIX = '~';
+
+function toBase64Url(str) {
+  const b64 = btoa(unescape(encodeURIComponent(str)));
+  return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
+}
+
+function fromBase64Url(str) {
+  let b64 = str.replace(/-/g, '+').replace(/_/g, '/');
+  while (b64.length % 4) b64 += '=';
+  return decodeURIComponent(escape(atob(b64)));
+}
+
+/**
+ * 编码原文:输出 `~` + Base64Url(原文)。
+ * @param {string} plain
+ * @returns {string}
+ */
+export function encryptCode(plain) {
+  if (plain == null || plain === '') return '';
+  try {
+    return ENCODE_PREFIX + toBase64Url(String(plain));
+  } catch (e) {
+    console.error('encryptCode failed:', e);
+    return String(plain);
+  }
+}
+
+/**
+ * 解码扫码内容:
+ * - 以 `~` 开头:Base64Url 解码还原原文;失败时回退原值;
+ * - 不以 `~` 开头:视为明文,直接返回(兼容历史码)。
+ * @param {string} code
+ * @returns {string}
+ */
+export function decryptCode(code) {
+  if (code == null || code === '') return '';
+  const str = String(code).trim();
+  if (!str.startsWith(ENCODE_PREFIX)) return str;
+  try {
+    return fromBase64Url(str.slice(ENCODE_PREFIX.length));
+  } catch (e) {
+    console.error('decryptCode failed:', e, code);
+    return str;
+  }
+}
+
+export default { encryptCode, decryptCode };

+ 54 - 12
src/views/bpm/handleTask/components/productionWarehousing/storage.vue

@@ -1261,6 +1261,7 @@
   import { mapActions, mapGetters } from 'vuex';
   import BigNumber from 'bignumber.js';
   import headList from '@/components/headList';
+  import { decryptCode } from '@/utils/crypto';
 
   // import dictMixins from '@/mixins/dictMixins';
   export default {
@@ -1401,36 +1402,81 @@
       await this.getNowFormatDate();
     },
     mounted() {
-      document.addEventListener('keydown', this.handleScanInput);
+      this._barcodeScanHandler = this.handleScanInput.bind(this);
+      document.addEventListener('keydown', this._barcodeScanHandler);
+      this._barcodePasteHandler = this.handleBarcodePaste.bind(this);
+      document.addEventListener('paste', this._barcodePasteHandler);
     },
     beforeDestroy() {
-      document.removeEventListener('keydown', this.handleScanInput);
+      if (this._barcodeScanHandler) {
+        document.removeEventListener('keydown', this._barcodeScanHandler);
+      }
+      if (this._barcodePasteHandler) {
+        document.removeEventListener('paste', this._barcodePasteHandler);
+      }
     },
     methods: {
       // 扫码枪键盘事件监听
+      // 通过按键间隔识别扫码枪输入:扫码枪通常 <30ms 一个键,
+      // 进入 scanning 模式后调用 preventDefault,阻止按键打入聚焦的输入框,
+      // 避免行字段被覆盖、表格行被重新渲染。
       handleScanInput(e) {
         const now = Date.now();
         const timeDiff = now - this.scanLastKeyTime;
         this.scanLastKeyTime = now;
 
         if (e.key === 'Enter') {
-          if (this.scanBuffer.length > 3) {
-            this.processScanCode(this.scanBuffer);
+          if (this._scanning && this.scanBuffer.length > 3) {
+            this.processScanCode(decryptCode(this.scanBuffer));
+            // 扫码枪结束,吞掉这次 Enter,避免触发表单提交/单元格回车跳转
+            e.preventDefault();
+            e.stopPropagation();
           }
           this.scanBuffer = '';
+          this._scanning = false;
           return;
         }
 
-        // 间隔超过 100ms 视为人工输入,重置缓冲
+        if (!e.key || e.key.length !== 1) return;
+
+        // 间隔超过 100ms 视为人工输入,重置缓冲与扫码状态
         if (timeDiff > 100) {
           this.scanBuffer = '';
+          this._scanning = false;
+        } else if (timeDiff < 30) {
+          // 连续两次按键间隔很短,认定进入扫码状态
+          this._scanning = true;
         }
 
-        if (e.key && e.key.length === 1) {
-          this.scanBuffer += e.key;
+        this.scanBuffer += e.key;
+
+        // 进入扫码状态后阻止默认行为,避免按键进入聚焦输入框
+        if (this._scanning) {
+          e.preventDefault();
+          e.stopPropagation();
+          // 同时让聚焦输入框失焦,避免极少数情况下首字符已经入框
+          if (
+            document.activeElement &&
+            typeof document.activeElement.blur === 'function'
+          ) {
+            document.activeElement.blur();
+          }
+        }
+      },
+      // 扫码枪粘贴事件监听
+      handleBarcodePaste(e) {
+        const text = (e.clipboardData || window.clipboardData)
+          .getData('text')
+          .trim();
+        console.log('扫码粘贴事件:', text);
+        if (text.length >= 3) {
+          this.processScanCode(decryptCode(text));
         }
       },
       // 处理扫码结果:先匹配产品信息,再匹配包装明细
+      // 直接把 confirmStatus 写到行对象上,便于业务读取与序列化。
+      // 由于 packingList watcher 已去掉 deep,且 productList 本就无 watcher,
+      // 这两次赋值都不会触发表格重渲染。
       processScanCode(code) {
         console.log('扫描条码:', code);
 
@@ -1442,13 +1488,10 @@
             code === `${item.categoryCode}-${item.batchNo}`
         );
         if (productIndex > -1) {
-          // productList 没有 deep watcher,用 $set 让字段进入响应式,
-          // 后续 spread/序列化都能拿到 confirmStatus
-          this.$set(this.productList[productIndex], 'confirmStatus', 1);
+          this.productList[productIndex].confirmStatus = 1;
           this.$message.success(
             `产品信息匹配成功:${this.productList[productIndex].categoryCode}-${this.productList[productIndex].batchNo}`
           );
-          console.log('修改后的数据', this.productList);
           return;
         }
 
@@ -1468,7 +1511,6 @@
                 item.engrave === engrave)
           );
           if (packingIndex > -1) {
-            // 直接赋值,避免触发 packingList 的 deep watcher 重新加载
             this.packingList[packingIndex].confirmStatus = 1;
             this.$message.success(`包装明细匹配成功:${code}`);
             return;