API 使用指南
本文详细介绍 Lovrabet SDK 的 API 使用方法,包括 CRUD 操作、查询参数、响应处理等核心功能。
开始之前,请确保已完成 SDK 配置。推荐使用 CLI 自动生成配置。
以下示例使用标准方式 client.models.dataset_xxx,也可以使用别名方式 client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df(功能完全一致)。
📊 基础 CRUD 操作
查询列表
// 基础查询
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter();
// 带参数查询
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage: 1, // 当前页码
pageSize: 20, // 每页记录数
status: "active", // 自定义查询条件
keyword: "search-term", // 关键词搜索
});
// 响应结构
console.log(response.tableData); // 数据列表
console.log(response.total); // 总记录数
console.log(response.currentPage); // 当前页
console.log(response.pageSize); // 页大小
获取单条记录
const user = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getOne("user-id");
console.log(user); // 用户详情对象
创建记录
const newUser = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.create({
name: "Jane Doe",
email: "jane@example.com",
status: "active",
});
console.log("新创建的用户 ID:", newUser.id);
从 v1.1.14 开始,OpenAPI 模式也支持 create() 操作。之前版本仅 WebAPI 模式支持。
更新记录
const updatedUser = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.update("user-id", {
name: "Jane Smith",
email: "jane.smith@example.com",
});
console.log("更新后的用户:", updatedUser);
从 v1.1.14 开始,OpenAPI 模式也支持 update() 操作。之前版本仅 WebAPI 模式支持。
删除记录
await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.delete("user-id");
console.log("用户已删除");
delete() 操作仅 WebAPI 模式(Cookie 认证)支持。OpenAPI 模式暂不支持删除操作。
导出 Excel v1.1.24+
将数据集导出为 Excel 文件,返回可供下载的文件 URL。
// 导出所有数据
const fileUrl = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.excelExport();
console.log(fileUrl);
// 'https://yuntoo-export-import.oss-cn-hangzhou.aliyuncs.com/xxx.xlsx?...'
// 在浏览器中打开下载
window.open(fileUrl, '_blank');
// 按条件筛选导出
const fileUrl = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.excelExport({
status: 'active',
createTime: '2025-01-01'
});
window.open(fileUrl, '_blank');
excelExport() 操作仅 WebAPI 模式(Cookie 认证)支持。OpenAPI 模式暂不支持此功能。
获取下拉选项 v1.1.18+
用于获取数据表的下拉选项数据,适用于 Select、Radio、Checkbox 等表单组件。
getSelectOptions() 是让数据表提供自身数据作为下拉选项,而不是查询其他表。
正确理解:
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions()- 查询 users 表自己的数据,生成用户列表选项- 这些选项可以用在:订单表的"选择用户"字段、文章表的"选择作者"字段等
常见误解:
- ❌ 在订单表中调用
client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.getSelectOptions()来获取用户列表 - ✅ 应该调用
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions()来获取用户列表
核心原则:谁的数据,谁提供选项
这体现了重要的软件设计原则:
- 🏗️ 单一职责原则(SRP) - 每个模型只负责管理和提供自己的数据
- 🔌 依赖倒置原则(DIP) - 订单表依赖用户表提供的接口,而非直接耦合
- 🎯 关注点分离(SoC) - 数据的提供者和消费者职责清晰分离
- 📦 服务提供者模式 - 表作为数据源,向外部提供标准化的数据服务
这种设计使系统更易维护、扩展和测试。
// 获取用户表的下拉选项(查询 users 表自身数据)
const userOptions = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions({
code: "user_id", // 用作选项值的字段名
label: "user_name", // 用作显示文本的字段名
});
// 返回标准格式
console.log(userOptions);
// [
// { label: '张三', value: 'user001' },
// { label: '李四', value: 'user002' },
// { label: '王五', value: 'user003' }
// ]
// 在 React 中使用
import { Select } from "antd";
const { Option } = Select;
function UserSelector() {
const [options, setOptions] = useState([]);
useEffect(() => {
loadOptions();
}, []);
const loadOptions = async () => {
const data = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions({
code: "id",
label: "name",
});
setOptions(data);
};
return (
<Select placeholder="选择用户">
{options.map((option) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
);
}
实际应用场景:
// 场景 1: 在订单表单中选择用户
// 调用 users 表提供用户选项
const userOptions = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions({
code: "user_id",
label: "user_name",
});
// 用在:<Select>选择下单用户</Select>
// 场景 2: 在文章表单中选择分类
// 调用 categories 表提供分类选项
const categoryOptions = await client.models.dataset_c1d2e3f4a5b6789012345678abcdef34.getSelectOptions({
code: "category_id",
label: "category_name",
});
// 用在:<Select>选择文章分类</Select>
// 场景 3: 在员工表单中选择部门
// 调用 departments 表提供部门选项
const deptOptions = await client.models.dataset_d1e2f3a4b5c6789012345678abcdef56.getSelectOptions({
code: "dept_id",
label: "dept_name",
});
// 用在:<Select>选择所属部门</Select>
// 场景 4: 在产品表单中选择供应商
// 调用 suppliers 表提供供应商选项
const supplierOptions = await client.models.dataset_e1f2a3b4c5d6789012345678abcdef78.getSelectOptions({
code: "supplier_id",
label: "supplier_name",
});
// 用在:<Select>选择供应商</Select>
记住核心原则: 哪个表的数据,就调用哪个表的 getSelectOptions()
getSelectOptions() 操作仅 WebAPI 模式(Cookie 认证)支持。OpenAPI 模式暂不支持此功能。
执行自定义 SQL v1.1.19+
执行在 Lovrabet 平台上配置的自定义 SQL 查询,适用于复杂统计和聚合查询场景。
从 v1.1.19 开始,推荐使用 client.sql.execute() 代替旧的 client.api.executeSql()。旧 API 仍然可用以保持向后兼容。
// 推荐方式:使用 client.sql 命名空间
const data = await client.sql.execute({
sqlCode: "fc8e7777-06e3847d"
});
// 检查执行结果
if (data.execSuccess && data.execResult) {
console.log(`查询成功,返回 ${data.execResult.length} 条数据`);
data.execResult.forEach((row) => {
console.log(row);
});
} else {
console.error("SQL 执行失败");
}
// 参数化查询(防止 SQL 注入)
const data = await client.sql.execute({
sqlCode: "fc8e7777-xxxxx",
params: {
userId: "123",
startDate: "2025-01-01",
endDate: "2025-12-31",
}
});
// 或使用直接传参方式(兼容)
const data = await client.sql.execute("fc8e7777-xxxxx", {
userId: "123",
startDate: "2025-01-01",
});
// 别名方式(向后兼容,不推荐)
const data = await client.api.executeSql("fc8e7777-xxxxx", {
userId: "123",
});
调用 BFF 后端函数 v1.3.0+
调用在 Lovrabet 平台上配置的 BFF (Backend For Frontend) 后端函数。
// 推荐方式:使用 client.bff 命名空间
const result = await client.bff.execute({
scriptName: 'calculatePrice',
params: { productId: '123', quantity: 10 }
});
// 带类型提示
interface PriceResult {
originalPrice: number;
discountedPrice: number;
discount: number;
}
const price = await client.bff.execute<PriceResult>({
scriptName: 'calculatePrice',
params: { productId: '123', quantity: 10 }
});
console.log(`原价: ${price.originalPrice}, 折后价: ${price.discountedPrice}`);
// 别名方式(向后兼容,不推荐)
const result = await client.api.bff('calculatePrice', {
productId: '123',
quantity: 10
});
SQL API 和 BFF API 支持完整的 TypeScript 类型、错误处理等高级功能。详见:
🔍 高级查询功能
分页查询
// 基础分页
const page1 = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage: 1,
pageSize: 10,
});
// 大数据集的完整加载
const loadAllData = async () => {
let allData = [];
let currentPage = 1;
let hasMore = true;
while (hasMore) {
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage,
pageSize: 100, // 使用较大的页面大小提高效率
});
allData.push(...response.tableData);
hasMore = response.tableData.length === 100;
currentPage++;
// 避免无限循环
if (currentPage > 1000) break;
}
return allData;
};
条件查询
// 多条件查询
const activeUsers = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
status: "active",
role: "admin",
createdAfter: "2024-01-01",
currentPage: 1,
pageSize: 50,
});
// 关键词搜索
const searchResults = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
keyword: "john", // 搜索包含 "john" 的记录
pageSize: 20,
});
排序查询 v1.1.16+
使用 sortList 参数进行单字段或多字段排序:
// 单字段排序
const sortedUsers = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter(
{ currentPage: 1, pageSize: 20 },
[{ createTime: "desc" }] // 按创建时间降序
);
// 多字段排序
const complexSort = await client.models.dataset_f7e6d5c4b3a2901234567890fedcba98.filter(
{ currentPage: 1, pageSize: 50 },
[
{ priority: "desc" }, // 第一优先级:按优先级降序
{ createTime: "desc" }, // 第二优先级:按创建时间降序
{ name: "asc" }, // 第三优先级:按名称升序
]
);
高级过滤查询 (filter API) v1.1.22+
filter() 方法提供了比 getList() 更强大和灵活的查询能力,支持复杂的条件组合、范围查询、模糊匹配等高级功能。
从 v1.1.22 版本开始,filter API 同时支持 OpenAPI 模式 和 WebAPI 模式。
// 范围查询:查找年龄在 18-35 岁之间的用户
const youngUsers = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
where: {
age: {
$gte: 18, // 大于等于 18
$lte: 35, // 小于等于 35
},
},
pageSize: 50,
});
// 集合匹配:查找特定状态的订单
const pendingOrders = await client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.filter({
where: {
status: { $in: ['pending', 'processing', 'confirmed'] },
},
sortList: [{ createTime: 'desc' }],
currentPage: 1,
pageSize: 20,
});
// 模糊搜索:搜索用户名包含特定关键词的用户
const searchUsers = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
where: {
username: { $contain: 'john' },
email: { $endWith: '@example.com' },
},
fields: ['id', 'username', 'email', 'createTime'],
});
// 复杂条件组合:使用 $and 和 $or
const complexQuery = await client.models.dataset_f7e6d5c4b3a2901234567890fedcba98.filter({
where: {
$and: [
{ price: { $gte: 100, $lte: 500 } },
{
$or: [
{ category: { $eq: 'electronics' } },
{ tags: { $contain: 'featured' } },
],
},
],
},
sortList: [{ price: 'asc' }],
pageSize: 30,
});
filter 相比 getList 的优势:
- 🎯 支持范围查询(
$gte、$lte) - 🔍 支持模糊匹配(
$contain、$startWith、$endWith) - 🧮 支持集合操作(
$in、$ne) - 🔗 支持复杂逻辑组合(
$and、$or) - 📋 支持字段筛选(
fields)减少数据传输
详细的操作符说明和更多示例,请参考 Filter API 完整指南。
复杂查询示例
// 组合查询:获取最近注册的活跃用户
const recentActiveUsers = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter(
{
status: "active",
registeredAfter: new Date(
Date.now() - 30 * 24 * 60 * 60 * 1000
).toISOString(), // 30天内
currentPage: 1,
pageSize: 20,
},
[{ registeredAt: "desc" }] // 按注册时间降序
);
// 获取特定角色的用户统计
const getUsersByRole = async (role: string) => {
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
role,
pageSize: 1, // 只需要总数
});
return {
role,
count: response.total,
users: response.tableData,
};
};
📈 批量操作
批量创建
// 批量创建用户
const createBatchUsers = async (
users: Array<{ name: string; email: string }>
) => {
const results = [];
// 分批处理,避免单次请求过大
const batchSize = 10;
for (let i = 0; i < users.length; i += batchSize) {
const batch = users.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map((user) => client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.create(user))
);
results.push(...batchResults);
// 避免请求过于频繁
if (i + batchSize < users.length) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
return results;
};
批量更新
// 批量更新用户状态
const batchUpdateStatus = async (userIds: string[], status: string) => {
const updates = userIds.map((id) =>
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.update(id, { status })
);
try {
const results = await Promise.all(updates);
console.log(`成功更新 ${results.length} 个用户`);
return results;
} catch (error) {
console.error("批量更新失败:", error);
throw error;
}
};
🔗 关联数据查询
获取关联数据
// 获取用户及其订单
const getUserWithOrders = async (userId: string) => {
// 获取用户信息
const user = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getOne(userId);
// 获取用户的订单
const orders = await client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.filter({
userId: userId,
currentPage: 1,
pageSize: 50,
});
return {
user,
orders: orders.tableData,
totalOrders: orders.total,
};
};
// 获取订单及客户信息
const getOrderWithCustomer = async (orderId: string) => {
const order = await client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.getOne(orderId);
const customer = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getOne(order.customerId);
return {
...order,
customer,
};
};
聚合查询模拟
// 用户订单统计
const getUserOrderStats = async (userId: string) => {
const orders = await client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.filter({
userId,
pageSize: 1000, // 获取所有订单
});
const stats = {
totalOrders: orders.total,
totalAmount: 0,
averageAmount: 0,
statusBreakdown: {} as Record<string, number>,
};
// 计算统计信息
orders.tableData.forEach((order) => {
stats.totalAmount += order.amount || 0;
stats.statusBreakdown[order.status] =
(stats.statusBreakdown[order.status] || 0) + 1;
});
stats.averageAmount =
stats.totalOrders > 0 ? stats.totalAmount / stats.totalOrders : 0;
return stats;
};
⚠️ 错误处理
基础错误处理(try-catch 模式)
try {
const users = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter();
console.log("获取用户成功:", users);
} catch (error) {
console.error("获取用户失败:", error);
// 根据错误类型进行处理
if (error.status === 404) {
console.log("资源不存在");
} else if (error.status === 403) {
console.log("权限不足");
} else if (error.status === 500) {
console.log("服务器错误");
}
}
使用 safe 函数(无 try-catch)v1.3.0+
import { safe } from "@lovrabet/sdk";
// 简洁的错误处理
const { data, error } = await safe(() =>
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter()
);
if (error) {
console.error("获取用户失败:", error.message, error.description);
return;
}
console.log("获取用户成功:", data);
并发请求的错误处理:
const [usersResult, ordersResult] = await Promise.all([
safe(() => client.models.users.filter()),
safe(() => client.models.orders.filter()),
]);
if (usersResult.error) {
console.error("用户加载失败");
}
if (ordersResult.error) {
console.error("订单加载失败");
}
// 使用数据
console.log("用户:", usersResult.data);
console.log("订单:", ordersResult.data);
高级错误处理
// 带重试的请求包装器
const withRetry = async <T>(
operation: () => Promise<T>,
maxRetries: number = 3,
delay: number = 1000
): Promise<T> => {
let lastError: any;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error: any) {
lastError = error;
// 某些错误不需要重试
if (
error.status === 400 ||
error.status === 401 ||
error.status === 403
) {
throw error;
}
if (attempt < maxRetries) {
console.log(`请求失败,${delay}ms 后重试 (${attempt}/${maxRetries})`);
await new Promise((resolve) => setTimeout(resolve, delay));
delay *= 2; // 指数退避
}
}
}
throw lastError;
};
// 使用重试包装器
const robustGetUsers = () => withRetry(() => client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter());
请求状态管理
// 请求状态管理类
class RequestManager {
private loadingStates = new Map<string, boolean>();
private errors = new Map<string, any>();
async executeRequest<T>(
key: string,
operation: () => Promise<T>
): Promise<T> {
this.loadingStates.set(key, true);
this.errors.delete(key);
try {
const result = await operation();
return result;
} catch (error) {
this.errors.set(key, error);
throw error;
} finally {
this.loadingStates.set(key, false);
}
}
isLoading(key: string): boolean {
return this.loadingStates.get(key) || false;
}
getError(key: string): any {
return this.errors.get(key);
}
}
// 使用示例
const requestManager = new RequestManager();
const loadUsers = () =>
requestManager.executeRequest("users_list", () =>
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter()
);
🎯 性能优化
请求缓存
// 简单的内存缓存
class SimpleCache {
private cache = new Map<string, { data: any; timestamp: number }>();
private ttl = 5 * 60 * 1000; // 5分钟TTL
set(key: string, data: any) {
this.cache.set(key, { data, timestamp: Date.now() });
}
get(key: string): any | null {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() - item.timestamp > this.ttl) {
this.cache.delete(key);
return null;
}
return item.data;
}
clear() {
this.cache.clear();
}
}
const cache = new SimpleCache();
// 带缓存的查询函数
const getCachedUsers = async (params: any = {}) => {
const cacheKey = `users_${JSON.stringify(params)}`;
// 尝试从缓存获取
let users = cache.get(cacheKey);
if (users) {
console.log("从缓存返回数据");
return users;
}
// 缓存未命中,从 API 获取
users = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter(params);
cache.set(cacheKey, users);
console.log("从 API 获取数据并缓存");
return users;
};
请求合并
// 请求去重和合并
class RequestDeduplicator {
private pendingRequests = new Map<string, Promise<any>>();
async request<T>(key: string, operation: () => Promise<T>): Promise<T> {
// 如果相同的请求正在进行中,直接返回该 Promise
if (this.pendingRequests.has(key)) {
return this.pendingRequests.get(key);
}
// 创建新的请求
const promise = operation().finally(() => {
// 请求完成后清除缓存
this.pendingRequests.delete(key);
});
this.pendingRequests.set(key, promise);
return promise;
}
}
const deduplicator = new RequestDeduplicator();
// 使用去重器
const getUsers = (params: any) =>
deduplicator.request(`users_${JSON.stringify(params)}`, () =>
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter(params)
);
// 多次调用相同参数的请求会被合并
Promise.all([
getUsers({ status: "active" }),
getUsers({ status: "active" }),
getUsers({ status: "active" }),
]); // 实际只会发起一次请求
分页优化
// 渐进式加载
class InfiniteLoader {
private data: any[] = [];
private currentPage = 1;
private hasMore = true;
private loading = false;
async loadMore(pageSize: number = 20): Promise<any[]> {
if (this.loading || !this.hasMore) {
return this.data;
}
this.loading = true;
try {
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage: this.currentPage,
pageSize,
});
this.data.push(...response.tableData);
this.hasMore = response.tableData.length === pageSize;
this.currentPage++;
return this.data;
} finally {
this.loading = false;
}
}
reset() {
this.data = [];
this.currentPage = 1;
this.hasMore = true;
this.loading = false;
}
getData() {
return this.data;
}
isLoading() {
return this.loading;
}
canLoadMore() {
return this.hasMore && !this.loading;
}
}
📊 数据处理工具
数据转换
// 数据转换工具
const transformUserData = (rawUser: any) => ({
id: rawUser.id,
name: rawUser.name,
email: rawUser.email,
displayName: `${rawUser.name} (${rawUser.email})`,
isActive: rawUser.status === "active",
createdAt: new Date(rawUser.created_at),
avatar: rawUser.avatar_url || "/default-avatar.png",
});
// 批量转换
const transformUserList = (response: any) => ({
users: response.tableData.map(transformUserData),
pagination: {
current: response.currentPage,
size: response.pageSize,
total: response.total,
pages: Math.ceil(response.total / response.pageSize),
},
});
// 使用
const getUsersForUI = async () => {
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter();
return transformUserList(response);
};
数据验证
// 数据验证工具
const validateUserData = (userData: any) => {
const errors: string[] = [];
if (!userData.name || userData.name.trim().length === 0) {
errors.push("姓名不能为空");
}
if (!userData.email || !/\S+@\S+\.\S+/.test(userData.email)) {
errors.push("请输入有效的邮箱地址");
}
if (userData.age && (userData.age < 0 || userData.age > 120)) {
errors.push("年龄必须在 0-120 之间");
}
return {
isValid: errors.length === 0,
errors,
};
};
// 安全的创建用户
const createUserSafely = async (userData: any) => {
const validation = validateUserData(userData);
if (!validation.isValid) {
throw new Error(`数据验证失败: ${validation.errors.join(", ")}`);
}
return await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.create(userData);
};
📖 下一步
掌握 API 使用后,您可以继续学习:
- 🎯 TypeScript 支持 - 类型安全的开发体验
- 🚀 高级功能 - 多项目支持和性能优化
- 🛠️ 实际应用示例 - React/Vue 集成示例
❓ 常见问题
Q: 如何处理大数据量的查询?
建议使用分页查询,避免一次性加载过多数据:
// 推荐:分页查询
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage: 1,
pageSize: 50, // 根据实际需求调整
});
// 不推荐:获取所有数据
// const allUsers = await loadAllData(); // 可能导致性能问题
Q: API 调用失败时如何调试?
启用详细的错误日志:
const client = createClient({
options: {
onError: (error) => {
console.group("API Error Details");
console.log("Message:", error.message);
console.log("Status:", error.status);
console.log("URL:", error.url);
console.log("Request Data:", error.requestData);
console.log("Response Data:", error.responseData);
console.groupEnd();
},
},
});
Q: 如何实现乐观更新?
// 乐观更新示例
const optimisticUpdate = async (userId: string, updates: any) => {
// 1. 立即更新 UI(乐观更新)
updateUserInUI(userId, updates);
try {
// 2. 发送 API 请求
const updatedUser = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.update(userId, updates);
// 3. 用服务器响应的数据更新 UI
updateUserInUI(userId, updatedUser);
return updatedUser;
} catch (error) {
// 4. 如果失败,回滚 UI 状态
revertUserInUI(userId);
throw error;
}
};