Vue3+Vite+Element Plus后台开发实战:从搭建到部署
2026/9/15 16:18:58 网站建设 项目流程

干这行的都清楚,这两年接后台管理系统、中台项目,绕不开一个组合:Vue3 + Vite + Element Plus。团队新来的同学经常问我怎么上手这套技术栈,社区里也每天有人问“从 Vue2 怎么切到 Vue3”“Vite 和 Webpack 到底差在哪”“Element UI 能不能直接升级”。这些问题我实际项目里都遇到过,今天就结合这几年做后台系统的经验,从环境搭建、项目初始化、UI 组件迁移到工程化配置和常见故障排查,把这套组合完整捋一遍。

这套组合要解决的核心问题,说白了是三件事:Vue3 把逻辑复用和组织方式做了重构,Vite 把开发调试的启动速度提了一个量级,Element Plus 把后台管理系统高频使用的表格、表单、弹窗、菜单这些组件打包好了直接给你调。适合谁看?刚接触 Vue3 的初中级前端、正在做技术选型的技术负责人,以及准备面试被问 Vue3 原理和 diff 算法的开发。内容偏实战,能直接照着操作。

1. 技术选型:为什么这套组合成了后台开发的新标配

1.1 Vue3 比 Vue2 强在哪:Composition API 和响应式重构

先说 Vue3 本身。Vue2 时代大家写业务逻辑,最头疼的就是 options API 的代码组织方式——data、computed、methods、watch 各占一块,一个功能相关的代码被拆得七零八落。一个复杂的表单页面写下来,data 里堆了十几个字段,methods 里二十多个方法,你根本分不清哪个方法对应哪个功能模块。Vue3 的 Composition API 把单个功能的响应式数据、计算属性和方法聚合在一起,代码内聚性强了不止一点。

比如一个搜索列表页,Vue2 里你需要在 data 里定义 searchForm、tableData、loading、pagination,在 methods 里写 handleSearch、handleReset、fetchList。Vue3 的 setup 里就可以把这一整套搜索逻辑抽成一个 useSearchList 函数,组件里引一下就行。这个改动对后台管理系统尤其友好,因为后台页面高度相似,列表页、表单页、详情页都是套路化的,逻辑复用能力就是开发效率。

再有就是响应式原理。Vue2 用 Object.defineProperty 做数据劫持,有几个硬伤:数组下标修改检测不到、新增对象属性不触发更新,所以 Vue2 才搞出 Vue.set 和 $forceUpdate 这种补丁方案。Vue3 改用 Proxy,代理整个对象,增删改查都能拦截,数组方法也不需要额外 hack。这一层是 Vue3 性能提升的根本。

在选型层面还有一个现实因素:Vue2 官方已停止维护,新项目不选 Vue3 就是在给自己埋技术债。企业项目对长期维护的要求高,选主流的、社区活跃的版本是稳妥做法。

1.2 Vite 比 Webpack 快在哪:开发体验的降维打击

Webpack 开发服务器冷启动一个大型后台项目,十几秒甚至几十秒是常态,改一行代码热更新时间也要等两三秒。Vite 解决的就是这个痛点。

Vite 启动快的原因在于它把模块分为依赖和源码两类。依赖用 esbuild 预打包,esbuild 是 Go 写的,比 Webpack 的 JS 打包器快几十倍。源码则利用浏览器原生 ES Module 支持,按需加载,浏览器请求哪个模块 Vite 就实时编译哪个模块,不需要像 Webpack 那样把所有模块全部打包完才能启动开发服务器。你在代码里点开一个页面,Vite 只编译那个页面依赖的模块,首屏自然快。

生产构建方面,Vite 默认用 Rollup,对于大多数中后台项目来说产物质量和构建速度都是够的。Vite 5 之后构建性能也有明显提升。

这里说句公道话,Webpack 并不是一无是处——它的生态成熟、插件丰富、老项目存量巨大。但新项目特别是 Vue3 项目,Vite 已经是社区绝对主流,官方脚手架默认就是 Vite,没有理由逆势选 Webpack。

1.3 Element Plus 和 Element UI 的关键差异

Element Plus 是 Element UI 的 Vue3 版本,但不是简单换个壳,组件 API 和内部实现都做了重构:

  • 组件升级:新增了更多实用组件,比如虚拟化表格 el-table-v2、描述列表 el-descriptions,这些在后台管理系统里很实用
  • 样式定制:全面切换为 CSS 变量,改主题色不用再覆盖 SCSS 变量了,直接在 :root 里设 --el-color-primary 就行
  • 按需引入:配合 unplugin-auto-import 和 unplugin-vue-components,可以做到按需自动引入组件和 API
  • TypeScript 支持:Element Plus 全程用 TS 编写,配合 Vue3 + TS 的项目类型提示非常友好

Element UI 官方也明确不会再支持 Vue3,所以 Vue3 项目里没有纠结的必要——直接用 Element Plus。

2. 环境准备与项目初始化实操

2.1 Node.js 版本与 Vue3 安装环境配置

先解决环境问题。很多人在这步踩坑,主要是 Node.js 版本太低。

Vite 3 以上要求 Node.js 14.18+ / 16+,Vite 5 要求 18+。现阶段我建议直接上 Node.js 18 或 20 LTS 版本。你可以在终端执行 node -v 检查当前版本,如果低于 16,去官网下载对应安装包覆盖安装。Windows 用户注意一点:安装 Node 过程中会自动配置 PATH,如果之前装过旧版本,尽量走卸载重装,避免环境变量残留导致 node 命令指向旧版本。

Node 装好后,检查 npm 版本,npm install -g npm 更新到 9 以上。然后安装 pnpm,我个人推荐团队统一用 pnpm 来管理依赖:

npm install -g pnpm pnpm -v

为什么用 pnpm?最大的好处是依赖存储是全局单例的,多个项目共享同一份缓存,安装速度快,磁盘占用小。在 monorepo 或者同时开发多个前端项目的时候优势非常明显。

2.2 用 Vite 从零创建 Vue3 项目

环境就绪后,创建项目很简单。我习惯用官方脚手架 create-vue,它会自动配置好 Vue3 + Vite + TypeScript + ESLint 等基础工程化能力:

npm create vue@latest

执行后按提示操作:

  • 输入项目名称
  • 选择是否启用 TypeScript:建议启用,后台系统类型约束能帮你提前发现问题
  • 选择是否启用 JSX:如果你的项目需要在 .vue 文件里写 JSX,可以启用
  • 选择是否启用 Vue Router、Pinia:后台系统基本都要用,直接选上
  • 其他如 Vitest、ESLint、Prettier 按团队需要选,我一般全开

脚手架会自动创建目录结构并安装依赖。初始化完成后进入项目目录,启动开发服务器:

cd my-project npm install npm run dev

启动后 Vite 会在终端输出本地访问地址,默认 http://localhost:5173。看到一个 Vue 默认首页,说明项目骨架就搭好了。

很多初学者在这个环节最容易犯的错误是把 create-vue 和 create-vite 搞混。create-vite 是纯 Vite 模板,不会帮你装 Vue Router 和 Pinia,需要手动配置。create-vue 是 Vue 官方推荐的脚手架,直接生成完整的 Vue3 工程。建议直接用 create-vue。

2.3 Element Plus 安装与自动按需引入

项目创建完成后,安装 Element Plus:

pnpm add element-plus

完整引入方式很简单,在 main.ts 里全部注册:

import { createApp } from 'vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' const app = createApp(App) app.use(ElementPlus) app.mount('#app')

全量引入在开发小工具或内部系统时可以接受,但对于大型外网项目,包体积会明显偏大。我更推荐按需自动导入方案。安装两个插件:

pnpm add -D unplugin-auto-import unplugin-vue-components

然后在 vite.config.ts 中配置:

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' export default defineConfig({ plugins: [ vue(), AutoImport({ resolvers: [ElementPlusResolver()] }), Components({ resolvers: [ElementPlusResolver()] }) ] })

配置完成后,在组件模板里直接写<el-button><el-table>,插件会帮助你自动导入组件和对应样式,代码里就不再需要手动 import Element Plus 的组件和 CSS 了。对于 ElMessage、ElMessageBox 这类函数式 API,AutoImport 插件同样会帮你自动引。

这里有个实际使用建议:按需引入后,组件库的暗黑模式、变量覆盖等高级功能可能需要额外配置。如果你只用默认主题,自动按需引入完全够用。

3. Element UI 迁移 Element Plus 的踩坑实录

3.1 迁移前必须知道的几个变化

很多老项目是 Vue2 + Element UI,想升级到 Vue3,首先要明白一件事:Element UI 和 Element Plus 不是直接替换的。组件名称、API、事件、插槽都有变化,如果你只是在 package.json 里把 element-ui 换成 element-plus,然后把导入语句改一下,项目会直接跑不起来。

