ct/app/components/blueToothTest/item2.vue
2025-10-08 10:16:40 +08:00

891 lines
26 KiB
Vue
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.

<script lang="ts" setup>
/**
* Web端蓝牙设备控制组件
* 集成blufi系统实现蓝牙设备扫描、连接、控制功能
*/
import { ref, onMounted, onUnmounted } from 'vue'
// 动态导入blufi系统避免Web环境兼容性问题
let mDeviceEvent: any = null
let XBLUFI_TYPE: any = null
let OnFireEvent: any = null
// 页面布局配置
definePageMeta({
layout: 'empty' // 使用空布局
})
/**
* 蓝牙设备状态管理
*/
const bluetoothState = ref({
isSupported: false, // 浏览器是否支持Web Bluetooth API
isConnected: false, // 是否已连接到设备
isScanning: false, // 是否正在扫描设备
devices: [] as any[], // 发现的设备列表
selectedDevice: null as any, // 当前选中的设备
deviceInfo: {
name: '',
id: '',
batteryLevel: 0,
signalStrength: 0,
firmwareVersion: '',
deviceType: ''
}
})
/**
* 控制命令管理
*/
const controlCommands = ref({
ledOn: false, // LED开关状态
motorSpeed: 0, // 电机速度 (0-100)
temperature: 0, // 温度设置 (0-50°C)
humidity: 0, // 湿度设置 (0-100%)
wifiSsid: '', // WiFi网络名称
wifiPassword: '', // WiFi密码
customData: '' // 自定义数据命令
})
/**
* 操作日志管理
*/
const logs = ref<string[]>([])
/**
* 页面初始化
*/
onMounted(() => {
// 检查浏览器支持
bluetoothState.value.isSupported = 'bluetooth' in navigator
if (!bluetoothState.value.isSupported) {
addLog('浏览器不支持Web Bluetooth API')
} else {
addLog('Web Bluetooth API 支持正常')
}
// 初始化blufi系统
initBlufiSystem()
// 设置事件监听
setupEventListeners()
})
/**
* 页面卸载时清理
*/
onUnmounted(() => {
// 清理事件监听
cleanupEventListeners()
// 断开设备连接
if (bluetoothState.value.isConnected) {
disconnectDevice()
}
})
/**
* 初始化blufi系统
*/
const initBlufiSystem = async () => {
try {
// 动态导入blufi系统
const blufiModule = await import('~/components/blufi/xBlufi.js')
mDeviceEvent = blufiModule.mDeviceEvent
XBLUFI_TYPE = blufiModule.XBLUFI_TYPE
OnFireEvent = blufiModule.OnFireEvent
// 初始化blufi系统Web端使用模拟实现
if (mDeviceEvent) {
mDeviceEvent.initXBlufi(mDeviceEvent.XMQTT_SYSTEM.WeChat)
addLog('Blufi系统初始化成功')
} else {
addLog('Blufi系统模块加载失败')
}
} catch (error) {
addLog(`Blufi系统初始化失败: ${error}`)
// 如果blufi系统加载失败使用Web Bluetooth API作为备选方案
addLog('将使用Web Bluetooth API作为备选方案')
}
}
/**
* 设置事件监听器
*/
const setupEventListeners = () => {
// 检查blufi系统是否已加载
if (!mDeviceEvent) {
addLog('Blufi系统未加载跳过事件监听器设置')
return
}
try {
// 监听设备消息事件
mDeviceEvent.listenDeviceMsgEvent(true, handleDeviceMessage)
// 监听设备发现事件
mDeviceEvent.listenStartDiscoverBle(true, handleDeviceDiscovery)
// 监听设备连接事件
mDeviceEvent.listenConnectBle(true, handleDeviceConnection)
// 监听设备初始化事件
mDeviceEvent.listenInitBleEsp32(true, handleDeviceInit)
// 监听WiFi配置事件
mDeviceEvent.listenSendRouterSsidAndPassword(true, handleWiFiConfig)
// 监听自定义数据事件
mDeviceEvent.listenSendCustomData(true, handleCustomData)
addLog('事件监听器设置成功')
} catch (error) {
addLog(`事件监听器设置失败: ${error}`)
}
}
/**
* 清理事件监听器
*/
const cleanupEventListeners = () => {
if (!mDeviceEvent) {
return
}
try {
mDeviceEvent.listenDeviceMsgEvent(false, handleDeviceMessage)
mDeviceEvent.listenStartDiscoverBle(false, handleDeviceDiscovery)
mDeviceEvent.listenConnectBle(false, handleDeviceConnection)
mDeviceEvent.listenInitBleEsp32(false, handleDeviceInit)
mDeviceEvent.listenSendRouterSsidAndPassword(false, handleWiFiConfig)
mDeviceEvent.listenSendCustomData(false, handleCustomData)
addLog('事件监听器清理成功')
} catch (error) {
addLog(`事件监听器清理失败: ${error}`)
}
}
/**
* 处理设备消息事件
*/
const handleDeviceMessage = (message: any) => {
addLog(`收到设备消息: ${JSON.stringify(message)}`)
switch (message.type) {
case XBLUFI_TYPE.TYPE_STATUS_CONNECTED:
bluetoothState.value.isConnected = message.result
addLog(`设备连接状态: ${message.result ? '已连接' : '已断开'}`)
break
case XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS:
if (message.result) {
bluetoothState.value.devices = message.data
addLog(`发现 ${message.data.length} 个设备`)
}
break
case XBLUFI_TYPE.TYPE_CONNECTED:
if (message.result) {
bluetoothState.value.isConnected = true
bluetoothState.value.deviceInfo.name = message.data.name
bluetoothState.value.deviceInfo.id = message.data.deviceId
addLog(`设备连接成功: ${message.data.name}`)
} else {
addLog(`设备连接失败: ${JSON.stringify(message.data)}`)
}
break
case XBLUFI_TYPE.TYPE_RECIEVE_CUSTON_DATA:
addLog(`收到自定义数据: ${message.data}`)
break
case XBLUFI_TYPE.TYPE_CONNECT_ROUTER_RESULT:
if (message.result) {
addLog('WiFi配置成功')
} else {
addLog('WiFi配置失败')
}
break
default:
addLog(`未知消息类型: ${message.type}`)
}
}
/**
* 处理设备发现事件
*/
const handleDeviceDiscovery = (options: any) => {
addLog(`设备发现: ${options.isStart ? '开始' : '停止'}`)
}
/**
* 处理设备连接事件
*/
const handleDeviceConnection = (options: any) => {
addLog(`设备连接: ${options.isStart ? '连接' : '断开'} ${options.deviceId}`)
}
/**
* 处理设备初始化事件
*/
const handleDeviceInit = (options: any) => {
addLog(`设备初始化: ${options.deviceId}`)
}
/**
* 处理WiFi配置事件
*/
const handleWiFiConfig = (options: any) => {
addLog(`WiFi配置: SSID=${options.ssid}`)
}
/**
* 处理自定义数据事件
*/
const handleCustomData = (options: any) => {
addLog(`发送自定义数据: ${options.customData}`)
}
/**
* 添加日志记录
*/
const addLog = (message: string) => {
const timestamp = new Date().toLocaleTimeString()
logs.value.unshift(`[${timestamp}] ${message}`)
// 限制日志数量
if (logs.value.length > 50) {
logs.value = logs.value.slice(0, 50)
}
}
/**
* 开始扫描蓝牙设备
*/
const startScanDevices = () => {
if (!bluetoothState.value.isSupported) {
addLog('浏览器不支持蓝牙')
return
}
try {
bluetoothState.value.isScanning = true
addLog('开始扫描蓝牙设备...')
// 使用blufi系统扫描设备
if (mDeviceEvent) {
mDeviceEvent.notifyStartDiscoverBle({
isStart: true,
filter: '' // 可以设置设备名称过滤
})
} else {
addLog('Blufi系统未加载使用Web Bluetooth API扫描')
// 这里可以添加Web Bluetooth API的扫描逻辑
}
} catch (error) {
addLog(`扫描失败: ${error}`)
bluetoothState.value.isScanning = false
}
}
/**
* 停止扫描蓝牙设备
*/
const stopScanDevices = () => {
try {
bluetoothState.value.isScanning = false
addLog('停止扫描蓝牙设备')
// 使用blufi系统停止扫描
if (mDeviceEvent) {
mDeviceEvent.notifyStartDiscoverBle({
isStart: false
})
}
} catch (error) {
addLog(`停止扫描失败: ${error}`)
}
}
/**
* 连接蓝牙设备
*/
const connectDevice = (device: any) => {
if (!device) {
addLog('请选择要连接的设备')
return
}
try {
addLog(`正在连接设备: ${device.name}`)
// 使用blufi系统连接设备
if (mDeviceEvent) {
mDeviceEvent.notifyConnectBle({
isStart: true,
deviceId: device.deviceId,
name: device.name
})
} else {
addLog('Blufi系统未加载无法连接设备')
return
}
bluetoothState.value.selectedDevice = device
} catch (error) {
addLog(`连接失败: ${error}`)
}
}
/**
* 断开设备连接
*/
const disconnectDevice = () => {
if (!bluetoothState.value.selectedDevice) {
addLog('没有连接的设备')
return
}
try {
addLog('正在断开设备连接...')
// 使用blufi系统断开连接
if (mDeviceEvent) {
mDeviceEvent.notifyConnectBle({
isStart: false,
deviceId: bluetoothState.value.selectedDevice.deviceId,
name: bluetoothState.value.selectedDevice.name
})
}
bluetoothState.value.isConnected = false
bluetoothState.value.selectedDevice = null
} catch (error) {
addLog(`断开连接失败: ${error}`)
}
}
/**
* 初始化设备
*/
const initDevice = () => {
if (!bluetoothState.value.selectedDevice) {
addLog('请先连接设备')
return
}
try {
addLog('正在初始化设备...')
// 使用blufi系统初始化设备
if (mDeviceEvent) {
mDeviceEvent.notifyInitBleEsp32({
deviceId: bluetoothState.value.selectedDevice.deviceId
})
} else {
addLog('Blufi系统未加载无法初始化设备')
}
} catch (error) {
addLog(`设备初始化失败: ${error}`)
}
}
/**
* 发送WiFi配置
*/
const sendWiFiConfig = () => {
if (!bluetoothState.value.isConnected) {
addLog('设备未连接')
return
}
if (!controlCommands.value.wifiSsid || !controlCommands.value.wifiPassword) {
addLog('请输入WiFi名称和密码')
return
}
try {
addLog('正在发送WiFi配置...')
// 使用blufi系统发送WiFi配置
if (mDeviceEvent) {
mDeviceEvent.notifySendRouterSsidAndPassword({
deviceId: bluetoothState.value.selectedDevice.deviceId,
serverId: '000000FF-0000-1000-8000-00805F9B34FB',
characterId: '0000FF01-0000-1000-8000-00805F9B34FB',
ssid: controlCommands.value.wifiSsid,
password: controlCommands.value.wifiPassword
})
} else {
addLog('Blufi系统未加载无法发送WiFi配置')
}
} catch (error) {
addLog(`发送WiFi配置失败: ${error}`)
}
}
/**
* 发送自定义数据
*/
const sendCustomData = () => {
if (!bluetoothState.value.isConnected) {
addLog('设备未连接')
return
}
if (!controlCommands.value.customData) {
addLog('请输入自定义数据')
return
}
try {
addLog(`正在发送自定义数据: ${controlCommands.value.customData}`)
// 使用blufi系统发送自定义数据
if (mDeviceEvent) {
mDeviceEvent.notifySendCustomData({
deviceId: bluetoothState.value.selectedDevice.deviceId,
serverId: '000000FF-0000-1000-8000-00805F9B34FB',
characterId: '0000FF01-0000-1000-8000-00805F9B34FB',
customData: controlCommands.value.customData
})
} else {
addLog('Blufi系统未加载无法发送自定义数据')
}
} catch (error) {
addLog(`发送自定义数据失败: ${error}`)
}
}
/**
* 控制LED开关
*/
const toggleLED = () => {
controlCommands.value.ledOn = !controlCommands.value.ledOn
const command = controlCommands.value.ledOn ? 'led_on' : 'led_off'
controlCommands.value.customData = command
sendCustomData()
}
/**
* 控制电机速度
*/
const setMotorSpeed = (speed: number) => {
controlCommands.value.motorSpeed = speed
controlCommands.value.customData = `motor_speed:${speed}`
sendCustomData()
}
/**
* 设置温度
*/
const setTemperature = (temp: number) => {
controlCommands.value.temperature = temp
controlCommands.value.customData = `temperature:${temp}`
sendCustomData()
}
/**
* 设置湿度
*/
const setHumidity = (humidity: number) => {
controlCommands.value.humidity = humidity
controlCommands.value.customData = `humidity:${humidity}`
sendCustomData()
}
/**
* 清除日志
*/
const clearLogs = () => {
logs.value = []
}
</script>
<template>
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 p-4">
<div class="max-w-6xl mx-auto space-y-6">
<!-- 页面标题 -->
<div class="text-center">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">
Web端蓝牙设备控制
</h1>
<p class="text-gray-600 dark:text-gray-300">
基于Blufi系统的蓝牙设备扫描连接和控制
</p>
</div>
<!-- 蓝牙状态和控制 -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- 设备扫描和控制 -->
<div class="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm">
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">
设备扫描和控制
</h2>
<!-- 蓝牙支持状态 -->
<div class="mb-4">
<div class="flex items-center space-x-2">
<div class="w-3 h-3 rounded-full" :class="bluetoothState.isSupported ? 'bg-green-500' : 'bg-red-500'"></div>
<span class="text-sm text-gray-600 dark:text-gray-300">
Web Bluetooth API: {{ bluetoothState.isSupported ? '支持' : '不支持' }}
</span>
</div>
</div>
<!-- 扫描控制按钮 -->
<div class="space-y-3 mb-6">
<button
@click="startScanDevices"
:disabled="!bluetoothState.isSupported || bluetoothState.isScanning"
class="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 text-white font-medium rounded-lg transition-colors"
>
{{ bluetoothState.isScanning ? '扫描中...' : '开始扫描设备' }}
</button>
<button
@click="stopScanDevices"
:disabled="!bluetoothState.isScanning"
class="w-full px-4 py-2 bg-gray-600 hover:bg-gray-700 disabled:bg-gray-400 text-white font-medium rounded-lg transition-colors"
>
停止扫描
</button>
</div>
<!-- 设备列表 -->
<div class="mb-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-3">发现的设备</h3>
<div class="space-y-2 max-h-40 overflow-y-auto">
<div
v-for="device in bluetoothState.devices"
:key="device.deviceId"
class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded-lg"
>
<div>
<p class="font-medium text-gray-900 dark:text-white">{{ device.name || '未知设备' }}</p>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ device.deviceId }}</p>
</div>
<button
@click="connectDevice(device)"
:disabled="bluetoothState.isConnected"
class="px-3 py-1 bg-green-600 hover:bg-green-700 disabled:bg-gray-400 text-white text-sm rounded transition-colors"
>
连接
</button>
</div>
<div v-if="bluetoothState.devices.length === 0" class="text-center text-gray-500 dark:text-gray-400 py-4">
暂无发现的设备
</div>
</div>
</div>
<!-- 连接控制 -->
<div class="space-y-3">
<button
@click="initDevice"
:disabled="!bluetoothState.isConnected"
class="w-full px-4 py-2 bg-purple-600 hover:bg-purple-700 disabled:bg-gray-400 text-white font-medium rounded-lg transition-colors"
>
初始化设备
</button>
<button
@click="disconnectDevice"
:disabled="!bluetoothState.isConnected"
class="w-full px-4 py-2 bg-red-600 hover:bg-red-700 disabled:bg-gray-400 text-white font-medium rounded-lg transition-colors"
>
断开连接
</button>
</div>
</div>
<!-- 设备信息 -->
<div class="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm">
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">
设备信息
</h2>
<div class="space-y-4">
<div class="flex items-center justify-between">
<span class="text-gray-600 dark:text-gray-300">设备名称:</span>
<span class="font-medium text-gray-900 dark:text-white">{{ bluetoothState.deviceInfo.name || '未连接' }}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-600 dark:text-gray-300">设备ID:</span>
<span class="font-medium text-gray-900 dark:text-white text-sm">{{ bluetoothState.deviceInfo.id || '未连接' }}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-600 dark:text-gray-300">连接状态:</span>
<div class="flex items-center space-x-2">
<div class="w-3 h-3 rounded-full" :class="bluetoothState.isConnected ? 'bg-green-500' : 'bg-red-500'"></div>
<span class="font-medium text-gray-900 dark:text-white">
{{ bluetoothState.isConnected ? '已连接' : '未连接' }}
</span>
</div>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-600 dark:text-gray-300">电池电量:</span>
<span class="font-medium text-gray-900 dark:text-white">{{ bluetoothState.deviceInfo.batteryLevel }}%</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-600 dark:text-gray-300">固件版本:</span>
<span class="font-medium text-gray-900 dark:text-white">{{ bluetoothState.deviceInfo.firmwareVersion || '未知' }}</span>
</div>
</div>
</div>
</div>
<!-- 设备控制 -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- 基础控制 -->
<div class="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm">
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">
基础控制
</h2>
<div class="space-y-4">
<!-- LED控制 -->
<div class="flex items-center justify-between">
<span class="text-gray-600 dark:text-gray-300">LED控制:</span>
<button
@click="toggleLED"
:disabled="!bluetoothState.isConnected"
class="px-4 py-2 rounded-lg transition-colors"
:class="controlCommands.ledOn
? 'bg-green-600 hover:bg-green-700 text-white'
: 'bg-gray-600 hover:bg-gray-700 text-white disabled:bg-gray-400'"
>
{{ controlCommands.ledOn ? '关闭LED' : '开启LED' }}
</button>
</div>
<!-- 电机速度控制 -->
<div>
<label class="block text-gray-600 dark:text-gray-300 mb-2">
电机速度: {{ controlCommands.motorSpeed }}%
</label>
<input
type="range"
min="0"
max="100"
v-model="controlCommands.motorSpeed"
@change="setMotorSpeed(controlCommands.motorSpeed)"
:disabled="!bluetoothState.isConnected"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<!-- 温度控制 -->
<div>
<label class="block text-gray-600 dark:text-gray-300 mb-2">
温度设置: {{ controlCommands.temperature }}°C
</label>
<input
type="range"
min="0"
max="50"
v-model="controlCommands.temperature"
@change="setTemperature(controlCommands.temperature)"
:disabled="!bluetoothState.isConnected"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<!-- 湿度控制 -->
<div>
<label class="block text-gray-600 dark:text-gray-300 mb-2">
湿度设置: {{ controlCommands.humidity }}%
</label>
<input
type="range"
min="0"
max="100"
v-model="controlCommands.humidity"
@change="setHumidity(controlCommands.humidity)"
:disabled="!bluetoothState.isConnected"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
</div>
</div>
<!-- WiFi配置和自定义数据 -->
<div class="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm">
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">
WiFi配置和自定义数据
</h2>
<div class="space-y-4">
<!-- WiFi配置 -->
<div>
<label class="block text-gray-600 dark:text-gray-300 mb-2">WiFi网络名称 (SSID)</label>
<input
type="text"
v-model="controlCommands.wifiSsid"
placeholder="请输入WiFi名称"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
</div>
<div>
<label class="block text-gray-600 dark:text-gray-300 mb-2">WiFi密码</label>
<input
type="password"
v-model="controlCommands.wifiPassword"
placeholder="请输入WiFi密码"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
</div>
<button
@click="sendWiFiConfig"
:disabled="!bluetoothState.isConnected || !controlCommands.wifiSsid || !controlCommands.wifiPassword"
class="w-full px-4 py-2 bg-orange-600 hover:bg-orange-700 disabled:bg-gray-400 text-white font-medium rounded-lg transition-colors"
>
发送WiFi配置
</button>
<!-- 自定义数据 -->
<div>
<label class="block text-gray-600 dark:text-gray-300 mb-2">自定义数据命令</label>
<input
type="text"
v-model="controlCommands.customData"
placeholder="请输入自定义命令"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
</div>
<button
@click="sendCustomData"
:disabled="!bluetoothState.isConnected || !controlCommands.customData"
class="w-full px-4 py-2 bg-purple-600 hover:bg-purple-700 disabled:bg-gray-400 text-white font-medium rounded-lg transition-colors"
>
发送自定义数据
</button>
</div>
</div>
</div>
<!-- 操作日志 -->
<div class="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
操作日志
</h2>
<button
@click="clearLogs"
class="px-3 py-1 bg-gray-600 hover:bg-gray-700 text-white text-sm rounded transition-colors"
>
清除日志
</button>
</div>
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 h-64 overflow-y-auto">
<div v-if="logs.length === 0" class="text-center text-gray-500 dark:text-gray-400 py-8">
暂无日志记录
</div>
<div v-else class="space-y-1">
<div
v-for="(log, index) in logs"
:key="index"
class="text-sm text-gray-700 dark:text-gray-300 font-mono"
>
{{ log }}
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
/* 自定义滚动条样式 */
.overflow-y-auto::-webkit-scrollbar {
width: 6px;
}
.overflow-y-auto::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.overflow-y-auto::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.overflow-y-auto::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* 暗色模式滚动条 */
.dark .overflow-y-auto::-webkit-scrollbar-track {
background: #374151;
}
.dark .overflow-y-auto::-webkit-scrollbar-thumb {
background: #6b7280;
}
.dark .overflow-y-auto::-webkit-scrollbar-thumb:hover {
background: #9ca3af;
}
/* 滑块样式 */
input[type="range"] {
-webkit-appearance: none;
appearance: none;
background: transparent;
cursor: pointer;
}
input[type="range"]::-webkit-slider-track {
background: #d1d5db;
height: 8px;
border-radius: 4px;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
background: #3b82f6;
height: 20px;
width: 20px;
border-radius: 50%;
cursor: pointer;
}
input[type="range"]::-moz-range-track {
background: #d1d5db;
height: 8px;
border-radius: 4px;
border: none;
}
input[type="range"]::-moz-range-thumb {
background: #3b82f6;
height: 20px;
width: 20px;
border-radius: 50%;
cursor: pointer;
border: none;
}
/* 暗色模式滑块 */
.dark input[type="range"]::-webkit-slider-track {
background: #4b5563;
}
.dark input[type="range"]::-moz-range-track {
background: #4b5563;
}
</style>