每天早上打开电脑,我大概有十分钟是处在一种“打仗前的混乱”里:GitHub 上有几个 PR 等着 review,CI 跑没跑完得刷一下,服务器负载高不高要 SSH 上去看一眼,日历里十点有个会,再顺手查一下天气决定要不要早点出门。这些信息分散在七八个页面和应用里,每看一个都像切换一次语境。去年年底我实在受够了,决定自己动手做一个只属于开发者的桌面仪表盘,名字就叫 Status Deck。这是一个全栈自造项目,从数据采集、聚合服务、桌面壳到前端展示,全部自己写。这一篇先讲清楚整体设计和技术选型,并给出第一版 MVP 的完整骨架,适合想用业余时间做点全栈作品的开发者参考。
1. 先从“为什么”说起:我缺的不是监控,是工作台
1.1 现成面板们都很强,但都不在点子上
在决定自造之前,我把市面上的“开发者仪表盘”方案都过了一遍,结论很有意思:它们各自解决了一个真实问题,但没有任何一个解决的是“开发者每天早上快速进入状态”这个问题。
| 方案 | 擅长解决的问题 | 潜在短板 |
|---|---|---|
| Grafana + Prometheus | 基础设施指标可视化、告警聚合 | 面向运维体系,配置复杂,搭建成本高 |
| Cockpit | 服务器管理、容器和日志查看 | 更偏单机运维,不适合多数据源工作流 |
| Uptime Kuma 等状态页 | 监控网站和 API 的可用性 | 信息维度单一,只能回答“挂没挂” |
| 自己写的 Shell 脚本 | 快速看 CPU、内存、磁盘 | 输出在终端里,没法做卡片化、可视化 |
| 浏览器收藏夹 / 书签 | 快速跳转到各平台 | 没有任何信息聚合,看完一个再看一个 |
Grafana 的强项是“指标监控”,它能画出漂亮的 CPU 曲线、网络流量图,但它的交互核心是图表面板和告警规则,不是“我今天有哪些 PR 要处理、CI 卡在哪一步”。真让我每天开着 Grafana 当作工作台,我大概率会迷失在一堆 Graph 里。Shell 脚本倒是够轻,可信息形态太原始,CPU 数字看一次就看完了,昨天和今天的对比、内存里有哪几个大进程占了坑,脚本根本说不清。
我真正缺的是一个“信息统一入口”:系统负载、代码仓库动态、CI 结果、待办事项,以一种低打扰、高密度、扫一眼就懂的方式出现在同一块屏幕上。这个需求落到产品形态上,就是标题里的核心词——Status Deck:一个给开发者的桌面仪表盘。
1.2 Status Deck 要做什么、不做什么
第一版的项目边界我卡得很死,因为我知道“仪表盘”这个词特别容易膨胀。一旦想加的东西太多,项目就会死在 Demo 阶段。
第一版目标功能清单:
- 系统资源预览:CPU 使用率、内存占用、磁盘剩余,为本地机器和常用开发机各做一组。
- 开发工作流:GitHub 仓库的待处理 PR、CI 状态、最近提交记录。
- 日程与待办:读取本地日历文件或 TODO 清单,以时间线形式排列。
- 自定义脚本卡片:任何能够输出 JSON 的一次性命令,都能被注册成一张卡片。
明确不做的事也有:不尝试替代 Grafana 做全量数据可视化,不做账号体系和多人协作,不搞复杂编辑器,所有配置以 JSON/YAML 文件为主。这四条不做的边界帮我留住了大量精力,让我能在两周的业余时间里把第一版跑起来。
1.3 卡片机制的雏形
从第一版开始,我就认准了一个抽象:数据源 + 卡片。每个数据源由聚合服务负责采集,每张卡片只管“收到数据后怎么展示”,两者的契约是一份 JSON。这个抽象几乎不花成本,却决定了整个项目后续的扩展方式——想要新功能,加一个采集器和一个卡片组件,其它代码一行都不用动。这是整个 Status Deck 里我认为最值钱的设计决策。
2. 全栈架构:三层解耦,数据只流一次
2.1 采集层、聚合层、展示层各司其职
Status Deck 的架构被拆成三个明确分层,每一层只解决一件事:
| 分层 | 职责 | 技术形态 |
|---|---|---|
| 采集层 | 从系统、API、自定义脚本获取原始数据 | Node.js 定时任务、shell 脚本、HTTP 请求 |
| 聚合层 | 统一调度采集任务、缓存快照、广播数据 | 本地 WebSocket 服务 |
| 展示层 | 订阅数据流,渲染卡片,处理窗口交互 | Tauri 壳 + React 前端 |
分层最大的好处是:数据只流一次。前端不会出现“N 个卡片各拉各的 API”这种失控局面。GitHub 数据如果三张卡片都需要,从前端视角只需要订阅一份,聚合服务负责把同一份快照广播给所有订阅者。这样做的另一个现实收益是 API 配额控制——GitHub 的未认证请求限制很严格,只有聚合服务一个出口,限速配置就能集中管理。
2.2 刷新策略:轮询与广播结合,前端只维护一条连接
这里有一个很关键的取舍:为什么不用前端每个组件 setInterval 自己刷新?
如果 SystemCard 每 3 秒轮询一次本地系统指标,GitHubCard 每 30 秒轮询一次 GitHub,这两个数看起来都不大,但一旦卡片数量涨到 10 张,前端就会有 10 个独立定时器,窗口切后台、Tab 休眠、网络抖动时,状态管理会变得极其混乱。
Status Deck 的做法正好反过来:所有采集节奏由聚合服务统一调度,前端只维护一条 WebSocket 连接。采集层各自按预设周期执行,聚合层把结果合并进“快照对象”,再通过 WebSocket 广播给所有连接中的前端。前端拿到快照后更新全局状态,卡片组件订阅所需字段重新渲染。整套链路下来,前端代码里几乎不需要出现“定时器”三个字,刷新策略的调整全部集中在聚合层的配置文件里。
2.3 配置驱动的卡片注册
配置体系是这套架构里连接“后端能力”和“前端展示”的桥梁。我在项目根目录放了一份config.json,里面写了每张卡片的启用状态和刷新周期:
{ "serverPort": 3801, "refresh": { "system": 5000, "github": 30000, "todo": 60000 }, "github": { "owner": "yourname", "repo": "yourrepo", "token": "" }, "cards": { "system": true, "github": true, "todo": false } }看到这个文件的读者应该已经能猜到它的逻辑:卡片在配置里声明一次,聚合层就按对应周期采集,前端就挂载对应组件;把todo改成false,整条链路直接关停。这个设计让 Status Deck 的“增删改查”变成了改文件,而不是改代码。
3. 技术选型:为什么桌面壳不用 Electron
3.1 Tauri 与 Electron 的取舍
桌面壳的选择是我在技术选型阶段纠结最久的一件事。过去几个项目里我一直在用 Electron,打包体积随便就是 80MB 起步,内存占用动辄几百 MB,对于一个“常驻桌面、专注信息聚合”的工具类应用来说,这个资源开销不太划算。
我把 Tauri 和 Electron 做了个直接对比:
| 维度 | Tauri | Electron |
|---|---|---|
| 安装包体积 | 通常 < 10MB | 通常 > 80MB |
| 内存占用 | 数十 MB 级别 | 数百 MB 级别 |
| 后端语言 | Rust(壳层) | Node.js |
| 前端兼容性 | 依赖系统 WebView | 内置 Chromium,兼容性最稳 |
| 生态成熟度 | 快速成长,社区比 Electron 年轻 | 非常成熟,资料多 |
| 适合场景 | 小体积常驻工具、系统托盘应用 | 复杂桌面应用、已有 Node 生态 |
结论很清晰:Status Deck 是一个以“常驻桌面”为核心场景的产品,资源占用直接关系到用户是否愿意让它一直开着,Tauri 明显更合适。但我也要提醒一句:如果团队已经有成熟的 Electron 基建和调试工具,就没有必要为了“新版跑得快”硬换成 Tauri。个人新项目从零开始,Tauri 的性价比更高。
3.2 聚合服务:Node.js 而不是把逻辑塞进 Rust
有人可能会问:Tauri 的后端本身是 Rust,为什么不直接在 Rust 里做采集和聚合,省去 Node.js 这一层?
原因很实际。第一版要快速验证数据流,Node.js 生态里既有systeminformation这种系统指标库,也有对 GitHub、Slack、日历等 Web 服务友好的 HTTP 客户端;写一个异步定时采集器,十分钟就能跑起来。Rust 虽然性能和稳定性更好,但开发节奏明显更慢,对 MVP 阶段来说不划算。我的方案是:Tauri 壳只负责窗口和系统托盘,聚合服务以独立进程运行,两边通过 WebSocket 通信。数据契约是标准 JSON,将来就算把采集层逐步迁到 Rust,前端和通信协议都不用动。
如果你更熟悉的是 Python、Java,这一层完全可以替换成自己的偏好技术栈。架构的关键不在具体语言,而在于“聚合服务与展示层通过统一协议解耦”这件事。Status Deck 的“全栈”价值,恰恰就在于它不绑定任何单一语言。
3.3 前端采用 React + Vite + Tailwind 的考量
前端我选了 React 18 + Vite + Tailwind CSS,理由干脆:
- React 的组件模型契合卡片机制:一张卡片就是一个组件,数据进 props、展示由内部状态控制,天然适合“数据源驱动 UI”。
- Vite 启动快:开发时改一行代码热更新基本毫秒级,对频繁调试卡片布局的体验非常友好。
- Tailwind 写样式效率高:卡片布局不追求复杂设计,重点是信息密度和间距节奏,Tailwind 的原子类能让我不用频繁切换 CSS 文件。
值得说明的是,这套前端并不依赖 Tauri 的任何特殊能力,它只是一个标准 Web 应用。因此你可以把 React 换成 Vue、Solid,甚至换成纯 HTML 加 Alpine.js,通信逻辑完全不变。给展示层和后端之间留一条干净的 WebSocket 协议,是整个项目最正确的架构决策之一。
3.4 这个全栈项目对学习路线的价值
从全栈视角看,Status Deck 是那种“麻雀虽小、五脏俱全”的练手项目。它同时逼着你处理三类问题:
- 后端侧:任务调度、API 封装、限速与失败重试、跨域设置。
- 前端侧:全局状态管理、组件拆分、实时数据的渲染性能、断线重建 UI 状态。
- 桌面侧:窗口生命周期、系统托盘、置顶与焦点、开机自启。
对正在给自己规划全栈学习路线的人来说,这个项目比重复写 CRUD 管理系统有价值得多——它让你在同一个项目里同时体会四项技术域的复杂度,并且每一项都不算深,刚好处于“入门到进阶”的甜点区。
4. 第一版代码骨架:从零到能跑
这一节给出的代码可以直接照着搭建,整体结构是:server/是聚合服务,src/是 React 前端,src-tauri/是 Tauri 壳。
4.1 初始化目录与依赖
# 创建前端项目 npm create vite@latest status-deck -- --template react-ts cd status-deck # 安装聚合服务依赖 npm install ws systeminformation npm install -D @types/ws # 初始化 Tauri npm install -D @tauri-apps/cli npm run tauri init目录结构建议保持如下风格:
status-deck/ ├── src/ # React 前端 │ ├── cards/ │ ├── store.ts │ └── App.tsx ├── server/ # 聚合服务 │ ├── collectors/ │ │ ├── system.ts │ │ └── github.ts │ └── index.ts ├── src-tauri/ # Tauri 桌面壳 │ └── tauri.conf.json └── config.json # 全局配置4.2 采集器:系统指标与 GitHub 数据
聚合服务的核心是两个采集器。第一个是系统信息采集器:
// server/collectors/system.ts import si from 'systeminformation'; export async function collectSystem() { const [load, mem, disk] = await Promise.all([ si.currentLoad(), si.mem(), si.fsSize(), ]); return { cpu: Math.round(load.currentLoad * 10) / 10, memUsedPercent: Math.round((mem.used / mem.total) * 1000) / 10, diskUsedPercent: disk[0]?.use ?? 0, uptime: Math.floor(process.uptime() / 60), }; }这里有个细节值得讲。os.cpus()拿到的是 CPU 时间的累积值(user、nice、sys、idle 等),要算“当前使用率”必须间隔两次采样再做差分,第一次采样的数据还得丢掉,稍不留神就会算出负值或跳变。systeminformation的currentLoad()把这些底层逻辑都封装好了,直接拿currentLoad字段就是当前使用率。对 MVP 来说,少踩一个坑比“只依赖标准库”的洁癖重要得多。
第二个是 GitHub 采集器。我用原生fetch而不是引入@octokit/rest,这样至少能少一层依赖:
// server/collectors/github.ts export async function collectGitHub(token: string, owner: string, repo: string) { const headers: Record<string, string> = { 'X-GitHub-Api-Version': '2022-11-28', 'Accept': 'application/vnd.github+json', }; if (token) headers.Authorization = `Bearer ${token}`; const url = `https://api.github.com/repos/${owner}/${repo}/pulls?state=open&per_page=5`; const res = await fetch(url, { headers }); if (!res.ok) { throw new Error(`GitHub API ${res.status}`); } const pulls = await res.json(); return pulls.map((pr: any) => ({ number: pr.number, title: pr.title, author: pr.user?.login ?? 'unknown', draft: pr.draft ?? false, updatedAt: pr.updated_at, })); }接入 GitHub 时有个必须提前知道的限制:未认证请求的速率上限是每个 IP 每小时 60 次,认证后是 5000 次。如果 Status Deck 每 30 秒轮询一次,一小时就是 120 次请求,不带 Token 几乎必然触发限流。所以哪怕只是自己本地用,也建议在 GitHub 申请一个 fine-grained personal access token,写入config.json的github.token字段。
4.3 聚合服务:定时采集与 WebSocket 广播
聚合服务是整套系统的调度中心。它维护一个快照对象,定时执行采集任务,然后通过 WebSocket 把快照广播出去:
// server/index.ts import { WebSocketServer } from 'ws'; import { collectSystem } from './collectors/system'; import { collectGitHub } from './collectors/github'; import config from '../config.json'; const wss = new WebSocketServer({ port: config.serverPort }); // 全局快照:任何新连接进来都能立即拿到最新数据 let snapshot: Record<string, unknown> = {}; async function tick() { const [system, github] = await Promise.allSettled([ collectSystem(), collectGitHub(config.github.token, config.github.owner, config.github.repo), ]); snapshot = { system: system.status === 'fulfilled' ? system.value : null, github: github.status === 'fulfilled' ? github.value : null, ts: Date.now(), }; broadcast(JSON.stringify({ type: 'snapshot', ...snapshot })); } function broadcast(message: string) { wss.clients.forEach((client) => { if (client.readyState === 1) client.send(message); }); } wss.on('connection', (ws) => { // 新连接先补发一份快照,避免卡半天空白 ws.send(JSON.stringify({ type: 'snapshot', ...snapshot })); }); const systemInterval = setInterval(tick, config.refresh.system); const githubInterval = setInterval(tick, config.refresh.github); tick();这里用了Promise.allSettled而不是Promise.all,是一个从踩坑里学到的细节:如果 GitHub API 限流或网络抖动导致某个采集器失败,Promise.all会让整个 tick 崩溃,连带系统指标也更新不了。改成allSettled后,单个采集器的失败只会让对应卡片显示“暂无数据”,其它卡片不受影响。很多分布式状态页看起来“稍微有点糙但一直能跑”,关键就在这种容错策略上。
采集周期我刻意做成两个独立定时器:系统指标 5 秒一刷,GitHub 数据 30 秒一刷。如果只用一个总定时器,所有任务都按最慢周期执行,系统指标就会白白变迟钝;都按最快周期执行,GitHub 配额又会很快烧完。分开调度是这里最合理的默认值。
4.4 前端卡片:WebSocket 订阅与渲染
前端用 zustand 管理全局状态。zustand比 Redux 省事太多,对“全局存一个快照对象”这种场景几乎是零样板代码:
// src/store.ts import { create } from 'zustand'; interface StatusState { snapshot: any; connected: boolean; setSnapshot: (data: any) => void; setConnected: (flag: boolean) => void; } export const useStatusStore = create<StatusState>((set) => ({ snapshot: null, connected: false, setSnapshot: (snapshot) => set({ snapshot }), setConnected: (connected) => set({ connected }), }));全局连接逻辑放在App.tsx里,组件挂载时建立 WebSocket,收到消息后直接写入 store:
// src/App.tsx import { useEffect } from 'react'; import { useStatusStore } from './store'; import SystemCard from './cards/SystemCard'; import GitHubCard from './cards/GitHubCard'; const WS_URL = 'ws://localhost:3801'; export default function App() { const setSnapshot = useStatusStore((s) => s.setSnapshot); const setConnected = useStatusStore((s) => s.setConnected); useEffect(() => { const ws = new WebSocket(WS_URL); ws.onopen = () => setConnected(true); ws.onmessage = (event) => { const msg = JSON.parse(event.data); if (msg.type === 'snapshot') setSnapshot(msg); }; ws.onclose = () => setConnected(false); ws.onerror = () => ws.close(); return () => ws.close(); }, [setSnapshot, setConnected]); return ( <div className="grid grid-cols-1 md:grid-cols-2 gap-4 p-4"> <SystemCard /> <GitHubCard /> </div> ); }系统卡片组件读 store 中的snapshot.system即可:
// src/cards/SystemCard.tsx import { useStatusStore } from '../store'; export default function SystemCard() { const snapshot = useStatusStore((s) => s.snapshot); if (!snapshot?.system) { return <div className="card">系统数据加载中...</div>; } const { cpu, memUsedPercent, diskUsedPercent } = snapshot.system; return ( <div className="card p-4 rounded-lg bg-neutral-900 text-white"> <h2 className="text-sm font-semibold text-neutral-400">系统负载</h2> <div className="mt-2 space-y-2"> <div> <span>CPU {cpu}%</span> <div className="h-2 bg-neutral-700 rounded"> <div className="h-2 bg-emerald-400 rounded" style={{ width: `${cpu}%` }} /> </div> </div> <div> <span>内存 {memUsedPercent}%</span> <div className="h-2 bg-neutral-700 rounded"> <div className="h-2 bg-sky-400 rounded" style={{ width: `${memUsedPercent}%` }} /> </div> </div> <div> <span>磁盘 {diskUsedPercent}%</span> <div className="h-2 bg-neutral-700 rounded"> <div className="h-2 bg-amber-400 rounded" style={{ width: `${diskUsedPercent}%` }} /> </div> </div> </div> </div> ); }整体逻辑非常“笨”——不计算、不加工、不二次请求,只做映射和渲染。这也是 Status Deck 刻意保持的简单性:复杂逻辑只允许出现在聚合层,前端卡片只负责把数据翻译成视觉信息。这样排错路径最短,出问题时不是猜前端 bug,而是直接看聚合服务日志。
4.5 最小配置文件示例
完整的最小配置文件config.json:
{ "serverPort": 3801, "refresh": { "system": 5000, "github": 30000 }, "github": { "owner": "yourname", "repo": "yourrepo", "token": "" }, "cards": { "system": true, "github": true } }配置化的意图是让这个项目对“非作者本人”也可用。读者拿到项目后不需要改任何代码,只改这份 JSON 就能挂上自己的仓库。配置文件里写的每一项,背后都对应聚合层的一个调度参数或采集器参数,没有多余的装饰字段。
5. 实操记录:本地跑通第一版
5.1 创建 Tauri 壳与配置文件
npm run tauri init之后,需要改src-tauri/tauri.conf.json里的两个核心字段,否则窗口加载不到前端页面:
{ "build": { "beforeDevCommand": "npm run dev", "devPath": "http://localhost:5173", "beforeBuildCommand": "npm run build", "distDir": "../dist" }, "app": { "windows": [ { "title": "Status Deck", "width": 1000, "height": 680, "resizable": true, "alwaysOnTop": false } ] } }这里devPath指向 Vite 开发服务器地址。如果不设置,Tauri 默认会去找distDir下的静态文件,开发阶段 Vite 的热更新就完全失效。这个字段是我第一版跑起来时最容易被忽略的地方。
5.2 启动开发链路
开发时需要两个终端:
# 终端 1:启动聚合服务 npx tsx server/index.ts # 终端 2:启动 Tauri 开发窗口 npm run tauri devnpx tsx可以直接运行 TypeScript 文件,省去先编译再运行的麻烦。聚合服务跑起来后,终端会停在监听状态,WebSocket 端口是 3801。Tauri 窗口打开后,前端页面会自动连接ws://localhost:3801,两到三秒内系统卡片就会出现第一条数据。
5.3 用真实数据源验证面板
启动完成后别急着开始新功能,先做一轮“冷启动验证”,重点确认三件事:
| 验证项 | 预期结果 | 排查方向 |
|---|---|---|
| 系统指标 5 秒内刷新 | CPU/内存/磁盘数字随负载变化 | 检查聚合服务日志、端口占用 |
| GitHub PR 列表加载 | 显示 5 条以内开放 PR | 检查 Token 是否配置、owner/repo 是否正确 |
| 杀掉聚合服务再重启 | 模块自动重连、数据恢复 | 检查前端 WebSocket 重连逻辑 |
这一轮验证的意义在于确认“数据链路是通的”。如果这一步跑通,后续加任何新卡片都只是在走重复路径:写采集器、加配置、写组件。真正困难的部分已经在这个小小的链路验证里全部暴露过了。
6. 排错实录:这些坑值得提前知道
6.1 系统指标数值跳变或显示为 0
第一版刚跑起来时,CPU 使用率偶尔会突然跳到 100%,然后下一秒回落到 20%。后来发现原因有两个:一是某些虚拟化环境下currentLoad()的第一次采样本来就不可靠,二是 3 秒以内的采样周期太短,单个进程的瞬时毛刺会被直接放大。
对策很简单:把系统指标刷新周期设为 5 秒以上,并在聚合层做一个简单的指数平滑。参考实现:
let smoothedCpu: number | null = null; export function smoothCpu(current: number, alpha = 0.3) { if (smoothedCpu === null) smoothedCpu = current; else smoothedCpu = alpha * current + (1 - alpha) * smoothedCpu; return Math.round(smoothedCpu * 10) / 10; }alpha越小,曲线越平滑,但响应越慢。0.3 是我试下来适合桌面工具的平衡点。
6.2 WebSocket 断线后前端变成“僵尸”
如果用户把电脑合盖休眠再唤醒,或者重启了聚合服务,前端 WebSocket 默认是不会自动重连的,页面会一直显示“暂无数据”。
处理方式是在App.tsx里加一个带退避的重连逻辑:
function connectWithRetry(attempt = 0) { const delay = Math.min(30000, Math.pow(2, attempt) * 1000); const ws = new WebSocket(WS_URL); ws.onopen = () => { setConnected(true); attempt = 0; }; ws.onclose = () => { setConnected(false); setTimeout(() => connectWithRetry(attempt + 1), delay); }; }指数退避的意思是:第一次失败后等 1 秒,第二次 2 秒,第三次 4 秒,最多封顶 30 秒。这样做既能快速恢复临时断线,又不会在服务端持续不可用时造成高频无效请求。
6.3 开发模式白屏
开发模式白屏最常见的两个原因,都出在“地址”上。第一,Vite 端口被占,devPath里写的 5173 已经不是当前前端服务的真实端口;第二,跨域问题——前端地址和 WebSocket 服务地址不在同一个源,浏览器拒绝了连接。
第一个问题用lsof -i :5173查端口占用,或者让 Vite 显式指定一个不冲突的端口:
npm run dev -- --port 5173 --strictPort第二个问题更简单,聚合服务端加一个显式 CORS 头即可:
res.setHeader('Access-Control-Allow-Origin', '*');需要提醒的是,CORS 只影响浏览器里的页面。如果是 Tauri 窗口加载的前端,devPath指向的地址和 WebSocket 的localhost跨域限制没有浏览器那么严格,但这个隐患在纯浏览器调试时会跳出来,最好从第一天就加上。
6.4 托盘、置顶与窗口细节
桌面工具和普通网页有一个很大的区别:用户期望它“在系统里像原生应用一样工作”。我第一版里踩过的坑包括:
- 窗口设了
alwaysOnTop: true后,写代码时它一直压在编辑器上面,非常碍事。后来改成默认不置顶,只有点按钮临时置顶 10 秒。 skipTaskbar如果设成 true,又没有托盘图标,窗口最小化后会出现“应用彻底消失”的错觉,只能靠任务管理器找回进程。桌面应用的托盘和窗口状态必须成对配置。- 聚合服务如果被手动关闭,托盘图标还好好的,点开会发现所有卡片都显示“暂无数据”。在托盘菜单里加入“重启聚合服务”或“查看日志”,能省下非常多无谓的排查时间。
这些坑不在技术难度,而在“桌面体验细节”。很多个人项目做到功能能跑就停了,正式使用之后才一个个暴露出来。提前把托盘、焦点、自启这些桌面元素纳入第一版范围,会让工具的完成度高一个级别。
6.5 排错速查表
| 现象 | 最可能原因 | 快速处理 |
|---|---|---|
| CPU 数值乱跳 | 采样周期过短、首次采样不可靠 | 周期调到 5s 以上,加指数平滑 |
| GitHub 卡片一直无数据 | Token 配额耗尽或未配置 | 配置 fine-grained token,降刷新频率 |
| WebSocket 连不上 | 聚合服务未启动 / 端口被占 | 检查 3801 端口,确认启动顺序 |
| 前端白屏 | devPath 与 Vite 端口不一致 | 固定端口并同步 devPath |
| 窗口最小化后找不到 | skipTaskbar 与托盘未成对配置 | 增加托盘图标,保持 taskbar 可见 |
最后分享一点我自己的体会
第一版跑通的那个晚上,我把 Tauri 窗口调到屏幕右侧,宽度只有 360 像素,然后正常打开编辑器开始写代码。五分钟之后我下意识偏头扫了一眼 Status Deck:CPU 45%,GitHub PR 只有一条待处理,CI 还在跑,那两条信息过去要切两个页面,现在一眼就够。那一刻我确认这个项目值得做下去。Status Deck 的核心价值不在于代码库本身多华丽,而在于它把开发者的“信息入口”从几十个 Tab 压缩成了一块安静的小面板,几乎不打扰你,但该知道的事一件都不会漏。下一篇我计划拆卡片系统与插件机制,把自定义脚本接入的完整链路展开讲。如果你也在搭自己的桌面工作台,建议先从这一篇的 MVP 入手,哪怕只加载两个数据源,也比继续刷二十个页面强。