仿写讯飞AI生成PPT大纲组件

效果

别的不说先上效果
在这里插入图片描述

在这里插入图片描述

难点

  1. 树的实现 :需要递归自身,有丶难度。但是对于各位应该是有手就彳亍
  2. 双亲节点样式 :可以观察到双亲节点在连接线左侧是有内容的,叶子节点则没有。
  3. 连接线:可以观察到双亲节点是实心圆点,叶子节点是空心圆圈且连接线有缩进。
  4. 确定层级:缩进是根据层级确定的,如何确定层级。
  5. 确定最后的叶子节点:最后一个叶子节点的连接线是没有往下的,这里需要做判断。
  6. 插槽透传:因为需要递归,一些插槽的透传也是必不可少的。
  7. 重新渲染:有时数据结构变了,但是组件的并没有重新渲染导致与预期不一致。

实现

在这里插入图片描述

确定层级

在组件props中 保留一个默认层级level = 0,当有子节点时把:level = "level + 1" 传入子组件中即可完成标识。例如第一层的level = 0 ,第二层的level = 1。后续还可以根据level计算缩进线宽度。

连接线

1.第一个节点没有上边线,这个很好判断。
2.中间的节点有上边线,下边线,缩进线。
3.最后一个节点没有下边线,这个不太好判断。
4.点还是圆圈,这个很好判断。
5.缩进线的宽度计算公式:level * 每一节缩进线的宽度,这里默认一节缩进线宽度为16
例如:第一层的双亲节点他的缩进线宽度为:0 * 16 = 0
例如:第二层的叶子节点他的缩进线宽度为:1 * 16 =16

重新渲染

在组件上加key,改变key即可强制重新渲染。
在这里插入图片描述

确定最后的叶子节点

这个在组件内不好判断呐,那我干脆让使用者来判断。让他来做这件事,那在编程中怎么去做事呢?当然是函数辣,让用户传入函数,根据函数调用返回的结果去决定是否是最后的叶子节点。

在这里插入图片描述
我把数据都给你,我让你自己去判断。

插槽透传

循环$slots即可
在这里插入图片描述上面这样写ts会报错,但是其实是没问题,其它项目也是这么写的都没问题。但是为了不报红我还是用下面的写法在这里插入图片描述

源码

<template>
  <div v-bind="$attrs" class="tree-wrap">
    <template v-for="(chapter, chapterIdx) in treeData" :key="chapter.id">
      <div class="tree-node">
        <div class="node-desc" :style="descStyle">
          <slot
            v-if="$slots.desc"
            name="desc"
            :chapter="chapter"
            :chapterIdx="chapterIdx"
            :level="level"
          ></slot>
          <span v-else>{{ nodeDescText(level, chapterIdx) }}</span>
        </div>
        <div class="node-dot-line" :style="nodeDotLineStyle">
          <div v-if="shouldShowTopVertical(chapterIdx)" class="line-top"></div>
          <div
            :class="[shouldShowHorizontal(level, chapter) ? 'circle' : 'dot']"
            :style="nodeDotCircleStyle"
          ></div>
          <div v-if="isRenderLineBottom(level, chapter, chapterIdx)" class="line-bottom"></div>
          <div
            v-if="shouldShowHorizontal(level, chapter)"
            class="line-horizontal"
            :style="horizontalLineStyle"
          ></div>
        </div>
        <div class="node-info" :style="nodeInfoStyle">
          <slot
            v-if="$slots.title"
            name="title"
            :chapter="chapter"
            :chapterIdx="chapterIdx"
            :level="level"
          ></slot>
          <span v-else :class="{ 'font-bold': level === 0 }">{{ chapter.chapterTitle }}</span>
        </div>
        <div v-if="$slots.extra" class="extra" :style="extraStyle">
          <slot name="extra" :chapter="chapter" :chapterIdx="chapterIdx" :level="level"></slot>
        </div>
      </div>
      <PptOutline
        v-if="chapter && chapter.chapterContents"
        :level="level + 1"
        :tree-data="chapter.chapterContents"
        :key="chapter.id"
        :is-render-line-bottom="isRenderLineBottom"
      >
        <template v-for="(_slotFn, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
          <slot v-if="slotName === 'desc'" name="desc" v-bind="slotProps"></slot>
          <slot v-if="slotName === 'title'" name="title" v-bind="slotProps"></slot>
          <slot v-if="slotName === 'extra'" name="extra" v-bind="slotProps"></slot>
        </template>
      </PptOutline>
    </template>
  </div>
