Vue3 还原一个企业级后台-11-Mock数据策略

Mock 数据策略:让原型看起来真实

原型好不好用,70% 取决于 Mock 数据的真实度。前面三篇业务模块的文章里我贴的截图,列表里那些"管网泄漏检测"、"压力监测分析"、"流量预测模型",名字不重复、时间有分布、状态有逻辑------不是因为我一口气写了 75 条手写数据,而是用了 mockjs。本文把这个"让假数据看起来像真的"的策略完整拆开。


一、为什么 Mock 数据不是"随便敲几条就行"

我给客户做演示时,对方第一个评价不是"界面不错",而是"你们已经接了很多模型了啊"------他看到了列表里的 30 条数据,每条名称都不一样、时间分布在最近三个月、状态有草稿有已发布有已下线,看起来就是一套真实运行中的系统。

这就是好 Mock 数据和差 Mock 数据的差距:

维度 差的 Mock 好的 Mock
数据量 3~5 条 30 条(填满分页)
名称 test1test2test3 "管网泄漏检测模型 V3"、"压力监测实时分析引擎"
时间 全是同一天 分布在最近 3 个月,符合正态分布
状态 全是"草稿" 四种状态按比例混合(30% 草稿、20% 审核中、40% 已发布、10% 已下线)
字段关联 外键对不上 模型类型和模型名称保持一致
搜索效果 搜"管网"返回空 搜"管网"返回 8 条,且结果各不相同

差的 Mock 数据做演示,客户一看就知道是 demo;好的 Mock 数据,客户会把它当成真实系统在操作。


二、mockjs 基础:三分钟上手

mockjs 是国产 Mock 神器,核心就两个东西:数据模板语法占位符

2.1 占位符:一行代码生成真实感数据

js 复制代码
import Mock from 'mockjs'

// 随机中文名称,5~10 个字
Mock.mock('@ctitle(5, 10)')       // → "管网泄漏检测模型"

// 随机中文人名
Mock.mock('@cname')                // → "张伟"

// 随机日期时间
Mock.mock('@datetime("yyyy-MM-dd HH:mm:ss")')  // → "2026-07-15 14:32:18"

// 随机 URL
Mock.mock('@url("https")')         // → "https://api.pipe-network.cn/v3/leak-detect"

// 从数组中随机选一个
Mock.mock('@pick(["HTTP", "HTTPS", "gRPC", "WebSocket"])')  // → "HTTPS"

// 随机 ID
Mock.mock('@id')                   // → "440000202607153217"

// 随机段落
Mock.mock('@cparagraph(2, 5)')     // → 2~5 句随机中文段落

2.2 数据模板语法

模板语法让一条数据模板生成数组变得极其简单:

js 复制代码
const template = {
  'list|30': [{                          // 生成 30 条
    id: '@id',
    modelName: '@ctitle(6, 14)',
    modelType: '@pick(["algorithm", "geometry", "physics", "rule"])',
    status: '@pick(["draft", "auditing", "published", "offline"])',
    creator: '@cname',
    createTime: '@datetime("yyyy-MM-dd HH:mm:ss")',
    updateTime: '@datetime("yyyy-MM-dd HH:mm:ss")',
    'apiCount|1-5': 1,                  // 1~5 之间的随机整数
    description: '@cparagraph(1, 3)'
  }]
}

const data = Mock.mock(template)
console.log(data.list.length)  // 30

'list|30' 中的 |30 就是数据模板语法的核心------告诉 mockjs 这个数组要生成 30 条。每条里的占位符每次调用都会重新随机,30 条数据天然不重复。


三、集成 axios:拦截请求,返回 Mock 数据

mockjs 最优雅的用法不是独立调用,而是集成到 axios 拦截器里------前端代码写正常的 axios.get('/api/models'),请求被 mockjs 拦截,返回假数据。整个前端业务代码一行不用改。

3.1 目录结构

复制代码
src/
├── mock/
│   ├── index.js          # 统一入口,按环境判断是否加载
│   ├── api-registry.js   # API 注册管理的 Mock
│   ├── model-hub.js      # 模型汇聚的 Mock
│   └── model-publish.js  # 模型标准发布的 Mock

3.2 统一入口:按环境加载

js 复制代码
// src/mock/index.js
import { setupApiRegistryMock } from './api-registry'
import { setupModelHubMock } from './model-hub'
import { setupModelPublishMock } from './model-publish'

