<template>
	<view class="page">
		<scroll-view class="content" scroll-y>
			<view class="section card">
				<view class="section-title-row">
					<text class="section-title">设备信息</text>
					<text class="section-tip">连接后自动同步</text>
				</view>
				<view class="name-edit-row">
					<input class="name-input" type="text" v-model="name" placeholder="设备名称" />
					<button class="save-name-btn" @click="saveName">保存</button>
				</view>

				<view class="info-grid two-col">
					<view class="info-item">
						<text class="label">电量</text>
						<text class="value">{{ battery.level }}%</text>
						<text class="sub">{{ battery.mv }} mV</text>
					</view>
					<view class="info-item">
						<text class="label">版本</text>
						<text class="value">{{ ver || '--' }}</text>
					</view>
				</view>
			</view>

			<view class="section card">
				<view class="section-title-row">
					<text class="section-title">治疗参数</text>
				</view>

				<view class="mode-wrap">
					<button class="mode-btn" :class="Number(mode) === 0 ? 'active' : ''" @click="setMode(0)">舒缓模式</button>
					<button class="mode-btn" :class="Number(mode) === 1 ? 'active' : ''" @click="setMode(1)">强效模式</button>
				</view>

				<view class="field-row">
					<view class="field-header">
						<text class="field-label">电压</text>
						<view class="quick-actions">
							<button class="mini-btn" @click="decreaseVoltage">-</button>
							<button class="mini-btn" @click="increaseVoltage">+</button>
						</view>
					</view>
					<view class="voltage-pill">{{ voltageText }}</view>
				</view>

				<view class="field-row">
					<view class="field-header">
						<text class="field-label">档位</text>
						<view class="quick-actions">
							<button class="mini-btn" @click="sendCtrl(0x03)">-</button>
							<button class="mini-btn" @click="sendCtrl(0x02)">+</button>
						</view>
					</view>
					<slider v-model="gear" step="1" :min="1" :max="10" show-value @change="handleGearChange" activeColor="#1f9f9a" backgroundColor="#bfe9e7" />
				</view>

				<view class="field-row">
					<text class="field-label">缩放</text>
					<slider v-model="scale" :step="0.1" :min="0.1" :max="5.0" show-value @change="setScale" activeColor="#1f9f9a" backgroundColor="#bfe9e7" />
				</view>

				<view class="field-row">
					<text class="field-label">频率</text>
					<slider v-model="frequency" step="20" :min="20" :max="200" show-value @change="setFrequency" activeColor="#1f9f9a" backgroundColor="#bfe9e7" />
				</view>

				<view class="field-row">
					<text class="field-label">脉宽</text>
					<slider v-model="pluse" step="100" :min="100" :max="500" show-value @change="setPluse" activeColor="#1f9f9a" backgroundColor="#bfe9e7" />
				</view>
			</view>

			<view class="section card">
				<view class="section-title-row">
					<text class="section-title">灯光个性化</text>
				</view>

				<view class="color-row">
					<view class="color-item" @click="handleOpenStandBy">
						<text class="field-label">待机颜色</text>
						<view class="color-dot" :style="{ backgroundColor: standbyColor || '#1e3a8a' }"></view>
					</view>
					<view class="color-item" @click="handleOpenRunning">
						<text class="field-label">运行颜色</text>
						<view class="color-dot" :style="{ backgroundColor: runningColor || '#dc2626' }"></view>
					</view>
				</view>

				<view class="field-row">
					<text class="field-label">亮度</text>
					<uni-data-select v-model="lighting" :localdata="[
						{ value: 0, text: '100%' },
						{ value: 1, text: '50%' },
						{ value: 2, text: '25%' },
						{ value: 3, text: '12.5%' },
						{ value: 4, text: '6.25%' }
					]" @change="setLighting" />
				</view>
			</view>
		</scroll-view>

		<view class="footer-actions">
			<button class="action-btn ghost" hover-class="btn-hover" @click="sendCMD(0x01, [])">刷新状态</button>
			<button
				v-if="isRunning"
				class="action-btn danger"
				hover-class="btn-hover"
				@click="stop"
			>停止</button>
			<button
				v-else
				class="action-btn primary"
				hover-class="btn-hover"
				@click="start"
			>启动</button>
		</view>

		<uni-popup ref="standByPopup" type="center">
			<x-color-picker v-model="standbyColor" :show-alpha="false" :show-presets="false" @confirm="setStandByColor" />
		</uni-popup>
		<uni-popup ref="runningPopup" type="center">
			<x-color-picker v-model="runningColor" :show-alpha="false" :show-presets="false" @confirm="setRunningColor" />
		</uni-popup>
	</view>