</template>

<script setup lang="ts">
import PptOutline from '@/components/PptOutline.vue'

const props = withDefaults(
  defineProps<{
    level?: number
    treeData: ChaptersItem[]
    isRenderLineBottom?: (level: number, chapter: ChaptersItem, chapterIdx: number) => boolean
  }>(),
  {
    level: 0,
    isRenderLineBottom: () => true
  }
)
const slots = useSlots()
const dotSize = 7
const descWidth = 50
const extraWidth = 100
const subNodeLineLeft = 16
const descStyle = {
  width: `${descWidth}px`
}
const extraStyle = {
  width: `${extraWidth}px`
}
// 横向连接线长度
const horizontalLineWidth = computed(() => props.level * subNodeLineLeft)
const horizontalLineStyle = computed(() => ({
  width: `${horizontalLineWidth.value}px`
}))
// 节点点样式
const nodeDotCircleStyle = computed(() => {
  if (props.level === 0) {
    return {
      left: `${horizontalLineWidth.value}px`
    }
  } else {
    return {
      left: `${horizontalLineWidth.value + dotSize / 2}px`
    }
  }
})
// 节点点线样式
const nodeDotLineWidth = computed(() => 3 * dotSize + horizontalLineWidth.value)
const nodeDotLineStyle = computed(() => ({
  width: `${nodeDotLineWidth.value}px`
}))
// 节点信息样式
const nodeInfoStyle = computed(() => {
  if (slots.extra) {
    return {
      width: `calc(100% - ${descWidth + extraWidth + nodeDotLineWidth.value}px)`
    }
  } else {
    return {
      width: `calc(100% - ${descWidth + nodeDotLineWidth.value}px)`
    }
  }
})
// 是否展示横向连接线
const shouldShowHorizontal = (level: number, chapter: ChaptersItem): boolean =>
  level !== 0 && Boolean(chapter)
// 是否展示上半部分纵向连接线
const shouldShowTopVertical = (chapterIdx: number): boolean => chapterIdx !== 0 || props.level > 0
// 展示节点描述信息
const nodeDescText = (level: number, chapterIdx: number): string =>
  level === 0 ? `章节${chapterIdx + 1}` : ''
</script>

<style lang="less" scoped>
.tree-wrap {
  width: 100%;
  @titleColor: #161724;
  @titleFontSize: 14px;
  @descColor: #8e90a5;
  @descFontSize: 12px;
  @dotLineColor: #bfc7d6;
  @hoverBgColor: #f6f6f6;
  @hoverBorderRadius: 6px;
  @dotSize: 7px;
  @dotLineWidth: 1px;
  @subNodeLineleft: 16px;
  @dotTop: 15px;
  @nodeMinHeight: 36px;

  /**每一个节点-start */
  .tree-node {
    display: flex;
    padding: 0 4px 0 20px;
    min-height: @nodeMinHeight;
    line-height: @nodeMinHeight;
    &:hover {
      background-color: @hoverBgColor;
      border-radius: @hoverBorderRadius;
    }

    /**节点描述:例如章节1 */
    .node-desc {
      padding-right: 10px;
      color: @descColor;
      font-size: @descFontSize;
      word-wrap: break-word;
      white-space: pre-line;
    }
    /**节点描述-end */

    /**节点连接线-start */
    .node-dot-line {
      position: relative;
      width: 15px;
      height: inherit;
      .line-top {
        position: absolute;
        top: 0;
        left: calc(@dotSize / 2);
        width: @dotLineWidth;
        height: calc(@dotTop + @dotSize / 2);
        background-color: @dotLineColor;
      }
      .dot {
        position: absolute;
        top: @dotTop;
        width: @dotSize;
        height: @dotSize;
        border-radius: 50%;
        background-color: @dotLineColor;
      }
      .circle {
        position: relative;
        top: @dotTop;
        width: @dotSize;
        height: @dotSize;
        border-radius: 50%;
        border: @dotLineWidth solid @dotLineColor;
      }
      .line-bottom {
        position: absolute;
        bottom: 0;
        left: calc(@dotSize / 2);
        width: @dotLineWidth;
        height: calc(100% - @dotTop - @dotSize / 2);
        background-color: @dotLineColor;
      }
      .line-horizontal {
        position: relative;
        top: calc(@dotTop - @dotSize / 2);
        left: calc(@dotSize / 2);
        width: @subNodeLineleft;
        height: @dotLineWidth;
        background-color: @dotLineColor;
      }
    }
    /**节点连接线-end */

    /**节点信息:例如标题 */
    .node-info {
      min-width: 400px;
      height: fit-content;
      font-size: @titleFontSize;
      word-wrap: break-word;
      white-space: pre-line;
    }
    /**节点信息-end */

    /**节点额外信息:例如图标 */
    .extra {
      height: @nodeMinHeight;
      color: @descColor;
      text-align: right;
    }
    /**节点额外信息-end */
  }
  /**每一个节点-end */
}
.font-bold {
  font-weight: bold;
}
</style>

