(一)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 布局。

相关推荐
常宇佳1 小时前
vue3 $refs使用方法
前端·vue.js·typescript
zhanghaha13141 小时前
HTML系列教程:4_什么是 HTML 元素(零基础超详细讲解)
前端·算法·html
AI 编程助手GPT2 小时前
VS Code 1.133 实战:Claude 可免 GitHub 登录,HTML 保存后自动刷新
前端·html·github
灯澜忆梦3 小时前
【基于GO的Web开发3】gin框架_HTML渲染
前端·golang·gin
小徐_23339 小时前
wot-ui-cli 1.1.0 发布:支持 OpenCode、Antigravity,wot-ui 图标迁移不再靠猜
前端
__zRainy__10 小时前
Node.js Web 框架选型指南:从 Express 到 Hono 的全景对比
前端·node.js·express·koa·nestjs·egg·fastify
西瓜有点饿10 小时前
PostCSS 和 UnoCSS 的作用和区别
前端·postcss
伟大的兔神10 小时前
我做了一个本地优先的 AI 图片工作台:Loomora v1.0.0 正式发布
前端·javascript·vue.js
90后的晨仔12 小时前
uni-app项目 Vue3 状态管理 Pinia 完全指南:从概念到实战的深度解析
前端