npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

jvs-ppt-ui

v0.0.34

Published

#### 方式 A:npm 安装(发布到 npm 或公司私有库后) ```bash npm install jvs-ppt-ui # 或者 pnpm add jvs-ppt-ui ```

Readme

1. 安装方式

方式 A:npm 安装(发布到 npm 或公司私有库后)

npm install jvs-ppt-ui
# 或者
pnpm add jvs-ppt-ui

方式 B:Monorepo / 本地直接引用

在同一个 workspace 或通过 npm link 引用本地构建好的 dist/lib 产物。


2. 全局注册(Plugin 模式)

在宿主应用的 main.ts 中引入并注册:

import { createApp } from 'vue'
import App from './App.vue'

// 引入 JVS-PPT 插件及其独立样式(已自包含所有内置组件与样式,宿主无需安装 Element Plus)
import JvsPptUi from 'jvs-ppt-ui'
import 'jvs-ppt-ui/dist/lib/style.css'

const app = createApp(App)
app.use(JvsPptUi) // 注册全局组件: <JvsPptEditor /> 和 <PptEditor />
app.mount('#app')

💡 零外部依赖与资产完全自包含(开箱即用):

  1. jvs-ppt-ui 已内置自包含所需的组件与完整样式,宿主项目无需安装、引入 Element Plus,也不受宿主项目自身 UI 库的任何限制,绝不产生依赖冲突。
  2. 图标库 100% 自包含:组件库已将阿里巴巴 Iconfont SVG Symbol 与字体样式全量打入 JS/CSS 产物中,宿主应用的 index.html 彻底不需要引入任何 <script> 或 <link> 图标文件,直接使用即可完整回显所有图标。

3. 按需引入组件

无需全局注册,可在具体页面中直接引入组件与类型:

<template>
  <div class="editor-wrapper">
    <PptEditor
      ref="pptEditorRef"
      v-model="currentDeck"
      :show-header="true"
      :config="{ showSaveButton: true }"
      @save="onSave"
      @change="onChange"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { PptEditor, type PptEditorApi, type DeckData } from 'jvs-ppt-ui'
import 'jvs-ppt-ui/dist/lib/style.css'

const pptEditorRef = ref<PptEditorApi | null>(null)
const currentDeck = ref<DeckData | null>(null)

const onSave = (deck: DeckData) => {
  console.log('保存 PPT 数据:', deck)
  // 调用后端 API 存储幻灯片数据
}

const onChange = (data: any) => {
  console.log('内容变更:', data)
}
</script>

<style scoped>
.editor-wrapper {
  width: 100vw;
  height: 100vh;
}
</style>

四、组件 Props 配置清单

| 属性名 | 类型 | 默认值 | 说明 | | :--- | :--- | :--- | :--- | | modelValue | Slide[] \| DeckData \| null | null | 幻灯片数据,支持 v-model 双向绑定 | | readonly | boolean | false | 是否只读/预览模式(禁用工具栏与编辑篡改,彻底屏蔽双击添加、元素拖拽、选中框选,同时完整保留浏览缩放、幻灯片放映、PPT 导入与 PPT 导出能力) | | showHeader | boolean | true | 是否展示顶部导航与工具栏 | | showMenu | boolean | true | 是否显示顶部左上角主菜单按钮(导入/导出/快捷键等弹窗菜单)。设置为 false 时,彻底隐藏主菜单按钮,左上角仅保留文档标题与重命名图标。适合弹窗嵌入或已有自定义操作栏场景 | | toolbarMode | 'auto' \| 'more' \| 'full' | 'auto' | 工具栏布局模式:'auto' 智能自适应窄屏(宽度不足时自动折叠收纳);'more' 强制收纳扩展功能为【··· 更多】功能模块面板(核心保留撤销/恢复/文本/形状/线条,其余收纳至弹出卡片,极其适合弹窗与小屏幕场景);'full' 强制全平铺展示 | | showSidebarLeft | boolean | true | 是否展示左侧幻灯片缩略图列表 | | showSidebarRight | boolean | true | 是否展示右侧对象/页面属性面板 | | enableAi | boolean | undefined | 是否启用 AI 功能(未配置或为 false 时不显示一键美化功能) | | aiApiUrl | string | '' | AI 一键美化请求接口地址。若未配置或未开启,则不显示一键美化功能 | | aiHeaders | Record<string, string> | {} | AI 请求接口的附加 Headers(如鉴权 Token) | | enableMaterial | boolean | undefined | 是否启用素材库功能(未配置或为 false 时不显示素材功能) | | materialApiUrl | string | '' | 素材库获取所有内容的 API 地址。只要配置了此地址,顶部工具栏就会自动展示【素材】按钮 | | materialListApiUrl | string | '' | 素材库获取所有内容 API 地址的别名(与 materialApiUrl 等价) | | materialTypeApiUrl | string | '' | 素材库获取单独类型/分类素材的 API 地址(可选,点击分类标签时触发) | | materialSaveApiUrl | string | '' | 素材库保存当前页为素材的 API 地址(可选,配置后出现【保存当前页】按钮) | | materialHeaders | Record<string, string> | {} | 素材库各接口请求时附加的统一 Headers(如鉴权 Token) | | enableMcp | boolean | false | 是否监听并从 URL ?deckId= 自动拉取 MCP 稿件 | | customUpload | CustomUploadHandler | undefined | 自定义资源上传函数:导入 PPTX/JSON、上传图片、视频、音频时自动走该函数并返回服务器 URL | | uploadHandler | CustomUploadHandler | undefined | 自定义资源上传函数别名(与 customUpload 等价) | | enableTemplate | boolean | true | 是否启用左侧“更多布局”中的【模板】库功能(设为 false 时隐藏“模板”Tab 与“加载本地模板”按钮,仅展示“版式”) | | enableTemplates | boolean | true | enableTemplate 的别名 | | templates | SlideTemplateInput[] | undefined | 自定义传入的模板列表(支持传入完整 Deck JSON 对象或按需懒加载索引项) | | templateBaseUrl | string | '' | 模板静态资源的自定义基础路径(如 'https://cdn.example.com/ppt-templates'),用于按需拉取 templates-first-page.json 与模板 JSON | | replaceBuiltinTemplates | boolean | true | 传入自定义 templates 时是否替换默认内置模板(设为 false 则与内置模板合并展示) | | config | PptEditorConfig | {} | 高级配置项(详见下方) |

