Skip to content

视频、音频和文件上传

视频、音频和文件统一通过 uploadAsset 接入。Core 只负责拿到上传结果并插入节点,真实上传、鉴权、进度、错误提示和 CDN 地址都由业务实现。

内置工具栏当前只暴露视频和文件上传入口。音频命令、解析和渲染能力仍保留,适合在自定义工具栏或业务流程中调用;待音频播放器选中体验稳定后,再恢复默认入口。

基础接入

vue
<script setup lang="ts">
import { ref } from 'vue'
import {
  ProEditorElementPlus,
  type EditorBehaviorOptions,
  type UploadAsset,
} from 'tiptap-vue-pro-element-plus'

const content = ref('')

const uploadAsset: UploadAsset = async (file, kind) => {
  const formData = new FormData()
  formData.append('file', file)
  formData.append('kind', kind)

  const res = await fetch('/api/upload-asset', {
    method: 'POST',
    body: formData,
  })
  const data = await res.json()

  return {
    url: data.url,
    name: data.name ?? file.name,
    size: data.size ?? file.size,
    mimeType: data.mimeType ?? file.type,
    fileTypeText: data.fileTypeText,
    uploadedAt: data.uploadedAt ?? Date.now(),
    duration: data.duration,
    poster: data.poster,
  }
}

const editorBehaviorOptions: EditorBehaviorOptions = {
  media: {
    video: {
      accept: 'video/mp4,video/webm',
      maxSize: 100 * 1024 * 1024,
      multiple: true,
      render: {
        displayMode: 'player',
        controls: true,
        muted: false,
        playsInline: true,
        allowFullscreen: true,
        allowDownload: false,
        allowPictureInPicture: true,
      },
    },
    audio: {
      accept: 'audio/mpeg,audio/wav,audio/ogg',
      maxSize: 30 * 1024 * 1024,
      multiple: true,
      render: {
        displayMode: 'player',
        controls: true,
        allowDownload: true,
      },
    },
    file: {
      accept: '.pdf,.doc,.docx,.xls,.xlsx,.zip',
      maxSize: 50 * 1024 * 1024,
      multiple: true,
      render: {
        showIcon: true,
        showName: true,
        showSize: true,
        showMimeType: true,
        showUploadedAt: true,
        openInNewTab: true,
        download: true,
      },
    },
  },
}
</script>

<template>
  <ProEditorElementPlus
    v-model="content"
    :upload-asset="uploadAsset"
    :editor-behavior-options="editorBehaviorOptions"
  />
</template>

本地 Mock 上传

还没有后端接口时,可以用本地 blob: URL 先验证视频、音频和附件节点:

ts
const uploadAsset: UploadAsset = async (file, kind) => {
  await new Promise((resolve) => window.setTimeout(resolve, 300))

  return {
    url: URL.createObjectURL(file),
    name: file.name,
    size: file.size,
    mimeType: file.type,
    fileTypeText: kind === 'file' ? '附件' : undefined,
    uploadedAt: Date.now(),
  }
}

blob: URL 只适合本地预览。生产环境应返回业务可长期访问的 URL,并尽量补齐 namesizemimeTypeuploadedAtposterduration 等元数据。

常见排查

  • 没有附件入口:确认传入了 uploadAsset,并且工具栏里保留 attachment 按钮。
  • 音频入口没显示:内置工具栏当前只暴露视频和文件上传入口;音频能力适合通过自定义工具栏或 core 命令调用。
  • 选择文件后没插入:确认 uploadAsset 返回字符串 URL、UploadedAssetnull,不要返回 undefined
  • 文件被跳过:检查对应 media.videomedia.audiomedia.fileacceptmaxSize
  • 线上刷新后打不开:确认上传结果不是本地 blob: URL,而是持久化 URL。

播放器或文件卡片

视频和音频都可以选择两种插入方式:

配置结果
displayMode: 'player'插入原生 <video> / <audio> 播放器
displayMode: 'file'插入文件卡片,按附件方式展示
ts
const editorBehaviorOptions: EditorBehaviorOptions = {
  media: {
    video: { render: { displayMode: 'file' } },
    audio: { render: { displayMode: 'file' } },
  },
}

multiple 默认 false,保持一次只选择一个文件。设置为 true 后,对应上传入口允许一次选择多个文件,并按选择顺序逐个调用 uploadAsset(file, kind)。内置工具栏当前只会为视频和文件创建选择入口;音频配置仍会被 core 命令使用。每个文件仍会独立应用 acceptmaxSize 校验,不通过的文件会跳过上传。

文件卡片会复用 media.file.render 的展示配置,可以控制图标、文件名、大小、文件类型标签、上传时间和时长;原始 MIME 会保存在 data-mime-type。默认文件类型标签会按当前 locale 输出,也可以通过 UploadedAsset.fileTypeText 覆盖。

iconMode: 'auto' 会根据 MIME 或扩展名识别常见类型:PDF、图片、视频、音频、压缩包、Word / Pages、Excel / CSV / Numbers、PPT / Keynote、文本和代码文件。识别结果会保存到 data-file-icon,方便 HTML 输出后继续按相同图标渲染。

插入后的上下文编辑

插入后的媒体和文件会在选中时显示上下文工具栏:

元素支持操作
视频 / 音频播放器编辑名称和播放配置、打开、下载、复制链接、切换为文件卡片、删除
文件卡片编辑文件名和展示开关、打开、下载、复制链接、删除
视频 / 音频文件卡片除文件操作外,还可以切换回播放器

这些编辑会写回节点属性。可序列化的数据会保存在 HTML / JSON 中;运行时函数仍不会写入文档内容。

HTML 输出

HTML 输出会尽量保持可逆:

  • 视频播放器使用 <video> 标准属性保存 srccontrolsmutedloopautoplaypreloadposter 等。
  • 音频播放器使用 <audio> 标准属性保存 srccontrolsmutedloopautoplaypreload 等。
  • 无法完整表达为标准属性的配置会写入 data-*,例如 data-namedata-sizedata-mime-typedata-file-type-textdata-durationdata-show-uploaded-at
  • 文件卡片使用 <a class="tvp-file-attachment"> 保存 URL 和展示开关。

函数不能序列化到 HTML。poster(asset)uploadedAtFormatdurationFormat 这类函数只影响当前运行时渲染;如果希望刷新后仍保留结果,请把计算结果放到 UploadedAsset.posteruploadedAtdurationfileTypeText 等字段里。

自定义渲染边界

内置渲染覆盖常见业务配置。如果需要完全自定义文件卡片结构,建议保持两层边界:

建议
数据层继续使用 UploadedAsset 和内置 data-* 属性保存可逆数据
UI 层在业务展示页或自定义 Tiptap extension 中按自己的组件渲染

这样 HTML / JSON 中仍然有稳定数据,UI 可以自由升级,不会把不可序列化的渲染函数塞进文档内容。

基于 Tiptap v3 + Vue 3 的社区封装。