</template>

<script>
	function toHex(n) {
		return n.toString(16).padStart(2, '0');
	}

	function utf8Encode(str) {
		let codePoints = [];
		let i = 0;
		const len = str.length;

		while (i < len) {
			let c = str.charCodeAt(i);

			// 单字节字符 (0x00-0x7F)
			if (c < 0x80) {
				codePoints.push(c);
				i++;
			}
			// 双字节字符 (0x80-0x7FF)
			else if (c < 0x800) {
				codePoints.push(0xC0 | (c >> 6));
				codePoints.push(0x80 | (c & 0x3F));
				i++;
			}
			// 三字节字符 (0x800-0xFFFF)
			else if (c < 0x10000) {
				codePoints.push(0xE0 | (c >> 12));
				codePoints.push(0x80 | ((c >> 6) & 0x3F));
				codePoints.push(0x80 | (c & 0x3F));
				i++;
			}
			// 四字节字符 (0x10000-0x10FFFF)
			else {
				c -= 0x10000;
				codePoints.push(0xF0 | (c >> 18));
				codePoints.push(0x80 | ((c >> 12) & 0x3F));
				codePoints.push(0x80 | ((c >> 6) & 0x3F));
				codePoints.push(0x80 | (c & 0x3F));
				i++;
			}
		}

		return codePoints; // 直接返回数组，无需再转 Array.from
	}

	function getErrStr(e) {
		return {
			0: "成功",
			1: "运行中禁止",
			2: "参数越界",
			3: "档位极限",
			0xEE: "校验错误",
			0xFF: "未知命令"
		} [e] || `Err(0x${e.toString(16)})`;
	}

	function safeDecode(bytes) {
		try {
			// 1. 统一格式：转为Uint8Array（兼容uniapp各端传入的不同字节格式）
			let uint8Bytes = bytes;
			if (!(bytes instanceof Uint8Array)) {
				uint8Bytes = new Uint8Array(bytes);
			}

			// 2. 找到第一个0终止符，截断无效内容（和原逻辑一致）
			let endIndex = -1;
			for (let i = 0; i < uint8Bytes.length; i++) {
				if (uint8Bytes[i] === 0) {
					endIndex = i;
					break;
				}
			}
			if (endIndex !== -1) {
				uint8Bytes = uint8Bytes.slice(0, endIndex);
			}

			// 3. 兼容处理：部分小程序端TextDecoder支持性差，增加兜底方案
			let result = '';
			if (typeof TextDecoder !== 'undefined') {
				// 优先使用标准TextDecoder
				result = new TextDecoder("utf-8", {
					fatal: false
				}).decode(uint8Bytes);
			} else {
				// 兜底：手动转换UTF-8字节到字符串（兼容低版本小程序）
				result = decodeURIComponent(escape(String.fromCharCode.apply(null, uint8Bytes)));
			}

			// 过滤解码后的空字符（避免不可见字符）
			return result.replace(/\0/g, '');
		} catch (e) {
			console.error('safeDecode解码失败：', e);
			return "[Decode Err]";
		}
	}

	export default {
		data() {
			return {
				isRunning: false,
				deviceId: '',
				logs: [],
				name: '',
				sn: '',
				ver: '',
				mode: '0',
				gear: 1,
				timer: '15',
				frequency: '100',
				pluse: '200',
				scale: '1',
				voltage: '0',
				standbyColor: '',
				runningColor: '',
				lighting: '0',
				SERVICE_UUID: '',
				CHAR_WRITE_UUID: '',
				CHAR_NOTIFY_UUID: '',
				battery: {
					level: 0,
					mv: 1600
				},
				inited: false,
				rxBuffer: [],
				timer1: '',
				timer2: ''
			}
		},
		onLoad(options) {
			uni.hideLoading()
			if (options && options.deviceId) {
				this.deviceId = decodeURIComponent(options.deviceId)
			}
			if (!this.deviceId) {
				uni.showToast({
					title: '缺少设备ID',
					icon: 'none'
				})
				return
			}
			const that = this
			uni.openBluetoothAdapter({
				success() {
					console.log('蓝牙已就绪')
					that.inited = true
					that.relink()
				},
				fail() {
					uni.showToast({
						title: '蓝牙不可用，请开启蓝牙',
						icon: 'none'
					})
				}
			})
		},
		destroyed() {
			// 关闭蓝牙适配器，释放资源
			uni.closeBluetoothAdapter({
				success() {
					console.log('蓝牙适配器已关闭');
				},
				fail(err) {
					// 捕获关闭失败的异常（比如蓝牙未打开时），避免控制台报错
					console.warn('关闭蓝牙适配器失败', err);
				}
			});
			clearTimeout(this.timer1);
			clearInterval(this.timer2)
		},
		computed: {
			stimPl() {
				const pl = [
					parseInt(this.mode),
					parseInt(this.gear) - 1,
					parseInt(this.timer),
					parseInt(this.frequency) >> 8,
					parseInt(this.frequency) & 0xFF,
					parseInt(this.pluse) >> 8,
					parseInt(this.pluse) & 0xFF,
					Math.round(parseFloat(this.scale) * 1000) >> 8,
					Math.round(parseFloat(this.scale) * 1000) & 0xFF,
					parseInt(this.voltage)
				]
				return pl
			},
			voltageText() {
				const map = ['11V', '15V', '26V', '30V', '45V', '48V', '52V', '56V']
				return map[parseInt(this.voltage)] || '--'
			}
		},
		methods: {
			handleGearChange(e) {
				this.gear = e.detail.value;
				this.sendSingle('GEAR');
			},
			setFrequency(e) {
				this.frequency = e.detail.value;
				this.sendStimAll()
			},
			setPluse(e) {
				this.pluse = e.detail.value;
				this.sendStimAll()
			},
			sendStimAll() {
				this.sendCMD(0x10, this.stimPl)
			},
			// 设置设备名称
			saveName() {
				const nextName = (this.name || '').trim()
				if (!nextName) {
					uni.showToast({ title: '请输入设备名称', icon: 'none' })
					return
				}
				const pl = utf8Encode(nextName)
				this.sendCMD(0xC1, Array.from(pl))
				uni.showToast({ title: '已发送保存', icon: 'none' })
			},

			relink() {
				const that = this;
				if (!this.inited) {
					uni.openBluetoothAdapter({
						success() {
							that.inited = true
							that.relink()
						}
					})
					return;
				}

				const deviceId = this.deviceId;
				uni.showLoading({ title: '正在准备连接设备' })

				const bindNotifyAndReady = () => {
					uni.notifyBLECharacteristicValueChange({
						deviceId,
						serviceId: that.SERVICE_UUID,
						characteristicId: that.CHAR_NOTIFY_UUID,
						state: true,
						success() {
							uni.onBLECharacteristicValueChange((res) => {
								const data = new Uint8Array(res.value)
								that.rxBuffer = that.rxBuffer || []
								that.rxBuffer.push(...data)
								while (true) {
									let headIdx = -1
									for (let i = 0; i < that.rxBuffer.length - 1; i++) {
										if (that.rxBuffer[i] === 0xAA && that.rxBuffer[i + 1] === 0x55) {
											headIdx = i
											break
										}
									}
									if (headIdx === -1) {
										if (that.rxBuffer.length > 200) that.rxBuffer = []
										break
									}
									if (headIdx > 0) that.rxBuffer.splice(0, headIdx)
									if (that.rxBuffer.length < 3) break
									const len = that.rxBuffer[2]
									const totalLen = len + 4
									if (that.rxBuffer.length < totalLen) break
									const packet = that.rxBuffer.slice(0, totalLen)
									that.rxBuffer.splice(0, totalLen)
									that.parsePkt(packet)
								}
							})
							that.timer1 = setTimeout(() => that.sendCMD(0xC0, []), 500)
							uni.hideLoading()
						},
						fail(err) {
							uni.hideLoading()
							console.log('订阅失败', err)
							uni.showToast({ icon: 'none', title: '订阅通知失败' })
						}
					})
				}

				const tryBindService = (services, idx) => {
					if (idx >= services.length) {
						uni.hideLoading()
						console.log('可用服务列表', services)
						uni.showToast({ icon: 'none', title: '未找到可通信特征值' })
						return
					}
					const currService = services[idx]
					uni.getBLEDeviceCharacteristics({
						deviceId,
						serviceId: currService.uuid,
						success(charRes) {
							const characteristics = charRes.characteristics || []
							let writeUUID = ''
							let notifyUUID = ''
							for (let i = 0; i < characteristics.length; i++) {
								const item = characteristics[i]
								const cUuid = (item.uuid || '').toLowerCase()
								const p = item.properties || {}
								if (!writeUUID && (cUuid.includes('ae01') || p.write || p.writeNoResponse)) {
									writeUUID = item.uuid
								}
								if (!notifyUUID && (cUuid.includes('ae02') || p.notify || p.indicate)) {
									notifyUUID = item.uuid
								}
							}
							if (writeUUID && notifyUUID) {
								that.SERVICE_UUID = currService.uuid
								that.CHAR_WRITE_UUID = writeUUID
								that.CHAR_NOTIFY_UUID = notifyUUID
								bindNotifyAndReady()
								return
							}
							tryBindService(services, idx + 1)
						},
						fail() {
							tryBindService(services, idx + 1)
						}
					})
				}

				const fetchServicesWithRetry = (retry = 0) => {
					uni.getBLEDeviceServices({
						deviceId,
						success(res) {
							const services = (res.services || []).filter(item => item && item.uuid)
							console.log('服务', services)
							if (!services.length) {
								if (retry < 4) {
									setTimeout(() => fetchServicesWithRetry(retry + 1), 700)
									return
								}
								uni.hideLoading()
								uni.showToast({ icon: 'none', title: '未发现可用服务' })
								return
							}
							tryBindService(services, 0)
						},
						fail(error) {
							if (retry < 1) {
								setTimeout(() => fetchServicesWithRetry(retry + 1), 500)
								return
							}
							uni.hideLoading()
							console.log('读取服务失败', error)
							uni.showToast({ icon: 'none', title: '设备连接异常' })
						}
					})
				}

				const continueWithConnectedLink = () => {
					that.CHAR_WRITE_UUID = ''
					that.CHAR_NOTIFY_UUID = ''
					setTimeout(() => fetchServicesWithRetry(0), 500)
				}

				uni.getConnectedBluetoothDevices({
					services: [],
					success(res) {
						const connected = (res.devices || []).some(d => d.deviceId === deviceId)
						if (connected) {
							console.log('复用已存在连接', deviceId)
							continueWithConnectedLink()
							return
						}
						uni.createBLEConnection({
							deviceId,
							success() {
								console.log('新建BLE连接', deviceId)
								continueWithConnectedLink()
							},
							fail(error) {
								uni.hideLoading()
								console.log(error)
								uni.showToast({ icon: 'none', title: '设备连接失败' })
							}
						})
					},
					fail() {
						uni.createBLEConnection({
							deviceId,
							success() {
								console.log('新建BLE连接(兜底)', deviceId)
								continueWithConnectedLink()
							},
							fail(error) {
								uni.hideLoading()
								console.log(error)
								uni.showToast({ icon: 'none', title: '设备连接失败' })
							}
						})
					}
				})
			},
			// 构建底层协议
			buildPacket(cmd, payload) {
				const len = payload.length + 1;
				let sum = len + cmd;
				payload.forEach(b => sum += b);
				const pkt = [0xAA, 0x55, len, cmd, ...payload];
				pkt.push(sum & 0xFF);
				return new Uint8Array(pkt);
			},
			// 记录日志 + 发送消息
			sendCMD(cmd, payload, desc) {
				const pkt = this.buildPacket(cmd, payload);
				// 发送消息
				uni.writeBLECharacteristicValue({
					deviceId: this.deviceId,
					serviceId: this.SERVICE_UUID,
					characteristicId: this.CHAR_WRITE_UUID,
					writeType: 'writeNoResponse',
					value: pkt.buffer,
					success() {
						console.log('写入成功', pkt, pkt.buffer)
					},
					fail(error) {
						console.log(error)
					}
				})
				// 记录日志
			},
			log() {
				// 本地日志 + 云端日志记录
			},
			// 单设通用
			sendSingle(t) {
				if (t == 'GEAR') {
					console.log(this.gear)
					this.sendCMD(0x13, [parseInt(this.gear) - 1], '单设档位');
				} else if (t === 'SCALE') {
					const s = Math.round(parseFloat(this.scale) * 1000);
					this.sendCMD(0x14, [s >> 8, s & 0xFF], '单设缩放');
				} else if (t === 'VOLT') {
					this.sendCMD(0x15, [parseInt(this.voltage)], '单设电压')
				} else if (t === 'BRIGHT') {
					this.sendCMD(0x22, [parseInt(this.lighting)], '单设亮度')
				} else if (t === 'COLOR') {
					const [s_r, s_g, s_b] = this.getColorRGB(this.standbyColor);
					const [r_r, r_g, r_b] = this.getColorRGB(this.runningColor);
					this.sendCMD(0x21, [s_r, s_g, s_b, r_r, r_g, r_b], '单设颜色')
				}
			},

			getColorRGB(hex) {
				const r = parseInt(hex.substr(1, 2), 16)
				const g = parseInt(hex.substr(3, 2), 16)
				const b = parseInt(hex.substr(5, 2), 16)
				return [r, g, b]
			},

			// 控制
			sendCtrl(a) {
				this.sendCMD(0x30, [a], `控制: ${['Stop','Start','Gear+','Gear-','ModeSw'][a]}`)
			},
			// 获取设备电刺激信息

			// 设定电刺激

			// 单设挡位

			// 单设缩放
			setScale(e) {
				const value = e.detail.value
				this.sendSingle("SCALE")
			},
			// 单设电压
			setVoltage1(e) {
				console.log(e)
				const value = e;
				this.sendSingle('VOLT')
			},
			increaseVoltage() {
				const curr = parseInt(this.voltage)
				if (curr >= 7) return
				this.voltage = curr + 1
				this.sendSingle('VOLT')
			},
			decreaseVoltage() {
				const curr = parseInt(this.voltage)
				if (curr <= 0) return
				this.voltage = curr - 1
				this.sendSingle('VOLT')
			},
			// 获取设备信息

			// 修改设备名称

			// 获取个性化信息

			// 设置个性化信息

			// 单设亮度

			// 单设颜色

			// 启动
			start() {
				if (this.isRunning) return
				this.sendCtrl(0x01)
				this.isRunning = true
			},
			// 停止
			stop() {
				if (!this.isRunning) return
				this.sendCtrl(0x00)
				this.isRunning = false
			},
			// 加档

			// 减档

			// 换模
			setMode(m) {
				if (this.mode === m) return
				this.mode = m
				if (m == 0) {
					this.sendCMD(0x11, [this.timer, this.frequency >> 8, this.frequency & 0xFF, this.pluse >> 8, this.pluse &
						0xFF
					])
				} else {
					this.sendCMD(0x12, [this.timer, this.frequency >> 8, this.frequency & 0xFF, this.pluse >> 8, this.pluse &
						0xFF
					])
				}
				this.sendCtrl(0x04)
			},
			handleModeChange(e) {
				const m = Number(e && e.detail ? e.detail.value : 0)
				this.setMode(m)
			},
			// 单设档位

			handleOpenStandBy() {
				this.$refs.standByPopup.open()
			},
			handleOpenRunning() {
				this.$refs.runningPopup.open()
			},
			setStandByColor(c) {
				this.standbyColor = c.hex
				this.$refs.standByPopup.close()
				this.sendSingle('COLOR')
			},
			setRunningColor(c) {
				this.runningColor = c.hex
				this.$refs.runningPopup.close()
				this.sendSingle('COLOR')
			},
			setLighting(v) {
				this.sendSingle('BRIGHT')
			},
			parsePkt(pkt) {
				const cmd = pkt[3];
				const pl = pkt.slice(4, pkt.length - 1);
				let desc = '',
					style = '';
				console.log('解析pkt', toHex(cmd), pl)

				try {
					switch (cmd) {
						case 0xFF: // ACK
							if (pl[1] === 0) desc = `ACK成功: 0x${pl[0].toString(16).toUpperCase()}`;
							else {
								desc = `ACK失败: 0x${pl[0].toString(16).toUpperCase()} -> ${getErrStr(pl[1])}`;
								style = "err";
							}
							break;

						case 0x80: // REPORT ALL
						case 0x81: { // REPORT STIM	
							console.log('获取所有参数')
							const m = pl[0],
								g = pl[1],
								t = pl[2];
							const f = (pl[3] << 8) | pl[4],
								w = (pl[5] << 8) | pl[6],
								s = (pl[7] << 8) | pl[8],
								v = pl[9],
								run = pl[10];
							desc =
								`状态: ${m===0?'舒缓':'强效'}, ${g+1}档, ${t}min, ${f}Hz, ${w}us, ${(s/1000).toFixed(1)}x}`;

							this.mode = m;
							this.gear = g + 1;
							this.timer = t;
							this.frequency = f;
							this.pluse = w;
							this.scale = (s / 1000).toFixed(1);
							this.voltage = v;
							this.isRunning = !!run;

							style = "sync";

							if (cmd === 0x80 && pl.length >= 18) {
								const sr = pl[11],
									sg = pl[12],
									sb = pl[13],
									rr = pl[14],
									rg = pl[15],
									rb = pl[16],
									br = pl[17];
								desc +=
									`\n个性: Sys(#${toHex(sr)}${toHex(sg)}${toHex(sb)}) Run(#${toHex(rr)}${toHex(rg)}${toHex(rb)})}`;
								this.standbyColor = '#' + toHex(sr) + toHex(sg) + toHex(sb)
								this.runningColor = '#' + toHex(rr) + toHex(rg) + toHex(rb)
								console.log(this.standbyColor, this.runningColor)
								this.lighting = br;
							}
							break;
						}

						case 0x82: { // REPORT PERSONAL
							sync = true;
							const sr2 = pl[0],
								sg2 = pl[1],
								sb2 = pl[2],
								rr2 = pl[3],
								rg2 = pl[4],
								rb2 = pl[5],
								br2 = pl[6];
							desc =
								`个性化: Sys(#${toHex(sr2)}${toHex(sg2)}${toHex(sb2)}) Run(#${toHex(rr2)}${toHex(rg2)}${toHex(rb2)}) 亮度${getBrightStr(br2)}`;
							this.standbyColor = '#' + toHex(sr2) + toHex(sg2) + toHex(sb2)
							this.runningColor = '#' + toHex(rr2) + toHex(rg2) + toHex(sb2)
							this.lighting = br2;
							break;
						}

						case 0xC0: { // Get All Info
							style = "sys";
							let ptr = 0;
							if (pl.length > 3) {
								const snLen = pl[ptr++];
								console.log(snLen, ptr, pl.length)
								if (ptr + snLen < pl.length) {
									console.log('开始解析SN')
									const sn = safeDecode(pl.slice(ptr, ptr + snLen));
									console.log('SN：', sn)
									ptr += snLen;
									this.sn = sn
									desc += `SN:${sn} `;
								}
								if (ptr < pl.length) {
									const nameLen = pl[ptr++];
									if (ptr + nameLen < pl.length) {
										const name = safeDecode(pl.slice(ptr, ptr + nameLen));
										ptr += nameLen;
										this.name = name
										desc += `Name:${name} `;
									}
								}
								if (ptr + 2 < pl.length) {
									const v1 = pl[ptr++],
										v2 = pl[ptr++],
										v3 = pl[ptr++];
									// safeSetVal('sys_ver', `v${v1}.${v2}.${v3}`);
									this.ver = `v${v1}.${v2}.${v3}`
									desc += `Ver:v${v1}.${v2}.${v3}`;
								}
							}
							break;
						}

						case 0xC1: // Set Name Echo
						case 0xC4: { // Get Name
							style = "sys";
							const nameStr = safeDecode(pl);
							desc = `蓝牙名: ${nameStr}`;
							this.name = nameStr
							break;
						}

						case 0xC3: { // Get SN
							style = "sys";
							const snStr = safeDecode(pl);
							desc = `SN序列号: ${snStr}`;
							this.sn = snStr
							break;
						}

						case 0xC5: { // Get Version
							style = "sys";
							desc = `固件版本: v${pl[0]}.${pl[1]}.${pl[2]}`;
							this.ver = `v${pl[0]}.${pl[1]}.${pl[2]}`
							break;
						}

						case 0xC2:
							desc = `设置SN结果: ${pl[0]===0?'成功':'失败'}`;
							style = "sys";
							break;
						case 0xB0:
							desc = "通知: 蓝牙已连接";
							uni.showToast({
								title: '蓝牙已连接',
								icon: 'none'
							});
							// 连接成功后获取一次设备信息
							this.sendCMD(0xC0, [])
							uni.hideLoading()
							break;
						case 0xB2:
							desc = "通知: [低电量关机请求]";
							uni.showToast({
								icon: 'exception',
								title: '电量不足'
							})
							style = "err";
							break;
						case 0xA1:
							this.battery = {
								level: pl[1],
								mv: (pl[2] << 8 | pl[3])
							}
							desc = `电池上报: ${pl[1]}%, ${(pl[2]<<8|pl[3])}mV`;
							break;

						default:
							desc = `CMD: 0x${cmd.toString(16).toUpperCase()}`;
					}
				} catch (e) {
					desc = "解析异常: " + e.message;
					style = "err";
				}
			},
		}
	}