config 对象字段说明:

interface PptEditorConfig {
  /** 演示文稿初始标题 */
  title?: string
  /** 是否显示左上角主菜单按钮(导入/导出/快捷键等弹窗菜单),默认为 true */
  showMenu?: boolean
  /** 工具栏布局模式:'auto' (自适应折叠)、'more' (收纳更多功能模式,适合弹窗/小屏)、'full' (全展开),默认为 'auto' */
  toolbarMode?: 'auto' | 'more' | 'full'
  /** 是否启用 AI PPT 智能生成与一键美化功能。未配置或为 false 时不显示一键美化功能 */
  enableAi?: boolean
  /** AI 一键美化请求接口地址。若未配置或未开启,则不显示一键美化功能 */
  aiApiUrl?: string
  /** AI 请求接口的额外 Headers(如鉴权 Token) */
  aiHeaders?: Record<string, string>
  /** 是否启用素材库功能。未配置或为 false 时不显示素材功能 */
  enableMaterial?: boolean
  /** 素材库获取所有内容的 API 地址(配置后自动激活并显示素材按钮) */
  materialApiUrl?: string
  /** 素材库获取所有内容接口地址的别名(与 materialApiUrl 等价) */
  materialListApiUrl?: string
  /** 素材库按类型获取单独分类素材的接口地址 */
  materialTypeApiUrl?: string
  /** 素材库保存当前页面为素材的接口地址 */
  materialSaveApiUrl?: string
  /** 素材库请求接口的额外 Headers(如鉴权 Token) */
  materialHeaders?: Record<string, string>
  /** 是否启用 LaTeX 公式编辑 */
  enableFormula?: boolean
  /** 是否启用 Unsplash 在线图库 */
  enableUnsplash?: boolean
  /** 自定义资源上传函数(若配载则导入文件或上传图片/音视频时自动走此函数上传并返回服务器文件地址) */
  customUpload?: (fileOrBlob: File | Blob, context: UploadResourceContext) => Promise<string> | string
  /** 自定义资源上传函数别名(等价于 customUpload) */
  uploadHandler?: (fileOrBlob: File | Blob, context: UploadResourceContext) => Promise<string> | string
  /** 是否启用左侧“更多布局”中的【模板】库功能,默认为 true(设为 false 时隐藏模板 Tab 及加载本地模板按钮) */
  enableTemplate?: boolean
  /** enableTemplate 的别名 */
  enableTemplates?: boolean
  /** 外部自定义传入的模板列表(支持传入完整 Deck JSON 对象、按需加载索引项或 SlideTemplateItem) */
  templates?: SlideTemplateInput[]
  /** 模板静态资源的自定义基础路径(如 'https://cdn.example.com/ppt-templates') */
  templateBaseUrl?: string
  /** 传入自定义 templates 时是否替换默认内置模板,默认为 true(设为 false 则与内置模板合并) */
  replaceBuiltinTemplates?: boolean
}

五、组件 Events 事件清单

| 事件名 | 参数 | 说明 | | :--- | :--- | :--- | | update:modelValue | (deck: DeckData) | 数据变化时触发,供 v-model 自动双向同步 | | change | ({ slides, activeIndex, title }) | 幻灯片内容、顺序或数量变化时触发 | | save | ({ format, data, deck }) | 外部调用 api.save() 时触发,附带产物与工程数据 | | slideChange | ({ index, slide }) | 当前选中的幻灯片切换时触发 | | export | ({ format, title, data }) | 导出 PPTX/JSON/图片完成后触发 | | ready | (api: PptEditorApi) | 编辑器初始化挂载完成时触发,返回 API 实例 |


六、组件 Expose API(编程式接口)

通过给组件绑定 ref="editorRef",可以调用以下所有对外暴露的方法:

const editorRef = ref<PptEditorApi | null>(null)

核心保存 API(获取文件或 JSON 数据)

  • save(options?: SaveOptions): Promise<any> 保存通用核心入口。使用方可直接指定想要的格式(返回 JSON、JSON 文件、真实 PPTX 二进制文件或图片 Zip 包):

    // 1. 获取 JSON 格式工程数据(默认):
    const deck = await editorRef.value.save() 
    // 或:await editorRef.value.save({ format: 'json' })
    
    // 2. 获取真实的 PPTX 二进制 Blob 对象(可直接 FormData 上传 OSS / 后端存储):
    const pptxBlob = await editorRef.value.save({ format: 'pptx' })
    const formData = new FormData()
    formData.append('file', pptxBlob, `${deck.title}.pptx`)
    await uploadToServer(formData)
    
    // 3. 获取带文件名的 PPTX File 对象:
    const pptxFile = await editorRef.value.save({ format: 'pptx-file' })
    
    // 4. 获取 JSON 格式的 Blob 对象:
    const jsonBlob = await editorRef.value.save({ format: 'json-blob' })
    
    // 5. 获取全部页面渲染图片的 ZIP 压缩包 Blob:
    const zipBlob = await editorRef.value.save({ format: 'images-zip' })
    
    // 6. 获取内容的同时,自动触发浏览器下载:
    const blob = await editorRef.value.save({ format: 'pptx', autoDownload: true })
  • getSaveData(): DeckData 直接同步获取当前完整的工程 JSON 对象(含 title、slides、slideMasters、themeClrScheme 等)。

  • getSaveJsonBlob(): Blob 直接获取 JSON 格式的 Blob。

  • getSaveJsonFile(filename?: string): File 直接获取包含文件名的 JSON File 对象。

  • getSavePptxBlob(): Promise<Blob | null> 直接异步生成并返回标准 PPTX 二进制 Blob 对象。

  • getSavePptxFile(filename?: string): Promise<File | null> 直接异步生成并返回标准 PPTX File 对象。

其它工程与页面操作

  • loadFile(source: File | Blob | ArrayBuffer | string | any, options?: LoadSourceOptions): Promise<LoadSourceResult> 【核心万能加载与渲染 API】:支持直接传入 本地 File 对象、网络下载的 Blob 二进制流、ArrayBuffer、JSON 字符串或对象、XML 字符串/Blob。插件会自动智能探测数据格式,并一键完成解析与整屏渲染! 别名:importFile 同步可用。

    // 场景 1:用户在宿主页面上传本地 PPTX / JSON 文件
    const handleFileUpload = async (e: Event) => {
      const file = (e.target as HTMLInputElement).files?.[0]
      if (file) {
        const res = await editorRef.value.loadFile(file)
        console.log(`成功加载 ${res.title},共 ${res.slidesCount} 页`)
      }
    }
    
    // 场景 2:直接加载后端接口下发的 PPTX 二进制流 Blob
    const res = await fetch('/api/ppt/download?id=123')
    const blob = await res.blob()
    await editorRef.value.loadFile(blob, { title: '业务汇报.pptx' })
    
    // 场景 3:直接加载 JSON 工程字符串或 Deck 对象
    await editorRef.value.loadFile(jsonString)
    
    // 场景 4:加载 XML 字符串
    await editorRef.value.loadFile(xmlString, { type: 'xml' })
    
    // 场景 5:直接传入远程或本地文件 URL 地址(自动从网络下载并渲染)
    await editorRef.value.loadFile('https://example.com/files/2024季度汇报.pptx')
    // 支持传递鉴权 Headers
    await editorRef.value.loadFile('/api/ppt/files/demo.pptx', {
      fetchOptions: {
        headers: { Authorization: 'Bearer token_xxx' }
      }
    })
  • getSlides(): Slide[] 获取当前所有幻灯片数组副本。

  • getDeck(): DeckData 获取当前完整工程数据。

  • setSlides(slides: Slide[]): void 直接设置/替换幻灯片列表。

  • loadDeck(deck: DeckData): void 加载完整的演示文稿工程数据。

页面导航与管理

  • getActiveSlide(): Slide | null 获取当前选中的幻灯片数据。
  • getActiveIndex(): number 获取当前选中幻灯片的索引(从 0 开始)。
  • selectSlide(index: number): void 切换到指定索引的幻灯片。
  • addSlide(index?: number): void 在指定位置(或末尾)新增空白幻灯片。
  • deleteSlide(index?: number): void 删除指定索引(或当前选中)的幻灯片。

导出功能

  • exportPptx(): Promise<void> 触发导出标准 .pptx 演示文稿文件并下载。
  • exportJson(): void 触发导出标准 .json 工程文件并下载。
  • exportImages(): Promise<void> 触发将全部幻灯片导出为高清晰度图片并打包为 .zip 下载。

历史与播放

  • undo(): void 撤销一步操作。
  • redo(): void 重做一步操作。
  • canUndo: Ref<boolean> 当前是否可以撤销。
  • canRedo: Ref<boolean> 当前是否可以重做。
  • startPresentation(): void 进入全屏放映演示模式。
  • stopPresentation(): void 退出放映模式。

画布控制与通知

  • zoomIn(): void 放大画布。
  • zoomOut(): void 缩小画布。
  • resetZoom(): void 重置画布缩放比例以适合当前屏幕。
  • notify(msg: string, type?: 'success' | 'warning' | 'error' | 'info'): void 弹出编辑器内部的 Toast 提示。

素材库与 AI 美化编程式配置

  • isMaterialEnabled: Ref<boolean> 当前素材库功能是否可用/展示(只要配置了获取所有内容的接口即为 true)。
  • setMaterialConfig(config): void 编程式动态更新素材库的各项 API 地址与 Headers:
    editorRef.value.setMaterialConfig({
      listApiUrl: '/jvs-dam/api/ppt/material/list',
      typeApiUrl: '/jvs-dam/api/ppt/material/by-type',
      saveApiUrl: '/jvs-dam/api/ppt/material/save',
      headers: { Authorization: 'Bearer token_xxx' },
      enable: true
    })
  • saveMaterial(meta?: { title?: string; category?: string; slide?: Slide }): Promise<any> 编程式将指定或当前幻灯片保存到素材库接口中。
  • isAiBeautifyEnabled: Ref<boolean> 当前一键美化功能是否可用/展示。
  • setAiConfig(config): void 编程式动态更新 AI 一键美化接口地址与 Token。

资源上传配置

  • setCustomUploadHandler(handler: CustomUploadHandler | null): void 动态配载或重置自定义资源上传函数。配载后,导入文件、JSON 或手动上传图片/音视频时将自动走该函数并返回服务器 URL。
  • getCustomUploadHandler(): CustomUploadHandler | null 获取当前已配载的上传函数。

