From a2c8a98bc50f954c4a25469a744374534f54b50d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=AE=E5=BE=AE=E4=B8=80=E7=AC=91?= <709648985@qq.com> Date: Wed, 1 Apr 2026 09:23:47 +0800 Subject: [PATCH 1/6] =?UTF-8?q?100J=E4=BC=98=E5=8C=96=E8=AF=AD=E9=9F=B3?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/100J/audioManager/AudioList.vue | 101 +++++--------------------- pages/100J/audioManager/Recording.vue | 19 ----- 2 files changed, 20 insertions(+), 100 deletions(-) diff --git a/pages/100J/audioManager/AudioList.vue b/pages/100J/audioManager/AudioList.vue index 6ec61c5..8666259 100644 --- a/pages/100J/audioManager/AudioList.vue +++ b/pages/100J/audioManager/AudioList.vue @@ -264,30 +264,14 @@ uni.navigateBack(); }, delayMs); }, - //语音管理列表(合并云端 + 本地无网络保存的语音) + // 语音列表仅展示接口数据;上传落库后以服务端 URL 为准,不再写/读本地 path 与 BLE 字节缓存。 getinitData(val, isLoadMore = false) { const deviceId = this.device.deviceId; if (!deviceId) return; - const mergeLocal = (serverList) => { - const key = `100J_local_audio_${deviceId}`; - const cacheKey = `100J_local_path_cache_${deviceId}`; - const localList = uni.getStorageSync(key) || []; - const pathCache = uni.getStorageSync(cacheKey) || {}; - const localMapped = localList.map(item => ({ - ...item, - fileNameExt: item.name || '本地语音', - createTime: item._createTime || item.createTime || Common.DateFormat(new Date(), "yyyy年MM月dd日"), - fileUrl: item.fileUrl || item.localPath, - useStatus: 0, - _isLocal: true - })); - const enriched = (serverList || []).map(item => { - const urlKey = item.fileUrl || item.url || item.filePath || item.audioUrl || item.ossUrl; - const localPath = pathCache[urlKey] || pathCache[item.id]; - return localPath ? { ...item, localPath } : item; - }); - return [...localMapped, ...enriched]; - }; + try { + uni.removeStorageSync(`100J_local_path_cache_${deviceId}`); + uni.removeStorageSync(`100J_local_audio_${deviceId}`); + } catch (e) {} deviceVoliceList({ deviceId }).then((res) => { if (res.code == 200) { this.total = res.total; @@ -296,14 +280,13 @@ createTime: item.createTime || Common.DateFormat(new Date(), "yyyy年MM月dd日"), useStatus: Number(item.useStatus) === 1 ? 1 : 0 })); - this.dataListA = mergeLocal(list); - if (this.mescroll) this.mescroll.endBySize(this.dataListA.length, this.total + (this.dataListA.length - list.length)); + this.dataListA = list; + if (this.mescroll) this.mescroll.endBySize(this.dataListA.length, this.total); } }).catch(() => { - // 无网络时仅显示本地保存的语音 - this.dataListA = mergeLocal([]); - this.total = this.dataListA.length; - if (this.mescroll) this.mescroll.endBySize(this.dataListA.length, this.total); + this.dataListA = []; + this.total = 0; + if (this.mescroll) this.mescroll.endBySize(0, 0); }); }, createAudioPlayer(localPath) { @@ -456,22 +439,11 @@ return; } let task = () => { - if (item._isLocal) { - // 本地项:从本地存储移除 - const devId = this.device.deviceId; - const vid = (item.id != null && item.id !== '') ? item.id : item.fileId; - remove100JVoiceBleCache(devId, vid); - const key = `100J_local_audio_${devId}`; - let list = uni.getStorageSync(key) || []; - list = list.filter(l => l.id !== item.id && l.Id !== item.Id); - uni.setStorageSync(key, list); - uni.showToast({ title: '已删除', icon: 'none', duration: 1000 }); - this.getinitData(); - this.$refs.swipeAction.closeAll(); - return; - } - deviceDeleteAudioFile({ fileId: item.fileId, deviceId: this.device.deviceId }).then((res) => { + const devId = this.device.deviceId; + const vid = (item.id != null && item.id !== '') ? item.id : item.fileId; + deviceDeleteAudioFile({ fileId: item.fileId, deviceId: devId }).then((res) => { if (res.code == 200) { + if (devId && vid != null && vid !== '') remove100JVoiceBleCache(devId, vid); uni.showToast({ title: res.msg, icon: 'none', duration: 1000 }); this.getinitData(); this.$refs.swipeAction.closeAll(); @@ -551,25 +523,14 @@ Apply(item, index) { this.updateProgress = 0; this.clearVoiceApplyTimers(); - // 本地项在无网时禁止下发,仅弹窗(isUpdating 在确认可执行后再置 true) - // 本地项优先用 localPath;云端项用 fileUrl(兼容多种字段名),相对路径补全 baseURL - let fileUrl = ''; - let localPath = (item.localPath && typeof item.localPath === 'string') ? item.localPath : ''; - if (!item._isLocal) { - const raw = item.fileUrl || item.url || item.filePath || item.audioUrl || item.ossUrl || ''; - fileUrl = (typeof raw === 'string' && raw) ? (raw.startsWith('/') ? (baseURL + raw) : raw) : ''; - } else { - // 本地项:localPath 优先;mergeLocal 可能把路径放在 fileUrl,但勿把 http 当成本地路径 - if (!localPath && item.fileUrl) { - const cand = String(item.fileUrl).trim(); - if (cand && !/^https?:\/\//i.test(cand)) localPath = cand; - } - } + const raw = item.fileUrl || item.url || item.filePath || item.audioUrl || item.ossUrl || ''; + const fileUrl = (typeof raw === 'string' && raw) + ? (raw.startsWith('/') ? (baseURL + raw) : raw) + : ''; const data = { id: (item.id != null && item.id !== '') ? item.id : item.fileId, - // 本地合并项 mergeLocal 会把路径写在 fileUrl,需带给接口层做 effectiveLocal 兜底 - fileUrl: item._isLocal ? (typeof item.fileUrl === 'string' ? item.fileUrl : '') : fileUrl, - localPath, + fileUrl, + localPath: '', onProgress: (p) => { const n = Math.min(100, Math.max(0, Math.round(Number(p) || 0))); const cur = Number(this.updateProgress) || 0; @@ -673,28 +634,6 @@ uni.showToast({ title: err.message || '操作失败', icon: 'none', duration: 2500 }); }); }; - if (item._isLocal) { - uni.getNetworkType({ - success: (net) => { - if (net.networkType === 'none') { - uni.showModal({ - title: '无法使用', - content: '无网保存的本地语音无法通过蓝牙下发。请先连接 WiFi 或移动网络后,重新录制并保存(上传云端),再点「使用」。', - showCancel: false, - confirmText: '知道了' - }); - return; - } - this.isUpdating = true; - runDeviceUpdate(); - }, - fail: () => { - this.isUpdating = true; - runDeviceUpdate(); - } - }); - return; - } this.isUpdating = true; runDeviceUpdate(); }, diff --git a/pages/100J/audioManager/Recording.vue b/pages/100J/audioManager/Recording.vue index 2ff2e10..3f59151 100644 --- a/pages/100J/audioManager/Recording.vue +++ b/pages/100J/audioManager/Recording.vue @@ -137,9 +137,6 @@ updateLoading } from '@/utils/loading.js'; import Common from '@/utils/Common.js'; - import { - cache100JVoiceFileForBle - } from '@/api/100J/HBY100-J.js'; export default { data() { @@ -523,22 +520,6 @@ } const resData = JSON.parse(res.data); if (resData.code === 200) { - // 缓存本地路径,Apply 时优先用本地文件走蓝牙,避免下载失败 - const deviceId = these.Status.ID; - if (deviceId) { - const cacheKey = `100J_local_path_cache_${deviceId}`; - const d = resData.data; - const fileUrl = (d && typeof d === 'object' && d.fileUrl) || (typeof d === 'string' ? d : ''); - if (filePath) { - let cache = uni.getStorageSync(cacheKey) || {}; - if (fileUrl) cache[fileUrl] = filePath; - if (d && typeof d === 'object' && d.id) cache[d.id] = filePath; - uni.setStorageSync(cacheKey, cache); - if (d && typeof d === 'object' && d.id) { - cache100JVoiceFileForBle(deviceId, d.id, filePath); - } - } - } // 合并两个存储操作 Promise.all([ new Promise((resolve, reject) => { From 64216eaa7515ecaabe6ef1484efc60248cd4d97f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=AE=E5=BE=AE=E4=B8=80=E7=AC=91?= <709648985@qq.com> Date: Wed, 1 Apr 2026 09:42:40 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=93=9D=E7=89=99?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/BleHelper.js | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/utils/BleHelper.js b/utils/BleHelper.js index 6795947..6ac8be8 100644 --- a/utils/BleHelper.js +++ b/utils/BleHelper.js @@ -630,20 +630,38 @@ class BleHelper { } return new Promise((resolve, reject) => { - if (this.data.isOpenBlue) { + const runInit = () => { + this.CheckBlue().then((res) => { + return init(); + }).then(resolve).catch((ex) => { + console.error("异常:", ex); + reject(ex); + }); + }; + if (!this.data.isOpenBlue) { + runInit(); + return; + } + // 已打开过仍须向系统确认可用:关蓝牙再开后 isOpenBlue 可能未及时被置 false,或状态回调未送达 + if (typeof uni.getBluetoothAdapterState !== 'function') { resolve(); return; } - - this.CheckBlue().then((res) => { - // console.log("res=", res) - return init(); - }).then(resolve).catch((ex) => { - console.error("异常:", ex); - reject(ex); + uni.getBluetoothAdapterState({ + success: (info) => { + this.data.available = !!info.available; + if (info.available) { + resolve(); + return; + } + this.data.isOpenBlue = false; + runInit(); + }, + fail: () => { + this.data.isOpenBlue = false; + runInit(); + } }); - - }); } @@ -865,6 +883,9 @@ class BleHelper { if (!state.available) { //蓝牙状态不可用了,将所有设备标记为断开连接 + // 系统关蓝牙后原生 BLE 适配器已销毁;若仍认为 isOpenBlue=true,OpenBlue 会跳过重开, + // 再开蓝牙后无法 createBLEConnection / 一直报 10001 适配器不可用 + this.data.isOpenBlue = false; console.log("蓝牙模块不可用了,将所有设备标记为断开连接"); this.data.connectingDevices = {}; //清空连接锁 this.data.LinkedList.filter((v) => { From 0c54f550b98c99d6bf426b7b78a361a5afb9a0f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=AE=E5=BE=AE=E4=B8=80=E7=AC=91?= <709648985@qq.com> Date: Wed, 1 Apr 2026 10:17:40 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D100J=E8=AF=AD=E9=9F=B3?= =?UTF-8?q?=E4=BC=A0=E8=BE=93=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/100J/HBY100-J.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/api/100J/HBY100-J.js b/api/100J/HBY100-J.js index 76f4448..ad9604c 100644 --- a/api/100J/HBY100-J.js +++ b/api/100J/HBY100-J.js @@ -1058,6 +1058,9 @@ export function deviceUpdateVoice(data) { const hasLocalPath = effectiveLocal.length > 0 || hasBleCache; const remoteUrl = isHttpUrlString(fu) ? fu : (isHttpUrlString(lp) ? lp : ''); const fileSource = hasLocalPath ? (effectiveLocal || BLE_CACHE_SENTINEL) : (remoteUrl || null); + // 仅「没有任何云端 URL、只能靠本机路径/缓存发二进制」时禁止 4G 兜底。 + // 若列表项带 https,即使用户曾下发过而残留 put100JVoiceBleCache,仍应允许走 4G(设备按 id 拉 OSS),否则会误报「本地语音需通过蓝牙下发」。 + const no4GFallback = !remoteUrl && hasLocalPath; if (!fileSource) { console.log('[100J] 语音上传:无 fileUrl/localPath,仅 HTTP updateVoice(不会走蓝牙传文件)'); return httpExec(0).then((res) => { if (res && typeof res === 'object') res._channel = '4g'; return res; }); @@ -1083,8 +1086,8 @@ export function deviceUpdateVoice(data) { }) ); const http4g = () => httpExec(0); - // 本地文件:禁止一切 4G 兜底(含蓝牙未开时),避免仅传 id 假成功 - return execWithBleFirst(bleExec, http4g, '语音文件上传', data.onWaiting, { no4GFallback: hasLocalPath }); + // 无云端 URL 的纯本地文件:禁止 4G 兜底,避免仅传 id 假成功;有 https 时蓝牙失败可 4G + return execWithBleFirst(bleExec, http4g, '语音文件上传', data.onWaiting, { no4GFallback }); } // 100J信息 export function deviceDetail(id) { From 0469abe228724ef7ae08931241f0f44cd3e3d497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=AE=E5=BE=AE=E4=B8=80=E7=AC=91?= <709648985@qq.com> Date: Wed, 1 Apr 2026 15:01:23 +0800 Subject: [PATCH 4/6] =?UTF-8?q?100J=E5=A2=9E=E5=8A=A0=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E8=AF=AD=E9=9F=B3=E7=AD=89=E5=BE=85=E8=AE=BE=E5=A4=87=E5=86=99?= =?UTF-8?q?=E5=85=A5=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/100J/HBY100-J.js | 41 +++++++++++++++++++++++++-- pages/100J/audioManager/AudioList.vue | 3 +- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/api/100J/HBY100-J.js b/api/100J/HBY100-J.js index ad9604c..4f8fd44 100644 --- a/api/100J/HBY100-J.js +++ b/api/100J/HBY100-J.js @@ -210,7 +210,7 @@ class HBY100JProtocol { } break; case 0x05: - // 05: 文件更新响应 FB 05 [fileType] [status] FF,status: 1=成功 2=失败 + // 05: 文件更新响应 FB 05 [fileType] [status] FF,status: 1=成功 2=失败(含开始/结束等阶段应答,以固件为准) if (data.length >= 1) result.fileType = data[0]; if (data.length >= 2) result.fileStatus = data[1]; // 1=Success, 2=Failure if (!skipSideEffects && this._fileResponseResolve) this._fileResponseResolve(result); @@ -704,6 +704,16 @@ class HBY100JProtocol { return bleToolPromise.then(ble => ble.sendData(this.bleDeviceId, buf, this.SERVICE_UUID, this.WRITE_UUID)); }; const delay = (ms) => new Promise(r => setTimeout(r, ms)); + const showTailWriteLoading = () => { + try { + uni.showLoading({ title: '设备写入中…', mask: true }); + } catch (e) {} + }; + const hideTailWriteLoading = () => { + try { + uni.hideLoading(); + } catch (e) {} + }; // 开始包: FA 05 [fileType] [phase=0] [size 4B LE] FF const startData = [ft, 0, total & 0xFF, (total >> 8) & 0xFF, (total >> 16) & 0xFF, (total >> 24) & 0xFF]; // 单包约 507B(500 负载),依赖 MTU;Android 上为整包 write @@ -720,9 +730,30 @@ class HBY100JProtocol { let seq = 0; const sendNext = (offset) => { if (offset >= total) { + // 结束包 FA 05 01 02 FF:协议(10) 设备应答 FB 05;尾包后设备需落盘,按约 7KB/s 估算等待,避免未写完就超时误走 4G + const WRITE_BPS = 7 * 1024; + const END_ACK_MS = Math.min(600000, Math.max(25000, Math.ceil(total / WRITE_BPS) * 1000 + 25000)); + console.log('[100J-蓝牙] 尾包后等待设备写入应答,超时', END_ACK_MS, 'ms(按约 7KB/s 估算落盘)'); return delay(DELAY_PACKET) .then(() => send([ft, 2], ' 结束包')) - .then(() => { emitProgress(99); }); + .then(() => { + showTailWriteLoading(); + return this.waitForFileResponse(END_ACK_MS); + }) + .then((ack) => { + if (ack && ack.fileStatus === 2) { + return Promise.reject(new Error('设备写入失败,请重试')); + } + if (ack && ack.fileStatus === 1) { + console.log('[100J-蓝牙] 结束包后设备确认写入成功 FB 05'); + emitProgress(99); + return; + } + return Promise.reject(new Error('设备写入确认超时,请稍后重试或靠近设备')); + }) + .finally(() => { + hideTailWriteLoading(); + }); } const chunk = bytes.slice(offset, Math.min(offset + chunkSize, total)); const chunkData = [ft, 1, seq & 0xFF, (seq >> 8) & 0xFF, ...chunk]; @@ -1119,6 +1150,12 @@ function execWithBleFirst(bleExec, httpExec, logName, onWaiting, opts = {}) { if (no4G) { return Promise.reject(e instanceof Error && e.message ? e : new Error(String((e && e.message) || '蓝牙发送语音失败,请靠近设备后重试'))); } + const msg = (e && e.message) ? String(e.message) : String(e || ''); + // 分片已发完、仅尾包后等设备落盘应答失败:不应再 4G updateVoice(文件已走蓝牙,重复下发会误导) + if (msg.indexOf('设备写入确认超时') !== -1 || msg.indexOf('设备写入失败') !== -1) { + console.log('[100J]', logName || '指令', '蓝牙语音尾包阶段失败,不回退4G:', msg); + return Promise.reject(e instanceof Error && e.message ? e : new Error(msg)); + } console.log('[100J]', logName || '指令', '蓝牙失败,回退4G'); return do4G(); }; diff --git a/pages/100J/audioManager/AudioList.vue b/pages/100J/audioManager/AudioList.vue index 8666259..827c890 100644 --- a/pages/100J/audioManager/AudioList.vue +++ b/pages/100J/audioManager/AudioList.vue @@ -567,9 +567,10 @@ clearTimeout(this.upgradeTimer); this.upgradeTimer = null; } + // 蓝牙链:尾包 FA 05 01 02 FF 后已收到 FB 05 成功应答,再调 updateVoice,此处再 toast 并返回上一页 const title = RES._updateVoiceAfterBleFailed ? '蓝牙已下发,云端同步失败可稍后重试' - : '音频上传成功'; + : '设备已确认写入'; this.syncVoiceListUseStatus(item); uni.showToast({ title, icon: RES._updateVoiceAfterBleFailed ? 'none' : 'success', duration: 2000 }); this.isUpdating = false; From d2c2e42d0654b82ec25dd498caacdecafcaa5fa3 Mon Sep 17 00:00:00 2001 From: fengerli <528575642@qq.com> Date: Wed, 1 Apr 2026 15:05:18 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E8=AF=AD=E9=9F=B3=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/100J/audioManager/AudioList.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pages/100J/audioManager/AudioList.vue b/pages/100J/audioManager/AudioList.vue index 8666259..504e4f6 100644 --- a/pages/100J/audioManager/AudioList.vue +++ b/pages/100J/audioManager/AudioList.vue @@ -392,7 +392,7 @@ let data = { fileName: this.cEdit.fileNameExt, deviceId: this.device.deviceId, - fileId: item.fileId + id: item.id } videRenameAudioFile(data).then((res) => { console.log('res'); @@ -440,8 +440,8 @@ } let task = () => { const devId = this.device.deviceId; - const vid = (item.id != null && item.id !== '') ? item.id : item.fileId; - deviceDeleteAudioFile({ fileId: item.fileId, deviceId: devId }).then((res) => { + const vid = (item.id != null && item.id !== '') ? item.id : item.id; + deviceDeleteAudioFile({ id: item.id, deviceId: devId }).then((res) => { if (res.code == 200) { if (devId && vid != null && vid !== '') remove100JVoiceBleCache(devId, vid); uni.showToast({ title: res.msg, icon: 'none', duration: 1000 }); @@ -528,7 +528,7 @@ ? (raw.startsWith('/') ? (baseURL + raw) : raw) : ''; const data = { - id: (item.id != null && item.id !== '') ? item.id : item.fileId, + id: item.id, fileUrl, localPath: '', onProgress: (p) => { From 6ba4df49c15ce9e9b3b08d4bead8fb25f85f50a2 Mon Sep 17 00:00:00 2001 From: fengerli <528575642@qq.com> Date: Wed, 1 Apr 2026 15:46:42 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/100J/HBY100-J.vue | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/pages/100J/HBY100-J.vue b/pages/100J/HBY100-J.vue index a28483f..c21712f 100644 --- a/pages/100J/HBY100-J.vue +++ b/pages/100J/HBY100-J.vue @@ -64,7 +64,7 @@ {{ deviceInfo && deviceInfo.longitude ? Number(deviceInfo.longitude).toFixed(4) : '' }} {{ deviceInfo && deviceInfo.latitude ? Number(deviceInfo.latitude).toFixed(4) : '' }} - + @@ -1097,6 +1097,7 @@ }); }); } else if (prevVoiceType === '7' && val !== '7' && val !== '-1') { + console.log('走到这里了没有'); // 从「播放语音」切到其它内置音色:先关播报;报警未开启时不走 forceAlarm,仅 UI 预选音色 const data = { deviceId: this.deviceInfo.deviceId, @@ -1105,14 +1106,16 @@ voiceStrobeAlarm: this.deviceInfo.voiceStrobeAlarm }; deviceVoiceBroadcast(data).then((res) => { + this.formData.sta_VoiceType = val if (res.code == 200) { uni.showToast({ - title: res.msg || '已切换', + title: res.msg, icon: 'none' }); + } else { uni.showToast({ - title: res.msg || '操作失败', + title: res.msg, icon: 'none' }); } @@ -1132,11 +1135,17 @@ const isClose = item === 0; // 与「已解除不再重复关报警」对称:已在报警中不再弹窗重复下发「开启」,未报警时不再重复「解除」 if (!isClose && this.deviceInfo.voiceStrobeAlarm === 1) { - uni.showToast({ title: '当前已在报警中', icon: 'none' }); + uni.showToast({ + title: '当前已在报警中', + icon: 'none' + }); return; } if (isClose && this.deviceInfo.voiceStrobeAlarm !== 1) { - uni.showToast({ title: '当前未在报警中', icon: 'none' }); + uni.showToast({ + title: '当前未在报警中', + icon: 'none' + }); return; } if (!this.Status) this.Status = {};