导读:本期聚焦于永濑创作的《如何用 Vue 3 工程化思路打造一个类似耕升 Gainward Expertool 的显卡工具?》,敬请观看详情。耕升显卡用户对 Gainward Expertool 应该不陌生,这款官方工具负责温度监控、风扇调速和频率调节,属于典型的 Windows 桌面硬件软件。想自己动手做一套类似的显卡工具,其实完全可以站在 Vue 3 的工程化体系上完成:用 Vite 管理构建,用 Electron 把页面变成桌面程序,再由 Node.js 主进程调用系统接口采集 GPU 温度、转速与频率数据。本文先拆解 Expertool 的功能边界与技术本质,接着给出 Vite 加 Electron 的完整工程搭建步骤,然后讲解监控面板、风扇曲线编辑器等核心组件的划分思路,演示 Pinia 如何接管实时数据流,最后覆盖原生数据采集方案与 Windows 安装包打包流程,帮你把一个前端项目真正落地成硬件工具。

耕升显卡附赠的 Gainward Expertool 是一款经典的显卡调节工具,核心能力集中在几块:实时显示 GPU 核心温度、风扇转速、核心与显存频率、功耗和显存占用,允许用户自定义风扇曲线、手动拉频率做超频,部分型号还带灯光控制。这类软件给人的第一印象是硬件级的,跟前端技术似乎不沾边,但把它的界面拆开看,本质就是一个数据密集型的桌面客户端:界面层负责展示与交互,数据层持续从驱动和传感器读取状态。而界面这一层,恰好是 Vue 3 加上成熟工程化体系最能发挥的地方。这篇文章就完整走一遍:怎么用 Vite 加 Electron 搭骨架,怎么拆组件,数据怎么从传感器流到界面,风扇曲线编辑器怎么写,最后怎么打包成 Windows 安装包。

如何用 Vue 3 工程化思路打造一个类似耕升 Gainward Expertool 的显卡工具?

先弄清楚 Expertool 的技术本质

Expertool 能读到温度和转速,靠的不是什么黑魔法,而是显卡驱动暴露出来的底层接口。NVIDIA 显卡主要走 NVAPI,AMD 显卡走 ADL SDK,另外还有通用硬件监控库的路线,比如 LibreHardwareMonitor 的做法是直接读 MSR 寄存器和 SMBus 设备拿到原始数值。这些接口吐出来的都是裸数据,界面层拿到之后再做格式化、阈值判断和图表绘制。所以一个显卡工具的完整公式很清晰:原生数据采集,加上一个桌面壳,再加上界面渲染。

把这个公式映射到前端技术栈,对应关系是一一对应的:桌面壳用 Electron 承担,窗口管理、系统托盘、开机自启这些桌面软件的标配能力它都有;界面用 Vue 3 写,组件化正好匹配监控面板这种多模块布局;数据采集放在 Electron 主进程里,用 Node.js 原生模块或者调用系统命令完成。渲染进程不直接碰硬件,全部通过 IPC 通道拿数据,职责划分和 Expertool 本身没有区别,差别只在实现语言。

有一点要提前说清楚:读数据和写数据是两个难度等级。读取温度、频率、利用率属于相对安全的信息查询,Windows 自带的性能计数器就能覆盖一部分;而写入频率、电压(也就是超频)需要驱动层的特权调用,得依赖带签名的原生模块,操作不当还有风险。本文把重点放在监控和风扇策略这条链路上,写入部分只给出思路。

搭建 Vite 加 Electron 的工程骨架

先看目录结构。整个项目分成两块:electron 目录放主进程、preload 脚本和硬件采集封装,src 目录放纯前端的 Vue 代码,两边通过 IPC 通信,互不越界。

