lsc 1 nedēļu atpakaļ
vecāks
revīzija
8cecf6b858

+ 54 - 1
src/App.vue

@@ -1,6 +1,7 @@
 <template>
   <template v-if="slides.length">
     <Screen v-if="screening" />
+    <Student v-else-if="viewMode === 'student'" />
     <Editor v-else-if="_isPC" />
     <Mobile v-else />
   </template>
@@ -10,7 +11,7 @@
 
 
 <script lang="ts" setup>
-import { onMounted } from 'vue'
+import { onMounted, ref, provide } from 'vue'
 import { storeToRefs } from 'pinia'
 import { useScreenStore, useMainStore, useSnapshotStore, useSlidesStore } from '@/store'
 import { LOCALSTORAGE_KEY_DISCARDED_DB } from '@/configs/storage'
@@ -21,6 +22,7 @@ import api from '@/services'
 import Editor from './views/Editor/index.vue'
 import Screen from './views/Screen/index.vue'
 import Mobile from './views/Mobile/index.vue'
+import Student from './views/Student/index.vue'
 import FullscreenSpin from '@/components/FullscreenSpin.vue'
 
 const _isPC = isPC()
@@ -32,6 +34,50 @@ const { databaseId } = storeToRefs(mainStore)
 const { slides } = storeToRefs(slidesStore)
 const { screening } = storeToRefs(useScreenStore())
 
+// 视图模式:'editor', 'student', 'screen'
+// 支持通过URL参数直接访问学生模式
+const getInitialViewMode = () => {
+  // 检查URL参数
+  const urlParams = new URLSearchParams(window.location.search)
+  const modeFromUrl = urlParams.get('mode')
+  
+  if (modeFromUrl === 'student') {
+    return 'student'
+  }
+  
+  // 检查localStorage
+  const modeFromStorage = localStorage.getItem('viewMode')
+  if (modeFromStorage) {
+    return modeFromStorage
+  }
+  
+  // 默认返回编辑模式
+  return 'editor'
+}
+
+const viewMode = ref(getInitialViewMode())
+
+// 全局切换视图模式的函数
+const switchViewMode = (mode: string) => {
+  viewMode.value = mode
+  localStorage.setItem('viewMode', mode)
+  
+  // 更新URL参数
+  const url = new URL(window.location.href)
+  if (mode === 'student') {
+    url.searchParams.set('mode', 'student')
+  }
+  else {
+    url.searchParams.delete('mode')
+  }
+  
+  // 使用 history.pushState 更新URL,不刷新页面
+  window.history.pushState({}, '', url.toString())
+}
+
+// 使用provide提供切换函数,供子组件调用
+provide('switchViewMode', switchViewMode)
+
 if (import.meta.env.MODE !== 'development') {
   window.onbeforeunload = () => false
 }
@@ -42,6 +88,13 @@ onMounted(async () => {
 
   await deleteDiscardedDB()
   snapshotStore.initSnapshotDatabase()
+  
+  // 监听视图模式切换事件
+  window.addEventListener('viewModeChanged', (event: any) => {
+    if (event.detail) {
+      switchViewMode(event.detail)
+    }
+  })
 })
 
 // 应用注销时向 localStorage 中记录下本次 indexedDB 的数据库ID,用于之后清除数据库

+ 10 - 0
src/hooks/useCreateElement.ts

@@ -2,6 +2,7 @@ import { storeToRefs } from 'pinia'
 import { nanoid } from 'nanoid'
 import { useMainStore, useSlidesStore } from '@/store'
 import { getImageSize } from '@/utils/image'
+import message from '@/utils/message'
 import type { PPTLineElement, PPTElement, TableCell, TableCellStyle, PPTShapeElement, ChartType } from '@/types/slides'
 import { type ShapePoolItem, SHAPE_PATH_FORMULAS } from '@/configs/shapes'
 import type { LinePoolItem } from '@/configs/lines'
