企业级 Vue3 + Vite 项目实战中vite-plugin-mock 的最佳实践方案,解决开发中不依赖后端的痛点

在前端开发过程中,经常会遇到后端数据缺失或后端服务尚未就绪的情况。此时,我们可以通过mock数据来模拟真实接口,确保开发工作不受影响。

接下来介绍下企业级 Vue3 + Vite 项目实战中vite-plugin-mock 的最佳实践方案。

一、先说适用范围与局限

vite-plugin-mock 原理 :在 Vite Dev Server(Connect 中间件层)拦截请求,只在 vite dev生效,不能用于 Vitest 单元测试或 Cypress E2E。

✅ 适合:前端独立开发、快速原型、无测试需求的中小型/中型项目

⚠️ 不适合:需要跨环境一致 mock(Vitest/Cypress)、需要模拟网络超时/断网等极端场景(建议用 MSW)


二、安装依赖

复制代码
pnpm add -D vite-plugin-mock mockjs @types/mockjs
# 如果要用生产环境演示 mock(可选)
pnpm add -D vite-plugin-mock/client

三、推荐目录结构(贴合你的企业级模板)

复制代码
my-enterprise-app/
├── mock/                         # ✅ 根目录,与 src 平级
│   ├── index.ts                  # 统一导出所有模块(供插件读取)
│   ├── types.ts                  # Mock 相关类型定义
│   ├── utils/
│   │   ├── helper.ts             # 分页/延迟/响应包装
│   │   └── db.ts                 # 简易内存数据库(CRUD 模拟)
│   ├── fixtures/
│   │   └── users.ts              # 固定基准数据
│   └── modules/
│       ├── user.ts               # 用户模块接口
│       └── order.ts              # 订单模块接口
├── src/
│   ├── services/request/http.ts  # Axios 实例
│   └── ...
├── vite.config.ts
├── .env.development
└── .env.mock                      # 开启 mock 的环境文件

四、vite.config.ts 配置(关键)

复制代码
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { viteMockServe } from 'vite-plugin-mock'
import path from 'node:path'

export default defineConfig(({ command, mode }) => {
  const env = loadEnv(mode, process.cwd())

  // 通过环境变量控制是否启用 mock
  const enableMock = env.VITE_USE_MOCK === 'true' && command === 'serve'

  return {
    plugins: [
      vue(),
      viteMockServe({
        mockPath: 'mock/modules',   // mock 模块目录
        enable: enableMock,          // 按环境开关
        logger: true,                // 控制台打印拦截日志
        watchFiles: true,            // 修改 mock 文件热更新
        // supportTs: true --- v3 默认支持,可不写
      }),
    ],
    resolve: {
      alias: {
        '@': path.resolve(__dirname, 'src'),
      },
    },
    server: {
      port: 3000,
      // ⚠️ 重要:proxy 不能抢走 mock 要拦截的路径
      // 如果配了 proxy['/api'],要确保 mock 先匹配
      proxy: {
        // '/api': { target: 'http://localhost:8080', changeOrigin: true }
        // 建议:mock 开启时注释掉对应 /api proxy,或只 proxy 非 mock 路径
      },
    },
  }
})

.env.development(联调后端):

复制代码
VITE_USE_MOCK=false
VITE_API_BASE_URL=http://localhost:8080

.env.mock(前端独立开发):

复制代码
VITE_USE_MOCK=true
VITE_API_BASE_URL=        # 留空!vite-plugin-mock 拦截相对路径 /api/xxx

五、Axios 适配要点

vite-plugin-mock 拦截的是相对路径请求 ,所以 mock 模式下 baseURL要为空或 /

复制代码
// src/services/request/http.ts
import axios from 'axios'
import { loadEnv } from 'vite'

const isMock = import.meta.env.VITE_USE_MOCK === 'true'

const http = axios.create({
  baseURL: isMock ? '' : import.meta.env.VITE_API_BASE_URL,
  timeout: 15000,
})

// 请求/响应拦截器照常写(token 注入、错误统一处理)
http.interceptors.request.use((config) => {
  const token = localStorage.getItem('token')
  if (token) config.headers!.Authorization = `Bearer ${token}`
  return config
})

http.interceptors.response.use(
  (res) => res.data,  // 根据你的后端结构拆包
  (err) => Promise.reject(err)
)

export default http

⚠️ 常见坑 :mock 开着时 Axios baseURL还配成 http://localhost:8080,请求变成绝对路径,Vite 中间件拦截不到,mock 失效。


六、Mock 核心代码(类型安全 + 业务结构)

类型定义

复制代码
// mock/types.ts
import type { MockMethod } from 'vite-plugin-mock'

export interface ApiResp<T = unknown> {
  code: number
  data: T
  message: string
}

export type AppMockMethod = MockMethod

工具函数(分页 + 响应包装)

复制代码
// mock/utils/helper.ts
import type { ApiResp } from '../types'

export function success<T>(data: T, message = 'ok'): ApiResp<T> {
  return { code: 200, data, message }
}

export function fail(message: string, code = 400): ApiResp<null> {
  return { code, data: null, message }
}

/** 简易内存分页 */
export function paginate<T>(
  list: T[],
  page = 1,
  pageSize = 20
): { items: T[]; total: number } {
  const start = (page - 1) * pageSize
  return {
    items: list.slice(start, start + pageSize),
    total: list.length,
  }
}

内存数据库(模拟 CRUD 状态变更)

复制代码
// mock/utils/db.ts
import Mock from 'mockjs'
import type { User } from '@/domain/user/types' // 复用你领域层类型

