Vue3 + Element Plus + Vite 企业级后台框架搭建全流程

项目写了三个月,目录乱成一锅粥,找个组件要翻半天------问题不在你,在一开始没搭好架子。这篇文章给你一套拿来即用的企业级框架模板。


一、前言

很多开发者拿到 Vue3 之后第一反应是:npm create vue@latest,跑起来就开干了。但企业级项目不是 Demo,如果开局没定好规矩,写到第三个月会发现:

  • 目录结构一团乱,找个组件要翻半天
  • ESLint 和同事的不一致,提交一次冲突一次
  • 环境变量东一个西一个,打包上线才发现接口地址配错了

这篇文章带你从零搭建一套可落地的企业级 Vue3 后台框架------不是玩具项目,而是我过去一年在生产环境中迭代出来的最佳实践。


二、技术选型与版本踩坑

2.1 选型理由

技术 版本 选型理由
Vue 3 3.5.x Composition API + <script setup> 是未来,Vue2 已经不推荐新项目使用
Vite 6.x 开发服务器秒启动,HMR 极快,Webpack 项目迁移过来开发体验提升明显
Element Plus 2.9.x 中文生态最好的 Vue3 UI 库,企业级组件覆盖完整
TypeScript 5.x 类型安全是多人协作的生命线,不解释了
Pinia 2.x Vuex 的官方替代,TS 友好,API 简洁
Vue Router 4.x Vue3 配套,支持组合式 API 路由守卫
unplugin-auto-import 0.18.x 自动导入 Vue/Element Plus API,告别重复 import
unplugin-vue-components 0.28.x Element Plus 组件按需导入,打包体积减少 60%+

2.2 一个我踩了两天的坑

Element Plus 版本兼容性问题 :去年我把项目从 Element Plus 2.2 升到 2.9,部分组件的 v-model 绑定方式变了,表单验证规则也改了。升级前务必读一遍 CHANGELOG,尤其是 Breaking Changes 部分。

经验之谈:锁定依赖版本号 ,别用 ^ 前缀。"element-plus": "2.9.7""^2.9.7" 安全得多。


三、项目初始化

3.1 创建项目

bash 复制代码
# 使用官方脚手架创建
npm create vue@latest admin-framework

# 选项配置
# ✔ TypeScript? ... Yes
# ✔ JSX Support? ... No(个人偏好,看团队)
# ✔ Vue Router? ... Yes
# ✔ Pinia? ... Yes
# ✔ Vitest? ... Yes
# ✔ ESLint? ... Yes
# ✔ Prettier? ... Yes

cd admin-framework
npm install

3.2 安装企业级依赖

bash 复制代码
# UI 框架 + 自动导入
npm install element-plus @element-plus/icons-vue

# 自动导入插件(开发依赖)
npm install -D unplugin-auto-import unplugin-vue-components

# 工具库
npm install axios dayjs nprogress

# 类型声明
npm install -D @types/node @types/nprogress

3.3 Vite 配置(核心)

ts 复制代码
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'node:url'
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()],
      imports: ['vue', 'vue-router', 'pinia'],
      dts: 'types/auto-imports.d.ts'
    }),
    Components({
      resolvers: [ElementPlusResolver()],
      dts: 'types/components.d.ts'
    })
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  server: {
    port: 80,
    host: '0.0.0.0',
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      },
      '/file': {
        target: 'http://localhost:9300',
        changeOrigin: true
      }
    }
  },
  build: {
    outDir: 'dist',
    assetsDir: 'static',
    chunkSizeWarningLimit: 2000,
    rollupOptions: {
      output: {
        manualChunks: {
          'element-plus': ['element-plus'],
          'vendor': ['vue', 'vue-router', 'pinia']
        }
      }
    }
  }
})

关键配置说明

  • manualChunks:把 Element Plus、Vue 核心单独拆包,首页加载体积从 2MB 降到 800KB
  • proxy/file:文件上传服务通常单独部署,这里做了独立代理

四、目录结构规范

这是我在若依框架基础上演进出的目录结构,经过 3 个项目验证:

perl 复制代码
src/
├── api/                    # API 接口层
│   ├── system/             # 系统管理模块
│   │   ├── user.ts
│   │   ├── role.ts
│   │   └── menu.ts
│   └── business/           # 业务模块(按需扩展)
│       └── order.ts
│
├── assets/                 # 静态资源
│   ├── images/             # 图片
│   └── styles/             # 全局样式
│       ├── variables.scss  # SCSS 变量
│       ├── element.scss    # Element Plus 样式覆盖
│       └── index.scss      # 全局样式入口
│
├── components/             # 公共组件
│   ├── FileUpload/         # 文件上传
│   ├── IconSelect/         # 图标选择器
│   ├── ImagePreview/       # 图片预览
│   └── ...
│
├── composables/            # 组合式函数(hooks)
│   ├── useAuth.ts          # 认证逻辑
│   ├── usePermission.ts    # 权限判断
│   └── useTable.ts         # 表格通用逻辑
│
├── directives/             # 自定义指令
│   ├── permission.ts       # v-hasPermi 按钮权限
│   └── debounce.ts         # v-debounce 防抖
│
├── layout/                 # 布局组件
│   ├── index.vue           # 主布局
│   ├── Sidebar/            # 侧边栏
│   ├── Navbar/             # 顶部导航
│   └── TagsView/           # 页签导航
│
├── router/                 # 路由配置
│   ├── index.ts            # 路由入口
│   └── modules/            # 按模块拆分路由
│       ├── system.ts
│       └── business.ts
│
├── store/                  # Pinia 状态管理
│   ├── modules/
│   │   ├── user.ts         # 用户状态
│   │   ├── app.ts          # 应用配置
│   │   └── permission.ts   # 权限状态
│   └── index.ts
│
├── utils/                  # 工具函数
│   ├── request.ts          # Axios 封装
│   ├── auth.ts             # Token 管理
│   └── index.ts            # 通用工具
│
├── views/                  # 页面视图
│   ├── system/             # 系统管理
│   └── business/           # 业务页面
│
├── App.vue
├── main.ts
└── permission.ts           # 路由守卫(全局)

三条铁律:

  1. api/ 目录必须和 views/ 一一对应------找接口和找页面路径一致,降低心智负担
  2. components/ 只放纯公共组件 ------业务组件放各自 views/ 下的 components/ 子目录
  3. 超过 3 个路由模块就拆 modules/ ------一个 index.ts 堆 50 个路由是灾难

五、环境变量与多环境配置

5.1 环境文件规划

bash 复制代码
.env                  # 所有环境共享
.env.development      # 开发环境
.env.production       # 生产环境
.env.staging          # 预发布环境(可选)

5.2 配置内容

bash 复制代码
# .env.development
VITE_APP_TITLE = '系统管理(开发)'
VITE_APP_BASE_API = '/api'
VITE_APP_FILE_URL = '/file'
VITE_APP_ENV = 'development'

# .env.production
VITE_APP_TITLE = 'MqCode'
VITE_APP_BASE_API = '/prod-api'
VITE_APP_FILE_URL = '/file'
VITE_APP_ENV = 'production'
ts 复制代码
// types/env.d.ts - 为环境变量提供类型提示
/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_APP_TITLE: string
  readonly VITE_APP_BASE_API: string
  readonly VITE_APP_FILE_URL: string
  readonly VITE_APP_ENV: 'development' | 'production' | 'staging'
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

5.3 Axios 请求封装

ts 复制代码
// src/utils/request.ts
import axios, { type AxiosInstance, type InternalAxiosRequestConfig } from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getToken } from './auth'

const service: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_APP_BASE_API,
  timeout: 30000,
  headers: { 'Content-Type': 'application/json;charset=utf-8' }
})

// 请求拦截器
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    const token = getToken()
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  (error) => Promise.reject(error)
)

// 响应拦截器
service.interceptors.response.use(
  (response) => {
    const { code, msg, data } = response.data
    if (code === 200) return data

    if (code === 401) {
      ElMessageBox.confirm('登录状态已过期,请重新登录', '系统提示', {
        confirmButtonText: '重新登录',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        location.href = '/login'
      })
      return Promise.reject(new Error(msg))
    }

    ElMessage.error(msg || '请求失败')
    return Promise.reject(new Error(msg))
  },
  (error) => {
    const message = error.response?.data?.msg || error.message || '网络异常'
    ElMessage.error(message)
    return Promise.reject(error)
  }
)

export default service

三个关键设计决策:

  1. response.data 直接返回 data :业务代码不需要每次都写 .data.data,减少嵌套
  2. 401 不直接跳转:弹窗提示用户,避免正在填写表单时被强制跳转导致数据丢失
  3. timeout 设为 30s:企业级文件上传可能需要较长时间,太短容易误报超时

六、代码规范

6.1 ESLint + Prettier

重要提醒 :ESLint 9 的配置格式是扁平化的 eslint.config.js,和旧版 .eslintrc 完全不同。如果你用的是若依框架的 ESLint 8 配置,别直接复制到新项目。

bash 复制代码
npm install -D eslint @eslint/js typescript-eslint eslint-plugin-vue prettier eslint-config-prettier
js 复制代码
// eslint.config.js(ESLint 9 扁平化配置)
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import pluginVue from 'eslint-plugin-vue'
import prettierConfig from 'eslint-config-prettier'

