ByteNoteByteNote
给内容站搭发布页:React 受控表单与 AI 摘要实践
字

字节笔记本

2026年10月7日 · 约 8 分钟读完

给内容站搭发布页:React 受控表单与 AI 摘要实践

API中转
¥120

给内容站搭一个发布页:React 受控表单与 AI 摘要实践

凡是有内容生产环节的网站,后台都绕不开那一个页面:填标题、写正文、点发布。它看起来是后台里最没有技术含量的一块,真要动手搭,却会撞上一串值得推敲的决策:表单状态怎么管、简介字段值不值得单独一块输入区、AI 生成摘要放在哪个交互位置、分类用什么控件承载。本文沿着一条从最小可用到逐步增强的路线,把发布页完整过一遍,方案基于 React 与 shadcn/ui,思路可以直接搬到任何表单型页面上。

发布页表单结构与受控状态

从三个字段开始:受控组件是地基

第一版发布页只需要三样东西:标题输入框、正文文本域、发布按钮。用 React 写下来,核心是两个状态加一个提交函数:

tsx
const [title, setTitle] = useState('');
const [content, setContent] = useState('');

const handlePublish = () => {
  console.log('发布文章:', { title, content });
  // 实际项目中,这里调用 API 保存文章
};

输入框用 value 加 onChange 绑定状态,也就是典型的受控组件写法。有人嫌它啰嗦,非受控的 defaultValue 加 ref 同样能拿到值,但在发布页这种场景里,受控几乎是没有悬念的选择:后面要加的字数统计、发布前校验、一键生成摘要,全都依赖随时读到最新输入值;等表单复杂了再从非受控改回受控,返工成本远高于一开始就做对。

UI 组件直接用 shadcn/ui 的 Button、Input、Textarea。这类基于 Radix 封装的组件不带主题包袱,样式跟着 Tailwind 走,代码复制进项目就是自己的,改起来比引用黑盒组件库省心,这也是它近年成为 React 项目默认搭配的原因之一。

布局上,整个表单用 max-w-2xl 居中,一栏到底:发布页是高频写作界面,窄栏让每行文字保持舒适长度,视线不必横向扫射。每个字段配 label 并用 htmlFor 与输入框关联,点击标签即聚焦,读屏器也能念出字段名,这类无障碍细节在表单里是顺手就该做对的。

简介字段:小输入区,大用途

第二版加一个简介。有人觉得简介可有可无,反正正文里都有,其实它在站点侧承担三件事:列表页的卡片摘要、搜索引擎抓取的 description、社交分享时的预览文案。一段认真写的 100 到 200 字简介,对点击率和 SEO 都有直接收益。

控件上用 Textarea 而不是 Input,行数设为 3:简介比标题长、常需要换行斟酌,单行输入框会给作者一种「只能写一句话」的心理暗示。细节很小,但会实实在在影响写出来的简介质量。

受控状态在这里立刻派上用场:一行 summary.length 就能在输入框下方做实时字数统计,逼近 200 字时把计数染成警示色,比发布后才报错体验好得多。

一键生成摘要:先占位,再换芯

第三个迭代把「自动生成」按钮放在简介输入框旁边,这是整个页面里最有意思的一步。原型实现分两层,第一层是纯前端截断:

tsx
const generateSummary = () => {
  // 占位实现:取正文前 100 个字符
  const generated = content.slice(0, 100) + '...';
  setSummary(generated);
};

这个占位版本看似敷衍,却是接 AI 服务前最合理的验证方式:按钮的位置、生成后摘要框内容被替换、作者仍可手动修改,这三个交互事实先确定下来,后面换成什么模型都不影响页面结构。真要接 AI 时,把函数改成异步即可:按钮进入 loading 态,请求后端转发给摘要模型,失败时保留原有简介并给出行内提示,成功后填入结果但不锁定编辑。

摘要服务本身没有太多玄机:后端把正文裁到模型上下文允许的长度,系统提示词里写清两点要求——忠实于原文、不引入正文没有的观点,再限制输出不超过两百字。它更像一个普通的接口调用,前端真正要花心思的是交互细节。

这一步有几个容易踩的坑:

  • 截断切坏字符。JavaScript 字符串按 UTF-16 码元切分,slice(0, 100) 遇到 emoji 或生僻字可能从代理对中间切开,得到乱码。稳妥做法是先用 Array.from(content) 摊平成码点数组再截取,或用 Intl.Segmenter 按字素切。
  • 生成结果覆盖手写内容。作者刚润色过的简介被一键覆盖是很恼人的体验,生成前给个确认,或只在简介为空时允许自动生成。
  • 要不要自动触发。正文输入防抖后自动生成固然省事,但每次输入都调模型,成本和延迟都摆在那里。手动按钮加上「正文变更后摘要标记为过期」的折中,往往是更实际的方案。

AI 摘要生成链路

分类下拉:一个 Select 背后的信息架构

第四个迭代加入分类下拉,用 shadcn/ui 的 Select 组件实现。分类列表是个数组,map 成一组 SelectItem,选中值存进 category 状态:

tsx
const categories = ['技术', '文化', '科学', '艺术', '其他'];

<Select onValueChange={setCategory} value={category}>
  <SelectTrigger className="w-full mt-1">
    <SelectValue placeholder="选择文章分类" />
  </SelectTrigger>
  <SelectContent>
    {categories.map((cat) => (
      <SelectItem key={cat} value={cat}>{cat}</SelectItem>
    ))}
  </SelectContent>
</Select>

相比原生 select,Radix 的 Select 带来统一的视觉样式、完整的键盘导航和干净的受控 API,这在管理后台里是体验上最容易感知的差别。字段顺序上,把分类放在标题之后、简介之前,填写动线是「先定这篇文章属于什么,再概括它,再展开它」,符合写作的自然顺序。

值得一提的是分类与标签的分工:分类是单选的粗粒度归属,适合站点导航;标签是多选的细粒度关键词,适合站内检索与相关文章推荐。发布页早期只做分类完全够用,标签等内容的量级上来再加不迟。另外,分类列表硬编码在前端只是原型做法,正式版应当从后端接口拉取,并允许运营在后台维护。

发布之前:校验、草稿与预览

到这里,表单已经有四个字段和一个提交函数,但 handlePublish 里还只是 console.log。真正接 API 之前,还有三件事值得先做:

校验。标题非空、简介在 100 到 200 字之间、正文达到最低长度,这些规则用 zod 定义一份 schema,配合 react-hook-form 做校验和错误提示,是 React 表单目前最主流的组合;字段多到一定程度后,它比手写一串 if 判断好维护得多。

草稿。长文写作最怕丢稿。把表单状态定期同步到 localStorage,重新进入页面时恢复并提示,几十行代码就能避免最伤作者的事故。

预览。正文如果是 Markdown,发布前渲染一版预览,能拦截大部分排版问题。再往后才是富文本编辑器或 MDX 这类重方案——在没有真实写作痛感之前,不必急着上。

写在最后

回看整个演进:三字段受控表单打地基,简介字段兼顾读者与 SEO,AI 摘要按钮先跑通交互再换模型,分类下拉守住信息架构,最后用校验、草稿、预览兜住发布质量。每一步都只引入当时必需的复杂度,这正是搭任何后台页面的通用节奏:先把数据流和交互骨架立对,增强项都可以后补。下次要搭发布页、配置页或任何表单界面,不妨按这条路线走一遍。

相关文章

分享: