Vue3 还原一个企业级后台-14-项目总结

项目总结:踩过的坑、经验、可复用模式

14 篇博客的最后一篇。不做新东西了,坐下来复盘------这个项目到底踩了哪些坑、沉淀了什么可以带走的经验、有哪些模式能在下个项目直接复用。


一、先看数据

把整个项目的数字摆出来,不是为了凑字数,而是让复盘有一个量化的锚点:

维度 数据 备注
开发周期 1 周 工作日每天 6~8h,周末每天 4~6h
代码量 ~8,000 行 含 Vue 组件 + JS 逻辑 + SCSS + Mock 数据
文件数 ~50 个 .vue 22 个,.js 18 个,.scss 6 个,其余配置/脚本
通用组件 11 个 AppTable / AppDialog / AppPagination / StatusTag 等
业务页面 12 个 覆盖登录 + API 管理 + 模型汇聚 + 模型发布四大模块
Figma Frame 17 个 API 管理 8 帧 / 模型汇聚 6 帧 / 模型发布 3 帧 / 登录 1 帧
Mock 数据 105 条 API 注册 30 条 + 模型汇聚 40 条 + 模型发布 35 条
博客产出 14 篇 从项目背景、技术选型、设计系统、业务实现到性能优化、像素验收

一个人、一周时间,从零到可演示原型 + 14 篇博客。规模不算大,但密度高------几乎每天都要做完一个完整模块、写完对应的博客。


二、踩过的 10 个坑

每个坑都花了至少半小时甚至半天去排查。写下来既是对自己的提醒,也希望读者能绕过。

坑 1:Element Plus 中文 locale 默认是英文

el-pagination 的分页文字、el-table 的空数据提示、el-date-picker 的日历文字------全部是英文。查了半小时文档才发现需要手动引入中文包:

js 复制代码
// main.js
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/es/locale/lang/zh-cn'

app.use(ElementPlus, { locale: zhCn })

教训:Element Plus 虽然是国内团队开发,但默认 locale 是英文(国际化考虑),每个新项目第一件事就是挂 locale。

坑 2:SCSS 全局变量需要在 Vite 预配置

想把 tokens.css 里定义的颜色变量在 .vue 组件的 <style lang="scss"> 中复用,直接 @import 报错。原因是 SCSS 的全局变量需要 Vite 在编译层注入:

