(一)pnpm+Vue3 JS Setup 组合式 仿若依后台管理系统 手把手搭建
技术栈:pnpm + Vue3 (JavaScript) +
<script setup>+ Vue‑Router4 + Pinia + Axios + Element Plus (unplugin 自动按需导入)
改造点:
- 全部使用 Setup 组合式语法,摒弃选项式
- 目录优化:
layout和components同级,不和 views 嵌套- 分离环境变量:
.env.development/.env.production- 支持mock 本地模拟数据 / 真实网络接口一键切换
- 修正原版若依部分不合理目录、全局变量写法
- 从
pnpm create vue@latest从零开始,分节操作,复制代码即可跑通
第一节:初始化 Vue3 项目
1.1 前置条件
已安装 Node.js ≥18 版本,pnpm 已安装
# 没有pnpm先安装 npm install-g pnpm1.2 创建 vue 项目
pnpm create vue@latest交互式选择,严格按照下面选:
✔ Project name:› vue3‑ruoyi‑setup ✔ Add TypeScript?… No/Yes →No(我们用JavaScript)✔ Add JSX Support?… No/Yes → No ✔ Add Vue RouterforSingle Page Application development?… No/Yes → Yes ✔ Add Piniaforstate management?… No/Yes → Yes ✔ Add VitestforUnit testing?… No/Yes → No ✔ Add an End‑to‑End Testing Solution?… No/Yes → No ✔ Add ESLintforcode quality?… No/Yes → Yes ✔ Add Prettierforcode formatting?… No/Yes → Yescd vue3‑ruoyi‑setup pnpm install pnpm dev访问http://localhost:5173,基础项目跑起来。
⚠️ 此时生成的代码,部分还是选项式,后面我们全部改成
<script setup>。
第二节:安装全部依赖(Element‑Plus + unplugin 按需导入 + axios + mock 相关)
2.1 安装依赖包
pnpm add element-plus axios pnpm add-D unplugin-vue-components unplugin-auto-importunplugin-auto-import:自动导入 vue、pinia、router api,不用每次写import ref,computedunplugin-vue-components:Element Plus 组件自动按需导入,无需手动 import 组件
2.2 修改 vite.config.js
替换全部内容
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'// https://vitejs.dev/config/exportdefaultdefineConfig({plugins:[vue(),AutoImport({resolvers:[ElementPlusResolver()],// 自动生成auto‑imports.d.ts,js项目可以忽略类型imports:['vue','vue-router','pinia'],dts:false}),Components({resolvers:[ElementPlusResolver()],dts:false})],resolve:{alias:{'@':fileURLToPath(newURL('./src',import.meta.url))}},server:{port:5173,open:true}})现在:不需要
import { ElButton } from 'element-plus',页面直接写<el‑button>就生效;ref/reactive 不用 import。
第三节:目录层级重构(重点,仿若依但优化结构)
❗原版若依 layout 放在
@/layout,views 另外;我们保持 layout 和 components同级,不嵌套在 views 下面
删除初始化自带无用文件,最终 src 目录结构:
src ├── api # 接口请求函数 ├── assets # 静态资源 图片、css ├── components # 公共小组件(和layout同级!) ├── layout # 后台布局组件:侧边栏、头部、主内容区(仿若依layout) ├── router # vue‑router路由 ├── pinia # pinia仓库 ├── mock # 本地mock模拟数据 ├── utils # 工具类:request请求封装、auth、常量 ├── views # 业务页面 ├── App.vue └── main.js执行删除:
- 删除
src/components/HelloWorld.vue - 删除
src/views/About.vue、src/views/HomeView.vue
清空App.vue、router/index.js,后面重写。
第四节:环境变量分离(开发 / 生产分开)
在项目根目录(不是 src 下)新建两个文件
.env.development开发环境
#.env.development VITE_APP_TITLE=Vue3‑Setup仿若依后台 # 是否开启mock模拟数据 true=本地mock;false=请求真实后端接口 VITE_USE_MOCK=true # 后端接口基础地址 VITE_API_BASE_URL='/api'.env.production生产环境
#.env.production VITE_APP_TITLE=Vue3‑Setup仿若依后台 VITE_USE_MOCK=false VITE_API_BASE_URL='https://api.example.com'使用方式:代码中
import.meta.env.VITE_USE_MOCK获取环境变量
切换 mock / 真实接口,只需要修改.env.development的VITE_USE_MOCK即可。
第五节:Axios 请求封装 utils/request.js
新建src/utils/request.js
import axios from'axios'// 创建axios实例constservice=axios.create({baseURL:import.meta.env.VITE_API_BASE_URL,timeout:10000})// 请求拦截器service.interceptors.request.use(config=>{// 可以在这里携带tokenconsttoken=localStorage.getItem('token')if(token){config.headers['Authorization']='Bearer '+token}returnconfig},error=>{returnPromise.reject(error)})// 响应拦截器service.interceptors.response.use(response=>{returnresponse.data},error=>{console.error('请求错误',error)returnPromise.reject(error)})exportdefaultservice新建src/utils/auth.js简单 token 工具
// token本地存储 export function getToken() { return localStorage.getItem('token') } export function setToken(token) { return localStorage.setItem('token', token) } export function removeToken() { return localStorage.removeItem('token') }第六节:Mock 本地模拟数据,支持开关切换
目标:当
VITE_USE_MOCK=true,拦截请求返回本地假数据;false 直接走真实 axios。
不使用 vite‑mock‑plugin 插件(配置复杂),做简易 mock 适配,适合学习。
新建src/mock/index.js
// mock模拟接口数据constmockData={// 登录接口'/login':{code:200,msg:'登录成功',data:{token:'mock‑admin‑token‑123456'}},// 获取用户信息'/getUserInfo':{code:200,data:{username:'admin',avatar:'',roles:['admin']}}}// mock请求函数export functionmockRequest(url){returnnewPromise((resolve)=>{setTimeout(()=>{resolve(mockData[url])},300)})}新建src/api/login.js接口层,实现 mock 和真实请求自动切换
import request from'@/utils/request'import{mockRequest}from'@/mock'constUSE_MOCK=import.meta.env.VITE_USE_MOCK==='true'// 登录接口export functionloginApi(data){if(USE_MOCK){returnmockRequest('/login')}else{returnrequest({url:'/login',method:'post',data})}}// 获取用户信息export functiongetUserInfoApi(){if(USE_MOCK){returnmockRequest('/getUserInfo')}else{returnrequest({url:'/getUserInfo',method:'get'})}}✅ 切换 mock 只需要修改环境变量,业务 api 代码不用改动。
第七节:Pinia 状态仓库搭建
新建src/pinia/user.js用户仓库
import{defineStore}from'pinia'import{setToken,getToken,removeToken}from'@/utils/auth'import{loginApi,getUserInfoApi}from'@/api/login'exportconstuseUserStore=defineStore('user',{state:()=>({token:getToken()||'',userInfo:{}}),actions:{// 登录asynclogin(loginForm){constres=awaitloginApi(loginForm)if(res.code===200){this.token=res.data.tokensetToken(res.data.token)}returnres},// 获取用户信息asyncgetUserInfo(){constres=awaitgetUserInfoApi()this.userInfo=res.datareturnres},// 登出logout(){this.token=''this.userInfo={}removeToken()}}})新建src/pinia/index.js
export * from './user'第八节:Vue‑Router4 路由配置(仿若依路由:登录页 + Layout 布局嵌套路由)
layout 作为父路由组件,子页面嵌套在 layout 内部显示。
修改src/router/index.js
import{createRouter,createWebHistory}from'vue-router'import{getToken}from'@/utils/auth'// 路由守卫:没有token跳登录constrequireAuth=(to,from)=>{if(getToken()){returntrue}else{return'/login'}}constroutes=[{path:'/login',name:'Login',component:()=>import('@/views/login/index.vue')},{path:'/',component:()=>import('@/layout/index.vue'),redirect:'/dashboard',beforeEnter:requireAuth,children:[{path:'dashboard',name:'Dashboard',component:()=>import('@/views/dashboard/index.vue'),meta:{title:'首页'}}]},{path:'/:pathMatch(.*)*',redirect:'/'}]constrouter=createRouter({history:createWebHistory(import.meta.env.BASE_URL),routes})exportdefaultrouter修改src/main.js
import'./assets/main.css'import{createApp}from'vue'import{createPinia}from'pinia'import App from'./App.vue'import router from'./router'constapp=createApp(App)app.use(createPinia())app.use(router)app.mount('#app')修改src/App.vue
<template><router-view/></template><script setup></script>第九节:Layout 布局组件(和 components 同级 @/layout)仿若依后台骨架
新建src/layout/index.vue后台主布局,分为头部、侧边栏、内容区域
<template><el-container style="height:100vh;"><!--侧边栏--><el-aside width="220px"style="background-color:#304156;"><div class="logo">Vue3‑Setup后台</div><el-menudefault-active="$route.path"background-color="#304156"text-color="#fff"active-text-color="#409eff"router><el-menu-item index="/dashboard"><template #title>首页</template></el-menu-item></el-menu></el-aside><el-container><!--顶部header--><el-header style="background:#fff;border-bottom:1px solid #eee;display:flex;justify-content:flex-end;align-items:center;"><el-button @click="handleLogout"type="text">退出登录</el-button></el-header><!--主内容区域--><el-main style="background:#f2f3f5;"><router-view></router-view></el-main></el-container></el-container></template><script setup>import{useUserStore}from'@/pinia/user'import{useRouter}from'vue-router'constuserStore=useUserStore()constrouter=useRouter()consthandleLogout=()=>{userStore.logout()router.push('/login')}</script><style scoped>.logo{height:50px;line-height:50px;color:#fff;text-align:center;font-size:16px;}</style>第十节:编写登录页面 views/login/index.vue
<template><div class="login-container"><el-card style="width:420px;"><h2 style="text-align:center;margin-bottom:20px;">系统登录</h2><el-form ref="loginFormRef":model="loginForm"label-width="0"><el-form-item prop="username"><el-input v-model="loginForm.username"placeholder="账号"></el-input></el-form-item><el-form-item prop="password"><el-input v-model="loginForm.password"type="password"placeholder="密码"></el-input></el-form-item><el-form-item><el-button type="primary"style="width:100%"@click="handleLogin">登录</el-button></el-form-item></el-form></el-card></div></template><script setup>import{reactive}from'vue'import{useUserStore}from'@/pinia/user'import{useRouter}from'vue-router'constuserStore=useUserStore()constrouter=useRouter()constloginForm=reactive({username:'admin',password:'123456'})consthandleLogin=async()=>{await userStore.login(loginForm)await userStore.getUserInfo()router.push('/dashboard')}</script><style scoped>.login-container{height:100vh;background:#283443;display:flex;justify-content:center;align-items:center;}</style>第十一节:首页页面 views/dashboard/index.vue
<template><el-card><h2>欢迎来到后台管理系统</h2><p>Mock开关状态:{{USE_MOCK}}</p><p>当前用户:{{userStore.userInfo.username}}</p></el-card></template><script setup>import{useUserStore}from'@/pinia/user'constUSE_MOCK=import.meta.env.VITE_USE_MOCKconstuserStore=useUserStore()</script>第十二节:运行测试、切换 Mock / 真实接口
pnpm dev- 访问
http://localhost:5173/#/login
账号 admin,密码随便填,mock 环境直接登录成功跳首页。 - 切换真实网络请求:修改
.env.development
VITE_USE_MOCK=false此时登录就会走 axios,请求后端/api/login接口。
vite 会自动读取 env 文件,修改后重启 dev 服务生效。
后续拓展方向(模仿若依剩下功能)
- 动态路由、菜单(从后端返回菜单渲染侧边栏 el‑menu)
- tags‑view 多标签页组件,放到
@/components - svg 图标封装
- 权限控制按钮指令
- 全局错误处理、页面分页封装组件
- 封装文件上传、表格 CRUD 通用页面模板
关键改造对比原版若依
表格
| 若依 vue2/vue3 原版 | 本项目优化 |
|---|---|
| 选项式 + setup 混合 | 全部<script setup>组合式 JS |
| layout 下嵌套很多业务组件 | layout 和 components同级,职责清晰 |
| mock 深度耦合插件 | 环境变量一键开关 mock / 真实接口,代码侵入小 |
| 环境变量写死代码 | .env.development.env.production分离 |
| 全局 this 大量使用 | 全部 pinia/composition api,无 this |
| Element‑Plus 完整引入 | unplugin 自动按需导入,打包体积小 |
常见坑
- 修改
.env.*文件必须重启 vite 开发服务器,否则变量不更新 - unplugin 自动导入:template 直接用 el 组件,script 里 ref/reactive 不需要 import
import.meta.envvite 环境变量,必须以VITE_开头,否则读不到- mock 模式打开时不会走 axios,关闭 mock 才会触发代理。