From 1ef1063281497f32fcfa4f14b07d99399c0bb765 Mon Sep 17 00:00:00 2001
From: jinglun-cloud <jinglun2019@foxmail.com>
Date: 星期四, 07 五月 2026 15:04:17 +0800
Subject: [PATCH] refactor(设备运动): 重构条码设备运动逻辑,提取运动常量并优化代码结构

---
 src/main/webapp/components/MapCanvas.js |  464 ++++++++++++++++++++++++++--------------------------------
 1 files changed, 208 insertions(+), 256 deletions(-)

diff --git a/src/main/webapp/components/MapCanvas.js b/src/main/webapp/components/MapCanvas.js
index 636f184..daaec0f 100644
--- a/src/main/webapp/components/MapCanvas.js
+++ b/src/main/webapp/components/MapCanvas.js
@@ -10,7 +10,25 @@
 
 // 浠呮湰鍦版祴璇曠敤
 const FAKE_MAX_LAYER = 1730000;
-const FAKE_MAX_CRN_LAYER = 4800
+const FAKE_MAX_CRN_LAYER = 4800;
+
+// 鏉$爜璁惧杩愬姩瀛﹀父閲忥紙setDeviceInfoByBarcode 閾捐矾浣跨敤锛�
+// 杩欓噷闆嗕腑鏀炬槸涓轰簡璁� tickBarcodeTrackSpriteMotion / updateMotionSpeed
+// 绛夊唴閮ㄦ柟娉曚笉鍐嶆暎甯冨ぇ閲� magic number
+const MOTION = {
+  DEFAULT_TICKER_DELTA_MS: 16.667, // dt 缂哄け鏃舵寜绾� 60fps 浼扮畻
+  FALLBACK_BASE_V_FACTOR: 2, // baseV 鍏滃簳 = sprite.width * factor
+  FALLBACK_BASE_V_MIN: 20,
+  STALE_INTERVAL_MULT: 2.65, // 澶氬皯涓吀鍨嬫帹閫佸懆鏈熷悗鍒ゅ畾涓洪暱鏃堕棿鏃犳洿鏂�
+  SLOW_RADIUS_FACTOR: 6.5, // 杩涘叆璇ュ崐寰勫紑濮嬪仛鍋滄鍦嗙紦鍔�
+  SLOW_RADIUS_MIN: 72,
+  EASE_EXPONENT: 0.45, // 鍋滄鍦嗗唴閫熺巼涓庤窛绂荤殑骞傛鍏崇郴
+  INITIAL_SPEED_RATIO: 0.35, // 璧锋閫熷害 = 鐩爣閫熷害 * ratio
+  MAX_ACCEL_FACTOR: 1.75, // 鏈�澶у姞閫熷害 = baseV * factor
+  MAX_ACCEL_MIN: 22,
+  VELOCITY_EWMA_ALPHA: 0.2, // 鏈嶅姟绔潎閫熶及璁$殑 EWMA 骞虫粦绯绘暟
+  VELOCITY_SAMPLE_MAX_DT: 10 // 瓒呰繃璇ョ鏁扮殑鏍锋湰涓嶅弬涓庡潎閫熶及璁�
+};
 /**
  * 閫氱敤杞鍣ㄧ被
  * 灏佽甯﹁姹傚彇娑堛�佸钩婊戝欢杩熻绠椼�侀敊璇��閬跨殑杞閫昏緫
@@ -1325,29 +1343,28 @@
       return (value * 100).toFixed(1) + '%';
     },
     /******************setDeviceInfo浣跨敤鐨勫嚱鏁�:******************/
