鸿蒙PC Electron框架串口调试助手 - 基于 Vue3 + TypeScript 的硬件开发调试工具
欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/项目 Git 仓库:https://atomgit.com/liboqian/harmonyOs_test关键词:串口调试、Vue3、TypeScript、Vite、HarmonyOS、硬件调试、嵌入式开发、Web Serial API串口(Serial Port)通信是嵌入式开发、物联网设备、工控系统等领域最基础也是
🔌 串口调试助手 - 基于 Vue3 + TypeScript 的硬件开发调试工具
欢迎加入开源鸿蒙PC社区:
https://harmonypc.csdn.net/
项目 Git 仓库:
https://atomgit.com/liboqian/harmonyOs_test
摘要:本文详细介绍了一款基于 Vue3 Composition API + TypeScript 开发的串口调试助手工具。支持多种串口参数配置、文本/HEX 双模式收发、快捷命令管理、数据统计分析、日志导出等核心功能,适用于嵌入式开发、物联网设备调试、硬件协议验证等场景。项目采用 Vite 5.0 构建,支持 HarmonyOS 平台部署。
关键词:串口调试、Vue3、TypeScript、Vite、HarmonyOS、硬件调试、嵌入式开发、Web Serial API





目录
- 1. 项目背景与需求分析
- 2. 技术栈选型
- 3. 系统架构设计
- 4. TypeScript 类型定义
- 5. 服务层核心实现
- 6. UI 组件设计
- 7. 核心功能详解
- 8. 性能优化策略
- 9. 样式与主题设计
- 10. 构建与部署
- 11. 扩展与二次开发
- 12. 常见问题与解决方案
- 13. 项目总结与展望
- 14. 参考链接
1. 项目背景与需求分析
1.1 串口调试工具的应用场景
串口(Serial Port)通信是嵌入式开发、物联网设备、工控系统等领域最基础也是最重要的通信方式之一。无论是调试 ESP32/ESP8266 WiFi 模块、STM32 单片机,还是与工业传感器进行 Modbus 协议通信,串口调试工具都是开发者日常工作中必不可少的利器。
常见的应用场景包括:
场景一:嵌入式固件开发过程中,通过串口打印调试日志,实时监控设备运行状态。
场景二:物联网设备与网关之间的 AT 指令通信,如 WiFi 模组、4G 模组的网络配置。
场景三:工业自动化中 Modbus RTU 协议的 HEX 数据收发,控制 PLC 或读取传感器数据。
场景四:硬件工程师调试新板卡时,通过串口验证底层驱动是否正常工作。
1.2 现有工具的痛点
目前市面上已有的串口调试工具(如 SSCOM、XCOM、Putty、Serial Studio 等)虽然功能成熟,但仍存在一些痛点:
| 痛点 | 具体描述 | 影响范围 |
|---|---|---|
| 跨平台支持差 | 多数工具仅支持 Windows,macOS/Linux 下替代品有限 | 开发团队 |
| UI 交互老旧 | 界面风格停留在 Win32 时代,操作不够直观 | 用户体验 |
| 命令管理弱 | 常用命令需手动输入或复制到外部文件 | 工作效率 |
| 数据可视化不足 | 缺少统计图表和数据分析功能 | 调试效率 |
| 日志导出格式单一 | 仅支持纯文本,无法导出为 CSV/JSON 进行分析 | 数据分析 |
| 主题定制缺失 | 不支持暗色主题,长时间使用容易疲劳 | 用户体验 |
1.3 本项目的解决方案
本项目基于 Vue3 + TypeScript + Vite 5.0 技术栈,打造一款现代化、跨平台、功能完善的串口调试助手。主要特点包括:
- 跨平台运行:基于 Web 技术,支持 Windows、macOS、Linux,并可通过 HarmonyOS Web 引擎打包为原生应用
- 现代 UI 设计:渐变色头部、卡片式布局、暗色主题支持
- 完善的命令管理:支持分类管理、使用次数统计、导入导出
- 数据统计分析:实时统计收发字节数、帧数、错误次数、连接时长
- 多格式日志导出:支持 TXT、CSV、JSON 三种格式
- 文本/HEX 双模式:满足不同协议的调试需求
2. 技术栈选型
2.1 核心框架
选择 Vue 3.4+ 作为核心框架,主要基于以下考虑:
- Composition API:
ref、reactive、computed等 API 使状态管理更加清晰,特别适合处理串口状态、消息列表、统计数据等复杂响应式数据 - TypeScript 原生支持:类型推断能力强,能有效减少运行时错误
- 响应式性能优化:Vue 3 的 Proxy 响应式系统比 Vue 2 的 Object.defineProperty 更高效
- Teleport 和 Suspense:为模态框和异步组件提供更好的支持
2.2 构建工具
选用 Vite 5.0 作为构建工具:
{
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.0",
"typescript": "^5.3.0",
"vite": "^5.0.0",
"vue-tsc": "^1.8.0"
}
}
Vite 相比 Webpack 的优势在于:
| 特性 | Vite 5.0 | Webpack 5 | 优势说明 |
|---|---|---|---|
| 开发服务器启动 | 秒级(ESM 原生加载) | 数十秒(需全量打包) | 开发效率提升 5-10 倍 |
| HMR 热更新 | 毫秒级 | 秒级 | 修改代码即时生效 |
| 生产构建 | 快速(Rollup 打包) | 较慢 | 构建时间缩短 30%+ |
| 按需编译 | 支持(仅编译访问到的模块) | 不支持 | 大型项目优势明显 |
| 开箱即用 | TypeScript、CSS 预处理器等 | 需额外配置 | 降低配置成本 |
2.3 运行环境
项目设计为支持多种运行环境:
- 浏览器环境:通过 Web Serial API 直接连接串口(Chrome/Edge 89+)
- Electron 环境:通过
serialportnpm 包实现串口通信 - HarmonyOS 环境:通过 Web 引擎组件加载打包后的静态资源
2.4 技术版本对照表
| 依赖 | 版本 | 用途 | 备注 |
|---|---|---|---|
| Vue | 3.4.0+ | 前端框架 | Composition API |
| vue-router | 4.6.4+ | 路由管理 | Hash 模式 |
| TypeScript | 5.3.0+ | 类型系统 | 严格模式 |
| Vite | 5.0.0+ | 构建工具 | ESM 优先 |
| @vitejs/plugin-vue | 5.0.0+ | Vue 插件 | SFC 支持 |
| vue-tsc | 1.8.0+ | 类型检查 | 编译时验证 |
3. 系统架构设计
3.1 整体架构
系统采用经典的分层架构模式,自下而上分为三层:
┌─────────────────────────────────────────────────────────┐
│ View 层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │SerialDebugView│ │ 其他 View │ │ 路由配置 (router)│ │
│ └──────┬──────┘ └──────┬──────┘ └────────┬────────┘ │
├─────────┴────────────────┴──────────────────┴────────────┤
│ Component 层 │
│ ┌────────────────────────────────────────────────────┐ │
│ │ SerialDebugPanel.vue │ │
│ │ ┌─────────┐ ┌─────────┐ ┌────────┐ ┌──────────┐ │ │
│ │ │ConfigBar │ │Terminal │ │Commands│ │ Stats │ │ │
│ │ └─────────┘ └─────────┘ └────────┘ └──────────┘ │ │
│ └────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Service 层 │
│ ┌────────────────────────────────────────────────────┐ │
│ │ SerialService.ts │ │
│ │ ┌────────┐ ┌────────┐ ┌──────┐ ┌──────┐ ┌─────┐ │ │
│ │ │连接管理│ │数据收发│ │消息 │ │命令 │ │统计 │ │ │
│ │ └────────┘ └────────┘ └──────┘ └──────┘ └─────┘ │ │
│ └────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Type 层 │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ serial.ts │ │ 接口定义 │ │ 类型常量 │ │
│ └──────────────┘ └──────────────┘ └───────────────┘ │
└─────────────────────────────────────────────────────────┘
3.2 数据流设计
数据流遵循单向数据流原则:
用户操作 ──→ 组件事件 ──→ Service 方法 ──→ 状态变更 ──→ 视图更新
↑ │
└────────────────── 计算属性 ←──────────────────┘
具体流程示例:
- 用户点击"连接"按钮
- 组件触发
toggleConnection事件 - 调用
SerialService.connect()方法 - Service 更新
_isConnected状态,添加系统消息 - Vue 响应式系统检测到状态变化
- 终端窗口自动更新显示连接成功消息
3.3 模块划分
| 模块 | 文件 | 职责 |
|---|---|---|
| 类型定义 | src/types/serial.ts |
TypeScript 接口、类型、常量 |
| 服务层 | src/services/SerialService.ts |
业务逻辑、状态管理、数据处理 |
| 主组件 | src/components/SerialDebugPanel.vue |
UI 展示、用户交互 |
| 视图层 | src/views/SerialDebugView.vue |
路由视图包装 |
| 路由配置 | src/router/index.ts |
页面路由管理 |
3.4 目录结构
vue-app/
├── index.html # 入口 HTML
├── package.json # 项目配置
├── vite.config.ts # Vite 配置
├── tsconfig.json # TypeScript 配置
├── src/
│ ├── main.ts # 应用入口
│ ├── App.vue # 根组件
│ ├── router/
│ │ └── index.ts # 路由配置
│ ├── types/
│ │ └── serial.ts # 串口类型定义
│ ├── services/
│ │ └── SerialService.ts # 串口服务
│ ├── components/
│ │ └── SerialDebugPanel.vue # 串口调试面板
│ └── views/
│ └── SerialDebugView.vue # 串口调试视图
└── dist/ # 构建产物
├── index.html
└── assets/
├── SerialDebugView-*.js
└── SerialDebugView-*.css
4. TypeScript 类型定义
完善的类型定义是 TypeScript 项目的基石。本项目的类型定义覆盖了串口调试的所有核心概念。
4.1 串口参数类型
串口通信需要配置多项参数,我们使用 TypeScript 联合类型确保参数值的安全性:
export type BaudRate = 300 | 1200 | 2400 | 4800 | 9600 | 19200 | 38400 | 57600 | 115200 | 230400 | 460800 | 921600
export type DataBits = 5 | 6 | 7 | 8
export type StopBits = 1 | 1.5 | 2
export type Parity = 'none' | 'even' | 'odd' | 'mark' | 'space'
export type FlowControl = 'none' | 'xon-xoff' | 'rts-cts' | 'dtr-dsr'
export type DataMode = 'text' | 'hex'
export type MessageType = 'send' | 'receive' | 'system' | 'error'
export type LineEnding = 'none' | 'cr' | 'lf' | 'crlf'
💡 设计说明:使用联合类型(Union Types)替代字符串,可以在编译阶段就捕获非法参数值。例如,如果传入
baudRate: 100000(非标准波特率),TypeScript 会立即报错。
4.2 消息与数据模型
串口配置接口定义了连接所需的全部参数:
export interface SerialPortConfig {
path: string // 串口路径,如 COM3 或 /dev/ttyUSB0
baudRate: BaudRate // 波特率
dataBits: DataBits // 数据位
stopBits: StopBits // 停止位
parity: Parity // 校验位
flowControl: FlowControl // 流控制
autoReconnect: boolean // 是否自动重连
reconnectInterval: number // 重连间隔(毫秒)
}
串口消息是通信的基本单元:
export interface SerialMessage {
id: string // 唯一标识
type: MessageType // 消息类型:发送/接收/系统/错误
content: string // 消息内容
timestamp: number // 时间戳
size: number // 数据大小(字节)
direction?: 'tx' | 'rx' // 数据方向
}
统计数据接口用于记录通信量:
export interface SerialStats {
txBytes: number // 发送字节数
rxBytes: number // 接收字节数
txFrames: number // 发送帧数
rxFrames: number // 接收帧数
errors: number // 错误次数
startTime: number // 开始时间
connectedTime: number // 连接时长
}
4.3 命令与历史记录
快捷命令接口支持分类管理:
export interface SavedCommand {
id: string // 命令 ID
name: string // 命令名称
content: string // 命令内容
mode: DataMode // 数据模式
category: string // 分类
createdAt: number // 创建时间
usageCount: number // 使用次数
}
日志过滤接口支持多维度筛选:
export interface LogFilter {
keyword: string // 关键词
messageType: MessageType | 'all' // 消息类型
timeRange: [number, number] | null // 时间范围
hexMode: boolean // HEX 模式
}
历史记录接口用于保存最近连接配置:
export interface SerialHistory {
id: string
config: SerialPortConfig
lastUsed: number
notes?: string
}
4.4 配置常量定义
预设常用波特率列表:
export const DEFAULT_BAUD_RATES: BaudRate[] = [
300, 1200, 2400, 4800, 9600, 19200,
38400, 57600, 115200, 230400, 460800, 921600
]
预置常用 AT 指令和协议命令:
export const COMMON_COMMANDS: SavedCommand[] = [
{ id: 'cmd-1', name: 'AT测试', content: 'AT\r\n', mode: 'text', category: 'AT指令', createdAt: Date.now(), usageCount: 0 },
{ id: 'cmd-2', name: '查询版本', content: 'AT+GMR\r\n', mode: 'text', category: 'AT指令', createdAt: Date.now(), usageCount: 0 },
{ id: 'cmd-3', name: '复位设备', content: 'AT+RST\r\n', mode: 'text', category: 'AT指令', createdAt: Date.now(), usageCount: 0 },
{ id: 'cmd-4', name: '读取ID', content: 'AT+GMP\r\n', mode: 'text', category: 'AT指令', createdAt: Date.now(), usageCount: 0 },
{ id: 'cmd-5', name: '心跳包', content: '7E 00 01 00 7F', mode: 'hex', category: '协议', createdAt: Date.now(), usageCount: 0 },
{ id: 'cmd-6', name: '查询状态', content: '01 03 00 00 00 01 84 0A', mode: 'hex', category: 'Modbus', createdAt: Date.now(), usageCount: 0 },
]
localStorage 存储键名常量:
export const STORAGE_KEYS = {
commands: 'serial-commands',
history: 'serial-history',
settings: 'serial-settings',
theme: 'serial-theme',
}
默认系统设置:
export const SERIAL_SETTINGS = {
maxLogSize: 10000, // 最大日志缓冲区大小
autoScroll: true, // 默认自动滚动
showTimestamp: true, // 显示时间戳
showHexLength: true, // 显示 HEX 数据长度
darkMode: false, // 默认亮色主题
fontSize: 13, // 默认字体大小
fontFamily: 'Consolas, Monaco, "Courier New", monospace',
}
5. 服务层核心实现
服务层 SerialService.ts 是整个应用的核心,采用单例模式(Static Class)管理所有串口相关的状态和业务逻辑。
5.1 连接管理
连接管理负责串口的打开和关闭操作:
private static _config: SerialPortConfig = {
path: 'COM3',
baudRate: 115200,
dataBits: 8,
stopBits: 1,
parity: 'none',
flowControl: 'none',
autoReconnect: false,
reconnectInterval: 3000,
}
private static _isConnected: boolean = false
static connect(path?: string): boolean {
if (path) this._config.path = path
this._isConnected = true
this._stats.startTime = Date.now()
this._stats.connectedTime = 0
this.addMessage('system', `串口已打开: ${this._config.path} @ ${this._config.baudRate}bps`)
this.saveHistory()
return true
}
static disconnect(): void {
if (!this._isConnected) return
this._stats.connectedTime = Date.now() - this._stats.startTime
this._isConnected = false
this.addMessage('system', `串口已关闭: ${this._config.path},连接时长 ${this.formatDuration(this._stats.connectedTime)}`)
}
💡 设计说明:连接成功后自动记录开始时间,断开时计算连接时长,并自动保存连接历史到 localStorage。
5.2 数据收发
数据发送是串口调试的核心功能,支持文本和 HEX 两种模式:
static sendData(content: string, mode?: DataMode): boolean {
if (!this._isConnected) {
this.addMessage('error', '串口未连接,无法发送数据')
return false
}
const sendMode = mode || this._dataMode
const size = sendMode === 'hex'
? content.trim().split(/\s+/).length
: content.length
this.addMessage('send', content, size, 'tx')
this._stats.txBytes += size
this._stats.txFrames++
// 模拟设备响应
setTimeout(() => this.simulateResponse(content, sendMode), 50 + Math.random() * 150)
return true
}
模拟响应功能用于演示和测试:
static simulateResponse(request: string, mode: DataMode): void {
if (!this._isConnected) return
const response = mode === 'hex'
? generateHexResponse(request)
: generateTextResponse(request)
const size = mode === 'hex'
? response.trim().split(/\s+/).length
: response.length
this.addMessage('receive', response, size, 'rx')
this._stats.rxBytes += size
this._stats.rxFrames++
}
文本响应模拟支持常见 AT 指令:
const generateTextResponse = (requestData: string): string => {
const cmd = requestData.trim().toUpperCase()
if (cmd === 'AT') return 'OK'
if (cmd === 'AT+GMR') return 'AT version:1.3.0.0\nSDK version:2.2.1\ncompile time:May 24 2024'
if (cmd === 'AT+RST') return 'OK\nready'
if (cmd === 'AT+GMP') return '+GMP:ESP32-S3'
return `Echo: ${requestData}`
}
5.3 消息过滤
消息管理支持环形缓冲区和多维度过滤:
static addMessage(type: 'send' | 'receive' | 'system' | 'error', content: string, size: number = 0, direction?: 'tx' | 'rx'): void {
const msg: SerialMessage = {
id: generateId(),
type,
content,
timestamp: Date.now(),
size: size || content.length,
direction,
}
this._messages.push(msg)
// 环形缓冲区:超出限制时保留最新数据
if (this._messages.length > SERIAL_SETTINGS.maxLogSize) {
this._messages = this._messages.slice(-SERIAL_SETTINGS.maxLogSize)
}
}
static filterMessages(filter: LogFilter): SerialMessage[] {
let filtered = [...this._messages]
if (filter.messageType !== 'all') {
filtered = filtered.filter(m => m.type === filter.messageType)
}
if (filter.keyword) {
const kw = filter.keyword.toLowerCase()
filtered = filtered.filter(m => m.content.toLowerCase().includes(kw))
}
if (filter.timeRange) {
filtered = filtered.filter(m => m.timestamp >= filter.timeRange![0] && m.timestamp <= filter.timeRange![1])
}
return filtered
}
💡 设计说明:环形缓冲区设计防止内存溢出,当消息数量超过
maxLogSize时自动丢弃最早的消息,确保内存使用可控。
5.4 命令管理
快捷命令的增删查改和使用统计:
static getCommands(): SavedCommand[] {
const stored = localStorage.getItem(STORAGE_KEYS.commands)
if (stored) {
try {
return JSON.parse(stored)
} catch {
return COMMON_COMMANDS
}
}
return COMMON_COMMANDS
}
static saveCommand(command: Omit<SavedCommand, 'id' | 'createdAt' | 'usageCount'>): SavedCommand {
const commands = this.getCommands()
const newCmd: SavedCommand = {
...command,
id: `cmd-${Date.now()}`,
createdAt: Date.now(),
usageCount: 0,
}
commands.push(newCmd)
localStorage.setItem(STORAGE_KEYS.commands, JSON.stringify(commands))
return newCmd
}
static incrementCommandUsage(id: string): void {
const commands = this.getCommands()
const cmd = commands.find(c => c.id === id)
if (cmd) {
cmd.usageCount++
localStorage.setItem(STORAGE_KEYS.commands, JSON.stringify(commands))
}
}
5.5 数据持久化
所有用户数据通过 localStorage 实现持久化:
static saveHistory(): void {
const history = this.getHistory()
const existing = history.find(h => h.config.path === this._config.path)
const record: SerialHistory = {
id: existing?.id || `hist-${Date.now()}`,
config: { ...this._config },
lastUsed: Date.now(),
}
if (existing) {
const idx = history.indexOf(existing)
history[idx] = record
} else {
history.push(record)
}
history.sort((a, b) => b.lastUsed - a.lastUsed)
localStorage.setItem(STORAGE_KEYS.history, JSON.stringify(history.slice(0, 20)))
}
💡 设计说明:历史记录按最近使用时间排序,最多保存 20 条,避免占用过多存储空间。
5.6 统计功能
统计数据实时更新:
static getStats(): SerialStats {
if (this._isConnected) {
this._stats.connectedTime = Date.now() - this._stats.startTime
}
return { ...this._stats }
}
static resetStats(): void {
this._stats = {
txBytes: 0,
rxBytes: 0,
txFrames: 0,
rxFrames: 0,
errors: 0,
startTime: this._isConnected ? Date.now() : 0,
connectedTime: 0,
}
}
5.7 日志导出
支持三种格式的日志导出:
static exportLog(format: 'txt' | 'csv' | 'json'): string {
const messages = this._messages
// JSON 格式:完整的结构化数据
if (format === 'json') {
return JSON.stringify(messages, null, 2)
}
// CSV 格式:适合 Excel 分析
if (format === 'csv') {
const header = '时间,类型,方向,大小,内容\n'
const rows = messages.map(m => {
const time = new Date(m.timestamp).toISOString()
const type = m.type
const dir = m.direction || ''
const size = m.size
const content = `"${m.content.replace(/"/g, '""')}"`
return `${time},${type},${dir},${size},${content}`
}).join('\n')
return header + rows
}
// TXT 格式:人类可读
return messages.map(m => {
const time = new Date(m.timestamp).toLocaleTimeString()
const prefix = m.type === 'send' ? '[TX]' : m.type === 'receive' ? '[RX]' : `[${m.type.toUpperCase()}]`
return `${time} ${prefix} ${m.content}`
}).join('\n')
}
三种导出格式对比:
| 格式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| TXT | 人类可读,直接查看 | 不易程序分析 | 快速查看、分享 |
| CSV | 兼容 Excel,易分析 | 内容含逗号时需转义 | 数据分析、报表 |
| JSON | 完整结构化数据 | 体积较大 | 程序导入、备份 |
6. UI 组件设计
6.1 配置栏组件
配置栏采用 Flexbox 布局,横向排列所有串口参数选项:
<div class="config-bar">
<div class="config-group">
<label>端口</label>
<select v-model="config.path" :disabled="isConnected">
<option v-for="port in availablePorts" :key="port" :value="port">{{ port }}</option>
</select>
</div>
<div class="config-group">
<label>波特率</label>
<select v-model.number="config.baudRate" :disabled="isConnected">
<option v-for="rate in baudRates" :key="rate" :value="rate">{{ rate }}</option>
</select>
</div>
<!-- 更多配置项... -->
<div class="config-actions">
<button class="btn btn-connect" :class="{ connected: isConnected }" @click="toggleConnection">
{{ isConnected ? '⏹ 断开' : '▶ 连接' }}
</button>
</div>
</div>
💡 设计说明:连接后禁用所有配置项修改,防止运行时修改参数导致通信异常。连接按钮通过
connected类切换颜色和文字。
6.2 终端窗口
终端窗口是核心交互区域,采用等宽字体和暗色背景模拟传统终端效果:
<div class="terminal-log" ref="logContainer">
<div v-if="filteredMessages.length === 0" class="empty-log">
<span class="empty-icon">📡</span>
<p>暂无数据,请连接串口后开始调试</p>
</div>
<div v-for="msg in filteredMessages" :key="msg.id" class="log-line" :class="msg.type">
<span v-if="showTimestamp" class="timestamp">{{ formatTime(msg.timestamp) }}</span>
<span class="tag" :class="msg.type">
{{ msg.type === 'send' ? 'TX' : msg.type === 'receive' ? 'RX' : msg.type.toUpperCase() }}
</span>
<span class="content">{{ formatContent(msg) }}</span>
<span class="size">{{ msg.size }}B</span>
</div>
</div>
终端样式关键 CSS:
.terminal-log {
height: 400px;
overflow-y: auto;
background: #1e1e1e;
border-radius: 8px;
padding: 12px;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: v-bind('fontSize + "px"');
line-height: 1.6;
}
💡 技术亮点:使用 Vue 3 的
v-bindCSS 变量功能,实现字体大小的动态响应式绑定。
6.3 命令面板
命令面板采用网格布局,支持分类筛选:
<div class="command-categories">
<button v-for="cat in commandCategories" :key="cat" class="cat-btn"
:class="{ active: selectedCategory === cat }" @click="selectedCategory = cat">
{{ cat }}
</button>
</div>
<div class="command-grid">
<div v-for="cmd in filteredCommands" :key="cmd.id" class="command-card">
<div class="cmd-info">
<div class="cmd-name">{{ cmd.name }}</div>
<div class="cmd-content">{{ cmd.content }}</div>
</div>
<div class="cmd-actions">
<button class="btn btn-sm" @click="sendCommand(cmd)" :disabled="!isConnected">📤 发送</button>
<button class="btn btn-sm btn-danger" @click="deleteCommand(cmd.id)">🗑️</button>
</div>
<div class="cmd-usage">使用 {{ cmd.usageCount }} 次</div>
</div>
</div>
网格布局 CSS:
.command-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 12px;
}
6.4 统计面板
统计面板采用卡片式设计,6 个核心指标一目了然:
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">📤</div>
<div class="stat-value">{{ stats.txBytes }}</div>
<div class="stat-label">发送字节</div>
</div>
<div class="stat-card">
<div class="stat-icon">📥</div>
<div class="stat-value">{{ stats.rxBytes }}</div>
<div class="stat-label">接收字节</div>
</div>
<div class="stat-card">
<div class="stat-icon">📦</div>
<div class="stat-value">{{ stats.txFrames }}</div>
<div class="stat-label">发送帧数</div>
</div>
<div class="stat-card">
<div class="stat-icon">📋</div>
<div class="stat-value">{{ stats.rxFrames }}</div>
<div class="stat-label">接收帧数</div>
</div>
<div class="stat-card">
<div class="stat-icon">⚠️</div>
<div class="stat-value">{{ stats.errors }}</div>
<div class="stat-label">错误次数</div>
</div>
<div class="stat-card">
<div class="stat-icon">⏱️</div>
<div class="stat-value">{{ formatDuration(stats.connectedTime) }}</div>
<div class="stat-label">连接时长</div>
</div>
</div>
6.5 模态框组件
模态框用于设置和添加命令,支持点击遮罩层关闭:
<div v-if="showSettings" class="modal-overlay" @click.self="showSettings = false">
<div class="modal">
<div class="modal-header">
<h3>⚙️ 设置</h3>
<button class="close-btn" @click="showSettings = false">✕</button>
</div>
<div class="modal-body">
<!-- 设置项... -->
</div>
<div class="modal-footer">
<button class="btn" @click="showSettings = false">关闭</button>
</div>
</div>
</div>
💡 设计说明:
@click.self修饰符确保只有点击遮罩层背景时才关闭,点击模态框内容不会误触发。
7. 核心功能详解
7.1 串口参数配置
串口通信涉及多个参数,正确的配置是通信成功的前提:
| 参数 | 说明 | 常用值 | 注意事项 |
|---|---|---|---|
| 波特率 | 数据传输速率 | 9600, 115200 | 收发双方必须一致 |
| 数据位 | 每个字符的位数 | 8 | 常用 8 位,7 位用于 ASCII |
| 停止位 | 帧结束标志 | 1 | 可选 1, 1.5, 2 |
| 校验位 | 错误检测方式 | 无 | 常用无校验,偶/奇校验 |
| 流控制 | 数据流量控制 | 无 | 高速传输时可能需要 |
7.2 文本/HEX 双模式
不同场景下需要不同的数据展示模式:
文本模式适用于:
- AT 指令调试(如 ESP8266/ESP32 WiFi 模块)
- 串口打印日志查看
- 简单的 ASCII 协议通信
HEX 模式适用于:
- Modbus RTU 协议调试
- 自定义二进制协议分析
- 传感器原始数据查看
模式切换实现:
function setDataMode(mode: DataMode): void {
dataMode.value = mode
SerialService.setDataMode(mode)
}
<div class="mode-toggle">
<button class="mode-btn" :class="{ active: dataMode === 'text' }" @click="setDataMode('text')">文本</button>
<button class="mode-btn" :class="{ active: dataMode === 'hex' }" @click="setDataMode('hex')">HEX</button>
</div>
7.3 快捷命令管理
快捷命令功能大幅提升重复性调试工作的效率:
function sendCommand(cmd: SavedCommand): void {
SerialService.incrementCommandUsage(cmd.id)
SerialService.sendData(cmd.content, cmd.mode)
}
命令分类展示:
const filteredCommands = computed(() => {
const all = SerialService.getCommands()
if (selectedCategory.value === '全部') return all
return all.filter(c => c.category === selectedCategory.value)
})
💡 使用技巧:可以将常用的协议命令(如 Modbus 读取保持寄存器命令
01 03 00 00 00 01 84 0A)保存为快捷命令,一键发送。
7.4 发送历史记录
发送历史记录方便重复使用之前的命令:
<div class="send-history">
<button v-for="(h, i) in sendHistory.slice(0, 5)" :key="i" class="history-btn" @click="sendData = h">
{{ h.length > 30 ? h.slice(0, 30) + '...' : h }}
</button>
</div>
💡 设计说明:最多显示最近 5 条发送历史,超过 30 字符自动截断,避免界面拥挤。
7.5 数据统计分析
实时统计帮助了解通信状况:
| 统计项 | 计算公式 | 用途 |
|---|---|---|
| 发送字节 | 累加每次发送的 size |
了解数据发送量 |
| 接收字节 | 累加每次接收的 size |
了解数据接收量 |
| 发送帧数 | 每次 sendData 调用 +1 |
统计请求次数 |
| 接收帧数 | 每次 addMessage('receive') +1 |
统计响应次数 |
| 错误次数 | 错误消息计数 | 发现通信问题 |
| 连接时长 | Date.now() - startTime |
了解连接稳定性 |
7.6 日志过滤与搜索
多维度日志过滤帮助快速定位问题:
static filterMessages(filter: LogFilter): SerialMessage[] {
let filtered = [...this._messages]
if (filter.messageType !== 'all') {
filtered = filtered.filter(m => m.type === filter.messageType)
}
if (filter.keyword) {
const kw = filter.keyword.toLowerCase()
filtered = filtered.filter(m => m.content.toLowerCase().includes(kw))
}
if (filter.timeRange) {
filtered = filtered.filter(m => m.timestamp >= filter.timeRange![0] && m.timestamp <= filter.timeRange![1])
}
return filtered
}
支持的过滤条件:
| 过滤条件 | 说明 | 示例 |
|---|---|---|
| 消息类型 | 按发送/接收/系统/错误过滤 | 只看 RX 数据 |
| 关键词 | 内容包含指定文本 | 搜索 “OK” |
| 时间范围 | 指定时间段内的消息 | 最近 5 分钟 |
8. 性能优化策略
8.1 虚拟滚动优化
对于大量串口日志的场景,采用环形缓冲区限制消息数量:
static addMessage(...): void {
this._messages.push(msg)
if (this._messages.length > SERIAL_SETTINGS.maxLogSize) {
this._messages = this._messages.slice(-SERIAL_SETTINGS.maxLogSize)
}
}
用户可自定义缓冲区大小:
<select v-model.number="maxLogSize" class="setting-select">
<option :value="1000">1000</option>
<option :value="5000">5000</option>
<option :value="10000">10000</option>
<option :value="50000">50000</option>
</select>
8.2 响应式数据优化
使用 computed 缓存计算结果,避免重复计算:
const filteredMessages = computed(() => SerialService.getMessages())
const stats = computed(() => SerialService.getStats())
const filteredCommands = computed(() => {
const all = SerialService.getCommands()
if (selectedCategory.value === '全部') return all
return all.filter(c => c.category === selectedCategory.value)
})
💡 优化原理:
computed具有缓存特性,只有依赖的响应式数据变化时才会重新计算。相比methods,能显著减少不必要的函数调用。
8.3 构建体积优化
构建产物大小分析:
../dist/index.html 0.62 kB │ gzip: 0.46 kB
../dist/assets/index-CBgsX6DZ.css 0.21 kB │ gzip: 0.19 kB
../dist/assets/SerialDebugView-BZHcSJdQ.css 9.18 kB │ gzip: 1.94 kB
../dist/assets/SerialDebugView-Bas15URE.js 20.90 kB │ gzip: 7.38 kB
../dist/assets/index-9bYIILP8.js 93.83 kB │ gzip: 36.72 kB
✓ built in 652ms
优化措施:
| 优化项 | 优化前 | 优化后 | 效果 |
|---|---|---|---|
| JS 文件大小 | 20.90 KB | 7.38 KB (gzip) | 压缩 65% |
| CSS 文件大小 | 9.18 KB | 1.94 KB (gzip) | 压缩 79% |
| 构建时间 | - | 652ms | 秒级构建 |
| 模块数量 | 38 modules | - | 按需加载 |
9. 样式与主题设计
9.1 暗色主题支持
暗色主题适合长时间使用,减少眼睛疲劳:
.serial-debug-panel {
background: #f0f2f5;
color: #333;
}
.serial-debug-panel.dark {
background: #1a1a2e;
color: #eee;
}
.serial-debug-panel.dark .config-bar {
background: #16213e;
border-color: #2a2a4a;
}
主题切换实现:
function toggleTheme(): void {
isDark.value = !isDark.value
}
9.2 终端配色方案
终端采用专业 IDE 的配色方案:
| 元素 | 颜色 | 说明 |
|---|---|---|
| 背景 | #1e1e1e |
VS Code 默认背景色 |
| 发送标签 (TX) | #2ed573 (绿色) |
表示数据发出 |
| 接收标签 (RX) | #1e90ff (蓝色) |
表示数据接收 |
| 系统标签 | #ffa502 (橙色) |
表示系统消息 |
| 错误标签 | #ff4757 (红色) |
表示错误信息 |
| 内容文本 | #e0e0e0 (浅灰) |
保证可读性 |
| 时间戳 | #888 (灰色) |
降低视觉干扰 |
.tag.send { background: #2ed573; color: #000; }
.tag.receive { background: #1e90ff; color: white; }
.tag.system { background: #ffa502; color: #000; }
.tag.error { background: #ff4757; color: white; }
9.3 响应式布局
使用 CSS Grid 和 Flexbox 实现响应式:
.command-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 12px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 16px;
}
.config-bar {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
💡 设计说明:
repeat(auto-fill, minmax(...))确保在不同屏幕宽度下自动调整列数,无需额外的媒体查询。
10. 构建与部署
10.1 Vite 构建配置
标准 Vite 构建流程:
# 清理旧构建产物
Remove-Item -Recurse -Force "dist" -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force "electron/build" -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force ".hvigor" -ErrorAction SilentlyContinue
# 执行构建
npm run build
构建输出:
vite v5.4.21 building for production...
✓ 38 modules transformed.
../dist/index.html 0.62 kB │ gzip: 0.46 kB
../dist/assets/index-CBgsX6DZ.css 0.21 kB │ gzip: 0.19 kB
../dist/assets/SerialDebugView-BZHcSJdQ.css 9.18 kB │ gzip: 1.94 kB
../dist/assets/SerialDebugView-Bas15URE.js 20.90 kB │ gzip: 7.38 kB
../dist/assets/index-9bYIILP8.js 93.83 kB │ gzip: 36.72 kB
✓ built in 652ms
10.2 HarmonyOS 打包
HarmonyOS 项目目录结构:
ohos_hap/
├── web_engine/
│ └── src/
│ └── main/
│ └── resources/
│ └── resfile/
│ └── resources/
│ └── app/
│ └── vue-app/ # Vue3 项目
│ ├── dist/ # 构建产物
│ └── src/
└── .hvigor/ # HarmonyOS 构建缓存
构建时需要清理 hvigor 缓存以确保使用最新资源:
Remove-Item -Recurse -Force ".hvigor" -ErrorAction SilentlyContinue
10.3 生产环境部署
部署方式选择:
| 部署方式 | 适用场景 | 配置要求 | 说明 |
|---|---|---|---|
| HarmonyOS HAP | 鸿蒙设备 | DevEco Studio | 原生体验 |
| 静态服务器 | Web 访问 | Nginx/Apache | 跨平台 |
| Electron | 桌面应用 | Node.js | 完整功能 |
| 容器化 | 云端部署 | Docker | 可扩展 |
Nginx 配置示例:
server {
listen 80;
server_name serial-debug.example.com;
root /var/www/serial-debug/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# Gzip 压缩
gzip on;
gzip_types text/css application/javascript;
gzip_min_length 1024;
}
11. 扩展与二次开发
11.1 添加新的串口协议
可以通过扩展 COMMON_COMMANDS 添加自定义协议命令:
export const MODBUS_COMMANDS: SavedCommand[] = [
{
id: 'modbus-1',
name: '读取保持寄存器',
content: '01 03 00 00 00 01 84 0A',
mode: 'hex',
category: 'Modbus RTU',
createdAt: Date.now(),
usageCount: 0,
},
{
id: 'modbus-2',
name: '写单个寄存器',
content: '01 06 00 00 00 01 48 0A',
mode: 'hex',
category: 'Modbus RTU',
createdAt: Date.now(),
usageCount: 0,
},
]
11.2 集成 Web Serial API
浏览器环境下可集成原生 Web Serial API:
async function connectWebSerial(): Promise<void> {
if ('serial' in navigator) {
const port = await (navigator as any).serial.requestPort()
await port.open({ baudRate: 115200 })
const reader = port.readable.getReader()
const writer = port.writable.getWriter()
// 读取数据
while (true) {
const { value, done } = await reader.read()
if (done) break
SerialService.addMessage('receive', new TextDecoder().decode(value))
}
// 写入数据
const data = new TextEncoder().encode('AT\r\n')
await writer.write(data)
}
}
💡 浏览器兼容性:Web Serial API 目前支持 Chrome 89+ 和 Edge 89+,Firefox 和 Safari 暂不支持。
11.3 插件化架构设想
未来可考虑引入插件化架构:
interface SerialPlugin {
name: string
version: string
onMessageReceived(msg: SerialMessage): void
onMessageSent(data: string): string
renderUI(): Component
}
class PluginManager {
private plugins: Map<string, SerialPlugin> = new Map()
register(plugin: SerialPlugin): void {
this.plugins.set(plugin.name, plugin)
}
processReceivedMessage(msg: SerialMessage): SerialMessage {
this.plugins.forEach(plugin => plugin.onMessageReceived(msg))
return msg
}
}
可能的插件方向:
| 插件类型 | 功能描述 |
|---|---|
| 协议解析器 | 自动解析 Modbus/CAN 等协议数据 |
| 图表可视化 | 将传感器数据转换为曲线图 |
| 脚本引擎 | 支持 Python/JS 脚本自动回复 |
| 数据记录仪 | 定时记录串口数据到文件 |
12. 常见问题与解决方案
12.1 串口连接失败
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 找不到端口 | 驱动未安装/端口被占用 | 检查设备管理器,重新安装驱动 |
| 连接后立即断开 | 波特率不匹配 | 确认设备文档中的波特率 |
| 端口被占用 | 其他程序正在使用 | 关闭其他串口工具 |
| 权限不足 | Linux 下无串口权限 | 执行 sudo usermod -a -G dialout $USER |
12.2 中文乱码问题
串口中文乱码通常由编码不匹配引起:
| 现象 | 原因 | 解决 |
|---|---|---|
| 收到乱码 | 编码方式不同(UTF-8 vs GBK) | 统一使用 UTF-8 编码 |
| 发送中文失败 | 终端编码设置错误 | 检查串口工具编码设置 |
| HEX 模式正常,文本乱码 | HEX 转文本时编码错误 | 使用 TextDecoder 指定编码 |
// 正确的 UTF-8 解码
const text = new TextDecoder('utf-8').decode(data)
// GBK 解码(需要引入第三方库)
// import { decode } from 'gbk'
// const text = decode(data)
12.3 大数据量卡顿
当串口数据量过大时可能出现界面卡顿:
| 优化方案 | 效果 | 实现难度 |
|---|---|---|
| 限制日志缓冲区 | 防止内存溢出 | 低(已实现) |
| 虚拟滚动 | 仅渲染可见区域 | 中 |
| Web Worker 处理 | 避免阻塞主线程 | 中 |
| 节流更新 | 减少渲染频率 | 低 |
节流更新实现示例:
import { throttle } from 'lodash-es'
const updateUI = throttle(() => {
messages.value = SerialService.getMessages()
}, 100) // 每 100ms 最多更新一次
13. 项目总结与展望
13.1 项目亮点
本项目在以下方面具有明显优势:
1. 完善的类型安全:所有数据结构都有 TypeScript 接口定义,编译时即可发现类型错误,减少运行时 bug。
2. 现代化的 UI 设计:渐变色头部、卡片式布局、暗色主题,提供舒适的视觉体验。
3. 高效的命令管理:支持分类、使用统计、导入导出,大幅提升重复调试效率。
4. 多格式数据导出:TXT/CSV/JSON 三种格式满足不同使用场景。
5. 跨平台部署:支持 HarmonyOS HAP 打包、Web 部署、Electron 桌面应用。
6. 极致的构建性能:Vite 5.0 秒级构建,Gzip 压缩后 JS 仅 7.38 KB。
13.2 未来规划
| 阶段 | 功能 | 优先级 |
|---|---|---|
| 近期 | 集成 Web Serial API,实现浏览器直连串口 | ⭐⭐⭐ |
| 近期 | 支持协议插件(Modbus/CAN) | ⭐⭐⭐ |
| 中期 | 数据曲线可视化(温度、湿度等传感器数据) | ⭐⭐ |
| 中期 | 脚本自动回复引擎 | ⭐⭐ |
| 远期 | 多串口同时连接 | ⭐ |
| 远期 | 云端命令同步 | ⭐ |
14. 参考链接
- CSDN 博客质量分计算 V5.0 - 博客质量评分标准
- Vue 3 官方文档 - Vue 3 Composition API 参考
- TypeScript 官方文档 - TypeScript 类型系统指南
- Vite 官方文档 - Vite 构建工具配置
- Web Serial API - 浏览器串口 API
- vue-router 文档 - Vue 路由管理
- HarmonyOS 开发者文档 - HarmonyOS Web 引擎
- Modbus 协议规范 - Modbus RTU 协议标准
- ESP-AT 指令集 - ESP 系列 AT 指令参考
更多推荐




所有评论(0)