Day 77 · BOSS 战 — 屏 B Vue 版整屏迁移与"原生 vs Vue"对比报告
本周收官。今天把 Day 74-76 的零件装配成完整的屏 B(实时监控页)Vue 版:五卡片布局 + 五图表模块 + 容错表现,与原生版行为逐项对齐。然后做本周最有价值的事:整理"原生 vs Vue 对照表"与性能对比——这张表是博客《原生 vs Vue 开发大屏的体会》(Week 14 写)的骨架,也是面试聊框架选型的全部弹药。
目录
- 一、屏 B Vue 版:装配全清单
- 二、ScreenB.vue 的完整结构
- 三、行为对齐验收(对照原生版)
- 四、性能对比:原生 vs Vue
- 五、原生 vs Vue 对照表(本周笔记精华)
- 六、常见坑点
- 七、自测挑战
- 八、总结
一、屏 B Vue 版:装配全清单
| 模块 | 原生版出处 | Vue 版实现 | 组件/函数 |
|---|---|---|---|
| 卡片容器 | Day 66 createTechCard | TechCard | Day 74 |
| 温度曲线 | Day 56/69 LineChart | TempLineChart | Day 76 |
| 仪表盘组 | Day 53 Gauge | GaugeRow(VChart 包装) | Day 76 模式 |
| KPI 卡 | Day 56 KpiCard + Day 67 翻牌 | KpiTile(useRollingNumber) | Day 73 |
| 能耗排名 | Day 54 RankChart | RankChart(VChart 包装) | Day 76 模式 |
| 健康雷达 | Day 54 RadarChart | RadarChart(VChart 包装) | Day 76 模式 |
| 数据管道 | Day 65 Store + activate 协议 | useScreenStore(常驻订阅) | Day 75 |
| 布局 | Day 56 grid CSS | 同一份 CSS 原样迁移 | — |
新写的只有"翻译层":把 Store 数据转成 option 的 computed——这正是组件化的兑换率证明。
二、ScreenB.vue 的完整结构
<!-- src/screens/ScreenB.vue -->
<script setup lang="ts">
/**
* 屏 B:实时监控(Vue 版)
* 对照原生版 createMonitorScreen(Day 69):
* - 布局 DOM 手艺 → 模板 + 全局 grid 类
* - activate/deactivate 协议 → 常驻订阅 + 分频(Day 75 简化决策)
* - resize 手动调用 → VChart 内部 ResizeObserver
*/
import TechCard from "../components/TechCard.vue";
import TempLineChart from "../components/TempLineChart.vue";
import GaugeRow from "../components/GaugeRow.vue";
import KpiTile from "../components/KpiTile.vue";
import RankChart from "../components/RankChart.vue";
import RadarChart from "../components/RadarChart.vue";
import { useScreenStore } from "../composables/useScreenStore";
// 数据管道:一行拿到全局响应式状态(订阅/退订/分频全部内聚在钩子里)
const { latest, tempWindows, connState } = useScreenStore();
</script>
<template>
<section class="screen screen-b">
<!-- 五卡片布局:Day 56 的 grid 原样迁移(全局类,非 scoped) -->
<div class="monitor-grid">
<div class="monitor__kpi">
<TechCard title="关键指标">
<div class="kpi-row">
<!-- KPI 翻牌:useRollingNumber 内聚在 KpiTile 里 -->
<KpiTile label="今日产量" :value="latest?.kpi.output ?? 0" />
<KpiTile label="平均温度" :value="latest?.kpi.avgTemp ?? 0" :decimals="1" />
<KpiTile label="告警数" :value="latest?.kpi.alarmCount ?? 0" />
</div>
</TechCard>
</div>
<div class="monitor__line">
<TechCard title="实时温度监控">
<template #extra>
<!-- 具名插槽:连接状态徽标(断线可见) -->
<span class="badge" :data-state="connState">
{{ connState === "connected" ? "实时" : "离线" }}
</span>
</template>
<!-- 空状态:断线且无数据时的占位(VChart 的 empty 位) -->
<TempLineChart
:windows="tempWindows"
:device-ids="['f1', 'f2']"
:names="['1号炉', '2号炉']"
:threshold="92"
:empty="connState === 'closed' && !latest"
/>
</TechCard>
</div>
<div class="monitor__rank">
<TechCard title="车间能耗排名"><RankChart :snapshot="latest" /></TechCard>
</div>
<div class="monitor__radar">
<TechCard title="设备健康度评估"><RadarChart :snapshot="latest" /></TechCard>
</div>
<div class="monitor__gauge">
<TechCard title="关键设备仪表"><GaugeRow :snapshot="latest" /></TechCard>
</div>
</div>
</section>
</template>
读这份模板的正确姿势:结构即文档——五卡片、每张卡的内容、数据来源,在模板里一眼可见。原生版要读 90 行 createMonitorScreen 才能拼出的画面,这里 40 行模板自解释。
三、行为对齐验收(对照原生版)
两个版本同时开(原生版 5174、Vue 版 5173,吃同一个数据服务器),逐项对照:
□ 数据一致性:两版曲线最新点相同、KPI 数值相同(同源同帧)
□ 分频表现:曲线持续推进、KPI 一秒一跳、仪表平滑摆动
□ 容错:杀服务器 → 徽标变"离线" + 曲线冻结;重启 → 两版同帧恢复
□ 视觉:并排截图逐像素对比(token 纪律的最终验收)
□ 动画:KPI 翻牌滚动节奏与原生版一致(同 easeOutCubic 800ms)
□ 性能:两版各跑 10 分钟 Performance 录制(第四节填表)
⚠️ 发现不一致时的排查顺序:先疑自己(翻译层的 computed 写错),再疑桥接(Day 75 镜像拷贝),最后才疑框架。今天修的每个不一致,都是对照表的一行素材。
四、性能对比:原生 vs Vue
| 指标 | 原生版(Day 69 实测) | Vue 版(今日实测) | 结论 |
|---|---|---|---|
| 屏 B 稳态 fps(10 分钟) | ___ | ___ | |
| 轮播整圈 fps | ___ | ___ | |
| JS Heap(30 分钟) | ___ | ___ | |
| 屏 B 相关代码行数 | ___ | ___ | |
| 首次可交互时间 | ___ | ___ |
预期与解读:
- fps 应持平(≥55):Vue 的响应式开销被"shallowRef + 引用替换"纪律压平——如果 Vue 版掉帧,先查有没有违反纪律(deep watch / reactive 大对象)
- Heap 略高是正常的:Vue 运行时本身有底噪(约 100-200KB 级),关键是趋势稳定而非绝对值
- 代码行数是最大赢面:翻译层模式让"每加一个图表"的边际成本从"一套 DOM 手艺"降到"一个 computed"
五、原生 vs Vue 对照表(本周笔记精华)
把 Day 71 起维护的 NOTES.md 汇总成表(≥12 行,Week 14 博客的骨架):
| 场景 | 原生版写法 | Vue 版写法 | 兑换率/代价 |
|---|---|---|---|
| 缩放壳 | addEventListener + 改 style | ref + :style 绑定 | 3 行换 1 行 |
| 状态灯 | 回调改 classList | :data-state 映射 | 免除手动同步 |
| 三屏互斥 | ScreenRouter 类 | v-show + current ref | 路由逻辑暂留类(W12) |
| KPI 翻牌 | 工厂 + 手动 update/cancel | useRollingNumber + watch | 清理由框架兜底 |
| 卡片 | createTechCard + append | TechCard 标签 + slot | 声明式自解释 |
| 告警列表 | 手动 DOM 增删 + 截断 | v-for + computed 截断 | diff 交给框架 |
| 曲线更新 | update(windows) 手动调 | computed option → VChart | watch 自动联动 |
| 图表 resize | 手动 resizeAll | ResizeObserver 内聚 | 永不遗漏 |
| 图表销毁 | 手动 dispose 纪律 | onUnmounted 结构保证 | 纪律变结构 |
| 数据订阅 | activate/deactivate 成对 | setup/unmount 成对 | 同上 |
| 排序/过滤 | 手动维护中间变量 | computed 链惰性缓存 | 免除手动维护 |
| 调试 | 断点跟 DOM 操作 | Vue Devtools 状态树 | 心智负担转移 |
表的第三列是灵魂:Vue 不是"更少的代码",是责任转移——手动同步的责任交给框架,你换来的是"状态即真相"的简单心智;代价是新增一层抽象的调试成本(Devtools 必须熟练)。
六、常见坑点
坑 1:两版数据不同帧
原生版与 Vue 版各自连了 WS(连接数翻倍)。检查:数据服务器控制台连接数应为 2(两个前端各一条);若 Vue 版内部出现第 3 条,说明有人绕过 useScreenStore 私连——Day 65 单连接纪律的回归检查。
坑 2:Vue 版 KPI 不更新但曲线正常
分频镜像正常、latest 镜像断了(或反之)。定位:useScreenStore 内四个镜像的赋值是否都写全——桥接层最容易漏 onSnapshot 里某个字段。
坑 3:并排对比时端口冲突
两个 Vite 工程默认都抢 5173。启动 Vue 版时指定端口(npm run dev -- --port 5173,原生版换 5174),或改 vite.config.ts 的 server.port 固化。
坑 4:对照表写成"Vue 真香"集
只记录变简单的地方,忽略变复杂的(如:多了构建层、调试要学 Devtools、deep watch 的隐性坑)——对比报告失去公信力。诚实的代价栏才是技术判断力的证明。
七、自测挑战
T1 · 屏 B 完整迁移(90 分钟)
完成第一至二节全部模块(仪表/排名/雷达按 Day 76 模式包装 VChart),第三节对齐清单全过。本周核心作业。
T2 · 性能对比填表(30 分钟)
第四节表格填满实测数据,与预期解读对照;如 Vue 版掉帧,用 Performance 火焰图定位违规(九成是 deep watch / reactive 大对象)。
T3 · 对照表成文(40 分钟)
第五节表格扩到 ≥ 12 行(本周 NOTES.md 汇总),每行必须能回答"为什么"——这是 Week 14 博客《原生 vs Vue 开发大屏的体会》的提纲验收。
八、总结
| 环节 | 要点 |
|---|---|
| 装配 | 零件全备,新写的只有 option 翻译层 |
| 对齐验收 | 双版并跑逐项对照——不一致即素材 |
| 性能 | fps 应持平;Heap 底噪略高、趋势稳定即可 |
| 对照表 | 兑换率 + 代价两栏——责任转移是本质 |
| 收官 | 屏 B Vue 版完成,Week 12 攻 Router + Pinia |
本周毕业标准:屏 B Vue 版与原生版行为对齐、性能达标、build 零报错、对照表 ≥ 12 行。达成即具备"用 Vue 干活"的能力——下周把手写的 ScreenRouter 和 ScreenStore 换成正规军(Vue Router + Pinia),并迁移屏 A 与屏 C。