
有人问我Vue 3 学了半年看文档都会一上手写项目还是懵到底怎么破我的回答通常是同一个别啃大项目也别光看教程直接拿 Element Plus 做 mini 实战一个案例一个案例地磨。今天要聊的这个 18-mini-vue element就是我自己整理的一套练手项目——用 18 个由浅入深的小案例把 Vue 3 Element Plus 的核心玩法全部串一遍顺带解决一堆新手必踩的坑。这套东西适合谁刚学完 Vue 基础语法、想在项目里用 Element Plus 但不知道从哪下手的人以及做过一两个 demo 但是对组件封装、路由传参、表格多选这些实际开发高频场景还发怵的人。不是那种照着敲一遍就忘的教程每一个 mini 案例都能独立跑起来并且可以直接搬进你的真实项目里改改就用。1. 内容整体设计与思路拆解1.1 为什么是 18 个 mini 案例而不是一个大项目很多人学 Vue 喜欢一上来就找个前后端分离的电商系统跟着敲磕磕绊绊两三周最后项目跑不起来或者跑起来了也不知道每一步为什么这么写。大项目的信息密度太高路由、状态管理、组件通信、权限控制全揉在一起任何一个环节报错新手都很难定位。mini 案例的核心思路是单一难点突破。每个案例只聚焦一个知识点比如第 3 关就只做表格数据渲染第 4 关专门搞表单校验第 7 关才引入路由参数。这样每一关的代码量控制在 100 到 300 行出了问题一眼就能看穿调试成本极低。等你把 18 关全部过完再回头看那些大项目会发现所谓的复杂系统本质就是这些 mini 案例的组合而已。这套练手方案还有一个隐藏优势积累组件片段。每一个 mini 案例都是一个独立的小组件我在实际开发中经常直接翻以前的案例代码来改比重新查文档写一遍快得多。1.2 技术选型Vue 3 Element Plus Vite 的组合逻辑先说 Element Plus 和 Element UI 的区别。Element UI 是配套 Vue 2 的Element Plus 是官方为 Vue 3 重写的组件库。现在新项目基本都上 Vue 3所以直接学 Element Plus 就行别再纠结要不要先学 Element UI——语法和组件名大体相似但响应式原理、弹窗调用方式、类型支持都有差异没必要走回头路。构建工具选 Vite 而不是 Vue CLI原因是 Vite 基于 esbuild 预构建依赖冷启动速度比 Webpack 快一个数量级。对于 mini 案例这种高频写一段、看效果的场景Vite 的开发体验是碾压级的。而且 Element Plus 官方文档的快速上手示例就是基于 Vite跟着走不会遇到版本兼容的坑。版本搭配上我推荐 Vite 4.x Vue 3.3.x Element Plus 2.4.x。Element Plus 2.x 版本迭代很快但核心 API 很稳定不用追最新稳定版本就够了。2. 核心细节解析与实操要点2.1 项目初始化的完整步骤与依赖安装先把这个阶段最容易踩的坑说在前面很多新手在安装 Element Plus 时会遇到codegen node is missing for element/if/for node这个报错原因是 Vite 的插件版本和 Vue 编译器版本不匹配。解决方式很简单把vitejs/plugin-vue升级到最新版或者统一使用 Vue 3.3 以上的版本。初始化项目就一段命令npm create vitelatest 18-mini-vue-element选择 Vue 框架然后进入项目目录装依赖cd 18-mini-vue-element npm install npm install element-plus npm install element-plus/icons-vue这里要多装一个element-plus/icons-vueElement Plus 的图标是独立发布的包不装的话图标组件全部无法使用。我见过不少人卡在这一步还以为是组件库没装好。装完之后在main.js里引入 Element Plus 和样式import { createApp } from vue import ElementPlus from element-plus import element-plus/dist/index.css import * as ElementPlusIconsVue from element-plus/icons-vue import App from ./App.vue const app createApp(App) for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } app.use(ElementPlus) app.mount(#app)全量引入的方式对 mini 案例来说足够了等以后项目体积敏感了再切换按需引入用unplugin-vue-components和unplugin-auto-import插件配置一下就行。前期没必要为了优化而优化先把功能跑通才是重点。2.2 Element Plus 核心组件使用中的 5 个高频细节细节一表格多选怎么保留勾选状态。这是 Element Plus 里被问烂了的问题场景是翻页之后之前勾选的行要保留。直接用selection-change只能拿到当前页的选中数据翻页就丢了。正确做法是配合row-key和reserve-selection属性el-table :datatableData row-keyid reftableRef selection-changehandleSelectionChange el-table-column typeselection reserve-selection / /el-table注意row-key必须指向表格数据里的唯一字段比如 idreserve-selection写在el-table-column上这样才能跨页保留勾选。我见过有人把reserve-selection写到el-table上结果完全不生效这是新手最常犯的错误。细节二el-form的校验时机。很多人写完表单发现点提交没有触发校验其实是没写prop属性。el-form-item上的prop必须和表单数据里的字段名一致而且el-form上要绑定:model不然校验规则根本找不到对应的字段。细节三弹窗关闭后表单数据还在。这也是高频问题。弹窗组件默认不会销毁内部内容所以编辑弹窗打开后上一次填写的数据还留在表单里。解决办法是在弹窗的close事件里调用formRef.value.resetFields()或者用v-if控制表单的渲染。我在案例里用的是后者代价最小el-dialog v-modeldialogVisible el-form v-ifdialogVisible :modelformData refformRef.../el-form /el-dialog细节四路由参数读取。Vue Router 4 里用useRoute拿参数route.query拿 query 参数route.params拿路径参数。有个容易忽略的点同一个组件复用时路由参数变了但组件不会重新渲染需要监听route的变化。mini 案例里专门开了一关讲这个用watch监听路由变化再重新请求数据。细节五el-table的样式异常。热词里有一条是Element Plus 表格偶尔出现莫名的阴影我遇到过通常和浏览器渲染的table边框有关加一条全局样式就能解决.el-table th.el-table__cell { background-color: #f5f7fa !important; }如果阴影问题依旧检查是不是多个el-table共用了同一个ref导致的渲染冲突。3. 实操过程与核心环节实现3.1 第一个 mini 案例从布局到表格的完整演示前面铺垫了这么多现在实际拆一个案例。第 1 关的目标很简单搭出一个后台管理页面最基础的骨架——顶部导航、侧边栏、中间内容区然后在内容区放一个表格。这个骨架搭完后面 17 关全部基于它扩展。布局用 Element Plus 的el-container系列组件el-container classlayout el-aside width200px el-menu :default-active$route.path router el-menu-item index/dashboard概览/el-menu-item el-menu-item index/user用户管理/el-menu-item /el-menu /el-aside el-container el-headermini-vue-element/el-header el-main router-view / /el-main /el-container /el-container注意el-menu上加了router属性这样点击菜单项会直接触发路由跳转不用手动写点击事件。index就是路由路径$route.path用于高亮当前菜单这是后台管理里最常用的写法。表格部分稍微加点料模拟一份用户列表数据el-table :datauserList stripe border el-table-column propname label姓名 min-width120 / el-table-column propage label年龄 width80 / el-table-column propemail label邮箱 min-width200 / el-table-column label操作 width150 template #default{ row } el-button typeprimary link clickhandleEdit(row)编辑/el-button el-button typedanger link clickhandleDelete(row)删除/el-button /template /el-table-column /el-tablestripe是斑马纹border是纵向边框这两个属性可以让表格更接近实际项目的视觉效果。操作列里通过#default{ row }获取当前行的数据这是 Element Plus 表格最常用的插槽写法一定要记牢。这个案例虽然短但已经把布局 路由 表格 插槽四个核心点全部串了起来后面每一个案例基本都在这个骨架里做文章。3.2 用 18 个关卡把 Vue 3 核心知识点逐一击破我把 18 个关卡的安排列出来你可以看到它的递进逻辑关卡主题核心知识点1-2项目搭建 布局Vite 初始化、Container 布局、路由基础3-5表格 表单Table 渲染、Form 校验、Dialog 弹窗6-8路由与通信路由参数、动态路由、组件通信9-11数据请求与状态Axios 封装、Pinia 状态管理、API 层设计12-14高级表格玩法多选保留、排序过滤、多表格导出 Excel15-16多媒体与地图m3u8 视频播放、地图组件接入17-18综合实战细节WebSocket 推送、打包部署与常见报错这个顺序不是随便排的。前 8 关解决的是页面怎么写中间 4 关解决的是数据从哪来、状态怎么管后面几关解决的是真实开发中的特殊场景。你会发现每一关都在前几关的基础上加一个新变量不会突然冒出一个完全陌生的概念。比如第 14 关多表格导出 Excel这是一个很多项目里都会遇到的需求但新手普遍不知道怎么下手。思路是先把多个表格的数据合并成一个二维数组再用xlsx库生成 Excel 文件。核心就两步第一步收集数据第二步调用库方法导出import * as XLSX from xlsx const exportExcel () { const sheet1Data tableData1.value.map(row [row.name, row.age]) const sheet2Data tableData2.value.map(row [row.product, row.price]) const worksheet1 XLSX.utils.aoa_to_sheet(sheet1Data) const worksheet2 XLSX.utils.aoa_to_sheet(sheet2Data) const workbook XLSX.utils.book_new() XLSX.utils.book_append_sheet(workbook, worksheet1, 用户表) XLSX.utils.book_append_sheet(workbook, worksheet2, 订单表) XLSX.writeFile(workbook, 数据导出.xlsx) }配合 Element Plus 的el-button点击事件整个功能 20 行代码以内完成。这就是 mini 案例的典型形态——足够小但可以直接复用。3.3 路由参数和组件通信的实战编码演示路由参数和组件通信是 Vue 开发里最绕不开的两个点我在案例里把它们分开做了两个关卡。路由参数这关用的是一个列表页点击跳转详情页的场景。首先在路由配置里定义带参数的路径const routes [ { path: /detail/:id, name: Detail, component: Detail } ]然后从列表页跳转const router useRouter() const goDetail (row) { router.push({ name: Detail, params: { id: row.id } }) }在详情页里接收参数const route useRoute() const id route.params.id // 用这个 id 请求详情数据组件通信这关我演示了三种方式父传子用 props子传父用 emit跨组件用 Pinia。这三种方式看似基础但很多人实际用的时候搞不清该用哪种。我的建议很简单父子之间直接用 props/emit不要绕道全局状态跨页面或跨多层组件才用 Pinia。违背这个原则项目代码很快就变成意大利面条。4. 常见问题与排查技巧实录4.1 高频报错的完整排查对照表练完 18 个案例我把自己遇到过的、以及学员问得最多的报错整理成了一张表按这个表排查90% 的问题都能自己解决报错/异常信息出现场景解决方式codegen node is missing for element/if/for nodeVue 编译器与 Vite 插件版本不匹配升级vitejs/plugin-vue到最新版Failed to resolve component: el-button组件未注册检查是否app.use(ElementPlus)或按需引入配置是否正确Cannot read properties of undefined (reading xxx)数据还没返回就渲染了使用v-if或可选链?.保护加载完成再渲染表格多选翻页后丢失未配置row-key和reserve-selection在el-table上加row-key在el-table-column上加reserve-selection表单校验不触发el-form-item的prop和表单数据字段不一致确保prop与数据属性名一一对应打包后布局异常静态资源路径问题Vite 配置base: ./使用相对路径最后一条是热词里很多人提到的vue 打包后布局异常我专门说一下。Vite 默认的base是/部署到服务器子目录时静态资源加载不出来页面布局自然就崩了。在vite.config.js里把base改成./让资源走相对路径绝大多数情况都能解决。4.2 从 0 到 1 独立解决报错的通用思路排查报错的心态比技术更重要。很多新手一看到英文报错就慌了其实前端报错信息已经把定位线索给得很清楚了关键是要学会逐层拆解。我的通用排查流程分三步走第一步看控制台报错信息的最后一行找关键词。比如Failed to resolve component说明组件没注册或引入路径写错Cannot read properties of undefined说明某个对象还没拿到数据。第二步确认报错出现的交互时机。是初始化时就有还是点击某个按钮后才出现初始化就报错问题通常在依赖引入或路由配置交互后才报错问题多在数据状态或事件处理逻辑。第三步二分注释法定位。把代码按功能块切开先注释掉一半看报错还在不在如果还在就注释掉另一半。循环几次问题代码块就会被锁定。这个办法土但极其有效尤其是在模板表达式复杂的情况下。还有一个小技巧在关键位置加console.log打印变量看数据结构是否符合预期。前端开发本质上就是数据流的管理搞清楚了每一步数据是什么形态90% 的问题都能自己找到根源。5. 进阶扩展从 mini 案例到真实项目的衔接5.1 把 mini 案例改造成可复用的业务模板18 个案例练完之后很多人会有一段时间的空窗期——感觉自己都会了但是面对一个全新项目又不知道从哪下手。我的建议是不要急着开新项目先把做过的 mini 案例改造成可复用的业务模板。具体做法是把每个案例中与具体业务相关的数据、文案、接口地址抽离出去让组件只保留交互逻辑 插槽 自定义事件。比如用户管理表格把userList改成通过props传入删除确认逻辑改成emit出去这样一个通用表格组件就出来了。再进一步把常用的 Axios 请求封装、菜单配置、权限判断逻辑沉淀到一个统一目录里。等下次再开项目你手上就有一套搭好骨架、填业务肉的半成品方案开发效率提升不是一点半点。5.2 数据请求、状态管理和打包部署的综合演练最后 3 个关卡我把数据请求、状态管理和打包部署放在一起做一个完整演练这是从会写页面到能上线的关键一步。数据请求环节我封装了一个轻量级 Axios 实例统一处理 baseURL、token 注入和错误提示// request.js import axios from axios import { ElMessage } from element-plus const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) request.interceptors.response.use( response response.data, error { ElMessage.error(error.response?.data?.message || 请求失败) return Promise.reject(error) } )状态管理用 Pinia 而不是 Vuex。Pinia 的 API 更简洁对 TypeScript 支持更好而且 Vue 官方已经把它作为推荐方案。一个典型的用户状态存储长这样import { defineStore } from pinia export const useUserStore defineStore(user, { state: () ({ name: , role: }), actions: { setUserInfo(info) { this.name info.name this.role info.role } } })使用的时候在组件里useUserStore()就能拿到 store 实例。Pinia 和 Vuex 的核心区别在于Pinia 没有mutations直接在actions里改状态少了一层概念心智负担小很多。如果你是刚学状态管理直接 Pinia 起步别回头碰 Vuex 了。打包部署环节除了前面说的base: ./配置还要注意生产环境移除 console 日志、开启 gzip 压缩等优化项。这些细节在本地开发时看不出来但一上线就会影响首次加载速度。我的案例里给了一份简单够用的 Vite 配置直接抄import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ base: ./, plugins: [vue()], build: { chunkSizeWarningLimit: 1500, rollupOptions: { output: { manualChunks: { element-plus: [element-plus], vendor: [vue, vue-router, pinia] } } } } })这段配置把 Element Plus 和 Vue 核心库分别拆包浏览器可以利用缓存二次访问的加载速度会有肉眼可见的提升。5.3 结合实例m3u8 视频播放、WebSocket 与地图接入的扩展玩法热词里出现了vue 播放 m3u8和vue 添加 websocket这说明很多人在真实项目中会遇到这些需求。mini 案例的第 16、17 关正好覆盖了这两个点顺带加了一个地图接入。m3u8 是 HLS 协议的索引文件格式浏览器原生不支持直接播放需要配合hls.js库来实现。Vue 里的用法很简单在mounted里实例化 Hls绑定到 video 元素import Hls from hls.js const videoRef ref(null) onMounted(() { const video videoRef.value if (Hls.isSupported()) { const hls new Hls() hls.loadSource(你的.m3u8地址) hls.attachMedia(video) } })WebSocket 的应用场景是服务端主动推送数据比如实时消息通知或工单状态变更。在 Vue 里接入 WebSocket关键要处理组件卸载时的连接关闭避免内存泄漏let socket null const connectWebSocket () { socket new WebSocket(ws://your-server.com/socket) socket.onmessage (event) { const data JSON.parse(event.data) // 更新页面数据 } } onBeforeUnmount(() { socket?.close() })地图接入用的腾讯地图Element Plus 里没有地图组件但可以结合el-dialog做一个地图选点弹窗选完把坐标回填到表单里。这一步的关键是初始化地图的时机——弹窗内容没渲染完就初始化地图容器是空的所以要在nextTick之后调用地图 API或者配合v-if控制弹窗内部内容的渲染时机。6. 关于这套练手方案的几句大实话练完 18 个 mini 案例最大的变化不是学会了 Element Plus 的 30 个组件而是建立了一种拆解思路遇到一个需求先想它由哪几个独立功能点构成每个功能点应该用什么组件、什么数据流来实现而不是直接打开搜索引擎搜某某功能怎么做。在实际操作中我还有一个习惯每个案例做完了不要急着进下一个花 10 分钟想想如果需求变一下这个组件要改成什么样。比如表格案例如果加上搜索、分页、批量删除呢这样一个小小的思维实验比多敲一个案例更能锻炼设计能力。最后分享一个小技巧把每个 mini 案例的最终效果截图保存下来配上 50 字以内的技术要点做成一张个人项目速查表。以后面试、写简历、或者开新项目都能快速回忆起每个方案的关键细节。这是我自己一直在用的方法实测比翻笔记高效得多。