diff --git a/api/100J/HBY100-J.js b/api/100J/HBY100-J.js index 7bd7e17..37eb207 100644 --- a/api/100J/HBY100-J.js +++ b/api/100J/HBY100-J.js @@ -663,7 +663,20 @@ class HBY100JProtocol { const DELAY_PACKET = 80; // 数据包间延时(ms),参考6155 const toHex = (arr) => Array.from(arr).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' '); console.log('[100J-蓝牙] 语音下发总大小:', total, '字节, fileType=', ft); - if (onProgress) onProgress(1); + // 进度单调递增:前段固定 2→8,数据段占 8~95,结束包 99→100,避免先 5% 再掉回 1% 的错觉 + let progressPeak = 0; + const emitProgress = (raw) => { + const n = Math.round(Number(raw)); + if (!Number.isFinite(n)) return; + const v = Math.min(100, Math.max(progressPeak, n)); + progressPeak = v; + if (onProgress) onProgress(v); + }; + if (total <= 0) { + emitProgress(100); + return Promise.resolve({ code: 200, msg: '语音文件已通过蓝牙上传' }); + } + emitProgress(2); const bleToolPromise = import('@/utils/BleHelper.js').then(m => m.default.getBleTool()); let bleRef = null; const send = (dataBytes, label = '') => { @@ -688,26 +701,30 @@ class HBY100JProtocol { bleRef = ble; ble.setVoiceUploading(true); return send(startData, ' 开始包') - .then(() => { if (onProgress) onProgress(3); return waitPromise; }) - .then(() => { if (onProgress) onProgress(5); return delay(DELAY_AFTER_START); }) + .then(() => waitPromise) + .then(() => delay(DELAY_AFTER_START)) .then(() => { + emitProgress(8); let seq = 0; const sendNext = (offset) => { if (offset >= total) { - return delay(DELAY_PACKET).then(() => send([ft, 2], ' 结束包')); + return delay(DELAY_PACKET) + .then(() => send([ft, 2], ' 结束包')) + .then(() => { emitProgress(99); }); } const chunk = bytes.slice(offset, Math.min(offset + chunkSize, total)); const chunkData = [ft, 1, seq & 0xFF, (seq >> 8) & 0xFF, ...chunk]; return send(chunkData, ` #${seq} 数据包`).then(() => { seq++; - if (onProgress) onProgress(Math.min(100, Math.floor((offset + chunk.length) / total * 100))); + const doneRatio = (offset + chunk.length) / total; + emitProgress(8 + Math.round(doneRatio * 87)); return delay(DELAY_PACKET).then(() => sendNext(offset + chunk.length)); }); }; return sendNext(0); }) .then(() => { - if (onProgress) onProgress(100); + emitProgress(100); return { code: 200, msg: '语音文件已通过蓝牙上传' }; }); }; diff --git a/pages/100J/audioManager/AudioList.vue b/pages/100J/audioManager/AudioList.vue index a02dd0a..6ec61c5 100644 --- a/pages/100J/audioManager/AudioList.vue +++ b/pages/100J/audioManager/AudioList.vue @@ -233,12 +233,37 @@ console.log("页面返回") }, onUnload() { - // 页面卸载时断开MQTT连接 + this.clearVoiceApplyTimers(); if (this.mqttClient) { this.mqttClient.disconnect(); } }, methods: { + /** 清除「使用」语音相关的全部定时器,避免返回上一页后仍触发 toast / 二次 navigateBack */ + clearVoiceApplyTimers() { + if (this._applyOverallTimer) { + clearTimeout(this._applyOverallTimer); + this._applyOverallTimer = null; + } + if (this.upgradeTimer) { + clearTimeout(this.upgradeTimer); + this.upgradeTimer = null; + } + if (this._applyNavigateTimer) { + clearTimeout(this._applyNavigateTimer); + this._applyNavigateTimer = null; + } + }, + scheduleNavigateBackAfterVoice(delayMs = 1500) { + if (this._applyNavigateTimer) { + clearTimeout(this._applyNavigateTimer); + this._applyNavigateTimer = null; + } + this._applyNavigateTimer = setTimeout(() => { + this._applyNavigateTimer = null; + uni.navigateBack(); + }, delayMs); + }, //语音管理列表(合并云端 + 本地无网络保存的语音) getinitData(val, isLoadMore = false) { const deviceId = this.device.deviceId; @@ -525,10 +550,7 @@ }, Apply(item, index) { this.updateProgress = 0; - if (this.upgradeTimer) { - clearTimeout(this.upgradeTimer); - this.upgradeTimer = null; - } + this.clearVoiceApplyTimers(); // 本地项在无网时禁止下发,仅弹窗(isUpdating 在确认可执行后再置 true) // 本地项优先用 localPath;云端项用 fileUrl(兼容多种字段名),相对路径补全 baseURL let fileUrl = ''; @@ -548,21 +570,35 @@ // 本地合并项 mergeLocal 会把路径写在 fileUrl,需带给接口层做 effectiveLocal 兜底 fileUrl: item._isLocal ? (typeof item.fileUrl === 'string' ? item.fileUrl : '') : fileUrl, localPath, - onProgress: (p) => { this.updateProgress = p; }, + onProgress: (p) => { + const n = Math.min(100, Math.max(0, Math.round(Number(p) || 0))); + const cur = Number(this.updateProgress) || 0; + this.updateProgress = Math.max(cur, n); + }, // 不传「蓝牙连接中」类提示:关蓝牙走 4G 时易误导;进度条 + 必要时全局请稍候即可 onWaiting: () => {} }; const runDeviceUpdate = () => { - const overallTimer = setTimeout(() => { + // 大文件蓝牙分片耗时可远超 2 分钟,整体超时放宽到 10 分钟(挂到实例上,便于 onUnload / 成功时清除) + const OVERALL_MS = 600000; + if (this._applyOverallTimer) { + clearTimeout(this._applyOverallTimer); + this._applyOverallTimer = null; + } + this._applyOverallTimer = setTimeout(() => { + this._applyOverallTimer = null; if (this.isUpdating) { - uni.showToast({ title: '操作超时', icon: 'none', duration: 2000 }); + uni.showToast({ title: '操作时间过长已中断,请重试或检查蓝牙连接', icon: 'none', duration: 2500 }); this.isUpdating = false; this.updateProgress = 0; } - }, 120000); + }, OVERALL_MS); // 进入列表时的蓝牙快照可能过期;与 HBY100 详情页一致,从 BleHelper 按 MAC 再对齐一次 sync100JBleProtocolFromHelper(this.device).then(() => deviceUpdateVoice(data)).then((RES) => { - clearTimeout(overallTimer); + if (this._applyOverallTimer) { + clearTimeout(this._applyOverallTimer); + this._applyOverallTimer = null; + } if (RES.code == 200) { // 蓝牙上传:进度已由 onProgress 更新,直接完成 if (RES._channel === 'ble') { @@ -576,37 +612,50 @@ this.syncVoiceListUseStatus(item); uni.showToast({ title, icon: RES._updateVoiceAfterBleFailed ? 'none' : 'success', duration: 2000 }); this.isUpdating = false; - setTimeout(() => { uni.navigateBack(); }, 1500); + this.scheduleNavigateBackAfterVoice(1500); return; } - // 4G:订阅 MQTT 获取设备端进度,6 秒超时 - this.upgradeTimer = setTimeout(() => { - if (this.isUpdating) { - uni.showToast({ title: '音频进度同步超时', icon: 'none', duration: 2000 }); + // 4G:MQTT 进度可能数十秒才上报,用「自上次进度起」滑动超时,避免误报 + const MQTT_IDLE_MS = 120000; + const armMqttIdle = () => { + if (this.upgradeTimer) clearTimeout(this.upgradeTimer); + this.upgradeTimer = setTimeout(() => { + if (!this.isUpdating) return; + uni.showToast({ + title: '长时间未收到设备进度,若语音已生效可返回查看', + icon: 'none', + duration: 3500 + }); this.isUpdating = false; this.updateProgress = 0; - } - }, 6000); + }, MQTT_IDLE_MS); + }; + armMqttIdle(); this.mqttClient = this.mqttClient || new MqttClient(); this.mqttClient.connect(() => { const statusTopic = `status/894078/HBY100/${this.device.deviceImei}`; this.mqttClient.subscribe(statusTopic, (payload) => { try { const payloadObj = typeof payload === 'string' ? JSON.parse(payload) : payload; - const progress = payloadObj.data?.progress; + const progress = payloadObj.data != null && payloadObj.data.progress !== undefined + ? payloadObj.data.progress + : payloadObj.progress; if (progress !== undefined && !isNaN(progress) && progress >= 0 && progress <= 100) { - this.updateProgress = progress; - if (progress === 100) { - clearTimeout(this.upgradeTimer); + armMqttIdle(); + const cur = Number(this.updateProgress) || 0; + this.updateProgress = Math.max(cur, Math.round(progress)); + if (Number(progress) === 100) { + if (this.upgradeTimer) clearTimeout(this.upgradeTimer); + this.upgradeTimer = null; this.syncVoiceListUseStatus(item); uni.showToast({ title: '音频上传成功', icon: 'success', duration: 2000 }); this.isUpdating = false; - setTimeout(() => { uni.navigateBack(); }, 1500); + this.scheduleNavigateBackAfterVoice(1500); } } } catch (e) { - clearTimeout(this.upgradeTimer); console.error('解析MQTT payload失败:', e); + armMqttIdle(); } }); }); @@ -615,7 +664,10 @@ uni.showToast({ title: RES.msg || '操作失败', icon: 'none', duration: 1000 }); } }).catch((err) => { - clearTimeout(overallTimer); + if (this._applyOverallTimer) { + clearTimeout(this._applyOverallTimer); + this._applyOverallTimer = null; + } this.isUpdating = false; this.updateProgress = 0; uni.showToast({ title: err.message || '操作失败', icon: 'none', duration: 2500 });