要把 Crucial MX500 的健康数据搬到 Vue 3 页面上,不能期望浏览器直接访问 SATA 设备。合理的工程化方案是增加一个轻量级 Node.js 数据服务,由它执行 smartctl 命令获取 SMART 参数,前端通过 HTTP 接口消费这些数据。本文以 MX500 为例,完整走一遍从项目搭建到可视化面板落地的流程。

一、项目初始化与工程化配置
工程化的第一步是确定项目边界。MX500 是一块 SATA 固态硬盘,它本身没有网络接口,也没有官方 JavaScript SDK,因此前端不能直接读取它的 SMART 信息。这里的解决思路是让 Vue 3 项目只负责展示和交互,另外启动一个 Node.js 服务专门负责命令调用与数据格式化。两者通过 RESTful API 通信,这样既保持了前端工程的结构清晰,也方便以后把服务端替换成 Electron 主进程或独立守护进程。
前端部分使用 Vite 官方模板创建 Vue 3 与 TypeScript 组合工程。创建完成后先安装两个核心依赖:Pinia 用于状态管理,ECharts 用于图表展示。目录结构建议拆成 components、composables、stores、types 四层。组件目录放展示卡片和图表,composables 目录放轮询和请求逻辑,stores 目录放全局硬盘状态,types 目录放接口返回类型。这样做的好处是当后续接入其他硬盘型号时,只需要扩展 types 和对应的数据映射,不会动到组件层。
npm create vite@latest mx500-dashboard -- --template vue-ts cd mx500-dashboard npm install pinia echarts npm install -D eslint prettier eslint-plugin-vue
接着在 vite.config.ts 中配置开发服务器代理,把 /api 前缀的请求转发到本机 Node 服务的 3001 端口。这样前端代码里只需要写相对路径,无需关心跨域问题。代理配置还能避免在开发环境中暴露后端端口,生产环境如果单独部署,可以由 Nginx 做同样的转发。
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
'/api': {
target: 'http://127.0.0.1:3001',
changeOrigin: true
}
}
}
})
这里要说明一个常见误区:不要把获取硬盘信息的逻辑写进 Vue 组件里。浏览器运行在受限的沙箱环境中,无法执行 smartctl 这类系统命令。即便使用 Electron,渲染进程也不应该直接调用 Node 能力,最好通过 preload 暴露受控接口。对于纯 Web 项目,独立 Node 服务是更稳妥的选择。
二、通过 Node 服务采集 MX500 的 SMART 数据
Crucial MX500 支持 S.M.A.R.T. 标准,通过 smartmontools 工具包可以读取到丰富的健康参数,包括重分配扇区数、累计写入量、通电时间、温度以及磨损均衡计数。Node 服务要做的第一件事是确认系统已安装 smartmontools。在 Ubuntu 或 Debian 上可以用 apt install smartmontools 安装,在 macOS 上可以使用 brew install smartmontools。Windows 用户需要单独下载 smartmontools,并把 smartctl.exe 所在目录加入环境变量。
Node 代码使用 child_process.execFile 来执行 smartctl -A /dev/sda 命令。execFile 的参数以数组形式传递,不会经过 shell 拼接,因此在接收前端传入的设备名时能有效避免命令注入。得到命令输出后,需要解析属性表。输出格式通常是多行文本,每行包含 ID、属性名、当前值、最差值、阈值、原始值等字段。可以按空白字符切分,也可以使用 smartmontools 提供的 --json 参数直接输出 JSON,省去正则解析的麻烦。
import express from 'express'
import { execFile } from 'child_process'
import { promisify } from 'util'
const execFileAsync = promisify(execFile)
const app = express()
app.get('/api/disk/info', async (req, res) => {
try {
const device = '/dev/sda'
const { stdout } = await execFileAsync('smartctl', ['-A', '--json', device])
const data = JSON.parse(stdout)
res.json({
model: data.model_name,
serial: data.serial_number,
temperature: data.temperature.current,
powerOnHours: data.power_on_time.hours,
wearLeveling: data.ata_smart_attributes.table.find(
(item) => item.name === 'Wear_Leveling_Count'
)
})
} catch (err) {
res.status(500).json({ error: 'smartctl 执行失败,请检查设备路径和权限' })
}
})
app.listen(3001)
在实际部署时,设备路径不能写死。MX500 在不同机器上可能被识别为 /dev/sda、/dev/sdb 或者 Windows 下的 PD0。比较好的做法是先用 smartctl --scan 扫描所有磁盘,再由用户在前端选择要监控的目标设备。服务端可以把扫描结果缓存下来,当设备切换时再重新读取对应 SMART 信息。
还需要注意 Linux 权限问题。普通用户执行 smartctl 通常需要 root 权限,否则会提示 Permission denied。生产环境不建议直接用 root 运行 Node 服务,可以为 smartctl 配置 sudo 免密规则,或者将运行用户加入 disk 组。错误处理必须覆盖命令不存在、设备不存在、输出解析失败三种情况,否则前端会拿到难以排查的 500 错误。
三、Vue 3 数据层与 Pinia 状态管理
前端拿到接口后,不能直接在组件里写 fetch 和 setInterval。数据轮询、状态缓存、错误恢复都属于应用级逻辑,应该放入 Pinia store。创建 diskStore,内部维护 diskInfo、loading、error 三个状态,并暴露 fetchDiskInfo 和 startPolling 两个 action。这样任何组件都能通过 store 共享同一份硬盘数据,避免多个组件各自请求造成接口压力。
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { DiskInfo } from '../types'
export const useDiskStore = defineStore('disk', () => {
const diskInfo = ref<DiskInfo | null>(null)
const loading = ref(false)
const error = ref('')
let timer: number | null = null
async function fetchDiskInfo() {
loading.value = true
error.value = ''
try {
const res = await fetch('/api/disk/info')
if (!res.ok) throw new Error('请求失败')
diskInfo.value = await res.json()
} catch (err) {
error.value = err instanceof Error ? err.message : '未知错误'
} finally {
loading.value = false
}
}
function startPolling(interval = 5000) {
stopPolling()
timer = window.setInterval(fetchDiskInfo, interval)
}
function stopPolling() {
if (timer !== null) {
window.clearInterval(timer)
timer = null
}
}
return { diskInfo, loading, error, fetchDiskInfo, startPolling, stopPolling }
})
轮询逻辑最好再封装成 composable,负责在组件挂载时启动轮询、卸载时停止轮询。这一层与 Pinia 解耦,组件里只需要调用 useDiskPolling() 即可。比如在首页组件里:
import { onMounted, onUnmounted } from 'vue'
import { useDiskStore } from '../stores/disk'
export function useDiskPolling(interval = 5000) {
const store = useDiskStore()
onMounted(() => {
store.fetchDiskInfo()
store.startPolling(interval)
})
onUnmounted(() => {
store.stopPolling()
})
return store
}
这里有一个容易忽略的点:如果用户切到其他路由,当前组件被卸载,轮询应该自动停止。把 stopPolling 放在 onUnmounted 中可以避免后台继续请求。对于需要长期监控的场景,可以把轮询句柄提升到全局 layout 组件中,确保整个应用生命周期内持续刷新。
MX500 的 SMART 参数中有些值变化缓慢,比如通电时间和累计写入量,不必每 5 秒都刷新。可以通过 store 中保存 lastFetchTime,在 startPolling 时判断当前间隔,也可以由服务端返回 cache-control 头来控制缓存。合理的轮询间隔一般设为 10 到 30 秒,既能及时反映温度变化,又不会给硬盘带来额外负担。
四、可视化组件与 ECharts 集成
数据到位后,展示层可以拆成两个组件:DiskHealthCard 负责显示型号、序列号、温度、健康状态等关键指标,TemperatureChart 负责绘制温度和写入量随时间变化的曲线。前者使用简单的响应式文本,后者借助 ECharts 的 init 和 setOption 方法动态更新。
图表组件如果每次数据更新都重新初始化实例,会造成明显的性能浪费。正确的做法是在 onMounted 中创建一次 chart 实例,后续通过 watch 监听数据变化,只调用 setOption 更新系列数据。当组件卸载时,必须调用 dispose 释放图表占用的内存,否则在频繁切换页面时会出现内存泄漏。
<template>
<div ref="chartRef" class="chart"></div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from 'vue'
import * as echarts from 'echarts'
import { useDiskStore } from '../stores/disk'
const chartRef = ref<HTMLDivElement | null>(null)
let chart: echarts.ECharts | null = null
const store = useDiskStore()
onMounted(() => {
if (chartRef.value) {
chart = echarts.init(chartRef.value)
}
})
watch(() => store.diskInfo, (info) => {
if (!chart || !info) return
chart.setOption({
xAxis: { type: 'time' },
yAxis: { type: 'value' },
series: [{
name: '温度',
type: 'line',
data: info.temperatureHistory
}]
})
})
onUnmounted(() => {
chart?.dispose()
chart = null
})
</script>
温度历史数据需要在前端维护一个环形数组,每次轮询时把最新温度和时间戳追加进去,同时限制最大长度。超出长度后移除最旧记录,这样既能让图表保持滚动效果,又不会让内存无限增长。写入量、磨损计数等指标同样可以用折线或柱状图展示,但需要注意单位换算。MX500 报告的原始写入量通常以扇区或 LBAs 为单位,需要乘以 512 字节再换算成 GB,否则图表上的数值会让人困惑。
除了图表,健康状态提示也应该直观。可以根据 SMART 属性中的 Reallocated_Sector_Ct 和 Wear_Leveling_Count 给出文字评价。例如重分配扇区数为 0、磨损计数低于 90% 时显示正常,否则给出警告颜色。这一层逻辑可以单独抽出 utils/health.ts,方便单元测试和后续复用到其他硬盘型号。
五、部署与后续工程化扩展
开发环境完成后,部署路径有两种选择。如果只做本地工具,可以用 Electron 把 Vue 3 前端和 Node 服务打包成桌面应用。主进程启动后拉起 Express 服务,渲染进程照常请求 http://127.0.0.1:3001。这样用户不需要手动运行两个进程,也能获得原生应用的托盘和开机自启能力。Electron 的 main 进程中可以使用 utilityProcess 来运行服务端代码,避免阻塞主线程。
如果希望部署成网页服务,则需要把前端打包后的静态文件交给 Nginx 托管,Node 服务用 pm2 或 systemd 守护。Nginx 配置中同样要把 /api 代理到 Node 端口,并限制访问来源。因为接口暴露了硬盘序列号等敏感信息,最好加上简单的访问令牌或内网防火墙规则。对于公网环境,强烈建议启用 HTTPS,避免数据在传输中被窃听。
进一步扩展时,可以把服务端做成插件式架构。不同硬盘厂商对 SMART 属性的命名和含义略有差异,有些 NVMe SSD 使用 smartctl -A 输出的字段完全不同。通过抽象一个 DeviceAdapter 接口,为 MX500 实现 SataAdapter,为后续添加的其他硬盘编写对应适配器,前端无需改动即可支持更多设备。日志和告警模块也可以逐步接入,比如温度超过 60 度时发送通知,或者累计写入量接近 TBW 时提示备份数据。
整个工程化的重点并不在于 Vue 3 本身有多复杂,而在于如何把硬件采集、服务端处理和前端展示这三层职责分清楚。MX500 只是具体载体,这套结构可以平滑迁移到任何支持 S.M.A.R.T. 的存储设备上。
Vue 3固态硬盘监控Crucial MX500修改时间:2026-09-20 08:04:59