(一)Vue3+JS+Vite+pnpm 仿若依后台 从零搭建

(一)Vue3+JS+Vite+pnpm 仿若依后台 从零搭建

技术栈:Vue3 (JS 非 TS) + Vite + Element‑Plus + Axios + mock 本地模拟数据,同时支持真实后端网络请求,布局菜单、登录、路由守卫对齐若依风格

1、创建项目

复制代码
pnpm create vue@latest

选项照着选:

复制代码
✔ Project name: ... szty-admin
✔ Add TypeScript? ... No
✔ Add JSX Support? ... No
✔ Add Vue Router for Single Page Application development? ... Yes ✅路由必须
✔ Add Pinia for state management? ... Yes ✅状态管理(存token用户信息,仿若依store)
✔ Add Vitest for Unit testing? ... No
✔ Add an End‑to‑End Testing Solution? ... No
✔ Add ESLint for code quality? ... Yes
✔ Add Prettier for code formatting? ... No

cd szty-admin
pnpm install

安装必须依赖

复制代码
# element‑plus UI库
pnpm add element-plus
# axios网络请求
pnpm add axios
# mock模拟数据 vite插件
pnpm add -D vite-plugin-mock mockjs

2、目录改造(对齐若依目录结构)

删除 src/components/HelloWorld.vue

完整 src 目录结构

复制代码
src
├─ api                # 请求接口,和若依一样
│  └─ user.js
├─ assets
├─ components         # 公共组件
├─ layout             # 主布局【仿若依Layout】
│  ├─ index.vue       # 主布局容器(侧边菜单+头部+主内容)
│  ├─ Sidebar.vue     # 侧边栏菜单
│  └─ Header.vue      # 顶部导航栏
├─ mock               # 本地mock模拟数据
│  ├─ user.js
│  └─ menu.js
├─ router
│  └─ index.js        # 路由、路由守卫
├─ stores             # pinia代替vuex,存储token用户信息
│  └─ user.js
├─ utils
│  └─ request.js      # axios封装(仿若依request拦截器)
├─ views              # 页面
│  ├─ login           # 登录页
│  │  └─ index.vue
│  └─ home            # 首页
│     └─ index.vue
├─ App.vue
└─ main.js

3、配置 vite.config.js 开启 mock

复制代码
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { viteMockServe } from 'vite-plugin-mock'
import path from 'path'

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    viteMockServe({
      mockPath: './src/mock', // mock文件夹位置
      localEnabled: true,    // 开发环境开启mock本地模拟数据
      prodEnabled: false     // 生产关闭mock,走真实后端接口
    })
  ],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  }
})

4、mock 模拟数据 src/mock/user.js

复制代码
// 登录mock接口,本地模拟返回token用户信息
export default [
  {
    url: '/api/login',
    method: 'post',
    response: ({ body }) => {
      const { username, password } = body
      if (username === 'admin' && password === '123456') {
        return {
          code: 200,
          msg: '登录成功',
          data: {
            token: 'admin-token-123456',
            username: 'admin'
          }
        }
      } else {
        return { code: 500, msg: '账号密码错误' }
      }
    }
  },
  {
    url: '/api/getMenu',
    method: 'get',
    response: () => {
      // 本地写死菜单数据,和若依返回菜单格式一致
      return {
        code:200,
        data:[
          {
            path:'/home',
            name:'Home',
            meta:{title:'首页',icon:'House'},
            component:'home/index'
          }
        ]
      }
    }
  }
]

5、axios 封装 src/utils/request.js

可以切换:mock 本地模拟数据 / 真实后端网络请求

复制代码
import axios from 'axios'
import { ElMessage } from 'element-plus'

const service = axios.create({
  baseURL: '/api', // mock使用 /api;对接后端改成 http://127.0.0.1:8080/api
  timeout: 5000
})

// 请求拦截器 带上token
service.interceptors.request.use(config => {
  const token = localStorage.getItem('token')
  if(token){
    config.headers['Authorization'] = token
  }
  return config
})

//响应拦截器
service.interceptors.response.use(res=>{
  const data = res.data
  if(data.code !== 200){
    ElMessage.error(data.msg || '请求失败')
    return Promise.reject(data)
  }
  return data
},err=>{
  ElMessage.error('网络异常')
  return Promise.reject(err)
})

export default service

6、api 接口层 src/api/user.js

复制代码
import request from '@/utils/request'

//登录
export function login(data){
  return request({
    url:'/login',
    method:'post',
    data
  })
}

//获取菜单
export function getMenu(){
  return request({
    url:'/getMenu',
    method:'get'
  })
}

7、pinia 用户状态 src/stores/user.js

复制代码
import { defineStore } from 'pinia'
import { login } from '@/api/user'

export const useUserStore = defineStore('user',{
  state(){
    return {
      token: localStorage.getItem('token') || '',
      username:''
    }
  },
  actions:{
    async loginAction(form){
      const res = await login(form)
      this.token = res.data.token
      localStorage.setItem('token',res.data.token)
      return res
    },
    logout(){
      this.token = ''
      localStorage.removeItem('token')
    }
  }
})

8、路由配置 src/router/index.js

复制代码
import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/stores/user'

//布局
const Layout = ()=>import('@/layout/index.vue')

