1170 lines
44 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

if (typeof Promise !== "undefined" && !Promise.prototype.finally) {
Promise.prototype.finally = function(callback) {
const promise = this.constructor;
return this.then(
(value) => promise.resolve(callback()).then(() => value),
(reason) => promise.resolve(callback()).then(() => {
throw reason;
})
);
};
}
;
if (typeof uni !== "undefined" && uni && uni.requireGlobal) {
const global = uni.requireGlobal();
ArrayBuffer = global.ArrayBuffer;
Int8Array = global.Int8Array;
Uint8Array = global.Uint8Array;
Uint8ClampedArray = global.Uint8ClampedArray;
Int16Array = global.Int16Array;
Uint16Array = global.Uint16Array;
Int32Array = global.Int32Array;
Uint32Array = global.Uint32Array;
Float32Array = global.Float32Array;
Float64Array = global.Float64Array;
BigInt64Array = global.BigInt64Array;
BigUint64Array = global.BigUint64Array;
}
;
if (uni.restoreGlobal) {
uni.restoreGlobal(Vue, weex, plus, setTimeout, clearTimeout, setInterval, clearInterval);
}
(function(vue) {
"use strict";
function formatAppLog(type, filename, ...args) {
if (uni.__log__) {
uni.__log__(type, filename, ...args);
} else {
console[type].apply(console, [...args, filename]);
}
}
const _export_sfc = (sfc, props) => {
const target = sfc.__vccOpts || sfc;
for (const [key, val] of props) {
target[key] = val;
}
return target;
};
const _sfc_main$2 = {
data() {
return {
foundDevices: [],
isScanning: false,
bluetoothEnabled: false,
connectedDeviceId: ""
};
},
onLoad() {
this.initBluetooth();
},
onUnload() {
this.stopScan();
uni.closeBluetoothAdapter();
uni.offBluetoothDeviceFound(this.onDeviceFound);
},
methods: {
// 初始化蓝牙适配器
initBluetooth() {
uni.openBluetoothAdapter({
success: () => {
this.bluetoothEnabled = true;
},
fail: (err) => {
this.bluetoothEnabled = false;
uni.showModal({
title: "蓝牙开启失败",
content: "请检查设备蓝牙是否开启",
showCancel: false
});
formatAppLog("error", "at pages/index/index.vue:88", "蓝牙初始化失败:", err);
}
});
},
// 切换扫描状态(开始/停止)
toggleScan() {
if (!this.bluetoothEnabled) {
this.initBluetooth();
return;
}
if (this.isScanning) {
this.stopScan();
} else {
this.startScan();
}
},
// 开始扫描设备
startScan() {
this.isScanning = true;
this.foundDevices = [];
uni.startBluetoothDevicesDiscovery({
services: [],
allowDuplicatesKey: false,
// 不允许重复上报
success: () => {
uni.showToast({ title: "开始扫描", icon: "none" });
uni.onBluetoothDeviceFound(this.onDeviceFound);
},
fail: (err) => {
this.isScanning = false;
uni.showToast({ title: "扫描失败", icon: "none" });
formatAppLog("error", "at pages/index/index.vue:124", "扫描失败:", err);
}
});
setTimeout(() => {
if (this.isScanning)
this.stopScan();
}, 5e3);
},
// 停止扫描
stopScan() {
if (!this.isScanning)
return;
uni.stopBluetoothDevicesDiscovery({
success: () => {
this.isScanning = false;
if (this.foundDevices.length == 0) {
uni.showToast({
title: `暂未扫描到任何设备`,
icon: "none"
});
return;
}
uni.showToast({
title: `扫描完成,发现${this.foundDevices.length}台设备`,
icon: "none"
});
},
fail: (err) => {
formatAppLog("error", "at pages/index/index.vue:154", "停止扫描失败:", err);
}
});
},
// 设备发现回调(处理去重和数据格式化)
onDeviceFound(res) {
const devices = res.devices || [];
devices.forEach((device) => {
if (!device.deviceId)
return;
const isExist = this.foundDevices.some(
(d) => d.deviceId === device.deviceId
);
if (isExist)
return;
var is_bj = false;
const advData = new Uint8Array(device.advertisData);
const devicenameData = advData.slice(2, 6);
const devicename = String.fromCharCode(...devicenameData);
if (devicename == "dzbj")
is_bj = true;
this.foundDevices.push({
is_bj,
name: device.name || "未知设备",
deviceId: device.deviceId,
rssi: device.RSSI,
connected: device.deviceId === this.connectedDeviceId
});
});
},
// 计算信号强度条宽度(-100dBm为0%-30dBm为100%
getRssiWidth(rssi) {
const minRssi = -100;
const maxRssi = -30;
let ratio = (rssi - minRssi) / (maxRssi - minRssi);
ratio = Math.max(0, Math.min(1, ratio));
return `${ratio * 100}%`;
},
connectDevice(device) {
var that = this;
this.stopScan();
uni.showLoading({ title: "连接中..." });
uni.createBLEConnection({
deviceId: device.deviceId,
success(res) {
that.foundDevices = that.foundDevices.map((d) => ({
...d,
connected: d.deviceId === device.deviceId
}));
uni.hideLoading();
uni.showToast({ title: `已连接${device.name}`, icon: "none" });
uni.navigateTo({
url: "../connect/connect?deviceId=" + device.deviceId,
fail: (err) => {
uni.showToast({ title: `连接失败,请稍后重试`, icon: "error" });
uni.closeBLEConnection({
deviceId: device.deviceId,
success() {
console("断开连接成功");
}
});
}
});
},
fail() {
uni.hideLoading();
uni.showToast({ title: `连接失败`, icon: "error" });
}
});
}
}
};
function _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) {
return vue.openBlock(), vue.createElementBlock("view", { class: "container" }, [
vue.createElementVNode("view", { class: "title-bar" }, [
vue.createElementVNode("text", { class: "title" }, "连接设备"),
vue.createElementVNode("button", {
class: "scan-btn",
disabled: $data.isScanning,
onClick: _cache[0] || (_cache[0] = (...args) => $options.toggleScan && $options.toggleScan(...args))
}, vue.toDisplayString($data.isScanning ? "停止扫描" : "开始扫描"), 9, ["disabled"])
]),
$data.isScanning ? (vue.openBlock(), vue.createElementBlock("view", {
key: 0,
class: "status-tip"
}, [
vue.createElementVNode("text", null, "正在扫描设备..."),
vue.createElementVNode("view", { class: "loading" })
])) : $data.foundDevices.length === 0 ? (vue.openBlock(), vue.createElementBlock("view", {
key: 1,
class: "status-tip"
}, [
vue.createElementVNode("text", null, '未发现设备,请点击"开始扫描"')
])) : vue.createCommentVNode("v-if", true),
vue.createElementVNode("view", { class: "device-list" }, [
(vue.openBlock(true), vue.createElementBlock(
vue.Fragment,
null,
vue.renderList($data.foundDevices, (device, index) => {
return vue.openBlock(), vue.createElementBlock("view", {
class: "device-item",
key: device.deviceId,
onClick: ($event) => $options.connectDevice(device)
}, [
vue.createElementVNode("view", { class: "device-name" }, [
vue.createElementVNode(
"text",
null,
vue.toDisplayString(device.name || "未知设备"),
1
/* TEXT */
),
device.is_bj ? (vue.openBlock(), vue.createElementBlock("view", {
key: 0,
class: "is_bj"
}, "吧唧")) : vue.createCommentVNode("v-if", true),
vue.createElementVNode(
"text",
{ class: "device-id" },
vue.toDisplayString(device.deviceId),
1
/* TEXT */
)
]),
vue.createElementVNode("view", { class: "device-rssi" }, [
vue.createElementVNode(
"text",
null,
"信号: " + vue.toDisplayString(device.rssi) + " dBm",
1
/* TEXT */
),
vue.createElementVNode(
"view",
{
class: "rssi-bar",
style: vue.normalizeStyle({ width: $options.getRssiWidth(device.rssi) })
},
null,
4
/* STYLE */
)
]),
device.connected ? (vue.openBlock(), vue.createElementBlock("view", {
key: 0,
class: "device-status"
}, [
vue.createElementVNode("text", { class: "connected" }, "已连接")
])) : vue.createCommentVNode("v-if", true)
], 8, ["onClick"]);
}),
128
/* KEYED_FRAGMENT */
))
])
]);
}
const PagesIndexIndex = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["render", _sfc_render$1], ["__scopeId", "data-v-1cf27b2a"], ["__file", "/Users/rdzleo/Desktop/uniapp_code/pages/index/index.vue"]]);
const _sfc_main$1 = {
data() {
return {
// 图片相关
uploadedImages: [],
// 已上传的图片列表
currentImageUrl: "",
// 当前选中的图片
currentImageIndex: -1,
// 当前选中的图片索引
imageDir: "",
// 图片保存目录
// BLE设备数据
isConnected: true,
// 是否连接
isScanning: false,
// 是否正在扫描
batteryLevel: 0,
// 电量
temperature: 0,
// 温度
humidity: 0,
// 湿度
faceStatus: 0,
// 表盘状态 0-正常 1-更新中 2-异常
// statusPotColor:'Green',
deviceId: "",
imageServiceuuid: "",
imageWriteuuid: "",
imageEdituuid: "",
isSending: false,
transferProgress: 0,
// 模拟数据定时器
dataTimer: null
};
},
computed: {
// 表盘状态文本
faceStatusText() {
switch (this.faceStatus) {
case 0:
return "正常";
case 1:
return "更新中";
case 2:
return "异常";
default:
return "未知";
}
},
// 电池颜色(根据电量变化)
batteryColor() {
if (this.batteryLevel > 70)
return "#52c41a";
if (this.batteryLevel > 30)
return "#faad14";
return "#ff4d4f";
}
},
onLoad(options) {
this.deviceId = options.deviceId;
this.initImageDir();
this.loadSavedImages();
this.setBleMtu();
},
onUnload() {
this.stopDataSimulation();
if (this.isConnected) {
this.disconnectDevice();
}
},
methods: {
// BLE 单次写入(内部方法)
_bleWriteOnce(characteristicId, buffer, writeType) {
return new Promise((resolve, reject) => {
uni.writeBLECharacteristicValue({
deviceId: this.deviceId,
serviceId: this.imageServiceuuid,
characteristicId,
value: buffer,
writeType: writeType || "writeNoResponse",
success: resolve,
fail: reject
});
});
},
// BLE 写入封装带重试writeNoResponse 失败自动降级为 write
async bleWrite(characteristicId, buffer) {
const MAX_RETRY = 3;
for (let i = 0; i < MAX_RETRY; i++) {
try {
await this._bleWriteOnce(characteristicId, buffer, "writeNoResponse");
return;
} catch (err) {
if (i < MAX_RETRY - 1) {
await this.delay(20 * (i + 1));
} else {
try {
await this._bleWriteOnce(characteristicId, buffer, "write");
return;
} catch (fallbackErr) {
throw fallbackErr;
}
}
}
}
},
// 延时
delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
},
// 读取图片文件为 ArrayBuffer
readImageAsArrayBuffer(filePath) {
return new Promise((resolve, reject) => {
formatAppLog("log", "at pages/connect/connect.vue:266", "readImageAsArrayBuffer 开始, 路径:", filePath);
plus.io.resolveLocalFileSystemURL(filePath, (fileEntry) => {
formatAppLog("log", "at pages/connect/connect.vue:268", "resolveLocalFileSystemURL 成功");
fileEntry.file((file) => {
formatAppLog("log", "at pages/connect/connect.vue:270", "获取File对象成功, 大小:", file.size);
const reader = new plus.io.FileReader();
reader.onloadend = (e) => {
formatAppLog("log", "at pages/connect/connect.vue:273", "FileReader onloadend, 有数据:", !!e.target.result);
if (e.target.result) {
resolve(e.target.result);
} else {
reject(new Error("读取文件结果为空"));
}
};
reader.onerror = (e) => {
formatAppLog("error", "at pages/connect/connect.vue:281", "FileReader onerror:", e);
reject(new Error("FileReader错误"));
};
reader.readAsDataURL(file);
}, (err) => {
formatAppLog("error", "at pages/connect/connect.vue:286", "fileEntry.file 失败:", JSON.stringify(err));
reject(err);
});
}, (err) => {
formatAppLog("error", "at pages/connect/connect.vue:290", "resolveLocalFileSystemURL 失败:", JSON.stringify(err));
reject(err);
});
});
},
// DataURL 转 ArrayBuffer
dataURLtoArrayBuffer(dataURL) {
const base64 = dataURL.split(",")[1];
const binary = atob(base64);
const len = binary.length;
const buffer = new ArrayBuffer(len);
const view = new Uint8Array(buffer);
for (let i = 0; i < len; i++) {
view[i] = binary.charCodeAt(i);
}
return buffer;
},
// 图片分包传输
async writeBleImage(filename, imageArrayBuffer) {
const data = new Uint8Array(imageArrayBuffer);
const len = data.length;
formatAppLog("log", "at pages/connect/connect.vue:311", "开始传输图片:", filename, "大小:", len, "字节");
const header = new Uint8Array(26);
header[0] = 253;
for (let i = 0; i < Math.min(filename.length, 22); i++) {
header[i + 1] = filename.charCodeAt(i);
}
header[23] = len >> 16 & 255;
header[24] = len >> 8 & 255;
header[25] = len & 255;
await this.bleWrite(this.imageWriteuuid, header.buffer);
await this.delay(50);
const CHUNK_SIZE = 507;
let offset = 0;
let packetNo = 0;
while (offset < len) {
const remaining = len - offset;
const chunkLen = Math.min(CHUNK_SIZE, remaining);
const isEnd = offset + chunkLen >= len ? 1 : 0;
const packet = new Uint8Array(2 + chunkLen);
packet[0] = packetNo & 255;
packet[1] = isEnd;
packet.set(data.slice(offset, offset + chunkLen), 2);
await this.bleWrite(this.imageWriteuuid, packet.buffer);
await this.delay(5);
offset += chunkLen;
packetNo++;
if (packetNo % 10 === 0 || isEnd) {
this.transferProgress = Math.floor(offset / len * 100);
uni.showLoading({ title: "传输中 " + this.transferProgress + "%", mask: true });
}
}
formatAppLog("log", "at pages/connect/connect.vue:355", "图片传输完成,共", packetNo, "包");
},
// 通过 16-bit UUID 在列表中查找(兼容多种格式)
findByUuid16(list, uuid16) {
const hex4 = uuid16.toString(16).padStart(4, "0").toLowerCase();
const fullTarget = "0000" + hex4 + "-0000-1000-8000-00805f9b34fb";
return list.find((item) => {
const uuid = item.uuid.toLowerCase();
if (uuid === fullTarget)
return true;
if (uuid === hex4 || uuid === "0000" + hex4)
return true;
if (uuid.startsWith("0000" + hex4 + "-"))
return true;
return false;
});
},
getBleService() {
var that = this;
uni.getBLEDeviceServices({
deviceId: that.deviceId,
success(res) {
formatAppLog("log", "at pages/connect/connect.vue:378", "发现服务数量:", res.services.length);
res.services.forEach((s, i) => {
formatAppLog("log", "at pages/connect/connect.vue:380", " 服务[" + i + "]:", s.uuid);
});
const service = that.findByUuid16(res.services, 2816);
if (service) {
that.imageServiceuuid = service.uuid;
formatAppLog("log", "at pages/connect/connect.vue:385", "图片服务已找到:", service.uuid);
that.getBleChar();
} else {
formatAppLog("error", "at pages/connect/connect.vue:388", "未找到图片传输服务 0x0B00请检查上方打印的UUID格式");
uni.showToast({ title: "未找到图片服务", icon: "none" });
}
},
fail() {
formatAppLog("log", "at pages/connect/connect.vue:393", "获取服务Id失败");
}
});
},
getBleChar() {
var that = this;
uni.getBLEDeviceCharacteristics({
deviceId: that.deviceId,
serviceId: that.imageServiceuuid,
success(res) {
formatAppLog("log", "at pages/connect/connect.vue:403", "发现特征数量:", res.characteristics.length);
res.characteristics.forEach((c, i) => {
formatAppLog("log", "at pages/connect/connect.vue:405", " 特征[" + i + "]:", c.uuid, "属性:", JSON.stringify(c.properties));
});
const writeChar = that.findByUuid16(res.characteristics, 2817);
const editChar = that.findByUuid16(res.characteristics, 2818);
if (writeChar) {
that.imageWriteuuid = writeChar.uuid;
formatAppLog("log", "at pages/connect/connect.vue:411", "写入特征已找到:", writeChar.uuid);
}
if (editChar) {
that.imageEdituuid = editChar.uuid;
formatAppLog("log", "at pages/connect/connect.vue:415", "编辑特征已找到:", editChar.uuid);
}
if (!writeChar || !editChar) {
formatAppLog("error", "at pages/connect/connect.vue:418", "特征发现不完整, write:", !!writeChar, "edit:", !!editChar);
}
},
fail() {
formatAppLog("log", "at pages/connect/connect.vue:422", "获取特征Id失败");
}
});
},
setBleMtu() {
var that = this;
uni.setBLEMTU({
deviceId: that.deviceId,
mtu: 512,
success() {
that.isConnected = true;
formatAppLog("log", "at pages/connect/connect.vue:434", "MTU设置成功");
that.getBleService();
},
fail() {
formatAppLog("log", "at pages/connect/connect.vue:438", "MTU设置失败");
}
});
},
// 初始化图片保存目录
initImageDir() {
const docPath = plus.io.convertLocalFileSystemURL("_doc/");
this.imageDir = docPath + "watch_faces/";
plus.io.resolveLocalFileSystemURL(
this.imageDir,
() => {
},
() => {
plus.io.resolveLocalFileSystemURL("_doc/", (root) => {
root.getDirectory("watch_faces", { create: true }, () => {
formatAppLog("log", "at pages/connect/connect.vue:453", "目录创建成功");
});
});
}
);
},
loadSavedImages() {
plus.io.resolveLocalFileSystemURL(this.imageDir, (dir) => {
const reader = dir.createReader();
reader.readEntries((entries) => {
const images = [];
entries.forEach((entry) => {
if (entry.isFile && this.isImageFile(entry.name)) {
images.push({
name: entry.name,
url: entry.toLocalURL()
});
}
});
this.uploadedImages = images;
if (images.length > 0) {
this.currentImageUrl = images[0].url;
this.currentImageIndex = 0;
}
});
});
},
// 判断是否为图片文件
isImageFile(filename) {
const ext = filename.toLowerCase().split(".").pop();
return ["jpg", "jpeg", "png", "gif", "bmp"].includes(ext);
},
// 选择图片(支持 GIF 和普通图片)
chooseImage() {
uni.chooseImage({
count: 1,
sizeType: ["compressed"],
sourceType: ["album", "camera"],
crop: {
quality: 100,
width: 360,
height: 360,
resize: true
},
success: (res) => {
formatAppLog("log", "at pages/connect/connect.vue:520", res);
const tempFilePath = res.tempFilePaths[0];
plus.io.resolveLocalFileSystemURL(tempFilePath, (fileEntry) => {
fileEntry.file((file) => {
const reader = new FileReader();
reader.onloadend = (e) => {
this.imageBuffer = e.target.result;
this.log = "图片读取成功,大小:" + this.imageBuffer.byteLength + "字节";
};
reader.readAsArrayBuffer(file);
});
});
this.saveImageToLocal(tempFilePath);
},
fail: (err) => {
formatAppLog("error", "at pages/connect/connect.vue:551", "选择图片失败:", err);
uni.showToast({ title: "选择图片失败", icon: "none" });
}
});
},
// 选择GIF动图不裁剪保留原始动画
chooseGif() {
uni.chooseImage({
count: 1,
sizeType: ["original"],
sourceType: ["album"],
success: (res) => {
formatAppLog("log", "at pages/connect/connect.vue:564", res);
const tempFilePath = res.tempFilePaths[0];
if (!tempFilePath.toLowerCase().endsWith(".gif")) {
uni.showToast({ title: "请选择GIF动图文件", icon: "none" });
return;
}
uni.getImageInfo({
src: tempFilePath,
success: (imgInfo) => {
formatAppLog("log", "at pages/connect/connect.vue:577", "GIF尺寸:", imgInfo.width, "×", imgInfo.height);
const sizeOk = imgInfo.width === 360 && imgInfo.height === 360;
const doSave = () => {
plus.io.resolveLocalFileSystemURL(tempFilePath, (fileEntry) => {
fileEntry.file((file) => {
if (file.size > 500 * 1024) {
uni.showModal({
title: "文件较大",
content: `GIF文件 ${(file.size / 1024).toFixed(0)}KB设备存储有限建议不超过500KB。是否继续`,
success: (modalRes) => {
if (modalRes.confirm) {
this.saveImageToLocal(tempFilePath);
}
}
});
} else {
this.saveImageToLocal(tempFilePath);
}
const reader = new FileReader();
reader.onloadend = (e) => {
this.imageBuffer = e.target.result;
this.log = "GIF读取成功大小" + this.imageBuffer.byteLength + "字节";
};
reader.readAsArrayBuffer(file);
});
});
};
if (!sizeOk) {
uni.showModal({
title: "GIF尺寸不匹配",
content: `当前GIF尺寸为 ${imgInfo.width}×${imgInfo.height},设备屏幕为 360×360。非标准尺寸的GIF播放可能不流畅且无法铺满屏幕建议使用360×360的GIF动图。是否继续上传`,
confirmText: "继续上传",
cancelText: "取消",
success: (modalRes) => {
if (modalRes.confirm) {
doSave();
}
}
});
} else {
doSave();
}
},
fail: () => {
formatAppLog("warn", "at pages/connect/connect.vue:632", "获取GIF尺寸失败跳过检测");
plus.io.resolveLocalFileSystemURL(tempFilePath, (fileEntry) => {
fileEntry.file((file) => {
this.saveImageToLocal(tempFilePath);
const reader = new FileReader();
reader.onloadend = (e) => {
this.imageBuffer = e.target.result;
this.log = "GIF读取成功大小" + this.imageBuffer.byteLength + "字节";
};
reader.readAsArrayBuffer(file);
});
});
}
});
},
fail: (err) => {
formatAppLog("error", "at pages/connect/connect.vue:653", "选择GIF失败:", err);
uni.showToast({ title: "选择GIF失败", icon: "none" });
}
});
},
// 保存图片到本地目录
saveImageToLocal(tempPath) {
const isGif = tempPath.toLowerCase().endsWith(".gif");
const fileName = `face_${Date.now()}.${isGif ? "gif" : "jpg"}`;
this.imageDir + fileName;
plus.io.resolveLocalFileSystemURL(tempPath, (file) => {
plus.io.resolveLocalFileSystemURL(this.imageDir, (dir) => {
file.copyTo(dir, fileName, (newFile) => {
const imageUrl = newFile.toLocalURL();
this.uploadedImages.push({
name: fileName,
url: imageUrl
});
this.currentImageIndex = this.uploadedImages.length - 1;
this.currentImageUrl = imageUrl;
uni.showToast({ title: "图片保存成功", icon: "success" });
}, (err) => {
formatAppLog("error", "at pages/connect/connect.vue:680", "保存图片失败:", err);
uni.showToast({ title: "保存图片失败", icon: "none" });
});
});
});
},
// 选择轮播中的图片
selectImage(index) {
this.currentImageIndex = index;
this.currentImageUrl = this.uploadedImages[index].url;
},
// 轮播图变化时触发
handleCarouselChange(e) {
const index = e.detail.current;
this.currentImageIndex = index;
this.currentImageUrl = this.uploadedImages[index].url;
},
// 压缩图片为JPEG格式设备端只支持JPEG解码
compressToJpeg(srcPath) {
return new Promise((resolve, reject) => {
uni.compressImage({
src: srcPath,
quality: 100,
success: (res) => {
formatAppLog("log", "at pages/connect/connect.vue:724", "JPEG压缩完成:", res.tempFilePath);
resolve(res.tempFilePath);
},
fail: (err) => {
formatAppLog("error", "at pages/connect/connect.vue:728", "JPEG压缩失败:", err);
reject(err);
}
});
});
},
// 设置为当前表盘BLE 传输图片到设备,支持 JPEG 和 GIF
async setAsCurrentFace() {
if (this.isSending)
return;
if (this.currentImageIndex < 0 || !this.imageWriteuuid) {
uni.showToast({ title: "请先选择图片并等待BLE就绪", icon: "none" });
return;
}
const currentImage = this.uploadedImages[this.currentImageIndex];
if (!currentImage)
return;
this.isSending = true;
this.faceStatus = 1;
this.transferProgress = 0;
const isGif = currentImage.name.toLowerCase().endsWith(".gif");
try {
let buffer;
let bleName;
if (isGif) {
uni.showLoading({ title: "读取GIF...", mask: true });
const dataURL = await this.readImageAsArrayBuffer(currentImage.url);
buffer = this.dataURLtoArrayBuffer(dataURL);
bleName = currentImage.name;
formatAppLog("log", "at pages/connect/connect.vue:762", "GIF读取完成大小:", buffer.byteLength, "字节");
if (buffer.byteLength > 200 * 1024) {
const sizeKB = (buffer.byteLength / 1024).toFixed(0);
uni.showLoading({ title: `传输GIF ${sizeKB}KB...`, mask: true });
}
} else {
uni.showLoading({ title: "压缩图片...", mask: true });
const jpegPath = await this.compressToJpeg(currentImage.url);
uni.showLoading({ title: "读取图片...", mask: true });
const dataURL = await this.readImageAsArrayBuffer(jpegPath);
buffer = this.dataURLtoArrayBuffer(dataURL);
bleName = currentImage.name.replace(/\.\w+$/, ".jpg");
formatAppLog("log", "at pages/connect/connect.vue:778", "JPEG读取完成大小:", buffer.byteLength, "字节");
}
await this.writeBleImage(bleName, buffer);
uni.hideLoading();
this.faceStatus = 0;
uni.showToast({ title: "表盘已更新", icon: "success" });
uni.setStorageSync("current_watch_face", this.currentImageUrl);
} catch (err) {
uni.hideLoading();
this.faceStatus = 2;
formatAppLog("error", "at pages/connect/connect.vue:791", "传输失败:", err);
uni.showToast({ title: "传输失败: " + (err.errMsg || err.message || err), icon: "none" });
setTimeout(() => {
this.faceStatus = 0;
}, 3e3);
} finally {
this.isSending = false;
}
},
// 切换设备连接状态
toggleConnection() {
if (this.isConnected) {
this.disconnectDevice();
} else {
this.connectDevice();
}
},
// 连接设备
connectDevice() {
var that = this;
this.isScanning = true;
uni.showToast({ title: "正在连接设备...", icon: "none" });
uni.createBLEConnection({
deviceId: that.deviceId,
success() {
that.isScanning = false;
that.isConnected = true;
uni.showToast({ title: "设备连接成功", icon: "success" });
that.setBleMtu();
that.startDataSimulation();
}
});
},
// 断开连接
disconnectDevice() {
var that = this;
uni.closeBLEConnection({
deviceId: that.deviceId,
success() {
that.isConnected = false;
that.statusPotColor = "red";
uni.showToast({ title: "已断开连接", icon: "none" });
that.stopDataSimulation();
},
fail(res) {
if (res == 1e4) {
uni.openBluetoothAdapter({
success() {
that.isConnected = false;
},
fail() {
formatAppLog("log", "at pages/connect/connect.vue:844", "初始化失败");
}
});
}
}
});
},
// 开始模拟数据更新
startDataSimulation() {
if (this.dataTimer) {
clearInterval(this.dataTimer);
}
this.batteryLevel = Math.floor(Math.random() * 30) + 70;
this.temperature = Math.floor(Math.random() * 10) + 20;
this.humidity = Math.floor(Math.random() * 30) + 40;
this.dataTimer = setInterval(() => {
if (this.batteryLevel > 1) {
this.batteryLevel = Math.max(1, this.batteryLevel - Math.random() * 2);
}
this.temperature = Math.max(15, Math.min(35, this.temperature + (Math.random() * 2 - 1)));
this.humidity = Math.max(30, Math.min(80, this.humidity + (Math.random() * 4 - 2)));
}, 5e3);
},
// 停止数据模拟
stopDataSimulation() {
if (this.dataTimer) {
clearInterval(this.dataTimer);
this.dataTimer = null;
}
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_uni_icons = vue.resolveComponent("uni-icons");
return vue.openBlock(), vue.createElementBlock("view", { class: "container" }, [
vue.createElementVNode("view", { class: "nav-bar" }, [
vue.createElementVNode("text", { class: "nav-title" }, "表盘管理器")
]),
vue.createElementVNode("view", { class: "content" }, [
vue.createElementVNode("view", { class: "preview-container" }, [
vue.createElementVNode("view", { class: "preview-frame" }, [
$data.currentImageUrl ? (vue.openBlock(), vue.createElementBlock("image", {
key: 0,
src: $data.currentImageUrl,
class: "preview-image",
mode: "aspectFill"
}, null, 8, ["src"])) : (vue.openBlock(), vue.createElementBlock("view", {
key: 1,
class: "empty-preview"
}, [
vue.createVNode(_component_uni_icons, {
type: "image",
size: "60",
color: "#ccc"
}),
vue.createElementVNode("text", null, "请选择表盘图片")
]))
]),
$data.currentImageUrl ? (vue.openBlock(), vue.createElementBlock("button", {
key: 0,
class: "set-btn",
onClick: _cache[0] || (_cache[0] = (...args) => $options.setAsCurrentFace && $options.setAsCurrentFace(...args)),
disabled: $data.isSending
}, vue.toDisplayString($data.isSending ? "传输中 " + $data.transferProgress + "%" : "设置为当前表盘"), 9, ["disabled"])) : vue.createCommentVNode("v-if", true),
vue.createElementVNode("view", { class: "btn-row" }, [
vue.createElementVNode("button", {
class: "upload-btn",
onClick: _cache[1] || (_cache[1] = (...args) => $options.chooseImage && $options.chooseImage(...args))
}, [
vue.createVNode(_component_uni_icons, {
type: "camera",
size: "24",
color: "#fff"
}),
vue.createElementVNode("text", null, "上传图片")
]),
vue.createElementVNode("button", {
class: "upload-btn gif-btn",
onClick: _cache[2] || (_cache[2] = (...args) => $options.chooseGif && $options.chooseGif(...args))
}, [
vue.createVNode(_component_uni_icons, {
type: "videocam",
size: "24",
color: "#fff"
}),
vue.createElementVNode("text", null, "上传GIF")
])
])
]),
vue.createElementVNode("view", { class: "carousel-section" }, [
vue.createElementVNode("text", { class: "section-title" }, "已上传表盘"),
vue.createElementVNode("view", { class: "carousel-container" }, [
vue.createElementVNode(
"swiper",
{
class: "card-swiper",
circular: "",
"previous-margin": "200rpx",
"next-margin": "200rpx",
onChange: _cache[3] || (_cache[3] = (...args) => $options.handleCarouselChange && $options.handleCarouselChange(...args))
},
[
(vue.openBlock(true), vue.createElementBlock(
vue.Fragment,
null,
vue.renderList($data.uploadedImages, (img, index) => {
return vue.openBlock(), vue.createElementBlock("swiper-item", { key: index }, [
vue.createElementVNode("view", {
class: "card-item",
onClick: ($event) => $options.selectImage(index)
}, [
vue.createElementVNode("view", { class: "card-frame" }, [
vue.createElementVNode("image", {
src: img.url,
class: "card-image",
mode: "aspectFill"
}, null, 8, ["src"]),
$data.currentImageIndex === index ? (vue.openBlock(), vue.createElementBlock("view", {
key: 0,
class: "card-overlay"
}, [
vue.createVNode(_component_uni_icons, {
type: "checkmark",
size: "30",
color: "#007aff"
})
])) : vue.createCommentVNode("v-if", true)
])
], 8, ["onClick"])
]);
}),
128
/* KEYED_FRAGMENT */
))
],
32
/* NEED_HYDRATION */
),
$data.uploadedImages.length === 0 ? (vue.openBlock(), vue.createElementBlock("view", {
key: 0,
class: "carousel-hint"
}, [
vue.createElementVNode("text", null, "暂无上传图片,请点击上方上传按钮添加")
])) : vue.createCommentVNode("v-if", true)
])
]),
vue.createElementVNode("view", { class: "data-section" }, [
vue.createElementVNode("text", { class: "section-title" }, "设备状态"),
vue.createElementVNode("view", { class: "data-grid" }, [
vue.createElementVNode("view", { class: "data-card" }, [
vue.createElementVNode("view", { class: "data-icon battery-icon" }, [
vue.createVNode(_component_uni_icons, {
type: "battery",
size: "36",
color: $options.batteryColor
}, null, 8, ["color"])
]),
vue.createElementVNode("view", { class: "data-info" }, [
vue.createElementVNode(
"text",
{ class: "data-value" },
vue.toDisplayString($data.batteryLevel) + "%",
1
/* TEXT */
),
vue.createElementVNode("text", { class: "data-label" }, "电量")
])
]),
vue.createElementVNode("view", { class: "data-card" }, [
vue.createElementVNode("view", { class: "data-icon temp-icon" }, [
vue.createVNode(_component_uni_icons, {
type: "thermometer",
size: "36",
color: "#ff7a45"
})
]),
vue.createElementVNode("view", { class: "data-info" }, [
vue.createElementVNode(
"text",
{ class: "data-value" },
vue.toDisplayString($data.temperature) + "°C",
1
/* TEXT */
),
vue.createElementVNode("text", { class: "data-label" }, "温度")
])
]),
vue.createElementVNode("view", { class: "data-card" }, [
vue.createElementVNode("view", { class: "data-icon humidity-icon" }, [
vue.createVNode(_component_uni_icons, {
type: "water",
size: "36",
color: "#40a9ff"
})
]),
vue.createElementVNode("view", { class: "data-info" }, [
vue.createElementVNode(
"text",
{ class: "data-value" },
vue.toDisplayString($data.humidity) + "%",
1
/* TEXT */
),
vue.createElementVNode("text", { class: "data-label" }, "湿度")
])
]),
vue.createElementVNode("view", { class: "data-card" }, [
vue.createElementVNode("view", { class: "data-icon status-icon" }, [
vue.createVNode(_component_uni_icons, {
type: "watch",
size: "36",
color: "#52c41a"
})
]),
vue.createElementVNode("view", { class: "data-info" }, [
vue.createElementVNode(
"text",
{ class: "data-value" },
vue.toDisplayString($options.faceStatusText),
1
/* TEXT */
),
vue.createElementVNode("text", { class: "data-label" }, "表盘状态")
])
])
]),
vue.createElementVNode("view", { class: "connection-status" }, [
vue.createVNode(_component_uni_icons, {
type: "bluetooth",
size: "24",
color: $data.isConnected ? "#52c41a" : "#ff4d4f",
animation: $data.isScanning ? "spin" : ""
}, null, 8, ["color", "animation"]),
vue.createElementVNode(
"view",
{
class: "status-pot",
style: vue.normalizeStyle({
backgroundColor: $data.isConnected ? "Green" : "red"
})
},
null,
4
/* STYLE */
),
vue.createElementVNode(
"text",
{ class: "status-text" },
vue.toDisplayString($data.isConnected ? "已连接设备" : $data.isScanning ? "正在搜索设备..." : "未连接设备"),
1
/* TEXT */
),
vue.createElementVNode("button", {
class: "connect-btn",
onClick: _cache[4] || (_cache[4] = (...args) => $options.toggleConnection && $options.toggleConnection(...args)),
disabled: $data.isScanning
}, vue.toDisplayString($data.isConnected ? "断开连接" : "连接设备"), 9, ["disabled"])
])
])
])
]);
}
const PagesConnectConnect = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["render", _sfc_render], ["__scopeId", "data-v-ea8c7664"], ["__file", "/Users/rdzleo/Desktop/uniapp_code/pages/connect/connect.vue"]]);
__definePage("pages/index/index", PagesIndexIndex);
__definePage("pages/connect/connect", PagesConnectConnect);
const _sfc_main = {
onLaunch: function() {
formatAppLog("log", "at App.vue:7", "App Launch");
},
onShow: function() {
formatAppLog("log", "at App.vue:10", "App Show");
},
onHide: function() {
formatAppLog("log", "at App.vue:13", "App Hide");
},
onExit: function() {
formatAppLog("log", "at App.vue:34", "App Exit");
}
};
const App = /* @__PURE__ */ _export_sfc(_sfc_main, [["__file", "/Users/rdzleo/Desktop/uniapp_code/App.vue"]]);
function createApp() {
const app = vue.createVueApp(App);
return {
app
};
}
const { app: __app__, Vuex: __Vuex__, Pinia: __Pinia__ } = createApp();
uni.Vuex = __Vuex__;
uni.Pinia = __Pinia__;
__app__.provide("__globalStyles", __uniConfig.styles);
__app__._component.mpType = "app";
__app__._component.render = () => {
};
__app__.mount("#app");
})(Vue);