在前端进行开发的时候,若后端还未开发完成,前端也可以通过 mockjs 自己手动模拟后端接口返回的随机数据。

官方介绍:

Mock.js (opens new window)可以 生成随机数据,拦截 Ajax 请求

提示

  关于 mock-server.js 和 vite-plugin-mock 两种方案的使用示例,可以查看个人项目:

https://github.com/Lencamo/mock-programme

# 一、Mock.js

优点:

  • 解决了很好的解决了数据模拟问题

缺点:

  • 在浏览器上不能真正的模拟请求与响应过程

network 中没有发出任何的请求、本地调试只能通过 console.log

# 1、安装配置

安装:

npm install mockjs -D

npm install axios

配置:

  新建文件并编写内容:

  • mock / index.js
import Mock from 'mockjs'
  • main.js
import './mock/index.js'

# 2、mock 语法

  Mock 语法的内容,在其官方文档中写得是非常详细的:

Mock 官方示例 (opens new window)

  更加便捷的是:当我们处于官方示例页面中时,可以打开控制台,随意地试验 mock 方法

使用示例:

  • mock / index.js
import Mock from 'mockjs'

const { newsList } = Mock.mock({
  'newsList|20-35': [
    {
      id: '@increment(1)',
      title: '@ctitle()',
      content: '@cparagraph(5,10)',
      img_url: "@image('50*50','#FF83FA','#FCFCFC','png','mono')",
      add_time: '@date(yyyy-MM-dd hh:mm:ss)'
    }
  ]
})

// 模拟返回新闻列表接口数据
Mock.mock('/api/news', 'get', () => {
  return {
    status: 200,
    message: '获取新闻列表成功',
    list: newsList,
    total: newsList.length
  }
})

# 3、请求拦截

  关于拦截 ajax 请求部分的官方文档(即 Mock.js 0.1 文档),在 mock 官方地址的文档部分可以查看到详细内容:

mock 官方文档 (opens new window)

Mock.mock( rurl?, rtype?, template|function(options) )

  各个参数的含义及其默认值:

参数 默认值 说明
rurl 可选 可以是 URL 字符串或 URL 正则
rtype 可选 可以是 GET、POST、PUT、DELETE 等
template 可选 可以是对象或字符串
options 本次请求的 Ajax 选项集

  下面列举几种使用情况:

① 根据数据模板生成模拟数据

  前面学习 mock 语法的示例代码使用的就是这个

Mock.mock(template)

② 拦截到匹配 rurl 的 Ajax 请求时,并根据数据模板 template 生成模拟数据

Mock.mock(rurl, rtype, template)

③ 当拦截到匹配 rurl 的 Ajax 请求时,函数 function(options) 将被执行,并把执行结果作为响应数据返回

Mock.mock( rurl, rtype, function(options) )

# 4、开发应用

https://www.bilibili.com/video/BV1v741197q2/ (55 分钟左右)

安装:

npm install mockjs -D

npm install axios

配置:

// main.js

// 使用mockjs模拟接口数据
import './mock/index.js'

文件结构:

query 参数:path-parser.js
// 手动🤔获取res.query参数
var getQuery = (url, name) => {
  console.log(url, name)
  const index = url.indexOf('?')
  if (index !== -1) {
    const queryStrArr = url.substr(index + 1).split('&')
    for (var i = 0; i < queryStrArr.length; i++) {
      const itemArr = queryStrArr[i].split('=')
      console.log(itemArr)
      if (itemArr[0] === name) {
        return itemArr[1]
      }
    }
  }
  return null
}
├── mock
    ├── index.js
    ├── utils
    │   └── path-parser.js
    └── modules  # api管理
        ├── news.js
        └── products.js

具体使用:

获取数据
  • 获取分页数据
// /api/news?pageinde=1&pagesize=10
Mock.mock(/\/api\/news/, 'get', (option) => {
  // console.log(option)
  const pageIndex = getQuery(option.url, 'pageIndex')
  const pageSize = getQuery(option.url, 'pageSize')

  const start = (pageIndex - 1) * pageSize
  const end = pageIndex * pageSize
  const totalPage = Math.ceil(newList.length / pageSize)

  // 获取指定的数据
  const list = pageIndex > totalPage ? [] : newsList.slice(start, end)

  return {
    status: 200,
    message: '获取新闻列表成功'list: list,
    total: newList.length
  }
})
  • 使用接口
getNewsList(){
  axios.get('/api/get/news',{
    params: {
      pageInex: 1,
      pageSize: 10
    }
  }).then(res => {
    this.list = res.data.list
  })
}
添加数据
  • 添加新闻
