| | |
| | | |
| | | // 仅本地测试用 |
| | | 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 // 超过该秒数的样本不参与均速估计 |
| | | }; |
| | | /** |
| | | * 通用轮询器类 |
| | | * 封装带请求取消、平滑延迟计算、错误退避的轮询逻辑 |
| | |
| | | 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); |
| | |
| | | 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 |
| | | ) { |
| | | /** 条码锚点对象(init 与 sync 共用) */ |
| | | createBarcodeAnchorState(trackId, minBarcode, maxBarcode, totalSegmentCount, barcode, point, path, angle) { |
| | | return { |
| | | barcode, |
| | | x: point.x, |
| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | mappingInfo.angle |
| | | ); |
| | | }, |
| | | /** 条码设备每帧插值移动(Pixi ticker 回调) */ |
| | | tickBarcodeTrackSpriteMotion(sprite, pathList) { |
| | | 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; |
| | | } |
| | | /** 当前帧 dt(秒);ticker 不可用时按约 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 * 2, 20); |
| | | : 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 * 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 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 * 0.35; |
| | | sprite._motionSpeed = targetSpeed * MOTION.INITIAL_SPEED_RATIO; |
| | | } |
| | | const maxAccel = Math.max(baseV * 1.75, 22); |
| | | 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)); |
| | | 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) { |
| | | 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.mappingInfo.x, |
| | | sprite.mappingInfo.y |
| | | ); |
| | | if (remainAlong != null) { |
| | | let stepMag = Math.min(stepCap, Math.abs(remainAlong), restDistance); |
| | | if (stepMag < EPSILON && restDistance > EPSILON) { |
| | | 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 }; |
| | | const sl = G.calcDistance(lineStart, lineEnd); |
| | | if (sl > EPSILON) { |
| | | if (G.calcDistance(lineStart, lineEnd) <= EPSILON) return null; |
| | | 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) { |
| | | 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; |
| | | const restDistance = G.calcDistance(sprite, sprite.mappingInfo); |
| | | if (restDistance <= EPSILON) { |
| | | this.finishDeviceMotion(sprite); |
| | | return; |
| | | } |
| | | smoothDistance = Math.sign(hint) * stepMag; |
| | | } else { |
| | | const dt = this.getMotionFrameDt(); |
| | | if (dt <= 0) return; |
| | | |
| | | const stepCap = this.updateMotionSpeed(sprite, restDistance, dt); |
| | | const path = sprite.path; |
| | | let signedStep; |
| | | if (this.isSingleLineTrack(sprite, pathList)) { |
| | | signedStep = this.computeSingleLineSignedStep(sprite, path, stepCap, restDistance); |
| | | if (signedStep == null) { |
| | | this.finishDeviceMotion(sprite); |
| | | return; |
| | | } |
| | | } else { |
| | | smoothDistance = Math.sign(remainAlong) * stepMag; |
| | | signedStep = stepCap; |
| | | } |
| | | } else { |
| | | smoothDistance = stepCap; |
| | | } |
| | | } else { |
| | | smoothDistance = 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],max 与 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, |
| | |
| | | ); |
| | | 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); |
| | | }, |
| | | // 适配不同接口返回的数据结构 |
| | | deviceAdapter(type, res, trackType) { |
| | | const devices = this.parseBarcodeDevicesResponse(res); |
| | | const deviceTypeInfo = this.DEVICE_MAP[type]; |
| | | // 环穿接口 |
| | | if (trackType === 'annulus') { |
| | | return devices.map((device) => { |
| | | /** 环穿条码接口的设备条目 -> 内部统一字段 */ |
| | | adaptAnnulusBarcodeDevice(device, deviceTypeInfo) { |
| | | const index = +device.index; |
| | | const sprite = deviceTypeInfo.pixiMap.get(index); |
| | | if (!sprite) { |
| | | return {}; |
| | | } |
| | | if (!sprite) return null; |
| | | const trackInfoParse = G.safeParseJson(sprite.trackInfo.value); |
| | | const statusColorStr = device.statusColor.split('#')[1]; |
| | | const statusColor = parseInt(statusColorStr, 16); |
| | | // 后端返回形如 "#27AE60" 的颜色字符串 |
| | | const hex = device.statusColor ? device.statusColor.split('#')[1] : null; |
| | | return { |
| | | index, |
| | | statusColor, |
| | | sprite, |
| | | statusColor: hex != null ? parseInt(hex, 16) : null, |
| | | minBarcode: trackInfoParse.barCodeStart, |
| | | maxBarcode: trackInfoParse.barCodeEnd, |
| | | rgvPos: device.rgvPos, |
| | | taskNo: device.taskNo |
| | | }; |
| | | }); |
| | | } |
| | | // 原版接口 |
| | | return devices.map((device) => { |
| | | }, |
| | | /** 原版条码接口(按 idName + statusInfo + laserValue)-> 内部统一字段 */ |
| | | adaptOriginalBarcodeDevice(device, deviceTypeInfo) { |
| | | const index = +device[deviceTypeInfo.idName]; |
| | | const sprite = deviceTypeInfo.pixiMap.get(index); |
| | | if (!sprite) { |
| | | return {}; |
| | | } |
| | | if (!sprite) return null; |
| | | 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, |
| | | 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 = Array.isArray(res) ? res : res && res.code === 200 ? res.data : null; |
| | | if (!devices) return []; |
| | | const deviceTypeInfo = this.DEVICE_MAP[type]; |
| | | 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 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, |
| | |
| | | ); |
| | | continue; |
| | | } |
| | | // sync 需要"刷新前"的条码与时间戳来计算段差/瞬时速度,单独取两个标量比 spread 整个 sprite 便宜得多 |
| | | const prevBarcode = sprite.barcode; |
| | | const prevTime = sprite.time; |
| | | this.syncBarcodeTrackSprite( |
| | | sprite, |
| | | device, |
| | | oldSprite, |
| | | prevBarcode, |
| | | prevTime, |
| | | pathList, |
| | | allDistance, |
| | | minBarcode, |