export let userDb: User[] = Mock.mock({
  'list|30': [
    {
      'id': '@guid',
      'name': '@cname',
      'phone': /1[3-9]\d{9}/,
      'email': '@email',
      'role': ['admin', 'user', 'editor'],
      'status|1': ['active', 'disabled'],
      'createdAt': '@datetime',
      'updatedAt': '@datetime',
    },
  ],
}).list

用户模块 Mock(完整 CRUD + 分页 + 错误模拟)

复制代码
// mock/modules/user.ts
import type { MockMethod } from 'vite-plugin-mock'
import { success, fail, paginate } from '../utils/helper'
import { userDb } from '../utils/db'
import type { User, CreateUserDto } from '@/domain/user/types'

export default [
  // 列表 + 分页 + 关键字搜索
  {
    url: '/api/users',
    method: 'get',
    response: ({ query }: any) => {
      const page = Number(query.page) || 1
      const pageSize = Number(query.pageSize) || 20
      const keyword = (query.keyword as string) || ''

      let filtered = userDb
      if (keyword) {
        filtered = filtered.filter(
          (u) => u.name.includes(keyword) || u.phone.includes(keyword)
        )
      }

      const { items, total } = paginate(filtered, page, pageSize)
      return success({ items, total, page, pageSize })
    },
  },

  // 详情
  {
    url: '/api/users/:id',
    method: 'get',
    response: ({ query, req }: any) => {
      // path-to-regexp 匹配到的 param 在 req.params
      const id = req.params?.id || query.id
      const user = userDb.find((u) => u.id === id)
      if (!user) return fail('用户不存在', 404)
      return success(user)
    },
  },

  // 新增
  {
    url: '/api/users',
    method: 'post',
    response: ({ body }: any) => {
      const dto = body as CreateUserDto
      if (!dto.phone) return fail('手机号不能为空')
      const newUser: User = {
        id: crypto.randomUUID(),
        name: dto.name || '新用户',
        phone: dto.phone,
        email: dto.email || '',
        role: dto.role || 'user',
        status: 'active',
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
      }
      userDb.unshift(newUser)
      return success(newUser, '创建成功')
    },
  },

  // 编辑
  {
    url: '/api/users/:id',
    method: 'put',
    response: ({ body, req }: any) => {
      const id = req.params?.id
      const idx = userDb.findIndex((u) => u.id === id)
      if (idx === -1) return fail('用户不存在', 404)
      userDb[idx] = { ...userDb[idx], ...body, updatedAt: new Date().toISOString() }
      return success(userDb[idx], '更新成功')
    },
  },

  // 删除
  {
    url: '/api/users/:id',
    method: 'delete',
    response: ({ req }: any) => {
      const id = req.params?.id
      userDb = userDb.filter((u) => u.id !== id)
      return success(null, '删除成功')
    },
  },

  // 批量删除
  {
    url: '/api/users/batch-delete',
    method: 'post',
    response: ({ body }: any) => {
      const { ids } = body as { ids: string[] }
      userDb = userDb.filter((u) => !ids.includes(u.id))
      return success(null, '批量删除成功')
    },
  },
] as MockMethod[]

统一入口(供 mockPath 自动扫描,也可手动 import)

复制代码
// mock/index.ts
// 如果只是让插件扫描 modules/* 可留空导出
// 如需手动注册或有额外逻辑可在这里聚合
export {}

七、package.json 启动脚本

复制代码
{
  "scripts": {
    "dev": "vite --mode development",
    "dev:mock": "vite --mode mock"
  }
}

运行 pnpm dev:mock→ 读取 .env.mockVITE_USE_MOCK=true→ mock 启动

运行 pnpm dev→ 读取 .env.development→ mock 关闭 → 请求走 proxy 到真实后端


八、可选:生产环境演示用 Mock(慎用)

vite-plugin-mock 支持打包进生产构建(仅用于演示/展示环境):

复制代码
// mockProdServer.ts --- 项目根目录
import { createProdMockServer } from 'vite-plugin-mock/client'
import userMock from './mock/modules/user'
import orderMock from './mock/modules/order'

export function setupProdMockServer() {
  createProdMockServer([...userMock, ...orderMock])
}

// src/main.ts --- 顶部注入
if (import.meta.env.PROD && import.meta.env.VITE_USE_MOCK === 'true') {
  import('../mockProdServer').then(({ setupProdMockServer }) => {
    setupProdMockServer()
  })
}

⚠️ 不建议常规生产开启,会增加首屏体积且无法热更新,仅用于内网演示服务器。


九、常见坑位排查清单

现象 原因 解决
mock 不生效,请求 404 / 直连后端 Axios baseURL是绝对路径 mock 模式 baseURL=''
mock 不生效,Vite 日志无 Loaded mock mockPath配错或文件未 export default [] 确认 TS 文件 as MockMethod[]且 default 导出数组
query 参数取不到 handler 里要从 { query, req }解构 req.params取路径参数,query?a=1
修改 mock 文件不热更新 watchFiles: false或文件被 ignore 确认 watchFiles: true,不被 .gitignore忽略
proxy 和 mock 冲突 /api被 Vite proxy 先匹配 mock 开启时注释掉对应 proxy,或确保 mock url 先注册

十、一句话总结

vite-plugin-mock 最佳实践 = 按模块拆分 + 与 Axios baseURL 解耦 + 环境变量控制开关 + 复用 Domain 类型 + Mock.js 生成数据 + 内存 DB 模拟 CRUD