</script>

<style scoped>
:root {
	--bg-page: #f8fafc;
	--bg-card: #ffffff;
	--bg-soft: #f8fafc;
	--border-soft: #edf2f7;
	--border-input: #e2e8f0;
	--text-main: #0f172a;
	--text-sub: #64748b;
	--text-muted: #94a3b8;
	--primary: #1f9f9a;
	--primary-soft: #e6f5f4;
	--danger-soft: #fef2f2;
	--danger: #b91c1c;
}

@media (prefers-color-scheme: dark) {
	:root {
		--bg-page: #0f172a;
		--bg-card: #111827;
		--bg-soft: #1f2937;
		--border-soft: #273244;
		--border-input: #334155;
		--text-main: #e5e7eb;
		--text-sub: #cbd5e1;
		--text-muted: #94a3b8;
		--primary: #22b3ad;
		--primary-soft: #113c39;
		--danger-soft: #3b1e24;
		--danger: #f87171;
	}
}

.page {
	background: var(--bg-page);
	min-height: 100vh;
	display: flex;
	flex-direction: column;
}

.content {
	flex: 1;
	padding: 20rpx 20rpx 140rpx;
	box-sizing: border-box;
}

.section {
	margin-bottom: 16rpx;
}

.card {
	background: #ffffff;
	border: 1rpx solid #edf2f7;
	border-radius: 16rpx;
	padding: 20rpx;
}

