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 秒。

相关推荐
动物园猫2 小时前
道路设施目标检测数据集:4类别、5,000张图像 | 目标检测
人工智能·目标检测·计算机视觉
2601_962077983 小时前
利用Python,四步掌握机器学习
python·机器学习·数据挖掘·数据分析·编程
天远Date Lab4 小时前
零信任架构实战:基于天远车信盟出险构建自动化汽车消费贷合规网关
人工智能·机器学习·计算机视觉·ocr
147API6 小时前
蒸馏项目什么时候该停,怎样切换到RAG、微调或模型路由
人工智能·深度学习·机器学习·数据挖掘
qq_425516187 小时前
双语字幕会议记录APP:多语言会议整理工具推荐
人工智能·智能手机·语音识别
YOLO视觉与编程7 小时前
YOLO / Labelme目标分割数据集增强扩充软件v1.0.0适用于YOLO全版本
人工智能·深度学习·yolo·计算机视觉
residual_fan7 小时前
持续对比强化学习(Continual Contrastive Reinforcement Learning)论文分享
人工智能·算法·数据挖掘·数据分析
leoZ2319 小时前
第 4 篇:布局骨架——页面壳、栅格、卡片
前端·javascript·vue.js·opencv·计算机视觉·数据挖掘·语音识别
2501_931819709 小时前
车载语音青岛方言数据集搭建阶段 信实翻译垂直场景标准赋能青岛方言标注公司作业
人工智能·语音识别
jay神9 小时前
深度学习如何确定Batch Size的大小?
人工智能·深度学习·计算机视觉·毕业设计·课程设计·batch