|
|
@@ -5,307 +5,342 @@
|
|
|
height: elementInfo.height + 'px',
|
|
|
|
|
|
-->
|
|
|
- <div class="base-element-frame"
|
|
|
- :style="{
|
|
|
- top: elementInfo.top + 'px',
|
|
|
- left: elementInfo.left + 'px',
|
|
|
- width: '100%',
|
|
|
- height: '100%',
|
|
|
- }"
|
|
|
+ <div
|
|
|
+ class="base-element-frame"
|
|
|
+ :style="{
|
|
|
+ transform: isThumbnail ? 'scale(1)': `scale(${1 / props.scale})`,
|
|
|
+ transformOrigin: 'top left', // 关键点
|
|
|
+ top: props.elementInfo.top + 'px',
|
|
|
+ left: props.elementInfo.left + 'px',
|
|
|
+ width: (isThumbnail ? props.elementInfo.width : width) + 'px',
|
|
|
+ height: (isThumbnail ? props.elementInfo.height : height) + 'px',
|
|
|
+ overflow: 'hidden',
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="rotate-wrapper"
|
|
|
+ :style="{ transform: `rotate(${elementInfo.rotate}deg)` }"
|
|
|
>
|
|
|
- <div
|
|
|
- class="rotate-wrapper"
|
|
|
- :style="{ transform: `rotate(${elementInfo.rotate}deg)` }"
|
|
|
- >
|
|
|
- <div class="element-content">
|
|
|
- <!-- 视频类型(type 74):使用 video 标签 -->
|
|
|
- <video
|
|
|
- v-if="elementInfo.toolType === 74 && !isThumbnail && isVisible"
|
|
|
- :key="`video-${iframeKey}`"
|
|
|
- :src="elementInfo.url"
|
|
|
- :width="elementInfo.width"
|
|
|
- :height="elementInfo.height"
|
|
|
- controls
|
|
|
- :style="{ width: '100%', height: '100%', objectFit: 'contain' }"
|
|
|
- ></video>
|
|
|
- <!-- B站视频类型(type 75):使用 iframe -->
|
|
|
- <iframe
|
|
|
- v-else-if="elementInfo.toolType === 75 && !isThumbnail && isVisible"
|
|
|
- :key="`bilibili-${iframeKey}`"
|
|
|
- :src="elementInfo.url"
|
|
|
- :style="{
|
|
|
- width: '100%',
|
|
|
- height: '100%',
|
|
|
- }"
|
|
|
- :frameborder="0"
|
|
|
- :allowfullscreen="true"
|
|
|
- allow="camera *; microphone *; display-capture; midi; encrypted-media; fullscreen; geolocation; clipboard-read; clipboard-write; accelerometer; autoplay; gyroscope; payment; picture-in-picture; usb; xr-spatial-tracking;"
|
|
|
- @load="handleIframeLoad"
|
|
|
- ></iframe>
|
|
|
- <!-- 延迟加载iframe:只有在可见且不是缩略图时才加载 -->
|
|
|
- <iframe
|
|
|
- :key="`html-${iframeKey}`"
|
|
|
- :srcdoc="elementInfo.url"
|
|
|
- v-else-if="elementInfo.isHTML && !isThumbnail && isVisible"
|
|
|
- :style="{
|
|
|
- width: '100%',
|
|
|
- height: '100%',
|
|
|
- }"
|
|
|
- :frameborder="0"
|
|
|
- :allowfullscreen="true"
|
|
|
- allow="camera *; microphone *; display-capture; midi; encrypted-media; fullscreen; geolocation; clipboard-read; clipboard-write; accelerometer; autoplay; gyroscope; payment; picture-in-picture; usb; xr-spatial-tracking;"
|
|
|
- @load="handleIframeLoad"
|
|
|
- ></iframe>
|
|
|
- <iframe
|
|
|
- :key="`src-${iframeKey}`"
|
|
|
- v-else-if="!isThumbnail && isVisible"
|
|
|
- :src="elementInfo.url"
|
|
|
- :style="{
|
|
|
- width: '100%',
|
|
|
- height: '100%',
|
|
|
- }"
|
|
|
- :frameborder="0"
|
|
|
- :allowfullscreen="true"
|
|
|
- allow="camera *; microphone *; display-capture; midi; encrypted-media; fullscreen; geolocation; clipboard-read; clipboard-write; accelerometer; autoplay; gyroscope; payment; picture-in-picture; usb; xr-spatial-tracking;"
|
|
|
- @load="handleIframeLoad"
|
|
|
- ></iframe>
|
|
|
- <!-- 占位符:当不可见时显示 -->
|
|
|
- <div v-else-if="!isThumbnail && !isVisible" class="iframe-placeholder">
|
|
|
- <div class="placeholder-content">
|
|
|
- <div class="placeholder-icon">🌐</div>
|
|
|
- <div class="placeholder-text">{{ lang.ssInteract }}</div>
|
|
|
- <div class="placeholder-type">({{ getTypeLabel(Number(elementInfo.toolType)) }})</div>
|
|
|
+ <div class="element-content">
|
|
|
+ <!-- 视频类型(type 74):使用 video 标签 -->
|
|
|
+ <video
|
|
|
+ v-if="elementInfo.toolType === 74 && !isThumbnail && isVisible"
|
|
|
+ :key="`video-${iframeKey}`"
|
|
|
+ :src="elementInfo.url"
|
|
|
+ :width="width"
|
|
|
+ :height="height"
|
|
|
+ controls
|
|
|
+ :style="{ width: '100%', height: '100%', objectFit: 'contain' }"
|
|
|
+ ></video>
|
|
|
+ <!-- B站视频类型(type 75):使用 iframe -->
|
|
|
+ <iframe
|
|
|
+ v-else-if="elementInfo.toolType === 75 && !isThumbnail && isVisible"
|
|
|
+ :key="`bilibili-${iframeKey}`"
|
|
|
+ :src="elementInfo.url"
|
|
|
+ :width="width"
|
|
|
+ :height="height"
|
|
|
+ :frameborder="0"
|
|
|
+ :allowfullscreen="true"
|
|
|
+ allow="camera *; microphone *; display-capture; midi; encrypted-media; fullscreen; geolocation; clipboard-read; clipboard-write; accelerometer; autoplay; gyroscope; payment; picture-in-picture; usb; xr-spatial-tracking;"
|
|
|
+ @load="handleIframeLoad"
|
|
|
+ ></iframe>
|
|
|
+ <!-- 延迟加载iframe:只有在可见且不是缩略图时才加载 -->
|
|
|
+ <iframe
|
|
|
+ :key="`html-${iframeKey}`"
|
|
|
+ :srcdoc="elementInfo.url"
|
|
|
+ v-else-if="elementInfo.isHTML && !isThumbnail && isVisible"
|
|
|
+ :width="width"
|
|
|
+ :height="height"
|
|
|
+ :frameborder="0"
|
|
|
+ :allowfullscreen="true"
|
|
|
+ allow="camera *; microphone *; display-capture; midi; encrypted-media; fullscreen; geolocation; clipboard-read; clipboard-write; accelerometer; autoplay; gyroscope; payment; picture-in-picture; usb; xr-spatial-tracking;"
|
|
|
+ @load="handleIframeLoad"
|
|
|
+ ></iframe>
|
|
|
+ <iframe
|
|
|
+ :key="`src-${iframeKey}`"
|
|
|
+ v-else-if="!isThumbnail && isVisible"
|
|
|
+ :src="elementInfo.url"
|
|
|
+ :width="width"
|
|
|
+ :height="height"
|
|
|
+ :frameborder="0"
|
|
|
+ :allowfullscreen="true"
|
|
|
+ allow="camera *; microphone *; display-capture; midi; encrypted-media; fullscreen; geolocation; clipboard-read; clipboard-write; accelerometer; autoplay; gyroscope; payment; picture-in-picture; usb; xr-spatial-tracking;"
|
|
|
+ @load="handleIframeLoad"
|
|
|
+ ></iframe>
|
|
|
+ <!-- 占位符:当不可见时显示 -->
|
|
|
+ <div v-else-if="!isThumbnail && !isVisible" class="iframe-placeholder">
|
|
|
+ <div class="placeholder-content">
|
|
|
+ <div class="placeholder-icon">🌐</div>
|
|
|
+ <div class="placeholder-text">{{ lang.ssInteract }}</div>
|
|
|
+ <div class="placeholder-type">
|
|
|
+ ({{ getTypeLabel(Number(elementInfo.toolType)) }})
|
|
|
</div>
|
|
|
</div>
|
|
|
- <!-- 缩略图模式 -->
|
|
|
- <div v-else-if="isThumbnail" class="thumbnail-content">
|
|
|
- <div class="thumbnail-content-inner">
|
|
|
- <div>{{ lang.ssInteract }}</div>
|
|
|
- <div>({{ getTypeLabel(Number(elementInfo.toolType)) }})</div>
|
|
|
- </div>
|
|
|
+ </div>
|
|
|
+ <!-- 缩略图模式 -->
|
|
|
+ <div v-else-if="isThumbnail" class="thumbnail-content">
|
|
|
+ <div class="thumbnail-content-inner">
|
|
|
+ <div>{{ lang.ssInteract }}</div>
|
|
|
+ <div>({{ getTypeLabel(Number(elementInfo.toolType)) }})</div>
|
|
|
</div>
|
|
|
- <!-- 在放映模式下不显示遮罩层,允许用户与iframe交互 -->
|
|
|
- <div class="mask" v-if="false"></div>
|
|
|
</div>
|
|
|
+ <!-- 在放映模式下不显示遮罩层,允许用户与iframe交互 -->
|
|
|
+ <div class="mask" v-if="false"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </template>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
- import type { PropType } from 'vue'
|
|
|
- import type { PPTFrameElement } from '@/types/slides'
|
|
|
- import { lang } from '@/main'
|
|
|
- import { ref, watch, nextTick } from 'vue'
|
|
|
-
|
|
|
- const props = defineProps({
|
|
|
- elementInfo: {
|
|
|
- type: Object as PropType<PPTFrameElement>,
|
|
|
- required: true,
|
|
|
- },
|
|
|
- isThumbnail: {
|
|
|
- type: Boolean,
|
|
|
- default: false,
|
|
|
- },
|
|
|
- isVisible: {
|
|
|
- type: Boolean,
|
|
|
- default: false,
|
|
|
- },
|
|
|
- })
|
|
|
-
|
|
|
- // 用于强制刷新iframe的key
|
|
|
- const iframeKey = ref(0)
|
|
|
-
|
|
|
- // 监听elementInfo.url的变化
|
|
|
- watch(() => props.elementInfo.url, (newUrl, oldUrl) => {
|
|
|
+import type { PropType } from "vue";
|
|
|
+import type { PPTFrameElement } from "@/types/slides";
|
|
|
+import { lang } from "@/main";
|
|
|
+import { ref, watch, nextTick } from "vue";
|
|
|
+import { computed } from 'vue'
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ elementInfo: {
|
|
|
+ type: Object as PropType<PPTFrameElement>,
|
|
|
+ required: true,
|
|
|
+ },
|
|
|
+ isThumbnail: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ scale: {
|
|
|
+ type: Number,
|
|
|
+ default: 1
|
|
|
+ },
|
|
|
+ isVisible: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+// 用于强制刷新iframe的key
|
|
|
+const iframeKey = ref(0);
|
|
|
+
|
|
|
+// 监听elementInfo.url的变化
|
|
|
+watch(
|
|
|
+ () => props.elementInfo.url,
|
|
|
+ (newUrl, oldUrl) => {
|
|
|
if (newUrl !== oldUrl) {
|
|
|
// 通过改变key来强制刷新iframe
|
|
|
- iframeKey.value++
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- // 获取类型标签
|
|
|
- const getTypeLabel = (type: number): string => {
|
|
|
- const typeMap: Record<number, keyof typeof lang> = {
|
|
|
- 45: 'ssChoiceQ',
|
|
|
- 15: 'ssEssayQ',
|
|
|
- 72: 'ssAIApp',
|
|
|
- 73: 'ssH5Page',
|
|
|
- 74: 'ssVideo',
|
|
|
- 75: lang.lang == 'cn' ? 'ssBiliVideo' : 'ssYouTube',
|
|
|
- 76: 'ssCreateSpace'
|
|
|
+ iframeKey.value++;
|
|
|
}
|
|
|
- const key = typeMap[type]
|
|
|
- return (key ? lang[key] : lang.ssUnknown) as string
|
|
|
}
|
|
|
-
|
|
|
- // 处理iframe加载完成事件
|
|
|
- const handleIframeLoad = async (event: Event) => {
|
|
|
- const iframe = event.target as HTMLIFrameElement
|
|
|
-
|
|
|
- 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)
|
|
|
+);
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+const width = computed(() => {
|
|
|
+ return props.elementInfo.width * props.scale;
|
|
|
+})
|
|
|
+
|
|
|
+const height = computed(() => {
|
|
|
+ return props.elementInfo.height * props.scale;
|
|
|
+})
|
|
|
+
|
|
|
+const left = computed(() => {
|
|
|
+ return props.elementInfo.left * props.scale;
|
|
|
+})
|
|
|
+
|
|
|
+const top = computed(() => {
|
|
|
+ return props.elementInfo.top * props.scale;
|
|
|
+})
|
|
|
+
|
|
|
+
|
|
|
+// 获取类型标签
|
|
|
+const getTypeLabel = (type: number): string => {
|
|
|
+ const typeMap: Record<number, keyof typeof lang> = {
|
|
|
+ 45: "ssChoiceQ",
|
|
|
+ 15: "ssEssayQ",
|
|
|
+ 72: "ssAIApp",
|
|
|
+ 73: "ssH5Page",
|
|
|
+ 74: "ssVideo",
|
|
|
+ 75: lang.lang == "cn" ? "ssBiliVideo" : "ssYouTube",
|
|
|
+ 76: "ssCreateSpace",
|
|
|
+ };
|
|
|
+ const key = typeMap[type];
|
|
|
+ return (key ? lang[key] : lang.ssUnknown) as string;
|
|
|
+};
|
|
|
+
|
|
|
+// 处理iframe加载完成事件
|
|
|
+const handleIframeLoad = async (event: Event) => {
|
|
|
+ const iframe = event.target as HTMLIFrameElement;
|
|
|
+
|
|
|
+ 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);
|
|
|
}
|
|
|
-
|
|
|
- // 可选:在iframe中执行一些初始化代码
|
|
|
- try {
|
|
|
- iframe.contentWindow.eval(`
|
|
|
+ }
|
|
|
+
|
|
|
+ // 可选:在iframe中执行一些初始化代码
|
|
|
+ try {
|
|
|
+ iframe.contentWindow.eval(`
|
|
|
console.log('iframe中的JS环境已准备就绪');
|
|
|
// 这里可以添加一些初始化代码
|
|
|
- `)
|
|
|
- }
|
|
|
- catch (evalError) {
|
|
|
- console.warn('无法在iframe中执行代码:', evalError)
|
|
|
- }
|
|
|
+ `);
|
|
|
+ } catch (evalError) {
|
|
|
+ console.warn("无法在iframe中执行代码:", evalError);
|
|
|
}
|
|
|
}
|
|
|
- else {
|
|
|
- console.warn('无法访问iframe内容,可能是跨域限制')
|
|
|
- }
|
|
|
- }, 1000)
|
|
|
- }
|
|
|
- catch (error) {
|
|
|
- console.error('注入JS到iframe失败:', error)
|
|
|
- }
|
|
|
+ } else {
|
|
|
+ console.warn("无法访问iframe内容,可能是跨域限制");
|
|
|
+ }
|
|
|
+ }, 1000);
|
|
|
+ } catch (error) {
|
|
|
+ console.error("注入JS到iframe失败:", error);
|
|
|
}
|
|
|
- </script>
|
|
|
+};
|
|
|
+</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
- .base-element-frame {
|
|
|
- position: absolute;
|
|
|
- }
|
|
|
- .element-content {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- overflow: hidden;
|
|
|
-
|
|
|
- video {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- object-fit: contain;
|
|
|
- background-color: #000;
|
|
|
- }
|
|
|
- }
|
|
|
- .mask {
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- bottom: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- }
|
|
|
- .rotate-wrapper {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- overflow: hidden;
|
|
|
- }
|
|
|
- .thumbnail-content {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- background-color: #fff;
|
|
|
- }
|
|
|
-
|
|
|
- .thumbnail-content-inner {
|
|
|
+.base-element-frame {
|
|
|
+ position: absolute;
|
|
|
+}
|
|
|
+.element-content {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ video {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
- color: #3681fc;
|
|
|
- font-size: 110px;
|
|
|
- font-weight: 600;
|
|
|
- text-align: center;
|
|
|
- line-height: 100%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- flex-direction: column;
|
|
|
- gap: 50px;
|
|
|
- }
|
|
|
-
|
|
|
- /* iframe占位符样式 */
|
|
|
- .iframe-placeholder {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
|
|
- border: 2px solid #dee2e6;
|
|
|
- border-radius: 8px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- position: relative;
|
|
|
- overflow: hidden;
|
|
|
- }
|
|
|
-
|
|
|
- .placeholder-content {
|
|
|
- text-align: center;
|
|
|
- color: #6c757d;
|
|
|
- font-family: Arial, sans-serif;
|
|
|
+ object-fit: contain;
|
|
|
+ background-color: #000;
|
|
|
}
|
|
|
-
|
|
|
- .placeholder-icon {
|
|
|
- font-size: 48px;
|
|
|
- margin-bottom: 12px;
|
|
|
- opacity: 0.7;
|
|
|
- }
|
|
|
-
|
|
|
- .placeholder-text {
|
|
|
- font-size: 16px;
|
|
|
- font-weight: 600;
|
|
|
- margin-bottom: 4px;
|
|
|
- }
|
|
|
-
|
|
|
- .placeholder-type {
|
|
|
- font-size: 12px;
|
|
|
- opacity: 0.8;
|
|
|
- }
|
|
|
-
|
|
|
- /* 添加加载动画效果 */
|
|
|
- .iframe-placeholder::before {
|
|
|
- content: '';
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
+}
|
|
|
+.mask {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+}
|
|
|
+.rotate-wrapper {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.thumbnail-content {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background-color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.thumbnail-content-inner {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ color: #3681fc;
|
|
|
+ font-size: 110px;
|
|
|
+ font-weight: 600;
|
|
|
+ text-align: center;
|
|
|
+ line-height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 50px;
|
|
|
+}
|
|
|
+
|
|
|
+/* iframe占位符样式 */
|
|
|
+.iframe-placeholder {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
|
|
+ border: 2px solid #dee2e6;
|
|
|
+ border-radius: 8px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.placeholder-content {
|
|
|
+ text-align: center;
|
|
|
+ color: #6c757d;
|
|
|
+ font-family: Arial, sans-serif;
|
|
|
+}
|
|
|
+
|
|
|
+.placeholder-icon {
|
|
|
+ font-size: 48px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ opacity: 0.7;
|
|
|
+}
|
|
|
+
|
|
|
+.placeholder-text {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.placeholder-type {
|
|
|
+ font-size: 12px;
|
|
|
+ opacity: 0.8;
|
|
|
+}
|
|
|
+
|
|
|
+/* 添加加载动画效果 */
|
|
|
+.iframe-placeholder::before {
|
|
|
+ content: "";
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: -100%;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(
|
|
|
+ 90deg,
|
|
|
+ transparent,
|
|
|
+ rgba(255, 255, 255, 0.4),
|
|
|
+ transparent
|
|
|
+ );
|
|
|
+ animation: shimmer 2s infinite;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes shimmer {
|
|
|
+ 0% {
|
|
|
left: -100%;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
|
|
|
- animation: shimmer 2s infinite;
|
|
|
}
|
|
|
-
|
|
|
- @keyframes shimmer {
|
|
|
- 0% {
|
|
|
- left: -100%;
|
|
|
- }
|
|
|
- 100% {
|
|
|
- left: 100%;
|
|
|
- }
|
|
|
+ 100% {
|
|
|
+ left: 100%;
|
|
|
}
|
|
|
- </style>
|
|
|
+}
|
|
|
+</style>
|