模板库编程式配置

  • isTemplateEnabled: Ref<boolean> 当前左侧“更多布局”中的【模板】功能是否启用。
  • setTemplateConfig(config?: { enable?: boolean; templates?: SlideTemplateInput[]; baseUrl?: string; replaceBuiltin?: boolean }): void 动态更新模板库开关、自定义模板列表及静态资源基础路径。
  • setTemplates(templates: SlideTemplateInput[], options?: { replaceBuiltin?: boolean; baseUrl?: string }): void 直接替换或合并当前模板库列表。
  • addTemplate(template: SlideTemplateInput): SlideTemplateItem 动态追加单个自定义模板到模板库顶部。
  • getTemplates(): SlideTemplateItem[] 获取当前已加载的模板库列表。

底层深度控制

  • slidesComposable: any 直接暴露内部 useSlides() composable 实例,供需要进行极深度定制的开发者直接访问内部所有的元素修改、主题色、母版版式等底层状态。

七、顶部导航与工具栏插槽(Slots)

当作为插件嵌入宿主系统时,PptEditor 支持高度自由的插槽体系,既可以整体替换 Header,也可以针对工具栏的前后或右侧扩展业务按钮。

1. 插槽列表

| 插槽名 (Slot Name) | 作用域参数 (Slot Props) | 说明 | | :--- | :--- | :--- | | header | (context) | 完全自定义替换整个顶部栏。提供后内置 Header 不再渲染 | | header-left-prefix | (context) | 顶部左侧最前置区域(常用作系统 Logo、返回上一级页面按钮等) | | header-left | (context) | 顶部左侧主体(默认渲染汉堡菜单与文稿标题,提供该插槽可完全替换) | | header-left-suffix | (context) | 顶部左侧后置区域(例如副标题、协作状态标签等) | | toolbar / header-center | (context) | 中间工具栏主体。提供后完全替换内置的撤销/加页/图形等工具栏 | | toolbar-prefix | (context) | 工具栏按钮组前置。在内置“撤销”按钮前插入自定义操作按钮 | | toolbar-suffix | (context) | 工具栏按钮组后置。在内置“一键美化”按钮后插入自定义操作按钮(如企业素材库、智能组件等) | | header-right-prefix | (context) | 顶部右侧前置区域(位于缩放比例下拉框之前) | | header-right | (context) | 顶部右侧主体(放映按钮左侧)。最常用的业务插槽,如放置自定义“保存草稿”、“发布”、“历史版本”等 | | header-right-suffix | (context) | 顶部右侧后置区域(位于全屏放映按钮之后,适合放置用户头像、帮助中心等) |

2. 插槽作用域参数 (context) 说明

所有插槽均可通过 v-slot="{ api, save, exportPptx, activeSlide, ... }" 解构出编辑器的上下文能力:

{
  api: PptEditorApi,           // 完整的编程式 API 实例
  deck: DeckData,              // 当前完整演示文稿数据
  slides: Slide[],             // 幻灯片数组
  activeSlide: Slide | null,   // 当前正在编辑的幻灯片
  activeIndex: number,         // 当前正在编辑的页码索引 (从 0 开始)
  title: string,               // 演示文稿标题
  setTitle: (t: string) => void, // 修改标题
  save: (options?: SaveOptions) => Promise<any>, // 快速保存
  exportPptx: () => Promise<void>, // 快速导出 PPTX
  exportJson: () => void,      // 快速导出 JSON
  undo: () => void,            // 撤销
  redo: () => void,            // 重做
  canUndo: boolean,            // 是否可撤销
  canRedo: boolean,            // 是否可重做
  zoomRatio: number,           // 当前缩放比
  resetZoom: () => void,       // 重置缩放比
  isMasterEditingView: boolean // 当前是否处于母版编辑模式
}

3. 工具栏与右侧插槽实战范例

<template>
  <PptEditor v-model="deck">
    <!-- 场景 1:在左侧汉堡菜单前添加返回按钮 -->
    <template #header-left-prefix>
      <el-button link @click="router.back()">
        <el-icon><ArrowLeft /></el-icon> 返回工作台
      </el-button>
    </template>

    <!-- 场景 2:在顶部工具栏尾部追加企业专属工具 -->
    <template #toolbar-suffix="{ api }">
      <div
        class="toolbar-btn-stacked"
        @click="openAssetGallery(api)"
        title="企业素材库"
      >
        <svg class="toolbar-btn-icon" viewBox="0 0 24 24">
          <path d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" stroke="currentColor" fill="none" />
        </svg>
        <span class="btn-label">素材库</span>
      </div>
    </template>

    <!-- 场景 3:在顶部右侧添加自定义保存与发布按钮 -->
    <template #header-right="{ api, save }">
      <el-button size="small" @click="handleDraftSave(save)">
        保存草稿
      </el-button>
      <el-button type="primary" size="small" @click="handlePublish(api)">
        发布
      </el-button>
    </template>

    <!-- 场景 4:在顶部最右侧放置当前协作用户头像 -->
    <template #header-right-suffix>
      <el-avatar :size="28" src="https://example.com/avatar.png" />
    </template>
  </PptEditor>
</template>

八、完整实战示例