Mock.mock('/api/news', 'post', (options) => {
  const body = JSON.parse(options.body)
  // console.log(body)

  // 添加数据操作
  newsList.push(
    Mock.mock({
      id: '@increment()',
      title: body.title,
      content: body.content,
      img_url: "@image('100x100','#31363f', '#00405d', '#FFF', 'png','任先生')",
      add_time: '@date(yyyy-MM-dd)'
    })
  )

  // 响应内容
  return {
    status: 200,
    message: '添加成功',
    list: newsList,
    total: newsList.length
  }
})
  • 使用接口
addNews() {
  axios.post('/api/news',{
    title: this.title,
    content: this.content
  }).then(res=>{
    console.log(res)
  })
}
删除数据
  • 删除新闻
Mock.mock('/api/news', 'delete', (options) => {
  const body = JSON.parse(options.body)
  // console.log(body)

  // 删除数据操作
  const index = newsList.findIndex((item) => item.id === body.id)
  newList.splice(index, 1)

  // 响应内容
  return {
    status: 200,
    message: '删除成功',
    list: newsList,
    total: newsList.length
  }
})
  • 使用接口
removeNews(id) {
  axios.delete('/api/news',{
    id
  }).then(res=>{
    console.log(res)
  })
}

# 二、mock-server.js

  • 一点点的 webpack 知识
  • 一点点的 node 知识

# 1、极简版

安装:

npm install mockjs -D

npm install axios

配置:

webpack3 中:devServer.before (opens new window)、webpack 新版 中:devServer.onBeforeSetupMiddleware (opens new window)

// vue.config.js
module.exports = {
  devServer: {
    before: require('./mock/index.js') // 引入mock/index.js
  },
  lintOnSave: false
}
使用示例
  • 新建:.env.development
MOCK = true
  • 新建:mock / index.js

最好对其进行模块化划分

const fs = require('fs')
const path = require('path')
const Mock = require('mockjs') // mockjs 导入依赖模块
const bodyParser = require('body-parser')

// 读取json文件
function getJsonFile(filePath) {
  // 读取指定json文件
  var json = fs.readFileSync(path.resolve(__dirname, filePath), 'utf-8')
  // 解析并返回
  return JSON.parse(json)
}

function checkToken(token) {
  if (!token || token === 'null' || token === 'undefind') return false
  if (
    token ===
    'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE1MTI1NDQyOTksImV4cCI6MTUxMjYzMDY5OX0.eGrsrvwHm-tPsO9r_pxHIQ5i5L1kX9RX444uwnRGaIM'
  ) {
    return true
  }
  return false
}

// 返回一个函数
module.exports = function (app) {
  if (process.env.MOCK == 'true') {
    app.use(bodyParser.json()) //数据JSON类型
    // 监听http请求
    app.post('/login', function (req, res) {
      // console.log(req.body);
      // 每次响应请求时读取mock data的json文件

      let username = req.body.username
      let password = req.body.password

      let json = ''
      if (username === 'admin' && password === '123456') {
        // getJsonFile方法定义了如何读取json文件并解析成数据对象
        json = getJsonFile('./userInfo1.json')
      } else if (username === 'zhangsan' && password === '123456') {
        json = getJsonFile('./userInfo2.json')
      } else {
        json = getJsonFile('./error.json')
      }

      // 将json传入 Mock.mock 方法中,生成的数据返回给浏览器
      res.json(Mock.mock(json))
    })

    app.get('/users', function (req, res) {
      let token = req.headers.authorization
      if (!checkToken(token)) {
        let errorJson = getJsonFile('./tokenerror.json')
        res.json(Mock.mock(errorJson))
        return
      }
      let json = getJsonFile('./userlist.json')
      res.json(Mock.mock(json))
    })

    app.delete('/users/:id', function (req, res) {
      let json = getJsonFile('./success.json')
      res.json(Mock.mock(json))
    })

    app.get('/goods', function (req, res) {
      let json = getJsonFile('./goods.json')
      res.json(Mock.mock(json))
    })
  }
}

# 2、基本概述

开源项目 vue-element-admin (opens new window) 的方案

核心代码地址 (opens new window)文档 (opens new window)

  mock-server 是我在 vue-element-admin (opens new window) 中看到的一种解决方案。

优点:它会在本地会启动一个 mock-server 来模拟数据,线上环境还是继续使用 mockjs 来进行模拟