.section-title-row {
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-bottom: 14rpx;
}

.section-title {
	font-size: 28rpx;
	font-weight: 600;
	color: #0f172a;
}

.section-tip {
	font-size: 22rpx;
	color: #94a3b8;
}

.name-edit-row {
	display: flex;
	align-items: center;
	gap: 12rpx;
	margin-bottom: 16rpx;
}

.name-input {
	flex: 1;
	height: 74rpx;
	line-height: 74rpx;
	background: var(--bg-soft);
	border-radius: 12rpx;
	padding: 0 18rpx;
	font-size: 28rpx;
	color: var(--text-main);
}

.save-name-btn {
	height: 74rpx;
	line-height: 74rpx;
	padding: 0 20rpx;
	border-radius: 12rpx;
	border: 1rpx solid var(--border-input);
	background: var(--bg-soft);
	color: var(--text-sub);
	font-size: 24rpx;
}

.info-grid {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 12rpx;
}

.info-item {
	background: #f8fafc;
	border-radius: 12rpx;
	padding: 14rpx;
	display: flex;
	flex-direction: column;
	gap: 4rpx;
}

.label {
	font-size: 22rpx;
	color: #64748b;
}

.value {
	font-size: 30rpx;
	font-weight: 600;
	color: #0f172a;
}

