This commit is contained in:
2026-06-02 15:28:25 +08:00
commit b271b84f70
249 changed files with 42240 additions and 0 deletions
+942
View File
@@ -0,0 +1,942 @@
<template>
<view class="container">
<!-- ========== 状态栏 ========== -->
<!-- 显示蓝牙适配器和打印机的当前连接状态 -->
<view class="status-section">
<view class="status-item">
<text class="status-label">蓝牙适配器</text>
<text class="status-value" :class="adapterState.available ? 'available' : ''">
{{ adapterState.available ? '就绪' : '未就绪' }}
</text>
</view>
<view class="status-item">
<text class="status-label">打印机</text>
<text class="status-value" :class="{ connected: connected }">
{{ connected ? '已连接' : '未连接' }}
</text>
</view>
</view>
<!-- ========== 蓝牙操作按钮 ========== -->
<!-- 初始化蓝牙搜索设备断开连接 -->
<view class="action-section">
<button type="primary" size="mini" @click="initBluetooth" :disabled="isInitializing">
{{ isInitializing ? '初始化中...' : '初始化蓝牙' }}
</button>
<button v-if="adapterState.available && !adapterState.discovering" type="primary" size="mini" @click="startDiscovery">
搜索打印机
</button>
<button v-if="adapterState.discovering" type="default" size="mini" @click="stopDiscovery">
停止搜索
</button>
<button v-if="connected" type="warn" size="mini" @click="disconnectPrinter">
断开连接
</button>
</view>
<!-- ========== 设备列表 ========== -->
<!-- 未连接时显示搜索到的蓝牙设备列表 -->
<view class="section" v-if="!connected">
<view class="section-header">
<text class="section-title">可用设备 ({{ deviceList.length }})</text>
</view>
<scroll-view class="device-list" scroll-y>
<view
class="device-item"
v-for="device in deviceList"
:key="device.deviceId"
:class="{ connecting: connectingDeviceId === device.deviceId }"
@click="connectDevice(device)">
<text class="device-name">{{ device.name || device.localName || '未知设备' }}</text>
<text class="device-id">{{ device.deviceId }}</text>
<text class="connect-hint" v-if="connectingDeviceId === device.deviceId">连接中...</text>
</view>
<view class="empty-tip" v-if="deviceList.length === 0 && !adapterState.discovering">
<text>暂无设备请点击搜索</text>
</view>
</scroll-view>
</view>
<!-- ========== 打印控制区 ========== -->
<!-- 已连接时显示打印控制界面包括服务配置指令模板打印测试等 -->
<view class="section print-section" v-else>
<view class="section-header">
<text class="section-title">打印控制</text>
<text class="printer-name">{{ connectedDeviceName }}</text>
</view>
<!-- ===== 蓝牙服务配置区域 ===== -->
<!-- 用于配置蓝牙服务的UUID和特征值这是蓝牙打印机通信的关键参数 -->
<view class="service-select-section" v-if="serviceList.length > 0">
<view class="section-header">
<text class="section-title">蓝牙服务配置</text>
<view class="header-actions">
<!-- 保存当前配置到本地存储下次可直接使用 -->
<button size="mini" type="primary" @click="saveCurrentConfig" :disabled="!connected || !writeCharacteristicId">保存配置</button>
</view>
</view>
<!-- ===== 打印纸尺寸选择 ===== -->
<view class="paper-size-section">
<text class="section-title">打印纸尺寸</text>
<view class="paper-size-grid">
<view
class="paper-size-item"
:class="{ selected: paperWidth === 100 && paperHeight === 100 }"
@click="selectPaperSize(100, 100)">
<text class="paper-size-name">100mm x 100mm</text>
</view>
<view
class="paper-size-item"
:class="{ selected: paperWidth === 150 && paperHeight === 150 }"
@click="selectPaperSize(150, 150)">
<text class="paper-size-name">150mm x 150mm</text>
</view>
<view
class="paper-size-item"
:class="{ selected: paperWidth === 200 && paperHeight === 200 }"
@click="selectPaperSize(200, 200)">
<text class="paper-size-name">200mm x 200mm</text>
</view>
</view>
</view>
<!-- 当前已选择的服务和特征值UUID -->
<view class="current-config">
<view class="config-item">
<text class="config-label">服务UUID:</text>
<text class="config-value" :class="{ active: writeServiceId }">{{ writeServiceId || '未选择' }}</text>
</view>
<view class="config-item">
<text class="config-label">特征值UUID:</text>
<text class="config-value" :class="{ active: writeCharacteristicId }">{{ writeCharacteristicId || '未选择' }}</text>
</view>
</view>
<!-- 服务列表点击可选择服务并获取其特征值 -->
<view class="service-list">
<text class="list-title">服务列表 ({{ serviceList.length }})</text>
<scroll-view class="service-scroll" scroll-x>
<view
class="service-tag"
v-for="(service, index) in serviceList"
:key="index"
:class="{ selected: selectedService && selectedService.uuid === service.uuid }"
@click="selectService(service)">
<text class="service-index">{{ index + 1 }}</text>
<text class="service-uuid">{{ service.uuid }}</text>
<text class="service-type">{{ service.isPrimary ? '主' : '从' }}</text>
</view>
</scroll-view>
</view>
<!-- 特征值列表显示每个特征值的属性write/read/notify等 -->
<view class="characteristic-list" v-if="characteristicList.length > 0">
<text class="list-title">特征值列表 ({{ characteristicList.length }})</text>
<scroll-view class="characteristic-scroll" scroll-y>
<view
class="characteristic-item"
v-for="(char, index) in characteristicList"
:key="index"
:class="{
selected: writeCharacteristicId === char.uuid,
writable: isWritable(char)
}"
@click="selectCharacteristic(char, serviceList.find(s => s.uuid === selectedServiceId))">
<view class="char-info">
<text class="char-uuid">{{ char.uuid }}</text>
<view class="char-props">
<text class="prop-tag" v-if="char.properties.write">write</text>
<text class="prop-tag" v-if="char.properties.writeNoResponse">writeNoResp</text>
<text class="prop-tag" v-if="char.properties.writeWithoutResponse">writeWithoutResp</text>
<text class="prop-tag" v-if="char.properties.read">read</text>
<text class="prop-tag" v-if="char.properties.notify">notify</text>
<text class="prop-tag" v-if="char.properties.indicate">indicate</text>
</view>
</view>
<text class="select-btn">{{ writeCharacteristicId === char.uuid ? '已选择' : '选择' }}</text>
</view>
</scroll-view>
</view>
</view>
</view>
<!-- ========== 操作日志 ========== -->
<!-- 记录所有蓝牙操作和打印指令发送的日志信息 -->
<view class="section log-section">
<view class="section-header">
<text class="section-title">操作日志</text>
<button size="mini" @click="clearLog">清空</button>
</view>
<scroll-view class="log-list" scroll-y>
<text class="log-item"
:class="log.type"
v-for="(log, index) in logList"
:key="index">
[{{ log.time }}] {{ log.message }}
</text>
</scroll-view>
</view>
</view>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
/**
* 蓝牙标签打印机配置页面
*
* 功能说明:
* 1. 初始化蓝牙适配器,搜索附近的蓝牙打印机设备
* 2. 连接选定的打印机,获取并配置蓝牙服务和特征值
* 3. 支持保存/加载打印机配置到 Vuex store
* 4. 显示操作日志便于调试
*
* 使用说明:
* - 首次使用需要初始化蓝牙并搜索设备
* - 连接设备后选择服务和特征值(通常自动选择可写的)
* - 点击"保存配置"保存当前配置,下次可直接使用
*/
export default {
data() {
return {
// ========== 蓝牙适配器状态 ==========
// available: 蓝牙适配器是否可用
// discovering: 是否正在搜索设备
adapterState: {
available: false,
discovering: false
},
isInitializing: false, // 是否正在初始化蓝牙
// ========== 设备列表 ==========
deviceList: [], // 搜索到的蓝牙设备列表
connectingDeviceId: '', // 正在连接的设备ID
// ========== 连接状态 ==========
connectedDeviceId: '', // 已连接设备ID
connectedDeviceName: '', // 已连接设备名称
connected: false, // 是否已连接打印机
// ========== 服务和特征值 ==========
// writeServiceId: 可写服务UUID(用于发送数据)
// writeCharacteristicId: 可写特征值UUID(数据写入点)
// serviceList: 设备的所有服务列表
// characteristicList: 当前服务的所有特征值列表
// selectedService: 当前选中的服务对象
// selectedServiceId: 当前选中的服务UUID
writeCharacteristicId: '',
writeServiceId: '',
serviceList: [],
characteristicList: [],
selectedService: null,
selectedServiceId: '',
// ========== 操作日志 ==========
// 记录所有操作和调试信息,最多保留100条
logList: []
}
},
computed: {
// 从 Vuex store 映射纸尺寸
...mapGetters('print', ['paperWidth', 'paperHeight'])
},
onLoad() {
// 页面加载时自动初始化蓝牙
this.initBluetooth()
// 设置蓝牙状态变化监听
this.setupListeners()
// 加载保存的配置(从 store 获取)
this.loadSavedConfig()
},
onUnload() {
// 页面卸载时清理资源(断开连接、停止搜索)
this.cleanup()
},
methods: {
// 映射 Vuex actions
...mapActions('print', ['updatePaperSize', 'savePrinterConfig']),
/**
* 添加日志记录
* @param {string} message - 日志消息内容
* @param {string} type - 日志类型:info/success/warn/error
*/
addLog(message, type = 'info') {
const now = new Date()
const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`
this.logList.unshift({ time, message, type })
// 最多保留100条日志
if (this.logList.length > 100) {
this.logList.pop()
}
console.log(`[BLE Print] ${message}`)
},
/**
* 清空所有日志
*/
clearLog() {
this.logList = []
},
/**
* 设置蓝牙事件监听器
* 包括:适配器状态变化、设备发现、连接状态变化
*/
setupListeners() {
// 监听蓝牙适配器状态变化
uni.onBluetoothAdapterStateChange((res) => {
this.addLog(`适配器: available=${res.available}, discovering=${res.discovering}`)
this.adapterState.available = res.available
this.adapterState.discovering = res.discovering
})
// 监听发现新设备
uni.onBluetoothDeviceFound((res) => {
const devices = res.devices || []
devices.forEach(device => {
// 过滤掉没有名称的设备
if (!device.name && !device.localName) return
const index = this.deviceList.findIndex(d => d.deviceId === device.deviceId)
if (index === -1) {
this.deviceList.push(device)
this.addLog(`发现: ${device.name || device.localName}`)
}
})
})
// 监听连接状态变化(用于检测设备被断开的情况)
uni.onBLEConnectionStateChange((res) => {
if (!res.connected && this.connectedDeviceId === res.deviceId) {
this.connected = false
this.connectedDeviceId = ''
this.connectedDeviceName = ''
this.addLog('设备已断开', 'warn')
}
})
},
/**
* 初始化蓝牙适配器
* 必须在使用任何蓝牙功能前调用
*/
initBluetooth() {
if (this.isInitializing) return
this.isInitializing = true
this.addLog('初始化蓝牙...')
uni.openBluetoothAdapter({
success: (res) => {
this.adapterState.available = true
this.isInitializing = false
this.addLog('蓝牙初始化成功')
// 获取已知设备(可能之前已配对)
this.getKnownDevices()
},
fail: (err) => {
this.isInitializing = false
this.adapterState.available = false
this.addLog(`初始化失败: ${err.errMsg}`, 'error')
}
})
},
/**
* 获取已知的蓝牙设备
* 这些设备可能是之前配对过的
*/
getKnownDevices() {
uni.getBluetoothDevices({
success: (res) => {
if (res.devices && res.devices.length > 0) {
this.deviceList = res.devices.filter(d => d.name || d.localName)
}
}
})
},
/**
* 开始搜索附近的蓝牙设备
* 搜索时长默认15秒
*/
startDiscovery() {
if (this.adapterState.discovering) return
this.deviceList = []
this.addLog('开始搜索...')
uni.startBluetoothDevicesDiscovery({
allowDuplicatesKey: true, // 允许重复发现同一设备
success: () => {
this.adapterState.discovering = true
this.addLog('搜索中...')
// 15秒后自动停止搜索
setTimeout(() => {
if (this.adapterState.discovering) {
this.stopDiscovery()
}
}, 15000)
},
fail: (err) => {
this.addLog(`搜索失败: ${err.errMsg}`, 'error')
}
})
},
/**
* 停止搜索蓝牙设备
*/
stopDiscovery() {
uni.stopBluetoothDevicesDiscovery({
success: () => {
this.adapterState.discovering = false
this.addLog('已停止搜索')
}
})
},
/**
* 连接指定的蓝牙设备
* @param {Object} device - 设备对象,包含deviceId和name
*/
connectDevice(device) {
if (this.connected) return
this.connectingDeviceId = device.deviceId
this.addLog(`连接 ${device.name || device.localName}...`)
uni.createBLEConnection({
deviceId: device.deviceId,
timeout: 10000, // 10秒超时
success: () => {
this.connected = true
this.connectedDeviceId = device.deviceId
this.connectedDeviceName = device.name || device.localName
this.connectingDeviceId = ''
this.addLog('连接成功')
// 获取设备的服务列表
this.getPrinterServices()
},
fail: (err) => {
this.connectingDeviceId = ''
this.addLog(`连接失败: ${err.errMsg}`, 'error')
}
})
},
/**
* 断开当前连接的蓝牙设备
*/
disconnectPrinter() {
if (!this.connectedDeviceId) return
this.addLog('断开连接...')
uni.closeBLEConnection({
deviceId: this.connectedDeviceId,
success: () => {
this.connected = false
this.connectedDeviceId = ''
this.connectedDeviceName = ''
this.serviceList = []
this.characteristicList = []
this.addLog('已断开')
},
fail: (err) => {
this.addLog(`断开失败: ${err.errMsg}`, 'error')
}
})
},
/**
* 获取已连接设备的服务列表
*/
getPrinterServices() {
this.addLog('获取服务...')
uni.getBLEDeviceServices({
deviceId: this.connectedDeviceId,
success: (res) => {
this.serviceList = res.services
this.addLog(`找到 ${res.services.length} 个服务`)
// 自动选择第一个主服务
const primaryService = res.services.find(s => s.isPrimary)
if (primaryService) {
this.selectService(primaryService)
}
},
fail: (err) => {
this.addLog(`获取服务失败: ${err.errMsg}`, 'error')
}
})
},
/**
* 选择服务并获取其特征值列表
* @param {Object} service - 选中的服务对象
*/
selectService(service) {
this.selectedService = service
this.selectedServiceId = service.uuid
this.addLog(`选择服务: ${service.uuid}`)
uni.getBLEDeviceCharacteristics({
deviceId: this.connectedDeviceId,
serviceId: service.uuid,
success: (res) => {
console.log(res)
this.characteristicList = res.characteristics
// 查找可写特征值,按优先级:write > writeNoResponse > writeWithoutResponse
const writableChar = res.characteristics.find(c => c.properties.write)
if (writableChar) {
this.writeCharacteristicId = writableChar.uuid
this.writeServiceId = service.uuid
this.addLog(`可写特征值: ${writableChar.uuid}`)
} else {
const writeNoRespChar = res.characteristics.find(c => c.properties.writeNoResponse)
if (writeNoRespChar) {
this.writeCharacteristicId = writeNoRespChar.uuid
this.writeServiceId = service.uuid
this.addLog(`可写特征值(writeNoResp): ${writeNoRespChar.uuid}`)
} else {
const writeWithoutRespChar = res.characteristics.find(c => c.properties.writeWithoutResponse)
if (writeWithoutRespChar) {
this.writeCharacteristicId = writeWithoutRespChar.uuid
this.writeServiceId = service.uuid
this.addLog(`可写特征值(writeWithoutResp): ${writeWithoutRespChar.uuid}`)
} else {
this.addLog(`未找到可写特征值,特征值数: ${res.characteristics.length}`, 'warn')
}
}
}
},
fail: (err) => {
this.addLog(`获取特征值失败: ${err.errMsg}`, 'error')
}
})
},
/**
* 选择特征值
* @param {Object} char - 选中的特征值对象
* @param {Object} service - 所属的服务对象
*/
selectCharacteristic(char, service) {
if (!service) {
this.addLog('请先选择服务', 'warn')
return
}
// 检查是否有任何可写属性
if (!this.isWritable(char)) {
this.addLog('该特征值无可写属性', 'warn')
return
}
this.writeCharacteristicId = char.uuid
this.writeServiceId = service.uuid
this.addLog(`已选择特征值: ${char.uuid}`)
uni.showToast({ title: '特征值已选择', icon: 'success' })
},
/**
* 检查特征值是否可写
* @param {Object} char - 特征值对象
* @returns {boolean} 是否可写
*/
isWritable(char) {
return char.properties && (
char.properties.write ||
char.properties.writeNoResponse ||
char.properties.writeWithoutResponse
)
},
/**
* 选择打印纸尺寸
* @param {number} width - 纸张宽度(mm)
* @param {number} height - 纸张高度(mm)
*/
async selectPaperSize(width, height) {
try {
await this.updatePaperSize({ width, height })
this.addLog(`已选择纸尺寸: ${width}mm x ${height}mm`)
uni.showToast({
title: `已选择 ${width}mm x ${height}mm`,
icon: 'success',
duration: 1500
})
} catch (error) {
this.addLog(`选择纸尺寸失败: ${error.message}`, 'error')
}
},
/**
* 清理资源
* 断开连接并停止搜索
*/
cleanup() {
if (this.connected) {
this.disconnectPrinter()
}
if (this.adapterState.discovering) {
this.stopDiscovery()
}
},
/**
* 保存当前配置到 Vuex store(自动同步到本地存储)
* 包括设备ID、服务UUID、特征值UUID等信息
* 保存后可快速加载使用
*/
async saveCurrentConfig() {
if (!this.connected) {
uni.showToast({ title: '请先连接打印机', icon: 'none' })
return
}
if (!this.writeServiceId || !this.writeCharacteristicId) {
uni.showToast({ title: '请先选择服务和特征值', icon: 'none' })
return
}
const config = {
deviceId: this.connectedDeviceId,
serviceId: this.writeServiceId,
writeCharId: this.writeCharacteristicId,
deviceName: this.connectedDeviceName,
connected: true,
lastUpdated: new Date().toISOString()
}
try {
await this.savePrinterConfig(config)
this.addLog(`配置已保存: ${this.connectedDeviceName}`, 'success')
uni.showToast({
title: '配置已保存',
icon: 'success',
duration: 2000
})
} catch (error) {
this.addLog(`保存配置失败: ${error.message}`, 'error')
}
},
/**
* 加载保存的配置
* 纸尺寸通过 computed 从 store 获取,无需手动加载
*/
loadSavedConfig() {
// 纸尺寸通过 mapGetters 映射,无需手动加载
this.addLog(`纸尺寸: ${this.paperWidth}mm x ${this.paperHeight}mm`)
}
}
}
</script>
<style scoped>
/* ========== 页面容器 ========== */
.container {
padding: 20rpx;
background: #f5f5f5;
min-height: 100vh;
}
/* ========== 状态栏 ========== */
.status-section {
display: flex;
background: #fff;
padding: 20rpx;
border-radius: 12rpx;
margin-bottom: 20rpx;
}
.status-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.status-label {
font-size: 24rpx;
color: #999;
margin-bottom: 8rpx;
}
.status-value {
font-size: 28rpx;
font-weight: bold;
color: #666;
}
.status-value.available {
color: #007aff; /* 蓝色 - 适配器就绪 */
}
.status-value.connected {
color: #34c759; /* 绿色 - 已连接 */
}
/* ========== 操作按钮区域 ========== */
.action-section {
display: flex;
gap: 16rpx;
margin-bottom: 20rpx;
flex-wrap: wrap;
}
.action-section button {
flex: 1;
min-width: 150rpx;
}
/* ========== 通用区块样式 ========== */
.section {
background: #fff;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.header-actions {
display: flex;
align-items: center;
gap: 16rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.printer-name {
font-size: 24rpx;
color: #34c759;
}
/* ========== 设备列表 ========== */
.device-list {
max-height: 300rpx;
}
.device-item {
padding: 24rpx;
border-bottom: 1rpx solid #eee;
}
.device-item:active {
background: #f5f5f5;
}
.device-item.connecting {
background: #e8f4ff; /* 连接中高亮 */
}
.device-name {
font-size: 28rpx;
font-weight: bold;
color: #333;
display: block;
}
.device-id {
font-size: 22rpx;
color: #999;
word-break: break-all;
}
.connect-hint {
font-size: 24rpx;
color: #007aff;
}
.empty-tip {
text-align: center;
padding: 40rpx;
color: #999;
}
/* ========== 打印控制区 ========== */
.print-section {
min-height: 200rpx;
}
/* ========== 打印纸尺寸选择 ========== */
.paper-size-section {
margin-bottom: 20rpx;
}
.paper-size-grid {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-top: 12rpx;
}
.paper-size-item {
flex: 1;
min-width: 200rpx;
padding: 20rpx;
background: #f0f0f0;
border-radius: 8rpx;
border: 2rpx solid transparent;
text-align: center;
}
.paper-size-item.selected {
background: #e8f4ff;
border-color: #007aff;
}
.paper-size-name {
font-size: 26rpx;
color: #333;
}
.paper-size-item.selected .paper-size-name {
color: #007aff;
font-weight: bold;
}
/* ========== 日志区域 ========== */
.log-section {
min-height: 200rpx;
}
.log-list {
height: 250rpx;
background: #1c1c1c; /* 深色背景 */
border-radius: 8rpx;
padding: 16rpx;
}
.log-item {
display: block;
font-size: 22rpx;
font-family: 'Courier New', monospace;
color: #fff;
margin-bottom: 8rpx;
line-height: 1.4;
}
.log-item.success {
color: #34c759; /* 绿色 */
}
.log-item.warn {
color: #ff9500; /* 橙色 */
}
.log-item.error {
color: #ff3b30; /* 红色 */
}
/* ========== 蓝牙服务配置区域 ========== */
.service-select-section {
margin-bottom: 20rpx;
padding: 16rpx;
background: #f8f8f8;
border-radius: 8rpx;
}
.current-config {
padding: 16rpx;
background: #fff;
border-radius: 8rpx;
margin-bottom: 16rpx;
}
.config-item {
display: flex;
margin-bottom: 8rpx;
}
.config-label {
font-size: 24rpx;
color: #666;
min-width: 140rpx;
}
.config-value {
font-size: 24rpx;
color: #999;
font-family: 'Courier New', monospace;
word-break: break-all;
flex: 1;
}
.config-value.active {
color: #34c759; /* 已选择状态 */
}
.service-list {
margin-bottom: 16rpx;
}
.list-title {
font-size: 26rpx;
color: #333;
margin-bottom: 12rpx;
display: block;
font-weight: bold;
}
.service-scroll {
width: 100%;
white-space: nowrap;
}
.service-tag {
display: inline-flex;
align-items: center;
padding: 12rpx 20rpx;
background: #fff;
border: 2rpx solid #ddd;
border-radius: 8rpx;
margin-right: 16rpx;
white-space: nowrap;
}
.service-tag.selected {
border-color: #007aff;
background: #e8f4ff;
}
.service-index {
font-size: 24rpx;
color: #007aff;
font-weight: bold;
margin-right: 12rpx;
}
.service-uuid {
font-size: 22rpx;
font-family: 'Courier New', monospace;
color: #333;
max-width: 300rpx;
overflow: hidden;
text-overflow: ellipsis;
}
.service-type {
font-size: 20rpx;
color: #fff;
background: #34c759;
padding: 4rpx 8rpx;
border-radius: 4rpx;
margin-left: 12rpx;
}
/* ========== 特征值列表 ========== */
.characteristic-list {
margin-bottom: 16rpx;
}
.characteristic-scroll {
max-height: 300rpx;
background: #fff;
border-radius: 8rpx;
}
.characteristic-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx;
border-bottom: 1rpx solid #eee;
}
.characteristic-item:last-child {
border-bottom: none;
}
.characteristic-item.selected {
background: #e8f4ff;
}
.characteristic-item.writable {
border-left: 4rpx solid #34c759; /* 可写特征值左侧标记 */
}
.char-info {
flex: 1;
}
.char-uuid {
font-size: 24rpx;
font-family: 'Courier New', monospace;
color: #333;
display: block;
margin-bottom: 8rpx;
word-break: break-all;
}
.char-props {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
}
.prop-tag {
font-size: 20rpx;
padding: 4rpx 8rpx;
background: #f0f0f0;
border-radius: 4rpx;
color: #666;
}
.select-btn {
font-size: 24rpx;
color: #007aff;
padding: 8rpx 16rpx;
}
</style>