<template>
  <div class="custom-ppt-container">
    <!-- 外部自定义业务工具栏 -->
    <div class="external-bar">
      <el-button type="primary" @click="handleExternalSave">外部保存</el-button>
      <el-button @click="handleExportPptx">下载 PPTX</el-button>
      <el-button @click="handleAddNewSlide">插入一页</el-button>
      <el-button @click="handlePlay">全屏放映</el-button>
    </div>

    <!-- PPT 编辑器核心组件 -->
    <div class="editor-box">
      <PptEditor
        ref="editorRef"
        v-model="deck"
        :show-header="true"
        :config="{
          title: '企业季度经营汇报',
          showSaveButton: true
        }"
        @save="onSave"
        @slideChange="onSlideChange"
        @ready="onReady"
      >
        <!-- 可选:在顶部导航右侧插入自定义按钮 -->
        <template #header-right>
          <button class="custom-header-btn" @click="shareLink">分享</button>
        </template>
      </PptEditor>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { PptEditor, type PptEditorApi, type DeckData } from 'jvs-ppt-ui'
import 'jvs-ppt-ui/dist/lib/style.css'

const editorRef = ref<PptEditorApi | null>(null)
const deck = ref<DeckData | null>(null)

// 编辑器就绪
const onReady = (api: PptEditorApi) => {
  console.log('编辑器已就绪,可随时调用 API:', api)
}

// 外部调用保存
const handleExternalSave = () => {
  if (!editorRef.value) return
  const currentDeck = editorRef.value.save()
  console.log('外部获取到的保存数据:', currentDeck)
}

// 外部调用导出
const handleExportPptx = async () => {
  await editorRef.value?.exportPptx()
}

// 外部插入页
const handleAddNewSlide = () => {
  editorRef.value?.addSlide()
}

// 外部触发全屏放映
const handlePlay = () => {
  editorRef.value?.startPresentation()
}

// 幻灯片切换
const onSlideChange = ({ index, slide }: any) => {
  console.log(`当前切到第 ${index + 1} 页:`, slide)
}

// 保存回调
const onSave = (savedDeck: DeckData) => {
  // 发送给服务器
  // axios.post('/api/ppt/save', savedDeck)
}

const shareLink = () => {
  alert('分享链接已生成!')
}
</script>