// 只在开发环境启用 Mock
if (import.meta.env.DEV) {
  setupApiRegistryMock()
  setupModelHubMock()
  setupModelPublishMock()
  console.log('[Mock] 已加载 3 个模块的 Mock 数据(共 75 条)')
}

3.3 单个模块的 Mock 定义

js 复制代码
// src/mock/api-registry.js
import Mock from 'mockjs'

export function setupApiRegistryMock() {
  // GET 列表接口(支持筛选和分页)
  Mock.mock(/\/api\/registry\/list/, 'get', (options) => {
    const params = new URLSearchParams(options.url.split('?')[1])
    const page = parseInt(params.get('page')) || 1
    const pageSize = parseInt(params.get('pageSize')) || 30
    const keyword = params.get('keyword') || ''
    const protocol = params.get('protocol') || ''
    const status = params.get('status') || ''

    // 生成 60 条原始数据(方便模拟筛选前后变化)
    const allData = Mock.mock({
      'list|60': [{
        id: '@id',
        name: '@ctitle(6, 14)',
        protocol: '@pick(["HTTP", "HTTPS", "gRPC", "WebSocket"])',
        url: '@url("https")',
        method: '@pick(["GET", "POST", "PUT", "DELETE"])',
        status: '@pick(["enabled", "disabled"])',
        creator: '@cname',
        'createTime': '@datetime("yyyy-MM-dd HH:mm:ss")',
        description: '@cparagraph(1, 2)'
      }]
    }).list

    // 筛选逻辑
    let filtered = allData
    if (protocol) filtered = filtered.filter(item => item.protocol === protocol)
    if (status) filtered = filtered.filter(item => item.status === status)
    if (keyword) filtered = filtered.filter(item => item.name.includes(keyword))

    const total = filtered.length
    const start = (page - 1) * pageSize
    const list = filtered.slice(start, start + pageSize)

    return {
      code: 200,
      data: { list, total }
    }
  })

  // POST 新增接口
  Mock.mock(/\/api\/registry\/create/, 'post', {
    code: 200,
    message: '新增成功'
  })

  // DELETE 删除接口
  Mock.mock(/\/api\/registry\/delete/, 'delete', {
    code: 200,
    message: '删除成功'
  })
}

注意几个关键设计:

  • 正则匹配路径/\/api\/registry\/list/ 用正则而非字符串,灵活性更高。
  • 从 query 中解析参数new URLSearchParams(options.url.split('?')[1]) 把真实 URL 参数拆出来用。
  • 筛选在前端 Mock 层完成 :前端传 ?protocol=HTTPS,Mock 层真的从 60 条里筛出 HTTPS 的------这样搜索、筛选、分页的行为和真实接口完全一致。

3.4 main.js 中引入

js 复制代码
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import pinia from './store'
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import 'element-plus/dist/index.css'

// Mock 必须在 axios 初始化之前引入
import './mock'

import './styles/index.scss'

const app = createApp(App)
app.use(pinia)
app.use(router)
app.use(ElementPlus, { locale: zhCn })
app.mount('#app')

关键顺序import './mock' 必须放在 axios 初始化之前。因为 mockjs 是通过重写 XMLHttpRequest 实现拦截的,如果你的 axios 实例已经在 mock 之前创建了请求,就不会被拦截。


四、真实感数据的四个原则

写 Mock 数据不是敲占位符就完了,要让它"看起来像真的",需要遵循四个原则:

4.1 差异性:每条数据都有差别

js 复制代码
// ❌ 差:所有模型都叫同一个名字
{ name: 'test_model', ... }

// ✅ 好:名称各不相同
{ name: '@ctitle(6, 14)', ... }

差异不仅体现在名称上,还要在元数据上有差异------描述长度不一、时间有先有后、状态按比例混合。

4.2 业务性:符合领域逻辑

不能随便拼字段。模型类型是"算法模型"时,名称里出现"几何"就不对。解决方式是按类型分组生成:

js 复制代码
const modelTypes = ['algorithm', 'geometry', 'physics', 'rule']
const namePrefixes = {
  algorithm: ['管网', '泄漏', '检测', '预测', '优化', '调度'],
  geometry: ['管道', '三维', '空间', '拓扑', '网络', '结构'],
  physics: ['压力', '流量', '温度', '流速', '水力', '水质'],
  rule: ['阈值', '规则', '策略', '控制', '联动', '触发']
}

function generateModelName(type) {
  const prefixes = namePrefixes[type]
  const a = prefixes[Math.floor(Math.random() * prefixes.length)]
  const b = prefixes[Math.floor(Math.random() * prefixes.length)]
  return `${a}${b}模型 V${Math.floor(Math.random() * 5) + 1}`
}