-    // 鐜┛鏉$爜鍦ㄦ�婚暱涓婄殑鏈夌鍙锋宸�
-    calcSignedSegmentDelta(fromBarcode, toBarcode, trackInfo, totalSegmentCount) {
+    /**
+     * 璁$畻涓ゆ潯鐮佷箣闂寸殑"娈靛樊"銆�
+     * - 鐜舰杞ㄩ亾鎸夋�绘鏁板彇妯★紝姘歌繙鍚戝墠鎺ㄨ繘锛圼0, totalSegmentCount)锛夈��
+     * - 闈炵幆褰㈣建閬撹繑鍥炴湁绗﹀彿宸紝鏂瑰悜鐢辫皟鐢ㄦ柟鍐冲畾銆�
+     */
+    calcSegmentDelta(fromBarcode, toBarcode, isAnnulus, totalSegmentCount) {
       const from = Number(fromBarcode);
       const to = Number(toBarcode);
       if (!isFinite(from) || !isFinite(to)) return 0;
       const raw = to - from;
-      if (trackInfo.type !== 'annulus' || totalSegmentCount <= 0) return raw;
-      // 闈炶礋鍙栨ā
+      if (!isAnnulus || totalSegmentCount <= 0) return raw;
       return ((raw % totalSegmentCount) + totalSegmentCount) % totalSegmentCount;
     },
+    /** 鎶婅澶囩洿鎺ュ榻愬埌 mappingInfo 涓婂苟娓呯悊 ticker */
     finishDeviceMotion(sprite) {
-      if (!sprite || !sprite.mappingInfo) {
-        return false;
-      }
-      const mx = sprite.mappingInfo.x;
-      const my = sprite.mappingInfo.y;
-      if (!isFinite(mx) || !isFinite(my)) {
-        return false;
-      }
+      if (!sprite || !sprite.mappingInfo) return false;
+      const { x: mx, y: my, path: mPath } = sprite.mappingInfo;
+      if (!isFinite(mx) || !isFinite(my)) return false;
       sprite.isFinish = true;
       sprite.x = mx;
       sprite.y = my;
-      sprite.path = sprite.mappingInfo.path || sprite.path;
+      sprite.path = mPath || sprite.path;
       sprite.rotation = G.getRotate(sprite.mappingInfo, sprite.mappingInfo.path) || sprite.rotation;
       if (sprite.ticker) {
         this.pixiApp.ticker.remove(sprite.ticker);
@@ -1357,35 +1374,17 @@
       sprite._motionSpeed = undefined;
       return true;
     },
-    /** Web 鎺ュ彛杩斿洖缁熶竴鎴愯澶囨暟缁� */
-    parseBarcodeDevicesResponse(res) {
-      return Array.isArray(res) ? res : res && res.code === 200 ? res.data : null;
-    },
     /** 鏉$爜璁惧锛氱紪鍙锋枃瀛� + 鐘舵�佽壊 */
     applyBarcodeSpriteAppearance(sprite, device, deviceTypeInfo) {
       const id = +device.index;
       const taskNo = device.taskNo;
-      if (taskNo != null && taskNo > 0) {
-        sprite.textObj.text = id + '(' + taskNo + ')';
-      } else {
-        sprite.textObj.text = String(id);
-      }
-      const statusColor = device.statusColor;
-      if (statusColor != null) {
-        deviceTypeInfo.statusInfo.updateTextureColor(sprite, statusColor);
+      sprite.textObj.text = taskNo != null && taskNo > 0 ? id + '(' + taskNo + ')' : String(id);
+      if (device.statusColor != null) {
+        deviceTypeInfo.statusInfo.updateTextureColor(sprite, device.statusColor);
       }
     },
-    /** 鏉$爜閿氱偣瀵硅薄锛堜袱澶勬瀯閫犲悎骞朵负鍚屼竴褰㈢姸锛� */
-    createBarcodeAnchorState(
-      trackId,
-      minBarcode,
-      maxBarcode,
-      totalSegmentCount,
-      barcode,
-      point,
-      path,
-      angle
-    ) {
+    /** 鏉$爜閿氱偣瀵硅薄锛坕nit 涓� sync 鍏辩敤锛� */
+    createBarcodeAnchorState(trackId, minBarcode, maxBarcode, totalSegmentCount, barcode, point, path, angle) {
       return {
         barcode,
         x: point.x,
@@ -1398,33 +1397,26 @@
         totalSegmentCount
       };
     },
+    /** 閿氱偣鏄惁闇�瑕侀噸缃細杞ㄩ亾銆佹潯鐮佸尯闂淬�佹鏁颁换涓�鍙樻洿閮借閲嶆柊寤虹珛 */
+    isBarcodeAnchorStale(anchor, sprite, minBarcode, maxBarcode, totalSegmentCount) {
+      return (
+        !anchor ||
+        anchor.trackId !== sprite.trackInfo.id ||
+        anchor.minBarcode !== minBarcode ||
+        anchor.maxBarcode !== maxBarcode ||
+        anchor.totalSegmentCount !== totalSegmentCount
+      );
+    },
     /** 棣栨鏈夋潯鐮佹暟鎹細鎸� rgvPos 钀藉埌杞ㄩ亾涓婂苟寤虹珛閿氱偣 */
-    initializeBarcodeTrackSprite(
-      sprite,
-      device,
-      pathList,
-      allDistance,
-      minBarcode,
-      maxBarcode,
-      totalSegmentCount
-    ) {
+    initializeBarcodeTrackSprite(sprite, device, pathList, allDistance, minBarcode, maxBarcode, totalSegmentCount) {
       sprite.barcode = device.rgvPos;
       sprite.time = nowMs();
-      const passedSegmentCount = this.calcSignedSegmentDelta(
-        minBarcode,
-        device.rgvPos,
-        sprite.trackInfo,
-        totalSegmentCount
-      );
+      const isAnnulus = sprite.trackInfo.type === 'annulus';
+      const passedSegmentCount = this.calcSegmentDelta(minBarcode, device.rgvPos, isAnnulus, totalSegmentCount);
       const deltaDistance = (allDistance * passedSegmentCount) / totalSegmentCount;
       const initPath = sprite.path;
-      const initMovePoint = G.snapToAnnulusPath(
-        sprite.trackInfo,
-        sprite.x,
-        sprite.y,
-        initPath
-      );
-      let mappingInfo = G.getPositionAfterMove({
+      const initMovePoint = G.snapToAnnulusPath(sprite.trackInfo, sprite.x, sprite.y, initPath);
+      const mappingInfo = G.getPositionAfterMove({
         point: initMovePoint,
         pathList,
         path: initPath,
@@ -1437,12 +1429,7 @@
       sprite.rotation = G.getRotate(mappingInfo, mappingInfo.path) || sprite.rotation;
       sprite.currentAngle = mappingInfo.angle;
       sprite.mappingInfo = mappingInfo;
-      const anchorPoint = G.snapToAnnulusPath(
-        sprite.trackInfo,
-        mappingInfo.x,
-        mappingInfo.y,
-        mappingInfo.path
-      );
+      const anchorPoint = G.snapToAnnulusPath(sprite.trackInfo, mappingInfo.x, mappingInfo.y, mappingInfo.path);
       sprite._barcodeAnchor = this.createBarcodeAnchorState(
         sprite.trackInfo.id,
         minBarcode,
@@ -1454,138 +1441,118 @@
         mappingInfo.angle
       );
     },
+    /** 褰撳墠甯� dt(绉�)锛泃icker 涓嶅彲鐢ㄦ椂鎸夌害 60fps 浼拌 */
+    getMotionFrameDt() {
+      const ticker = this.pixiApp && this.pixiApp.ticker;
+      const dtMs = ticker && typeof ticker.deltaMS === 'number' ? ticker.deltaMS : MOTION.DEFAULT_TICKER_DELTA_MS;
+      return Math.max(0, dtMs) / 1000;
+    },
+    /**
+     * 鎺ㄨ繘 sprite._motionSpeed锛堝甫鍔犻�熷害涓婇檺鍜�"鍋滄鍦�"缂撳姩锛夛紝杩斿洖鏈抚鏈�澶у彲璧拌窛绂汇��
+     * 鏈嶅姟绔暱鏃堕棿鏈帹閫佹柊鏉$爜鏃讹紝闈犺繎鐩爣鐐硅闄嶉�熼伩鍏嶈秺杩�/鎶栧姩銆�
+     */
+    updateMotionSpeed(sprite, restDistance, dt) {
+      const baseV =
+        typeof sprite.maV === 'number' && isFinite(sprite.maV) && sprite.maV > 0
+          ? sprite.maV
+          : Math.max(sprite.width * MOTION.FALLBACK_BASE_V_FACTOR, MOTION.FALLBACK_BASE_V_MIN);
+      const msSinceUpdate = sprite.time ? nowMs() - sprite.time : 0;
+      const typicalIntervalMs = (sprite.lastDeltaTime || 1) * 1000;
+      const isStale = msSinceUpdate > typicalIntervalMs * MOTION.STALE_INTERVAL_MULT;
+      const slowRadius = Math.max(sprite.width * MOTION.SLOW_RADIUS_FACTOR, MOTION.SLOW_RADIUS_MIN);
+      const easing = isStale ? Math.min(1.0, Math.pow(restDistance / slowRadius, MOTION.EASE_EXPONENT)) : 1.0;
+      const targetSpeed = baseV * easing;
+      if (typeof sprite._motionSpeed !== 'number' || !isFinite(sprite._motionSpeed)) {
+        sprite._motionSpeed = targetSpeed * MOTION.INITIAL_SPEED_RATIO;
+      }
+      const maxAccel = Math.max(baseV * MOTION.MAX_ACCEL_FACTOR, MOTION.MAX_ACCEL_MIN);
+      const dv = targetSpeed - sprite._motionSpeed;
+      const cap = maxAccel * dt;
+      sprite._motionSpeed += Math.max(-cap, Math.min(cap, dv));
+      return Math.min(restDistance, Math.max(0, sprite._motionSpeed) * dt);
+    },
+    /**
+     * 鍗曠嚎闈炵幆褰㈣建閬擄細鏂瑰悜鍙栧喅浜庢潯鐮佽繘閫�锛岄渶瑕佽繑鍥炴湁绗﹀彿 step銆�
+     * 杩斿洖 null 琛ㄧず杩欏抚搴旂洿鎺ュ仠鍦� mappingInfo锛堟姇褰遍��鍖栨垨鐩寸嚎閫�鍖栵級銆�
+     */
+    computeSingleLineSignedStep(sprite, path, stepCap, restDistance) {
+      const remainAlong = G.lineSignedRemainAlong(
+        path,
+        sprite.x,
+        sprite.y,
+        sprite.mappingInfo.x,
+        sprite.mappingInfo.y
+      );
+      if (remainAlong == null) return stepCap;
+      const stepMag = Math.min(stepCap, Math.abs(remainAlong), restDistance);
+      if (stepMag >= EPSILON || restDistance <= EPSILON) {
+        return Math.sign(remainAlong) * stepMag;
+      }
+      // 鎶曞奖 stepMag 閫�鍖栦负 0 浣嗘姘忚窛绂讳粛瀛樺湪锛氱敤涓栫晫鍚戦噺涓庣洿绾挎柟鍚戠殑鍐呯Н鍏滃簳鍒ゆ柟鍚�
+      const lineStart = { x: path.startX, y: path.startY };
+      const lineEnd = { x: path.x, y: path.y };
+      if (G.calcDistance(lineStart, lineEnd) <= EPSILON) return null;
+      const { x: ux, y: uy } = G.normalizeVector(lineStart, lineEnd);
+      const hint = (sprite.mappingInfo.x - sprite.x) * ux + (sprite.mappingInfo.y - sprite.y) * uy;
+      if (hint === 0) return null;
+      return Math.sign(hint) * Math.min(stepCap, restDistance);
+    },
+    isSingleLineTrack(sprite, pathList) {
+      return pathList.length === 1 && pathList[0].type === 'line' && sprite.trackInfo.type !== 'annulus';
+    },
     /** 鏉$爜璁惧姣忓抚鎻掑�肩Щ鍔紙Pixi ticker 鍥炶皟锛� */
     tickBarcodeTrackSpriteMotion(sprite, pathList) {
-      if (sprite.isFinish) {
-        return;
-      }
+      if (sprite.isFinish) return;
       const restDistance = G.calcDistance(sprite, sprite.mappingInfo);
       if (restDistance <= EPSILON) {
         this.finishDeviceMotion(sprite);
         return;
       }
-      const dtMs =
-        this.pixiApp && this.pixiApp.ticker && typeof this.pixiApp.ticker.deltaMS === 'number'
-          ? this.pixiApp.ticker.deltaMS
-          : 16.667;
-      const dt = Math.max(0, dtMs) / 1000;
-      if (dt <= 0) {
-        return;
-      }
-      const baseV =
-        typeof sprite.maV === 'number' && isFinite(sprite.maV) && sprite.maV > 0
-          ? sprite.maV
-          : Math.max(sprite.width * 2, 20);
-      const msSinceUpdate = sprite.time ? nowMs() - sprite.time : 0;
-      const typicalIntervalMs = (sprite.lastDeltaTime || 1) * 1000;
-      const isStale = msSinceUpdate > typicalIntervalMs * 2.65;
-      const slowRadius = Math.max(sprite.width * 6.5, 72);
-      const easing = isStale ? Math.min(1.0, Math.pow(restDistance / slowRadius, 0.45)) : 1.0;
-      const targetSpeed = baseV * easing;
-      if (typeof sprite._motionSpeed !== 'number' || !isFinite(sprite._motionSpeed)) {
-        sprite._motionSpeed = targetSpeed * 0.35;
-      }
-      const maxAccel = Math.max(baseV * 1.75, 22);
-      const dv = targetSpeed - sprite._motionSpeed;
-      const cap = maxAccel * dt;
-      sprite._motionSpeed += Math.max(-cap, Math.min(cap, dv));
+      const dt = this.getMotionFrameDt();
+      if (dt <= 0) return;
+
+      const stepCap = this.updateMotionSpeed(sprite, restDistance, dt);
       const path = sprite.path;
-      const stepCap = Math.min(restDistance, Math.max(0, sprite._motionSpeed) * dt);
-      const singleLineTrack =
-        pathList.length === 1 && pathList[0].type === 'line' && sprite.trackInfo.type !== 'annulus';
-      let smoothDistance;
-      if (singleLineTrack) {
-        const remainAlong = G.lineSignedRemainAlong(
-          path,
-          sprite.x,
-          sprite.y,
-          sprite.mappingInfo.x,
-          sprite.mappingInfo.y
-        );
-        if (remainAlong != null) {
-          let stepMag = Math.min(stepCap, Math.abs(remainAlong), restDistance);
-          if (stepMag < EPSILON && restDistance > EPSILON) {
-            const lineStart = { x: path.startX, y: path.startY };
-            const lineEnd = { x: path.x, y: path.y };
-            const sl = G.calcDistance(lineStart, lineEnd);
-            if (sl > EPSILON) {
-              const { x: ux, y: uy } = G.normalizeVector(lineStart, lineEnd);
-              const wdx = sprite.mappingInfo.x - sprite.x;
-              const wdy = sprite.mappingInfo.y - sprite.y;
-              const hint = wdx * ux + wdy * uy;
-              stepMag = Math.min(stepCap, restDistance);
-              if (hint === 0) {
-                this.finishDeviceMotion(sprite);
-                return;
-              }
-              smoothDistance = Math.sign(hint) * stepMag;
-            } else {
-              this.finishDeviceMotion(sprite);
-              return;
-            }
-          } else {
-            smoothDistance = Math.sign(remainAlong) * stepMag;
-          }
-        } else {
-          smoothDistance = stepCap;
+      let signedStep;
+      if (this.isSingleLineTrack(sprite, pathList)) {
+        signedStep = this.computeSingleLineSignedStep(sprite, path, stepCap, restDistance);
+        if (signedStep == null) {
+          this.finishDeviceMotion(sprite);
+          return;
         }
       } else {
-        smoothDistance = stepCap;
+        signedStep = stepCap;
       }
-      const movePointBarcode = G.snapToAnnulusPath(
-        sprite.trackInfo,
-        sprite.x,
-        sprite.y,
-        path
-      );
-      const angle = Math.atan2(sprite.y - path.y, sprite.x - path.x);
-      const p = G.getPositionAfterMove({
-        point: movePointBarcode,
+
+      const movePoint = G.snapToAnnulusPath(sprite.trackInfo, sprite.x, sprite.y, path);
+      const radialAngle = Math.atan2(sprite.y - path.y, sprite.x - path.x);
+      const next = G.getPositionAfterMove({
+        point: movePoint,
         pathList,
         path,
-        deltaDistance: smoothDistance,
-        angle
+        deltaDistance: signedStep,
+        angle: radialAngle
       });
-      sprite.path = p.path;
-      sprite.x = p.x;
-      sprite.y = p.y;
-      sprite.rotation = G.getRotate(p, p.path) || sprite.rotation;
-      const restDistanceAfter = G.calcDistance({ x: sprite.x, y: sprite.y }, sprite.mappingInfo);
-      if (restDistanceAfter <= EPSILON || Math.abs(smoothDistance) >= restDistance - EPSILON) {
+      sprite.path = next.path;
+      sprite.x = next.x;
+      sprite.y = next.y;
+      sprite.rotation = G.getRotate(next, next.path) || sprite.rotation;
+      const restAfter = G.calcDistance({ x: sprite.x, y: sprite.y }, sprite.mappingInfo);
+      if (restAfter <= EPSILON || Math.abs(signedStep) >= restDistance - EPSILON) {
         this.finishDeviceMotion(sprite);
       }
     },
     /**
-     * 鏍规嵁鏈嶅姟绔潯鐮佸埛鏂� mappingInfo銆侀�熷害涓� ticker銆�
-     * rgvPosMax 閫氬父鏄棴鍖洪棿 [min, max]锛屼笖 max 瀵瑰簲 100% 涓� min 鍚屼竴鐐癸紙璧颁竴鍦堝洖鍒板師鐐癸級銆�
-     * 鍥犳鎸� 鈥滄鏁� = max - min鈥� 鎹㈢畻璺濈銆�
+     * 鏍规嵁鏈嶅姟绔潯鐮佸埛鏂� mappingInfo銆侀�熷害浼拌涓� ticker銆�
+     * rgvPosMax 閫氬父鏄棴鍖洪棿 [min, max]锛宮ax 涓� min 鍦ㄨ建閬撲笂鍚岀偣锛堣蛋涓�鍦堝洖鍒板師鐐癸級锛�
+     * 鏁呮寜 娈垫暟 = max - min 鎹㈢畻璺濈銆�
      */
-    syncBarcodeTrackSprite(
-      sprite,
-      device,
-      oldSprite,
-      pathList,
-      allDistance,
-      minBarcode,
-      maxBarcode,
-      totalSegmentCount
-    ) {
+    syncBarcodeTrackSprite(sprite, device, prevBarcode, prevTime, pathList, allDistance, minBarcode, maxBarcode, totalSegmentCount) {
       let anchor = sprite._barcodeAnchor;
-      const needResetAnchor =
-        !anchor ||
-        anchor.trackId !== sprite.trackInfo.id ||
-        anchor.minBarcode !== minBarcode ||
-        anchor.maxBarcode !== maxBarcode ||
-        anchor.totalSegmentCount !== totalSegmentCount;
-      if (needResetAnchor) {
+      if (this.isBarcodeAnchorStale(anchor, sprite, minBarcode, maxBarcode, totalSegmentCount)) {
         const anchorPath = sprite.path;
-        const anchorPoint = G.snapToAnnulusPath(
-          sprite.trackInfo,
-          sprite.x,
-          sprite.y,
-          anchorPath
-        );
-        const anchorBarcode = oldSprite.barcode != null ? oldSprite.barcode : device.rgvPos;
+        const anchorPoint = G.snapToAnnulusPath(sprite.trackInfo, sprite.x, sprite.y, anchorPath);
+        const anchorBarcode = prevBarcode != null ? prevBarcode : device.rgvPos;
         anchor = this.createBarcodeAnchorState(
           sprite.trackInfo.id,
           minBarcode,
@@ -1598,120 +1565,101 @@
         );
         sprite._barcodeAnchor = anchor;
       }
-      const passedSegmentCount = this.calcSignedSegmentDelta(
-        anchor.barcode,
-        device.rgvPos,
-        sprite.trackInfo,
-        totalSegmentCount
-      );
+      const isAnnulus = sprite.trackInfo.type === 'annulus';
+      const passedSegmentCount = this.calcSegmentDelta(anchor.barcode, device.rgvPos, isAnnulus, totalSegmentCount);
       const deltaDistance = (allDistance * passedSegmentCount) / totalSegmentCount;
       const path = anchor.path || sprite.path;
-      const barcodeMovePoint = { x: anchor.x, y: anchor.y };
-      let finalPosition = G.getPositionAfterMove({
-        point: barcodeMovePoint,
+      const finalPosition = G.getPositionAfterMove({
+        point: { x: anchor.x, y: anchor.y },
         pathList,
         path,
         deltaDistance,
         angle: anchor.angle
       });
       let curveDistance = G.calcDistance(sprite, finalPosition);
-      if (!isFinite(curveDistance)) {
-        curveDistance = deltaDistance;
-      }
+      if (!isFinite(curveDistance)) curveDistance = deltaDistance;
       sprite.mappingInfo = finalPosition;
+
       const now = nowMs();
-      const deltaTime = (now - oldSprite.time) / 1000 || 0;
+      const deltaTime = (now - prevTime) / 1000 || 0;
       sprite.time = now;
       sprite.barcode = device.rgvPos;
-      if (deltaTime > 0 && deltaTime <= 10 && curveDistance > 0) {
+      if (deltaTime > 0 && deltaTime <= MOTION.VELOCITY_SAMPLE_MAX_DT && curveDistance > 0) {
         sprite.lastDeltaTime = deltaTime;
         const v = curveDistance / deltaTime;
-        const alpha = 0.2;
-        sprite.maV =
-          typeof sprite.maV === 'number' && isFinite(sprite.maV)
-            ? sprite.maV * (1 - alpha) + v * alpha
-            : v;
+        const a = MOTION.VELOCITY_EWMA_ALPHA;
+        sprite.maV = typeof sprite.maV === 'number' && isFinite(sprite.maV) ? sprite.maV * (1 - a) + v * a : v;
       }
+
       if (curveDistance < EPSILON) {
         this.finishDeviceMotion(sprite);
         return;
       }
       sprite.isFinish = false;
-      if (sprite.ticker) {
-        return;
-      }
+      if (sprite.ticker) return;
       sprite.ticker = () => this.tickBarcodeTrackSpriteMotion(sprite, pathList);
       this.pixiApp.ticker.add(sprite.ticker);
     },
-    // 閫傞厤涓嶅悓鎺ュ彛杩斿洖鐨勬暟鎹粨鏋�
+    /** 鐜┛鏉$爜鎺ュ彛鐨勮澶囨潯鐩� -> 鍐呴儴缁熶竴瀛楁 */
+    adaptAnnulusBarcodeDevice(device, deviceTypeInfo) {
+      const index = +device.index;
+      const sprite = deviceTypeInfo.pixiMap.get(index);
+      if (!sprite) return null;
+      const trackInfoParse = G.safeParseJson(sprite.trackInfo.value);
+      // 鍚庣杩斿洖褰㈠ "#27AE60" 鐨勯鑹插瓧绗︿覆
+      const hex = device.statusColor ? device.statusColor.split('#')[1] : null;
+      return {
+        index,
+        sprite,
+        statusColor: hex != null ? parseInt(hex, 16) : null,
+        minBarcode: trackInfoParse.barCodeStart,
+        maxBarcode: trackInfoParse.barCodeEnd,
+        rgvPos: device.rgvPos,
+        taskNo: device.taskNo
+      };
+    },
+    /** 鍘熺増鏉$爜鎺ュ彛锛堟寜 idName + statusInfo + laserValue锛�-> 鍐呴儴缁熶竴瀛楁 */
+    adaptOriginalBarcodeDevice(device, deviceTypeInfo) {
+      const index = +device[deviceTypeInfo.idName];
+      const sprite = deviceTypeInfo.pixiMap.get(index);
+      if (!sprite) return null;
+      const trackInfoParse = G.safeParseJson(sprite.trackInfo.value);
+      const statusInfo = deviceTypeInfo.statusInfo;
+      return {
+        index,
+        sprite,
+        statusColor: statusInfo.getStatus(device[statusInfo.name]),
+        minBarcode: trackInfoParse.barCodeStart,
+        maxBarcode: trackInfoParse.barCodeEnd,
+        rgvPos: (device.laserValue * FAKE_MAX_LAYER) / FAKE_MAX_CRN_LAYER,
+        taskNo: device.taskNo
+      };
+    },
     deviceAdapter(type, res, trackType) {
-      const devices = this.parseBarcodeDevicesResponse(res);
+      const devices = Array.isArray(res) ? res : res && res.code === 200 ? res.data : null;
+      if (!devices) return [];
       const deviceTypeInfo = this.DEVICE_MAP[type];
-      // 鐜┛鎺ュ彛
-      if (trackType === 'annulus') {
-        return devices.map((device) => {
-          const index = +device.index;
-          const sprite = deviceTypeInfo.pixiMap.get(index);
-          if (!sprite) {
-            return {};
-          }
-          const trackInfoParse = G.safeParseJson(sprite.trackInfo.value);
-          const statusColorStr = device.statusColor.split('#')[1];
-          const statusColor = parseInt(statusColorStr, 16);
-          return {
-            index,
-            statusColor,
-            sprite,
-            minBarcode: trackInfoParse.barCodeStart,
-            maxBarcode: trackInfoParse.barCodeEnd,
-            rgvPos: device.rgvPos,
-            taskNo: device.taskNo
-          };
-        });
+      const adapt =
+        trackType === 'annulus'
+          ? (d) => this.adaptAnnulusBarcodeDevice(d, deviceTypeInfo)
+          : (d) => this.adaptOriginalBarcodeDevice(d, deviceTypeInfo);
+      const out = [];
+      for (const device of devices) {
+        const item = adapt(device);
+        if (item) out.push(item);
       }
-      // 鍘熺増鎺ュ彛
-      return devices.map((device) => {
-        const index = +device[deviceTypeInfo.idName];
-        const sprite = deviceTypeInfo.pixiMap.get(index);
-        if (!sprite) {
-          return {};
-        }
-        const trackInfoParse = G.safeParseJson(sprite.trackInfo.value);
-        const minBarcode = trackInfoParse.barCodeStart;
-        const maxBarcode = trackInfoParse.barCodeEnd;
-        const statusInfo = deviceTypeInfo.statusInfo;
-        const statusColor = statusInfo.getStatus(device[statusInfo.name]);
-        return {
-          index,
-          statusColor,
-          sprite,
-          minBarcode,
-          maxBarcode,
-          rgvPos: device.laserValue * FAKE_MAX_LAYER / FAKE_MAX_CRN_LAYER,
-          taskNo: device.taskNo
-        };
-      });
+      return out;
     },
     setDeviceInfoByBarcode(type, res, trackType = 'annulus') {
       const devices = this.deviceAdapter(type, res, trackType);
       const deviceTypeInfo = this.DEVICE_MAP[type];
-      for (let i = 0; i < devices.length; i++) {
-        const device = devices[i] || {};
+      for (const device of devices) {
         const { sprite, minBarcode, maxBarcode } = device;
-        if (!sprite) {
-          continue;
-        }
         this.applyBarcodeSpriteAppearance(sprite, device, deviceTypeInfo);
         const pathList = sprite.trackInfo.pathList;
         const allDistance = G.getAllDistance(pathList);
         const totalSegmentCount = Math.max(1, maxBarcode - minBarcode);
-        const oldSprite = {
-          ...sprite,
-          x: sprite.x,
-          y: sprite.y,
-          mappingInfo: { ...sprite.mappingInfo }
-        };
-        if (!sprite.barcode && sprite.barcode !== 0) {
+        if (sprite.barcode == null) {
           this.initializeBarcodeTrackSprite(
             sprite,
             device,
@@ -1723,10 +1671,14 @@
           );
           continue;
         }
+        // sync 闇�瑕�"鍒锋柊鍓�"鐨勬潯鐮佷笌鏃堕棿鎴虫潵璁$畻娈靛樊/鐬椂閫熷害锛屽崟鐙彇涓や釜鏍囬噺姣� spread 鏁翠釜 sprite 渚垮疁寰楀
+        const prevBarcode = sprite.barcode;
+        const prevTime = sprite.time;
         this.syncBarcodeTrackSprite(
           sprite,
           device,
-          oldSprite,
+          prevBarcode,
+          prevTime,
           pathList,
           allDistance,
           minBarcode,

--
Gitblit v1.9.1