<style scoped>
.custom-ppt-container {
  display: flex;
  flex-direction: column;
  width: 100vw;
  height: 100vh;
}
.external-bar {
  height: 48px;
  background: #1e293b;
  display: flex;
  align-items: center;
  padding: 0 16px;
  gap: 12px;
}
.editor-box {
  flex: 1;
  overflow: hidden;
}
.custom-header-btn {
  height: 32px;
  padding: 0 12px;
  background: #10b981;
  color: #fff;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}
</style>

八、素材库(Material Library)三层 API 架构与使用指南

jvs-ppt-ui 内置了完整的素材中心能力,支持查看全部、按单独类型拉取以及保存当前页到素材库三种 API 联动体系。

1. 按钮显示策略

💡 只要配置了获取所有内容的 API (materialApiUrl 或 materialListApiUrl),顶部工具栏便会自动显示【素材】按钮。
未配置接口且未设置环境变量时,素材按钮将自动隐藏,完全不干扰原界面。

2. 核心配置项与环境变量对照

| API 功能 | 组件 Props / Config | 对应环境变量 | 说明 | | :--- | :--- | :--- | :--- | | 获取所有内容 | materialApiUrl 或 materialListApiUrl | VITE_MATERIAL_API_URL 或 VITE_MATERIAL_LIST_API_URL | 核心配置项,加载素材总览列表;配置后按钮自动激活 | | 获取单独类型 | materialTypeApiUrl | VITE_MATERIAL_TYPE_API_URL | 可选项,点击分类标签(封面/目录/正文等)时调用 | | 保存素材 | materialSaveApiUrl | VITE_MATERIAL_SAVE_API_URL | 可选项,配置后素材面板自动展示【保存当前页】按钮 | | 统一请求头 | materialHeaders | - | 支持注入鉴权 Token 等 Headers |

3. 组件引入与配置示例

<template>
  <PptEditor
    ref="pptEditorRef"
    :enable-material="true"
    material-api-url="/jvs-dam/api/ppt/material/list"
    material-type-api-url="/jvs-dam/api/ppt/material/by-type"
    material-save-api-url="/jvs-dam/api/ppt/material/save"
    :material-headers="{ Authorization: 'Bearer token_xxx' }"
  />
</template>

4. 接口协议示例

① 获取所有内容 / 单独类型素材接口响应格式(GET)

{
  "code": 0,
  "msg": "success",
  "data": [
    {
      "id": "tpl_01",
      "title": "商业计划书通用封面",
      "category": "封面",
      "pptSource": "商务风模板",
      "url": "/jvs-dam/api/ppt/material/detail/tpl_01",
      "elementsCount": 8,
      "originalWidth": 1280,
      "originalHeight": 720
    }
  ]
}

② 保存素材接口请求格式(POST)

当用户在素材面板点击【保存当前页】并确认后,系统将自动打包当前幻灯片结构 POST 到 materialSaveApiUrl:

{
  "title": "自定义正文排版素材",
  "category": "正文",
  "slide": {
    "id": "slide_1",
    "title": "核心产品架构",
    "background": "#ffffff",
    "elements": [ /* 幻灯片内所有图元元素数据 */ ]
  },
  "elementsCount": 5,
  "originalSize": { "width": 1280, "height": 720 },
  "createdAt": "2026-09-22T09:45:00.000Z"
}

十、自定义资源上传(图片/视频/音频与文件导入)指南

jvs-ppt-ui 作为插件和 API 嵌入宿主系统时,提供了统一的自定义资源上传机制。当导入文件、导入 JSON 或用户上传媒体资源时,编辑器会智能判定是否配置了上传函数:

  • 若配置了上传函数:所有本地生成的媒体资源(图片、视频、音频、背景)都会自动传入上传函数上传到服务器,编辑器最终使用上传函数返回的远程文件 URL 字符串。
  • 若未配置上传函数:保持开箱即用的默认本地行为。

1. 未配置上传函数时的默认行为说明

| 场景 | 未配置上传函数时的默认行为 | 是否为 Base64 | 持久化特性 | | :--- | :--- | :---: | :--- | | 工具栏上传本地图片 | 使用 FileReader.readAsDataURL 读取 | 是 (Base64) | 存入 JSON 后离线持久有效 | | 画布直接粘贴剪贴板图片 | 使用 readAsDataURL 读取图片流 | 是 (Base64) | 存入 JSON 后离线持久有效 | | 占位符点击上传图片 | 使用 readAsDataURL 读取 | 是 (Base64) | 存入 JSON 后离线持久有效 | | 侧边栏上传幻灯片背景图片 | 使用 readAsDataURL 读取 | 是 (Base64) | 存入 JSON 后离线持久有效 | | 本地音视频文件选择 | 使用 readAsDataURL 读取 | 是 (Base64) | 存入 JSON 后离线持久有效 | | 导入 PPTX 文件 | 底层解析器默认产出临时 Blob URL (blob:http://...),部分母版装饰图为 Base64 | 否 (主要是 Blob URL) | blob: URL 与当前页面会话绑定,刷新后失效,建议配置上传函数持久化为远程 URL |

💡 为什么推荐配置上传函数?

  1. PPTX 导入的图片默认为临时 blob: URL,刷新页面后会失效;配置上传函数后会自动持久化到对象存储/服务器。
  2. 纯 Base64 会使保存的 JSON 体积增大 33% 左右;配置上传函数后,JSON 内仅存储几十字节的远程 URL,工程数据极度轻量。

2. 上传函数签名与上下文定义

import type { UploadResourceContext, CustomUploadHandler } from 'jvs-ppt-ui'

/**
 * 上传上下文参数
 */
export interface UploadResourceContext {
  /** 资源媒体类型 */
  type: 'image' | 'video' | 'audio'
  /** 触发来源 */
  source: 'file-upload' | 'pptx-import' | 'json-import' | 'paste' | 'drop' | 'background' | 'placeholder'
  /** 原始文件名(若有) */
  filename?: string
  /** 其它扩展字段 */
  [key: string]: any
}

/**
 * 自定义上传函数类型
 * 接收 File 或 Blob 对象与上下文信息,同步或异步返回远程文件地址字符串
 */
export type CustomUploadHandler = (
  fileOrBlob: File | Blob,
  context: UploadResourceContext
) => Promise<string> | string

3. 使用方式

方式 A:通过组件 Props / Config 传入

<template>
  <PptEditor
    v-model="deck"
    :custom-upload="handleCustomUpload"
  />
  <!-- 或在 config 对象中传入 -->
  <!-- :config="{ customUpload: handleCustomUpload }" -->
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { PptEditor, type UploadResourceContext } from 'jvs-ppt-ui'
import 'jvs-ppt-ui/dist/lib/style.css'

const deck = ref(null)

/**
 * 自定义资源上传处理器
 * @param fileOrBlob 要上传的文件(File 或 Blob)
 * @param context 上传来源与资源类型上下文
 * @returns 最终文件的远程访问地址 URL
 */
const handleCustomUpload = async (fileOrBlob: File | Blob, context: UploadResourceContext): Promise<string> => {
  const formData = new FormData()
  const ext = fileOrBlob.type?.split('/')[1] || (context.type === 'video' ? 'mp4' : context.type === 'audio' ? 'mp3' : 'png')
  const fileName = context.filename || `upload_${Date.now()}.${ext}`
  formData.append('file', fileOrBlob, fileName)
  formData.append('type', context.type)
  formData.append('source', context.source)

  // 调用您系统自有的文件上传 API(例如 OSS、S3、MinIO 或业务文件网关)
  const response = await fetch('/api/common/upload', {
    method: 'POST',
    body: formData,
    headers: {
      Authorization: 'Bearer token_xxx'
    }
  }).then(res => res.json())

  // 返回最终文件的远程访问 URL 字符串即可
  return response.data.url
}
</script>

方式 B:通过编程式 API 动态配载

若编辑器由外部动态控制,或上传逻辑依赖某些异步状态(如登录后动态获取的上传凭证),可直接调用 API 动态配载:

const editorRef = ref<PptEditorApi | null>(null)

// 动态配载上传处理器
editorRef.value.setCustomUploadHandler(async (fileOrBlob, context) => {
  const url = await uploadToOss(fileOrBlob, context)
  return url
})

// 获取当前配载的上传函数
const currentHandler = editorRef.value.getCustomUploadHandler()

// 卸载自定义上传处理器,恢复默认本地行为
editorRef.value.setCustomUploadHandler(null)

4. 自动化去重优化策略

在导入大型 PPTX 文件时,同一张图或装饰元素往往在多页幻灯片中被多次引用。jvs-ppt-ui 内置了内存 Promise 缓存与去重机制:

  • 相同的图片/媒体在单次导入中仅会触发一次真实的服务器上传请求;
  • 其余引用处会自动复用首个上传请求返回的远程 URL,避免重复浪费网络流量与服务器存储空间。

十、常见接入场景与最佳实践

1. 弹窗 / 抽屉模式嵌入(小屏响应式自适应)

在将 PptEditor 嵌入到 Dialog 模态弹窗或 Drawer 抽屉时(容器宽度在 800px ~ 1200px 左右),推荐以下配置:

<template>
  <el-dialog v-model="visible" fullscreen :show-close="false">
    <PptEditor
      ref="editorRef"
      v-model="deckData"
      :show-menu="false"
      @save="handleSave"
    >
      <!-- 在右侧添加弹窗关闭或自定义业务按钮 -->
      <template #header-right-suffix>
        <el-button size="small" type="primary" @click="handleSave">保存</el-button>
        <el-button size="small" @click="visible = false">关闭</el-button>
      </template>
    </PptEditor>
  </el-dialog>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { PptEditor } from 'jvs-ppt-ui'
import 'jvs-ppt-ui/dist/lib/style.css'

const visible = ref(true)
const deckData = ref(null)
const editorRef = ref(null)

const handleSave = async () => {
  const data = await editorRef.value?.save({ format: 'json' })
  // 保存到服务端...
}
</script>

小屏自适应机制说明:

  • 菜单控制 (:show-menu="false"):关闭左上角内置的测试/导入弹层菜单,左上角仅保留文档标题与重命名,更符合宿主系统定制界面的规范。
  • 标题防挤压折行:标题容器内置单行截断(white-space: nowrap; text-overflow: ellipsis)和鼠标悬浮 :title 提示,不再被中间工具栏挤成纵向乱序文字。
  • 工具栏弹性间距:工具栏按钮间距自适应缩小(gap: clamp(...)),宽度受限时末尾的“动画与切换”、“一键美化”等按钮依然紧凑展示;极窄尺寸下支持平滑横向滚动。
  • 右侧功能区恒定展示:全屏放映、母版模式与缩放下拉框恒定单行展示(flex-shrink: 0),绝不被压扁变形。

2. 图标库免配置说明(Zero Configuration)

  • 宿主系统(如 Vue CLI / Vite 项目)引入 jvs-ppt-ui 时,无需在宿主系统的 index.html 中手动添加任何 <script> 或 <link> 图标标签。
  • 只需在入口 import 'jvs-ppt-ui/dist/lib/style.css'(或 import 'jvs-ppt-ui/style.css'),组件库内部已自执行注入阿里巴巴 Iconfont SVG Symbol 与样式,实现真正的“开箱即用”。

十一、非编辑/预览模式(Readonly Mode)使用与行为规范

在许多业务场景(如课件预览、报告审阅、大屏展示、审批流中查看幻灯片)中,需要将 PPT 编辑器作为纯预览/浏览播放器使用。 通过在 <PptEditor> 上设置 :readonly="true" 即可一键启用非编辑/只读预览模式。

1. 基础用法

<template>
  <PptEditor
    ref="editorRef"
    v-model="deckData"
    :readonly="true"
    :show-menu="false"
    :show-sidebar-right="false"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { PptEditor } from 'jvs-ppt-ui'
import 'jvs-ppt-ui/dist/lib/style.css'

const editorRef = ref(null)
const deckData = ref(null)
</script>

2. 非编辑/预览模式下的严格限制(防止意外篡改)

| 交互类型 | 只读模式下的行为表现 | | :--- | :--- | | 画布空白处双击 | 彻底禁用。不会创建任何新文本框或元素,画布保持原样。 | | 图元双击编辑 | 彻底禁用。双击文本、形状、表格单元格均不会进入可编辑(ContentEditable)状态,双击公式不弹窗,双击图片占位符不弹上传框。 | | 图元拖拽移动 | 彻底禁用。无法通过鼠标拖动改变任何文字、图形、图片、表格、组合子元素或线条控制点的位置与尺寸。 | | 选中状态与边框 | 彻底隐藏。点击图元不再显示编辑选中边框、旋转手柄与拖动手柄(cursor: move),统一保持默认箭头光标(cursor: default)。 | | 画布拉框框选 | 彻底禁用。在画布上拖拽鼠标不会拉出蓝色橡皮筋选区框,不会批量选中图元。 | | 表格编辑标识 | 彻底隐藏。表格和公式上不再显示“双击编辑”浮层提示;禁用表格列宽与行高拖拽把手。 | | 编辑工具栏 | 彻底隐藏。顶部文本、形状、线条、图片、AI美化等插入与格式工具栏自动隐藏。 | | 幻灯片管理 | 左侧缩略图列表隐藏“新建幻灯片”按钮,并禁用幻灯片上下拖拽排序;汉堡菜单中**屏蔽“重置幻灯片”**清空操作。 |

3. 非编辑/预览模式下保留的完整功能

即使在 :readonly="true" 下,以下关键浏览、演示及导入导出能力依然完全可用:

  1. 幻灯片全屏放映(F5 / Shift+F5):
    • 顶部常驻【幻灯片放映 ▾】按钮,支持从头放映或从当前页放映;
    • 快捷键 F5(从第一页放映)、Shift + F5(从当前页放映)依然生效;
    • 放映模式支持键盘空格、左右方向键切页,按 Esc 键退出放映。
  2. 导入 PPT(PPTX / JSON):
    • 界面操作:顶部导航栏直接提供【导入 ▾】下拉菜单,支持选择本地 .pptx 文件或 .json 配置文件一键导入解析并即时渲染预览;
    • 编程式调用:支持通过组件 API 调用 editorRef.value.loadFile(file) 或 editorRef.value.importFile(file) 载入并呈现文件;
    • 导入后自动适配屏幕并继续保持只读模式。
  3. 导出 PPT(PPTX / JSON / ZIP / PNG / JPEG):
    • 界面操作:顶部导航栏直接提供【导出 ▾】下拉菜单,支持快速下载为 PPTX、导出完整工程 JSON、打包全部页面为图片 ZIP、或下载当前页高清 PNG/JPEG 图片;
    • 编程式调用:可直接通过组件 API 调用 editorRef.value.exportPptx()、editorRef.value.exportJson()、editorRef.value.exportImages() 或 editorRef.value.getSavePptxBlob()。
  4. 画布多尺度缩放与平移浏览:
    • 顶部提供比例切换下拉框(50%、75%、100%、125%、150%、200% 及“适合画布”);
    • 按住 Space(空格键)或鼠标中键拖动画布,可自由平移浏览大尺寸幻灯片。
  5. 缩略图导航与翻页:
    • 点击左侧缩略图可自由跳转指定幻灯片;支持键盘 PageUp/PageDown、ArrowUp/ArrowDown 快速切页。
  6. 文字原生划词复制(Copy):
    • 允许鼠标划选幻灯片内的文本内容并按 Ctrl + C(或右键复制)拷贝到剪贴板,方便审阅者摘录内容。

十二、模板库(Template Gallery)开关与自定义模板接入指南

jvs-ppt-ui 在左侧「新建幻灯片 ▾」下拉面板中内置了**【模板】与【版式】**双视图(注意:为保证 NPM 插件包轻量,npm publish 默认不会将数十 MB 的本地演示模板资源打入 NPM 包中)。宿主系统可以自由控制是否启用模板功能,并按标准格式注入自有模板。

1. 控制是否开启模板功能 (enableTemplate)

当设置 :enable-template="false" 时,左侧「更多布局」弹窗会自动隐藏 【模板】 Tab 与 【加载本地模板】 按钮,直接展示 【版式】 列表:

<template>
  <!-- 方式 A:直接通过 Prop 关闭模板功能 -->
  <PptEditor v-model="deck" :enable-template="false" />

  <!-- 方式 B:通过 config 对象关闭 -->
  <PptEditor v-model="deck" :config="{ enableTemplate: false }" />
</template>

2. 传入自定义模板 (templates / templateBaseUrl)

插件使用者可以通过 :templates 传入自定义模板数组(默认替换内置模板;若希望与内置模板合并展示,可设置 :replace-builtin-templates="false"),支持以下 3 种标准格式:

格式一:直接传入完整模板 Deck 对象(适合模板数量较少或已获取完整 JSON)

<template>
  <PptEditor v-model="deck" :templates="myTemplates" />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { PptEditor, type SlideTemplateInput } from 'jvs-ppt-ui'

const deck = ref(null)

const myTemplates: SlideTemplateInput[] = [
  {
    id: 'corp-tpl-annual',
    name: '企业年度述职报告',      // 也可使用 title 字段
    category: 'Business',         // 可选:分类名称
    slides: [                     // 完整幻灯片页面数组(即导出 JSON 中的 slides)
      {
        id: 'slide-1',
        background: '#0F172A',
        bgType: 'color',
        elements: [/* 第一页图元列表,自动作为一级列表封面渲染 */]
      },
      {
        id: 'slide-2',
        background: '#FFFFFF',
        bgType: 'color',
        elements: [/* 第二页图元列表 */]
      }
    ],
    styleRecipes: [/* 可选:配套主题色板 */]
  }
]
</script>

格式二:按需懒加载索引格式(推荐多套大图模板使用,首屏只渲染第一页,点击时才按需拉取完整 JSON)

const myLazyTemplates: SlideTemplateInput[] = [
  {
    id: 'tpl-product-launch',
    name: '产品发布会',
    category: 'Product',
    slidesCount: 35,                                      // 一级列表右下角展示的总页数
    firstSlide: { /* 第一页 slide 对象,用于一级列表实时渲染封面 */ },
    jsonUrl: 'https://cdn.example.com/ppt/Launch.json'    // 用户点击该模板卡片时按需 fetch 的完整模板 JSON 地址
  }
]

格式三:配置静态模板目录地址 (templateBaseUrl)

既可以写宿主项目本地的静态目录路径(public/ 下),也可以写远程 CDN / Nginx 地址(组件会自动将模板 JSON 内的 /templates/image/...、/templates/mp4/... 替换为 templateBaseUrl 前缀):

  1. 写法 A:使用宿主项目本地路径(放在宿主项目的 public/ 目录下)
    • 如果直接放到宿主项目的 public/templates/ 目录下:甚至无需传 template-base-url,默认就会请求 /templates/...。
    • 如果放到宿主项目的自定义目录(如 public/ppt-templates/ 或 public/static/ppt-templates/):
    <template>
      <!-- 对应宿主项目本地的 public/ppt-templates/ 目录 -->
      <PptEditor
        v-model="deck"
        template-base-url="/ppt-templates"
      />
    </template>
  2. 写法 B:使用远程 CDN 或独立静态服务器地址
    <template>
      <PptEditor
        v-model="deck"
        template-base-url="https://cdn.example.com/ppt-templates"
      />
    </template>

3. 通过编程式 API 动态管理模板

通过组件 ref 实例可以在运行时随时开关模板库、替换模板列表或追加新模板:

const editorRef = ref<PptEditorApi | null>(null)

// 1. 动态替换全部模板(replaceBuiltin 设为 false 则与默认模板合并)
editorRef.value?.setTemplates(myTemplates, { replaceBuiltin: true })

// 2. 动态追加单个新模板到模板库顶部
editorRef.value?.addTemplate(newTemplateDeckJson)

// 3. 获取当前模板库中的所有模板
const currentTemplates = editorRef.value?.getTemplates()

// 4. 动态关闭或开启模板功能
editorRef.value?.setTemplateConfig({ enable: false })