jvs-ppt-ui 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -2
- package/dist/lib/jvs-ppt-ui.es.js +17099 -16996
- package/dist/lib/jvs-ppt-ui.umd.js +77 -77
- package/dist/lib/style.css +1 -1
- package/package.json +1 -1
- package/src/components/CanvasArea.vue +40 -10
- package/src/components/Header.vue +12 -4
- package/src/components/PptEditor.vue +8 -1
- package/src/components/Toolbar.vue +2 -1
- package/src/composables/canvas/useCanvasTransform.ts +13 -0
- package/src/composables/useSlides.ts +13 -5
- package/src/types/index.ts +4 -0
- package/src/utils/line.ts +274 -312
package/package.json
CHANGED
|
@@ -3086,6 +3086,9 @@ function stopCreationDrag() {
|
|
|
3086
3086
|
}
|
|
3087
3087
|
|
|
3088
3088
|
activeSlide.value.elements.push(createdElement)
|
|
3089
|
+
if (createdElement.type === 'line' && (createdElement.startConnection || createdElement.endConnection)) {
|
|
3090
|
+
syncSlideConnections(activeSlide.value, logicalWidth.value, logicalHeight.value, [createdElement.id])
|
|
3091
|
+
}
|
|
3089
3092
|
selectedElementId.value = createdElement.id
|
|
3090
3093
|
selectedElementIds.value = [createdElement.id]
|
|
3091
3094
|
justCreatedElement = true
|
|
@@ -4575,14 +4578,37 @@ function startDrag(e: MouseEvent, el: any) {
|
|
|
4575
4578
|
: selectedEls
|
|
4576
4579
|
|
|
4577
4580
|
// 检查是否有连线需要同步,避免无连线时每帧循环
|
|
4578
|
-
const selIds = new Set(selectedElementIds.value)
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4581
|
+
const selIds = new Set<string>(selectedElementIds.value)
|
|
4582
|
+
if (Array.isArray(targetIds)) targetIds.forEach(id => selIds.add(id))
|
|
4583
|
+
dragTargetEls.forEach((item: any) => {
|
|
4584
|
+
if (item?.id) selIds.add(item.id)
|
|
4585
|
+
if (item?.type === 'group' && Array.isArray(item.elements)) {
|
|
4586
|
+
const addChildren = (children: any[]) => {
|
|
4587
|
+
children.forEach(ch => {
|
|
4588
|
+
if (ch?.id) selIds.add(ch.id)
|
|
4589
|
+
if (ch?.type === 'group' && Array.isArray(ch.elements)) addChildren(ch.elements)
|
|
4590
|
+
})
|
|
4591
|
+
}
|
|
4592
|
+
addChildren(item.elements)
|
|
4593
|
+
}
|
|
4594
|
+
})
|
|
4595
|
+
const checkHasConnection = (elements: any[]): boolean => {
|
|
4596
|
+
for (const it of elements) {
|
|
4597
|
+
if (!it) continue
|
|
4598
|
+
if (it.type === 'line' && (
|
|
4599
|
+
selIds.has(it.id) ||
|
|
4600
|
+
(it.startConnection && selIds.has(it.startConnection.id)) ||
|
|
4601
|
+
(it.endConnection && selIds.has(it.endConnection.id))
|
|
4602
|
+
)) {
|
|
4603
|
+
return true
|
|
4604
|
+
}
|
|
4605
|
+
if (it.type === 'group' && Array.isArray(it.elements)) {
|
|
4606
|
+
if (checkHasConnection(it.elements)) return true
|
|
4607
|
+
}
|
|
4608
|
+
}
|
|
4609
|
+
return false
|
|
4610
|
+
}
|
|
4611
|
+
hasConnectionsToSync = checkHasConnection(activeSlide.value?.elements || [])
|
|
4586
4612
|
|
|
4587
4613
|
// Record drag states
|
|
4588
4614
|
dragStates = dragTargetEls.map((item: any) => {
|
|
@@ -7920,8 +7946,12 @@ async function handleGlobalPaste(e: ClipboardEvent) {
|
|
|
7920
7946
|
v-show="el.visible !== false"
|
|
7921
7947
|
v-memo="[
|
|
7922
7948
|
(isDragging || isCanvasDragging)
|
|
7923
|
-
? (selectedElementIds.includes(el.id)
|
|
7924
|
-
|
|
7949
|
+
? (selectedElementIds.includes(el.id)
|
|
7950
|
+
? `${el.style?.left}_${el.style?.top}_${el.style?.width}_${el.style?.height}_${el.style?.rotate}`
|
|
7951
|
+
: (el.type === 'line'
|
|
7952
|
+
? `${el.style?.left}_${el.style?.top}_${el.style?.width}_${el.style?.height}_${el.start?.join(',')}_${el.end?.join(',')}_${el.broken?.join(',')}_${el.broken2?.join(',')}_${el.curve?.join(',')}`
|
|
7953
|
+
: 'frozen'))
|
|
7954
|
+
: `${el.id}_${el.style?.left}_${el.style?.top}_${el.style?.width}_${el.style?.height}_${el.style?.rotate}_${el.type === 'line' ? `${el.start?.join(',')}_${el.end?.join(',')}_${el.broken?.join(',')}_${el.broken2?.join(',')}_${el.curve?.join(',')}_` : ''}${selectedElementIds.includes(el.id)}_${selectedElementId === el.id}_${isEditingText}_${activeTab}_${activeSlideVersion}`
|
|
7925
7955
|
]"
|
|
7926
7956
|
:class="{
|
|
7927
7957
|
selected: selectedElementIds.includes(el.id),
|
|
@@ -7189,12 +7189,16 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7189
7189
|
reader.readAsArrayBuffer(file)
|
|
7190
7190
|
}
|
|
7191
7191
|
|
|
7192
|
-
const props = defineProps<{
|
|
7192
|
+
const props = withDefaults(defineProps<{
|
|
7193
7193
|
slotProps?: any
|
|
7194
|
-
|
|
7194
|
+
showMenu?: boolean
|
|
7195
|
+
}>(), {
|
|
7196
|
+
showMenu: true
|
|
7197
|
+
})
|
|
7195
7198
|
|
|
7196
7199
|
const headerContext = computed(() => ({
|
|
7197
7200
|
...(props.slotProps || {}),
|
|
7201
|
+
showMenu: props.showMenu,
|
|
7198
7202
|
presentationTitle: presentationTitle.value,
|
|
7199
7203
|
isEditingTitle: isEditingTitle.value,
|
|
7200
7204
|
startEditingTitle,
|
|
@@ -7232,7 +7236,7 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7232
7236
|
|
|
7233
7237
|
<!-- 外部左侧主体插槽 (默认呈现内置菜单和标题) -->
|
|
7234
7238
|
<slot name="left" v-bind="headerContext">
|
|
7235
|
-
<el-popover ref="menuPopoverRef" placement="bottom-start" :width="320" trigger="click"
|
|
7239
|
+
<el-popover v-if="showMenu" ref="menuPopoverRef" placement="bottom-start" :width="320" trigger="click"
|
|
7236
7240
|
popper-style="padding: 0; border-radius: 12px; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1); border: 1px solid #e2e8f0; background: #fff; overflow: hidden;">
|
|
7237
7241
|
<template #reference>
|
|
7238
7242
|
<button class="btn-icon" style="padding: 4px; color: var(--text-primary); flex-shrink: 0;" data-tooltip="菜单">
|
|
@@ -7401,7 +7405,7 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7401
7405
|
</div>
|
|
7402
7406
|
|
|
7403
7407
|
<!-- Center Section: Integrated Top Toolbar -->
|
|
7404
|
-
<div class="header-center-tools" :style="{ justifyContent: isMasterEditingView ? 'flex-start' : 'center' }" style="flex: 1; display: flex; align-items: center; overflow: hidden; min-width: 0; position: relative; z-index: 10; margin: 0 8px;">
|
|
7408
|
+
<div class="header-center-tools" :style="{ justifyContent: isMasterEditingView ? 'flex-start' : 'center' }" style="flex: 1; display: flex; align-items: center; overflow-x: auto; overflow-y: hidden; min-width: 0; position: relative; z-index: 10; margin: 0 8px; scrollbar-width: none; -ms-overflow-style: none;">
|
|
7405
7409
|
<!-- 外部中间工具栏插槽 (默认呈现内置 Toolbar,支持前置/后置插槽扩展或整栏替换) -->
|
|
7406
7410
|
<slot name="center" v-bind="headerContext">
|
|
7407
7411
|
<slot name="toolbar" v-bind="headerContext">
|
|
@@ -7826,4 +7830,8 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7826
7830
|
font-weight: 500;
|
|
7827
7831
|
font-family: inherit;
|
|
7828
7832
|
}
|
|
7833
|
+
|
|
7834
|
+
.header-center-tools::-webkit-scrollbar {
|
|
7835
|
+
display: none;
|
|
7836
|
+
}
|
|
7829
7837
|
</style>
|
|
@@ -33,6 +33,7 @@ const props = withDefaults(defineProps<PptEditorProps>(), {
|
|
|
33
33
|
modelValue: null,
|
|
34
34
|
readonly: false,
|
|
35
35
|
showHeader: true,
|
|
36
|
+
showMenu: true,
|
|
36
37
|
showSidebarLeft: true,
|
|
37
38
|
showSidebarRight: true,
|
|
38
39
|
enableMcp: false,
|
|
@@ -895,6 +896,12 @@ const headerSlotContext = computed(() => ({
|
|
|
895
896
|
// 对外通过 ref 暴露所有方法
|
|
896
897
|
defineExpose(editorApi)
|
|
897
898
|
|
|
899
|
+
const computedShowMenu = computed(() => {
|
|
900
|
+
if (props.showMenu !== undefined) return props.showMenu
|
|
901
|
+
if (props.config?.showMenu !== undefined) return props.config.showMenu
|
|
902
|
+
return true
|
|
903
|
+
})
|
|
904
|
+
|
|
898
905
|
onMounted(async () => {
|
|
899
906
|
// 触发 ready 事件
|
|
900
907
|
emit('ready', editorApi)
|
|
@@ -928,7 +935,7 @@ onMounted(async () => {
|
|
|
928
935
|
<div class="app-container jvs-ppt-editor-root" :class="{ 'is-readonly': readonly }">
|
|
929
936
|
<!-- Main top Navigation bar (支持完整 header 插槽替换或精细区域插槽定制) -->
|
|
930
937
|
<slot name="header" v-bind="headerSlotContext">
|
|
931
|
-
<Header ref="headerRef" v-show="showHeader" :slot-props="headerSlotContext">
|
|
938
|
+
<Header ref="headerRef" v-show="showHeader" :show-menu="computedShowMenu" :slot-props="headerSlotContext">
|
|
932
939
|
<!-- 左侧前置 (Logo、返回按钮等) -->
|
|
933
940
|
<template v-if="slots['header-left-prefix']" #left-prefix="sp">
|
|
934
941
|
<slot name="header-left-prefix" v-bind="sp"></slot>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ref, nextTick, type Ref } from 'vue'
|
|
2
2
|
import { SHAPE_PATH_FORMULAS } from '../../utils/pptxShapes'
|
|
3
|
+
import { syncSlideConnections } from '../../utils/line'
|
|
3
4
|
|
|
4
5
|
export interface ResizeState {
|
|
5
6
|
el: any
|
|
@@ -412,6 +413,9 @@ export function useCanvasTransform(options: CanvasTransformOptions) {
|
|
|
412
413
|
}
|
|
413
414
|
}
|
|
414
415
|
}
|
|
416
|
+
if (activeSlide.value && activeSlide.value.elements) {
|
|
417
|
+
syncSlideConnections(activeSlide.value, logicalWidth.value, logicalHeight.value, selectedElementIds.value)
|
|
418
|
+
}
|
|
415
419
|
}
|
|
416
420
|
|
|
417
421
|
function stopResize() {
|
|
@@ -539,6 +543,9 @@ export function useCanvasTransform(options: CanvasTransformOptions) {
|
|
|
539
543
|
newAngle = (newAngle % 360 + 360) % 360
|
|
540
544
|
state.el.style.rotate = Math.round(newAngle)
|
|
541
545
|
}
|
|
546
|
+
if (activeSlide.value && activeSlide.value.elements) {
|
|
547
|
+
syncSlideConnections(activeSlide.value, logicalWidth.value, logicalHeight.value, selectedElementIds.value)
|
|
548
|
+
}
|
|
542
549
|
}
|
|
543
550
|
|
|
544
551
|
function stopRotate() {
|
|
@@ -1031,6 +1038,9 @@ export function useCanvasTransform(options: CanvasTransformOptions) {
|
|
|
1031
1038
|
parentGroup.elements[idx] = { ...curEl, path: newPath, content: newPath }
|
|
1032
1039
|
}
|
|
1033
1040
|
}
|
|
1041
|
+
if (activeSlide.value && activeSlide.value.elements) {
|
|
1042
|
+
syncSlideConnections(activeSlide.value, logicalWidth.value, logicalHeight.value, selectedElementIds.value)
|
|
1043
|
+
}
|
|
1034
1044
|
}
|
|
1035
1045
|
|
|
1036
1046
|
function stopDragKeypoint() {
|
|
@@ -1249,6 +1259,9 @@ export function useCanvasTransform(options: CanvasTransformOptions) {
|
|
|
1249
1259
|
currentElement.value = null
|
|
1250
1260
|
window.removeEventListener('mousemove', handleDragLinePoint)
|
|
1251
1261
|
window.removeEventListener('mouseup', stopDragLinePoint)
|
|
1262
|
+
if (activeSlide.value && activeSlide.value.elements) {
|
|
1263
|
+
syncSlideConnections(activeSlide.value, logicalWidth.value, logicalHeight.value, selectedElementIds.value)
|
|
1264
|
+
}
|
|
1252
1265
|
pauseHistory.value = false
|
|
1253
1266
|
addHistorySnapshot()
|
|
1254
1267
|
}
|
|
@@ -2568,6 +2568,18 @@ function markAllPagesDirty() {
|
|
|
2568
2568
|
for (let i = 0; i < slides.value.length; i++) dirtyPageIndexes.add(i)
|
|
2569
2569
|
}
|
|
2570
2570
|
|
|
2571
|
+
// 递归释放元素(含组合内子元素)的 blob: URL,防止导入替换后组内媒体泄漏
|
|
2572
|
+
function revokeElementBlobUrls(el: any) {
|
|
2573
|
+
if (!el) return
|
|
2574
|
+
if (Array.isArray(el.elements)) {
|
|
2575
|
+
for (const child of el.elements) revokeElementBlobUrls(child)
|
|
2576
|
+
}
|
|
2577
|
+
const src = el.src || el.content
|
|
2578
|
+
if (typeof src === 'string' && src.startsWith('blob:')) {
|
|
2579
|
+
try { URL.revokeObjectURL(src) } catch (e) {}
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2571
2583
|
function revokeSlideBlobUrls(slideList: any[]) {
|
|
2572
2584
|
if (!slideList || !Array.isArray(slideList)) return
|
|
2573
2585
|
for (const slide of slideList) {
|
|
@@ -2585,11 +2597,7 @@ function revokeSlideBlobUrls(slideList: any[]) {
|
|
|
2585
2597
|
}
|
|
2586
2598
|
if (slide.elements && Array.isArray(slide.elements)) {
|
|
2587
2599
|
for (const el of slide.elements) {
|
|
2588
|
-
|
|
2589
|
-
const src = el.src || el.content
|
|
2590
|
-
if (typeof src === 'string' && src.startsWith('blob:')) {
|
|
2591
|
-
try { URL.revokeObjectURL(src) } catch (e) {}
|
|
2592
|
-
}
|
|
2600
|
+
revokeElementBlobUrls(el)
|
|
2593
2601
|
}
|
|
2594
2602
|
}
|
|
2595
2603
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -92,6 +92,8 @@ export type CustomUploadHandler = (
|
|
|
92
92
|
export interface PptEditorConfig {
|
|
93
93
|
/** 演示文稿初始标题 */
|
|
94
94
|
title?: string
|
|
95
|
+
/** 是否显示左上角主菜单按钮(导入/导出/快捷键等弹窗菜单),默认为 true */
|
|
96
|
+
showMenu?: boolean
|
|
95
97
|
/** 是否启用 AI PPT 智能生成与一键美化功能。未配置或为 false 时不显示一键美化功能 */
|
|
96
98
|
enableAi?: boolean
|
|
97
99
|
/** AI 一键美化请求接口地址。如果未配置或未开启,则不显示一键美化功能 */
|
|
@@ -138,6 +140,8 @@ export interface PptEditorProps {
|
|
|
138
140
|
readonly?: boolean
|
|
139
141
|
/** 是否显示顶部导航与工具栏 */
|
|
140
142
|
showHeader?: boolean
|
|
143
|
+
/** 是否显示左上角主菜单按钮(导入/导出/快捷键等弹窗菜单),默认为 true */
|
|
144
|
+
showMenu?: boolean
|
|
141
145
|
/** 是否显示左侧缩略图导航栏 */
|
|
142
146
|
showSidebarLeft?: boolean
|
|
143
147
|
/** 是否显示右侧属性面板 */
|