迁移前建议先梳理项目里用了哪些组件和 API,逐一对查官方迁移文档。我实际踩过坑的集中在几个高频组件上:

  • el-dialog:原本的visible.sync改成了v-modeltitle插槽的作用域也变了
  • el-table:slot-scope="scope"改成默认插槽配合#default="scope",操作列的写法完全变了
  • el-pagination:事件名从current-change的触发时机和参数保持一致,但布局设置layout的选项略有差异
  • 表单校验:rules里的type: 'number'校验,有时需要显式设置trigger才能触发表单验证

在迁移计划里,我一般会按模块来做,先迁移公共组件和工具函数,再迁移页面。每迁移完一个页面就执行一次构建和冒烟测试,避免一口气全改完再调错。

3.2 组件和 API 破坏性变更整理

这里列一张我在实际迁移中总结的高频变更表:

变更点Vue2 + Element UIVue3 + Element Plus
弹窗控制visible.syncv-model
插槽语法slot="name" / slot-scope="scope"#name / #default="scope"
尺寸大小size="mini / small / medium / large"size="small / default / large"
图标i class="el-icon-edit"el-icon 组件配合 svg 图标
按钮 loadingloading 属性loading 属性,但事件回调推荐用 promise
表单事件@submit.native.prevent@submit.prevent 直接可作用于 el-form
全局 APIthis.$message({...})ElMessage({...}) 或 this.$message 需注册
颜色主题覆盖 SCSS 变量CSS 变量 --el-color-primary

有一个我之前忽略的坑是全局注册的this.$message。Element UI 里很多项目是挂到原型上用的,Element Plus 里依然可以通过app.config.globalProperties挂载,但类型提示需要额外声明,不如直接引入 ElMessage 函数。

另外,Element Plus 的按需自动引入方案和手动完整引入,在使用函数式组件时要特别注意:ElMessage 的样式在按需构建下如果没有被自动导入,会出现弹窗出现但没有样式的问题。解决方法是把ElMessage的样式手动引一下,或者在组件插件的 resolver 配置里把importStyle参数加上。

3.3 修改组件样式的几种实用方案

后台管理系统免不了要定制主题色、圆角、间距。升级到 Element Plus 后,修改样式的方式也从 SCSS 变量变成了 CSS 变量。

主题色自定义是在全局样式文件里重写 CSS 变量:

:root { --el-color-primary: #409eff; --el-color-success: #67c23a; --el-border-radius-base: 6px; }

这里有一个巨坑:我在项目里用了 postcss-pxtorem 做移动端适配,发现图表 echarts 的字体和尺寸没有被转成 rem。排查下来,原因是 pxtorem 默认只处理项目源码里的 CSS,处理不了 echarts 内部通过 canvas 绘制的内容——canvas 里画的文字不是 CSS 字体,rem 对它没有意义。如果是大屏项目用 echarts 配合 rem 适配,正确做法是把 rem 转成像素绑定到 html 的 font-size 上,绘图时通过动态计算 px 值来设置文字大小。

再比如修改 tabs 标签页样式,Element Plus 的 tabs 类名和 Element UI 有差别,官方样式用 BEM 命名,覆盖的时候需要看清楚类名结构。推荐的做法是给加载组件的容器加一个自定义 class 作为命名空间,再配合:deep()穿透到子组件内部选择器:

<style scoped lang="scss"> .my-tabs { :deep(.el-tabs__item) { color: #666; &:hover { color: var(--el-color-primary); } &.is-active { font-weight: 600; } } } </style>

记住一点:Element Plus 重构后,很多样式细节都收敛到了 CSS 变量,优先设置变量而不要强行去改组件内部类名。改内部类名维护成本高,且对升级组件库不友好。

4. 工程化配置与典型问题排查

4.1 多环境构建:dev、test、prod 怎么配置

后台项目一般至少有三套环境:开发环境、测试环境、生产环境。环境变量和接口地址不能写死,Vite 里通过.env文件管理和--mode参数切换构建环境:

# .env VITE_APP_TITLE=your-project # .env.development VITE_APP_BASE_API=/api VITE_APP_ENV=development # .env.test VITE_APP_BASE_API=https://test-api.example.com VITE_APP_ENV=test # .env.production VITE_APP_BASE_API=https://api.example.com VITE_APP_ENV=production

构建命令:

{ "scripts": { "dev": "vite", "build:test": "vite build --mode test", "build:prod": "vite build --mode production" } }

这里补充一个细节:vite 的--mode默认是production,所以当你执行vite build --mode test时,Vite 会加载.env.test文件来覆盖默认环境变量。而在代码里通过import.meta.env.VITE_APP_BASE_API读取到的就是对应环境的值。环境变量名的前缀VITE_决定了变量是否会暴露给客户端代码,如果你在.env定义的变量没有这个前缀,默认是访问不到的。这是 Vite 暴露给前端环境变量的安全边界。

实际项目里,测试环境联调时接口地址变化频率很高,把接口地址收敛到一个配置文件里,通过环境变量分发,比在组件里硬编码更快、更安全。

4.2 列表懒加载和嵌套 iframe 点击冲突

列表懒加载在后台管理系统里很常见,Element Plus 的 table 支持el-table结合v-infinite-scroll做滚动加载。一个常见的实现思路是:监听分页数据滚动,当滚动到底部时重新请求接口追加数据。

<el-table v-infinite-scroll="loadMore" :data="tableData" height="500" v-loading="loading" > <el-table-column prop="name" label="名称" /> </el-table>
const loadMore = () => { if (page.value * size.value >= total.value) return page.value++ fetchList() }

这里有个小坑:v-infinite-scroll绑定的容器必须是可滚动的元素。如果 el-table 高度没有设置,表格本身不产生滚动条,指令永远不会触发加载。表格高度固定之后,才能形成滚动容器。

再来说 iframe 点击问题。很多后台系统会嵌入大屏或者其他系统页面,比如地图大屏、第三方报表。我在一个可视化大屏项目里遇到一个很头疼的问题:外层 div 点击事件无法触发,因为内层 iframe 会拦截所有鼠标事件,父页面的 click 根本不会跑到外层 div 上。这个问题其实是 iframe 的天然行为——iframe 是独立的文档上下文,鼠标进入 iframe 区域之后,父页面元素收不到任何事件。

可行的处理方案,一是 iframe 盖一层透明的 div,让这层 div 去响应点击,这是一种笨办法但对拦截用户点击有用;二是让父页面和 iframe 页面之间用 postMessage 通信,iframe 页面检测到自己的点击事件后主动向父页面发消息;三是在 iframe 外层加遮罩层,把点击事件转移到遮罩上。具体选哪种取决于你的场景:如果需要在 iframe 内部正常操作,不能简单盖一层透明的,只能用 postMessage 的方式做事件桥接。

4.3 Nginx 部署 Vue3 项目的配置要点

部署这一块是面试和实战都绕不开的。Vue3 项目构建完是一个纯静态资源目录,用 Nginx 部署很简单,但有几个坑必须在配置里处理:

server { listen 80; server_name your-domain.com; root /usr/share/nginx/html; index index.html; # 前端路由 history 模式核心配置 location / { try_files $uri $uri/ /index.html; } # 静态资源缓存策略 location /assets/ { expires 30d; add_header Cache-Control "public, immutable"; } # 接口反向代理 location /api/ { proxy_pass http://backend-server:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

第一个易错点:如果你的前端路由用的 HTML5 history 模式(即路由地址中没有 #),用户直接访问 /some/route 刷新页面时,如果 Nginx 没有try_files配置,会返回 404。配置里try_files $uri $uri/ /index.html就是为了把所有非文件请求回退到 index.html,让前端路由接管。

第二个易错点:proxy_pass路径里的/api/要不要带,直接决定了后端接口的路径拼接是否正常。这里有带/api/时会把原始请求中的 /api/ 路径也传递过去;如果不带斜杠,会把匹配前缀部分剥离。遇到接口 404 时先检查这个。

第三个易错点:Windows 服务器上用 Nginx 部署 Vue3 项目时,路径分隔符、编码格式可能导致静态资源 404。Windows 下 Nginx 的路径最好不要带中文,且统一使用/分隔符。

5. 进阶能力:Vue3 核心特性和状态管理

5.1 computed 和 Composition API 的正确用法

过了基础阶段,Vue3 的生态里最重要的就是 Composition API 的思维转换。热词里搜“vue3 computed”的人不少,我展开讲一下。

跟 Vue2 的 computed 选项相比,Vue3 里它是一个 API 函数:

import { computed, ref } from 'vue' const keyword = ref('') const total = ref(100) const isSearchDisabled = computed(() => { return keyword.value.trim().length === 0 })

这里有一个关键点:computed 内部如果依赖 ref,读取时必须带.value。在模板里不需要带,因为模板会自动解包 ref,但在 setup 内部必须带。很多从 Vue2 直接转 Vue3 的同学都在这上面花过时间。

computed 和 watch 的选择上,一个实用经验是:计算属性用于“从已有数据派生新数据”,watch 用于“数据变化后执行副作用”。比如搜索防抖、请求数据这类操作用 watch;计算按钮禁用状态、过滤列表、计算分页总数用 computed。

Composition API 的核心优势在于逻辑复用。抽一个图表 Hook,可以把 echarts 的初始化、数据请求和 resize 监听统一封装:

function useEcharts(domRef, option) { let chart onMounted(() => { chart = echarts.init(domRef.value) chart.setOption(option.value) }) onBeforeUnmount(() => { if (chart) chart.dispose() }) return { chart } }

这个 Hook 放在大屏、报表、详情面板都能复用,而且是按需引入,不会给你的项目增加不必要的包体积。

5.2 Pinia 替代 Vuex 的迁移实践

后台系统的全局状态管理,以前默认 Vuex,现在新的 Vue3 项目基本都用 Pinia。Pinia 保留了 Vuex 的理念,但把 API 精简到了极致:没有 mutations,只有 state、getters、actions。

import { defineStore } from 'pinia' export const useUserStore = defineStore('user', { state: () => ({ token: '', userInfo: {} }), getters: { isLogin: (state) => !!state.token }, actions: { async login(loginForm) { const res = await loginApi(loginForm) this.token = res.data.token setToken(this.token) }, logout() { this.token = '' this.userInfo = {} removeToken() } } })

在组件里用的时候,不需要 mapState、mapActions 之类的辅助函数,直接引用:

import { useUserStore } from '@/store/user' const userStore = useUserStore() const handleLogout = () => { userStore.logout() }

从 Vuex 迁移到 Pinia 我踩到的一个细节是模块组织。Vuex 里通过 modules 嵌套模块,Pinia 里每个 store 独立定义,更扁平的写法让代码查找更容易。后台系统里我一般按业务模块拆 store:user、app、permission、tabs。tabs store 管理多标签页的缓存和关闭逻辑,这在后台系统里很好用。

5.3 Diff 算法和渲染性能优化

面试中高频考点之一就是 Vue3 的 diff 算法。我去讲的时候会用一个形象类比:Vue2 的 diff 是双端比较,Vue3 在此基础上增加了“最长递增子序列”算法优化,通过减少移动次数提升性能。

Vue3 的 diff 过程可以概括为:对比新旧虚拟节点;相同类型的节点直接复用 DOM 元素,只更新变化的部分;对于动态子节点数组,Vue3 会给节点打 patchFlag 标记(按需更新的动态内容),整体静态节点在编译阶段被跳过比对,这就是为什么 Vue3 更新性能比 Vue2 快的核心原因。

在业务里,使用合理key能利用 diff 加速。列表渲染时直接给 key 加 index 是最常见的反例——删除中间项会导致后边所有项被重建。更合理的是使用 item.id 这类稳定且唯一的业务标识。

另一个性能优化点是v-memo指令。当你按条件渲染大量数据时,v-memo可以对一组依赖做记忆,依赖未变时直接跳过渲染。这个指令在接口返回大数据量表格时能明显减少渲染开销。

6. 一些额外的心得和建议

Vue3 + Vite + Element Plus 这套技术栈的生态,这两年已经很成熟了,后台管理系统基本是开箱即用。不管你是从 Vue2 迁移过来,还是直接新学 Vue3,我给出的路线是:先跑通官方脚手架,理解 setup 语法和 Composition API 的代码组织方式,然后在项目里逐个实践 Element Plus 的常用组件,同时把 Vite 的配置从零到一读一遍。

团队决策层面我也给个私货建议:新项目如果还没定方案,强烈建议直接用 TypeScript。后台管理系统数据类型复杂、接口字段变更频繁,TS 能帮你挡住一大半低级错误。Element Plus、Pinia 对 TS 的支持都做得非常好,配合起来体验顺畅。

如果你正在准备面试,Vue3 相关的热点一定要吃透这几块:响应式原理对比(Proxy vs Object.defineProperty)、diff 算法优化、Composition API 和 options API 的使用场景、组件通信方式、Pinia 状态管理。热搜词里反复出现的“vue3 面试题”基本都会往这几个方向扎堆。

最后再说一个我实际项目里的体验:选了 Vue3 + Vite 之后,如果团队里有同学还是习惯写 Vue2 语法,不要强行一步到位全部改成 Composition API。Vue3 完全兼容 options API,在 setup 里也可以用 data、methods 这些旧写法。比较稳妥的过渡方式是:新写页面优先用 Composition API,老页面迁移时再顺手改。慢慢来,代码组织方式的改进比框架版本升级更重要。

我个人在实际项目里最满意的一个改动是,把登录态、权限、多标签页、缓存、接口请求都统一收口到 Pinia 的 store 里,页面组件里只剩数据渲染和事件处理。这套做法跑了一年多,新同学接手代码时上手速度明显比老项目快。如果你想给自己的后台系统提效,不妨从这个角度开始:把公共逻辑尽可能抽走,组件只做它该做的事。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询