const routes = [
  {
    path:'/login',
    component:()=>import('@/views/login/index.vue')
  },
  {
    path:'/',
    component:Layout,
    redirect:'/home',
    children:[
      {
        path:'home',
        name:'home',
        component:()=>import('@/views/home/index.vue'),
        meta:{title:'首页'}
      }
    ]
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

//路由守卫,仿若依,没token跳登录
router.beforeEach((to,from,next)=>{
  const userStore = useUserStore()
  const token = userStore.token
  if(to.path === '/login'){
    next()
  }else{
    if(token){
      next()
    }else{
      next('/login')
    }
  }
})

export default router

9、主布局 src/layout/index.vue

复制代码
<template>
  <el-container style="height:100vh">
    <el-aside width="220px">
      <Sidebar />
    </el-aside>
    <el-container>
      <el-header height="60px">
        <Header />
      </el-header>
      <el-main>
        <router-view></router-view>
      </el-main>
    </el-container>
  </el-container>
</template>

<script setup>
import Sidebar from './Sidebar.vue'
import Header from './Header.vue'
</script>

src/layout/Sidebar.vue 侧边菜单

复制代码
<template>
  <el-menu router default-active="$route.path" background-color="#304156" text-color="#fff">
    <el-menu-item index="/home">首页</el-menu-item>
  </el-menu>
</template>

src/layout/Header.vue 顶部栏

复制代码
<template>
  <div class="header">
    <span>后台管理系统</span>
    <el-button @click="handleLogout" type="text">退出登录</el-button>
  </div>
</template>

<script setup>
import { useUserStore } from '@/stores/user'
import { useRouter } from 'vue-router'
const userStore = useUserStore()
const router = useRouter()

const handleLogout = ()=>{
  userStore.logout()
  router.push('/login')
}
</script>
<style scoped>
.header{
  display:flex;
  justify-content: space-between;
  align-items:center;
  height:100%;
  padding:0 20px;
}
</style>

10、登录页面 src/views/login/index.vue

复制代码
<template>
  <div class="login">
    <el-card style="width:400px">
      <el-form ref="formRef" :model="form">
        <el-form-item label="账号">
          <el-input v-model="form.username"></el-input>
        </el-form-item>
        <el-form-item label="密码">
          <el-input v-model="form.password" type="password"></el-input>
        </el-form-item>
        <el-button type="primary" @click="submit">登录</el-button>
      </el-form>
    </el-card>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useUserStore } from '@/stores/user'
import { useRouter } from 'vue-router'
const userStore = useUserStore()
const router = useRouter()

const form = ref({
  username:'admin',
  password:'123456'
})

const submit = async ()=>{
  await userStore.loginAction(form.value)
  router.push('/')
}
</script>

<style scoped>
.login{
  height:100vh;
  display:flex;
  justify-content:center;
  align-items:center;
  background:#f5f7fa;
}
</style>

11、首页 src/views/home/index.vue

复制代码
<template>
  <div>欢迎登录后台管理系统</div>
</template>

12、main.js

复制代码
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

const app = createApp(App)
app.use(router)
app.use(createPinia())
app.use(ElementPlus)
app.mount('#app')

13、App.vue

复制代码
<template>
  <router-view></router-view>
</template>
<style>
*{margin:0;padding:0}
</style>

运行项目

复制代码
pnpm dev

访问:http://localhost:5173

账号admin,密码123456

源码

两种模式切换

  1. 本地 mock 模拟(现在用的) vite.config.jslocalEnabled:true,所有接口走 src/mock 模拟数据,不需要后端服务。
  2. 对接真实后端网络访问
  • 修改 src/utils/request.jsbaseURL改为后端地址 baseURL:'http://127.0.0.1:8080/api'
  • vite.config.js localEnabled:false,关闭 mock,全部请求发送到后端。

和若依逻辑完全对齐:登录存 token、路由守卫拦截、axios 请求 / 响应拦截、api 接口分层、layout 布局。

相关推荐
晴天167 小时前
React 版本迭代解析:从15到19 核心差异-Day40
前端·react.js·前端框架
APItesterCris7 小时前
告别人工盯品!借助 Open‑Claw 快速搭建电商商品全自动监控与数据分析系统(完整实操代码)
java·大数据·前端·数据库
啊阿狸不会拉杆8 小时前
《计算机网络-自顶向下方法》6.7 回顾:Web页面请求的历程 读书笔记
前端·计算机网络
云上工程笔记8 小时前
四柱记账法和复式记账法有什么区别?复式记账为什么更适合查错和对账
大数据·前端·人工智能
恋猫de小郭8 小时前
Dart Skills CLI 1.0 :AI 时代的 Dart 交付支持
android·前端·flutter
Hilaku8 小时前
为什么技术极强的前端,往往当不好前端 Team Leader?
前端·javascript·程序员
Knight_AL8 小时前
EasyExcel 导入模板踩坑:row.add(““)导致文本格式失效与单元格保护问题解决方案
java·前端·easyexcel
Csvn8 小时前
模板字面量类型:用类型做字符串的正则
前端
倾颜9 小时前
AI Chat 长会话性能实践:消息虚拟化、动态高度与流式滚动设计
前端·react.js·node.js
幸运小圣9 小时前
SSE 与 WebSocket 新手入门:前端实时通信完全指南【JavaScript】
前端·javascript·websocket