Преглед изворни кода

refactor(frame-element): 优化iframe脚本注入方式

将原本运行时动态注入脚本改为构建时内联注入,重命名页面原有$变量避免冲突,移除冗余的跨域检查和动态导入逻辑
lsc пре 1 дан
родитељ
комит
6903854d6a

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

@@ -2593,7 +2593,7 @@ const handleRefreshPage = () => {
         setTimeout(() => {
           iframe.srcdoc = originalSrcdoc
           console.log(`iframe ${i + 1} (srcdoc) 刷新完成`)
-        }, 5000)
+        }, 50)
         refreshedCount++
       }
     }

+ 70 - 72
src/views/components/element/FrameElement/BaseFrameElement.vue

@@ -64,7 +64,7 @@
         <!-- 延迟加载iframe:只有在可见且不是缩略图时才加载 -->
         <iframe
           :key="`html-${iframeKey}`"
-          :srcdoc="elementInfo.url"
+          :srcdoc="processedSrcdoc"
           v-else-if="elementInfo.isHTML && !isThumbnail && isVisible"
           :width="width"
           :height="height"
@@ -121,6 +121,10 @@ import {
 } from '@/configs/englishSpeakingTools'
 import { ref, watch, nextTick } from 'vue'
 import { computed } from 'vue'
+import jqueryRaw from './jquery-3.6.0.min.js?raw'
+import awsSdkRaw from './aws-sdk-2.235.1.min.js?raw'
+import jietuRaw from './jietu.js?raw'
+import html2canvasRaw from 'html2canvas/dist/html2canvas.min.js?raw'
 
 const props = defineProps({
   elementInfo: {
@@ -144,6 +148,65 @@ const props = defineProps({
 // 用于强制刷新iframe的key
 const iframeKey = ref(0)
 
+// 处理 srcdoc:将页面自定义的 $ 改名为 _$,注入 jQuery 等脚本
+const processedSrcdoc = computed(() => {
+  if (!props.elementInfo.isHTML) return props.elementInfo.url
+
+  const html = props.elementInfo.url
+  try {
+    const parser = new DOMParser()
+    const doc = parser.parseFromString(html, 'text/html')
+
+    // 1. 重命名页面已有的 $ 变量为 _$,避免与 jQuery 冲突
+    // 支持 var/let/const 声明,以及 $(...) 调用和 $.xxx 属性访问
+    const scripts = doc.querySelectorAll('script:not([src])')
+    scripts.forEach(script => {
+      const content = script.textContent || ''
+      if (!content) return
+      const modified = content
+        // 重命名声明:var $ = / let $ = / const $ =
+        .replace(/\b(var|let|const)\s+\$\s*=/g, '$1 _$ =')
+        // 重命名调用:$('id') → _$('id')
+        .replace(/(^|[^a-zA-Z0-9_$])\$\s*\(/g, '$1_$(')
+        // 重命名属性访问:$.ajax → _$.ajax
+        .replace(/(^|[^a-zA-Z0-9_$])\$\./g, '$1_$.')
+      script.textContent = modified
+    })
+
+    // 2. 在 head 开头注入 jQuery、html2canvas、aws-sdk
+    const head = doc.head || doc.getElementsByTagName('head')[0] || doc.createElement('head')
+    if (!head.parentNode) {
+      doc.documentElement.insertBefore(head, doc.body || null)
+    }
+
+    const injectScript = (content: string, prepend = false) => {
+      const s = doc.createElement('script')
+      s.textContent = content
+      if (prepend) head.insertBefore(s, head.firstChild)
+      else head.appendChild(s)
+    }
+
+    // 按顺序注入:jQuery → html2canvas → aws-sdk
+    injectScript(jqueryRaw, true)
+    injectScript(html2canvasRaw)
+    injectScript(awsSdkRaw)
+
+    // 3. 在 body 末尾追加 jietu.js(此时 $ 是 jQuery)
+    const body = doc.body || doc.getElementsByTagName('body')[0]
+    if (body) {
+      const jietuScript = doc.createElement('script')
+      jietuScript.textContent = jietuRaw
+      body.appendChild(jietuScript)
+    }
+
+    return '<!DOCTYPE html>' + doc.documentElement.outerHTML
+  }
+  catch (e) {
+    console.error('处理 srcdoc 失败:', e)
+    return html
+  }
+})
+
 // 监听elementInfo.url的变化
 watch(
   () => props.elementInfo.url,
@@ -198,78 +261,13 @@ const getTypeLabel = (type: number): string => {
 
 const frameloading = ref(false)
 
-// 处理iframe加载完成事件
-const handleIframeLoad = async (event: Event) => {
-  const iframe = event.target as HTMLIFrameElement
+// 处理iframe加载完成事件(脚本已在 srcdoc 中内联注入,这里只需隐藏 loading)
+const handleIframeLoad = async () => {
   frameloading.value = true
-  try {
-    // 等待iframe完全加载
-    await nextTick()
-    setTimeout(async () => {
-      // 检查iframe是否可访问(同源检查)
-      if (iframe.contentWindow && iframe.contentDocument) {
-        const iframeDoc = iframe.contentDocument
-        const iframeHead =
-          iframeDoc.head || iframeDoc.getElementsByTagName('head')[0]
-
-        if (iframeHead) {
-          // 使用动态导入获取JS文件内容
-          const jsFiles = [
-            {
-              id: 'aws-sdk',
-              importPath: () => import('./aws-sdk-2.235.1.min.js?raw'),
-            },
-            {
-              id: 'jquery',
-              importPath: () => import('./jquery-3.6.0.min.js?raw'),
-            },
-            { id: 'jietu', importPath: () => import('./jietu.js?raw') },
-          ]
-
-          for (const jsFile of jsFiles) {
-            try {
-              // 检查是否已经注入过
-              if (!iframeDoc.getElementById(jsFile.id)) {
-                const jsModule = await jsFile.importPath()
-                const jsContent = jsModule.default || jsModule
-
-                const scriptElement = iframeDoc.createElement('script')
-                scriptElement.id = jsFile.id
-                scriptElement.textContent = jsContent
-                iframeHead.appendChild(scriptElement)
-
-                console.log(`已注入 ${jsFile.id} 到iframe中`)
-              }
-            }
-            catch (fetchError) {
-              console.error(`获取 ${jsFile.id} 失败:`, fetchError)
-            }
-          }
-          frameloading.value = false
-
-          // 可选:在iframe中执行一些初始化代码
-          try {
-            iframe.contentWindow.eval(`
-                console.log('iframe中的JS环境已准备就绪');
-                // 这里可以添加一些初始化代码
-              `)
-          }
-          catch (evalError) {
-            frameloading.value = false
-            console.warn('无法在iframe中执行代码:', evalError)
-          }
-        }
-      }
-      else {
-        frameloading.value = false
-        console.warn('无法访问iframe内容,可能是跨域限制')
-      }
-    }, 1000)
-  }
-  catch (error) {
+  await nextTick()
+  setTimeout(() => {
     frameloading.value = false
-    console.error('注入JS到iframe失败:', error)
-  }
+  }, 500)
 }
 </script>
   
@@ -434,4 +432,4 @@ const handleIframeLoad = async (event: Event) => {
     transform: rotate(360deg);
   }
 }
-</style>
+</style>