本地数据模式
纯前端商业版允许业务页面在后端尚未完成时直接运行。这里的“本地数据”是指 src/mock 内由前端函数返回的预置数据,不是网络 Mock 服务、IndexedDB 数据库,也不是生产环境的离线存储方案。
为什么保留预置数据
实际项目中,页面设计、交互评审和后端开发往往并行进行。如果所有页面都强制等待接口完成,前端只能长期维护临时请求和不稳定字段。预置数据模式解决的是研发阶段的协作问题:
- 前端可以独立完成页面布局、交互、空状态和响应式验收。
- 产品和客户可以在后端完成前评审业务流程。
- 后端可以依据已经稳定的页面字段设计接口。
- 业务模块可以逐步迁移,不需要一次切换全部数据源。
它不解决多用户一致性、事务、审计、备份、权限裁决和任务执行,这些能力必须由后端提供。
目录组织
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 类,因为它们会让临时预置数据变成另一套需要长期维护的数据层。
读取数据的基本模型
一个典型的本地列表函数会完成四件事:
静态数据源
-> 根据页面实际条件过滤
-> 分页
-> 深拷贝结果
-> 可选的演示延迟返回类型通常保持为项目通用分页结构:
type PaginatedResponse<T> = {
records: T[]
total: number
current: number
size: number
}页面可以直接把这个函数交给 useTable:
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 的统一提示,而不是修改内存数组后返回“操作成功”。
import { showDemoActionNotice } from '@/mock/shared/action'
showDemoActionNotice({
action: 'update',
subject: '用户'
})为什么不做浏览器伪持久化
LocalStorage 或 IndexedDB 可以让一台浏览器“看起来保存成功”,但无法提供账号隔离、服务端鉴权、事务、审计、备份和多人一致性。它还会掩盖后端接口尚未完成的事实。因此项目宁可明确提示未写入,也不伪造生产能力。
页面与数据源的显式关系
页面通过导入路径明确选择数据源:
// 当前使用预置数据
import { getDemoUserList } from '@/mock/system/organization'迁移后:
// 当前使用客户后端
import { fetchUserList as getDemoUserList } from '@/api/customer/user'也可以保留相同导出名称:
import { getDemoUserList } from '@/api/customer/user'重点是保持参数和返回类型一致,让页面、表格列和搜索逻辑无需重写。
迁移一个页面
以用户列表为例,推荐按以下顺序进行。
1. 盘点页面调用
从 src/views/system/user/index.vue 找到:
- 列表函数和查询参数。
- 角色、部门、岗位等辅助数据。
- 新增、编辑、删除操作。
- 页面使用的
Api.*类型。
2. 先实现查询接口
在 src/api/customer/user.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。只有满足以下条件时才删除对应业务域:
- 页面不再导入该目录中的任何函数。
- 查询和写操作都已经连接真实后端。
- 空状态、错误状态和权限场景已经验收。
- 账号、通知、站点配置等隐藏依赖也已迁移或明确保留。
rg "@/mock/<domain>" src不再返回业务引用。
可以按业务域逐个清理,不要求最后保留一个空的 src/mock 目录。
不推荐的做法
- 接口失败后自动回退到预置数据。
- 用环境变量在同一页面偷偷切换两种返回格式。
- 把客户真实数据提交到
src/mock。 - 为了模拟保存而维护复杂内存 CRUD。
- 在 LocalStorage 中保存业务表、审批记录或敏感信息。
- 尚未接入写接口时显示“保存成功”。
下一步阅读接口接入,把稳定的数据函数替换为真实 HTTP 请求。