gpu-expertool/
  electron/
    main.js        主进程:窗口管理与数据推送
    preload.js     上下文桥:暴露安全 API 给页面
    gpu-sensor.js  硬件数据采集封装
  src/
    App.vue
    stores/gpu.js  Pinia 数据仓库
    components/
      GpuStatusCard.vue
      FanCurveEditor.vue
      ClockPanel.vue
  vite.config.js
  package.json

package.json 里有几个关键点。main 字段必须指向主进程入口,开发脚本要用 concurrently 同时拉起 Vite 开发服务器和 Electron,再用 wait-on 等端口就绪后才启动桌面进程,否则 Electron 会加载一个还没准备好的空白页。

{
  "name": "gpu-expertool",
  "version": "0.1.0",
  "main": "electron/main.js",
  "scripts": {
    "dev": "concurrently \"npm:dev:vite\" \"npm:dev:electron\"",
    "dev:vite": "vite",
    "dev:electron": "wait-on tcp:5173 && cross-env VITE_DEV_SERVER_URL=http://127.0.0.1:5173 electron .",
    "build": "vite build && electron-builder"
  },
  "devDependencies": {
    "vue": "^3.4.0",
    "pinia": "^2.1.0",
    "vite": "^5.0.0",
    "electron": "^28.0.0",
    "electron-builder": "^24.0.0",
    "concurrently": "^8.0.0",
    "wait-on": "^7.0.0",
    "cross-env": "^7.0.0"
  }
}

主进程的写法是标准套路,重点在 webPreferences 的配置:contextIsolation 打开、nodeIntegration 关闭,这是 Electron 官方反复强调的安全基线,页面里拿不到 Node 能力,只能通过 preload 暴露的白名单接口干活。

const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const { startGpuPolling } = require('./gpu-sensor');

function createWindow() {
  const win = new BrowserWindow({
    width: 1180,
    height: 760,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false
    }
  });

  if (process.env.VITE_DEV_SERVER_URL) {
    win.loadURL(process.env.VITE_DEV_SERVER_URL);
  } else {
    win.loadFile(path.join(__dirname, '../dist/index.html'));
  }
}

// 页面发起订阅后,主进程按固定间隔推送显卡状态
ipcMain.on('gpu:subscribe', (event) => {
  startGpuPolling(1000, (status) => {
    event.sender.send('gpu:data', status);
  });
});

app.whenReady().then(createWindow);

preload 这一层是渲染进程和主进程之间唯一的大门,用 contextBridge 把订阅和设置风扇转速两个能力挂到 window 上,页面代码从此只认 gpuBridge 这个对象,完全感知不到 IPC 的存在。

const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('gpuBridge', {
  subscribe: (callback) => {
    ipcRenderer.on('gpu:data', (_event, status) => callback(status));
    ipcRenderer.send('gpu:subscribe');
  },
  setFanSpeed: (percent) => ipcRenderer.invoke('gpu:setFanSpeed', percent)
});

组件化拆分监控界面

回头观察 Expertool 的界面布局,可以很自然地切成几块:顶部是状态总览卡(型号、温度、功耗、显存占用),中间是实时曲线区域,右侧是风扇控制面板,底部是频率与电压调节。映射到 Vue 组件,就是一个根组件挂着 GpuStatusCard、FanCurveEditor、ClockPanel 三类子组件,各自独立、各自可测。

先写最简单的状态卡。用 <script setup> 语法,props 接收一个 status 对象,模板里直接渲染各项指标。注意温度超过 80 度时的告警高亮,用动态 class 一行就能搞定。

<template>
  <div class="status-card">
    <h3>{{ gpuName }}</h3>
    <p class="metric">
      核心温度
      <strong :class="{ danger: status.temp >= 80 }">{{ status.temp }}℃</strong>
    </p>
    <p class="metric">风扇转速 {{ status.fanSpeed }} RPM({{ status.fanPercent }}%)</p>
    <p class="metric">核心频率 {{ status.coreClock }} MHz / 显存 {{ status.memClock }} MHz</p>
    <p class="metric">显存占用 {{ status.memUsed }} / {{ status.memTotal }} GB</p>
  </div>
