jvs-ppt-ui 0.0.7 → 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 +135146 -112663
- package/dist/lib/jvs-ppt-ui.umd.js +101 -101
- package/dist/lib/style.css +1 -1
- package/package.json +1 -1
- package/src/components/CanvasArea.vue +91 -21
- package/src/components/Header.vue +42 -23
- package/src/components/PptEditor.vue +1047 -1005
- package/src/components/PresentationMode.vue +3 -1
- package/src/components/SidebarLeft.vue +14 -7
- package/src/components/Toolbar.vue +2 -1
- package/src/components/sidebar/panels/SidebarDesignPanel.vue +1341 -1336
- package/src/composables/canvas/useCanvasTable.ts +183 -146
- package/src/composables/canvas/useCanvasTransform.ts +13 -0
- package/src/composables/sidebar/useSidebarTextFormat.ts +1 -1
- package/src/composables/useSlides.ts +244 -39
- package/src/index.ts +2 -1
- package/src/types/index.ts +4 -0
- package/src/utils/line.ts +274 -312
- package/dist/lib/__vite-browser-external-CbV4tiB6.js +0 -7
- package/dist/lib/dist-DkuYVyNR.js +0 -16010
- package/dist/lib/es-CfdgxdYp.js +0 -477
- package/dist/lib/jszip.min-Kba9Mb0l.js +0 -3034
- package/dist/lib/pptxgen.es-D0HrgUe_.js +0 -2352
package/package.json
CHANGED
|
@@ -965,6 +965,18 @@ function getResizeCursor(handle: string, el: any) {
|
|
|
965
965
|
// 拖拽期间全局图元尺寸缓存池:彻底消除拖动期间的 Forced Synchronous Layout(强制回流与 DOM 读取)
|
|
966
966
|
const activeDragDimensions = new Map<string, { width: number; height: number }>()
|
|
967
967
|
|
|
968
|
+
// 渲染期图元尺寸记忆化:模板对同一元素每渲染批会调 getElementWidthHeight 2-4 次,
|
|
969
|
+
// 签名含全部输入(style 尺寸/排版项、内容长度+首尾采样、选中 RO 尺寸),命中则免重复 DOM 测量
|
|
970
|
+
let elementDimCache = new WeakMap<object, { sig: string; dims: { width: number; height: number } }>()
|
|
971
|
+
watch(activeIndex, () => {
|
|
972
|
+
elementDimCache = new WeakMap()
|
|
973
|
+
})
|
|
974
|
+
if (typeof document !== 'undefined' && (document as any).fonts?.ready) {
|
|
975
|
+
;(document as any).fonts.ready.then(() => {
|
|
976
|
+
elementDimCache = new WeakMap()
|
|
977
|
+
}).catch(() => {})
|
|
978
|
+
}
|
|
979
|
+
|
|
968
980
|
const getOverlayWidth = (el: any) => {
|
|
969
981
|
if (el.type === 'line') {
|
|
970
982
|
return el.style?.width || (el.width !== undefined ? `${el.width}px` : '1px')
|
|
@@ -1197,8 +1209,11 @@ function observeSelectedElement() {
|
|
|
1197
1209
|
const curW = curIsVertical && curEditableDom
|
|
1198
1210
|
? Math.max(curEditableDom.scrollWidth, dom.offsetWidth)
|
|
1199
1211
|
: dom.offsetWidth
|
|
1200
|
-
|
|
1201
|
-
|
|
1212
|
+
// 值相等守卫:避免 RO → 渲染 → RO 的无谓链路
|
|
1213
|
+
const wStr = `${curW}px`
|
|
1214
|
+
const hStr = `${curH}px`
|
|
1215
|
+
if (selectedElementWidth.value !== wStr) selectedElementWidth.value = wStr
|
|
1216
|
+
if (selectedElementHeight.value !== hStr) selectedElementHeight.value = hStr
|
|
1202
1217
|
})
|
|
1203
1218
|
selectedElementResizeObserver.observe(dom)
|
|
1204
1219
|
if (editableDom && isVertical) {
|
|
@@ -1432,18 +1447,20 @@ function getCubeShadingPaths(el: any) {
|
|
|
1432
1447
|
function fitToScreen() {
|
|
1433
1448
|
if (!viewportContainerRef.value) return
|
|
1434
1449
|
|
|
1435
|
-
|
|
1436
|
-
|
|
1450
|
+
const cw = viewportContainerRef.value.clientWidth
|
|
1451
|
+
const ch = viewportContainerRef.value.clientHeight
|
|
1437
1452
|
|
|
1438
1453
|
const padding = 60
|
|
1439
|
-
const
|
|
1440
|
-
const
|
|
1454
|
+
const scale = Math.min((cw - padding) / logicalWidth.value, (ch - padding) / logicalHeight.value)
|
|
1455
|
+
const targetZoom = Math.max(20, Math.min(200, Math.round(scale * 100)))
|
|
1441
1456
|
|
|
1442
|
-
|
|
1443
|
-
|
|
1457
|
+
// 守卫:容器尺寸与目标 zoom 都与现状一致时直接返回,
|
|
1458
|
+
// 吃掉 window resize 与 viewport RO 的双触发/RO 风暴(用户手动缩放不受影响:zoomRatio≠target 时照常重适配)
|
|
1459
|
+
if (cw === viewportWidth.value && ch === viewportHeight.value && targetZoom === zoomRatio.value) return
|
|
1444
1460
|
|
|
1445
|
-
|
|
1446
|
-
|
|
1461
|
+
viewportWidth.value = cw
|
|
1462
|
+
viewportHeight.value = ch
|
|
1463
|
+
zoomRatio.value = targetZoom
|
|
1447
1464
|
centerSlideInViewport()
|
|
1448
1465
|
}
|
|
1449
1466
|
|
|
@@ -3069,6 +3086,9 @@ function stopCreationDrag() {
|
|
|
3069
3086
|
}
|
|
3070
3087
|
|
|
3071
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
|
+
}
|
|
3072
3092
|
selectedElementId.value = createdElement.id
|
|
3073
3093
|
selectedElementIds.value = [createdElement.id]
|
|
3074
3094
|
justCreatedElement = true
|
|
@@ -4126,6 +4146,27 @@ function getElementWidthHeight(el: any): { width: number; height: number } {
|
|
|
4126
4146
|
const isWPercent = wStr.endsWith('%')
|
|
4127
4147
|
const isHPercent = hStr.endsWith('%')
|
|
4128
4148
|
|
|
4149
|
+
// 纯数学快路径:非文本、非选中的元素,宽高都能从 style 纯算(%×基数 / px 字面量 / 无 width 归 200),
|
|
4150
|
+
// 与下方 else 分支逐条等价,但零 DOM 读、零 offset* 强制布局
|
|
4151
|
+
if (el.type !== 'text' && el.id !== selectedElementId.value && hStr && hStr !== 'auto' && (!wStr || !isNaN(parseFloat(wStr)))) {
|
|
4152
|
+
const parentGroup = findParentGroup(el.id)
|
|
4153
|
+
const baseDim = parentGroup ? getElementPixels(parentGroup) : { width: logicalWidth.value, height: logicalHeight.value }
|
|
4154
|
+
const width = !wStr
|
|
4155
|
+
? 200
|
|
4156
|
+
: (isWPercent ? (parseFloat(wStr) / 100) * baseDim.width : (parseFloat(wStr) || 200))
|
|
4157
|
+
const height = isHPercent ? (parseFloat(hStr) / 100) * baseDim.height : (parseFloat(hStr) || 100)
|
|
4158
|
+
return { width, height }
|
|
4159
|
+
}
|
|
4160
|
+
|
|
4161
|
+
// 记忆化:同渲染批内模板会重复调用本函数,签名命中直接复用上次测量结果
|
|
4162
|
+
const st = el.style || {}
|
|
4163
|
+
const selW = el.id === selectedElementId.value ? selectedElementWidth.value : ''
|
|
4164
|
+
const selH = el.id === selectedElementId.value ? selectedElementHeight.value : ''
|
|
4165
|
+
const contentSig = typeof el.content === 'string' ? `${el.content.length}|${el.content.slice(0, 12)}|${el.content.slice(-12)}` : ''
|
|
4166
|
+
const dimSig = `${el.type}|${wStr}|${hStr}|${el.width || 0}|${el.height || 0}|${contentSig}|${el.latex || ''}|${st.fontSize || ''}|${st.fontFamily || ''}|${st.fontWeight || ''}|${st.lineHeight || ''}|${st.letterSpacing || ''}|${st.writingMode || ''}|${st.whiteSpace || ''}|${st.padding || ''}|${el.autoFit?.type || ''}|${el.autoFit?.fontScale || 0}|${selW}|${selH}|${logicalWidth.value}|${logicalHeight.value}|${isDragging.value ? 1 : 0}|${isResizing.value ? 1 : 0}`
|
|
4167
|
+
const dimCached = elementDimCache.get(el)
|
|
4168
|
+
if (dimCached && dimCached.sig === dimSig) return dimCached.dims
|
|
4169
|
+
|
|
4129
4170
|
if (!isWPercent && !isHPercent && wStr && hStr && wStr !== 'auto' && hStr !== 'auto') {
|
|
4130
4171
|
const w = parseFloat(wStr)
|
|
4131
4172
|
const h = parseFloat(hStr)
|
|
@@ -4221,7 +4262,9 @@ function getElementWidthHeight(el: any): { width: number; height: number } {
|
|
|
4221
4262
|
}
|
|
4222
4263
|
}
|
|
4223
4264
|
|
|
4224
|
-
|
|
4265
|
+
const dims = { width, height }
|
|
4266
|
+
elementDimCache.set(el, { sig: dimSig, dims })
|
|
4267
|
+
return dims
|
|
4225
4268
|
}
|
|
4226
4269
|
|
|
4227
4270
|
// Helper to get element dimensions in logical canvas pixel space
|
|
@@ -4535,14 +4578,37 @@ function startDrag(e: MouseEvent, el: any) {
|
|
|
4535
4578
|
: selectedEls
|
|
4536
4579
|
|
|
4537
4580
|
// 检查是否有连线需要同步,避免无连线时每帧循环
|
|
4538
|
-
const selIds = new Set(selectedElementIds.value)
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
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 || [])
|
|
4546
4612
|
|
|
4547
4613
|
// Record drag states
|
|
4548
4614
|
dragStates = dragTargetEls.map((item: any) => {
|
|
@@ -7880,8 +7946,12 @@ async function handleGlobalPaste(e: ClipboardEvent) {
|
|
|
7880
7946
|
v-show="el.visible !== false"
|
|
7881
7947
|
v-memo="[
|
|
7882
7948
|
(isDragging || isCanvasDragging)
|
|
7883
|
-
? (selectedElementIds.includes(el.id)
|
|
7884
|
-
|
|
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}`
|
|
7885
7955
|
]"
|
|
7886
7956
|
:class="{
|
|
7887
7957
|
selected: selectedElementIds.includes(el.id),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref, computed, nextTick } from 'vue'
|
|
2
|
+
import { ref, computed, nextTick, defineAsyncComponent, watch } from 'vue'
|
|
3
3
|
import Toolbar from './Toolbar.vue'
|
|
4
4
|
import { useSlides } from '../composables/useSlides'
|
|
5
5
|
import type JSZip from 'jszip'
|
|
@@ -13,12 +13,16 @@ import { parseColorString, extractTextAlignFromHtml, updateTextAlignInHtml, norm
|
|
|
13
13
|
|
|
14
14
|
import { SHAPE_LIST as PPTX_SHAPE_LIST, SHAPE_PATH_FORMULAS as PPTX_SHAPE_PATH_FORMULAS } from '../utils/pptxShapes'
|
|
15
15
|
import { inferElementTextType, type SlideTextType } from '../utils/textTypes'
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// 快捷键弹层异步化;mock DSL 模板(~77KB)改为触发时动态加载
|
|
17
|
+
const ShortcutsDialog = defineAsyncComponent(() => import('./ShortcutsDialog.vue'))
|
|
18
18
|
|
|
19
19
|
const { presentationTitle, exportSlides, importSlides, currentZipMeta, startPresentation, activeIndex, slides, notify, renderLatexToSvg, globalFontFamily, globalFontColor, globalThemeColor, themeSchemeFillMap, themeClrScheme, logicalWidth, logicalHeight, dslInputDialogVisible, aiPptDialogVisible, shortcutsDialogVisible, registerPPTParser, isMasterEditingView, enterMasterView, exitMasterView, zoomRatio, resetZoom, normalizeSlideBackground, getEffectiveMasterElementsForSlide, getEffectiveSlideBackground, uploadMediaResourcesInSlides, getCustomUploadHandler } = useSlides()
|
|
20
20
|
import { sanitizeNbspInHtml, checkIsVertical } from '../composables/useSlides'
|
|
21
21
|
|
|
22
|
+
// 快捷键弹层挂载闩锁:首次打开后保持挂载,异步 chunk 只加载一次
|
|
23
|
+
const shortcutsOpened = ref(false)
|
|
24
|
+
watch(shortcutsDialogVisible, v => { if (v) shortcutsOpened.value = true })
|
|
25
|
+
|
|
22
26
|
function handleZoomCommand(command: string | number) {
|
|
23
27
|
if (command === 'reset') {
|
|
24
28
|
resetZoom()
|
|
@@ -73,9 +77,11 @@ const handleAIPPT = () => {
|
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
const handleLoadDsl = () => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
import('../utils/dslTemplate').then(m => {
|
|
81
|
+
importSlides(JSON.parse(JSON.stringify(m.mockAIPptDsl)))
|
|
82
|
+
presentationTitle.value = 'AI-DSL 智能技术排版示例'
|
|
83
|
+
notify('成功导入 AI-DSL 演示幻灯片')
|
|
84
|
+
})
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
const handleJVSPPTImport = () => {
|
|
@@ -7183,12 +7189,16 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7183
7189
|
reader.readAsArrayBuffer(file)
|
|
7184
7190
|
}
|
|
7185
7191
|
|
|
7186
|
-
const props = defineProps<{
|
|
7192
|
+
const props = withDefaults(defineProps<{
|
|
7187
7193
|
slotProps?: any
|
|
7188
|
-
|
|
7194
|
+
showMenu?: boolean
|
|
7195
|
+
}>(), {
|
|
7196
|
+
showMenu: true
|
|
7197
|
+
})
|
|
7189
7198
|
|
|
7190
7199
|
const headerContext = computed(() => ({
|
|
7191
7200
|
...(props.slotProps || {}),
|
|
7201
|
+
showMenu: props.showMenu,
|
|
7192
7202
|
presentationTitle: presentationTitle.value,
|
|
7193
7203
|
isEditingTitle: isEditingTitle.value,
|
|
7194
7204
|
startEditingTitle,
|
|
@@ -7220,16 +7230,16 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7220
7230
|
<template>
|
|
7221
7231
|
<header class="app-header">
|
|
7222
7232
|
<!-- Left side: Hamburger menu + Title input -->
|
|
7223
|
-
<div style="display: flex; align-items: center; gap:
|
|
7233
|
+
<div style="display: flex; align-items: center; gap: 8px; flex-shrink: 0; min-width: 0;">
|
|
7224
7234
|
<!-- 外部左侧前置插槽 (例如返回按钮、Logo) -->
|
|
7225
7235
|
<slot name="left-prefix" v-bind="headerContext"></slot>
|
|
7226
7236
|
|
|
7227
7237
|
<!-- 外部左侧主体插槽 (默认呈现内置菜单和标题) -->
|
|
7228
7238
|
<slot name="left" v-bind="headerContext">
|
|
7229
|
-
<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"
|
|
7230
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;">
|
|
7231
7241
|
<template #reference>
|
|
7232
|
-
<button class="btn-icon" style="padding: 4px; color: var(--text-primary)" data-tooltip="菜单">
|
|
7242
|
+
<button class="btn-icon" style="padding: 4px; color: var(--text-primary); flex-shrink: 0;" data-tooltip="菜单">
|
|
7233
7243
|
<svg class="icon" style="width: 18px; height: 18px" viewBox="0 0 24 24">
|
|
7234
7244
|
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" />
|
|
7235
7245
|
</svg>
|
|
@@ -7372,17 +7382,18 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7372
7382
|
</div>
|
|
7373
7383
|
</div>
|
|
7374
7384
|
</el-popover>
|
|
7375
|
-
<div class="presentation-title-container" style="display: flex; align-items: center; gap: 6px; margin-left: 8px;">
|
|
7385
|
+
<div class="presentation-title-container" style="display: flex; align-items: center; gap: 6px; margin-left: 8px; flex-shrink: 0; min-width: 0; max-width: 280px;">
|
|
7376
7386
|
<span v-if="!isEditingTitle" @click="startEditingTitle" class="presentation-title-text"
|
|
7377
|
-
style="font-size: 15px; font-weight: 600; color: #0f172a; cursor: pointer; transition: color 0.15s ease;"
|
|
7387
|
+
style="font-size: 15px; font-weight: 600; color: #0f172a; cursor: pointer; transition: color 0.15s ease; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: inline-block; max-width: 240px;"
|
|
7388
|
+
:title="presentationTitle || '未命名演示文稿'">
|
|
7378
7389
|
{{ presentationTitle || '未命名演示文稿' }}
|
|
7379
7390
|
</span>
|
|
7380
7391
|
<input v-else ref="titleInputRef" class="presentation-title-input-edit" v-model="presentationTitle"
|
|
7381
7392
|
@blur="stopEditingTitle" @keyup.enter="stopEditingTitle"
|
|
7382
|
-
style="font-size: 15px; font-weight: 600; color: #0f172a; border: 1px solid #3b82f6; border-radius: 6px; padding: 4px 8px; outline: none; background: #fff; box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.15); width:
|
|
7393
|
+
style="font-size: 15px; font-weight: 600; color: #0f172a; border: 1px solid #3b82f6; border-radius: 6px; padding: 4px 8px; outline: none; background: #fff; box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.15); width: 200px;" />
|
|
7383
7394
|
<svg v-if="!isEditingTitle" @click="startEditingTitle" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
7384
7395
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
|
7385
|
-
style="width: 14px; height: 14px; color: #3b82f6; cursor: pointer; margin-left: 2px;">
|
|
7396
|
+
style="width: 14px; height: 14px; color: #3b82f6; cursor: pointer; margin-left: 2px; flex-shrink: 0;">
|
|
7386
7397
|
<path d="M12 20h9" />
|
|
7387
7398
|
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" />
|
|
7388
7399
|
</svg>
|
|
@@ -7394,7 +7405,7 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7394
7405
|
</div>
|
|
7395
7406
|
|
|
7396
7407
|
<!-- Center Section: Integrated Top Toolbar -->
|
|
7397
|
-
<div class="header-center-tools" :style="{ justifyContent: isMasterEditingView ? 'flex-start' : 'center' }" style="flex: 1; display: flex; align-items: center; overflow:
|
|
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;">
|
|
7398
7409
|
<!-- 外部中间工具栏插槽 (默认呈现内置 Toolbar,支持前置/后置插槽扩展或整栏替换) -->
|
|
7399
7410
|
<slot name="center" v-bind="headerContext">
|
|
7400
7411
|
<slot name="toolbar" v-bind="headerContext">
|
|
@@ -7411,7 +7422,7 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7411
7422
|
</div>
|
|
7412
7423
|
|
|
7413
7424
|
<!-- Right side: Utility icons -->
|
|
7414
|
-
<div style="display: flex; align-items: center; gap: 10px">
|
|
7425
|
+
<div style="display: flex; align-items: center; gap: 10px; flex-shrink: 0; white-space: nowrap;">
|
|
7415
7426
|
<!-- 外部右侧前置插槽 -->
|
|
7416
7427
|
<slot name="right-prefix" v-bind="headerContext"></slot>
|
|
7417
7428
|
|
|
@@ -7422,7 +7433,7 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7422
7433
|
<!-- Zoom Ratio Selector Dropdown -->
|
|
7423
7434
|
<el-dropdown trigger="click" @command="handleZoomCommand">
|
|
7424
7435
|
<div class="zoom-select-btn"
|
|
7425
|
-
style="height: 32px; display: flex; align-items: center; gap: 8px; padding: 0 12px; border: none; border-radius: 6px; font-size: 13px; font-weight: 500; color: #334155; cursor: pointer; background: #f1f5f9;">
|
|
7436
|
+
style="height: 32px; display: flex; align-items: center; gap: 8px; padding: 0 12px; border: none; border-radius: 6px; font-size: 13px; font-weight: 500; color: #334155; cursor: pointer; background: #f1f5f9; white-space: nowrap; flex-shrink: 0;">
|
|
7426
7437
|
<span>{{ zoomRatio }}%</span>
|
|
7427
7438
|
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="#64748b" stroke-width="2.5">
|
|
7428
7439
|
<path d="M6 9l6 6 6-6" />
|
|
@@ -7444,7 +7455,7 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7444
7455
|
|
|
7445
7456
|
<!-- Exit Master View Button (Placed right after Zoom Ratio Dropdown) -->
|
|
7446
7457
|
<button v-if="isMasterEditingView" class="btn-exit-master" @click="exitMasterView()"
|
|
7447
|
-
style="height: 32px; padding: 0 12px; border: 1px solid #fee2e2; border-radius: 6px; background: #fef2f2; color: #ef4444; font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 6px; cursor: pointer; transition: all 0.15s ease;"
|
|
7458
|
+
style="height: 32px; padding: 0 12px; border: 1px solid #fee2e2; border-radius: 6px; background: #fef2f2; color: #ef4444; font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 6px; cursor: pointer; transition: all 0.15s ease; white-space: nowrap; flex-shrink: 0;"
|
|
7448
7459
|
title="退出母版" data-tooltip="退出母版">
|
|
7449
7460
|
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="#ef4444" stroke-width="2.2"
|
|
7450
7461
|
stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -7455,8 +7466,8 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7455
7466
|
|
|
7456
7467
|
<!-- Slide Master Mode Toggle Button -->
|
|
7457
7468
|
<button v-if="!isMasterEditingView" class="btn-master-toggle" @click="enterMasterView()"
|
|
7458
|
-
style="height: 32px; padding: 0 12px; border: none; border-radius: 6px; background: #f1f5f9; color: #334155; font-size: 13px; font-weight: 500; display: flex; align-items: center; gap: 6px; cursor: pointer;">
|
|
7459
|
-
<svg viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor">
|
|
7469
|
+
style="height: 32px; padding: 0 12px; border: none; border-radius: 6px; background: #f1f5f9; color: #334155; font-size: 13px; font-weight: 500; display: flex; align-items: center; gap: 6px; cursor: pointer; white-space: nowrap; flex-shrink: 0;">
|
|
7470
|
+
<svg viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor" style="flex-shrink: 0;">
|
|
7460
7471
|
<use xlink:href="#icon-muban"></use>
|
|
7461
7472
|
</svg>
|
|
7462
7473
|
<span>母版</span>
|
|
@@ -7465,7 +7476,7 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7465
7476
|
<!-- Presentation Mode Split Button -->
|
|
7466
7477
|
<div v-if="!isMasterEditingView" class="presentation-split-btn">
|
|
7467
7478
|
<button class="presentation-main-btn" @click="startFromBeginning" title="从头开始放映 (F5)">
|
|
7468
|
-
<svg viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor">
|
|
7479
|
+
<svg viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor" style="flex-shrink: 0;">
|
|
7469
7480
|
<use xlink:href="#icon-huandengpianfangying"></use>
|
|
7470
7481
|
</svg>
|
|
7471
7482
|
<span>幻灯片放映</span>
|
|
@@ -7513,7 +7524,7 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7513
7524
|
</div>
|
|
7514
7525
|
|
|
7515
7526
|
<!-- Shortcuts Dialog Modal -->
|
|
7516
|
-
<ShortcutsDialog v-model:visible="shortcutsDialogVisible" />
|
|
7527
|
+
<ShortcutsDialog v-if="shortcutsOpened" v-model:visible="shortcutsDialogVisible" />
|
|
7517
7528
|
</header>
|
|
7518
7529
|
|
|
7519
7530
|
<teleport to="body">
|
|
@@ -7746,6 +7757,8 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7746
7757
|
color: #3b82f6;
|
|
7747
7758
|
transition: all 0.15s ease;
|
|
7748
7759
|
box-sizing: border-box;
|
|
7760
|
+
white-space: nowrap;
|
|
7761
|
+
flex-shrink: 0;
|
|
7749
7762
|
}
|
|
7750
7763
|
|
|
7751
7764
|
.presentation-split-btn:hover {
|
|
@@ -7767,6 +7780,8 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7767
7780
|
cursor: pointer;
|
|
7768
7781
|
transition: background-color 0.15s ease;
|
|
7769
7782
|
border-radius: 5px 0 0 5px;
|
|
7783
|
+
white-space: nowrap;
|
|
7784
|
+
flex-shrink: 0;
|
|
7770
7785
|
}
|
|
7771
7786
|
|
|
7772
7787
|
.presentation-main-btn:hover {
|
|
@@ -7815,4 +7830,8 @@ const importPptxArrayBuffer = async (buffer: ArrayBuffer, fileName?: string): Pr
|
|
|
7815
7830
|
font-weight: 500;
|
|
7816
7831
|
font-family: inherit;
|
|
7817
7832
|
}
|
|
7833
|
+
|
|
7834
|
+
.header-center-tools::-webkit-scrollbar {
|
|
7835
|
+
display: none;
|
|
7836
|
+
}
|
|
7818
7837
|
</style>
|