表单页:新增与修改数据
列表能看、详情能改,接下来自然就是新建了。这篇文章用 SDK 的 create 和 update API 实现表单功能,包括表单状态管理、前端校验、提交和错误反馈。
本节你将学到
- 使用
createAPI 创建新数据 - 使用
updateAPI 修改数据 - 表单状态管理和校验
- 错误处理和用户反馈
需求
实现一个客户新建表单:输入姓名、电话、公司、状态,前端校验通过后提交,成功返回列表。
最终效果:

前置条件
信息
- 已完成 客户列表页面
- 已配置 React Router
实现步骤
步骤 1:创建表单组件
// src/pages/data-form.tsx
/**
* Title: 新建数据
*/
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { lovrabetClient } from "../api/client";
interface FormData {
name: string;
phone: string;
company: string;
status: "potential" | "dealed" | "lost";
}
const initialForm: FormData = {
name: "",
phone: "",
company: "",
status: "potential",
};
export default function DataForm() {
const navigate = useNavigate();
const [form, setForm] = useState<FormData>(initialForm);
const [errors, setErrors] = useState<Partial<Record<keyof FormData, string>>>(
{}
);
const [submitting, setSubmitting] = useState(false);
const MODEL_NAME = "dataset_customers"; // 标准方式,推荐
// const MODEL_NAME = "customers"; // 别名方式
// 表单校验
const validate = (): boolean => {
const newErrors: Partial<Record<keyof FormData, string>> = {};
if (!form.name.trim()) {
newErrors.name = "姓名不能为空";
}
if (!form.phone.trim()) {
newErrors.phone = "电话不能为空";
} else if (!/^1[3-9]\d{9}$/.test(form.phone)) {
newErrors.phone = "请输入正确的手机号";
}
if (!form.status) {
newErrors.status = "请选择状态";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
// 提交表单
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validate()) {
return;
}
setSubmitting(true);
try {
const result = await lovrabetClient.models[MODEL_NAME].create({
name: form.name.trim(),
phone: form.phone.trim(),
company: form.company.trim(),
status: form.status,
});
console.log("创建成功:", result);
alert("创建成功");
navigate("/data"); // 返回列表页
} catch (error) {
console.error("创建失败:", error);
alert("创建失败,请重试");
} finally {
setSubmitting(false);
}
};
// 取消
const handleCancel = () => {
if (form.name || form.phone || form.company) {
if (!confirm("确定要取消吗?已填写的内容将会丢失。")) {
return;
}
}
navigate("/data");
};
return (
<div className="data-form">
<div className="header">
<h1>新建数据</h1>
</div>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label>
姓名 <span className="required">*</span>
</label>
<input
type="text"
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
placeholder="请输入姓名"
disabled={submitting}
/>
{errors.name && <span className="error">{errors.name}</span>}
</div>
<div className="form-group">
<label>
电话 <span className="required">*</span>
</label>
<input
type="text"
value={form.phone}
onChange={(e) => setForm({ ...form, phone: e.target.value })}
placeholder="请输入手机号"
disabled={submitting}
/>
{errors.phone && <span className="error">{errors.phone}</span>}
</div>
<div className="form-group">
<label>公司</label>
<input
type="text"
value={form.company}
onChange={(e) => setForm({ ...form, company: e.target.value })}
placeholder="请输入公司名称"
disabled={submitting}
/>
</div>
<div className="form-group">
<label>
状态 <span className="required">*</span>
</label>
<select
value={form.status}
onChange={(e) =>
setForm({ ...form, status: e.target.value as any })
}
disabled={submitting}
>
<option value="potential">潜在</option>
<option value="dealed">成交</option>
<option value="lost">流失</option>
</select>
{errors.status && <span className="error">{errors.status}</span>}
</div>
<div className="actions">
<button type="button" onClick={handleCancel} disabled={submitting}>
取消
</button>
<button type="submit" disabled={submitting}>
{submitting ? "提交中..." : "保存"}
</button>
</div>
</form>
</div>
);
}
步骤 2:添加路由配置
// src/App.tsx
import { BrowserRouter, Routes, Route } from "react-router-dom";
import DataList from "./pages/data-list";
import DataDetail from "./pages/data-detail";
import DataForm from "./pages/data-form";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/data" element={<DataList />} />
<Route path="/data/new" element={<DataForm />} />
<Route path="/data/:id" element={<DataDetail />} />
</Routes>
</BrowserRouter>
);
}
步骤 3:列表页添加新建按钮
// src/pages/data-list.tsx
import { useNavigate } from "react-router-dom";
export default function DataList() {
const navigate = useNavigate();
return (
<div className="data-list">
<div className="header">
<h1>数据列表</h1>
<button onClick={() => navigate("/data/new")}>+ 新建</button>
</div>
{/* ... 其他内容 */}
</div>
);
}
知识点整理
create API
创建新记录:
// 基础用法
const result = await lovrabetClient.models.dataset_xxx.create({
name: "张三",
phone: "13800138000",
status: "potential",
});
// 返回创建后的完整数据
console.log(result.id); // 新创建记录的 ID
console.log(result.create_time); // 创建时间
| 参数 | 类型 | 说明 |
|---|---|---|
data | Record<string, any> | 要创建的数据对象 |
返回值:返回创建后的完整记录数据(包含自动生成的字段如 id、create_time 等)。
必填字段不能省略,字段名要和数据集字段名一致。OpenAPI 模式和 WebAPI 模式都支持(v1.1.14+)。
表单校验模式
提交时校验
const validate = (): boolean => {
const newErrors: Record<string, string> = {};
if (!form.name.trim()) {
newErrors.name = "姓名不能为空";
}
if (!form.phone.trim()) {
newErrors.phone = "电话不能为空";
} else if (!/^1[3-9]\d{9}$/.test(form.phone)) {
newErrors.phone = "请输入正确的手机号";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!validate()) return;
// 提交逻辑...
};
实时校验(输入时即时反馈)
const [errors, setErrors] = useState<Record<string, string>>({});
const handleFieldChange = (field: string, value: string) => {
setForm({ ...form, [field]: value });
// 实时清除错误
if (errors[field]) {
setErrors({ ...errors, [field]: "" });
}
};
const handleFieldBlur = (field: string) => {
const newErrors = { ...errors };
if (field === "name" && !form.name.trim()) {
newErrors.name = "姓名不能为空";
}
if (field === "phone") {
if (!form.phone.trim()) {
newErrors.phone = "电话不能为空";
} else if (!/^1[3-9]\d{9}$/.test(form.phone)) {
newErrors.phone = "请输入正确的手机号";
}
}
setErrors(newErrors);
};
常见校验规则
必填校验
if (!value || !value.trim()) {
return "该字段不能为空";
}
长度校验
if (value.length < 2) {
return "至少需要2个字符";
}
if (value.length > 50) {
return "不能超过50个字符";
}
手机号校验
// 简单校验
if (!/^1\d{10}$/.test(value)) {
return "请输入正确的手机号";
}
// 严格校验(运营商号段)
if (!/^1[3-9]\d{9}$/.test(value)) {
return "请输入正确的手机号";
}
邮箱校验
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
return "请输入正确的邮箱地址";
}
数字范围校验
const num = Number(value);
if (isNaN(num)) {
return "请输入有效的数字";
}
if (num < 0 || num > 100) {
return "请输入0-100之间的数字";
}
几个好的做法
使用受控组件
// 推荐:受控组件
<input
type="text"
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
/>
// 不推荐:非受控组件
<input type="text" defaultValue={form.name} ref={nameRef} />
trim 处理输入
await lovrabetClient.models.dataset_xxx.create({
name: form.name.trim(), // 去除首尾空格
phone: form.phone.trim(), // 去除首尾空格
company: form.company.trim(), // 去除首尾空格
});
提交中禁用表单
<button type="submit" disabled={submitting}>
{submitting ? "提交中..." : "保存"}
</button>
<input disabled={submitting} />
取消时确认
const handleCancel = () => {
// 检查是否有已填写的内容
const hasContent = Object.values(form).some((v) => v && v.trim());
if (hasContent && !confirm("确定要取消吗?已填写的内容将会丢失。")) {
return;
}
navigate("/data");
};
常见问题
Q: 创建成功后如何获取新数据的 ID?
create 返回值包含创建后的完整数据:
const result = await lovrabetClient.models.dataset_xxx.create({
name: "张三",
phone: "13800138000",
});
console.log(result.id); // 新创建的 ID
console.log(result.create_time); // 创建时间
Q: 必填字段校验在哪里做?
建议两层校验:前端校验提升用户体验(立即反馈),后端通过数据集配置的"必填"设置确保数据完整性。
// 前端校验
if (!form.name.trim()) {
setErrors({ name: "姓名不能为空" });
return;
}
// 提交(后端也会校验)
await lovrabetClient.models.dataset_xxx.create(form);
Q: 如何处理枚举字段?
使用 select 下拉框:
<select
value={form.status}
onChange={(e) => setForm({ ...form, status: e.target.value as any })}
>
<option value="potential">潜在</option>
<option value="dealed">成交</option>
<option value="lost">流失</option>
</select>
枚举值需要与数据集配置中的枚举值完全一致。
Q: 如何实现多步骤表单?
用状态管理当前步骤:
const [step, setStep] = useState(1);
const [formData, setFormData] = useState({});
const nextStep = () => {
// 校验当前步骤
if (!validateStep(step)) return;
setStep(step + 1);
};
const prevStep = () => {
setStep(step - 1);
};
// 最后一步提交
const handleSubmit = async () => {
await lovrabetClient.models.dataset_xxx.create(formData);
navigate("/data");
};
本节小结
恭喜你完成了新增与编辑功能!你学会了:
| 知识点 | 说明 |
|---|---|
create API | 创建新记录,返回包含 ID 的完整数据 |
update API | 更新记录,需要传入 ID |
| 表单校验 | 前端校验提升用户体验 |
| 错误处理 | try-catch 捕获异常,友好提示用户 |
最佳实践
- 前端校验做基础格式检查(如手机号格式)
- 业务规则校验放在后端(如唯一性检查)
- 始终使用 try-catch 处理异常
- 提交时禁用按钮防止重复提交
下一步
- 主从表:客户与订单 — 学习复杂的主子表单和事务处理
相关阅读
核心文档
进阶主题
- 销售报表:自定义 SQL — 复杂统计使用 SQL 查询
- 多表关联查询 — filter 多表查询 JOIN 关联数据
难度等级:L1 | 预计耗时:30 分钟