</template>

<script setup>
defineProps({
  gpuName: { type: String, default: '耕升 RTX 显卡' },
  status: { type: Object, required: true }
});
</script>

这里有个设计取舍值得说一句:组件只做展示,数据一律从 props 进来,组件内部不去订阅数据源。这样 GpuStatusCard 拿一份 mock 数据就能单独跑起来调试,不用先起 Electron 再等传感器出数,开发体验完全不是一个量级。真实数据的注入统一放在根组件里完成,单向数据流也保持了干净。

数据链路:从传感器流到 Pinia

数据从主进程推过来之后,需要一个落脚点。用 Pinia 建一个 gpu 仓库,除了保存当前状态,还要维护一份历史序列,因为温度曲线图需要过去几分钟的数据才能画出来。

import { defineStore } from 'pinia';

export const useGpuStore = defineStore('gpu', {
  state: () => ({
    status: {
      temp: 0, fanSpeed: 0, fanPercent: 0,
      coreClock: 0, memClock: 0,
      memUsed: 0, memTotal: 0, power: 0
    },
    history: []
  }),
  actions: {
    update(next) {
      this.status = next;
      this.history.push({ time: Date.now(), temp: next.temp });
      // 每秒一条,只保留最近十分钟
      if (this.history.length > 600) {
        this.history.shift();
      }
    }
  }
});

根组件在挂载时发起订阅,之后每一条推送都交给仓库处理,组件树里任何需要数据的地方直接从仓库取,订阅动作全局只发生一次。

import { onMounted } from 'vue';
import { useGpuStore } from './stores/gpu';

const gpuStore = useGpuStore();

onMounted(() => {
  window.gpuBridge.subscribe((status) => {
    gpuStore.update(status);
  });
});

剩下的问题是主进程怎么真正拿到显卡数据,这也是整个项目里最非前端的一环,现实中有三条路可走。第一条是 Windows 性能计数器,系统自带,能拿到 GPU 利用率和部分温度指标,适合起步阶段验证链路;第二条是基于 LibreHardwareMonitor 的思路,读底层寄存器拿全量传感器数据,社区有现成的 .NET 库,可以让主进程 spawn 一个小的采集进程通过标准输出回传 JSON;第三条是针对 N 卡的 NVAPI 原生绑定,能力最全,读写都支持,但需要自己编译原生模块。下面用性能计数器演示第一条路的写法:

const { execFile } = require('child_process');

