文档

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>
  )}
/>

🎯 使用指南

基础使用

  1. 文本格式化: 使用工具栏按钮或快捷键(Ctrl+B粗体等)
  2. 插入图片:
    • 拖拽图片到编辑器
    • 从剪贴板粘贴图片
    • 点击工具栏上传按钮
  3. 撤销重做: Ctrl+Z / Ctrl+Y 或工具栏按钮

高级功能

自定义配置:

<TiptapRichEditor
  value={content}
  onChange={(html, images) => {
    // html: 富文本HTML内容
    // images: 内容中所有图片的URL数组
  }}
  placeholder="自定义占位文本"
  height={400} // 自定义高度
/>

图片自动管理: 编辑器会自动:

  • 追踪内容中的所有图片
  • 在onChange回调中返回图片URL数组
  • 用于后续的图片管理和清理

🔄 数据流转

保存流程

  1. 用户编辑内容 → Tiptap生成HTML
  2. 组件提取图片URLs → 调用onChange回调
  3. 表单更新 description(HTML) 和 contentImages(数组)
  4. 提交到后端 → 保存到数据库

加载流程

  1. 从数据库读取 description 字段
  2. 传入TiptapRichEditor的value属性
  3. 编辑器渲染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: 检查以下几点:

  1. 文件大小是否超过5MB
  2. S3配置是否正确
  3. 网络连接是否正常
  4. 浏览器控制台是否有错误信息

Q: 如何限制编辑器功能?

A: 可以通过配置extensions来启用/禁用特定功能:

const editor = useEditor({
  extensions: [
    StarterKit.configure({
      heading: false, // 禁用标题
      codeBlock: false, // 禁用代码块
    }),
    // 只包含需要的扩展
  ],
});

Q: 如何获取纯文本内容?

A: 使用editor的getText()方法:

const plainText = editor?.getText() || '';

📈 性能考量

  • 懒加载: 编辑器组件支持懒加载,避免首页加载时间过长
  • 图片优化: 上传的图片会自动压缩和优化
  • 内存管理: 编辑器在卸载时会自动清理资源

🔮 未来计划

  • 支持表格编辑
  • 添加代码高亮
  • 支持数学公式
  • 协作编辑功能
  • 更多图片编辑功能(裁剪、滤镜等)

这个文档详细介绍了Tiptap富文本编辑器在我们系统中的集成方式。如有问题,请联系开发团队。