export default [
  js.configs.recommended,
  ...tseslint.configs.recommended,
  ...pluginVue.configs['flat/recommended'],
  prettierConfig,
  {
    files: ['**/*.{ts,vue}'],
    languageOptions: {
      parserOptions: { parser: tseslint.parser }
    }
  },
  {
    ignores: ['dist/**', 'node_modules/**', 'types/**']
  }
]
json 复制代码
// .prettierrc
{
  "semi": false,
  "singleQuote": true,
  "trailingComma": "none",
  "printWidth": 100,
  "tabWidth": 2,
  "arrowParens": "avoid",
  "endOfLine": "auto"
}

6.2 Git 提交规范

bash 复制代码
npm install -D @commitlint/cli @commitlint/config-conventional
npm install -D husky lint-staged
js 复制代码
// commitlint.config.js
export default {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [2, 'always', [
      'feat',     // 新功能
      'fix',      // 修复
      'docs',     // 文档
      'style',    // 格式
      'refactor', // 重构
      'perf',     // 性能优化
      'test',     // 测试
      'chore',    // 构建/工具
      'revert'    // 回滚
    ]]
  }
}

提交格式示例:feat: 新增生产工单列表页面 / fix: 修复质检模板保存失败的问题


七、启动模板的完整 main.ts

ts 复制代码
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import pinia from './store'

// Element Plus 样式
import 'element-plus/dist/index.css'
// 若依框架自定义样式
import '@/assets/styles/index.scss'

// 自定义指令
import permission from '@/directives/permission'

// 路由权限守卫
import './permission'

const app = createApp(App)

app.use(router)
app.use(pinia)
app.directive('hasPermi', permission)

app.mount('#app')

main.ts 坚持一个小原则:每一行 import 只做一件事,不混在一起。这样六个月后回来看代码,一眼就知道初始化流程。


八、踩坑记录

现象 原因 解决方案
Element Plus 样式丢失 页面空白/组件无样式 unplugin-vue-components 未配置 importStyle: 'css' ElementPlusResolver({ importStyle: 'css' })
Vite proxy 不生效 接口请求 404 路径重写规则不对 打开浏览器 Network 面板,确认请求 URL 是否被正确代理
SCSS 变量全局引入失败 组件中用了 $primary-color 报错 Vite 需要配置 css.preprocessorOptions additionalData: '@use "@/assets/styles/variables.scss" as *;'
TypeScript 找不到路径别名 import from '@/api' 报红 未配置 tsconfig.jsonpaths 见下方配置
json 复制代码
// tsconfig.json 补充(追加到 compilerOptions)
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

九、总结

这套模板的特点是不追求炫技,只追求稳

  1. Vite + unplugin 自动导入:开发体验流畅,打包体积可控
  2. 目录结构按模块划分api/views/ 一一对应,降低协作成本
  3. Axios 封装一次到位:Token 刷新、统一错误处理、接口类型安全
  4. 环境变量 + 多环境切换:告别"打包前手动改地址"的噩梦
  5. ESLint + Prettier + Commitlint 三板斧:代码风格一致,提交记录可读

如果你是独立开发者,这套模板能让你跳过基础搭建,直接进入业务开发。

如果你是团队合作,这套规范能让新人第一天就能写出风格一致的代码。


📌 关于作者

我是一名全栈开发者,目前在深圳创业,专注于印刷包装行业的数字化系统建设。技术栈:Java / Spring Boot / Vue3 / uni-app / MySQL / Redis。

我会持续分享全栈开发实战、MES & CRM 产品设计,以及独立开发者的技术与商业思考。

如果这篇文章帮你省下了搭建后台框架的时间,点个赞 👍 让我知道。想获取更多实战文章和完整代码,欢迎关注微信公众号「MqCode」,每周更新,绝不断更。

相关推荐
SL-staff2 小时前
Web 白板技术架构深度解析:从渲染到协作的选型哲学
前端·架构
微扬嘴角2 小时前
react篇4--setState、LazyLoad和Hooks
前端·javascript·react.js
杨梦馨2 小时前
万级数据表格卡死?Web Worker 一招搞定
前端·javascript·vue.js
阿明在折腾2 小时前
从Canvas到AI模型:我在线工具站里的图片处理实战
前端·后端
CainChen2 小时前
Chrome 远程调试 Android 卡在 Pending authentication 的解决办法
前端
杨运交2 小时前
[030][Web模块]Spring Boot 验证与 OpenAPI 集成实战:从校验规则到文档生成
前端·spring boot·python
天le2 小时前
基于cocos3.x复刻《猪了个猪》挪了个船:位置生成实现
前端
青木_JS2 小时前
qiankun 子应用重开后仍显示旧数据?问题出在模块顶层的 useStore()
前端
货拉拉技术2 小时前
面向 Agent Skill 的 CLI/SSO 鉴权体系:安全、无感、可追溯
前端·agent