// 二级标题(章节)
declare interface ChaptersItem {
  id: number
  chapterTitle: string | null
  fileUrl: string | null
  fileType: number
  chartFlag: string | null
  searchFlag: string | null
  chapterContents: ChaptersItem[] | null
  [key: string]: any
}
// 大纲
declare interface OutLine {
  id: number
  title: string | null
  subTitle: string | null
  fileUrl: string | null
  fileType: number
  chapters: ChaptersItem[] | null
  end: string | null
  fileId: string | null
  [key: string]: any
}
// 大纲返回内容
declare interface PptOutlineData {
  sid: string
  coverImgSrc: string
  title: string
  subTitle: string
  outline: OutLine
  [key: string]: any
}
### 自建AI系统生成PPT教程 构建一个基于Python或深度学习框架的AI系统以生成PPT,涉及多个技术环节,包括自然语言处理(NLP)、自动化工具集成以及演示文档的设计与渲染。以下是关于如何实现这一目标的关键技术和方法。 #### 1. 技术栈概述 为了完成此任务,可以采用以下主要组件和技术: - **大型语言模型 (LLM)**:用于生成高质量的内容摘要、大纲和具体文本[^2]。 - **Python 库支持**:`python-pptx` 是核心库之一,它提供了创建和修改 PowerPoint 文件的功能[^4]。 - **图形界面控制工具**:如果需要模拟人工操作,则 `PyAutoGUI` 可作为辅助手段来驱动外部应用程序的行为[^1]。 #### 2. 数据准备阶段 在实际开发之前,需准备好必要的资源文件,比如预设好的 PPT 模板或者自定义设计风格的主题背景图片等素材资料。这些都将直接影响最终输出作品的质量水平。 #### 3.逻辑代码 下面展示了一个简单的例子程序片段,说明怎样调用 LLM 接口获取所需材料,并将其嵌入到新的幻灯片当中去: ```python from pptx import Presentation def create_presentation(content_list, template_path="template.pptx"): prs = Presentation(template_path) for content in content_list: slide_layout = prs.slide_layouts[0] # Assuming first layout is Title Slide slide = prs.slides.add_slide(slide_layout) title = slide.shapes.title subtitle = slide.placeholders[1] title.text = content['title'] subtitle.text = content['subtitle'] prs.save('output.pptx') # Example usage with hypothetical generated contents from an LLM API call. generated_contents = [ {"title": "Introduction", "subtitle": "Overview of the topic"}, {"title": "Conclusion", "subtitle": "Summary points"} ] create_presentation(generated_contents) ``` 上述脚本展示了基本的工作流程——即从某个地方取得一系列字典形式表示的信息单元之后逐一添加至空白项目之中形成完整的报告结构[^3]。 #### 4. 集成深度学习模块(可选) 对于更高级别的需求来说,还可以引入神经网络架构来进行更加精细的任务处理,例如图像识别分类以便自动挑选合适的配图;或者是情感分析判断语气从而调整字体颜色大小等等特性增强用户体验效果。不过这通常意味着更高的计算成本和技术门槛要求。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值