.sub {
	font-size: 22rpx;
	color: #94a3b8;
}

.text-ellipsis {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.mode-wrap {
	margin-bottom: 12rpx;
	display: flex;
	gap: 12rpx;
}

.mode-btn {
	flex: 1;
	height: 68rpx;
	line-height: 68rpx;
	border-radius: 10rpx;
	border: 1rpx solid #bfe9e7;
	background: #f2fbfa;
	color: #0f7f7a;
	font-size: 24rpx;
	font-weight: 500;
}

.mode-btn.active {
	background: #1f9f9a;
	border-color: #1f9f9a;
	color: #ffffff;
}

.field-row {
	margin-top: 12rpx;
}

.field-header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-bottom: 8rpx;
}

.field-label {
	font-size: 24rpx;
	color: #0f7f7a;
	margin-bottom: 6rpx;
	display: block;
}

.quick-actions {
	display: flex;
	gap: 10rpx;
}

.mini-btn {
	min-width: 54rpx;
	height: 44rpx;
	line-height: 44rpx;
	border-radius: 10rpx;
	background: #f2fbfa;
	color: #0f7f7a;
	font-size: 24rpx;
	padding: 0 12rpx;
	border: 1rpx solid #bfe9e7;
}

.voltage-pill {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 120rpx;
	height: 56rpx;
	padding: 0 16rpx;
	border-radius: 10rpx;
	background: #f2fbfa;
	border: 1rpx solid #bfe9e7;
	font-size: 24rpx;
	color: #0f7f7a;
	font-weight: 600;
}

.color-row {
	display: flex;
	gap: 12rpx;
	margin-bottom: 8rpx;
}

.color-item {
	flex: 1;
	background: #f8fafc;
	border-radius: 12rpx;
	padding: 14rpx;
	display: flex;
	justify-content: space-between;
	align-items: center;
}

.color-dot {
	width: 44rpx;
	height: 44rpx;
	border-radius: 10rpx;
	border: 1rpx solid #e2e8f0;
}

.footer-actions {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	padding: 16rpx 20rpx 20rpx;
	background: #f8fafc;
	display: flex;
	gap: 12rpx;
	border-top: 1rpx solid #edf2f7;
}

.action-btn {
	flex: 1;
	height: 76rpx;
	line-height: 76rpx;
	border-radius: 12rpx;
	font-size: 26rpx;
	border: none;
}

.action-btn.ghost {
	background: #e2e8f0;
	color: #334155;
}

.action-btn.danger {
	background: #fee2e2;
	color: #b91c1c;
}

.action-btn.primary {
	background: #1f9f9a;
	color: #ffffff;
}

.action-btn.danger {
	background: var(--danger-soft);
	color: var(--danger);
}

.btn-hover {
	transform: scale(0.98);
	opacity: 0.9;
}

.action-btn,
.mini-btn,
.save-name-btn,
.color-item {
	transition: all 0.15s ease;
}

