Browse Source

视频 音频类型修改

黄琪勇 3 months ago
parent
commit
3970d0ae11

File diff suppressed because it is too large
+ 0 - 0
public/mjkJson/8.json


+ 6 - 4
src/hooks/useCreateElement.ts

@@ -283,7 +283,8 @@ export default () => {
    */
   const createVideoElement = (src: string) => {
     createElement({
-      type: "video",
+      type: "elf",
+      subtype: "elf-video",
       id: nanoid(10),
       width: 500,
       height: 300,
@@ -301,10 +302,11 @@ export default () => {
    */
   const createAudioElement = (src: string) => {
     createElement({
-      type: "audio",
+      type: "elf",
+      subtype: "elf-audio",
       id: nanoid(10),
-      width: 50,
-      height: 50,
+      width: 100,
+      height: 100,
       rotate: 0,
       left: (viewportSize.value - 50) / 2,
       top: (viewportSize.value * viewportRatio.value - 50) / 2,

+ 2 - 1
src/hooks/useImport.ts

@@ -14,7 +14,7 @@ export default () => {
   })
   // 导入PPTX文件
   const importPPTXFile = (files: FileList) => {
-    getHttpJson("./mjkJson/7.json").then(res => {
+    getHttpJson("./mjkJson/8.json").then(res => {
       if (res.code === 200) {
         jsonToPpt(res.data)
       } else {
@@ -46,6 +46,7 @@ export default () => {
             getHttpJson(e.extra.url).then(res => {
               if (res.code === 200) {
                 jsonToPpt(res.data)
+                exporting.value = false
               } else {
                 exporting.value = false
               }

+ 97 - 105
src/hooks/useSlideTheme.ts

@@ -1,9 +1,9 @@
-import tinycolor from 'tinycolor2'
-import { storeToRefs } from 'pinia'
-import { useSlidesStore } from '@/store'
-import type { Slide } from '@/types/slides'
-import type { PresetTheme } from '@/configs/theme'
-import useHistorySnapshot from '@/hooks/useHistorySnapshot'
+import tinycolor from "tinycolor2"
+import { storeToRefs } from "pinia"
+import { useSlidesStore } from "@/store"
+import type { Slide } from "@/types/slides"
+import type { PresetTheme } from "@/configs/theme"
+import useHistorySnapshot from "@/hooks/useHistorySnapshot"
 
 interface ThemeValueWithArea {
   area: number
@@ -27,64 +27,65 @@ export default () => {
 
     for (const slide of slides) {
       if (slide.background) {
-        if (slide.background.type === 'solid' && slide.background.color) {
+        if (slide.background.type === "solid" && slide.background.color) {
           backgroundColorValues.push({ area: 1, value: slide.background.color })
-        }
-        else if (slide.background.type === 'gradient' && slide.background.gradient) {
+        } else if (slide.background.type === "gradient" && slide.background.gradient) {
           const len = slide.background.gradient.colors.length
-          backgroundColorValues.push(...slide.background.gradient.colors.map(item => ({
-            area: 1 / len,
-            value: item.color,
-          })))
-        }
-        else backgroundColorValues.push({ area: 1, value: theme.value.backgroundColor })
+          backgroundColorValues.push(
+            ...slide.background.gradient.colors.map(item => ({
+              area: 1 / len,
+              value: item.color
+            }))
+          )
+        } else backgroundColorValues.push({ area: 1, value: theme.value.backgroundColor })
       }
       for (const el of slide.elements) {
         const elWidth = el.width
         let elHeight = 0
-        if (el.type === 'line') {
+        if (el.type === "line") {
           const [startX, startY] = el.start
           const [endX, endY] = el.end
           elHeight = Math.sqrt(Math.pow(Math.abs(startX - endX), 2) + Math.pow(Math.abs(startY - endY), 2))
-        }
-        else elHeight = el.height
-  
+        } else elHeight = el.height
+
         const area = elWidth * elHeight
-  
-        if (el.type === 'shape' || el.type === 'text') {
+
+        if (el.type === "shape" || el.type === "text") {
           if (el.fill) {
             themeColorValues.push({ area, value: el.fill })
           }
-          if (el.type === 'shape' && el.gradient) {
+          if (el.type === "shape" && el.gradient) {
             const len = el.gradient.colors.length
-            themeColorValues.push(...el.gradient.colors.map(item => ({
-              area: 1 / len * area,
-              value: item.color,
-            })))
+            themeColorValues.push(
+              ...el.gradient.colors.map(item => ({
+                area: (1 / len) * area,
+                value: item.color
+              }))
+            )
           }
 
-          const text = (el.type === 'shape' ? el.text?.content : el.content) || ''
+          const text = (el.type === "shape" ? el.text?.content : el.content) || ""
           if (!text) continue
 
-          const plainText = text.replace(/<[^>]+>/g, '').replace(/\s*/g, '')
+          const plainText = text.replace(/<[^>]+>/g, "").replace(/\s*/g, "")
           const matchForColor = text.match(/<[^>]+color: .+?<\/.+?>/g)
           const matchForFont = text.match(/<[^>]+font-family: .+?<\/.+?>/g)
-  
+
           let defaultColorPercent = 1
           let defaultFontPercent = 1
-  
+
           if (matchForColor) {
             for (const item of matchForColor) {
               const ret = item.match(/color: (.+?);/)
               if (!ret) continue
-              const text = item.replace(/<[^>]+>/g, '').replace(/\s*/g, '')
+              const text = item.replace(/<[^>]+>/g, "").replace(/\s*/g, "")
               const color = ret[1]
               const percentage = text.length / plainText.length
               defaultColorPercent = defaultColorPercent - percentage
-              
+
               fontColorValues.push({
                 area: area * percentage,
-                value: color,
+                value: color
               })
             }
           }
@@ -92,36 +93,35 @@ export default () => {
             for (const item of matchForFont) {
               const ret = item.match(/font-family: (.+?);/)
               if (!ret) continue
-              const text = item.replace(/<[^>]+>/g, '').replace(/\s*/g, '')
+              const text = item.replace(/<[^>]+>/g, "").replace(/\s*/g, "")
               const font = ret[1]
               const percentage = text.length / plainText.length
               defaultFontPercent = defaultFontPercent - percentage
-              
+
               fontNameValues.push({
                 area: area * percentage,
-                value: font,
+                value: font
               })
             }
           }
-  
+
           if (defaultColorPercent) {
-            const _defaultColor = el.type === 'shape' ? el.text?.defaultColor : el.defaultColor
+            const _defaultColor = el.type === "shape" ? el.text?.defaultColor : el.defaultColor
             const defaultColor = _defaultColor || theme.value.fontColor
             fontColorValues.push({
               area: area * defaultColorPercent,
-              value: defaultColor,
+              value: defaultColor
             })
           }
           if (defaultFontPercent) {
-            const _defaultFont = el.type === 'shape' ? el.text?.defaultFontName : el.defaultFontName
+            const _defaultFont = el.type === "shape" ? el.text?.defaultFontName : el.defaultFontName
             const defaultFont = _defaultFont || theme.value.fontName
             fontNameValues.push({
               area: area * defaultFontPercent,
-              value: defaultFont,
+              value: defaultFont
             })
           }
-        }
-        else if (el.type === 'table') {
+        } else if (el.type === "table") {
           const cellCount = el.data.length * el.data[0].length
           let cellWithFillCount = 0
           for (const row of el.data) {
@@ -131,12 +131,12 @@ export default () => {
                 themeColorValues.push({ area: area / cellCount, value: cell.style?.backcolor })
               }
               if (cell.text) {
-                const percent = (cell.text.length >= 10) ? 1 : (cell.text.length / 10)
+                const percent = cell.text.length >= 10 ? 1 : cell.text.length / 10
                 if (cell.style?.color) {
-                  fontColorValues.push({ area: area / cellCount * percent, value: cell.style?.color })
+                  fontColorValues.push({ area: (area / cellCount) * percent, value: cell.style?.color })
                 }
                 if (cell.style?.fontname) {
-                  fontColorValues.push({ area: area / cellCount * percent, value: cell.style?.fontname })
+                  fontColorValues.push({ area: (area / cellCount) * percent, value: cell.style?.fontname })
                 }
               }
             }
@@ -145,29 +145,25 @@ export default () => {
             const percent = 1 - cellWithFillCount / cellCount
             themeColorValues.push({ area: area * percent, value: el.theme.color })
           }
-        }
-        else if (el.type === 'chart') {
+        } else if (el.type === "chart") {
           if (el.fill) {
             themeColorValues.push({ area: area * 0.5, value: el.fill })
           }
           themeColorValues.push({ area: area * 0.5, value: el.themeColors[0] })
-        }
-        else if (el.type === 'line') {
+        } else if (el.type === "line") {
           themeColorValues.push({ area, value: el.color })
-        }
-        else if (el.type === 'audio') {
+        } else if (el.type === "elf" && el.subtype === "elf-audio") {
           themeColorValues.push({ area, value: el.color })
-        }
-        else if (el.type === 'latex') {
+        } else if (el.type === "latex") {
           fontColorValues.push({ area, value: el.color })
         }
       }
     }
-    
+
     const backgroundColors: { [key: string]: number } = {}
     for (const item of backgroundColorValues) {
       const color = tinycolor(item.value).toRgbString()
-      if (color === 'rgba(0, 0, 0, 0)') continue
+      if (color === "rgba(0, 0, 0, 0)") continue
       if (!backgroundColors[color]) backgroundColors[color] = item.area
       else backgroundColors[color] += item.area
     }
@@ -175,7 +171,7 @@ export default () => {
     const themeColors: { [key: string]: number } = {}
     for (const item of themeColorValues) {
       const color = tinycolor(item.value).toRgbString()
-      if (color === 'rgba(0, 0, 0, 0)') continue
+      if (color === "rgba(0, 0, 0, 0)") continue
       if (!themeColors[color]) themeColors[color] = item.area
       else themeColors[color] += item.area
     }
@@ -183,11 +179,11 @@ export default () => {
     const fontColors: { [key: string]: number } = {}
     for (const item of fontColorValues) {
       const color = tinycolor(item.value).toRgbString()
-      if (color === 'rgba(0, 0, 0, 0)') continue
+      if (color === "rgba(0, 0, 0, 0)") continue
       if (!fontColors[color]) fontColors[color] = item.area
       else fontColors[color] += item.area
     }
-  
+
     const fontNames: { [key: string]: number } = {}
     for (const item of fontNameValues) {
       if (!fontNames[item.value]) fontNames[item.value] = item.area
@@ -198,7 +194,7 @@ export default () => {
       backgroundColors: Object.keys(backgroundColors).sort((a, b) => backgroundColors[b] - backgroundColors[a]),
       themeColors: Object.keys(themeColors).sort((a, b) => themeColors[b] - themeColors[a]),
       fontColors: Object.keys(fontColors).sort((a, b) => fontColors[b] - fontColors[a]),
-      fontNames: Object.keys(fontNames).sort((a, b) => fontNames[b] - fontNames[a]),
+      fontNames: Object.keys(fontNames).sort((a, b) => fontNames[b] - fontNames[a])
     }
   }
 
@@ -206,39 +202,39 @@ export default () => {
   const getSlideAllColors = (slide: Slide) => {
     const colors: string[] = []
     for (const el of slide.elements) {
-      if (el.type === 'shape' && tinycolor(el.fill).getAlpha() !== 0) {
+      if (el.type === "shape" && tinycolor(el.fill).getAlpha() !== 0) {
         const color = tinycolor(el.fill).toRgbString()
         if (!colors.includes(color)) colors.push(color)
       }
-      if (el.type === 'text' && el.fill && tinycolor(el.fill).getAlpha() !== 0) {
+      if (el.type === "text" && el.fill && tinycolor(el.fill).getAlpha() !== 0) {
         const color = tinycolor(el.fill).toRgbString()
         if (!colors.includes(color)) colors.push(color)
       }
-      if (el.type === 'table' && el.theme && tinycolor(el.theme.color).getAlpha() !== 0) {
+      if (el.type === "table" && el.theme && tinycolor(el.theme.color).getAlpha() !== 0) {
         const color = tinycolor(el.theme.color).toRgbString()
         if (!colors.includes(color)) colors.push(color)
       }
-      if (el.type === 'chart' && el.themeColors[0] && tinycolor(el.themeColors[0]).getAlpha() !== 0) {
+      if (el.type === "chart" && el.themeColors[0] && tinycolor(el.themeColors[0]).getAlpha() !== 0) {
         const color = tinycolor(el.themeColors[0]).toRgbString()
         if (!colors.includes(color)) colors.push(color)
       }
-      if (el.type === 'line' && tinycolor(el.color).getAlpha() !== 0) {
+      if (el.type === "line" && tinycolor(el.color).getAlpha() !== 0) {
         const color = tinycolor(el.color).toRgbString()
         if (!colors.includes(color)) colors.push(color)
       }
-      if (el.type === 'audio' && tinycolor(el.color).getAlpha() !== 0) {
+      if (el.type === "elf" && el.subtype === "elf-audio" && tinycolor(el.color).getAlpha() !== 0) {
         const color = tinycolor(el.color).toRgbString()
         if (!colors.includes(color)) colors.push(color)
       }
     }
     return colors
   }
-  
+
   // 创建原颜色与新颜色的对应关系表
   const createSlideThemeColorMap = (slide: Slide, newColors: string[]): { [key: string]: string } => {
     const oldColors = getSlideAllColors(slide)
     const themeColorMap: { [key: string]: string } = {}
-  
+
     if (oldColors.length > newColors.length) {
       const analogous = tinycolor(newColors[0]).analogous(oldColors.length - newColors.length + 10)
       const otherColors = analogous.map(item => item.toHexString()).slice(1)
@@ -247,31 +243,31 @@ export default () => {
     for (let i = 0; i < oldColors.length; i++) {
       themeColorMap[oldColors[i]] = newColors[i]
     }
-  
+
     return themeColorMap
   }
-  
+
   // 设置幻灯片主题
   const setSlideTheme = (slide: Slide, theme: PresetTheme) => {
     const colorMap = createSlideThemeColorMap(slide, theme.colors)
-  
-    if (!slide.background || slide.background.type !== 'image') {
+
+    if (!slide.background || slide.background.type !== "image") {
       slide.background = {
-        type: 'solid',
-        color: theme.background,
+        type: "solid",
+        color: theme.background
       }
     }
     for (const el of slide.elements) {
-      if (el.type === 'shape') {
+      if (el.type === "shape") {
         el.fill = colorMap[tinycolor(el.fill).toRgbString()] || el.fill
         if (el.gradient) delete el.gradient
       }
-      if (el.type === 'text') {
+      if (el.type === "text") {
         if (el.fill) el.fill = colorMap[tinycolor(el.fill).toRgbString()] || el.fill
         el.defaultColor = theme.fontColor
         el.defaultFontName = theme.fontname
       }
-      if (el.type === 'table') {
+      if (el.type === "table") {
         if (el.theme) el.theme.color = colorMap[tinycolor(el.theme.color).toRgbString()] || el.theme.color
         for (const rowCells of el.data) {
           for (const cell of rowCells) {
@@ -282,27 +278,27 @@ export default () => {
           }
         }
       }
-      if (el.type === 'chart') {
+      if (el.type === "chart") {
         el.themeColors = [colorMap[tinycolor(el.themeColors[0]).toRgbString()]] || el.themeColors
         el.textColor = theme.fontColor
       }
-      if (el.type === 'line') el.color = colorMap[tinycolor(el.color).toRgbString()] || el.color
-      if (el.type === 'audio') el.color = colorMap[tinycolor(el.color).toRgbString()] || el.color
-      if (el.type === 'latex') el.color = theme.fontColor
+      if (el.type === "line") el.color = colorMap[tinycolor(el.color).toRgbString()] || el.color
+      if (el.type === "elf" && el.subtype === "elf-audio") el.color = colorMap[tinycolor(el.color).toRgbString()] || el.color
+      if (el.type === "latex") el.color = theme.fontColor
     }
   }
-  
+
   // 应用预置主题(单页)
   const applyPresetThemeToSingleSlide = (theme: PresetTheme) => {
     const newSlide: Slide = JSON.parse(JSON.stringify(currentSlide.value))
     setSlideTheme(newSlide, theme)
     slidesStore.updateSlide({
       background: newSlide.background,
-      elements: newSlide.elements,
+      elements: newSlide.elements
     })
     addHistorySnapshot()
   }
-  
+
   // 应用预置主题(全部)
   const applyPresetThemeToAllSlides = (theme: PresetTheme) => {
     const newSlides: Slide[] = JSON.parse(JSON.stringify(slides.value))
@@ -313,42 +309,40 @@ export default () => {
       backgroundColor: theme.background,
       themeColor: theme.colors[0],
       fontColor: theme.fontColor,
-      fontName: theme.fontname,
+      fontName: theme.fontname
     })
     slidesStore.setSlides(newSlides)
     addHistorySnapshot()
   }
-  
+
   // 将当前主题配置应用到全部页面
   const applyThemeToAllSlides = (applyAll = false) => {
     const newSlides: Slide[] = JSON.parse(JSON.stringify(slides.value))
     const { themeColor, backgroundColor, fontColor, fontName, outline, shadow } = theme.value
-  
+
     for (const slide of newSlides) {
-      if (!slide.background || slide.background.type !== 'image') {
+      if (!slide.background || slide.background.type !== "image") {
         slide.background = {
-          type: 'solid',
+          type: "solid",
           color: backgroundColor
         }
       }
-  
+
       for (const el of slide.elements) {
         if (applyAll) {
-          if ('outline' in el && el.outline) el.outline = outline
-          if ('shadow' in el && el.shadow) el.shadow = shadow
+          if ("outline" in el && el.outline) el.outline = outline
+          if ("shadow" in el && el.shadow) el.shadow = shadow
         }
 
-        if (el.type === 'shape') {
+        if (el.type === "shape") {
           el.fill = themeColor
           if (el.gradient) delete el.gradient
-        }
-        else if (el.type === 'line') el.color = themeColor
-        else if (el.type === 'text') {
+        } else if (el.type === "line") el.color = themeColor
+        else if (el.type === "text") {
           el.defaultColor = fontColor
           el.defaultFontName = fontName
           if (el.fill) el.fill = themeColor
-        }
-        else if (el.type === 'table') {
+        } else if (el.type === "table") {
           if (el.theme) el.theme.color = themeColor
           for (const rowCells of el.data) {
             for (const cell of rowCells) {
@@ -358,13 +352,11 @@ export default () => {
               }
             }
           }
-        }
-        else if (el.type === 'chart') {
+        } else if (el.type === "chart") {
           el.themeColors = [themeColor]
           el.textColor = fontColor
-        }
-        else if (el.type === 'latex') el.color = fontColor
-        else if (el.type === 'audio') el.color = themeColor
+        } else if (el.type === "latex") el.color = fontColor
+        else if (el.type === "elf" && el.subtype === "elf-audio") el.color = themeColor
       }
     }
     slidesStore.setSlides(newSlides)
@@ -375,6 +367,6 @@ export default () => {
     getSlidesThemeStyles,
     applyPresetThemeToSingleSlide,
     applyPresetThemeToAllSlides,
-    applyThemeToAllSlides,
+    applyThemeToAllSlides
   }
-}
+}

+ 16 - 6
src/types/slides.ts

@@ -28,11 +28,15 @@ export const enum ElementTypes {
   CHART = "chart",
   TABLE = "table",
   LATEX = "latex",
-  VIDEO = "video",
-  AUDIO = "audio",
+  ELF = "elf",
   CLOUDCOACH = "cloudCoach"
 }
 
+export const enum ElementSubtypeTypes {
+  VIDEO = "elf-video",
+  AUDIO = "elf-audio"
+}
+
 /**
  * 渐变
  *
@@ -562,7 +566,9 @@ export interface PPTLatexElement extends PPTBaseElement {
 /**
  * 视频元素
  *
- * type: 元素类型(video)
+ * type: elf
+ *
+ * subtype: elf-video
  *
  * src: 视频地址
  *
@@ -573,7 +579,8 @@ export interface PPTLatexElement extends PPTBaseElement {
  * ext: 视频后缀,当资源链接缺少后缀时用该字段确认资源类型
  */
 export interface PPTVideoElement extends PPTBaseElement {
-  type: "video"
+  type: "elf"
+  subtype: "elf-video"
   src: string
   autoplay: boolean
   poster?: string
@@ -583,7 +590,9 @@ export interface PPTVideoElement extends PPTBaseElement {
 /**
  * 音频元素
  *
- * type: 元素类型(audio)
+ * type: elf
+ *
+ * subtype: elf-audio
  *
  * fixedRatio: 固定图标宽高比例
  *
@@ -598,7 +607,8 @@ export interface PPTVideoElement extends PPTBaseElement {
  * ext: 音频后缀,当资源链接缺少后缀时用该字段确认资源类型
  */
 export interface PPTAudioElement extends PPTBaseElement {
-  type: "audio"
+  type: "elf"
+  subtype: "elf-audio"
   fixedRatio: boolean
   color: string
   loop: boolean

+ 8 - 5
src/views/Editor/Canvas/EditableElement.vue

@@ -13,7 +13,7 @@
 
 <script lang="ts" setup>
 import { computed } from "vue"
-import { ElementTypes, type PPTElement } from "@/types/slides"
+import { ElementTypes, ElementSubtypeTypes, type PPTElement } from "@/types/slides"
 import type { ContextmenuItem } from "@/components/Contextmenu/types"
 
 import useLockElement from "@/hooks/useLockElement"
@@ -54,11 +54,14 @@ const currentElementComponent = computed<unknown>(() => {
     [ElementTypes.CHART]: ChartElement,
     [ElementTypes.TABLE]: TableElement,
     [ElementTypes.LATEX]: LatexElement,
-    [ElementTypes.VIDEO]: VideoElement,
-    [ElementTypes.AUDIO]: AudioElement,
-    [ElementTypes.CLOUDCOACH]: cloudCoachElement
+    [ElementTypes.CLOUDCOACH]: cloudCoachElement,
+    [ElementTypes.ELF]: null
   }
-  return elementTypeMap[props.elementInfo.type] || null
+  const elementSubtypeMap = {
+    [ElementSubtypeTypes.AUDIO]: AudioElement,
+    [ElementSubtypeTypes.VIDEO]: VideoElement
+  }
+  return elementTypeMap[props.elementInfo.type] || elementSubtypeMap[props.elementInfo.subtype] || null
 })
 
 const { orderElement } = useOrderElement()

+ 15 - 21
src/views/Editor/Canvas/Operate/CommonElementOperate.vue

@@ -1,15 +1,9 @@
 <template>
   <div class="common-element-operate">
-    <BorderLine 
-      class="operate-border-line"
-      v-for="line in borderLines" 
-      :key="line.type" 
-      :type="line.type" 
-      :style="line.style"
-    />
+    <BorderLine class="operate-border-line" v-for="line in borderLines" :key="line.type" :type="line.type" :style="line.style" />
     <template v-if="handlerVisible">
       <ResizeHandler
-        class="operate-resize-handler" 
+        class="operate-resize-handler"
         v-for="point in resizeHandlers"
         :key="point.direction"
         :type="point.direction"
@@ -18,7 +12,7 @@
         @mousedown.stop="$event => scaleElement($event, elementInfo, point.direction)"
       />
       <RotateHandler
-        class="operate-rotate-handler" 
+        class="operate-rotate-handler"
         v-if="!cannotRotate"
         :style="{ left: scaleWidth / 2 + 'px' }"
         @mousedown.stop="$event => rotateElement($event, elementInfo)"
@@ -29,21 +23,21 @@
 
 <script lang="ts">
 export default {
-  inheritAttrs: false,
+  inheritAttrs: false
 }
 </script>
 
 <script lang="ts" setup>
-import { computed } from 'vue'
-import { storeToRefs } from 'pinia'
-import { useMainStore } from '@/store'
-import type { PPTVideoElement, PPTLatexElement, PPTAudioElement, PPTChartElement } from '@/types/slides'
-import type { OperateResizeHandlers } from '@/types/edit'
-import useCommonOperate from '../hooks/useCommonOperate'
+import { computed } from "vue"
+import { storeToRefs } from "pinia"
+import { useMainStore } from "@/store"
+import type { PPTVideoElement, PPTLatexElement, PPTAudioElement, PPTChartElement } from "@/types/slides"
+import type { OperateResizeHandlers } from "@/types/edit"
+import useCommonOperate from "../hooks/useCommonOperate"
 
-import RotateHandler from './RotateHandler.vue'
-import ResizeHandler from './ResizeHandler.vue'
-import BorderLine from './BorderLine.vue'
+import RotateHandler from "./RotateHandler.vue"
+import ResizeHandler from "./ResizeHandler.vue"
+import BorderLine from "./BorderLine.vue"
 
 type PPTElement = PPTVideoElement | PPTLatexElement | PPTAudioElement | PPTChartElement
 
@@ -60,5 +54,5 @@ const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
 const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
 const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
 
-const cannotRotate = computed(() => ['chart', 'video', 'audio'].includes(props.elementInfo.type))
-</script>
+const cannotRotate = computed(() => ["chart", "elf"].includes(props.elementInfo.type))
+</script>

+ 2 - 3
src/views/Editor/Canvas/Operate/index.vue

@@ -82,9 +82,8 @@ const currentOperateComponent = computed<unknown>(() => {
     [ElementTypes.TABLE]: TableElementOperate,
     [ElementTypes.CHART]: CommonElementOperate,
     [ElementTypes.LATEX]: CommonElementOperate,
-    [ElementTypes.VIDEO]: CommonElementOperate,
-    [ElementTypes.AUDIO]: CommonElementOperate,
-    [ElementTypes.CLOUDCOACH]: CommonElementOperate
+    [ElementTypes.CLOUDCOACH]: CommonElementOperate,
+    [ElementTypes.ELF]: CommonElementOperate
   }
   return elementTypeMap[props.elementInfo.type] || null
 })

+ 11 - 11
src/views/Editor/Toolbar/ElementPositionPanel.vue

@@ -11,7 +11,7 @@
     </ButtonGroup>
 
     <Divider />
-    
+
     <div class="title">对齐:</div>
     <ButtonGroup class="row">
       <Button style="flex: 1;" v-tooltip="'左对齐'" @click="alignElementToCanvas(ElementAlignCommands.LEFT)"><IconAlignLeft /></Button>
@@ -65,17 +65,17 @@
             宽度:
           </template>
         </NumberInput>
-        <template v-if="['image', 'shape', 'audio'].includes(handleElement!.type)">
+        <template v-if="['image', 'shape'].includes(handleElement!.type) || ['elf-audio'].includes(handleElement!.subtype)">
           <IconLock style="width: 10%;" class="icon-btn active" v-tooltip="'解除宽高比锁定'" @click="updateFixedRatio(false)" v-if="fixedRatio" />
           <IconUnlock style="width: 10%;" class="icon-btn" v-tooltip="'宽高比锁定'" @click="updateFixedRatio(true)" v-else />
         </template>
         <div style="width: 10%;" v-else></div>
-        <NumberInput 
+        <NumberInput
           :min="minSize"
           :max="800"
           :step="5"
-          :disabled="isHorizontalText || handleElement!.type === 'table'" 
-          :value="height" 
+          :disabled="isHorizontalText || handleElement!.type === 'table'"
+          :value="height"
           @update:value="value => updateHeight(value)"
           style="width: 45%;"
         >
@@ -86,17 +86,17 @@
       </div>
     </template>
 
-    <template v-if="!['line', 'video', 'audio'].includes(handleElement!.type)">
+    <template v-if="!['line', 'elf'].includes(handleElement!.type)">
       <Divider />
 
       <div class="row">
-        <NumberInput 
+        <NumberInput
           :min="-180"
           :max="180"
           :step="5"
-          :value="rotate" 
-          @update:value="value => updateRotate(value)" 
-          style="width: 45%;" 
+          :value="rotate"
+          @update:value="value => updateRotate(value)"
+          style="width: 45%;"
         >
           <template #prefix>
             旋转:
@@ -311,4 +311,4 @@ const updateRotate45 = (command: '+' | '-') => {
     border-radius: $borderRadius;
   }
 }
-</style>
+</style>

+ 1 - 1
src/views/Editor/Toolbar/ElementStylePanel/MultiStylePanel.vue

@@ -198,7 +198,7 @@ const updateFill = (value: string) => {
       updateElement(el.id, { data })
     }
 
-    if (el.type === 'audio') updateElement(el.id, { color: value })
+    if (el.type === "elf" && el.subtype === "elf-audio") updateElement(el.id, { color: value })
   }
   fill.value = value
 }

+ 9 - 7
src/views/Editor/Toolbar/ElementStylePanel/index.vue

@@ -8,7 +8,7 @@
 import { computed } from "vue"
 import { storeToRefs } from "pinia"
 import { useMainStore } from "@/store"
-import { ElementTypes } from "@/types/slides"
+import { ElementTypes, ElementSubtypeTypes } from "@/types/slides"
 
 import TextStylePanel from "./TextStylePanel.vue"
 import ImageStylePanel from "./ImageStylePanel.vue"
@@ -30,11 +30,13 @@ const panelMap = {
   [ElementTypes.CHART]: ChartStylePanel,
   [ElementTypes.TABLE]: TableStylePanel,
   [ElementTypes.LATEX]: LatexStylePanel,
-  [ElementTypes.VIDEO]: VideoStylePanel,
-  [ElementTypes.AUDIO]: AudioStylePanel,
-  [ElementTypes.CLOUDCOACH]: CloudCoachStylePanel
+  [ElementTypes.CLOUDCOACH]: CloudCoachStylePanel,
+  [ElementTypes.ELF]: null
+}
+const elementSubtypeMap = {
+  [ElementSubtypeTypes.AUDIO]: AudioStylePanel,
+  [ElementSubtypeTypes.VIDEO]: VideoStylePanel
 }
-
 const { activeElementIdList, activeElementList, handleElement, activeGroupElementId } = storeToRefs(useMainStore())
 
 const currentPanelComponent = computed<unknown>(() => {
@@ -42,9 +44,9 @@ const currentPanelComponent = computed<unknown>(() => {
     if (!activeGroupElementId.value) return MultiStylePanel
 
     const activeGroupElement = activeElementList.value.find(item => item.id === activeGroupElementId.value)
-    return activeGroupElement ? panelMap[activeGroupElement.type] || null : null
+    return activeGroupElement ? panelMap[activeGroupElement.type] || elementSubtypeMap[activeGroupElement.subtype] || null : null
   }
 
-  return handleElement.value ? panelMap[handleElement.value.type] || null : null
+  return handleElement.value ? panelMap[handleElement.value.type] || elementSubtypeMap[handleElement.value.subtype] || null : null
 })
 </script>

+ 24 - 24
src/views/Mobile/MobileEditor/ElementToolbar.vue

@@ -1,34 +1,34 @@
 <template>
   <div class="element-toolbar">
-    <Tabs 
-      :tabs="tabs" 
-      v-model:value="activeTab" 
-      :tabsStyle="{ marginBottom: '8px' }" 
+    <Tabs
+      :tabs="tabs"
+      v-model:value="activeTab"
+      :tabsStyle="{ marginBottom: '8px' }"
       :tabStyle="{
         width: '30%',
         margin: '0 10%',
-      }" 
+      }"
     />
 
     <div class="content">
       <div class="style" v-if="activeTab === 'style'">
         <ButtonGroup class="row">
-          <CheckboxButton 
+          <CheckboxButton
             style="flex: 1;"
             :checked="richTextAttrs.bold"
             @click="emitRichTextCommand('bold')"
           ><IconTextBold /></CheckboxButton>
-          <CheckboxButton 
+          <CheckboxButton
             style="flex: 1;"
             :checked="richTextAttrs.em"
             @click="emitRichTextCommand('em')"
           ><IconTextItalic /></CheckboxButton>
-          <CheckboxButton 
+          <CheckboxButton
             style="flex: 1;"
             :checked="richTextAttrs.underline"
             @click="emitRichTextCommand('underline')"
           ><IconTextUnderline /></CheckboxButton>
-          <CheckboxButton 
+          <CheckboxButton
             style="flex: 1;"
             :checked="richTextAttrs.strikethrough"
             @click="emitRichTextCommand('strikethrough')"
@@ -36,21 +36,21 @@
         </ButtonGroup>
 
         <ButtonGroup class="row">
-          <Button 
+          <Button
             style="flex: 1;"
             @click="emitRichTextCommand('fontsize-add')"
           ><IconFontSize />+</Button>
-          <Button 
+          <Button
             style="flex: 1;"
             @click="emitRichTextCommand('fontsize-reduce')"
           ><IconFontSize />-</Button>
         </ButtonGroup>
-        
+
         <Divider :margin="20" />
 
-        <RadioGroup 
-          class="row" 
-          button-style="solid" 
+        <RadioGroup
+          class="row"
+          button-style="solid"
           :value="richTextAttrs.align"
           @update:value="value => emitRichTextCommand('align', value)"
         >
@@ -58,14 +58,14 @@
           <RadioButton value="center" style="flex: 1;"><IconAlignTextCenter /></RadioButton>
           <RadioButton value="right" style="flex: 1;"><IconAlignTextRight /></RadioButton>
         </RadioGroup>
-        
+
         <Divider :margin="20" />
 
         <div class="row-block">
           <div class="label">文字颜色:</div>
           <div class="colors">
-            <div class="color" 
-              v-for="color in colors" 
+            <div class="color"
+              v-for="color in colors"
               :key="color"
               @click="updateFontColor(color)"
             >
@@ -76,8 +76,8 @@
         <div class="row-block">
           <div class="label">填充色:</div>
           <div class="colors">
-            <div class="color" 
-              v-for="color in colors" 
+            <div class="color"
+              v-for="color in colors"
               :key="color"
               @click="updateFill(color)"
             >
@@ -92,7 +92,7 @@
           <Button style="flex: 1;" @click="copyElement()"><IconCopy class="icon" /> 复制</Button>
           <Button style="flex: 1;" @click="deleteElement()"><IconDelete class="icon" /> 删除</Button>
         </ButtonGroup>
-        
+
         <Divider :margin="20" />
 
         <ButtonGroup class="row">
@@ -101,7 +101,7 @@
           <Button style="flex: 1;" @click="orderElement(handleElement!, ElementOrderCommands.UP)"><IconBringToFront class="icon" /> 上移</Button>
           <Button style="flex: 1;" @click="orderElement(handleElement!, ElementOrderCommands.DOWN)"><IconSentToBack class="icon" /> 下移</Button>
         </ButtonGroup>
-        
+
         <Divider :margin="20" />
 
         <ButtonGroup class="row">
@@ -217,7 +217,7 @@ const updateFill = (color: string) => {
     updateElement(handleElementId.value, { data })
   }
 
-  if (handleElement.value.type === 'audio') updateElement(handleElementId.value, { color })
+  if (handleElement.value.type === "elf" && handleElement.value.subtype === "elf-audio") updateElement(handleElementId.value, { color })
 }
 </script>
 
@@ -288,4 +288,4 @@ const updateFill = (color: string) => {
     border-radius: 50%;
   }
 }
-</style>
+</style>

+ 23 - 23
src/views/Mobile/MobileEditor/MobileEditableElement.vue

@@ -1,32 +1,28 @@
 <template>
-  <div 
+  <div
     class="mobile-editable-element"
     :style="{
-      zIndex: elementIndex,
+      zIndex: elementIndex
     }"
   >
-    <component
-      :is="currentElementComponent"
-      :elementInfo="elementInfo"
-      :selectElement="selectElement"
-      :contextmenus="() => null"
-    ></component>
+    <component :is="currentElementComponent" :elementInfo="elementInfo" :selectElement="selectElement" :contextmenus="() => null"></component>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue'
-import { ElementTypes, type PPTElement } from '@/types/slides'
+import { computed } from "vue"
+import { ElementTypes, ElementSubtypeTypes, type PPTElement } from "@/types/slides"
 
-import ImageElement from '@/views/components/element/ImageElement/index.vue'
-import TextElement from '@/views/components/element/TextElement/index.vue'
-import ShapeElement from '@/views/components/element/ShapeElement/index.vue'
-import LineElement from '@/views/components/element/LineElement/index.vue'
-import ChartElement from '@/views/components/element/ChartElement/index.vue'
-import TableElement from '@/views/components/element/TableElement/index.vue'
-import LatexElement from '@/views/components/element/LatexElement/index.vue'
-import VideoElement from '@/views/components/element/VideoElement/index.vue'
-import AudioElement from '@/views/components/element/AudioElement/index.vue'
+import ImageElement from "@/views/components/element/ImageElement/index.vue"
+import TextElement from "@/views/components/element/TextElement/index.vue"
+import ShapeElement from "@/views/components/element/ShapeElement/index.vue"
+import LineElement from "@/views/components/element/LineElement/index.vue"
+import ChartElement from "@/views/components/element/ChartElement/index.vue"
+import TableElement from "@/views/components/element/TableElement/index.vue"
+import LatexElement from "@/views/components/element/LatexElement/index.vue"
+import VideoElement from "@/views/components/element/VideoElement/index.vue"
+import AudioElement from "@/views/components/element/AudioElement/index.vue"
+import cloudCoachElement from "@/views/components/element/cloudCoachElement"
 
 const props = defineProps<{
   elementInfo: PPTElement
@@ -43,9 +39,13 @@ const currentElementComponent = computed<unknown>(() => {
     [ElementTypes.CHART]: ChartElement,
     [ElementTypes.TABLE]: TableElement,
     [ElementTypes.LATEX]: LatexElement,
-    [ElementTypes.VIDEO]: VideoElement,
-    [ElementTypes.AUDIO]: AudioElement,
+    [ElementTypes.CLOUDCOACH]: cloudCoachElement,
+    [ElementTypes.ELF]: null
   }
-  return elementTypeMap[props.elementInfo.type] || null
+  const elementSubtypeMap = {
+    [ElementSubtypeTypes.AUDIO]: AudioElement,
+    [ElementSubtypeTypes.VIDEO]: VideoElement
+  }
+  return elementTypeMap[props.elementInfo.type] || elementSubtypeMap[props.elementInfo.subtype] || null
 })
-</script>
+</script>

+ 8 - 8
src/views/Mobile/MobileEditor/MobileOperate.vue

@@ -9,15 +9,15 @@
     }"
   >
     <template v-if="isSelected">
-      <BorderLine 
+      <BorderLine
         class="operate-border-line"
-        v-for="line in borderLines" 
-        :key="line.type" 
-        :type="line.type" 
+        v-for="line in borderLines"
+        :key="line.type"
+        :type="line.type"
         :style="line.style"
       />
       <ResizeHandler
-        class="operate-resize-handler" 
+        class="operate-resize-handler"
         v-for="point in resizeHandlers"
         :key="point.direction"
         :type="point.direction"
@@ -26,7 +26,7 @@
         @touchstart.stop="$event => scaleElement($event, elementInfo, point.direction)"
       />
       <RotateHandler
-        class="operate-rotate-handler" 
+        class="operate-rotate-handler"
         :style="{ left: scaleWidth / 2 + 'px' }"
         v-if="!cannotRotate"
         @touchstart.stop="$event => rotateElement($event, elementInfo as CanRotatePPTElement)"
@@ -67,7 +67,7 @@ const {
 
 const resizeHandlers = props.elementInfo.type === 'text' || props.elementInfo.type === 'table' ? textElementResizeHandlers : _resizeHandlers
 
-const cannotRotate = computed(() => ['chart', 'video', 'audio'].includes(props.elementInfo.type))
+const cannotRotate = computed(() => ['chart', 'elf'].includes(props.elementInfo.type))
 </script>
 
 <style lang="scss" scoped>
@@ -76,4 +76,4 @@ const cannotRotate = computed(() => ['chart', 'video', 'audio'].includes(props.e
   z-index: 100;
   user-select: none;
 }
-</style>
+</style>

+ 8 - 5
src/views/Screen/ScreenElement.vue

@@ -21,7 +21,7 @@
 import { computed } from "vue"
 import { storeToRefs } from "pinia"
 import { useSlidesStore } from "@/store"
-import { ElementTypes, type PPTElement } from "@/types/slides"
+import { ElementTypes, ElementSubtypeTypes, type PPTElement } from "@/types/slides"
 
 import BaseImageElement from "@/views/components/element/ImageElement/BaseImageElement.vue"
 import BaseTextElement from "@/views/components/element/TextElement/BaseTextElement.vue"
@@ -51,11 +51,14 @@ const currentElementComponent = computed<unknown>(() => {
     [ElementTypes.CHART]: BaseChartElement,
     [ElementTypes.TABLE]: BaseTableElement,
     [ElementTypes.LATEX]: BaseLatexElement,
-    [ElementTypes.VIDEO]: ScreenVideoElement,
-    [ElementTypes.AUDIO]: ScreenAudioElement,
-    [ElementTypes.CLOUDCOACH]: ScreenCloudCoachElement
+    [ElementTypes.CLOUDCOACH]: ScreenCloudCoachElement,
+    [ElementTypes.ELF]: null
   }
-  return elementTypeMap[props.elementInfo.type] || null
+  const elementSubtypeMap = {
+    [ElementSubtypeTypes.AUDIO]: ScreenAudioElement,
+    [ElementSubtypeTypes.VIDEO]: ScreenVideoElement
+  }
+  return elementTypeMap[props.elementInfo.type] || elementSubtypeMap[props.elementInfo.subtype] || null
 })
 
 const { formatedAnimations, theme } = storeToRefs(useSlidesStore())

+ 8 - 5
src/views/components/ThumbnailSlide/ThumbnailElement.vue

@@ -12,7 +12,7 @@
 
 <script lang="ts" setup>
 import { computed } from "vue"
-import { ElementTypes, type PPTElement } from "@/types/slides"
+import { ElementTypes, ElementSubtypeTypes, type PPTElement } from "@/types/slides"
 
 import BaseImageElement from "@/views/components/element/ImageElement/BaseImageElement.vue"
 import BaseTextElement from "@/views/components/element/TextElement/BaseTextElement.vue"
@@ -39,10 +39,13 @@ const currentElementComponent = computed<unknown>(() => {
     [ElementTypes.CHART]: BaseChartElement,
     [ElementTypes.TABLE]: BaseTableElement,
     [ElementTypes.LATEX]: BaseLatexElement,
-    [ElementTypes.VIDEO]: BaseVideoElement,
-    [ElementTypes.AUDIO]: BaseAudioElement,
-    [ElementTypes.CLOUDCOACH]: BaseCloudCoachElement
+    [ElementTypes.CLOUDCOACH]: BaseCloudCoachElement,
+    [ElementTypes.ELF]: null
   }
-  return elementTypeMap[props.elementInfo.type] || null
+  const elementSubtypeMap = {
+    [ElementSubtypeTypes.AUDIO]: BaseAudioElement,
+    [ElementSubtypeTypes.VIDEO]: BaseVideoElement
+  }
+  return elementTypeMap[props.elementInfo.type] || elementSubtypeMap[props.elementInfo.subtype] || null
 })
 </script>

Some files were not shown because too many files changed in this diff