开发指南Tiptap 富文本编辑器
Tiptap 富文本编辑器
🚀 Cheatsheet (快速执行)
目标: 5分钟内了解Tiptap富文本编辑器的使用和集成方式
快速使用:
import { TiptapRichEditor } from "@/components/ui/tiptap-rich-editor";
// 在表单中使用
<TiptapRichEditor
value={content}
onChange={(html, images) => {
setContent(html);
setContentImages(images);
}}
placeholder="开始编写内容..."
height={350}
/>
📖 概述
Tiptap是一个现代化的富文本编辑器,基于ProseMirror构建。我们将其集成到活动创建表单中,用于替代简单的textarea,提供更丰富的内容编辑体验。
主要功能
- ✅ 富文本格式化: 粗体、斜体、删除线、列表、引用等
- ✅ 图片上传: 支持拖拽、粘贴、点击上传图片到S3
- ✅ 图片管理: 自动追踪内容中的图片URLs
- ✅ 撤销/重做: 完整的编辑历史管理
- ✅ 响应式设计: 适配各种屏幕尺寸
- ✅ S3集成: 复用现有的图片上传基础设施
🔧 技术架构
核心依赖
{
"@tiptap/react": "^3.4.2",
"@tiptap/starter-kit": "^3.4.2",
"@tiptap/extension-image": "^3.4.2",
"@tiptap/extension-placeholder": "^3.4.2",
"@tiptap/extension-dropcursor": "^3.4.2"
}
数据库扩展
在Event模型中添加了两个新字段:
model Event {
// ... 其他字段
description String // 兼容原有的Markdown描述
richContent String? // 富文本HTML内容
contentImages String[] @default([]) // 内容中的图片URL数组
}
图片存储流程
graph LR
A[用户操作] --> B[文件验证]
B --> C[生成唯一文件名]
C --> D[调用S3上传API]
D --> E[获取图片URL]
E --> F[插入到编辑器]
F --> G[更新contentImages数组]
💡 实现细节
1. TiptapRichEditor 组件
位置: src/components/ui/tiptap-rich-editor.tsx
interface TiptapRichEditorProps {
value?: string; // 当前HTML内容
onChange?: (html: string, images: string[]) => void; // 内容变化回调
placeholder?: string; // 占位文本
height?: number; // 编辑器高度
}
关键特性:
- 自动提取HTML中的图片URLs
- 支持拖拽、粘贴、点击三种图片上传方式
- 文件类型和大小验证(最大5MB)
- 实时上传进度提示
2. 图片上传集成
复用现有的S3上传系统:
const uploadImage = async (file: File): Promise<string> => {
// 1. 生成唯一文件名
const fileName = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}.${extension}`;
const filePath = `event-content/${fileName}`;
// 2. 获取S3签名上传URL
const response = await fetch(`/api/uploads/signed-upload-url?...`);
const { signedUrl } = await response.json();
// 3. 直接上传到S3
await fetch(signedUrl, {
method: "PUT",
body: file,
headers: { "Content-Type": file.type },
});
// 4. 返回公共访问URL
return `${config.storage.endpoints.public}/${filePath}`;
};
3. 表单集成
在 BasicInfoForm.tsx
中替换了原有的Textarea:
<FormField
control={control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>活动详情 *</FormLabel>
<FormControl>
<TiptapRichEditor
value={field.value}
onChange={(html, images) => {
field.onChange(html);
// 同时更新图片数组字段
setValue('contentImages', images);
}}
placeholder="详细描述你的活动内容、流程、亮点..."
height={350}
/>
</FormControl>
<FormDescription>
支持富文本格式、图片上传。可拖拽图片或点击工具栏上传按钮插入图片。
</FormDescription>
</FormItem>
)}
/>
🎯 使用指南
基础使用
- 文本格式化: 使用工具栏按钮或快捷键(Ctrl+B粗体等)
- 插入图片:
- 拖拽图片到编辑器
- 从剪贴板粘贴图片
- 点击工具栏上传按钮
- 撤销重做: Ctrl+Z / Ctrl+Y 或工具栏按钮
高级功能
自定义配置:
<TiptapRichEditor
value={content}
onChange={(html, images) => {
// html: 富文本HTML内容
// images: 内容中所有图片的URL数组
}}
placeholder="自定义占位文本"
height={400} // 自定义高度
/>
图片自动管理: 编辑器会自动:
- 追踪内容中的所有图片
- 在onChange回调中返回图片URL数组
- 用于后续的图片管理和清理
🔄 数据流转
保存流程
- 用户编辑内容 → Tiptap生成HTML
- 组件提取图片URLs → 调用onChange回调
- 表单更新
description
(HTML) 和contentImages
(数组) - 提交到后端 → 保存到数据库
加载流程
- 从数据库读取
description
字段 - 传入TiptapRichEditor的value属性
- 编辑器渲染HTML内容
🛠️ 扩展开发
添加新的格式化选项
// 1. 添加扩展
import Underline from '@tiptap/extension-underline';
// 2. 在editor配置中添加
const editor = useEditor({
extensions: [
StarterKit,
Underline,
// ... 其他扩展
],
});
// 3. 添加工具栏按钮
<Button onClick={() => editor.chain().focus().toggleUnderline().run()}>
<Underline className="h-4 w-4" />
</Button>
自定义图片处理
const customImageUpload = async (file: File) => {
// 自定义上传逻辑
const url = await yourUploadFunction(file);
// 插入到编辑器
editor?.chain().focus().setImage({
src: url,
alt: file.name,
title: file.name
}).run();
};
📱 移动端适配
编辑器已针对移动端优化:
- 触摸友好的工具栏按钮
- 响应式布局
- 移动端键盘适配
- 触摸拖拽支持
🐛 常见问题
Q: 图片上传失败怎么办?
A: 检查以下几点:
- 文件大小是否超过5MB
- S3配置是否正确
- 网络连接是否正常
- 浏览器控制台是否有错误信息
Q: 如何限制编辑器功能?
A: 可以通过配置extensions来启用/禁用特定功能:
const editor = useEditor({
extensions: [
StarterKit.configure({
heading: false, // 禁用标题
codeBlock: false, // 禁用代码块
}),
// 只包含需要的扩展
],
});
Q: 如何获取纯文本内容?
A: 使用editor的getText()方法:
const plainText = editor?.getText() || '';
📈 性能考量
- 懒加载: 编辑器组件支持懒加载,避免首页加载时间过长
- 图片优化: 上传的图片会自动压缩和优化
- 内存管理: 编辑器在卸载时会自动清理资源
🔮 未来计划
- 支持表格编辑
- 添加代码高亮
- 支持数学公式
- 协作编辑功能
- 更多图片编辑功能(裁剪、滤镜等)
这个文档详细介绍了Tiptap富文本编辑器在我们系统中的集成方式。如有问题,请联系开发团队。