Day 71 · Vite 项目搭建与大屏骨架迁移 — Vue 版的第一块砖
今天不碰任何图表和数据,只做一件事:把大屏的"壳"用 Vue3 重建——Vite 工程、SFC 单文件组件、三屏容器、scale 自适应壳。壳迁移完成的标准:浏览器打开后,三个屏幕容器按 Day 64 的布局正确摆放(内容暂时空白)、1920×1080 与 1366×768 无破版。壳是所有后续组件的容器,壳不对后面全歪。
目录
- 一、创建工程:Vite + Vue3 + TS
- 二、SFC 与 script setup:心智模型的切换
- 三、目录设计:Vue 版大屏的骨架
- 四、迁移三屏容器与 scale 壳
- 五、主题 token 的原样搬迁
- 六、常见坑点
- 七、自测挑战
- 八、总结
一、创建工程:Vite + Vue3 + TS
# 在 my-study 下创建 Vue 版大屏工程(与原生版 dashboard 平级)
npm create vue@latest dashboard-vue
# 交互选择:TypeScript = Yes,其余(Router/Pinia/测试)本周先全 No
# —— 生态件 Week 12 再加,今天保持最小心智负担
创建后先跑通:
cd dashboard-vue
npm install
npm run dev # 打开 http://localhost:5173 看到 Vue 欢迎页
npm run build # 验证 vue-tsc + vite build 全绿再继续
🎯 npm run build 必须先绿:工程链路有问题时越早暴露越便宜。此后每天收工前 build 一次当回归测试。
二、SFC 与 script setup:心智模型的切换
2.1 从"命令式"到"声明式"
原生版(Day 64-70)的心智:拿 DOM,改 DOM。
// 原生版:命令式——状态变了,由你亲手改界面
header.innerHTML = `<span class="tech-card__bar"></span>${title}`;
el.classList.add("screen-hidden");
Vue 版的心智:声明状态,界面是状态的映射。
<!-- Vue 版:声明式——只描述"界面长什么样",状态变了界面自动变 -->
<div :class="{ 'screen-hidden': !isActive }"></div>
本周最重要的认知转变:以后你的工作是"改状态",而不是"改界面"。所有 classList、innerHTML、appendChild 手艺,都兑换成"状态 + 模板"。
2.2 SFC 的三段结构
<script setup lang="ts">
// 逻辑:状态、计算、侦听(TS 生效)
import { ref } from "vue";
const title = ref("智慧工厂监控中心");
</script>
<template>
<!-- 结构:状态的声明式映射 -->
<header class="screen-header">{{ title }}</header>
</template>
<style scoped>
/* 样式:scoped 让样式只作用于本组件 */
.screen-header { color: var(--text-title); }
</style>
<script setup> 是 Vue3 的语法糖:顶层变量/函数自动暴露给模板,无需 return { title }(选项式 API 的写法,看旧代码时认识即可)。
三、目录设计:Vue 版大屏的骨架
dashboard-vue/
├── server/ # 直接复用原生版数据服务器(拷贝或软链,一字不改)
├── src/
│ ├── App.vue # 根组件:scale 壳 + 三屏容器
│ ├── main.ts # 入口:挂载 + 主题注册
│ ├── styles/
│ │ └── tokens.css # Day 66 的主题 token 原样搬迁(见第五节)
│ ├── core/ # 与框架无关的纯逻辑(Week 10 迁移过来的类)
│ │ ├── types.ts # PanelSnapshot / DeviceState 等类型
│ │ └── screen-router.ts # ScreenRouter 暂时原样保留(Week 12 组件化)
│ ├── screens/ # 三屏,每屏一个组件
│ │ ├── ScreenA.vue # KPI 总览(本周占位)
│ │ ├── ScreenB.vue # 实时监控(Day 77 的主战场)
│ │ └── ScreenC.vue # 产线拓扑(Week 13 迁移)
│ └── components/ # 跨屏复用组件(Day 74 开始填充)
└── vite.config.ts
设计原则:core/ 放"不 import vue 的代码"(可测试、可迁移),组件层薄薄一层负责"状态 → 界面"。ScreenRouter 本周不组件化——先让旧类在新工程里跑起来,再逐步 Vue 化(绞杀者模式的小型演练)。
四、迁移三屏容器与 scale 壳
4.1 App.vue:scale 壳 + 三屏容器
<script setup lang="ts">
/**
* 根组件:1920×1080 设计稿 + transform scale 自适应壳
* 迁移自原生版 Day 55 的 ScaleShell(逻辑一字不改,换成生命周期钩子)
*/
import { onMounted, onUnmounted, ref } from "vue";
import ScreenA from "./screens/ScreenA.vue";
import ScreenB from "./screens/ScreenB.vue";
import ScreenC from "./screens/ScreenC.vue";
const scale = ref(1); // 缩放系数(响应式,模板自动跟随)
/** 窗口变化时重算缩放(Day 55 原逻辑) */
const updateScale = (): void => {
scale.value = Math.min(innerWidth / 1920, innerHeight / 1080);
};
onMounted(() => {
updateScale();
window.addEventListener("resize", updateScale); // 成对纪律:挂载注册
});
onUnmounted(() => {
window.removeEventListener("resize", updateScale); // 卸载清理(Day 69 泄漏教训的 Vue 版)
});
</script>
<template>
<!-- 外层撑满视口并居中,内层固定设计稿尺寸再整体缩放 -->
<div class="screen-frame">
<div class="screen-canvas" :style="{ transform: `scale(${scale})` }">
<ScreenA />
<ScreenB />
<ScreenC />
</div>
</div>
</template>
<style scoped>
.screen-frame {
width: 100vw; height: 100vh; overflow: hidden;
display: flex; align-items: center; justify-content: center;
background: var(--bg-page);
}
.screen-canvas {
width: 1920px; height: 1080px; flex: none; /* 固定设计稿尺寸 */
transform-origin: center center;
position: relative;
}
</style>
注意:这里已经悄悄用上了响应式——scale 是 ref,resize 回调里改 scale.value,模板的 transform 自动更新。原生版里"改 style 属性"的活,Vue 版变成"改一个数"。
4.2 三屏组件的占位实现
<!-- screens/ScreenB.vue(占位版,Day 77 替换为完整实现) -->
<script setup lang="ts">
/** 屏 B:实时监控(本周从占位开始,逐步长出血肉) */
</script>
<template>
<section class="screen screen-b">
<h2>屏 B:实时监控(迁移中)</h2>
</section>
</template>
<style scoped>
.screen {
position: absolute; inset: 0; /* 三屏叠放,互斥显示 */
}
.screen-b { background: var(--bg-page); }
</style>
五、主题 token 的原样搬迁
把原生版 tokens.css(Day 66 的三层 token:raw → 语义 → 组件)整文件复制到 src/styles/tokens.css,并在 main.ts 引入:
/**
* 入口:先主题后挂载——保证首帧就有正确配色,避免闪白
*/
import { createApp } from "vue";
import App from "./App.vue";
import "./styles/tokens.css"; // 全局样式不走 scoped,在入口统一引入
createApp(App).mount("#app");
🎯 视觉零变化纪律的落地:token 文件一个字符都不改。改了,"原生 vs Vue 对比"就不成立——对照实验要求控制变量。
六、常见坑点
坑 1:scoped 样式选择不到子组件根元素
scoped 会给选择器加属性后缀,跨组件选中需要 :deep()。规避:全局布局类(.screen 等)放在非 scoped 的全局 CSS 中,组件内 scoped 只写私有样式。
坑 2:Vite 严格模式下水印报错 / vue-tsc 版本不匹配
npm create vue@latest 生成的版本应是自洽的;如果手动升级了 typescript 版本导致 vue-tsc 报错,回退到脚手架版本——第一周不动工具链。
坑 3:把 server 也塞进 src 让 Vite 编译
数据服务器是 Node 进程,不归 Vite 管。保持 server/ 独立目录、独立终端启动(node server/index.js),与原生版完全一致——Vue 版前端吃的是同一条数据链路,这本身就是重构正确性的验证手段。
坑 4:模板里忘了 .value(或反过来)
模板中 ref 自动解包(写 scale 不写 scale.value);script 中必须 .value。报错特征:模板显示 [object Object] 或 NaN。建立肌肉记忆:script 里见 ref 名就想 .value,模板里绝不写。
七、自测挑战
T1 · 工程搭建 + 骨架迁移(80 分钟)
完成第一至五节:build 绿、双分辨率无破版、三屏占位容器就位、server 复用跑通。
T2 · 响应式初体验(20 分钟)
给 App.vue 加一个当前时间 ref(setInterval 每秒更新,onUnmounted 清除定时器),显示在标题栏右侧。体验"改状态 → 界面自动变",并确认卸载后定时器不泄漏(DevTools 里组件树销毁后 CPU 归零)。
T3 · 对照笔记开篇(20 分钟)
新建 NOTES.md,写"原生 vs Vue 对照表"前三行:scale 壳、标题文本、resize 监听——各写两版写法与行数。这个表要维护到 Day 77。
八、总结
| 环节 | 要点 |
|---|---|
| 工程创建 | create-vue 脚手架,TS 开、生态件全关,build 先绿 |
| 心智切换 | 命令式改 DOM → 声明式改状态 |
| 目录设计 | core/ 不依赖 Vue;screens/ 三屏;旧类先跑再绞杀 |
| scale 壳 | 逻辑原样迁移,addEventListener 成对清理 |
| 主题搬迁 | token 整文件复制零修改——对照实验的控制变量 |
明天进入 Vue 的心脏:响应式系统。