🔌 串口调试助手 - 基于 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. 项目背景与需求分析

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+ 作为核心框架,主要基于以下考虑:

  1. Composition APIrefreactivecomputed 等 API 使状态管理更加清晰,特别适合处理串口状态、消息列表、统计数据等复杂响应式数据
  2. TypeScript 原生支持:类型推断能力强,能有效减少运行时错误
  3. 响应式性能优化:Vue 3 的 Proxy 响应式系统比 Vue 2 的 Object.defineProperty 更高效
  4. 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 环境:通过 serialport npm 包实现串口通信
  • 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 方法 ──→ 状态变更 ──→ 视图更新
    ↑                                              │
    └────────────────── 计算属性 ←──────────────────┘

具体流程示例:

  1. 用户点击"连接"按钮
  2. 组件触发 toggleConnection 事件
  3. 调用 SerialService.connect() 方法
  4. Service 更新 _isConnected 状态,添加系统消息
  5. Vue 响应式系统检测到状态变化
  6. 终端窗口自动更新显示连接成功消息

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-bind CSS 变量功能,实现字体大小的动态响应式绑定。

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. 参考链接

  1. CSDN 博客质量分计算 V5.0 - 博客质量评分标准
  2. Vue 3 官方文档 - Vue 3 Composition API 参考
  3. TypeScript 官方文档 - TypeScript 类型系统指南
  4. Vite 官方文档 - Vite 构建工具配置
  5. Web Serial API - 浏览器串口 API
  6. vue-router 文档 - Vue 路由管理
  7. HarmonyOS 开发者文档 - HarmonyOS Web 引擎
  8. Modbus 协议规范 - Modbus RTU 协议标准
  9. ESP-AT 指令集 - ESP 系列 AT 指令参考

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