4.3 时效性:时间分布在合理区间

js 复制代码
// ❌ 差:所有数据都是同一天
createTime: '2026-08-10 14:00:00'

// ✅ 好:分布在最近 3 个月
createTime: Mock.mock('@datetime("yyyy-MM-dd HH:mm:ss")')

mockjs 的 @datetime 默认生成当年内的随机时间。如果你需要控制在最近 3 个月,可以用 Random.date() 配合手动拼接:

js 复制代码
Mock.Random.extend({
  recentDate() {
    const now = Date.now()
    const threeMonthsAgo = now - 90 * 24 * 3600 * 1000
    const ts = threeMonthsAgo + Math.random() * (now - threeMonthsAgo)
    const d = new Date(ts)
    const pad = n => String(n).padStart(2, '0')
    return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
  }
})

4.4 关联性:外键字段对得上

模型的标准发布记录里有个 modelId 字段,它必须和模型汇聚里的某个模型 ID 一致。在 Mock 阶段,可以先生成模型池,再基于池子生成发布记录:

js 复制代码
// 先生成 30 个模型(全局共享)
const modelPool = Mock.mock({ 'list|30': [{ id: '@id', name: '@ctitle(6, 14)', type: '@pick(...)' }] }).list

// 再基于 modelPool 生成发布记录
const publishList = modelPool.slice(0, 15).map(model => ({
  modelId: model.id,
  modelName: model.name,
  modelType: model.type,
  status: Mock.mock('@pick(["draft","auditing","published","offline"])'),
  // ...
}))

五、三个模块的 Mock 数据概览

模块 数据量 核心字段 特殊逻辑
API 注册管理 60 条 name / protocol / url / method / status 支持 keyword / protocol / status 三维筛选
模型汇聚 30 条 modelName / modelType / provider / interfaces 4 种类型各 7~8 条,按比例混合
模型标准发布 15 条 modelName / channel / version / status 四种状态按 3:2:4:1 比例分布,modelId 外键关联模型池

三个模块合计 105 条原始数据,经过分页、筛选、搜索后呈现在界面上。数据够多、够真、够乱(有草稿、有已下线、有空描述),看起来就是"用了几个月的系统"。


六、分页接口的 Mock:关键是要返回 total

这是新手最容易踩的坑------Mock 的分页接口只是把前端传的 pagepageSize 乘一下切数组,但不返回 total。前端分页器的总条数永远是 0 或者 30。

正确做法:

js 复制代码
Mock.mock(/\/api\/registry\/list/, 'get', (options) => {
  const { page, pageSize, ...filters } = parseParams(options.url)

  const allData = generate60Records()   // 先生成全量数据
  const filtered = applyFilters(allData, filters)  // 在 Mock 层筛选

  const total = filtered.length                // ← 这个必须有
  const start = (page - 1) * pageSize
  const list = filtered.slice(start, start + pageSize)

  return {
    code: 200,
    data: { list, total }                      // ← 返回 total
  }
})

有了 total,前端分页器才能正确显示"共 60 条,第 1/2 页"。筛选条件变化时 total 也会随之变化------搜"管网"可能返回 8 条,筛"HTTPS"可能返回 23 条。


七、模拟网络延迟:让原型有"加载感"

真实系统不会瞬间返回数据。如果 Mock 接口 0ms 响应,用户感觉不到数据是从后端来的,而且你也没法验证 loading 态的效果。

js 复制代码
function simulateDelay(ms = 300) {
  return new Promise(resolve => setTimeout(resolve, ms + Math.random() * 200))
}

Mock.mock(/\/api\/registry\/list/, 'get', async (options) => {
  // ... 数据生成逻辑 ...

  await simulateDelay(400)  // 模拟 400~600ms 网络延迟

  return { code: 200, data: { list, total } }
})

Math.random() * 200 让每次请求的延迟略有差异(400~600ms),就像真实网络抖动。调试 loading 动画、骨架屏、防重复提交,这些全靠这段延迟。


八、dev/prod 环境自动切换

这是 mockjs 的保命技能------忘了关 Mock 就把项目部署上线,客户看到的全是假数据,这是灾难级事故。

js 复制代码
// src/mock/index.js
if (import.meta.env.DEV) {
  // 开发环境:加载所有 Mock
  setupAllMocks()
} else {
  // 生产环境:什么都不做,请求正常发送到真实后端
  console.log('[Mock] 生产环境,Mock 已自动禁用')
}

