Browse Source

feat: 延迟处理 type=73 元素的探测抓取逻辑

将 type=73 元素的探测和抓取逻辑从页面初始化时提前到 BaseFrameElement 组件打开时执行,避免页面加载时的性能开销。同时增加了背景图片上传替换的逻辑,处理 base64 格式的背景图片上传到 S3 并替换为可访问的链接。另外简化了 elementDone 函数中 type=73 元素的处理逻辑,移除了冗余的临时 iframe 探测代码。
lsc 5 days ago
parent
commit
6a496b0d50

+ 20 - 0
src/hooks/useImport.ts

@@ -340,6 +340,26 @@ export default () => {
       // isChange 模式:检查图片元素 src 是否为 base64,是则上传 S3 并替换为链接
       // isChange 模式:检查图片元素 src 是否为 base64,是则上传 S3 并替换为链接
       if (isChange && Array.isArray(slides)) {
       if (isChange && Array.isArray(slides)) {
         for (const slide of slides) {
         for (const slide of slides) {
+          // 背景图片:background.type === 'image' 且 src 为 base64 时同样上传替换
+          const bgSrc = slide?.background?.image?.src
+          if (slide?.background?.type === 'image' && typeof bgSrc === 'string' && bgSrc.startsWith('data:')) {
+            (async () => {
+              try {
+                const file = await makeWhiteTransparent(bgSrc, `background_${Date.now()}.png`)
+                if (file) {
+                  const url = await uploadFileToS3(file)
+                  slide.background.image.src = url
+                  // updateSlide 缺省 slideId 时会落到当前页,原始数据还没分配 id 时只改原始对象
+                  if (slide.id) {
+                    slidesStore.updateSlide({ background: { ...slide.background, image: { ...slide.background.image, src: url } } }, slide.id)
+                  }
+                }
+              }
+              catch (error) {
+                console.error('Background image upload failed:', error)
+              }
+            })()
+          }
           if (!slide?.elements) continue
           if (!slide?.elements) continue
           for (const element of slide.elements) {
           for (const element of slide.elements) {
             if (element.type !== 'image') continue
             if (element.type !== 'image') continue

+ 2 - 1
src/views/Student/index.vue

@@ -1634,7 +1634,8 @@ const processIframeLinks = async () => {
                 console.log('更新后的iframe元素:', updatedElement)
                 console.log('更新后的iframe元素:', updatedElement)
                 return {
                 return {
                   ...updatedElement,
                   ...updatedElement,
-                  isDone: true
+                  // type=73 的探测+抓取延迟到 BaseFrameElement 打开时执行(isDone!==true 才触发)
+                  isDone: element.toolType === 73 ? (updatedElement.isDone ?? false) : true
                 }
                 }
               }
               }
 
 

+ 9 - 97
src/views/Student/index2.vue

@@ -1599,7 +1599,8 @@ const processIframeLinks = async () => {
                 console.log('更新后的iframe元素:', updatedElement)
                 console.log('更新后的iframe元素:', updatedElement)
                 return {
                 return {
                   ...updatedElement,
                   ...updatedElement,
-                  isDone: true
+                  // type=73 的探测+抓取延迟到 BaseFrameElement 打开时执行(isDone!==true 才触发)
+                  isDone: element.toolType === 73 ? (updatedElement.isDone ?? false) : true
                 }
                 }
               }
               }
 
 
@@ -1812,102 +1813,13 @@ const elementDone = async (element: any, slideIndex: number) => {
   }
   }
   else if (toolType === 73) {
   else if (toolType === 73) {
     hasIframe = true
     hasIframe = true
-                  
-    // 先尝试获取iframe的contentWindow,如果获取不到再使用HTML方式
-    try {
-      // 创建一个临时的iframe来测试是否能获取contentWindow
-      const tempIframe = document.createElement('iframe')
-      // 不用 display:none:iPad Safari/WKWebView 会对隐藏 iframe 资源节流,
-      // onload 迟迟不触发导致超时误判为"禁止嵌入",误入抓 HTML 路径(跨站 Cookie 被 ITP 拦截必然失败)
-      tempIframe.style.position = 'absolute'
-      tempIframe.style.left = '-9999px'
-      tempIframe.style.top = '-9999px'
-      tempIframe.style.width = '1px'
-      tempIframe.style.height = '1px'
-      tempIframe.src = iframeSrc
-                    
-      // 先将临时iframe添加到body,否则onload事件不会触发
-      document.body.appendChild(tempIframe)
-      // 等待iframe加载完成
-      await new Promise((resolve, reject) => {
-        tempIframe.onload = resolve
-        tempIframe.onerror = reject
-        // iPad 网络/渲染较慢,超时放宽到 12 秒
-        setTimeout(() => reject(new Error('Timeout')), 12000)
-      })
-                    
-      // 尝试获取contentDocument(标准属性:跨域返回 null 不抛异常;
-      // 不能用 contentWindow.document,iPad Safari 跨域访问会直接抛 SecurityError,又会误入抓 HTML 路径)
-      if (tempIframe.contentDocument) {
-        console.log(`iframe ${iframeSrc} 可以获取contentWindow,使用直接加载方式`)
-        // 移除临时iframe
-        document.body.removeChild(tempIframe)
-                      
-        _element = {
-          ...element,
-          isHTML: false,
-          url: iframeSrc
-        }
-      }
-      else {
-      // 加载完成但无法获取contentWindow,也要移除iframe
-        document.body.removeChild(tempIframe)
-      }
-    }
-    catch (error) {
-      console.log(`iframe ${iframeSrc} 无法获取contentWindow,使用HTML方式:`, error)
-                  
-      // 如果无法获取contentWindow,使用HTML方式
-      let html = null
-      try {
-        html = await api.getHTML(iframeSrc)
-        console.log('getHTML 成功获取内容:', html)
-      }
-      catch (error) {
-        console.log(`getHTML 失败,尝试使用 getFile:`, error)
-        try {
-          const fileData = await getFile(iframeSrc)
-          if (fileData && fileData.data) {
-            const uint8Array = new Uint8Array(fileData.data)
-            html = new TextDecoder('utf-8').decode(uint8Array)
-            console.log('getFile 成功获取内容:', html)
-          }
-        }
-        catch (error2) {
-          console.log(`getFile 失败,尝试使用 getHTML:`, error2)
-          try {
-            const fileData2 = await getFile2(iframeSrc)
-            if (fileData2 && fileData2.data) {
-              const uint8Array = new Uint8Array(fileData2.data)
-              html = new TextDecoder('utf-8').decode(uint8Array)
-              console.log('getFile2 成功获取内容:', html)
-            }
-          }
-          catch (error3) {
-            console.error('getFile2 也失败:', error3)
-            console.error('无法获取内容: getFile、getFile2 和 getHTML 都失败了')
-          }
-        }
-      }
-      console.log(`处理幻灯片 ${slideIndex + 1} 中的iframe链接:`, iframeSrc)
-      if (html) {
-        _element = {
-          ...element,
-          isHTML: true,
-          url: html,
-          curl: iframeSrc
-        }
-      }
-      else {
-        // getHTML/getFile/getFile2 全失败:不能置空 url 硬切 srcdoc(会白屏),
-        // 回退为直接加载原始链接,至少保持与进页面时一致的显示效果
-        console.warn(`幻灯片 ${slideIndex + 1} 未获取到HTML内容,回退为直接加载:`, iframeSrc)
-        _element = {
-          ...element,
-          isHTML: false,
-          url: iframeSrc
-        }
-      }
+    // type=73 的探测+抓取延迟到 BaseFrameElement 打开时执行(isDone!==true 才触发)
+    // 这里只做域名修正,url 已经是前面通用替换后的 iframeSrc
+    console.log(`type=73 延迟处理中,url=`, iframeSrc)
+    _element = {
+      ...element,
+      url: iframeSrc
+      // 不设 isHTML / curl,保持 isDone 默认值让 BaseFrameElement 感知
     }
     }
   }
   }
 
 

+ 60 - 0
src/views/components/element/FrameElement/BaseFrameElement.vue

@@ -122,6 +122,12 @@ import {
 } from '@/configs/englishSpeakingTools'
 } from '@/configs/englishSpeakingTools'
 import { ref, watch, nextTick } from 'vue'
 import { ref, watch, nextTick } from 'vue'
 import { computed } from 'vue'
 import { computed } from 'vue'
+import { useImport } from '@/hooks/useImport'
+import { useSlidesStore } from '@/store/slides'
+import api from '@/services/course'
+
+const slidesStore = useSlidesStore()
+const { getFile, getFile2 } = useImport()
 
 
 const props = defineProps({
 const props = defineProps({
   elementInfo: {
   elementInfo: {
@@ -301,6 +307,60 @@ const frameloading = ref(false)
 const handleIframeLoad = async (event: Event) => {
 const handleIframeLoad = async (event: Event) => {
   const iframe = event.target as HTMLIFrameElement
   const iframe = event.target as HTMLIFrameElement
   frameloading.value = true
   frameloading.value = true
+
+  // ★ type=73 延迟处理:elementDone 进页面时只做了域名修正,探测+抓取留到 iframe 真正打开时
+  // 触发条件:isDone !== true(processIframeLinks 里跳过了 type=73 的 isDone 赋值)
+  const info = props.elementInfo
+  if (info.toolType === 73 && !info.isDone && props.isVisible && !props.isThumbnail) {
+    try {
+      console.log('🖼️ type=73 延迟探测触发, url=', info.url)
+      // 直接用当前真实 iframe 测跨域(contentDocument 跨域返回 null 不抛异常;
+      // 不用 contentWindow.document,iPad Safari 跨域会抛 SecurityError)
+      if (iframe.contentDocument) {
+        console.log('type=73 同源可访问 contentDocument,标记 isDone,保留直接加载')
+        slidesStore.updateElement({ id: info.id, props: { isDone: true } })
+      } else {
+        console.log('type=73 跨域,尝试抓取 HTML 做 srcdoc')
+        let html: string | null = null
+        try {
+          html = await api.getHTML(info.url)
+        } catch (_e) {
+          try {
+            const fd = await getFile(info.url)
+            if (fd?.data) html = new TextDecoder('utf-8').decode(new Uint8Array(fd.data))
+          } catch (_e2) {
+            try {
+              const fd2 = await getFile2(info.url)
+              if (fd2?.data) html = new TextDecoder('utf-8').decode(new Uint8Array(fd2.data))
+            } catch (e3) {
+              console.error('type=73 抓取全部失败:', e3)
+            }
+          }
+        }
+
+        if (html) {
+          console.log('type=73 抓取成功,切换为 srcdoc')
+          // 更新 slidesStore:设 isHTML、url=html、curl=原始链接、isDone=true
+          // 触发 watch url → iframeKey++ → iframe 重建为 srcdoc 模式
+          slidesStore.updateElement({
+            id: info.id,
+            props: { isHTML: true, url: html, curl: info.url, isDone: true },
+          })
+          return // updateElement 会触发 iframe 重建,不再走下面的 JS 注入
+        } else {
+          console.warn('type=73 抓取失败,回退为直接加载,标记 isDone')
+          slidesStore.updateElement({ id: info.id, props: { isDone: true } })
+        }
+      }
+    } catch (err) {
+      console.error('type=73 延迟处理异常:', err)
+      // 出错也设 isDone=true,避免无限重试
+      slidesStore.updateElement({ id: info.id, props: { isDone: true } })
+    }
+    frameloading.value = false
+    return
+  }
+
   try {
   try {
     // 等待iframe完全加载
     // 等待iframe完全加载
     await nextTick()
     await nextTick()