.action-btn:active,
.mini-btn:active,
.save-name-btn:active,
.color-item:active {
	transform: scale(0.98);
	opacity: 0.9;
}

/* 统一 uni 组件视觉 */
:deep(.uni-data-checklist) {
	width: 100%;
}

:deep(.uni-data-checklist .checklist-group) {
	display: flex;
	gap: 12rpx;
}

:deep(.uni-data-checklist .checklist-box) {
	flex: 1;
	padding: 14rpx 0;
	border-radius: 10rpx;
	border: 1rpx solid #bfe9e7;
	background: #f2fbfa;
	text-align: center;
}

:deep(.uni-data-checklist .checklist-box.is--default.is-checked),
:deep(.uni-data-checklist .checklist-box.is-checked),
:deep(.uni-data-checklist .checklist-box.is--tag.is-checked) {
	background: #e6f5f4 !important;
	border-color: #bfe9e7 !important;
}

:deep(.uni-data-checklist .checklist-content) {
	justify-content: center;
}

:deep(.uni-data-checklist .checklist-text) {
	font-size: 24rpx;
	color: #0f7f7a;
}

:deep(.uni-data-checklist .checklist-box.is--default.is-checked .checklist-text),
:deep(.uni-data-checklist .checklist-box.is-checked .checklist-text),
:deep(.uni-data-checklist .checklist-box.is--tag.is-checked .checklist-text) {
	color: #0f7f7a !important;
	font-weight: 600;
}

