Skip to content

本地数据模式

纯前端商业版允许业务页面在后端尚未完成时直接运行。这里的“本地数据”是指 src/mock 内由前端函数返回的预置数据,不是网络 Mock 服务、IndexedDB 数据库,也不是生产环境的离线存储方案。

为什么保留预置数据

实际项目中,页面设计、交互评审和后端开发往往并行进行。如果所有页面都强制等待接口完成,前端只能长期维护临时请求和不稳定字段。预置数据模式解决的是研发阶段的协作问题:

  • 前端可以独立完成页面布局、交互、空状态和响应式验收。
  • 产品和客户可以在后端完成前评审业务流程。
  • 后端可以依据已经稳定的页面字段设计接口。
  • 业务模块可以逐步迁移,不需要一次切换全部数据源。

它不解决多用户一致性、事务、审计、备份、权限裁决和任务执行,这些能力必须由后端提供。

目录组织

text
src/mock/
├── account.ts
├── assistant/
├── content/
├── files/
├── monitor/
├── notification/
├── scheduler/
├── system/
├── workflow/
└── shared/
    ├── action.ts
    ├── clone.ts
    ├── delay.ts
    ├── filter.ts
    └── pagination.ts

业务数据按领域拆分,公共目录只提供少量稳定工具。项目有意避免建立 Provider、Repository、Registry 或通用 CRUD 类,因为它们会让临时预置数据变成另一套需要长期维护的数据层。

读取数据的基本模型

一个典型的本地列表函数会完成四件事:

text
静态数据源
  -> 根据页面实际条件过滤
  -> 分页
  -> 深拷贝结果
  -> 可选的演示延迟

返回类型通常保持为项目通用分页结构:

ts
type PaginatedResponse<T> = {
  records: T[]
  total: number
  current: number
  size: number
}

页面可以直接把这个函数交给 useTable

ts
import { getDemoUserList } from '@/mock/system/organization'

const { data, loading, pagination, refreshData } = useTable({
  core: {
    apiFn: getDemoUserList,
    apiParams: {
      current: 1,
      size: 20
    },
    columnsFactory: () => [
      { prop: 'username', label: '用户名' }
    ]
  }
})

虽然配置项仍叫 apiFn,但它只要求一个返回 Promise 的数据函数,因此可以来自 src/mock,也可以来自 src/api

为什么需要深拷贝

src/mock/shared/clone.ts 用于返回隔离副本,避免页面排序、表单编辑或表格格式化直接修改模块级静态数据。否则离开页面再返回时,演示数据可能已经被上一次操作污染。

深拷贝只提供当前浏览器会话内的数据隔离,不等于持久化。

写操作的处理原则

未接入后端的新增、编辑、删除、执行、撤销等操作必须明确告诉用户当前边界。页面使用 src/mock/shared/action.ts 的统一提示,而不是修改内存数组后返回“操作成功”。

ts
import { showDemoActionNotice } from '@/mock/shared/action'

showDemoActionNotice({
  action: 'update',
  subject: '用户'
})

为什么不做浏览器伪持久化

LocalStorage 或 IndexedDB 可以让一台浏览器“看起来保存成功”,但无法提供账号隔离、服务端鉴权、事务、审计、备份和多人一致性。它还会掩盖后端接口尚未完成的事实。因此项目宁可明确提示未写入,也不伪造生产能力。

页面与数据源的显式关系

页面通过导入路径明确选择数据源:

ts
// 当前使用预置数据
import { getDemoUserList } from '@/mock/system/organization'

迁移后:

ts
// 当前使用客户后端
import { fetchUserList as getDemoUserList } from '@/api/customer/user'

也可以保留相同导出名称:

ts
import { getDemoUserList } from '@/api/customer/user'

重点是保持参数和返回类型一致,让页面、表格列和搜索逻辑无需重写。

迁移一个页面

以用户列表为例,推荐按以下顺序进行。

1. 盘点页面调用

src/views/system/user/index.vue 找到:

  • 列表函数和查询参数。
  • 角色、部门、岗位等辅助数据。
  • 新增、编辑、删除操作。
  • 页面使用的 Api.* 类型。

2. 先实现查询接口

src/api/customer/user.ts 中实现:

ts
import request from '@/utils/http'

export function fetchUserList(params: Api.Identity.UserSearchParams) {
  return request.get<Api.Common.PaginatedResponse<Api.Identity.UserListItem>>({
    url: '/customer-api/users',
    params
  })
}

3. 替换页面导入

只替换目标页面的数据函数,其他页面继续读取 src/mock

4. 验证查询契约

至少测试:

  • 默认查询与加载状态。
  • 搜索条件和清空搜索。
  • 页码与每页数量。
  • 空列表。
  • 401、403、500 和网络失败。

5. 再迁移写操作

新增、编辑和删除需要后端同时实现:

  • 参数校验。
  • 资源级权限判断。
  • 冲突和重复数据处理。
  • 审计记录。
  • 正确的 HTTP 与业务错误响应。

接口成功后刷新列表,接口失败时保留表单数据并展示可理解的错误。

什么时候删除 Mock

不要在项目开始时一次性删除 src/mock。只有满足以下条件时才删除对应业务域:

  1. 页面不再导入该目录中的任何函数。
  2. 查询和写操作都已经连接真实后端。
  3. 空状态、错误状态和权限场景已经验收。
  4. 账号、通知、站点配置等隐藏依赖也已迁移或明确保留。
  5. rg "@/mock/<domain>" src 不再返回业务引用。

可以按业务域逐个清理,不要求最后保留一个空的 src/mock 目录。

不推荐的做法

  • 接口失败后自动回退到预置数据。
  • 用环境变量在同一页面偷偷切换两种返回格式。
  • 把客户真实数据提交到 src/mock
  • 为了模拟保存而维护复杂内存 CRUD。
  • 在 LocalStorage 中保存业务表、审批记录或敏感信息。
  • 尚未接入写接口时显示“保存成功”。

下一步阅读接口接入,把稳定的数据函数替换为真实 HTTP 请求。

根据 MIT 许可证发布