CatPtain commited on
Commit
108f409
·
verified ·
1 Parent(s): 28e1dba

Upload 5 files

Browse files
frontend/doc/AIPPT.md ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## AIPPT的基本原理
2
+ 1. 定义PPT结构(一套PPT中都有什么类型的页面,每种页面都有些什么内容);
3
+ 2. 基于以上结构,定义数据格式,该数据将用于AI生成结构化的PPT数据,具体结构见:
4
+ - 示例数据:`public/mocks/AIPPT.json`
5
+ - 结构定义:`src/types/AIPPT.ts`
6
+ 3. 制作模板,模板中标记好结构类型;
7
+ 4. AI生成符合第1步定义的PPT结构的数据;
8
+ 5. 利用AI或其他方案,生成相关的配图(常见途径有:AI文生图、图库搜索匹配);
9
+ 6. 将AI生成的数据、配图与模板进行匹配结合,生成最终的PPT。
10
+
11
+ > 注:虽然当前线上版本不提供配图演示效果,但是AIPPT的方法是支持此功能的,你只需要自己提供图片源,按照要求的格式将待选图片集合传入AIPPT方法即可。
12
+
13
+ ## AIPPT模板制作流程
14
+ 1. 打开PPTist;
15
+ 2. 制作模板页面;
16
+ 3. 打开左上角菜单[幻灯片类型标注]功能;
17
+ 4. 为制作好的页面标注页面类型和节点类型;
18
+ 5. 使用导出功能导出为JSON文件。
19
+
20
+ > 注意:实际上并不存在专门提供给AIPPT的模板。所谓的AIPPT模板只是把在PPTist中制作的普通页面标注上类型标记而已。这些数据不仅仅用于AI生成PPT,也可以作为普通的页面模板使用。
21
+
22
+ ## 模板标记类型:页面标记和节点标记
23
+ #### 封面页
24
+ * 标题
25
+ * 正文
26
+ * 图片(背景图、页面插图)
27
+ #### 目录页
28
+ * 目录标题(标记类型为:列表项目)
29
+ * 图片(背景图、页面插图)
30
+ #### 过渡页(章节过渡)
31
+ * 标题
32
+ * 正文
33
+ * 节编号
34
+ * 图片(背景图、页面插图)
35
+ #### 内容页
36
+ * 标题
37
+ * 2~4个内容项,包括:
38
+ * 内容项标题(标记类型为:列表项标题)
39
+ * 内容项正文(标记类型为:列表项目)
40
+ * 内容项编号(标记类型为:项目编号)
41
+ * 图片(背景图、页面插图、项目插图)
42
+ #### 结束页(致谢页)
43
+ * 图片(背景图、页面插图)
44
+
45
+ > 节点标记分为两种 - 文本标记和图片标记:
46
+ > - 文本标记可作用于文本节点和带文字的形状节点;
47
+ > - 图片标记只作用于图片节点;
48
+ > - 你可以自行添加更多类型的标记(如图表)。
49
+
50
+ ## AIPPT模板制作原则
51
+ 一个用于AIPPT的模板至少包括以下页面(共12页):
52
+ * 1个封面页
53
+ * 6个目录页(2~6个目录项各1个,10个目录项的1个)
54
+ * 1个过渡页
55
+ * 3个内容页(2~4个内容项各1个)
56
+ * 1个结束页
57
+
58
+ > 注意:
59
+ > 1. 以上页数仅满足当前替换逻辑的最基本要求,如果希望AI生成的PPT具有一定的随机性,需要适当增加每种页面的数量(举个例子,假设模板中存在3个封面页,生成时会从3个中随机选择1个使用);
60
+ > 2. 当前替换逻辑下,目录页可支持1~20个目录项,内容页可支持1~12个内容项,但是却不需要每种数量的模板都做一遍,是因为程序会自动通过模板的拼接/裁减方式来实现特殊的项目数;
61
+ > 3. 你可以自行调整替换逻辑,以支持更多情况。
frontend/doc/Canvas.md ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## 画布与元素
2
+
3
+ #### 编辑器的基本结构
4
+ ```
5
+ └──编辑器
6
+ ├── 顶部菜单栏
7
+ ├── 左侧导航栏
8
+ ├── 右侧导航栏
9
+ ├── 中上部插入/工具栏
10
+ ├── 底部演讲者备注
11
+ └── 画布
12
+ ├── 可视区域
13
+ │ ├── 可编辑元素
14
+ │ └── 鼠标选框
15
+
16
+ └── 画布工具
17
+ ├── 参考线
18
+ ├── 标尺
19
+ ├── 元素操作节点层(如拖拽缩放点)
20
+ ├── 吸附对齐线
21
+ └── 可视区域背景
22
+ ```
23
+
24
+ #### 画布的基本原理
25
+ 我们把关注点放在相对复杂的【画布】部分。画布中的每一个元素都由一组数据来描述,例如:
26
+ ```typescript
27
+ interface PPTBaseElement {
28
+ id: string;
29
+ left: number;
30
+ top: number;
31
+ width: number;
32
+ height: number;
33
+ }
34
+ ```
35
+ 顾名思义,`left` 表示元素距离画布左上角的位置,`width` 表示元素的宽度,以此类推。
36
+ 重点需要知道的是:可视区域默认以 宽1000像素 、高562.5像素为基础比例。即无论画布和可视区域实际大小是多少,一个 `{ width: 1000px, height: 562.5px, left: 0, top: 0 }` 的元素一定会正好铺满整个可视区域。
37
+ 具体实现的方法很简单:假设可视区域的实际宽度为 1200px ,计算出此时的缩放比为 1200 / 1000 = 1.2 ,然后将可视区域内的元素全部缩放到 1.2 倍即可。
38
+ 同理【缩略图】 和 【放映页面】 其实上就是一个实际大小更小或更大的可视区域。
39
+
40
+ #### 画布内的元素
41
+ 除了上述中的位置和尺寸信息,还可以携带更多的数据,以一个文本元素为例:
42
+ ```typescript
43
+ interface PPTTextElement {
44
+ type: 'text';
45
+ id: string;
46
+ left: number;
47
+ top: number;
48
+ lock?: boolean;
49
+ groupId?: string;
50
+ width: number;
51
+ height: number;
52
+ link?: string;
53
+ content: string;
54
+ rotate: number;
55
+ defaultFontName: string;
56
+ defaultColor: string;
57
+ outline?: PPTElementOutline;
58
+ fill?: string;
59
+ lineHeight?: number;
60
+ wordSpace?: number;
61
+ opacity?: number;
62
+ shadow?: PPTElementShadow;
63
+ }
64
+ ```
65
+ 你可以定义一个 `rotate` 来表示文本框旋转的角度、定义一个 `opacity` 来表示文本框的透明度 等。在实现时只需要按照你所定义的数据来渲染元素组件即可,而编辑元素的本质就是在修改这些数据。
66
+ 以上就是一个画布最基本的组成了。
frontend/doc/CustomElement.md ADDED
@@ -0,0 +1,414 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## 如何自定义一个元素
2
+
3
+ 我们以【网页元素】为例,来梳理下自定义一个元素的过程。
4
+ > 完整代码在 https://github.com/pipipi-pikachu/PPTist/tree/document-demo
5
+
6
+ > 注意:由于版本更新,该文档和仓库中的代码并不是直接复制粘贴就可以使用,这里仅提供思路。
7
+
8
+ ### 编写新元素的结构与配置
9
+ 首先需要定义这个元素的结构,并添加该元素类型
10
+ ```typescript
11
+ // types/slides.ts
12
+
13
+ export const enum ElementTypes {
14
+ TEXT = 'text',
15
+ IMAGE = 'image',
16
+ SHAPE = 'shape',
17
+ LINE = 'line',
18
+ CHART = 'chart',
19
+ TABLE = 'table',
20
+ LATEX = 'latex',
21
+ VIDEO = 'video',
22
+ AUDIO = 'audio',
23
+ FRAME = 'frame', // add
24
+ }
25
+
26
+ // add
27
+ export interface PPTFrameElement extends PPTBaseElement {
28
+ type: 'frame'
29
+ id: string;
30
+ left: number;
31
+ top: number;
32
+ width: number;
33
+ height: number;
34
+ url: string; // 网页链接地址
35
+ }
36
+
37
+ // 修改 PPTElement Type
38
+ export type PPTElement = PPTTextElement | PPTImageElement | PPTShapeElement | PPTLineElement | PPTChartElement | PPTTableElement | PPTLatexElement | PPTVideoElement | PPTAudioElement | PPTFrameElement
39
+ ```
40
+
41
+ 在配置文件中添加新元素的中文名,以及最小尺寸:
42
+ ```typescript
43
+ // configs/element
44
+
45
+ export const ELEMENT_TYPE_ZH = {
46
+ text: '文本',
47
+ image: '图片',
48
+ shape: '形状',
49
+ line: '线条',
50
+ chart: '图表',
51
+ table: '表格',
52
+ video: '视频',
53
+ audio: '音频',
54
+ frame: '网页', // add
55
+ }
56
+
57
+ export const MIN_SIZE = {
58
+ text: 20,
59
+ image: 20,
60
+ shape: 15,
61
+ chart: 200,
62
+ table: 20,
63
+ video: 250,
64
+ audio: 20,
65
+ frame: 200, // add
66
+ }
67
+ ```
68
+
69
+ ### 编写新元素组件
70
+ 然后开始编写该元素的组件:
71
+ ```html
72
+ <!-- views/components/element/FrameElement/index.vue -->
73
+
74
+ <template>
75
+ <div class="editable-element-frame"
76
+ :style="{
77
+ top: elementInfo.top + 'px',
78
+ left: elementInfo.left + 'px',
79
+ width: elementInfo.width + 'px',
80
+ height: elementInfo.height + 'px',
81
+ }"
82
+ >
83
+ <div
84
+ class="rotate-wrapper"
85
+ :style="{ transform: `rotate(${elementInfo.rotate}deg)` }"
86
+ >
87
+ <div
88
+ class="element-content"
89
+ v-contextmenu="contextmenus"
90
+ @mousedown="$event => handleSelectElement($event)"
91
+ @touchstart="$event => handleSelectElement($event)"
92
+ >
93
+ <iframe
94
+ :src="elementInfo.url"
95
+ :width="elementInfo.width"
96
+ :height="elementInfo.height"
97
+ :frameborder="0"
98
+ :allowfullscreen="true"
99
+ ></iframe>
100
+
101
+ <div class="drag-handler top"></div>
102
+ <div class="drag-handler bottom"></div>
103
+ <div class="drag-handler left"></div>
104
+ <div class="drag-handler right"></div>
105
+
106
+ <div class="mask"
107
+ v-if="handleElementId !== elementInfo.id"
108
+ @mousedown="$event => handleSelectElement($event, false)"
109
+ @touchstart="$event => handleSelectElement($event, false)"
110
+ ></div>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ </template>
115
+
116
+ <script lang="ts" setup>
117
+ import { PropType } from 'vue'
118
+ import { storeToRefs } from 'pinia'
119
+ import { useMainStore } from '@/store'
120
+ import { PPTFrameElement } from '@/types/slides'
121
+ import { ContextmenuItem } from '@/components/Contextmenu/types'
122
+
123
+ const props = defineProps({
124
+ elementInfo: {
125
+ type: Object as PropType<PPTFrameElement>,
126
+ required: true,
127
+ },
128
+ selectElement: {
129
+ type: Function as PropType<(e: MouseEvent | TouchEvent, element: PPTFrameElement, canMove?: boolean) => void>,
130
+ required: true,
131
+ },
132
+ contextmenus: {
133
+ type: Function as PropType<() => ContextmenuItem[] | null>,
134
+ },
135
+ })
136
+
137
+ const { handleElementId } = storeToRefs(useMainStore())
138
+
139
+ const handleSelectElement = (e: MouseEvent | TouchEvent, canMove = true) => {
140
+ e.stopPropagation()
141
+ props.selectElement(e, props.elementInfo, canMove)
142
+ }
143
+ </script>
144
+
145
+ <style lang="scss" scoped>
146
+ .editable-element-frame {
147
+ position: absolute;
148
+ }
149
+ .element-content {
150
+ width: 100%;
151
+ height: 100%;
152
+ cursor: move;
153
+ }
154
+ .drag-handler {
155
+ position: absolute;
156
+
157
+ &.top {
158
+ height: 20px;
159
+ left: 0;
160
+ right: 0;
161
+ top: 0;
162
+ }
163
+ &.bottom {
164
+ height: 20px;
165
+ left: 0;
166
+ right: 0;
167
+ bottom: 0;
168
+ }
169
+ &.left {
170
+ width: 20px;
171
+ top: 0;
172
+ bottom: 0;
173
+ left: 0;
174
+ }
175
+ &.right {
176
+ width: 20px;
177
+ top: 0;
178
+ bottom: 0;
179
+ right: 0;
180
+ }
181
+ }
182
+ .mask {
183
+ position: absolute;
184
+ top: 0;
185
+ bottom: 0;
186
+ left: 0;
187
+ right: 0;
188
+ }
189
+ </style>
190
+ ```
191
+
192
+ 此外我们需要另一个不带编辑功能的基础版组件,用于缩略图/放映模式下显示:
193
+ ```html
194
+ <!-- views/components/element/FrameElement/BaseFrameElement.vue -->
195
+
196
+ <template>
197
+ <div class="base-element-frame"
198
+ :style="{
199
+ top: elementInfo.top + 'px',
200
+ left: elementInfo.left + 'px',
201
+ width: elementInfo.width + 'px',
202
+ height: elementInfo.height + 'px',
203
+ }"
204
+ >
205
+ <div
206
+ class="rotate-wrapper"
207
+ :style="{ transform: `rotate(${elementInfo.rotate}deg)` }"
208
+ >
209
+ <div class="element-content">
210
+ <iframe
211
+ :src="elementInfo.url"
212
+ :width="elementInfo.width"
213
+ :height="elementInfo.height"
214
+ :frameborder="0"
215
+ :allowfullscreen="true"
216
+ ></iframe>
217
+
218
+ <div class="mask"></div>
219
+ </div>
220
+ </div>
221
+ </div>
222
+ </template>
223
+
224
+ <script lang="ts" setup>
225
+ import { PropType } from 'vue'
226
+ import { PPTFrameElement } from '@/types/slides'
227
+
228
+ const props = defineProps({
229
+ elementInfo: {
230
+ type: Object as PropType<PPTFrameElement>,
231
+ required: true,
232
+ },
233
+ })
234
+ </script>
235
+
236
+ <style lang="scss" scoped>
237
+ .base-element-frame {
238
+ position: absolute;
239
+ }
240
+ .element-content {
241
+ width: 100%;
242
+ height: 100%;
243
+ }
244
+ .mask {
245
+ position: absolute;
246
+ top: 0;
247
+ bottom: 0;
248
+ left: 0;
249
+ right: 0;
250
+ }
251
+ </style>
252
+ ```
253
+
254
+ 在这里你可能会发现,这两个组件非常相似,确实如此,对于比较简单的元素组件来说,可编辑版和不可编辑版是高度一致的,不可编辑版可能仅仅是少了一些方法而已。但是对于比较复杂的元素组件,两者的差异就会比较大了(具体可以比较文本元素和图片元素的两版),因此,你可以自行判断是否将二者合并抽象为一个组件,这里不过多展开。
255
+
256
+ 编写完元素组件,我们需要把它用在需要的地方,具体可能包括:
257
+
258
+ - 缩略图元素组件 `views/components/ThumbnailSlide/ThumbnailElement.vue`
259
+ - 放映元素组件 `views/Screen/ScreenElement.vue`
260
+ - 可编辑元素组件 `views/Editor/Canvas/EditableElement.vue`
261
+ - 移动端可编辑元素组件 `views/Mobile/MobileEditor/MobileEditableElement.vue`
262
+
263
+ 一般来说,前两者使用不可编辑版,后两者使用可编辑版。
264
+ 这里仅以画布中的可编辑元素组件为例:
265
+ ```html
266
+ <!-- views/Editor/Canvas/EditableElement.vue -->
267
+
268
+ <script lang="ts" setup>
269
+ import FrameElement from '@/views/components/element/FrameElement/index.vue'
270
+
271
+ const currentElementComponent = computed(() => {
272
+ const elementTypeMap = {
273
+ [ElementTypes.IMAGE]: ImageElement,
274
+ [ElementTypes.TEXT]: TextElement,
275
+ [ElementTypes.SHAPE]: ShapeElement,
276
+ [ElementTypes.LINE]: LineElement,
277
+ [ElementTypes.CHART]: ChartElement,
278
+ [ElementTypes.TABLE]: TableElement,
279
+ [ElementTypes.LATEX]: LatexElement,
280
+ [ElementTypes.VIDEO]: VideoElement,
281
+ [ElementTypes.AUDIO]: AudioElement,
282
+ [ElementTypes.FRAME]: FrameElement, // add
283
+ }
284
+ return elementTypeMap[props.elementInfo.type] || null
285
+ })
286
+ </script>
287
+ ```
288
+
289
+ 在画布的可编辑元素中,还需要为元素添加操作节点 `Operate`(一般包括八个缩放点、四条边线、一个旋转点),对于特殊的元素(如线条的操作节点明显与其他不同)你可以自己编写该组件,但是一般情况下可以直接使用已经编写好的通用操作节点:
290
+ ```html
291
+ <!-- src\views\Editor\Canvas\Operate\index.vue -->
292
+
293
+ <script lang="ts" setup>
294
+ const currentOperateComponent = computed(() => {
295
+ const elementTypeMap = {
296
+ [ElementTypes.IMAGE]: ImageElementOperate,
297
+ [ElementTypes.TEXT]: TextElementOperate,
298
+ [ElementTypes.SHAPE]: ShapeElementOperate,
299
+ [ElementTypes.LINE]: LineElementOperate,
300
+ [ElementTypes.TABLE]: TableElementOperate,
301
+ [ElementTypes.CHART]: CommonElementOperate,
302
+ [ElementTypes.LATEX]: CommonElementOperate,
303
+ [ElementTypes.VIDEO]: CommonElementOperate,
304
+ [ElementTypes.AUDIO]: CommonElementOperate,
305
+ [ElementTypes.FRAME]: CommonElementOperate, // add
306
+ }
307
+ return elementTypeMap[props.elementInfo.type] || null
308
+ })
309
+ </script>
310
+ ```
311
+
312
+ ### 编写右侧元素编辑面板
313
+ 接下来需要为元素添加一个样式面板。当选中元素时,右侧工具栏会自动聚焦到该面板,你需要在这里添加一些你认为需要的设置项来操作元素本身,只需要记住一点:修改元素实际是修改元素的数据,也就是最开始定义的结构中的各个字段。
314
+ 另外,修改元素后不要忘了将操作添加到历史记录。
315
+ ```html
316
+ <!-- src\views\Editor\Toolbar\ElementStylePanel\FrameStylePanel.vue -->
317
+
318
+ <template>
319
+ <div class="frame-style-panel">
320
+ <div class="row">
321
+ <div>网页链接:</div>
322
+ <Input v-model:value="url" placeholder="请输入网页链接" />
323
+ <Button @click="updateURL()">确定</Button>
324
+ </div>
325
+ </div>
326
+ </template>
327
+
328
+ <script lang="ts" setup>
329
+ import { ref } from 'vue'
330
+ import { storeToRefs } from 'pinia'
331
+ import { useMainStore, useSlidesStore } from '@/store'
332
+ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
333
+
334
+ const slidesStore = useSlidesStore()
335
+ const { handleElementId } = storeToRefs(useMainStore())
336
+
337
+ const { addHistorySnapshot } = useHistorySnapshot()
338
+
339
+ const url = ref('')
340
+
341
+ const updateURL = () => {
342
+ if (!handleElementId.value) return
343
+ slidesStore.updateElement({ id: handleElementId.value, props: { url: url.value } })
344
+ addHistorySnapshot()
345
+ }
346
+ </script>
347
+ ```
348
+ ```html
349
+ <script lang="ts" setup>
350
+ import FrameStylePanel from './FrameStylePanel.vue'
351
+
352
+ const panelMap = {
353
+ [ElementTypes.TEXT]: TextStylePanel,
354
+ [ElementTypes.IMAGE]: ImageStylePanel,
355
+ [ElementTypes.SHAPE]: ShapeStylePanel,
356
+ [ElementTypes.LINE]: LineStylePanel,
357
+ [ElementTypes.CHART]: ChartStylePanel,
358
+ [ElementTypes.TABLE]: TableStylePanel,
359
+ [ElementTypes.LATEX]: LatexStylePanel,
360
+ [ElementTypes.VIDEO]: VideoStylePanel,
361
+ [ElementTypes.AUDIO]: AudioStylePanel,
362
+ [ElementTypes.FRAME]: FrameStylePanel, // add
363
+ }
364
+ </script>
365
+ ```
366
+
367
+ ### 创建元素
368
+ 这是自定义一个新元素的最后一步。首先编写一个创建元素的方法:
369
+ ```typescript
370
+ // src\hooks\useCreateElement.ts
371
+
372
+ const createFrameElement = (url: string) => {
373
+ createElement({
374
+ type: 'frame',
375
+ id: nanoid(10),
376
+ width: 800,
377
+ height: 480,
378
+ rotate: 0,
379
+ left: (VIEWPORT_SIZE - 800) / 2,
380
+ top: (VIEWPORT_SIZE * viewportRatio.value - 480) / 2,
381
+ url,
382
+ })
383
+ }
384
+ ```
385
+ 然后在插入工具栏中使用:
386
+ ```html
387
+ <!-- src\views\Editor\CanvasTool\index.vue -->
388
+
389
+ <template>
390
+ <div class="canvas-tool">
391
+ <div class="add-element-handler">
392
+ <!-- add -->
393
+ <span class="handler-item" @click="createFrameElement('https://v3.cn.vuejs.org/')">插入网页</span>
394
+ </div>
395
+ </div>
396
+ </template>
397
+
398
+ <script lang="ts" setup>
399
+ const {
400
+ createImageElement,
401
+ createChartElement,
402
+ createTableElement,
403
+ createLatexElement,
404
+ createVideoElement,
405
+ createAudioElement,
406
+ createFrameElement, // add
407
+ } = useCreateElement()
408
+ </script>
409
+ ```
410
+ 点击【插入网页】按钮,你就会看到一个网页元素被添加到画布中了。
411
+
412
+ ### 总结
413
+ 至此就是自定义一个元素的基本流程了。整个过程比较繁琐,但并不复杂,重点在于元素结构的定义与元素组件的编写,这决定了新元素将具备怎样的能力与外表。而其他的部分仅依葫芦画瓢即可。
414
+ 除此之外,还有一些非必须的调整:比如你希望导出能够支持新元素,则需要在导出相关的方法中进行扩展;比如你希望主题功能能够应用在新元素上,则需要在主题相关的方法中进行扩展,以此类推。
frontend/doc/DirectoryAndData.md ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## 项目目录与数据结构
2
+
3
+ ### 项目目录结构
4
+ ```
5
+ ├── assets // 静态资源
6
+ │ ├── fonts // 在线字体文件
7
+ │ └── styles // 样式
8
+ │ ├── antd.scss // antd默认样式覆盖
9
+ │ ├── font.scss // 在线字体定义
10
+ │ ├── global.scss // 通用全局样式
11
+ │ ├── mixin.scss // scss全局混入
12
+ │ ├── variable.scss // scss全局变量
13
+ │ └── prosemirror.scss // ProseMirror 富文本默认样式
14
+ ├── components // 与业务逻辑无关的通用组件
15
+ ├── configs // 配置文件,如:画布尺寸、字体、动画配置、快捷键配置、预置形状、预置线条等数据。
16
+ ├── hooks // 供多个组件(模块)使用的 hooks 方法
17
+ ├── mocks // mocks 数据
18
+ ├── plugins // 自定义的 Vue 插件
19
+ ├── types // 类型定义文件
20
+ ├── store // Pinia store,参考:https://pinia.vuejs.org/
21
+ ├── utils // 通用的工具方法
22
+ └── views // 业务组件目录,分为 `编辑器` 和 `播放器` 两个部分。
23
+ ├── components // 公用的业务组件
24
+ ├── Editor // 编辑器模块
25
+ ├── Screen // 播放器模块
26
+ └── Mobile // 移动端模块
27
+ ```
28
+
29
+
30
+ ### 数据
31
+ 幻灯片的数据主要由 `slides` 和 `theme` 两部分组成。
32
+ > 换句话说,在实际的生产环境中,一般只需要存储这两项数据即可。
33
+
34
+ - `slides` 表示幻灯片页面数据,包括每一页的ID、元素内容、备注、背景、动画、切页方式等信息
35
+ - `theme` 表示幻灯片主题数据,包括背景色、主题色、字体颜色、字体等信息
36
+
37
+ 具体类型的定义可见:[https://github.com/pipipi-pikachu/PPTist/blob/master/src/types/slides.ts](https://github.com/pipipi-pikachu/PPTist/blob/master/src/types/slides.ts)
frontend/doc/Q&A.md ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## 常见问题
2
+
3
+ #### Q. 为什么xxx快捷键没有作用?
4
+
5
+ A. 部分快捷键需要聚焦到指定区域才会生效,例如焦点在左边缩略图列表才能使用操作页面的快捷键,焦点在画布区域才能使用操作元素的快捷键。
6
+
7
+ #### Q. 为什么粘贴没有作用?
8
+
9
+ A. 请注意允许浏览器访问系统剪贴板。
10
+
11
+ #### Q. 为什么浏览器刷新或重新打开后,之前做的PPT没有了?
12
+
13
+ A. 仓库提供的链接仅供演示,且该项目是纯前端部署的,没有后台,不会保存数据。
14
+
15
+ #### Q. 如何调整幻灯片页面的顺序?
16
+
17
+ A. 按住左侧缩略图可进行拖拽调整顺序。
18
+
19
+ #### Q. 为什么插入图片后会出现操作卡顿的情况?
20
+
21
+ A. 由于本演示项目不依赖后端,插入本地图片实际引用的是Base64,导致数据体积非常大,在真正的生产环境中应该上传图片后引用图片地址,就不会出现这样的情况了。
22
+
23
+ #### Q. 为什么应用预置主题后没有效果?
24
+
25
+ A. 设置预置主题的作用是使新添加的元素和页面应用主题样式,不会对已有的元素和页面生效,您可以使用“应用主题到全部”功能,将当前主题应用到全部页面中。
26
+
27
+ #### Q. 设置在线字体不生效?
28
+
29
+ A. 设置在线字体时会下载对应的字体文件,该文件较大,需要等待下载完成后才会应用新的字体。
30
+
31
+ #### Q. 关于导入导出PPTX文件
32
+
33
+ A. 作为一个在线幻灯片应用,导出、导入 PPTX 文件是非常重要的功能,但是经过调研发现,该功能实现起来的复杂度远超过了预期。由于个人能力和时间有限,这部分功能只能借助第三方的轮子来完成。
34
+
35
+ - 导出:目前导出功能主要基于 [PptxGenJS](https://github.com/gitbrent/PptxGenJS/) 完成,能够实现大多数基本元素的导出,但还有非常多的缺陷需要一点点完善。同时需要知晓的是:1、该功能依赖 PptxGenJS,对于该库本身无法实现的部分(如动画),本项目也无能为力;2、导出功能的目标只是【导出样式尽可能一致的元素】,而不是一比一将网页还原到PPT,一些样式差异是必然存在的。
36
+
37
+ - 导入:导入功能目前暂时没有合适的解决方案,还在调研和观望中。如果有感兴趣或做过相关内容的朋友,欢迎来 issues 中讨论。
38
+
39
+ > PS. 我做了一个 [pptx转json](https://github.com/pipipi-pikachu/pptx2json) 的实验,如果你急需实现导入PPTX文件功能,可以此为参考自行实现。
40
+
41
+ 同时补充一点,本项目不是 office PPT 的专属在线编辑器,本质上与 office PPT 没有任何关系。【导入/导出 ppt 文件】只是项目的一个[功能]而非[目的]。
42
+
43
+ #### Q. 视频元素支持哪些格式?
44
+
45
+ A. 本项目只提供最基础的视频能力,正常状态下可以播放video标签本身支持的格式。
46
+
47
+ 此外,可以额外引入 [hls.js](https://github.com/video-dev/hls.js) 或 [flv.js](https://github.com/Bilibili/flv.js) 来支持对应的格式(.m3u8 .flv),你只需要在项目中引入对应的文件(如cdn)即可,无需其他配置。
48
+
49
+ #### Q. 关于导入JSON文件
50
+
51
+ A. 首先,出于安全等原因,个人并不建议将这种功能在前端直接暴露给用户,或者说用户根本就不应该接触到JSON这种格式(甚至导出JSON功能的初衷也只是为了方便开发)。如果真的有相关的需求,请自行在服务端实现,核心在于做好进行数据的校验,前端实现也是一样。
52
+
53
+ #### Q. 打印 / 导出 PDF 样式与实际有出入
54
+
55
+ A. 请注意在浏览器弹出的打印窗口调整相关的设置。建议:设置边距为【默认】、取消勾选【页眉和页脚】、勾选【背景图形】。另外,建议在正式环境中采用后端生成PDF的方案效果更佳(如puppeteer)。
56
+
57
+ #### Q. 为什么移动端不支持 xxx 功能?
58
+
59
+ A. 首先需要明确的一点,就是移动端无论怎么做,体验上都是必然大不如 PC 端的。因此个人将移动端定位为:简单进行一些临时处理的应急使用。真正的设计/制作幻灯片应在电脑上使用完整的功能。如实在有移动端的特殊需求,可尝试在移动端使用电脑模式打开(当然,体验会更槽糕),或者开发者自己进行二次开发。
60
+
61
+ #### Q. 关于兼容性?
62
+
63
+ A. 本项目优先兼容Chrome、Firefox。在Safari下可能存在部分兼容性问题。不兼容IE。
64
+
65
+ #### Q. 为什么不是NPM包?
66
+
67
+ A. 大家都知道,对于一般的插件/库而言,一个封装好的npm包能够更方便的接入现有的项目中,但PPTist是特殊的,这是一个完整的程序,而不是作为程序的一部分存在。如果你需要使用PPTist,那么我认为你必然需要在此基础上做很多定制化的开发,包括但不限于:与后台的通信、各种模板和预置素材、新的元素类型、使用其他方案实现现有的某些元素、自己的主题、更换快捷键,等等……而不是仅仅安装一个和现有demo一样的东西就行了(这样虽然方便,但在实际的产品开发中没有任何意义)。正如前面所列举的,需要可配置的东西太多了,如果作为一个插件的存在,很难兼顾得了,或者说这样做的开发量是巨大的,目前个人还承担不起。
68
+
69
+ 因此,使用PPTist开发项目正确的做法是:拉取完整的代码、尝试理解它、基于它改造你自己的东西。社区中也不乏类似的项目,例如 [drawio](https://github.com/jgraph/drawio)
70
+
71
+ #### Q. 关于 AI PPT
72
+
73
+ A. 首先需要说明,AIPPT不是PPTist的重点,现在或以后都不是,它只是PPTist众多功能中非常小的一部分而已,并且是比较简单一部分,你可以理解为这只是一个跟风的小功能点,我不想蹭AI的热度,但无奈太多人将AI看得太重要太复杂了,于是我做了这个DEMO(它真的没那么复杂),目前此功能仅作参考,内部实现了最基础的AIPPT生成逻辑,即:模板定制 + AI生成数据与模板结合 + 配图替换。为控制成本暂时只能做到这里,但为了达到生产环节的效果,你还需要做更多,例如更多的模板、更细致的AI工作流程。
74
+
75
+ 注:配图替换仅提供方法,不提供实际演示功能,你需要自己提供图片源接入(如AI文生图、图库搜索匹配等方法)
76
+
77
+ #### Q. 其他
78
+
79
+ A. 另外需要强调,PPTist只是一个开源项目而非面向普通用户的产品,主要提供的是技术解决方案,一些产品化的需求/优化还需要开发者自己去实现和完善。
80
+
81
+ ## FAQ
82
+ #### Q. Why doesn’t the xxx shortcut work?
83
+
84
+ A. Some shortcuts only work when the focus is on a specific area. For example, the shortcuts for operating pages only work when the focus is on the thumbnail list on the left, and the shortcuts for operating elements only work when the focus is on the canvas area.
85
+
86
+ #### Q. Why isn’t pasting working?
87
+
88
+ A. Please make sure to allow the browser access to the system clipboard.
89
+
90
+ #### Q. Why do my previous PPT disappear after refreshing or reopening the browser?
91
+
92
+ A. The links provided by the repository are for demonstration purposes only, and the project is deployed as a pure front-end application without a backend, thus it does not save data.
93
+
94
+ #### Q. How do I adjust the order of slides?
95
+
96
+ A. You can drag and drop the thumbnails on the left to adjust the order.
97
+
98
+ #### Q. Why does the application become unresponsive after inserting images?
99
+
100
+ A. Since this demo project does not rely on a backend, inserting local images actually references Base64 encoded data, which can result in very large data sizes. In a real production environment, you should upload images and reference their addresses to avoid this issue.
101
+
102
+ #### Q. Why doesn’t the preset theme take effect after being applied?
103
+
104
+ A. Applying a preset theme affects new elements and pages added, but will not apply to existing elements and pages. You can use the “Apply Theme to All” feature to apply the current theme to all pages.
105
+
106
+ #### Q. Why doesn’t setting an online font work?
107
+
108
+ A. Setting an online font involves downloading the corresponding font file, which can be large and requires time to complete the download before the new font is applied.
109
+
110
+ #### Q. About Importing and Exporting PPTX Files
111
+
112
+ A. As an online presentation application, the ability to import and export PPTX files is very important. However, it has been found that the complexity of implementing this feature far exceeds expectations. Due to limited personal capacity and time, this functionality can only be achieved with the help of third-party solutions.
113
+
114
+ Export: The current export function is mainly based on [PptxGenJS](https://github.com/gitbrent/PptxGenJS/), and it can export most basic elements, but there are still many defects that need to be improved. It’s important to note that: 1) This feature relies on PptxGenJS, and for parts that the library itself cannot implement (such as animations), there’s nothing this project can do; 2) The goal of the export function is to export elements with styles as consistent as possible, not to recreate the web page one-to-one in PPT, and some style differences are inevitable.
115
+
116
+ Import: The import function currently does not have a suitable solution and is still under investigation. If you are interested or have experience in related areas, please discuss in the issues.
117
+
118
+ > PS. I made an experimental [pptx to json](https://github.com/pipipi-pikachu/pptx2json) converter. If you urgently need to implement the import PPTX file function, you can use this as a reference for your own implementation.
119
+
120
+ It should be noted that this project is not an exclusive online editor for Office PPT. It is essentially unrelated to Office PPT. The [import/export of PPT files] is just a [feature] of the project, not its [purpose].
121
+
122
+ #### Q. Which video formats are supported?
123
+
124
+ A. This project only provides basic video capabilities and can play formats supported by the video tag in normal conditions.
125
+
126
+ Additionally, you can introduce [hls.js](https://github.com/video-dev/hls.js) or [flv.js](https://github.com/Bilibili/flv.js) to support corresponding formats (.m3u8 .flv) by simply including the corresponding files (such as CDN) in your project, without any other configuration required.
127
+
128
+ #### Q. About Importing JSON Files
129
+
130
+ A. Firstly, due to security reasons, I do not recommend exposing such functionality directly to users on the front end, or users should not even come into contact with formats like JSON in the first place (even the export JSON feature was initially intended only for development convenience). If there is a real need, please implement it on the server side, with a focus on data validation, and the same goes for the front end.
131
+
132
+ #### Q. Print / Export PDF Styles Are Different from the Actual
133
+
134
+ A. Please adjust the settings in the print dialog that pops up in the browser. It is recommended to set the margins to [default], uncheck [headers and footers], and check [background graphics]. Furthermore, it is recommended to adopt a backend-generated PDF solution (such as Puppeteer) for a more optimal outcome in a formal environment.
135
+
136
+ #### Q. Why doesn’t the mobile version support xxx feature?
137
+
138
+ A. The first thing to clarify is that the mobile experience will inevitably be inferior to the PC experience no matter what. Therefore, the mobile version is positioned for simple, temporary handling in emergency situations. True design and creation of slides should be done on a computer with full functionality. If there is a specific need for the mobile version, you can try opening it in desktop mode on mobile (of course, the experience will be worse), or the developer can do further custom development.
139
+
140
+ #### Q. About Compatibility?
141
+
142
+ A. This project prioritizes compatibility with Chrome and Firefox. There may be some compatibility issues under Safari. It is not compatible with IE.
143
+
144
+ #### Q. Why isn’t it an NPM package?
145
+
146
+ A. Everyone knows that for general plugins/libraries, a well-packaged NPM package can more easily integrate into existing projects. However, PPTist is special; it is a complete program, not a part of another program. If you need to use PPTist, I believe you will necessarily need to do a lot of custom development based on it, including but not limited to: communication with the backend, various templates and pre-installed materials, new element types, using other solutions to implement certain existing elements, your own themes, changing shortcuts, and so on… It’s not just about installing something that is the same as the existing demo (which may be convenient but has no practical significance in actual product development). As previously mentioned, there are many things that need to be configurable, and it would be difficult to cater to all these needs if it were an NPM plugin. The development effort would be enormous, and currently, I cannot afford it.
147
+
148
+ Therefore, the correct way to develop a project using PPTist is to pull the complete code, try to understand it, and modify it to suit your own needs. There are also similar projects in the community, such as [drawio](https://github.com/jgraph/drawio).
149
+
150
+ #### Q. About AI PPT
151
+ A. I don't want to ride the wave of AI hype, but it's unavoidable as too many people place too much importance on AI. So, I created this DEMO (it's really not that complicated). Currently, this feature is for reference only, and internally, it implements the most basic AI PPT generation logic, which is: template customization + AI-generated data combined with templates + image replacement. To control costs, we can only go this far for now. However, to achieve the effect of a production environment, you would need to do more, such as creating more templates and refining the AI workflow.
152
+
153
+ Note: Image replacement only provides the method and does not offer an actual demonstration function. You will need to provide your own image sources (such as AI text-to-image generation, image library search matching, etc.).
154
+
155
+ #### Q. Other
156
+
157
+ A. Additionally, it is important to emphasize that PPTist is merely an open-source project, not a product tailored for the average user. It primarily offers technical solutions. Some product-oriented demands and optimizations require developers to implement and refine on their own.