@@ -316,6 +317,15 @@ export default () => {
    * @param url 网页链接地址
    */
   const createFrameElement = (url: string) => {
+    // 检查当前幻灯片是否已经包含网页元素
+    const { currentSlide } = storeToRefs(useSlidesStore())
+    const hasWebpage = currentSlide.value?.elements?.some(element => element.type === 'frame')
+    
+    if (hasWebpage) {
+      message.error('当前幻灯片已包含网页元素,一个幻灯片只能插入一个网页')
+      return
+    }
+    
     console.log(viewportSize)
     const width = viewportSize.value
     const height = width * viewportRatio.value

+ 88 - 6
src/hooks/useImport.ts

@@ -1,4 +1,4 @@
-import { ref } from 'vue'
+import { ref, nextTick } from 'vue'
 import { storeToRefs } from 'pinia'
 import { parse, type Shape, type Element, type ChartItem, type BaseElement } from 'pptxtojson'
 import { nanoid } from 'nanoid'
@@ -70,31 +70,113 @@ export default () => {
   // 直接读取JSON功能,暴露到window.readJSON
   const readJSON = (jsonData: string | any, cover = false) => {
     try {
-      let slides
+      console.log('readJSON 开始执行:', { jsonData, cover })
+      
+      let parsedData
       if (typeof jsonData === 'string') {
-        const parsed = JSON.parse(jsonData)
-        slides = parsed.slides || parsed
+        parsedData = JSON.parse(jsonData)
+        console.log('解析字符串后的数据:', parsedData)
       }
       else {
-        slides = jsonData.slides || jsonData
+        parsedData = jsonData
       }
       
+      // 提取所有可能的数据
+      const slides = parsedData.slides || parsedData
+      const title = parsedData.title
+      const theme = parsedData.theme
+      const width = parsedData.width
+      const height = parsedData.height
+      const viewportRatio = parsedData.viewportRatio || (height && width ? height / width : undefined)
+      
+      console.log('提取的数据:', { slides: slides.length, title, theme, width, height, viewportRatio })
+      
+      // 更新幻灯片数据
       if (cover) {
+        console.log('覆盖模式:更新幻灯片数据')
         slidesStore.updateSlideIndex(0)
         slidesStore.setSlides(slides)
         addHistorySnapshot()
       }
       else if (isEmptySlide.value) {
+        console.log('空幻灯片模式:更新幻灯片数据')
         slidesStore.setSlides(slides)
         addHistorySnapshot()
       }
       else {
+        console.log('添加模式:添加幻灯片数据')
         addSlidesFromData(slides)
       }
       
-      return { success: true, slides }
+      // 同步更新其他相关内容
+      if (title !== undefined) {
+        console.log('正在更新标题:', title)
+        slidesStore.setTitle(title)
+        console.log('标题更新完成')
+      }
+      
+      if (theme !== undefined) {
+        console.log('正在更新主题:', theme)
+        slidesStore.setTheme(theme)
+        console.log('主题更新完成')
+      }
+      
+      // 更新视口尺寸(如果提供了的话)
+      if (width !== undefined && height !== undefined) {
+        console.log('正在触发视口尺寸更新事件:', { width, height, viewportRatio })
+        
+        // 同时也要更新slidesStore中的相关数据
+        if (slidesStore.setViewportSize) {
+          console.log('正在更新store中的视口尺寸')
+          slidesStore.setViewportSize(width)
+          if (slidesStore.setViewportRatio && viewportRatio !== undefined) {
+            slidesStore.setViewportRatio(viewportRatio)
+            console.log('视口比例已更新:', viewportRatio)
+          }
+        }
+        
+        window.dispatchEvent(new CustomEvent('viewportSizeUpdated', { 
+          detail: { width, height, viewportRatio }
+        }))
+        console.log('视口尺寸更新事件已触发')
+      }
+      
+      // 导入成功后,触发画布尺寸更新
+      // 使用 nextTick 确保DOM更新完成后再触发
+      console.log('开始触发画布尺寸更新事件...')
+      nextTick(() => {
+        console.log('DOM更新完成,触发 slidesDataUpdated 事件')
+        // 触发自定义事件,通知需要更新画布尺寸的组件
+        window.dispatchEvent(new CustomEvent('slidesDataUpdated', { 
+          detail: { 
+            slides, 
+            cover,
+            title,
+            theme,
+            width,
+            height,
+            viewportRatio,
+            timestamp: Date.now()
+          } 
+        }))
+        console.log('slidesDataUpdated 事件已触发')
+        
+        // 检查并调整幻灯片索引,确保在有效范围内
+        const newSlideCount = slides.length
+        const currentIndex = slidesStore.slideIndex
+        if (currentIndex >= newSlideCount) {
+          console.log('调整幻灯片索引:', currentIndex, '->', Math.max(0, newSlideCount - 1))
+          slidesStore.updateSlideIndex(Math.max(0, newSlideCount - 1))
+        }
+        
+        console.log('画布尺寸更新事件处理完成')
+      })
+      
+      console.log('readJSON 执行成功')
+      return { success: true, slides, title, theme, width, height, viewportRatio }
     }
     catch (error) {
+      console.error('readJSON 执行失败:', error)
       const errorMsg = '无法正确读取 / 解析该JSON数据'
       message.error(errorMsg)
       return { success: false, error: errorMsg, details: error }

+ 1 - 1
src/views/Editor/CanvasTool/WebpageInput.vue

@@ -27,7 +27,7 @@ const emit = defineEmits<{
   (event: 'close'): void
 }>()
 
-const webpageUrl = ref('https://aichat.cocorobo.cn/#/?id=4e826675-b8e3-44af-9ef3-8cebd302f702&type=agent/')
+const webpageUrl = ref('https://aichat.cocorobo.cn/#/?id=4e826675-b8e3-44af-9ef3-8cebd302f702&type=agent')
 
 const insertWebpage = () => {
   if (!webpageUrl.value) return message.error('请先输入正确的网页链接')

+ 2 - 0
src/views/Editor/CanvasTool/index.vue

@@ -225,6 +225,8 @@ const drawLine = (line: LinePoolItem) => {
   linePoolVisible.value = false
 }
 
+
+
 // 打开选择面板
 const toggleSelectPanel = () => {
   mainStore.setSelectPanelState(!showSelectPanel.value)

+ 9 - 1
src/views/Editor/EditorHeader/index.vue

@@ -62,6 +62,9 @@
           <div class="arrow-btn"><IconDown class="arrow" /></div>
         </Popover>
       </div>
+      <!-- <div class="menu-item" v-tooltip="'学生视图'" @click="enterStudentView()">
+        <IconUser class="icon" />
+      </div> -->
       <div class="menu-item" v-tooltip="'AI生成PPT'" @click="openAIPPTDialog(); mainMenuVisible = false">
         <span class="text ai">AI</span>
       </div>
@@ -87,7 +90,7 @@
 </template>
 
 <script lang="ts" setup>
-import { nextTick, ref, useTemplateRef } from 'vue'
+import { nextTick, ref, useTemplateRef, inject } from 'vue'
 import { storeToRefs } from 'pinia'
 import { useMainStore, useSlidesStore } from '@/store'
 import useScreening from '@/hooks/useScreening'
@@ -144,6 +147,11 @@ const openMarkupPanel = () => {
 const openAIPPTDialog = () => {
   mainStore.setAIPPTDialogState(true)
 }
+
+const enterStudentView = () => {
+  // 通过路由跳转到学生模式
+  window.location.href = '/?mode=student'
+}
 </script>
 
 <style lang="scss" scoped>

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 502 - 0
src/views/Student/index.vue


Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels