前端配合后端完成数据对接,和"接口联调"有重叠,但侧重点不同:
- 接口联调:偏过程,重点是"接口能不能通、问题怎么排查"。
- 数据对接:偏内容,重点是"数据长什么样、怎么传、怎么转、怎么存、怎么保证一致"。
一句话:
联调是让接口通,数据对接是让数据对、准、稳、可维护。
数据对接做不好,常见后果是:字段对不上、格式混乱、空值崩溃、精度丢失、状态不一致、改一处崩多处。
下面按"契约 → 格式 → 数据层 → 校验 → 状态 → 安全 → 流程"讲。
一、先定数据契约
数据对接的第一步不是写代码,而是和后端把"数据长什么样"定清楚。
1. 字段层面
每个字段至少明确:
- 字段名
- 类型:string、number、boolean、array、object、null
- 是否必填
- 默认值
- 示例值
- 含义说明
- 枚举值范围
示例:
json
{
"id": 1,
"name": "张三",
"status": 1,
"amount": 99.9,
"createdAt": "2026-10-17 10:30:00",
"tags": ["vip", "new"]
}
2. 结构层面
- 是否统一包裹:
{ code, message, data } - 分页结构:
list、total、page、pageSize - 嵌套层级:是否有多层对象
- 数组元素结构是否稳定
- 空数据返回
null、[]还是{}
3. 错误层面
- 错误码表
- 错误信息格式
- 业务错误和系统错误区分
- 字段级错误怎么返回
4. 推荐工具
- Apifox / YApi / Swagger
- OpenAPI 生成 TS 类型
- JSON Schema 校验
二、统一数据格式
前后端最容易扯皮的就是格式。建议项目一开始就定死。
1. 时间
- 统一用字符串:
YYYY-MM-DD HH:mm:ss - 或统一用时间戳:毫秒
- 明确时区
- 前端用 dayjs 统一格式化
ts
import dayjs from 'dayjs'
export const formatTime = (val: string | number) =>
dayjs(val).format('YYYY-MM-DD HH:mm:ss')
2. 金额
- 后端返回分,前端转元;或后端返回字符串
- 不要用浮点数直接计算
- 用
decimal.js或整数运算
ts
import Decimal from 'decimal.js'
export const fenToYuan = (fen: number) => new Decimal(fen).div(100).toFixed(2)
3. 枚举
- 后端返回 code,前端维护映射表
- 映射表统一放
constants或字典接口 - 不要在前端写死中文
ts
export const ORDER_STATUS = {
0: '待支付',
1: '已支付',
2: '已发货',
3: '已完成'
} as const
4. ID
- 大整数 ID 用字符串返回,避免 JS 精度丢失
- 前后端统一 ID 类型
5. 布尔
- 统一用
true/false - 不要混用
0/1、"Y"/"N"
6. 空值
- 空数组返回
[],不要返回null - 空对象返回
{}或null要约定 - 字符串空值统一
""还是null
7. 分页
统一格式:
json
{
"list": [],
"total": 100,
"page": 1,
"pageSize": 10
}
三、设计前端数据层
不要页面直接调 axios 拿数据就用。中间加一层,做转换、校验、兜底。
推荐分层:
text
页面/组件
↓
composables / hooks
↓
api 层(请求)
↓
adapter / mapper(数据转换)
↓
model / types(类型)
1. API 层
只负责请求,不处理业务:
ts
// api/user.ts
import request from '@/utils/request'
export const getUserList = (params: UserListParams) =>
request.get<PageResult<UserDTO>>('/user/list', { params })
export const getUserDetail = (id: string) =>
request.get<UserDTO>(`/user/${id}`)
2. DTO 与 Model 分离
- DTO:后端返回的原始结构
- Model:前端页面真正使用的结构
ts
// types/user.ts
export interface UserDTO {
id: string
user_name: string
status: number
created_at: string
}
export interface UserModel {
id: string
name: string
status: 'active' | 'disabled'
createdAt: string
}
3. Adapter 做转换
把后端数据转成前端模型:
ts
// adapters/user.ts
import type { UserDTO, UserModel } from '@/types/user'
export function toUserModel(dto: UserDTO): UserModel {
return {
id: dto.id,
name: dto.user_name || '-',
status: dto.status === 1 ? 'active' : 'disabled',
createdAt: dto.created_at
}
}
这样后端改字段,只改 adapter,页面不用动。
4. 统一处理空值和异常
ts
export function safeArray<T>(val: T[] | null | undefined): T[] {
return Array.isArray(val) ? val : []
}
export function safeObject<T>(val: T | null | undefined, fallback: T): T {
return val ?? fallback
}
四、数据校验与容错
后端数据不一定可信,前端要做兜底。
1. 类型校验
- 用 TypeScript 定义类型
- 运行时用 Zod / Valibot 校验关键数据
ts
import { z } from 'zod'
const UserSchema = z.object({
id: z.string(),
name: z.string(),
status: z.number()
})
const data = UserSchema.parse(res.data)
2. 空值兜底
ts
const list = res.data?.list ?? []
const total = res.data?.total ?? 0
3. 枚举兜底
未知枚举值显示"未知",不要显示 undefined。
4. 异常数据
- 图片 URL 为空,显示占位图
- 时间格式错,显示
- - 金额为 null,显示
0.00
五、数据状态管理
数据拿到后,怎么存、怎么更新、怎么同步。
1. 页面级数据
列表、表单、弹窗数据放页面内,用 ref / reactive。
2. 全局数据
用户信息、权限、字典放 Pinia:
ts
export const useUserStore = defineStore('user', () => {
const userInfo = ref<UserModel | null>(null)
async function fetchUserInfo() {
const dto = await getUserInfo()
userInfo.value = toUserModel(dto)
}
return { userInfo, fetchUserInfo }
})
3. 缓存策略
- 字典、配置类数据缓存到 localStorage 或 Pinia
- 列表数据不缓存,每次刷新
- 详情数据可按 ID 缓存
4. 数据刷新
- 增删改后刷新列表
- 用
refresh()或重新请求 - 避免手动改本地数组导致不一致
5. 乐观更新
体验要求高时,先更新 UI,失败再回滚:
ts
const old = list.value.slice()
list.value = list.value.filter(i => i.id !== id)
try {
await deleteUser(id)
} catch {
list.value = old
}
6. 并发与重复提交
- 按钮 loading
- 请求取消(AbortController)
- 防抖节流
六、数据安全
1. 敏感数据脱敏
手机号、身份证、银行卡前端展示时脱敏:
ts
export const maskPhone = (phone: string) =>
phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
2. 权限控制
- 后端返回权限码
- 前端控制菜单、按钮、字段显示
- 但接口权限必须后端校验,前端只做展示控制
3. 不要信任前端
- 金额、库存、状态等关键数据以后端为准
- 前端计算结果只做展示,提交时后端重新校验
4. 防 XSS
- 用户输入内容渲染时转义
- 富文本用 DOMPurify 清洗
七、数据一致性
1. 前后端字段一致
- 用 OpenAPI 生成类型
- 联调后更新文档
- 字段变更走通知
2. 多端一致
- Web、H5、小程序共用接口时,格式统一
- 枚举、字典从接口获取,不要各端写死
3. 缓存一致
- 更新数据后清缓存
- 关键数据加版本号
- 避免 localStorage 脏数据
4. 状态一致
- 列表和详情数据同步
- 编辑后返回列表要刷新
- 跨页面操作要通知
八、对接流程建议
- 定契约:字段、类型、格式、错误码、分页。
- 生成类型:OpenAPI 转 TS。
- Mock 开发:前端先跑通。
- 写 API 层:统一请求封装。
- 写 Adapter:DTO 转 Model。
- 页面接入:处理加载、空、错误。
- 联调排错:Network、抓包、请求 ID。
- 补异常:空值、超时、无权限、重复提交。
- 更新文档:契约变更同步。
- 回归测试:主流程 + 边界。
九、常见坑
- 字段名前后端不一致:
userNamevsuser_name - 类型不一致:数字传成字符串
- 大整数 ID 精度丢失
- 金额用浮点数计算
- 时间格式混用、时区错
- 枚举各端写死,改一处漏一处
- 空数组返回
null,页面崩溃 - 分页格式不统一
- 错误码各写各的,无法统一处理
- 页面直接调 axios,没有数据层
- 后端改字段,前端大面积改
- 前端信任后端数据,不做兜底
- 缓存不清理,显示旧数据
十、总结
前端配合后端完成数据对接的核心:
契约先行、格式统一、数据分层、转换隔离、校验兜底、状态可控、安全可信、文档同步。
一个健康的数据对接应该做到:
- 字段、类型、格式有明确约定
- 后端数据通过 adapter 转成前端模型
- 页面不直接依赖后端原始字段
- 空值、异常、错误码有统一处理
- 关键数据有类型校验
- 缓存和刷新策略清晰
- 敏感数据脱敏,权限后端校验
- 契约变更及时同步
如果你告诉我你们用的接口工具和项目类型,我可以给你一份更具体的数据对接清单和 adapter 代码模板。