原理:mockjs 通过重写 XMLHttpRequest.prototype.send 来拦截请求。如果你的 Mock 文件根本没有被 import,就不会发生重写------请求走正常网络通道。

Vite 的 import.meta.env.DEVvite build 时会被静态替换为 false,tree-shaking 甚至会直接把整个 mock 目录的代码从生产包中剔除。零运行时开销,零泄露风险。


九、Mock 数据的工程价值:不只是"造假"

Mock 数据在原型阶段的价值是显而易见的------看得见摸得着。但它真正的工程价值在于三个方面:

1. 前端可以完全独立开发。

后端接口还没写好?没关系,Mock 先行。前后端只需要约定好接口字段(name / protocol / url / method / status),前端写完 Mock 就可以开发 UI、交互、状态管理、筛选分页,后端出来后再换接口------改一行 axios 的 baseURL 即可。

2. 倒逼接口设计。

写 Mock 的过程就是思考"这个接口需要返回什么字段"的过程。如果你连 Mock 数据都写不出来(不知道字段叫什么、类型是什么),说明接口设计还没想清楚。

3. 发现 UI 边界问题。

Mock 数据生成 60 条,列表正常;把某条数据的 description 设为空字符串,UI 是崩了还是优雅降级?把 name 设为 40 个字,表格列宽会不会撑破?这些问题,Mock 阶段就能发现。


十、一张图总结 Mock 策略

复制代码
                      ┌──────────────────────┐
                      │   前端业务代码        │
                      │   axios.get('/api/   │
                      │   models?page=1)     │
                      └──────────┬───────────┘
                                 │
                    import.meta.env.DEV?
                     │            │
                    YES           NO
                     │            │
                     ▼            ▼
             ┌──────────────┐  ┌──────────────┐
             │  mockjs 拦截  │  │  真实网络请求  │
             │  · 生成 60 条 │  │  · 请求后端    │
             │  · 筛选+分页  │  │  · 返回真实数据 │
             │  · 模拟 400ms │  │               │
             └──────┬───────┘  └──────────────┘
                    │
                    ▼
            ┌──────────────────┐
            │  返回 Mock 数据    │
            │  { list, total }  │
            └──────────────────┘

前端代码只写 axios.get(),不关心数据来源。开发环境 mockjs 拦截,生产环境自动走真实网络。这就是 Mock 策略的工程之美。


上一篇:10 - 业务实现 3:模型标准发布模块

下一篇预告:数据有了,交互也完整了,但首屏 3.2 秒、打包 1.8MB------原型也要快。下一篇讲路由懒加载、Element Plus 按需引入、KeepAlive 缓存,把首屏压到 0.9 秒。

相关推荐
AndrewHZ3 小时前
图像处理入门006:图像质量评价三剑客:PSNR / SSIM / MSE 原理与代码实战
图像处理·计算机视觉·cv·psnr·rse·ssim
HyperAI超神经4 小时前
128K长上下文+智能体强化训练!LFM2.5-2.6B解锁端侧大模型高效部署;DETR用Transformer斩断NMS与Anchor,重塑目标检测
人工智能·深度学习·目标检测·计算机视觉·数据集·transformer
民乐团扒谱机4 小时前
【微实验】谐波乘积谱(HPS)算法深度解析:原理、数学与代码实现
开发语言·人工智能·python·算法·语音识别·音乐
科技每日热闻5 小时前
AI 赋能财务资金管理、风险预警、经营数据分析,合适的云上财务智能 Agent 有哪些?—— 优先采用 Amazon Quick 四链路解决方案
人工智能·ai·数据挖掘·数据分析
浪里镖客5 小时前
zed 2i相机坐标系与内参、外参
数码相机·opencv·计算机视觉
qyyyyy5705 小时前
PDF 表格翻译后总是错位?参数表、测试数据和跨页表格处理方法
ai·语音识别·机器翻译·数据库管理员·石墨文档
147API7 小时前
蒸馏模型数据漂移怎么监控,从输入变化到回归验证
人工智能·数据挖掘·回归
头发够用的程序员7 小时前
Nsight Systems 零基础入门:Trace采集与基础操作
人工智能·深度学习·神经网络·计算机视觉·边缘计算·分析工具
AndrewHZ1 天前
图像处理入门(第005期):颜色空间入门——RGB/HSV/CMYK/Lab
图像处理·python·opencv·hsv·rgb·色彩空间