js 复制代码
// vite.config.js
export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@use "@/styles/variables.scss" as *;`
      }
    }
  }
})

注意:用 @use 代替 @import(Sass 新语法),且 as * 让变量暴露到全局命名空间,否则每个文件都得写 variables.$color-primary

教训 :CSS 变量(:root 声明)可以直接用,SCSS 变量需要编译层注入。两者在工程中承担的职责不同------CSS 变量负责运行时主题切换,SCSS 变量负责编译时一致性。

坑 3:路径别名需要 Vite + jsconfig 双重配置

Vite 里配了 @src 别名,编辑器还是报红波浪线。因为编辑器(VS Code)只看 jsconfig.jsontsconfig.json,不看 vite.config.js

json 复制代码
// jsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

教训 :路径别名是一个三方协同的事------Vite 负责编译、JS/TS config 负责编辑器提示、ESLint 需要额外插件(eslint-import-resolver-alias)。缺一个环节就报错。

坑 4:Figma 嵌套节点导出 SVG 的层级问题

Figma 里图标可能是一个 FRAME 包着一个 VECTOR,导出时用 mcp__figma__download_figma_images 可能只导出外层 FRAME 而丢失内部路径。解决方法是递归遍历节点,找到最底层的 VECTOR / COMPONENT 才能正确导出。

教训:Figma 的节点树比你想象中深。设计稿里看起来是"一个图标",在节点树里可能是 FRAME → GROUP → COMPONENT → VECTOR 的三四层嵌套。导出时要穿透到叶子节点。

坑 5:mockjs 在生产环境要自动失效

Mock 数据是用 Mock.mock() 全局注册的,如果不加环境判断,打出来的生产包也会拦截 axios 请求。解决方案是用 import.meta.env.DEV 包裹:

js 复制代码
// mock/index.js
if (import.meta.env.DEV) {
  import('./api-registry.mock.js')
  import('./model-hub.mock.js')
  import('./model-publish.mock.js')
}

import.meta.env.DEV 是 Vite 在构建时替换的常量,生产构建中为 false,Tree Shaking 会直接删掉整个 if 块。

教训 :Mock 数据只属于开发环境。Vite 的 import.meta.env 常量 + 动态 import 是最干净的方案。

坑 6:KeepAlive + 动态路由的匹配失效

想用 <keep-alive> 缓存模型详情的 3 个 Tab,切换回来时保留 Tab 状态。结果切回来还是重新渲染了。

html 复制代码
<!-- ❌ 这样不生效 -->
<keep-alive>
  <router-view />
</keep-alive>

原因:keep-alive 按组件 name 缓存,但动态路由 /model-hub/detail/:id 里不同 id 对应的是同一个组件。需要特殊配置:

html 复制代码
<keep-alive :include="['ModelHubList', 'ModelDetail']">
  <router-view :key="$route.fullPath" />
</keep-alive>

教训:keep-alive 的缓存粒度是"组件名 + key",不是"路由路径"。不同参数的同名组件会被复用,需要显式设置 key。

坑 7:大列表渲染卡顿

模型汇聚列表一共 40 条数据,每条含 6 个字段 + 1 个头像 + 状态标签。在 Chrome 里渲染时间正常,但在演示用的低配笔记本上,列表滚动有肉眼可见的掉帧。

排查后发现是 el-table 的列太多(每列都计算宽度 + 排序箭头渲染),且没有分页。

修复

  1. 加上 AppPagination,每页 10 条(不再一次性渲染 40 行)
  2. 对于确实需要大列表的场景,后续可用 el-table-v2 虚拟滚动

教训:"数据不多"不代表"渲染不卡"。表格渲染的瓶颈在 DOM 节点数,40 行 × 8 列 = 320 个表格单元格,每个单元格内有文字 + 样式 + 事件绑定,低配设备上会卡。分页是最简单的解决方案。

坑 8:复选框跨页保持选择

API 注册列表有全选/单选功能,切换分页后之前选中的行丢了。因为 el-tableselection 只记住当前页。

修复方案:在 Pinia 中维护一个 selectedIds 数组,翻页时手动同步:

js 复制代码
// 翻页时
const handlePageChange = () => {
  nextTick(() => {
    tableRef.value?.toggleRowSelection(row, selectedIds.value.includes(row.id))
  })
}

教训 :Element Plus 的 el-table 的 selection 是"当前页级"的,跨页选择必须在状态管理层(Pinia / Vuex)自己维护。

坑 9:Tab 切换 URL 同步的时机问题

模型详情的 3 个 Tab,设计上是 ?tab=interface / ?tab=files / ?tab=script 同步到 URL。但直接 router.replace({ query: { tab } }) 会在 Tab 切换时触发页面重新渲染------因为 router-view 检测到了路由变化。

解决:Tab 切换只改 query,不触发组件销毁重建。用 router.replace 且确保组件内的状态不受 query 变化的副作用影响。

js 复制代码
// ✅ Tab 切换时:改 query,但不触发组件 rebuild
const handleTabChange = (tab) => {
  router.replace({ query: { tab } })  // 不改变 path,不会重新创建组件
}

并且组件内 watch query 变化来驱动 Tab 切换,而不是反过来。

教训:URL 同步是"双刃剑"------它提升了可分享性,但也可能引入意外的路由变化副作用。原则是:手动操作驱动 URL 变化,URL 变化驱动 UI 状态,但要防止"URL 变化 → 组件重建 → 状态丢失"的死循环。

坑 10:多步骤表单的步骤校验时机

模型新增弹窗是 3 步骤表单(基本信息 → 接口配置 → 文件上传)。用户在第 2 步时回头改了第 1 步的内容,第 3 步的"提交"按钮没有感知到变更。

修复:每个步骤的表单校验结果存入一个 reactive 对象,提交时统一校验 3 个步骤:

js 复制代码
const stepValidation = reactive({
  step1: false,
  step2: false,
  step3: false,
})

const canSubmit = computed(() => {
  return stepValidation.step1 && stepValidation.step2 && stepValidation.step3
})

教训:多步骤表单的校验逻辑不在"当前步骤的表单内",而在"跨步骤的验证状态管理器"中。每个步骤只负责更新自己的状态位,提交流程汇总所有状态。


三、5 个可复用的工程模式

坑踩完了,说收获。下面是 5 个我在这个项目中验证过、可以直接复用到下一个项目的模式。

模式 1:设计 Token + CSS 变量双层架构

不要直接在组件里写 background-color: #C8E1FF,也不要只定义 CSS 变量就完事。用双层架构:

复制代码
Layer 1: tokens.css    ── 纯设计 token,如 --color-bg-page: #C8E1FF
Layer 2: element-override.scss ── 把 token 映射到 Element Plus 变量

好处:改主题色只需要改一个 CSS 变量文件;组件库不从 token 层直接取值,而是通过覆写层中转,保证一致性。

模式 2:AppTable 通用表格 + 业务配置分离

11 个通用组件中最值钱的就是 AppTable。它的设计思想是:把 Element Plus 的 el-table 的"通用能力"和"业务列配置"分离开

html 复制代码
<AppTable
  :data="list"
  :columns="columns"
  :loading="loading"
  :showSelection="true"
  @selection-change="handleSelectionChange"
>
  <template #actions="{ row }">
    <el-button @click="handleEdit(row)">编辑</el-button>
  </template>
</AppTable>

columns 是一个纯配置数组(不含 JSX、不含 v-if),由业务页面声明;AppTable 负责渲染、排序、分页、复选框联动。新页面只需配置 columns 就能得到一个完整表格。

模式 3:mockjs + axios 拦截器 + 环境开关

Mock 数据策略的三件套:

  1. mockjs 生成差异化数据(@ctitle / @cname / @datetime / @pick
  2. axios 拦截器做请求路径匹配,模拟接口响应
  3. 环境开关import.meta.env.DEV)让 Mock 只在 dev 生效

把这个模式抽成一个 createMock(moduleName, config) 工厂函数,新模块接入只需一行:

js 复制代码
// mock/api-registry.mock.js
export default createMock('api-registry', {
  basePath: '/api/registry',
  listFields: ['name', 'protocol', 'url', 'method', 'status'],
  listCount: 30
})

模式 4:路由 + 布局嵌套 + meta 驱动的菜单

后台系统的路由和菜单天然是一一对应的。不要让菜单组件硬编码路由路径,而是用 route.meta 驱动:

js 复制代码
{
  path: '/model-hub',
  component: MainLayout,
  meta: { title: '模型汇聚', icon: 'hub', module: 'model-hub' },
  children: [
    { path: '', component: ModelHubList, meta: { title: '模型列表' } },
    { path: 'detail/:id', component: ModelDetail, meta: { title: '模型详情', hidden: true } }
  ]
}

侧栏组件遍历路由树自动生成菜单,meta.hidden 的页面不出现在菜单中(如详情页)。新增一个模块 = 新增一组路由配置,菜单自动跟上。

模式 5:Playwright 像素对比验收流水线

第 13 篇讲过的 Playwright + pixelmatch 方案,这里再做一次提炼------它是一个独立于前端框架的通用验收模式:

复制代码
Figma 导出截图 → Playwright 自动化截图 → pixelmatch 像素对比 → 差异报告

3 个脚本文件(screenshot-all.js / compare-all.js / diff-report.js),换一组页面 URL 就能复用。不需要依赖任何前端框架,纯 Node.js 工具链。


四、性能优化数据回顾

第 12 篇的优化结果在这里汇总,作为项目复盘的"成绩单":

指标 优化前 优化后 提升
首屏时间 (FCP) 3.2s 0.9s -72%
JS 体积 (gzip) 1.8MB 580KB -68%
CSS 体积 (gzip) 320KB 48KB -85%
Lighthouse 评分 65 92 +27

核心优化手段:路由懒加载(砍 73% 首屏 JS)+ Element Plus 按需引入(省 84% CSS)+ vendor chunk 手动拆分 + KeepAlive 缓存 + Mock 生产剔除。

这个成绩对于"原型"来说已经超标。如果是正式生产项目,还可以再往下挖------HTTP/2 Server Push、CDN 边缘缓存、Service Worker 离线化,但那些属于另一个话题了。


五、三条最重要的经验沉淀

14 篇博客写下来,三条认知反复出现,我把它们提炼出来:

5.1 前端工程师 = 设计稿的"翻译官"

不是抄图。Figma 给你的是一个"视觉答案",你的工作是把它翻译成工程语言------CSS 变量、组件 props、路由配置、状态管理。翻译质量的衡量标准不是"颜色一样",而是"改一个设计变量时只有一处改动,改完后全部页面同步生效"。

5.2 Mock 数据是原型的灵魂

交互逻辑对不对,代码写得再好也看不出来------得灌入数据。而灌入数据的真实度决定了原型的说服力。用 mockjs 生成 105 条有差异性的数据,字段名符合业务逻辑、时间用最近日期、状态分布合理,演示时客户不会问"这些是假数据吗"。

5.3 通用组件是业务提速器

AppTable、AppDialog、AppPagination 这三个组件让后三个业务模块的开发从"搭 UI"变成了"配 JSON"。06 篇布局写完后的模块实现,每个模块的核心开发时间不超过 4 小时------因为 80% 的 UI 已经被通用组件覆盖了。


六、这个项目的"可复用性"

"做完就扔"的项目不值得写 14 篇博客。这个项目的真正价值在于------它是一个可复用的后台原型模板

模板的技术要素

  • Vue 3 + Vite 5 + Element Plus 2.4 工程脚手架
  • 设计 Token → CSS 变量 → Element Plus 覆写 的完整链路
  • 11 个通用组件(AppTable / AppDialog / AppPagination / StatusTag 等)
  • mockjs + axios 拦截器的 Mock 数据方案
  • 路由懒加载 + 按需引入 + vendor chunk 拆分的性能配方
  • Playwright + pixelmatch 的像素对比验收工具链

换一个 Figma 设计稿能复用多少?

可复用度 说明
工程脚手架 100% 直接复制,改项目名
设计 Token 体系 80% 颜色/字体需要重新提取,架构保持不变
通用组件 70% AppTable/AppDialog/AppPagination 直接复用,StatusTag 等需要调整
Mock 数据方案 90% createMock 工厂函数通用,数据模板需要按新业务重写
性能优化 100% 懒加载/按需引入/KeepAlive/vendor chunk 与业务无关
像素验收工具 95% 只改页面 URL 列表

结论:换一个 Figma 文件、换一组业务模块,约 60% 的代码可以直接复用,另外 30% 做配置调整,只有 10% 是完全新的业务代码。


七、后续可以做什么

这个项目做到现在,"演示原型"的目标已经完成。如果要继续推进,有 5 个方向:

方向 内容 优先级
TypeScript 迁移 当前是 JS,对大型项目维护性不足 ⭐⭐⭐
单元测试 vitest 覆盖通用组件(AppTable / AppDialog) ⭐⭐⭐
E2E 测试 Playwright 不只会截图,还能做交互测试 ⭐⭐
替换 Mock 接真实后端 对接 Python / Java 后端 API,去掉 mockjs ⭐⭐
移动端适配 当前只适配 1920×1080,考虑响应式

其中 TypeScript + 单元测试是最值得做的两个,因为这两项直接提升代码的可维护性,且不依赖后端就绪。


八、写给跟读了全系列的读者

如果你从第 01 篇一路读到这里------谢谢。

14 篇博客、50 个文件、8000 行代码、1 周时间。这不是一个"技术炫技"的项目,而是一个"把工程规范落到实战"的项目。我想通过这个系列表达的核心观点只有一句:

企业级开发不是会用什么技术,而是能把一组技术组织成一条可维护的工程链路。

设计稿 → 设计 Token → CSS 变量 → 工程脚手架 → 通用组件 → 业务模块 → Mock 数据 → 性能优化 → 像素验收 → 复盘总结------这条链路中的每一步,分开看都不难,难的是把它们串起来,让每一步的输出正好是下一步的输入。

如果你读完这 14 篇,能带走其中一两个模式用到自己的项目里,这个系列就值了。


下一篇:系列完结,感谢阅读。

全系列回顾:

相关推荐
前端粉刷匠1 小时前
2025 年是 Agent 的,2026 年是 Harness 的——AI 编程 Harness 架构深度解析
前端·人工智能
小白马突突突1 小时前
零信脱敏现已支持导出适合AI 分析的 脱敏 Markdown
人工智能
坤岭1 小时前
企业级Agent从0到1
后端
skywalk81631 小时前
硬核移植实录:在 FreeBSD 15.1 上从零跑起 DeepSeek 智能体 harness(附完整踩坑手册)
人工智能·freebsd·deepseek·harness
武子康1 小时前
GPT-Live 分析研究:从回合式语音到连续交互循环
人工智能·llm·agent
fail_to_code1 小时前
从 Lighthouse 83 到 100:一次 Vue 项目的性能排查实录
前端·人工智能
zlinear数据采集卡1 小时前
数据采集卡从入门到精通(7):流水线型ADC——级级接力,高速与高精的平衡术
开发语言·arm开发·嵌入式硬件·fpga开发·c#
用户69371750013841 小时前
DeepSeek 调价正式生效:一夜涨 11 倍,靠低价薅羊毛的日子结束了
前端·人工智能·后端
JAI科研1 小时前
Deepseek Agent Harness教程(二) | DeepSeek Harness 设计思路
人工智能·深度学习·算法·机器学习·自然语言处理·transformer·vllm