Vue3+Element-Plus+Mock.js前端骨架:按需引入与联调切换 简介面向希望快速熟悉 Vue3 新语法与前端工程化流程的开发者这套 vue3-demo 以 Vue3 框架配合 Element-Plus 组件库搭建基础工程并预置 Mockjs 假数据与 axios 请求工具已经完成表格组件的引入与页面示例解压后即可在此基础上继续开发省去环境配置和依赖调试的重复劳动。压缩包共 27 个文件以 9 个 vue 单文件组件、3 个 js 脚本、3 个 json 配置和 3 个 md 说明文档为主另含 css 样式、html 入口及 png 图片等资源整体约 130KB目录精简、层次清楚main.js 入口、App.vue 根组件、components 组件目录与 vite 构建配置分工明确便于按模块查阅。已有 2140 人学习下载可作为入门参照。读者能够从中了解组合式写法的组织方式、Element-Plus 表格组件的接入步骤、Mockjs 模拟数据与 axios 请求的配合用法并直接沿用现有结构扩展页面、路由和接口模拟较快验证自己的想法也适合作为课程演示或小型练习的起步模板。1. 一套开箱即用的 Vue3 Element-Plus Mock.js 前端骨架值不值得拿来当项目起点接手后台页面的活最烦的从来不是写界面而是等接口静态页画完了后端字段还没定联调前一天字段集体改名这种事经历一次就想着给自己留条后路。vue3-demo这套组合解决的就是这段空窗期——Vue3 组合式 API 打底Element-Plus 出中后台那套表格、表单、弹窗Mock.js 顶在请求前面先造数据依赖和环境都配好装完依赖就能点。它适合三类人刚转 Vue3、想找个能跑起来的完整骨架的手里有中后台需求、后端排期排到下周的想练 Element-Plus 组件组合的。别指望它当生产级架构路由权限、请求重试、错误上报都没做边界认清再动手比事后骂脚手架缺东西有用得多。2. 跑通 vue3-demoNode 版本、依赖安装和启动脚本的三处硬性检查拿到一个「环境已经配好」的包最容易犯的错是直接npm install然后等奇迹。所谓配好指的是作者机器上配好了你机器上的 Node 版本、包管理器、端口占用情况它一概不管。我在不同机器上跑同一份vue3-demo翻车最多的三次都出在这三件事上而不是代码本身。这一章先把运行时地基夯实后面 Element-Plus 和 Mock 才有讨论的意义。2.1 目录结构决定你该先动哪个文件解压之后先别装依赖花两分钟看目录。这套骨架里真正会影响你后续开发的文件就那么几个其余都是可以直接忽略的模板资产。先分清哪些是入口、哪些是配置、哪些是假数据能省掉后面「改了没反应」的排查时间。路径职责动它的风险vite.config.js插件装载、请求转发、样式预处理改错直接起不来改动前先备份src/main.js应用入口注册插件与全局样式按需引入后这里越干净越好src/router/路由表与页面挂载点删页面忘记删路由会白屏mock/Mock.js 数据模板目录目录名和mockPath必须对齐src/utils/request.jsaxios 实例与拦截器baseURL改动会影响所有接口src/views/业务页面练手主要在这里相对安全随便改看到mock/目录和request.js同时存在基本可以确认这套骨架的接口链路是「页面 → axios 实例 → Mock 拦截」而不是在组件里直接import Mock from mockjs造假数据。这两种做法差别很大前者切换真接口时只改一个baseURL后者得把每个组件里的假数据删干净。选型理由就在这里——假数据必须待在请求层之外才能被干净地摘掉。2.2 安装与启动三条命令和失败时的对照信号顺序不能反先验版本再装依赖最后起服务。很多人跳过第一步结果在npm install阶段看到一堆EBADENGINE警告或者启动时报crypto.hash is not a function这类错误十有八九是 Node 太旧。Vite 5 起步要求 Node 18Vite 6 建议 Node 20 LTS 以上用nvm或fnm切一下比修依赖快得多。# 1. 版本体检Node 18 是底线20 LTS 更省心 node -v npm -v # 若使用 nvm切到 20 再继续 # nvm use 20这一步的逻辑很简单Vite 的依赖预构建、ESM 解析、import.meta.env注入都依赖较新的 Node 运行时版本不够时错误信息往往指向具体某个包很容易误导你去降级依赖实际只要换 Node 就行。# 2. 装依赖有锁文件时优先 ci保证和作者环境一致 npm ci # 没有 package-lock.json 才退回 install # npm installnpm ci会严格按package-lock.json安装并先清空node_modules装出来的依赖树和作者机器上的一致代价是不能改package.json。如果仓库用的是 pnpm看到pnpm-lock.yaml对应命令是pnpm install --frozen-lockfile用 yarn 就是yarn install --frozen-lockfile。混用包管理器是另一类高频事故node_modules里同时存在 npm 和 pnpm 的目录结构表现为「明明装了某个包运行时报找不到」。# 3. 起服务并固定端口避免端口漂移导致接口前缀对不上 npm run dev -- --host 0.0.0.0 --port 5173--host 0.0.0.0是为了让同一局域网内的手机或另一台机器能访问做移动端适配时很有用--port 5173固定端口的意义在于有些骨架会把完整地址写进.env或 Mock 的baseURL端口一变请求就打到别处去了。启动成功后终端会打印本地地址和网络地址两个都能点。失败信号对照着看会快很多终端报Port 5173 is in use说明端口被占换端口或杀掉旧进程页面白屏但终端无报错先开控制台看是不是某个组件按需引入的样式没加载Failed to resolve import一类错误八成是路径别名没在vite.config.js的resolve.alias里配或者编辑器没读到jsconfig.json。2.3 package.json 里三个脚本的真实分工dev、build、preview三个脚本不是随便起的名字它们对应三种完全不同的运行形态混淆它们会引出一堆「本地好好的打包就废」的疑问。dev走 Vite 开发服务器模块按需编译、热更新生效、import.meta.env.DEV为trueMock 插件在这个形态下工作。build走 Rollup 打包产出到dist/此时import.meta.env.PROD为true开发期插件一般不参与。preview是拿dist/起一个静态服务器做产物验收它不会重新编译所以你改了源码不重新buildpreview看到的还是旧东西——这一点坑过不少人。我一般的习惯是跑通dev之后立刻跑一次build哪怕业务代码一行没写。原因很实在按需引入、环境变量、Mock 开关这些配置在开发模式下往往「看起来正常」一打包就暴露问题早暴露比上线前暴露便宜得多。这一步只要两分钟能提前发现 80% 的构建期配置错误。3. 按需引入 Element-Plus从全量app.use到自动导入样式和体积怎么同时管住Element-Plus 的默认文档示例是app.use(ElementPlus)全量注册写法最短代价是整包进产物首屏 JS 里它一家能占掉相当可观的一块。中后台项目组件用得多全量引入在小项目上感知不明显一旦页面数量上来、又要求首屏速度就得改成按需。这一章把自动导入的两个插件、d.ts生成、主题变量覆盖串成一条线顺带说清函数式组件样式为什么会单独掉队。3.1 全量引入的代价与按需引入的边界全量引入的本质是「把上百个组件的注册逻辑和样式一次性打进包里」构建工具没法帮你摇掉没用到的那部分因为注册是运行时行为。按需引入靠的是编译期改写模板里写了el-table插件就自动补一行import { ElTable } from element-plus和对应的样式引入没写的组件不进来。按需方案不是没有成本。它依赖两个 unplugin 插件在编译期做 AST 分析配置写错时表现为「组件能渲染但样式全丢」或者「ElMessage弹窗没背景色」排查起来比全量引入更绕。常见做法是两个插件一起上unplugin-vue-components负责模板里的组件unplugin-auto-import负责ElMessage、ElMessageBox、ElNotification这类在script setup里直接调用的函数式 API。3.2 unplugin 双插件的配置与 d.ts 生成配置集中在vite.config.js两个插件都要挂且 resolver 都要传少一个就会在函数式组件上翻车。// vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers import path from node:path export default defineConfig({ resolve: { alias: { : path.resolve(__dirname, src) } // 路径别名和 jsconfig 保持一致 }, plugins: [ vue(), AutoImport({ imports: [vue, vue-router], // ref/computed/useRoute 免手写 import resolvers: [ElementPlusResolver()], // 关键函数式组件的样式靠这一行 dts: src/auto-imports.d.ts // 生成类型声明编辑器才有提示 }), Components({ resolvers: [ElementPlusResolver({ importStyle: sass })], // 用 sass 变量覆盖主题时必须改成 sass dts: src/components.d.ts, dirs: [src/components] // 自己写的组件也自动注册 }) ] })几处参数值得单独说明。imports: [vue, vue-router]让ref、reactive、onMounted、useRouter这些常用 API 免手写导入代价是新人读代码时看不出它们从哪来团队里最好统一约定。resolvers在AutoImport里必须再写一遍ElementPlusResolver()因为ElMessage是在脚本里被调用的模板编译器看不到它只有AutoImport能接管它的导入和样式。importStyle默认是css一旦你要改主题色就必须换成sass否则覆盖变量不生效。生成的src/auto-imports.d.ts和src/components.d.ts要纳入版本管理并在jsconfig.json的include里带上否则 VS Code 里会出现一片红色波浪线。这两个文件是自动生成的别手改改配置后重启一次开发服务器就会重新生成。3.3 主题色覆盖sass 变量与注入顺序Element-Plus 的主题定制走的是 sass 变量forward ... with (...)覆盖文件必须比组件样式先被处理顺序错了就是白改。// src/styles/element/index.scss forward element-plus/theme-chalk/src/common/var.scss with ( $colors: ( primary: (base: #2f6fed), success: (base: #22a06b) ), $border-radius: ( base: 4px ) );这段的意思是把 Element-Plus 的变量文件转发出去同时用with覆盖其中的$colors映射。注意$colors是个嵌套 map只写primary: #2f6fed会编译报错必须写成(base: 值)的结构。想改哪个变量去node_modules/element-plus/theme-chalk/src/common/var.scss里搜对应名字别凭记忆写。// vite.config.js 的 css 段 css: { preprocessorOptions: { scss: { // 每个 scss 文件编译前自动注入保证变量先于组件样式生效 additionalData: use /styles/element/index.scss as *;, api: modern-compiler // sass 1.80 建议显式声明新编译器 API } } }additionalData的作用是在每个.scss文件顶部隐式插入一行use这样组件内部的样式文件也能读到被覆盖后的变量。as *表示不建命名空间直接注入。api: modern-compiler是为了压掉 sass 新版对 legacy API 的弃用警告如果你的 sass 版本较旧这一行可以去掉。改完这些必须重启开发服务器热更新对vite.config.js不生效。4. 用 Mock.js 接管接口数据模板、分页延迟与 CRUD 的四个写法假数据这件事写起来简单写对不容易。随手Mock.mock({...})返回一个固定对象页面能亮但翻页不生效、搜索不过滤、loading 一闪而过等到真接口接进来前端逻辑全是白写的。这一章按「插件选型 → 数据模板 → 写操作 → 与请求层对接」的顺序铺开目标是把假接口做得跟真接口一样难用——该有的分页、延迟、错误码一个不少。4.1 为什么不直接在 main.js 里 import Mock在入口import Mock from mockjs然后Mock.mock(/api/user/list, ...)是网上流传最广的写法也是坑最深的写法。它靠改写XMLHttpRequest工作而axios在新版本里默认走fetch适配器两者对不上表现为「Mock 规则写了请求照样发出去」。就算强行让 axios 走 XHR这套逻辑也会被打进生产包上线后产生一堆没人能解释的假数据。vite-plugin-mock的思路不一样它在 Vite 开发服务器上挂中间件请求先经过它命中规则就返回没命中才继续往下走。业务代码完全不知情axios该怎么写还怎么写。切真接口时把插件关掉即可没有残留。// vite.config.js 片段 import { viteMockServe } from vite-plugin-mock viteMockServe({ mockPath: mock, // mock 文件目录相对项目根目录 localEnabled: true, // 开发环境开启2.x 版本这个字段叫 enable prodEnabled: false, // 生产环境关闭避免假数据进产物 logger: true // 控制台打印命中的规则排查时非常有用 })mockPath必须和实际目录名一字不差写成./mock或mock/在部分版本上也能识别但我建议就用mock少一个变量。logger: true是排查利器请求没生效时先看终端有没有打印对应规则没有就是路径没匹配上有打印但页面数据不对那就是返回结构的问题。prodEnabled保持false除非你确实要做纯前端演示的静态站点。4.2 数据模板与分页一次造 186 条固定数据数据池要在模块加载时一次性造好并存在模块作用域里而不是每次请求都重新Mock.mock生成。后者的问题很直观翻到第二页第一页的数据全变了删掉的记录刷新一下又回来你会误以为是组件状态有问题。// mock/user.js import Mock from mockjs const { Random } Mock // 模块加载时造一次186 条故意不是 10 的整数倍用来验证最后一页 const userPool Array.from({ length: 186 }).map((_, i) ({ id: i 1, name: Random.cname(), account: user_${String(i 1).padStart(4, 0)}, dept: Random.pick([研发中心, 市场部, 财务部, 客服中心]), status: Random.pick([1, 1, 1, 0]), // 权重倾斜让「禁用」成为少数 createdAt: Random.datetime(yyyy-MM-dd HH:mm:ss) })) export default [ { url: /api/user/list, method: get, timeout: 300, // 模拟网络耗时loading 才看得见 response: ({ query }) { const page Number(query.page) || 1 const size Number(query.size) || 10 const keyword (query.keyword || ).trim() const filtered keyword ? userPool.filter(u u.name.includes(keyword) || u.account.includes(keyword)) : userPool const start (page - 1) * size return { code: 0, message: ok, data: { total: filtered.length, list: filtered.slice(start, start size) } } } } ]Array.from({ length: 186 })配map的写法比for循环干净padStart(4, 0)让账号定长方便你在搜索框里验证前缀匹配。Random.pick([1, 1, 1, 0])用重复元素做权重这是 Mock.js 里最省事的加权手段比写条件判断直观。timeout: 300别省没有延迟的话v-loading根本来不及渲染你会以为 loading 指令没生效。response回调收到的参数是{ url, body, query, headers }query已经是解析好的对象不用再自己拆字符串。返回值直接作为 JSON 响应体所以统一包一层{ code, message, data }是必要的——后面 axios 拦截器要按这个结构做业务判断。提示url写成/api/user/:id这类带参形式能匹配上但回调里不给解析后的 params得自己从url末尾切。接口简单时干脆用 query 传 id少一层解析。4.3 写操作与延迟让 loading 和空状态真的能被看到只做列表接口的 Mock 是不完整的新增、编辑、删除缺一个你都没法验证表单校验、二次确认、列表刷新这一整条链路。写操作的 Mock 要注意两件事直接改userPool这个内存数组让改动在后续查询里可见返回结构跟真实后端约定保持一致。// mock/user.js 中追加到导出数组 { url: /api/user, method: post, timeout: 200, response: ({ body }) { // body 已被插件解析为对象无需 JSON.parse if (!body.account) { return { code: 40001, message: 账号不能为空, data: null } } const exists userPool.some(u u.account body.account) if (exists) { return { code: 40002, message: 账号已存在, data: null } } const id userPool.length 1 userPool.unshift({ id, status: 1, createdAt: Random.now(yyyy-MM-dd HH:mm:ss), ...body }) return { code: 0, message: created, data: { id } } } }, { url: /api/user/:id, method: delete, timeout: 150, response: ({ url }) { const id Number(url.split(/).pop()) // 路径参数得自己切 const idx userPool.findIndex(u u.id id) if (idx -1) return { code: 40004, message: 记录不存在, data: null } userPool.splice(idx, 1) return { code: 0, message: deleted, data: null } } }故意留了两个错误分支账号为空、账号重复这是有意的真实接口一定会返回业务错误码前端ElMessage.error的分支不测一遍等真接口上来才发现自己只写了成功路径。unshift而不是push是为了让新增的记录出现在列表第一页省得你翻页去找。4.4 axios 封装与 mock 前缀的对接约定Mock 规则里的url和 axios 实例的baseURL必须能拼成同一个路径这是最容易错位的地方。约定是baseURL设为/apiMock 规则以/api开头两边都不带域名。// src/utils/request.js import axios from axios const service axios.create({ baseURL: import.meta.env.VITE_API_BASE || /api, timeout: 10000 }) service.interceptors.response.use( (res) { const { code, message, data } res.data if (code ! 0) return Promise.reject(new Error(message || 业务异常)) return data // 业务层拿到的直接是 data不用层层 .data.data }, (err) Promise.reject(err) ) export default service拦截器里把code ! 0统一转成 rejected Promise业务代码就只需要try/catch或者.catch不必每个页面判断一次状态码。import.meta.env.VITE_API_BASE走环境变量切真接口时改.env文件即可不用动代码。这套约定的好处是 Mock 与真实后端的差异被压缩到一个前缀上后面第 6 章的开关方案才有地方落。5. 排查清单联调阶段最容易翻车的五个现场前四章的配置跑通之后日常开发里剩下的事基本就是「为什么没按我想的来」。这一章把这类问题按现象归了五类每条都写清原因和处置方式。顺序按出现频率排第一条几乎每个新拉项目的人都会撞上一次。5.1 现象接口 404 或返回了 index.htmlMock 规则像是不存在原因通常是路径没对上。vite-plugin-mock的匹配是前缀精确匹配Mock 里写/api/user/list请求发到/api/user/list/末尾多一个斜杠就不命中method写成大写GET在部分版本上也匹配不到。还有一种情况是 axios 的baseURL已经带了/api请求路径里又写了一遍/api/user/list实际发出的是/api/api/user/list。解决方式很机械开logger: true重启开发服务器看终端有没有打印规则命中没打印就检查mockPath目录名、规则url前缀、method大小写三处打印了但页面还是错的去 Network 面板看实际请求 URL和规则逐个字符比对。改完mock/下的文件后建议重启一次热更新对 Mock 文件的覆盖在部分版本上不彻底。5.2 现象npm run build之后部署所有接口 404这是prodEnabled开着但没配生产 Mock 的典型症状或者反过来——prodEnabled: false产物里根本没有 Mock 中间件请求打到静态服务器上自然 404。前者的处置是关掉prodEnabled后者说明你部署的环境里缺后端服务需要配一个反向转发把/api指向真实服务地址。要特别提醒一点prodEnabled: true必须配合在main.js里引入createProdMockServer否则插件只是把 mock 文件打进包并不会启用。这条路只适合做纯前端演示站点正经项目别走产物里带着一堆假数据是审计上的隐患。5.3 现象ElMessage弹出来了但没有背景色和图标像一段裸文字函数式组件不走模板编译unplugin-vue-components看不到它只有AutoImport的 resolver 能接管。如果你只在Components里配了ElementPlusResolverAutoImport那行漏了就会出现「组件正常、弹窗裸奔」的割裂状态。处置是在AutoImport的resolvers里补上ElementPlusResolver()。还有一种更粗暴的兜底在main.js里手动import element-plus/es/components/message/style/css能解决但会把样式引入散落到各处不建议长期这么干。改完记得删掉node_modules/.vite缓存再重启Vite 的依赖预构建缓存有时候会咬人。5.4 现象表格里改了scope.row.xxx视图不更新多半是响应式丢了。props解构、reactive对象整体替换、把数组元素取出来单独存变量再改这三种写法都会丢响应性。Vue3 的reactive靠 Proxy 追踪一旦你把属性值取出来赋给普通变量追踪链就断了。处置改对象属性而不是替换对象比如row.status 0可以row { ...row, status: 0 }在部分场景下不行需要解构props时用toRefs或const { list } toRefs(props)数组整体替换用list.value newList而不是list.value.length 0后逐个 push。这类问题在 Element-Plus 表格 弹窗编辑的组合里出现频率最高。5.5 现象本地一切正常同事拉下来跑接口全挂环境差异八成出在.env文件没进版本库或者端口不一致。.env.local默认被.gitignore忽略你在里面配了VITE_API_BASE同事那边就是undefinedbaseURL退回默认值请求打到别处。端口方面骨架里如果写死了5173同事机器上被占就会自动换端口而某些写死地址的地方不会跟着变。处置是把「所有人都需要的变量」放.env.development并提交只把个人差异放.env.local端口统一在vite.config.js的server.port里声明配合strictPort: true让冲突直接报错而不是静默换端口。静默换端口这件事看起来友好实际是把问题推迟到你最不想排查的时候。6. 假数据切真接口一个环境变量加一次构建自检切换的开关用一个环境变量就够别搞太复杂。.env.development里写VITE_USE_MOCKtrue.env.production里不写或写falsevite.config.js里读它决定localEnabled。这样本地开发默认走 Mock构建产物里一定没有 Mock逻辑只有一处。// vite.config.js import { loadEnv } from vite export default defineConfig(({ mode }) { const env loadEnv(mode, process.cwd()) const useMock env.VITE_USE_MOCK true return { plugins: [ vue(), // ...其余插件 viteMockServe({ mockPath: mock, localEnabled: useMock, prodEnabled: false }) ] } })场景VITE_USE_MOCKMock 行为接口指向本地纯前端开发true命中规则直接返回不发出真实请求本地连后端联调false插件不拦截VITE_API_BASE指向的服务构建产物不设置完全不参与部署环境的反向转发验证方法我给一个笨但有效的执行npm run build之后在dist/assets里搜一下mockjs或你写的假数据特征串比如研发中心搜到就说明有东西漏进产物了回头查是不是哪个文件被静态import进了业务代码。这个动作我一般放在提交前三十秒的事。参数上还有一个细节VITE_API_BASE在开发时是/api靠 Vite 的server.proxy转发到后端地址在生产时改成真实域名或留空走同源。两套环境用同一个变量名代码里就永远只有一处引用改起来不会漏。从那以后我每次接中后台项目都先把 Mock 开关和接口前缀这两件事在第一天定死再动手写页面后面省下来的排查时间远超过这点准备成本。希望帮到你。本文还有配套的精品资源点击获取