/* 覆盖 uni-data-checkbox 在 tag 模式下内部 uni-tag 的默认蓝色 */
:deep(.uni-data-checklist .uni-tag),
:deep(.uni-data-checklist .uni-tag--default) {
	background-color: #f2fbfa !important;
	border-color: #bfe9e7 !important;
	color: #0f7f7a !important;
}

:deep(.uni-data-checklist .uni-tag--primary),
:deep(.uni-data-checklist .uni-tag--primary.uni-tag--inverted),
:deep(.uni-data-checklist .is--checked .uni-tag) {
	background-color: #e6f5f4 !important;
	border-color: #7fd3cf !important;
	color: #0f7f7a !important;
}

:deep(.uni-data-checklist .uni-tag-text) {
	color: inherit !important;
}

:deep(.uni-select) {
	border-radius: 10rpx !important;
	border: 1rpx solid #e2e8f0 !important;
	background: #f8fafc !important;
	min-height: 72rpx;
}

:deep(.uni-select__selector) {
	border-radius: 10rpx !important;
	border-color: #e2e8f0 !important;
}

:deep(.uni-select__input-box) {
	padding: 0 12rpx;
}

:deep(.uni-select__input-text) {
	font-size: 24rpx !important;
	color: #0f172a !important;
}

:deep(.uni-icons) {
	color: #64748b !important;
}

:deep(.uni-slider-handle-wrapper) {
	height: 6rpx !important;
}

:deep(.uni-slider-thumb) {
	width: 28rpx !important;
	height: 28rpx !important;
	border: 2rpx solid #ffffff !important;
	box-shadow: 0 0 0 1rpx #bfdbfe !important;
}
</style>