实现原理

  mock 是完全基于 webpack-dev-serve (opens new window) 来实现的,所以在你启动前端服务的同时,mock-server 就会自动启动,而且这里还通过 chokidar (opens new window) 来观察 mock 文件夹内容的变化。在发生变化时会清除之前注册的 mock-api 接口,重新动态挂载新的接口,从而支持热更新

  由于是一个真正的 server,所以你可以通过控制台中的 network,清楚的知道接口返回的数据结构。并且同时解决了之前 mockjs 会重写 XMLHttpRequest 对象,导致很多第三方库失效的问题。

  参与 mock-server 的相关文件分布:

├── mock
    ├── index.js
    ├── mock-core # 核心代码
    │   ├── utils.js
    │   ├── mock-XHR.js # --> mock.js封装
    │   └── mock-server.js # --> mock-server封装
    └── modules  # api管理
        ├── cart.js
        └── products.js
核心代码:mock-server.js
const chokidar = require('chokidar')
const bodyParser = require('body-parser')
const chalk = require('chalk')
const path = require('path')
const Mock = require('mockjs')

const mockDir = path.join(process.cwd(), 'mock')

function registerRoutes(app) {
  let mockLastIndex
  const { mocks } = require('./../index.js')
  const mocksForServer = mocks.map((route) => {
    return responseFake(route.url, route.type, route.response)
  })
  for (const mock of mocksForServer) {
    app[mock.type](mock.url, mock.response)
    mockLastIndex = app._router.stack.length
  }
  const mockRoutesLength = Object.keys(mocksForServer).length
  return {
    mockRoutesLength: mockRoutesLength,
    mockStartIndex: mockLastIndex - mockRoutesLength
  }
}

function unregisterRoutes() {
  Object.keys(require.cache).forEach((i) => {
    if (i.includes(mockDir)) {
      delete require.cache[require.resolve(i)]
    }
  })
}

// for mock server
const responseFake = (url, type, respond) => {
  return {
    url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
    type: type || 'get',
    response(req, res) {
      console.log('request invoke:' + req.path)
      res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
    }
  }
}

module.exports = (devServer) => {
  // const { app } = devServer

  // parse app.body
  // https://expressjs.com/en/4x/api.html#req.body
  devServer.app.use(bodyParser.json())
  devServer.app.use(
    bodyParser.urlencoded({
      extended: true
    })
  )

  const mockRoutes = registerRoutes(devServer.app)
  var mockRoutesLength = mockRoutes.mockRoutesLength
  var mockStartIndex = mockRoutes.mockStartIndex

  // watch files, hot reload mock server
  chokidar
    .watch(mockDir, {
      ignored: /mock-server/,
      ignoreInitial: true
    })
    .on('all', (event, path) => {
      if (event === 'change' || event === 'add') {
        try {
          // remove mock routes stack
          devServer.app._router.stack.splice(mockStartIndex, mockRoutesLength)

          // clear routes cache
          unregisterRoutes()

          const mockRoutes = registerRoutes(devServer.app)
          mockRoutesLength = mockRoutes.mockRoutesLength
          mockStartIndex = mockRoutes.mockStartIndex

          console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed  ${path}`))
        } catch (error) {
          console.log(chalk.redBright(error))
        }
      }
    })
}

# 3、安装配置

安装:

npm install mockjs chokidar@2.1.5 chalk@2.4.2 -D

npm install axios

配置:

  • .env.development
# just a flag
ENV = 'development'

# base api
VUE_APP_BASE_API = '/dev-api'

① 本地使用 mock-server,线上使用 mockjs

  • vue.config.js
// 开启:【本地】mock-server模拟
  devServer: {
    port: port,
    open: true,
    client: {
      overlay: {
        errors: true,
        warnings: false,
        runtimeErrors: true
      }
    },
    onBeforeSetupMiddleware: require('./mock/mock-core/mock-server.js')
  },
  • main.js
// 开启:【线上】使用mock.js模拟
import { mockXHR } from '../mock/mock-core/mock-XHR.js'
if (process.env.NODE_ENV === 'production') {
  mockXHR()
}

② 本地、线上都使用 mockjs

  • main.js
// 完全使用mock.js模拟
import { mockXHR } from '../mock/mock-core/mock-XHR.js'
mockXHR()

# 4、使用示例

  • mock / index.js
const role = require('./api-mock/role')

const mocks = [...role]
  • mock / modules / users.js
代码示例
// 请求获取的数据(当再次请求时,数据会回到起点)
const Mock = require('mockjs')

const { userList } = Mock.mock({
  'userList|35': [
    {
      'role|1': ['user', 'teacher', 'super-admin', 'root', 'oj-admin'],
      name: '@cname()',
      classes: '软件@string(number,1,2).@string(number,1,2)本',
      studentId: '@id()',
      email: '@email()',
      uuid: '@guid()'
    }
  ]
})

module.exports = [
  // 用户列表请求处理
  {
    url: '/user/userList',
    type: 'get',
    response: (config) => {
      const { page, size } = config.query
      const newsList = userList.filter(
        (item, index) => index < size * page && index >= size * (page - 1)
      )

      return {
        code: 200,
        msg: '获取列表成功!',
        data: newsList,
        total: userList.length
      }
    }
  },

  // 添加用户请求处理
  {
    url: '/user/addUser',
    type: 'post',
    response: (config) => {
      const { role, name, classes, studentId, email } = config.body

      const newUser = Mock.mock({
        role: role,
        name: name,
        classes: classes ? classes : '空',
        studentId: studentId ? studentId : '空',
        email: email,
        uuid: '@guid()'
      })

      userList.push(newUser)

      return {
        code: 200,
        msg: '添加用户成功!',
        data: userList,
        total: userList.length
      }
    }
  },

  // 删除用户请求处理
  {
    url: '/user/delUser',
    type: 'post',
    response: (config) => {
      const { id } = config.body

      userList.splice(id, 1)

      return {
        code: 200,
        msg: '删除用户成功!',
        data: userList,
        total: userList.length
      }
    }
  },

  // 编辑某个用户信息请求处理
  {
    url: '/user/editUser',
    type: 'post',
    response: (config) => {
      const { id, userForm } = config.body

      const userObj = []
      userObj.push(userForm)

      const userDetail = userList.splice(id, 1, userObj[0])

      return {
        code: 200,
        msg: '更改用户信息成功!',
        data: userDetail,
        total: userList.length
      }
    }
  },

  // 更改用户角色信息请求处理
  {
    url: '/user/userRoleChange',
    type: 'post',
    response: (config) => {
      const { id, role } = config.body

      const userDetail = userList.slice(id, id + 1)
      // console.log(userDetail)
      userDetail[0].role = role

      return {
        code: 200,
        msg: '更改用户角色成功!'
      }
    }
  },

  // 批量添加用户请求处理
  {
    url: '/user/addMoreUser',
    type: 'post',
    response: (config) => {
      const addUserDetailArr = config.body
      // console.log('测试信息')

      userList.push.apply(userList, addUserDetailArr)

      return {
        code: 200,
        msg: '批量添加用户成功!',
        data: userList,
        total: userList.length
      }
    }
  }
]

# 三、vite-plugin-mock

这个示例项目,可以用于vite环境下的数据模拟(mock)

# 1、基本概述

  vite-plugin-mock 是我在 vue-vben-admin (opens new window) 中看到的一种用于模拟数据的方案。

  根据vite-plugin-mock (opens new window)的描述,它支持本地环境和生产环境,Connect 服务中间件在本地使用,mockjs 在生产环境中使用。

[!WARNING] 经过我的亲身测试,vite-plugin-mock 在开发环境下可以到达 mock-server 一样的效果。但生产环境下表现并不如意,官方自己也说了,生产环境下不支持 header 的获取、RESTful 格式参数获取、mock 文件不要使用 node 模块等。除此之外,vite-plugin-mock 在使用时还不可以设置全局的 baseURL(得用代理解决)。并且关于生产环境的相关 issues,官方也没有进行相关 fix。

# 2、文件分析

  vite-plugin-mock 和前面的 mock-server 使用基本一致,没有什么大的变动。

├── mock
    ├── index.js
    ├── mock-core # 核心代码
    │   ├── utils.js
    │   └── mock-XHR.js # --> mock.js封装
    └── modules  # api管理
        ├── cart.js
        └── products.js

# 3、安装配置

安装:

npm install mockjs vite-plugin-mock -D
npm install axios

配置:

  • .env.development
ENV = 'development'

# base api
VITE_BASE_API = '/mock-api'

① 本地使用 vite-plugin-mock

  • vite.config.js
import { viteMockServe } from 'vite-plugin-mock'

// https://vitejs.dev/config/
export default defineConfig({
  server: {
    // 注意:不同于mock-server,此处必须要通过代理去掉 VITE_BASE_API(大大的无语)
    proxy: {
      '/mock-api': {
        target: 'http://127.0.0.1:5174/',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/mock\-api/, '')
      }
    }
  },
  plugins: [
    // 开启:【本地】vite-plugin-mock模拟
    viteMockServe({
      mockPath: 'mock',
      enable: process.env.NODE_ENV === 'development',
      watchFiles: false
    })
  ]
})

② 线上使用 mockjs

  • main.js
// 开启:【线上】使用mock.js模拟
import { mockXHR } from '../mock/mock-core/mock-XHR'
if (import.meta.env.PROD) {
  mockXHR()
}
更新于 : 8/7/2024, 2:16:31 PM