jvs-ppt-ui 0.0.11 → 0.0.14

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jvs-ppt-ui",
3
3
  "private": false,
4
- "version": "0.0.11",
4
+ "version": "0.0.14",
5
5
  "type": "module",
6
6
  "main": "./dist/lib/jvs-ppt-ui.umd.js",
7
7
  "module": "./dist/lib/jvs-ppt-ui.es.js",
package/src/App.vue CHANGED
@@ -47,7 +47,7 @@ async function handleCustomUpload(fileOrBlob: File | Blob, context: UploadResour
47
47
  <!-- :custom-upload="handleCustomUpload" -->
48
48
  <PptEditor ref="editorRef" :custom-upload="handleCustomUpload" :enable-mcp="true" :enable-ai="true"
49
49
  ai-api-url="/jvs-dam/api/ppt/ai-replace" :enable-material="true"
50
- material-tree-api-url="/jvs-dam/api/dam/material/tree" material-page-api-url="/jvs-dam/api/dam/material/page"
51
- material-api-url="/jvs-dam/api/dam/material/tree" material-save-api-url="/jvs-dam/api/dam/material/save"
50
+ material-api-url="/jvs-dam/stock/search" material-page-api-url="/jvs-dam/stock/search"
51
+ material-save-api-url="/jvs-dam/api/dam/material/save"
52
52
  @save="handleSave" @ready="handleReady" />
53
53
  </template>
@@ -18,6 +18,7 @@ import CanvasGroupElement from './canvas/elements/CanvasGroupElement.vue'
18
18
  import CanvasSymbolPanel from './canvas/panels/CanvasSymbolPanel.vue'
19
19
  import CanvasSearchPanel from './canvas/panels/CanvasSearchPanel.vue'
20
20
  import CanvasSelectionPanel from './canvas/panels/CanvasSelectionPanel.vue'
21
+ import { isTextContainingElement } from '../utils/groupFlip'
21
22
 
22
23
  import { useCanvasTable, cleanTableDOMForSaving } from '../composables/canvas/useCanvasTable'
23
24
  import { useCanvasAlignment } from '../composables/canvas/useCanvasAlignment'
@@ -66,7 +67,7 @@ const {
66
67
  globalThemeColor, globalFontColor, globalFontFamily, globalBgColor, getActiveSlideThemeRecipe, croppingElementId,
67
68
  isMasterEditingView, slideMasters, activeMasterId, activeLayoutId, activeLayout, sortSlideElements,
68
69
  selectedSlideIndexes, isMouseOverSlideList, selectAllSlides, deleteSelectedSlides, copySelectedSlides, cutSelectedSlides, duplicateSelectedSlides, duplicateSelectedElements,
69
- getEffectiveMasterElementsForSlide, getEffectiveSlideBackground, uploadResource
70
+ getEffectiveMasterElementsForSlide, getEffectiveSlideBackground, uploadResource, isReadonly
70
71
  } = useSlides()
71
72
  const activeTab = activeTabRaw as any
72
73
 
@@ -205,7 +206,8 @@ const {
205
206
  applyTextStyleToHtml: (html: string, style: Record<string, any>) => applyTextStyleToHtml(html, style),
206
207
  renderLatexToSvg: (latex: string) => renderLatexToSvg(latex),
207
208
  sanitizeNbspInHtml: (html: string) => sanitizeNbspInHtml(html),
208
- updateTextFontScale: (el: any) => updateTextFontScale(el)
209
+ updateTextFontScale: (el: any) => updateTextFontScale(el),
210
+ isReadonly
209
211
  })
210
212
  const lastMouseDownTarget = ref<HTMLElement | null>(null)
211
213
 
@@ -405,7 +407,8 @@ const {
405
407
  getOverlayWidth: (el: any) => getOverlayWidth(el),
406
408
  getOverlayHeight: (el: any) => getOverlayHeight(el),
407
409
  selectedElementWidth,
408
- selectedElementHeight
410
+ selectedElementHeight,
411
+ isReadonly
409
412
  })
410
413
 
411
414
  const getSelectedCellCount = () => selectedCellCount.value
@@ -1509,7 +1512,8 @@ const { handleGlobalKeyDown } = useCanvasShortcuts({
1509
1512
  getElementPixels: (el: any) => getElementPixels(el),
1510
1513
  shapeFormatPainter,
1511
1514
  notify,
1512
- addHistorySnapshot
1515
+ addHistorySnapshot,
1516
+ isReadonly
1513
1517
  })
1514
1518
 
1515
1519
  watch(fitTrigger, () => {
@@ -2397,6 +2401,7 @@ function getLineConfigFromContent(content: string) {
2397
2401
  }
2398
2402
 
2399
2403
  function handleCanvasDblClick(e: MouseEvent) {
2404
+ if (isReadonly.value) return
2400
2405
  // 1. 如果事件目标本身或其父级就在某元素内
2401
2406
  const target = e.target as HTMLElement
2402
2407
  const closestElDom = target?.closest?.('.canvas-element, [id^="canvas-el-"]') as HTMLElement | null
@@ -2555,6 +2560,12 @@ function handleCanvasMouseDown(e: MouseEvent) {
2555
2560
  return
2556
2561
  }
2557
2562
 
2563
+ if (isReadonly.value) {
2564
+ selectedElementId.value = null
2565
+ selectedElementIds.value = []
2566
+ return
2567
+ }
2568
+
2558
2569
  if (shapeFormatPainter.value) {
2559
2570
  shapeFormatPainter.value = null
2560
2571
  return
@@ -3256,7 +3267,7 @@ let lineControlPointStartX = 0
3256
3267
  let lineControlPointStartY = 0
3257
3268
 
3258
3269
  function startDragLineControlPoint(e: MouseEvent, el: any, type: string) {
3259
- if (el.locked) return
3270
+ if (isReadonly.value || el.locked) return
3260
3271
  e.stopPropagation()
3261
3272
  e.preventDefault()
3262
3273
 
@@ -3578,6 +3589,10 @@ function handleElementMouseDown(e: MouseEvent, el: any) {
3578
3589
 
3579
3590
  e.stopPropagation()
3580
3591
 
3592
+ if (isReadonly.value) {
3593
+ return
3594
+ }
3595
+
3581
3596
  if (shapeFormatPainter.value) {
3582
3597
  if (shapeFormatPainter.value.mode === 'text-range') {
3583
3598
  return
@@ -3606,6 +3621,7 @@ function handleElementMouseDown(e: MouseEvent, el: any) {
3606
3621
  }
3607
3622
 
3608
3623
  function handleBorderMouseDown(e: MouseEvent, el: any) {
3624
+ if (isReadonly.value) return
3609
3625
  if (e.button !== 0) return
3610
3626
  if (isLockedMasterElement(el) || el.locked) {
3611
3627
  return
@@ -3650,6 +3666,7 @@ function handleBorderMouseDown(e: MouseEvent, el: any) {
3650
3666
  }
3651
3667
 
3652
3668
  function handleElementDblClick(e: MouseEvent, el: any) {
3669
+ if (isReadonly.value) return
3653
3670
  let target = el
3654
3671
  if (selectedElementIds.value.length > 0 && !selectedElementIds.value.includes(el.id)) {
3655
3672
  const activeSelectedEl = findSelectedElementUnderPoint(e.clientX, e.clientY)
@@ -3762,6 +3779,7 @@ function handleChildDragKeyUp(e: KeyboardEvent) {
3762
3779
  }
3763
3780
 
3764
3781
  function startChildDrag(e: MouseEvent, child: any, parentGroup: any) {
3782
+ if (isReadonly.value) return
3765
3783
  const childContainerEl = document.getElementById(`editable-${child.id}`)
3766
3784
  const childCanvasElDom = document.getElementById(`canvas-el-${child.id}`)
3767
3785
  const isChildClickInside = (childContainerEl && (childContainerEl === e.target || childContainerEl.contains(e.target as Node))) ||
@@ -3975,6 +3993,7 @@ function handleChildMouseDown(e: MouseEvent, child: any, parentGroup: any) {
3975
3993
  checkAndExitEditText(undefined, true)
3976
3994
  }
3977
3995
  e.stopPropagation()
3996
+ if (isReadonly.value) return
3978
3997
 
3979
3998
  if (shapeFormatPainter.value) {
3980
3999
  if (shapeFormatPainter.value.mode === 'text-range') {
@@ -4039,7 +4058,7 @@ function handleChildMouseDown(e: MouseEvent, child: any, parentGroup: any) {
4039
4058
  }
4040
4059
 
4041
4060
  function handleChildClick(e: MouseEvent, child: any, parentGroup: any) {
4042
- if (activeTool.value) return
4061
+ if (isReadonly.value || activeTool.value) return
4043
4062
  if (isLockedMasterElement(child)) return
4044
4063
  e.stopPropagation()
4045
4064
  if (dragOccurred) {
@@ -4073,6 +4092,7 @@ function handleChildClick(e: MouseEvent, child: any, parentGroup: any) {
4073
4092
  }
4074
4093
 
4075
4094
  function handleChildDblClick(e: MouseEvent, child: any, parentGroup: any) {
4095
+ if (isReadonly.value) return
4076
4096
  e.stopPropagation()
4077
4097
  if (isLockedMasterElement(child)) return
4078
4098
  // 已在编辑该子元素时的双击 = 原生选词,保持选区不重进编辑
@@ -4096,6 +4116,7 @@ function handleChildDblClick(e: MouseEvent, child: any, parentGroup: any) {
4096
4116
  }
4097
4117
 
4098
4118
  function handleElementClick(e: MouseEvent, elId: string) {
4119
+ if (isReadonly.value) return
4099
4120
  if (activeTool.value) return
4100
4121
  if (elId) {
4101
4122
  const targetEl = activeSlide.value?.elements.find(x => x.id === elId)
@@ -4385,7 +4406,7 @@ function getOverlayStyle(el: any) {
4385
4406
  width: `${w}px`,
4386
4407
  height: `${h}px`,
4387
4408
  zIndex: isSelectedActive ? 300 : 100,
4388
- transform: `rotate(${((parentPixels.rotate || 0) + (el.style?.rotate || 0))}deg) scale(${el.type !== 'text' && el.type !== 'line' && ((el.style as any)?.flipH || el.flipH || el.isFlipH) ? -1 : 1}, ${el.type !== 'text' && el.type !== 'line' && ((el.style as any)?.flipV || el.flipV || el.isFlipV) ? -1 : 1})`,
4409
+ transform: `rotate(${((parentPixels.rotate || 0) + (el.style?.rotate || 0))}deg) scale(${!isTextContainingElement(el) && ((el.style as any)?.flipH || el.flipH || el.isFlipH) ? -1 : 1}, ${!isTextContainingElement(el) && ((el.style as any)?.flipV || el.flipV || el.isFlipV) ? -1 : 1})`,
4389
4410
  pointerEvents: 'none' as const,
4390
4411
  boxSizing: 'border-box' as const,
4391
4412
  '--handle-scale': +(100 / (zoomRatio.value || 100)).toFixed(4)
@@ -4399,7 +4420,7 @@ function getOverlayStyle(el: any) {
4399
4420
  width: getOverlayWidth(el),
4400
4421
  height: getOverlayHeight(el),
4401
4422
  zIndex: isSelectedActive ? 300 : 100,
4402
- transform: el.type === 'line' ? 'none' : `rotate(${el.style.rotate || 0}deg) scale(${el.type !== 'text' && ((el.style as any)?.flipH || el.flipH || el.isFlipH) ? -1 : 1}, ${el.type !== 'text' && ((el.style as any)?.flipV || el.flipV || el.isFlipV) ? -1 : 1})`,
4423
+ transform: el.type === 'line' ? 'none' : `rotate(${el.style.rotate || 0}deg) scale(${!isTextContainingElement(el) && ((el.style as any)?.flipH || el.flipH || el.isFlipH) ? -1 : 1}, ${!isTextContainingElement(el) && ((el.style as any)?.flipV || el.flipV || el.isFlipV) ? -1 : 1})`,
4403
4424
  pointerEvents: 'none' as const,
4404
4425
  boxSizing: 'border-box' as const,
4405
4426
  '--handle-scale': +(100 / (zoomRatio.value || 100)).toFixed(4)
@@ -4495,6 +4516,7 @@ function handleTextMouseLeave(_e: MouseEvent, el: any) {
4495
4516
  }
4496
4517
 
4497
4518
  function startDrag(e: MouseEvent, el: any) {
4519
+ if (isReadonly.value) return
4498
4520
  const target = e.target as HTMLElement
4499
4521
  const container = document.getElementById(`editable-${el.id}`)
4500
4522
  const canvasElDom = document.getElementById(`canvas-el-${el.id}`)
@@ -7821,7 +7843,7 @@ async function handleGlobalPaste(e: ClipboardEvent) {
7821
7843
  </defs>
7822
7844
  </svg>
7823
7845
  <!-- Smart Guides & Corner Decorations -->
7824
- <CanvasSmartGuides :alignment-lines="alignmentLines" :active-slide-id="activeSlide?.id" />
7846
+ <CanvasSmartGuides v-if="!isReadonly" :alignment-lines="alignmentLines" :active-slide-id="activeSlide?.id" />
7825
7847
 
7826
7848
  <!-- Ctrl Drag Copy Origin Replica (原元素在原地 100% 完整原样真实呈现,杜绝空虚高亮块) -->
7827
7849
  <template v-if="ctrlDragReplicas.length > 0">
@@ -7836,7 +7858,7 @@ async function handleGlobalPaste(e: ClipboardEvent) {
7836
7858
  width: (replica.el.type === 'text' && !isMasterOrPlaceholder(replica.el) && (replica.el.autoFit?.type === 'shape' || replica.el.style?.width === 'auto') && !replica.el.content?.includes('<table') && !replica.el.latex && (replica.el.style?.writingMode === 'vertical-rl' || replica.el.style?.writingMode === 'vertical-lr')) ? 'auto' : (replica.el.style?.width || (replica.el.width ? `${replica.el.width}px` : 'auto')),
7837
7859
  height: (replica.el.type === 'text' && !isMasterOrPlaceholder(replica.el) && (replica.el.autoFit?.type === 'shape' || replica.el.style?.height === 'auto') && !replica.el.content?.includes('<table') && !replica.el.latex && replica.el.style?.writingMode !== 'vertical-rl' && replica.el.style?.writingMode !== 'vertical-lr') ? 'auto' : (replica.el.style?.height || (replica.el.height ? `${replica.el.height}px` : 'auto')),
7838
7860
  zIndex: 2,
7839
- transform: replica.el.type === 'line' ? 'none' : `rotate(${replica.el.style?.rotate || replica.el.rotate || 0}deg) scale(${replica.el.type !== 'text' && ((replica.el.style as any)?.flipH || replica.el.flipH || replica.el.isFlipH) ? -1 : 1}, ${replica.el.type !== 'text' && ((replica.el.style as any)?.flipV || replica.el.flipV || replica.el.isFlipV) ? -1 : 1})`,
7861
+ transform: replica.el.type === 'line' ? 'none' : `rotate(${replica.el.style?.rotate || replica.el.rotate || 0}deg) scale(${!isTextContainingElement(replica.el) && ((replica.el.style as any)?.flipH || replica.el.flipH || replica.el.isFlipH) ? -1 : 1}, ${!isTextContainingElement(replica.el) && ((replica.el.style as any)?.flipV || replica.el.flipV || replica.el.isFlipV) ? -1 : 1})`,
7840
7862
  opacity: (replica.el.style as any)?.opacity !== undefined ? (replica.el.style as any)?.opacity : 1,
7841
7863
  filter: getElementDropShadowFilter(replica.el),
7842
7864
  pointerEvents: 'none'
@@ -7926,7 +7948,7 @@ async function handleGlobalPaste(e: ClipboardEvent) {
7926
7948
  width: el.style?.width || (el.width !== undefined ? `${el.width}px` : 'auto'),
7927
7949
  height: el.style?.height || (el.height !== undefined ? `${el.height}px` : 'auto'),
7928
7950
  zIndex: 1,
7929
- transform: `rotate(${el.style?.rotate || el.rotate || 0}deg) scale(${el.type !== 'text' && ((el.style as any)?.flipH || el.flipH || el.isFlipH) ? -1 : 1}, ${el.type !== 'text' && ((el.style as any)?.flipV || el.flipV || el.isFlipV) ? -1 : 1})`,
7951
+ transform: `rotate(${el.style?.rotate || el.rotate || 0}deg) scale(${!isTextContainingElement(el) && ((el.style as any)?.flipH || el.flipH || el.isFlipH) ? -1 : 1}, ${!isTextContainingElement(el) && ((el.style as any)?.flipV || el.flipV || el.isFlipV) ? -1 : 1})`,
7930
7952
  opacity: el.style?.opacity !== undefined ? el.style.opacity : 1,
7931
7953
  pointerEvents: 'none'
7932
7954
  }">
@@ -8025,14 +8047,14 @@ async function handleGlobalPaste(e: ClipboardEvent) {
8025
8047
  width: (el.type === 'text' && !isMasterOrPlaceholder(el) && (el.autoFit?.type === 'shape' || el.style?.width === 'auto') && !el.content?.includes('<table') && !el.latex && (el.style?.writingMode === 'vertical-rl' || el.style?.writingMode === 'vertical-lr')) ? 'auto' : (el.style?.width || 'auto'),
8026
8048
  height: (el.type === 'text' && !isMasterOrPlaceholder(el) && (el.autoFit?.type === 'shape' || el.style?.height === 'auto') && !el.content?.includes('<table') && !el.latex && el.style?.writingMode !== 'vertical-rl' && el.style?.writingMode !== 'vertical-lr') ? 'auto' : (el.style?.height || (el.height ? `${el.height}px` : 'auto')),
8027
8049
  zIndex: 3,
8028
- cursor: (isEditingText && (selectedElementId === el.id || editingElementId === el.id) && (el.type === 'text' || el.type === 'shape') && !el.latex && !el.content?.includes('<table')) ? 'text' : undefined,
8029
- transform: el.type === 'line' ? 'none' : `rotate(${el.style?.rotate || 0}deg) scale(${el.type !== 'text' && ((el.style as any)?.flipH || el.flipH || el.isFlipH) ? -1 : 1}, ${el.type !== 'text' && ((el.style as any)?.flipV || el.flipV || el.isFlipV) ? -1 : 1})`,
8050
+ cursor: isReadonly ? 'default' : ((isEditingText && (selectedElementId === el.id || editingElementId === el.id) && (el.type === 'text' || el.type === 'shape') && !el.latex && !el.content?.includes('<table')) ? 'text' : undefined),
8051
+ transform: el.type === 'line' ? 'none' : `rotate(${el.style?.rotate || 0}deg) scale(${!isTextContainingElement(el) && ((el.style as any)?.flipH || el.flipH || el.isFlipH) ? -1 : 1}, ${!isTextContainingElement(el) && ((el.style as any)?.flipV || el.flipV || el.isFlipV) ? -1 : 1})`,
8030
8052
  opacity: (el.style as any)?.opacity !== undefined ? (el.style as any)?.opacity : 1,
8031
8053
  filter: getElementDropShadowFilter(el),
8032
8054
  pointerEvents: (isDragging || isCanvasDragging) ? 'none' : ((isDraggingLine && el.id === selectedElementId) ? 'none' : (isLockedMasterElement(el) ? 'none' : (el.type === 'group' ? 'none' : (el.type === 'line' ? 'none' : (((el.type === 'shape' || !el.type) && !hasElementFill(el) && !hasElementBorder(el) && !isImagePlaceholder(el)) ? 'none' : 'auto')))))
8033
8055
  }" @mousedown="handleElementMouseDown($event, el)" @click="handleElementClick($event, el.id)"
8034
8056
  @contextmenu.stop.prevent="handleElementContextMenu($event, el)"
8035
- @dblclick.stop="handleElementDblClick($event, el)"
8057
+ @dblclick.stop="isReadonly ? null : handleElementDblClick($event, el)"
8036
8058
  @mouseenter="!(isDragging || isCanvasDragging) && (hoveredElementId = el.id)"
8037
8059
  @mouseleave="!(isDragging || isCanvasDragging) && (hoveredElementId = (hoveredElementId === el.id ? null : hoveredElementId))">
8038
8060
 
@@ -8124,7 +8146,7 @@ async function handleGlobalPaste(e: ClipboardEvent) {
8124
8146
  @focusout="handleTextBlur($event, el)"
8125
8147
  @input="handleInput($event, el)"
8126
8148
  @keydown="handleTextKeyDown($event, el)"
8127
- @dblclick="el.latex ? handleFormulaDblClick(el) : (el.content.includes('<table') ? handleTableDblClick($event, el) : startEditingTextElement(el.id, $event))"
8149
+ @dblclick="isReadonly ? null : (el.latex ? handleFormulaDblClick(el) : (el.content.includes('<table') ? handleTableDblClick($event, el) : startEditingTextElement(el.id, $event)))"
8128
8150
  @mousemove="handleTextMouseMove($event, el)"
8129
8151
  @mouseleave="handleTextMouseLeave($event, el)"
8130
8152
  @table-dbl-click="handleTableDblClick($event, el)"
@@ -8154,7 +8176,7 @@ async function handleGlobalPaste(e: ClipboardEvent) {
8154
8176
  @text-key-down="handleTextKeyDown($event, el)"
8155
8177
  @mousemove="handleTextMouseMove($event, el)"
8156
8178
  @mouseleave="handleTextMouseLeave($event, el)"
8157
- @dblclick="startEditingShapeText(el, $event)"
8179
+ @dblclick="isReadonly ? null : startEditingShapeText(el, $event)"
8158
8180
  @start-editing="(ev) => startEditingShapeText(el, ev)"
8159
8181
  @placeholder-upload="(id) => triggerPlaceholderImageUpload(id)"
8160
8182
  />
@@ -8466,7 +8488,7 @@ async function handleGlobalPaste(e: ClipboardEvent) {
8466
8488
  </div>
8467
8489
 
8468
8490
  <!-- Global Selection Overlay (renders on top of all canvas elements to avoid masking) -->
8469
- <div v-for="selEl in selectedElements" :key="selEl.id" class="global-selection-overlay" :class="{
8491
+ <div v-for="selEl in (isReadonly ? [] : selectedElements)" :key="selEl.id" class="global-selection-overlay" :class="{
8470
8492
  'line-selection': selEl.type === 'line',
8471
8493
  'locked-selection': selEl.locked,
8472
8494
  'table-selection': selEl.type === 'text' && selEl.content && selEl.content.includes('<table'),
@@ -8636,7 +8658,7 @@ async function handleGlobalPaste(e: ClipboardEvent) {
8636
8658
  </template>
8637
8659
 
8638
8660
  <!-- Draggable border overlay to facilitate dragging and state switching (available in both editing & selected states) -->
8639
- <div v-if="selEl.type !== 'line' && !selEl.locked && croppingElementId !== selEl.id && selectedElementIds.includes(selEl.id)"
8661
+ <div v-if="!isReadonly && selEl.type !== 'line' && !selEl.locked && croppingElementId !== selEl.id && selectedElementIds.includes(selEl.id)"
8640
8662
  class="text-edit-drag-helper"
8641
8663
  style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; pointer-events: none; z-index: 200;"
8642
8664
  @contextmenu.stop.prevent="handleElementContextMenu($event, selEl)"
@@ -8697,7 +8719,7 @@ async function handleGlobalPaste(e: ClipboardEvent) {
8697
8719
 
8698
8720
  <!-- Transform, Resize, Line Endpoints & Crop Handles -->
8699
8721
  <CanvasTransformHandles
8700
- v-if="selectedElementIds.includes(selEl.id)"
8722
+ v-if="!isReadonly && selectedElementIds.includes(selEl.id)"
8701
8723
  :element="selEl"
8702
8724
  :selected-element-id="selectedElementId"
8703
8725
  :selected-element-ids="selectedElementIds"
@@ -8860,7 +8882,7 @@ async function handleGlobalPaste(e: ClipboardEvent) {
8860
8882
 
8861
8883
  <!-- Floating Quick Toolbar (No rotation, rendered as sibling of main-slide inside slide-zoom-container) -->
8862
8884
  <CanvasQuickToolbar
8863
- v-if="!isDragging && !isResizing && !isRotating && !isDraggingLine"
8885
+ v-if="!isReadonly && !isDragging && !isResizing && !isRotating && !isDraggingLine"
8864
8886
  :selected-element="selectedElement"
8865
8887
  :selected-element-ids="selectedElementIds"
8866
8888
  :is-dragging="isDragging"
@@ -8934,11 +8956,12 @@ async function handleGlobalPaste(e: ClipboardEvent) {
8934
8956
 
8935
8957
  <!-- Footer Notes -->
8936
8958
  <div class="canvas-footer">
8937
- <input class="canvas-footer-notes" v-model="activeSlide.notes" placeholder="点击输入演讲者备注" />
8959
+ <input class="canvas-footer-notes" :readonly="isReadonly" v-model="activeSlide.notes" :placeholder="isReadonly ? '演讲者备注' : '点击输入演讲者备注'" />
8938
8960
  </div>
8939
8961
 
8940
8962
  <!-- Custom Context Menu Component -->
8941
8963
  <CanvasContextMenu
8964
+ v-if="!isReadonly"
8942
8965
  v-model:visible="showContextMenu"
8943
8966
  :position="contextMenuPos"
8944
8967
  :type="contextMenuType"