完善100J蓝牙
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
|
import Common from '@/utils/Common.js'
|
||||||
|
|
||||||
// ================== 蓝牙协议封装类 ==================
|
// ================== 蓝牙协议封装类 ==================
|
||||||
class HBY100JProtocol {
|
class HBY100JProtocol {
|
||||||
@ -205,45 +206,110 @@ class HBY100JProtocol {
|
|||||||
return reject(new Error('缺少文件地址或本地路径'));
|
return reject(new Error('缺少文件地址或本地路径'));
|
||||||
}
|
}
|
||||||
const isLocalPath = !/^https?:\/\//i.test(fileUrlOrLocalPath);
|
const isLocalPath = !/^https?:\/\//i.test(fileUrlOrLocalPath);
|
||||||
|
if (onProgress) onProgress(1);
|
||||||
const readFromPath = (path) => {
|
const readFromPath = (path) => {
|
||||||
if (typeof plus === 'undefined' || !plus.io) {
|
const doSend = (bytes) => {
|
||||||
reject(new Error('当前环境不支持文件读取'));
|
this._sendVoiceChunks(bytes, fileType, CHUNK_SIZE, onProgress)
|
||||||
return;
|
.then(resolve).catch(reject);
|
||||||
}
|
};
|
||||||
plus.io.resolveLocalFileSystemURL(path, (entry) => {
|
// App 端 getFileSystemManager 未实现,直接用 plus.io.requestFileSystem+getFile
|
||||||
|
readFromPathPlus(path, doSend, reject);
|
||||||
|
};
|
||||||
|
const readFileEntry = (entry, doSend, reject) => {
|
||||||
entry.file((file) => {
|
entry.file((file) => {
|
||||||
const reader = new plus.io.FileReader();
|
const reader = new plus.io.FileReader();
|
||||||
reader.onloadend = (e) => {
|
reader.onloadend = (e) => {
|
||||||
try {
|
try {
|
||||||
const buf = e.target.result;
|
const buf = e.target.result;
|
||||||
const bytes = new Uint8Array(buf);
|
const bytes = new Uint8Array(buf);
|
||||||
this._sendVoiceChunks(bytes, fileType, CHUNK_SIZE, onProgress)
|
doSend(bytes);
|
||||||
.then(resolve).catch(reject);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('[100J-蓝牙] 读取ArrayBuffer异常:', err);
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
reader.onerror = () => reject(new Error('读取文件失败'));
|
reader.onerror = () => reject(new Error('读取文件失败'));
|
||||||
reader.readAsArrayBuffer(file);
|
reader.readAsArrayBuffer(file);
|
||||||
}, (err) => reject(err));
|
}, (err) => reject(err));
|
||||||
|
};
|
||||||
|
const readFromPathPlus = (path, doSend, reject) => {
|
||||||
|
if (typeof plus === 'undefined' || !plus.io) {
|
||||||
|
console.error('[100J-蓝牙] 当前环境不支持文件读取(plus.io)');
|
||||||
|
reject(new Error('当前环境不支持文件读取'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// _downloads/ 用 requestFileSystem+getFile(避免 resolveLocalFileSystemURL 卡住)
|
||||||
|
if (path && path.startsWith('_downloads/')) {
|
||||||
|
const fileName = path.replace(/^_downloads\//, '');
|
||||||
|
plus.io.requestFileSystem(plus.io.PUBLIC_DOWNLOADS, (fs) => {
|
||||||
|
fs.root.getFile(fileName, {}, (entry) => readFileEntry(entry, doSend, reject), (err) => reject(err));
|
||||||
}, (err) => reject(err));
|
}, (err) => reject(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// _doc/ 用 requestFileSystem(PRIVATE_DOC),逐级 getDirectory 再 getFile(嵌套路径兼容)
|
||||||
|
if (path && path.startsWith('_doc/')) {
|
||||||
|
const relPath = path.replace(/^_doc\//, '');
|
||||||
|
const parts = relPath.split('/');
|
||||||
|
const fileName = parts.pop();
|
||||||
|
const dirs = parts;
|
||||||
|
plus.io.requestFileSystem(plus.io.PRIVATE_DOC, (fs) => {
|
||||||
|
let cur = fs.root;
|
||||||
|
const next = (i) => {
|
||||||
|
if (i >= dirs.length) {
|
||||||
|
cur.getFile(fileName, { create: false }, (entry) => readFileEntry(entry, doSend, reject), (err) => reject(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cur.getDirectory(dirs[i], { create: false }, (dir) => { cur = dir; next(i + 1); }, (err) => reject(err));
|
||||||
|
};
|
||||||
|
next(0);
|
||||||
|
}, (err) => reject(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 其他路径兜底
|
||||||
|
let resolvePath = path;
|
||||||
|
if (path && path.startsWith('/') && !path.startsWith('file://')) resolvePath = 'file://' + path;
|
||||||
|
plus.io.resolveLocalFileSystemURL(resolvePath, (entry) => readFileEntry(entry, doSend, reject), (err) => reject(err));
|
||||||
};
|
};
|
||||||
if (isLocalPath) {
|
if (isLocalPath) {
|
||||||
// 本地路径:无网络时直接读取
|
// 本地路径:无网络时直接读取
|
||||||
readFromPath(fileUrlOrLocalPath);
|
readFromPath(fileUrlOrLocalPath);
|
||||||
} else {
|
} else {
|
||||||
// 网络 URL:需下载后读取
|
// 网络 URL:优先用 uni.request 直接拉取 ArrayBuffer(类似 100 设备,无文件 IO),失败再走 downloadFile
|
||||||
uni.downloadFile({
|
let fetchUrl = fileUrlOrLocalPath;
|
||||||
url: fileUrlOrLocalPath,
|
if (fetchUrl.startsWith('http://')) fetchUrl = 'https://' + fetchUrl.slice(7);
|
||||||
|
uni.request({
|
||||||
|
url: fetchUrl,
|
||||||
|
method: 'GET',
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
timeout: 60000,
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.statusCode !== 200) {
|
if (res.statusCode === 200 && res.data) {
|
||||||
reject(new Error('下载失败: ' + res.statusCode));
|
const bytes = res.data instanceof ArrayBuffer ? new Uint8Array(res.data) : new Uint8Array(res.data || []);
|
||||||
|
if (bytes.length > 0) {
|
||||||
|
const doSend = (b) => {
|
||||||
|
this._sendVoiceChunks(b, fileType, CHUNK_SIZE, onProgress).then(resolve).catch(reject);
|
||||||
|
};
|
||||||
|
doSend(bytes);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
readFromPath(res.tempFilePath);
|
}
|
||||||
|
fallbackDownload();
|
||||||
|
},
|
||||||
|
fail: () => fallbackDownload()
|
||||||
|
});
|
||||||
|
const fallbackDownload = () => {
|
||||||
|
uni.downloadFile({
|
||||||
|
url: fetchUrl,
|
||||||
|
success: (res) => {
|
||||||
|
if (res.statusCode !== 200 || !res.tempFilePath) {
|
||||||
|
reject(new Error('下载失败: ' + (res.statusCode || '无路径')));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Common.moveFileToDownloads(res.tempFilePath).then((p) => readFromPath(p)).catch(() => readFromPath(res.tempFilePath));
|
||||||
},
|
},
|
||||||
fail: (err) => reject(err)
|
fail: (err) => reject(err)
|
||||||
});
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -251,41 +317,49 @@ class HBY100JProtocol {
|
|||||||
_sendVoiceChunks(bytes, fileType, chunkSize, onProgress) {
|
_sendVoiceChunks(bytes, fileType, chunkSize, onProgress) {
|
||||||
const total = bytes.length;
|
const total = bytes.length;
|
||||||
const ft = (fileType & 0xFF) || 1;
|
const ft = (fileType & 0xFF) || 1;
|
||||||
const DELAY_AFTER_START = 80; // 开始包后、等设备响应后再发的缓冲(ms)
|
const DELAY_AFTER_START = 200; // 开始包后、等设备响应后再发的缓冲(ms)
|
||||||
const DELAY_PACKET = 80; // 数据包间延时(ms),参考6155
|
const DELAY_PACKET = 200; // 数据包间延时(ms),设备收不全时适当加大
|
||||||
|
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(0);
|
||||||
const bleToolPromise = import('@/utils/BleHelper.js').then(m => m.default.getBleTool());
|
const bleToolPromise = import('@/utils/BleHelper.js').then(m => m.default.getBleTool());
|
||||||
const send = (dataBytes) => {
|
const send = (dataBytes, label = '') => {
|
||||||
const buf = new ArrayBuffer(dataBytes.length + 3);
|
const buf = new ArrayBuffer(dataBytes.length + 3);
|
||||||
const v = new Uint8Array(buf);
|
const v = new Uint8Array(buf);
|
||||||
v[0] = 0xFA;
|
v[0] = 0xFA;
|
||||||
v[1] = 0x05;
|
v[1] = 0x05;
|
||||||
for (let i = 0; i < dataBytes.length; i++) v[2 + i] = dataBytes[i];
|
for (let i = 0; i < dataBytes.length; i++) v[2 + i] = dataBytes[i];
|
||||||
v[v.length - 1] = 0xFF;
|
v[v.length - 1] = 0xFF;
|
||||||
|
const hex = toHex(v);
|
||||||
|
const preview = v.length <= 32 ? hex : hex.slice(0, 96) + '...';
|
||||||
|
console.log(`[100J-蓝牙] 发送${label} 共${v.length}字节:`, preview);
|
||||||
return bleToolPromise.then(ble => ble.sendData(this.bleDeviceId, buf, this.SERVICE_UUID, this.WRITE_UUID));
|
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 delay = (ms) => new Promise(r => setTimeout(r, ms));
|
||||||
// 开始包: FA 05 [fileType] [phase=0] [size 4B LE] FF
|
// 开始包: FA 05 [fileType] [phase=0] [size 4B LE] FF
|
||||||
const startData = [ft, 0, total & 0xFF, (total >> 8) & 0xFF, (total >> 16) & 0xFF, (total >> 24) & 0xFF];
|
const startData = [ft, 0, total & 0xFF, (total >> 8) & 0xFF, (total >> 16) & 0xFF, (total >> 24) & 0xFF];
|
||||||
const waitPromise = this.waitForFileResponse(1000);
|
const waitPromise = this.waitForFileResponse(1000);
|
||||||
return send(startData)
|
return send(startData, ' 开始包')
|
||||||
.then(() => waitPromise)
|
.then(() => { if (onProgress) onProgress(1); return waitPromise; })
|
||||||
.then(() => delay(DELAY_AFTER_START))
|
.then(() => { if (onProgress) onProgress(2); return delay(DELAY_AFTER_START); })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
let seq = 0;
|
let seq = 0;
|
||||||
const sendNext = (offset) => {
|
const sendNext = (offset) => {
|
||||||
if (offset >= total) {
|
if (offset >= total) {
|
||||||
return delay(DELAY_PACKET).then(() => send([ft, 2]));
|
return delay(DELAY_PACKET).then(() => send([ft, 2], ' 结束包'));
|
||||||
}
|
}
|
||||||
const chunk = bytes.slice(offset, Math.min(offset + chunkSize, total));
|
const chunk = bytes.slice(offset, Math.min(offset + chunkSize, total));
|
||||||
const chunkData = [ft, 1, seq & 0xFF, (seq >> 8) & 0xFF, ...chunk];
|
const chunkData = [ft, 1, seq & 0xFF, (seq >> 8) & 0xFF, ...chunk];
|
||||||
return send(chunkData).then(() => {
|
return send(chunkData, ` #${seq} 数据包`).then(() => {
|
||||||
seq++;
|
seq++;
|
||||||
if (onProgress) onProgress(Math.min(100, Math.floor((offset + chunk.length) / total * 100)));
|
const pct = Math.round((offset + chunk.length) / total * 100);
|
||||||
|
if (onProgress) onProgress(Math.min(99, Math.max(3, pct)));
|
||||||
return delay(DELAY_PACKET).then(() => sendNext(offset + chunk.length));
|
return delay(DELAY_PACKET).then(() => sendNext(offset + chunk.length));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return sendNext(0);
|
return sendNext(0);
|
||||||
})
|
})
|
||||||
|
.then(() => delay(DELAY_PACKET))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (onProgress) onProgress(100);
|
if (onProgress) onProgress(100);
|
||||||
return { code: 200, msg: '语音文件已通过蓝牙上传' };
|
return { code: 200, msg: '语音文件已通过蓝牙上传' };
|
||||||
@ -382,8 +456,8 @@ export function deviceDeleteAudioFile(params) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新语音,使用语音(优先蓝牙:有 fileUrl 或 localPath 且蓝牙连接时通过蓝牙上传,否则走 4G)
|
// 更新语音/使用语音:蓝牙优先,4G 兜底(不影响原有 4G 音频下发)
|
||||||
// localPath:无网络时本地文件路径,可直接通过蓝牙发送
|
// 有 fileUrl 或 localPath 且蓝牙可用时走蓝牙;否则或蓝牙失败时走 4G(与原先逻辑一致)
|
||||||
export function deviceUpdateVoice(data) {
|
export function deviceUpdateVoice(data) {
|
||||||
const httpExec = () => request({
|
const httpExec = () => request({
|
||||||
url: `/app/hby100j/device/updateVoice`,
|
url: `/app/hby100j/device/updateVoice`,
|
||||||
@ -396,9 +470,9 @@ export function deviceUpdateVoice(data) {
|
|||||||
const hasFileUrl = fileUrl && typeof fileUrl === 'string' && fileUrl.length > 0;
|
const hasFileUrl = fileUrl && typeof fileUrl === 'string' && fileUrl.length > 0;
|
||||||
const fileSource = hasLocalPath ? localPath : (hasFileUrl ? fileUrl : null);
|
const fileSource = hasLocalPath ? localPath : (hasFileUrl ? fileUrl : null);
|
||||||
if (!fileSource) {
|
if (!fileSource) {
|
||||||
return httpExec(); // 无文件源直接走 4G
|
return httpExec(); // 无文件源:直接 4G(原有逻辑)
|
||||||
}
|
}
|
||||||
const bleExec = () => protocolInstance.uploadVoiceFileBle(fileSource, 1, data.onProgress); // fileType=1 语音
|
const bleExec = () => protocolInstance.uploadVoiceFileBle(fileSource, 1, data.onProgress);
|
||||||
return execWithBleFirst(bleExec, httpExec, '语音文件上传');
|
return execWithBleFirst(bleExec, httpExec, '语音文件上传');
|
||||||
}
|
}
|
||||||
// 100J信息
|
// 100J信息
|
||||||
@ -409,31 +483,15 @@ export function deviceDetail(id) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先蓝牙:未连接时先尝试重连;蓝牙发送失败时回退4G
|
// 蓝牙优先、4G 兜底:未连接时尝试重连;蓝牙失败时回退 4G(保持原有 4G 通讯不变)
|
||||||
function execWithBleFirst(bleExec, httpExec, logName) {
|
function execWithBleFirst(bleExec, httpExec, logName) {
|
||||||
const doBle = () => {
|
const doBle = () => bleExec().then(res => ({ ...(res || {}), _channel: 'ble' }));
|
||||||
return bleExec().then(res => ({ ...(res || {}), _channel: 'ble' }));
|
const do4G = () => httpExec().then(res => { res._channel = '4g'; return res; });
|
||||||
};
|
|
||||||
const do4G = () => {
|
|
||||||
console.log('[100J-4G]', logName, '已通过HTTP发送', '(蓝牙不可用)');
|
|
||||||
return httpExec().then(res => { res._channel = '4g'; return res; });
|
|
||||||
};
|
|
||||||
if (protocolInstance.isBleConnected) {
|
if (protocolInstance.isBleConnected) {
|
||||||
console.log('[100J-蓝牙]', logName, '(连接正常)');
|
return doBle().catch(() => { console.log('[100J] 蓝牙失败,回退4G'); return do4G(); });
|
||||||
return doBle().catch(err => {
|
|
||||||
console.log('[100J] 蓝牙发送失败,回退4G', err);
|
|
||||||
return do4G();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return tryReconnectBle(2500).then(reconnected => {
|
return tryReconnectBle(2500).then(reconnected => {
|
||||||
if (reconnected) {
|
return reconnected ? doBle().catch(() => { console.log('[100J] 蓝牙失败,回退4G'); return do4G(); }) : do4G();
|
||||||
console.log('[100J-蓝牙]', logName, '(重连成功)');
|
|
||||||
return doBle().catch(err => {
|
|
||||||
console.log('[100J] 蓝牙发送失败,回退4G', err);
|
|
||||||
return do4G();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return do4G();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -106,6 +106,7 @@
|
|||||||
deviceDeleteAudioFile,
|
deviceDeleteAudioFile,
|
||||||
deviceUpdateVoice
|
deviceUpdateVoice
|
||||||
} from '@/api/100J/HBY100-J.js'
|
} from '@/api/100J/HBY100-J.js'
|
||||||
|
import { baseURL } from '@/utils/request.js'
|
||||||
import {
|
import {
|
||||||
showLoading,
|
showLoading,
|
||||||
hideLoading,
|
hideLoading,
|
||||||
@ -237,7 +238,9 @@
|
|||||||
if (!deviceId) return;
|
if (!deviceId) return;
|
||||||
const mergeLocal = (serverList) => {
|
const mergeLocal = (serverList) => {
|
||||||
const key = `100J_local_audio_${deviceId}`;
|
const key = `100J_local_audio_${deviceId}`;
|
||||||
|
const cacheKey = `100J_local_path_cache_${deviceId}`;
|
||||||
const localList = uni.getStorageSync(key) || [];
|
const localList = uni.getStorageSync(key) || [];
|
||||||
|
const pathCache = uni.getStorageSync(cacheKey) || {};
|
||||||
const localMapped = localList.map(item => ({
|
const localMapped = localList.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
fileNameExt: item.name || '本地语音',
|
fileNameExt: item.name || '本地语音',
|
||||||
@ -246,7 +249,12 @@
|
|||||||
useStatus: 0,
|
useStatus: 0,
|
||||||
_isLocal: true
|
_isLocal: true
|
||||||
}));
|
}));
|
||||||
return [...localMapped, ...(serverList || [])];
|
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];
|
||||||
};
|
};
|
||||||
deviceVoliceList({ deviceId }).then((res) => {
|
deviceVoliceList({ deviceId }).then((res) => {
|
||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
@ -486,10 +494,17 @@
|
|||||||
Apply(item, index) {
|
Apply(item, index) {
|
||||||
this.updateProgress = 0;
|
this.updateProgress = 0;
|
||||||
this.isUpdating = true;
|
this.isUpdating = true;
|
||||||
|
// 本地项优先用 localPath;云端项用 fileUrl(兼容多种字段名),相对路径补全 baseURL
|
||||||
|
let fileUrl = '';
|
||||||
|
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) : '';
|
||||||
|
}
|
||||||
|
const localPath = (item.localPath && typeof item.localPath === 'string') ? item.localPath : '';
|
||||||
const data = {
|
const data = {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
fileUrl: item._isLocal ? '' : (item.fileUrl || item.url),
|
fileUrl,
|
||||||
localPath: item._isLocal ? item.localPath : '',
|
localPath,
|
||||||
onProgress: (p) => { this.updateProgress = p; }
|
onProgress: (p) => { this.updateProgress = p; }
|
||||||
};
|
};
|
||||||
// 整体超时 60 秒(仅影响蓝牙上传,4G HTTP 很快返回)
|
// 整体超时 60 秒(仅影响蓝牙上传,4G HTTP 很快返回)
|
||||||
@ -502,11 +517,15 @@
|
|||||||
}, 60000);
|
}, 60000);
|
||||||
deviceUpdateVoice(data).then((RES) => {
|
deviceUpdateVoice(data).then((RES) => {
|
||||||
clearTimeout(overallTimer);
|
clearTimeout(overallTimer);
|
||||||
console.log(RES, 'RES');
|
|
||||||
if (RES.code == 200) {
|
if (RES.code == 200) {
|
||||||
|
// 更新列表选中状态:当前项设为使用中,其他项取消
|
||||||
|
const targetId = item.id || item.Id;
|
||||||
|
this.dataListA.forEach(it => {
|
||||||
|
it.useStatus = ((it.id || it.Id) === targetId) ? 1 : 0;
|
||||||
|
});
|
||||||
// 蓝牙上传:进度已由 onProgress 更新,直接完成
|
// 蓝牙上传:进度已由 onProgress 更新,直接完成
|
||||||
if (RES._channel === 'ble') {
|
if (RES._channel === 'ble') {
|
||||||
uni.showToast({ title: '升级完成!', icon: 'success', duration: 2000 });
|
uni.showToast({ title: '音频上传成功', icon: 'success', duration: 2000 });
|
||||||
this.isUpdating = false;
|
this.isUpdating = false;
|
||||||
setTimeout(() => { uni.navigateBack(); }, 1500);
|
setTimeout(() => { uni.navigateBack(); }, 1500);
|
||||||
return;
|
return;
|
||||||
@ -514,7 +533,7 @@
|
|||||||
// 4G:订阅 MQTT 获取设备端进度,6 秒超时
|
// 4G:订阅 MQTT 获取设备端进度,6 秒超时
|
||||||
this.upgradeTimer = setTimeout(() => {
|
this.upgradeTimer = setTimeout(() => {
|
||||||
if (this.isUpdating) {
|
if (this.isUpdating) {
|
||||||
uni.showToast({ title: '升级进度同步超时', icon: 'none', duration: 2000 });
|
uni.showToast({ title: '音频进度同步超时', icon: 'none', duration: 2000 });
|
||||||
this.isUpdating = false;
|
this.isUpdating = false;
|
||||||
this.updateProgress = 0;
|
this.updateProgress = 0;
|
||||||
}
|
}
|
||||||
@ -530,7 +549,12 @@
|
|||||||
this.updateProgress = progress;
|
this.updateProgress = progress;
|
||||||
if (progress === 100) {
|
if (progress === 100) {
|
||||||
clearTimeout(this.upgradeTimer);
|
clearTimeout(this.upgradeTimer);
|
||||||
uni.showToast({ title: '升级完成!', icon: 'success', duration: 2000 });
|
// 更新列表选中状态(4G 成功时)
|
||||||
|
const targetId = item.id || item.Id;
|
||||||
|
this.dataListA.forEach(it => {
|
||||||
|
it.useStatus = ((it.id || it.Id) === targetId) ? 1 : 0;
|
||||||
|
});
|
||||||
|
uni.showToast({ title: '音频上传成功', icon: 'success', duration: 2000 });
|
||||||
this.isUpdating = false;
|
this.isUpdating = false;
|
||||||
setTimeout(() => { uni.navigateBack(); }, 1500);
|
setTimeout(() => { uni.navigateBack(); }, 1500);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -422,12 +422,14 @@
|
|||||||
}, 1200);
|
}, 1200);
|
||||||
},
|
},
|
||||||
// 无网络时保存到本地,供蓝牙直接发送(不依赖 OSS)
|
// 无网络时保存到本地,供蓝牙直接发送(不依赖 OSS)
|
||||||
|
// 将临时文件复制到持久化目录 _doc/100J_audio/,避免被系统清理
|
||||||
saveLocalForBle(filePath) {
|
saveLocalForBle(filePath) {
|
||||||
const deviceId = these.Status.ID;
|
const deviceId = these.Status.ID;
|
||||||
if (!deviceId) return;
|
if (!deviceId) return;
|
||||||
|
const doSave = (persistentPath) => {
|
||||||
const item = {
|
const item = {
|
||||||
...these.cEdit,
|
...these.cEdit,
|
||||||
localPath: filePath,
|
localPath: persistentPath,
|
||||||
fileUrl: '',
|
fileUrl: '',
|
||||||
deviceId,
|
deviceId,
|
||||||
id: 'local_' + these.cEdit.Id,
|
id: 'local_' + these.cEdit.Id,
|
||||||
@ -441,6 +443,21 @@
|
|||||||
these.AudioData.tempFilePath = "";
|
these.AudioData.tempFilePath = "";
|
||||||
these.Status.isRecord = false;
|
these.Status.isRecord = false;
|
||||||
uni.navigateBack();
|
uni.navigateBack();
|
||||||
|
};
|
||||||
|
if (typeof plus !== 'undefined' && plus.io) {
|
||||||
|
const fileName = 'audio_' + (these.cEdit.Id || Date.now()) + '.mp3';
|
||||||
|
plus.io.resolveLocalFileSystemURL(filePath, (entry) => {
|
||||||
|
plus.io.resolveLocalFileSystemURL('_doc/', (docEntry) => {
|
||||||
|
docEntry.getDirectory('100J_audio', { create: true }, (dirEntry) => {
|
||||||
|
entry.copyTo(dirEntry, fileName, (newEntry) => {
|
||||||
|
doSave(newEntry.fullPath);
|
||||||
|
}, () => { doSave(filePath); });
|
||||||
|
}, () => { doSave(filePath); });
|
||||||
|
}, () => { doSave(filePath); });
|
||||||
|
}, () => { doSave(filePath); });
|
||||||
|
} else {
|
||||||
|
doSave(filePath);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// 保存录音并上传(已修复文件格式问题)
|
// 保存录音并上传(已修复文件格式问题)
|
||||||
uploadLuYin() {
|
uploadLuYin() {
|
||||||
@ -507,6 +524,19 @@
|
|||||||
}
|
}
|
||||||
const resData = JSON.parse(res.data);
|
const resData = JSON.parse(res.data);
|
||||||
if (resData.code === 200) {
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
// 合并两个存储操作
|
// 合并两个存储操作
|
||||||
Promise.all([
|
Promise.all([
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user