// 通过 Windows 性能计数器读取 GPU 利用率,仅用于链路验证
function readGpuUsage() {
  return new Promise((resolve) => {
    execFile('typeperf', ['\\GPU Engine(*)\\Utilization Percentage', '-sc', '1'], (err, stdout) => {
      if (err) return resolve(0);
      const lines = stdout.split('\r\n');
      const values = lines[2] ? lines[2].split(',').slice(1) : [];
      const nums = values
        .map(v => parseFloat(v.replace(/"/g, '')))
        .filter(v => !isNaN(v));
      resolve(nums.length ? Math.max(...nums) : 0);
    });
  });
}

必须提醒一句,execFile 拉起外部命令这种方式延迟高、开销大,拿来做原型可以,正式版本请换成原生模块或者常驻采集进程,否则每秒一次的轮询会把 CPU 白白吃掉几个百分点,对一个硬件监控工具来说相当讽刺。

风扇曲线编辑器的实现

Expertool 最有辨识度的交互就是那条可拖拽的风扇曲线:横轴温度、纵轴转速百分比,玩家拖几个控制点就能让显卡在低温时安静、高负载时全力散热。这个交互用 SVG 实现非常顺手,polyline 画曲线,circle 画控制点,事件绑定都是现成的。

<template>
  <svg ref="svgEl" viewBox="0 0 400 240" class="fan-curve"
       @mousemove="onMove" @mouseup="endDrag" @mouseleave="endDrag">
    <polyline :points="curvePoints" fill="none" stroke="#4cc2ff" stroke-width="2" />
    <circle
      v-for="(p, i) in controlPoints"
      :key="i"
      :cx="p.x" :cy="p.y" r="6"
      class="ctrl-point"
      @mousedown="startDrag(i)"
    />
  </svg>
</template>

拖拽的核心是把鼠标的像素坐标换算回 SVG 的 viewBox 坐标,很多初学者在这里栽跟头:直接用 offset 坐标在视图被拉伸时会飘掉,正确做法是拿 getBoundingClientRect 算比例。

import { ref, computed } from 'vue';

const svgEl = ref(null);
const dragIndex = ref(-1);
const controlPoints = ref([
  { x: 40, y: 200 },   // 低温对应低转速
  { x: 160, y: 160 },
  { x: 280, y: 80 },
  { x: 380, y: 20 }    // 接近满载时接近全速
]);

const curvePoints = computed(() =>
  controlPoints.value.map(p => `${p.x},${p.y}`).join(' ')
);

function onMove(e) {
  if (dragIndex.value === -1) return;
  const rect = svgEl.value.getBoundingClientRect();
  const x = ((e.clientX - rect.left) / rect.width) * 400;
  const y = ((e.clientY - rect.top) / rect.height) * 240;
  const p = controlPoints.value[dragIndex.value];
  p.x = Math.min(400, Math.max(0, x));
  p.y = Math.min(240, Math.max(0, y));
}

function startDrag(i) { dragIndex.value = i; }
function endDrag() { dragIndex.value = -1; }

控制点确定之后,把坐标反算成温度到转速的映射表(比如 x 除以 4 得到温度、y 换算成百分比),调用 gpuBridge 的 setFanSpeed 或者整表提交给主进程,由主进程按温度区间应用转速。这里有两个工程细节别忽略:一是转速要设下限,比如 30%,否则曲线拉太低显卡风扇停转,热量堆积反而危险;二是首尾两个控制点建议锁定,只开放中间点拖拽,避免用户把曲线改成非单调的奇怪形状。

打包成 Windows 安装包

功能写完之后就是分发环节。electron-builder 是目前的事实标准,配置放在 package.json 的 build 字段或者单独的 electron-builder.yml 里,Windows 平台选 nsis 作为目标,产出常规的下一步式安装向导。

{
  "appId": "com.ipipp.gputool",
  "productName": "GpuExpertool",
  "directories": { "output": "release" },
  "win": {
    "target": "nsis",
    "icon": "build/icon.ico"
  },
  "nsis": {
    "oneClick": false,
    "allowToChangeInstallationDirectory": true
  },
  "asar": true
}

执行 npm run build,Vite 先把 Vue 项目产出到 dist,electron-builder 再把 dist 连同主进程代码一起塞进 asar 包并生成安装程序。图标记得准备 ico 格式且包含 256 尺寸,否则安装后任务栏图标会糊。

最后聊几个容易踩的坑。第一,硬件工具建议做代码签名,没有签名的安装包会被 Windows SmartScreen 拦一道,普通用户看到警告基本就直接放弃了;第二,如果后续接了原生模块,记得在 electron-builder 里配置重新编译,原生模块必须和 Electron 自带的 Node 版本匹配,否则启动直接报错;第三,读数据容易写数据难,超频相关的写入操作务必走带校验的原生接口,并且给用户留好一键恢复默认的退路,这也是 Expertool 多年来坚持的设计原则。整套架构跑通之后你会发现,所谓硬件工具,前端承担的那一半工作量和普通后台管理系统差不多,真正深的功夫都在主进程那一侧。

Vue 3 工程化Gainward Expertool显卡监控工具修改时间:2026-10-03 19:27:09

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/1003/65230.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。