|
|
@@ -1,199 +1,161 @@
|
|
|
<template>
|
|
|
<div class="gis-page">
|
|
|
<div class="card">
|
|
|
- <!-- <div class="card-head">空间一张图</div> -->
|
|
|
<div class="card-body">
|
|
|
<div class="gis-container" ref="iframeContainer">
|
|
|
- <iframe
|
|
|
- ref="iframeRef"
|
|
|
+ <!-- iframe 加载完成前隐藏,避免白屏闪出 -->
|
|
|
+ <iframe
|
|
|
+ v-show="isIframeReady"
|
|
|
+ ref="iframeRef"
|
|
|
:key="iframeKey"
|
|
|
- :src="iframeSrc"
|
|
|
- frameborder="0"
|
|
|
- allowfullscreen
|
|
|
- width="100%"
|
|
|
- height="100%"
|
|
|
- style=""
|
|
|
+ :src="iframeSrc"
|
|
|
+ frameborder="0"
|
|
|
+ allowfullscreen
|
|
|
+ width="100%"
|
|
|
+ height="100%"
|
|
|
@load="onIframeLoad"
|
|
|
></iframe>
|
|
|
+ <!-- 加载遮罩(iframe 隐藏时显示) -->
|
|
|
+ <div v-if="!isIframeReady" class="iframe-loading">
|
|
|
+ <div class="iframe-loading-spinner"></div>
|
|
|
+ <div class="iframe-loading-text">{{ loadingText }}</div>
|
|
|
+ <div v-if="retryCount > 0" class="iframe-loading-retry">
|
|
|
+ 已自动重试 {{ retryCount }} 次
|
|
|
+ <el-button type="primary" link size="small" @click="reloadIframe(true)">手动重试</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
+
|
|
|
<script setup lang="ts">
|
|
|
import CONFIG from '@/utils/config'
|
|
|
-import { ref, onMounted, watch } from 'vue'
|
|
|
-import { ElLoading } from "element-plus";
|
|
|
-import type { LoadingInstance } from 'element-plus'
|
|
|
+import { ref, onMounted, onUnmounted } from 'vue'
|
|
|
|
|
|
const iframeRef = ref<HTMLIFrameElement | null>(null)
|
|
|
-const iframeContainer = ref<HTMLElement | null>(null)
|
|
|
-const loadingInstance = ref<LoadingInstance | null>(null)
|
|
|
+// const iframeContainer = ref<HTMLElement | null>(null)
|
|
|
const iframeKey = ref(0)
|
|
|
const iframeSrc = ref(CONFIG.ONEMAP_URL)
|
|
|
const isIframeReady = ref(false)
|
|
|
-const loadStartTime = ref(0)
|
|
|
-const minLoadTime = 3000 // 最小加载时间(毫秒),确保页面有足够时间渲染
|
|
|
-const maxLoadTime = 15000 // 最大等待时间(毫秒)
|
|
|
-
|
|
|
-// 关闭 loading 的方法
|
|
|
-const closeLoading = () => {
|
|
|
- if (loadingInstance.value) {
|
|
|
- loadingInstance.value.close()
|
|
|
- loadingInstance.value = null
|
|
|
+const loadingText = ref('加载地图中...')
|
|
|
+const retryCount = ref(0)
|
|
|
+
|
|
|
+const maxLoadTime = 15000
|
|
|
+const maxRetryCount = 2
|
|
|
+
|
|
|
+let maxTimeoutTimer: number | null = null
|
|
|
+let manualTimeoutTimer: number | null = null
|
|
|
+
|
|
|
+const closeMaxTimeoutTimer = () => {
|
|
|
+ if (maxTimeoutTimer) {
|
|
|
+ clearTimeout(maxTimeoutTimer)
|
|
|
+ maxTimeoutTimer = null
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 检查 iframe 是否真正渲染完成
|
|
|
-const checkIframeFullyRendered = (): boolean => {
|
|
|
- const iframe = iframeRef.value
|
|
|
- if (!iframe) return false
|
|
|
-
|
|
|
- try {
|
|
|
- const doc = iframe.contentDocument || (iframe.contentWindow as Window)?.document
|
|
|
- if (!doc) return false
|
|
|
-
|
|
|
- // 检查文档状态
|
|
|
- if (doc.readyState !== 'complete') {
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- // 检查是否有实际内容
|
|
|
- if (!doc.body || !doc.body.innerHTML || doc.body.innerHTML.trim().length < 100) {
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- // 检查是否有可渲染的元素
|
|
|
- const hasVisibleContent = doc.body.querySelector('div, svg, canvas, img') !== null
|
|
|
- if (!hasVisibleContent) {
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- return true
|
|
|
- } catch (e) {
|
|
|
- // 跨域情况下,无法访问 document,说明 iframe 已加载但有跨域限制
|
|
|
- return true
|
|
|
+const closeManualTimeoutTimer = () => {
|
|
|
+ if (manualTimeoutTimer) {
|
|
|
+ clearTimeout(manualTimeoutTimer)
|
|
|
+ manualTimeoutTimer = null
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 等待 iframe 完全加载的定时器
|
|
|
-let renderCheckTimer: number | null = null
|
|
|
-let maxTimeoutTimer: number | null = null
|
|
|
+const onIframeLoad = () => {
|
|
|
+ closeMaxTimeoutTimer()
|
|
|
|
|
|
-// 轮询检查 iframe 是否渲染完成
|
|
|
-const startRenderCheck = () => {
|
|
|
- // 清除之前的定时器
|
|
|
- if (renderCheckTimer) clearInterval(renderCheckTimer)
|
|
|
- if (maxTimeoutTimer) clearTimeout(maxTimeoutTimer)
|
|
|
+ const iframe = iframeRef.value
|
|
|
+ // const tempUser = sessionStorage.getItem('tempUser')
|
|
|
+ // if (tempUser && iframe?.contentWindow) {
|
|
|
+ // try {
|
|
|
+ // const user = JSON.parse(tempUser)
|
|
|
+ // iframe.contentWindow.postMessage(
|
|
|
+ // {
|
|
|
+ // type: 'AUTO_LOGIN',
|
|
|
+ // username: user.username,
|
|
|
+ // password: user.password
|
|
|
+ // },
|
|
|
+ // CONFIG.ONEMAP_URL
|
|
|
+ // )
|
|
|
+ // } catch (err) {
|
|
|
+ // }
|
|
|
+ // }
|
|
|
|
|
|
- // 轮询检查
|
|
|
- renderCheckTimer = window.setInterval(() => {
|
|
|
- const elapsed = Date.now() - loadStartTime.value
|
|
|
-
|
|
|
- // 至少等待最小加载时间
|
|
|
- if (elapsed < minLoadTime) return
|
|
|
-
|
|
|
- // 检查是否渲染完成
|
|
|
- if (checkIframeFullyRendered()) {
|
|
|
- isIframeReady.value = true
|
|
|
- closeLoading()
|
|
|
- if (renderCheckTimer) clearInterval(renderCheckTimer)
|
|
|
- } else {
|
|
|
+ if (iframe?.contentWindow) {
|
|
|
+ try {
|
|
|
+ iframe.contentWindow.postMessage(
|
|
|
+ {
|
|
|
+ type: 'AUTO_LOGIN',
|
|
|
+ token: localStorage.getItem('token'),
|
|
|
+ },
|
|
|
+ CONFIG.ONEMAP_URL
|
|
|
+ )
|
|
|
+ } catch (err) {
|
|
|
+
|
|
|
}
|
|
|
- }, 500) // 每 500ms 检查一次
|
|
|
-
|
|
|
- // 设置最大超时
|
|
|
- maxTimeoutTimer = window.setTimeout(() => {
|
|
|
- isIframeReady.value = true
|
|
|
- closeLoading()
|
|
|
- if (renderCheckTimer) clearInterval(renderCheckTimer)
|
|
|
- }, maxLoadTime)
|
|
|
-}
|
|
|
-
|
|
|
-// iframe onload 事件处理
|
|
|
-const onIframeLoad = () => {
|
|
|
- // 发送登录信息
|
|
|
- const iframe = iframeRef.value
|
|
|
- const tempUser = sessionStorage.getItem('tempUser')
|
|
|
- if (tempUser && iframe) {
|
|
|
- const user = JSON.parse(tempUser)
|
|
|
- iframe.contentWindow?.postMessage(
|
|
|
- {
|
|
|
- type: 'AUTO_LOGIN',
|
|
|
- username: user.username,
|
|
|
- password: user.password
|
|
|
- },
|
|
|
- CONFIG.ONEMAP_URL
|
|
|
- )
|
|
|
}
|
|
|
-
|
|
|
- // 开始轮询检查渲染状态
|
|
|
- startRenderCheck()
|
|
|
+
|
|
|
+ // 文档 load 完成先显示 iframe,瓦片继续后台加载
|
|
|
+ isIframeReady.value = true
|
|
|
}
|
|
|
|
|
|
-const reloadIframe = () => {
|
|
|
- // 重置状态
|
|
|
+/**
|
|
|
+ * 刷新 iframe
|
|
|
+ * @param hardRefresh true 强制销毁 DOM(解决缓存异常时使用)
|
|
|
+ */
|
|
|
+const reloadIframe = (hardRefresh = false) => {
|
|
|
isIframeReady.value = false
|
|
|
- loadStartTime.value = Date.now()
|
|
|
-
|
|
|
- // 通过改变 key 强制 iframe 重新渲染
|
|
|
- iframeKey.value++
|
|
|
-
|
|
|
- // 同时更新 src 确保重新加载(添加时间戳避免缓存)
|
|
|
- iframeSrc.value = CONFIG.ONEMAP_URL + (CONFIG.ONEMAP_URL.includes('?') ? '&' : '?') + 't=' + Date.now()
|
|
|
+ closeMaxTimeoutTimer()
|
|
|
+ closeManualTimeoutTimer()
|
|
|
+
|
|
|
+ const timestamp = 't=' + Date.now()
|
|
|
+ const sep = CONFIG.ONEMAP_URL.includes('?') ? '&' : '?'
|
|
|
+ const newSrc = CONFIG.ONEMAP_URL + sep + timestamp
|
|
|
+
|
|
|
+ if (hardRefresh) {
|
|
|
+ iframeKey.value++
|
|
|
+ }
|
|
|
+ iframeSrc.value = newSrc
|
|
|
+ loadingText.value = '加载地图中...'
|
|
|
+
|
|
|
+ // 超时兜底:防止 load 事件因跨域资源卡死而永不触发
|
|
|
+ maxTimeoutTimer = window.setTimeout(() => {
|
|
|
+ // 超时后还没 ready,尝试自动重试(最多 maxRetryCount 次)
|
|
|
+ if (retryCount.value < maxRetryCount) {
|
|
|
+ retryCount.value++
|
|
|
+ loadingText.value = `加载超时,第 ${retryCount.value} 次重试...`
|
|
|
+ reloadIframe(true)
|
|
|
+ } else {
|
|
|
+ // 重试耗尽,强制显示 iframe(即使是空白也让用户看到手动重试按钮)
|
|
|
+ isIframeReady.value = true
|
|
|
+ }
|
|
|
+ }, maxLoadTime)
|
|
|
}
|
|
|
|
|
|
-// 监听子页面发送的消息
|
|
|
+// 接收子页面消息(如果未来子页面支持 IFRAME_READY 消息可直接复用)
|
|
|
const handleMessage = (event: MessageEvent) => {
|
|
|
- // 验证消息来源
|
|
|
- const expectedOrigin = new URL(CONFIG.ONEMAP_URL).origin
|
|
|
- if (event.origin !== expectedOrigin) {
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- // 如果子页面发送加载完成的消息,立即关闭 loading
|
|
|
- if (event.data && event.data.type === 'IFRAME_READY') {
|
|
|
- isIframeReady.value = true
|
|
|
- closeLoading()
|
|
|
- if (renderCheckTimer) clearInterval(renderCheckTimer)
|
|
|
- if (maxTimeoutTimer) clearTimeout(maxTimeoutTimer)
|
|
|
+ try {
|
|
|
+ const expectedOrigin = new URL(CONFIG.ONEMAP_URL).origin
|
|
|
+ if (event.origin !== expectedOrigin) return
|
|
|
+ if (event.data?.type === 'IFRAME_READY') {
|
|
|
+ isIframeReady.value = true
|
|
|
+ closeMaxTimeoutTimer()
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
onMounted(() => {
|
|
|
- // 只在容器存在时显示加载
|
|
|
- if (iframeContainer.value) {
|
|
|
- loadingInstance.value = ElLoading.service({
|
|
|
- target: iframeContainer.value,
|
|
|
- lock: true,
|
|
|
- text: 'Loading',
|
|
|
- background: 'rgba(0, 0, 0, 0.7)',
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- // 记录开始时间
|
|
|
- loadStartTime.value = Date.now()
|
|
|
-
|
|
|
- // 强制重新加载 iframe
|
|
|
- reloadIframe()
|
|
|
-
|
|
|
- // 监听子页面消息
|
|
|
+ reloadIframe(false)
|
|
|
window.addEventListener('message', handleMessage)
|
|
|
-
|
|
|
- // 页面刷新前的清理
|
|
|
- window.addEventListener('beforeunload', () => {
|
|
|
- closeLoading()
|
|
|
- if (renderCheckTimer) clearInterval(renderCheckTimer)
|
|
|
- if (maxTimeoutTimer) clearTimeout(maxTimeoutTimer)
|
|
|
- })
|
|
|
})
|
|
|
|
|
|
-// 监听 key 变化,重置加载状态
|
|
|
-watch(iframeKey, () => {
|
|
|
- loadStartTime.value = Date.now()
|
|
|
+onUnmounted(() => {
|
|
|
+ closeMaxTimeoutTimer()
|
|
|
+ closeManualTimeoutTimer()
|
|
|
+ window.removeEventListener('message', handleMessage)
|
|
|
})
|
|
|
-
|
|
|
-
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
@@ -208,22 +170,58 @@ watch(iframeKey, () => {
|
|
|
border-radius: 10px;
|
|
|
}
|
|
|
|
|
|
-.card-head {
|
|
|
- padding: 12px 16px;
|
|
|
- border-bottom: 1px solid #e8ecef;
|
|
|
- font-weight: 600;
|
|
|
- font-size: 14px;
|
|
|
-}
|
|
|
-
|
|
|
.card-body {
|
|
|
padding: 0;
|
|
|
}
|
|
|
|
|
|
.gis-container {
|
|
|
+ position: relative;
|
|
|
display: flex;
|
|
|
- height: calc(100vh - (170px - 108px));
|
|
|
- /* height: calc(100vh - (170px - 46px));
|
|
|
- border-radius: 10px; */
|
|
|
+ height: calc(100vh - (170px - 102px));
|
|
|
+ /* 深底色盖住 iframe load 前可能出现的空白闪烁 */
|
|
|
+ background: #1a2a3a;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+/* iframe 加载中的遮罩层 */
|
|
|
+.iframe-loading {
|
|
|
+ position: absolute;
|
|
|
+ inset: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 16px;
|
|
|
+ background: #1a2a3a;
|
|
|
+ z-index: 10;
|
|
|
+}
|
|
|
+
|
|
|
+.iframe-loading-spinner {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ border: 3px solid rgba(255, 255, 255, 0.2);
|
|
|
+ border-top-color: #409eff;
|
|
|
+ border-radius: 50%;
|
|
|
+ animation: spin 0.8s linear infinite;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes spin {
|
|
|
+ to {
|
|
|
+ transform: rotate(360deg);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.iframe-loading-text {
|
|
|
+ color: #c0ccda;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.iframe-loading-retry {
|
|
|
+ color: #909399;
|
|
|
+ font-size: 13px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
}
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
@@ -231,13 +229,5 @@ watch(iframeKey, () => {
|
|
|
flex-direction: column;
|
|
|
height: auto;
|
|
|
}
|
|
|
- .gis-map {
|
|
|
- height: 300px;
|
|
|
- }
|
|
|
- .gis-sidebar {
|
|
|
- width: 100%;
|
|
|
- border-left: none;
|
|
|
- border-top: 1px solid #e8ecef;
|
|
|
- }
|
|
|
}
|
|
|
</style>
|