feat:修复页脚问题

This commit is contained in:
liulong 2026-05-26 21:42:20 +08:00
parent c10dc212b0
commit 5b6600be63
4 changed files with 329 additions and 184 deletions

View File

@ -474,10 +474,16 @@ export default {
} }
}, },
updateIframeUrl(menus) { updateIframeUrl(menus) {
if (!menus || !this.kxurl) return; if (!menus) return;
menus.forEach(menu => { menus.forEach(menu => {
if (menu.path) { if (menu.path) {
menu.iframeUrl = `${this.kxurl}${menu.path}`; // /web/ / nginx便 iframe
const portalEmbed = menu.iframeUrl && String(menu.iframeUrl).startsWith('/web/');
if (portalEmbed) {
menu.iframeUrl = `/web${menu.path}`;
} else if (this.kxurl) {
menu.iframeUrl = `${this.kxurl}${menu.path}`;
}
} }
if (menu.child && menu.child.length > 0) { if (menu.child && menu.child.length > 0) {
this.updateIframeUrl(menu.child); this.updateIframeUrl(menu.child);

View File

@ -0,0 +1,65 @@
/**
* 碳证中心 iframe 地址归一化门户内嵌统一走同源 /web/ 反代 carbon.liantu.tech/web/...
*/
export function normalizeTzzxPageUrl(page) {
if (!page || typeof page !== 'string') return '';
let url = page.trim();
if (!url) return '';
if (url.startsWith('http://') || url.startsWith('https://')) {
try {
const parsed = new URL(url);
if (parsed.origin === window.location.origin) {
url = parsed.pathname + parsed.search + parsed.hash;
}
} catch (e) {
return url;
}
}
if (url.startsWith('/web/')) {
return url;
}
const kxtfwzxMarker = '/view/kxtfwzx';
const kxtIdx = url.indexOf(kxtfwzxMarker);
if (kxtIdx !== -1) {
const rest = url.slice(kxtIdx + kxtfwzxMarker.length);
return `/web${rest.startsWith('/') ? rest : `/${rest}`}`;
}
const carbonPathMatch = url.match(/\/(carbon[\w-]*|trustedCarbon[\w-/]*)(?:\?.*)?$/i);
if (carbonPathMatch) {
const pathStart = url.indexOf(carbonPathMatch[0]);
const subPath = url.slice(pathStart);
return subPath.startsWith('/web/') ? subPath : `/web${subPath.startsWith('/') ? subPath : `/${subPath}`}`;
}
if (url.startsWith('/') && !url.startsWith('/web/')) {
return `/web${url}`;
}
return url;
}
export function getViewportIframeFallbackHeight() {
const navOffset = parseInt(
getComputedStyle(document.documentElement).getPropertyValue('--page-offset-top'),
10
);
const offset = Number.isFinite(navOffset) ? navOffset : 76;
return Math.max(window.innerHeight - offset, 480);
}
export function isAllowedIframeMessageOrigin(origin) {
if (!origin) return false;
if (origin === window.location.origin) return true;
try {
const host = window.location.hostname;
if (host === 'localhost' || host === '127.0.0.1') return true;
} catch (e) {
return false;
}
return false;
}

View File

@ -128,10 +128,10 @@ export default {
window.location.href = `/view/mhzc/login`; window.location.href = `/view/mhzc/login`;
return; return;
} }
// this.iframeUrl = iframeUrl;
this.$router.push({ this.$router.push({
path: `/tzzx?page=${iframeUrl}` path: '/tzzx',
}) query: { page: iframeUrl },
});
}, },

View File

@ -1,179 +1,253 @@
<template> <template>
<div class="tzzx-page"> <div class="tzzx-page">
<div v-if="loading" class="loading">加载中...</div> <div v-if="loading" class="loading">加载中...</div>
<template v-else-if="iframeUrl"> <template v-else-if="iframeUrl">
<iframe <iframe
ref="tzzxIframe" ref="tzzxIframe"
:src="iframeUrl" :src="iframeUrl"
width="100%" class="tzzx-iframe"
:height="iframeHeight" frameborder="0"
frameborder="0" :scrolling="iframeScrolling"
scrolling="no" :style="iframeStyle"
@load="onIframeLoad" @load="onIframeLoad"
></iframe> ></iframe>
<Footer /> </template>
</template> <div v-else class="empty">链接错误</div>
<div v-else class="empty">链接错误</div> </div>
</div> </template>
</template>
<script>
<script> import {
import Footer from '@/pages/index/components/footer/index.vue'; normalizeTzzxPageUrl,
getViewportIframeFallbackHeight,
export default { isAllowedIframeMessageOrigin,
name: 'tzzx', } from '@/pages/index/utils/tzzx-iframe';
components: { Footer },
data() { const DEFAULT_IFRAME_HEIGHT = 800;
return {
iframeUrl: '', export default {
loading: true, name: 'tzzx',
iframeHeight: 800, data() {
resizeObserver: null, return {
heightCheckTimer: null, iframeUrl: '',
}; loading: true,
}, iframeHeight: DEFAULT_IFRAME_HEIGHT,
mounted() { iframeScrolling: 'no',
this.fetchPage(); heightMode: 'default',
this.$watch(() => this.$route.query, () => this.fetchPage()); resizeObserver: null,
}, heightCheckTimer: null,
beforeDestroy() { _messageHandler: null,
this.cleanup(); _iframeLoadGeneration: 0,
}, };
methods: { },
fetchPage() { computed: {
const { page } = this.$route.query; iframeStyle() {
if (page) { return {
this.iframeUrl = page; width: '100%',
this.loading = false; height: `${this.iframeHeight}px`,
} else { border: 'none',
this.iframeUrl = ''; display: 'block',
this.loading = false; };
} },
}, },
mounted() {
onIframeLoad() { this.fetchPage();
const iframe = this.$refs.tzzxIframe; this.$watch(() => this.$route.query.page, () => this.fetchPage());
if (!iframe) return; window.addEventListener('resize', this.onWindowResize);
},
const isSameOrigin = this.trySameOrigin(iframe); activated() {
this.fetchPage();
if (!isSameOrigin) { },
this.setupPostMessage(iframe); beforeDestroy() {
} window.removeEventListener('resize', this.onWindowResize);
}, this.cleanup();
},
trySameOrigin(iframe) { deactivated() {
try { this.cleanup();
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; },
methods: {
if (!iframeDoc || !iframeDoc.body) { fetchPage() {
return false; const rawPage = this.$route.query.page;
} const page = Array.isArray(rawPage) ? rawPage[0] : rawPage;
const nextUrl = normalizeTzzxPageUrl(page);
const updateHeight = () => {
const height = Math.max( if (nextUrl === this.iframeUrl && !this.loading) {
iframeDoc.body.scrollHeight, return;
iframeDoc.body.offsetHeight, }
iframeDoc.documentElement.scrollHeight,
iframeDoc.documentElement.offsetHeight this.cleanup();
); this.resetIframeMetrics();
if (height > 0) {
this.iframeHeight = height + 20; if (nextUrl) {
} this.iframeUrl = nextUrl;
}; this.loading = false;
} else {
updateHeight(); this.iframeUrl = '';
this.loading = false;
if (window.ResizeObserver) { }
this.resizeObserver = new ResizeObserver(updateHeight); },
this.resizeObserver.observe(iframeDoc.body);
this.resizeObserver.observe(iframeDoc.documentElement); resetIframeMetrics() {
} else { this.iframeHeight = DEFAULT_IFRAME_HEIGHT;
this.heightCheckTimer = setInterval(updateHeight, 500); this.iframeScrolling = 'no';
} this.heightMode = 'default';
this._iframeLoadGeneration += 1;
const images = iframeDoc.querySelectorAll('img'); },
images.forEach(img => {
if (!img.complete) { onWindowResize() {
img.addEventListener('load', updateHeight); if (this.heightMode === 'viewport-fallback') {
img.addEventListener('error', updateHeight); this.iframeHeight = getViewportIframeFallbackHeight();
} }
}); },
return true; onIframeLoad() {
} catch (e) { const iframe = this.$refs.tzzxIframe;
console.log('[tzzx] 检测到跨域iframe将使用postMessage方案'); if (!iframe) return;
return false;
} const loadGeneration = this._iframeLoadGeneration;
}, this.cleanupObserversOnly();
setupPostMessage(iframe) { const isSameOrigin = this.trySameOrigin(iframe, loadGeneration);
this._messageHandler = (event) => { if (!isSameOrigin) {
if (event.data && event.data.type === 'iframeHeight') { this.setupPostMessage(iframe, loadGeneration);
this.iframeHeight = event.data.height; }
} },
};
window.addEventListener('message', this._messageHandler); trySameOrigin(iframe, loadGeneration) {
try {
const requestHeight = () => { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
try { if (!iframeDoc || !iframeDoc.body) {
iframe.contentWindow.postMessage( return false;
{ type: 'REQUEST_HEIGHT' }, }
'*'
); const updateHeight = () => {
} catch (e) {} if (loadGeneration !== this._iframeLoadGeneration) return;
};
const height = Math.max(
requestHeight(); iframeDoc.body.scrollHeight,
iframeDoc.body.offsetHeight,
let retryCount = 0; iframeDoc.documentElement.scrollHeight,
this.heightCheckTimer = setInterval(() => { iframeDoc.documentElement.offsetHeight
requestHeight(); );
retryCount++; if (height > 0) {
if (retryCount > 5) { this.iframeHeight = height + 20;
clearInterval(this.heightCheckTimer); this.heightMode = 'same-origin';
this.heightCheckTimer = null; this.iframeScrolling = 'no';
} }
}, 1000); };
},
updateHeight();
cleanup() {
if (this.resizeObserver) { if (window.ResizeObserver) {
this.resizeObserver.disconnect(); this.resizeObserver = new ResizeObserver(updateHeight);
this.resizeObserver = null; this.resizeObserver.observe(iframeDoc.body);
} this.resizeObserver.observe(iframeDoc.documentElement);
if (this.heightCheckTimer) { } else {
clearInterval(this.heightCheckTimer); this.heightCheckTimer = setInterval(updateHeight, 500);
this.heightCheckTimer = null; }
}
if (this._messageHandler) { const images = iframeDoc.querySelectorAll('img');
window.removeEventListener('message', this._messageHandler); images.forEach((img) => {
this._messageHandler = null; if (!img.complete) {
} img.addEventListener('load', updateHeight);
}, img.addEventListener('error', updateHeight);
}, }
}; });
</script>
return true;
<style lang="less" scoped> } catch (e) {
.tzzx-page { console.log('[tzzx] 检测到跨域 iframe将使用 postMessage / 视口降级方案');
width: 100%; return false;
}
iframe { },
display: block;
width: 100%; setupPostMessage(iframe, loadGeneration) {
border: none; this._messageHandler = (event) => {
} if (loadGeneration !== this._iframeLoadGeneration) return;
if (!isAllowedIframeMessageOrigin(event.origin)) return;
.loading, if (event.source !== iframe.contentWindow) return;
.empty {
display: flex; const data = event.data;
align-items: center; if (!data || data.type !== 'iframeHeight') return;
justify-content: center;
width: 100%; const height = Number(data.height);
height: 100vh; if (!Number.isFinite(height) || height <= 0) return;
font-size: 16px;
color: #999; this.iframeHeight = height + 20;
} this.heightMode = 'post-message';
} this.iframeScrolling = 'no';
</style> };
window.addEventListener('message', this._messageHandler);
const requestHeight = () => {
if (loadGeneration !== this._iframeLoadGeneration) return;
try {
iframe.contentWindow.postMessage({ type: 'REQUEST_HEIGHT' }, '*');
} catch (e) {}
};
requestHeight();
let retryCount = 0;
this.heightCheckTimer = setInterval(() => {
requestHeight();
retryCount += 1;
if (retryCount >= 5) {
clearInterval(this.heightCheckTimer);
this.heightCheckTimer = null;
if (loadGeneration === this._iframeLoadGeneration && this.heightMode === 'default') {
this.applyViewportFallback();
}
}
}, 1000);
},
applyViewportFallback() {
this.iframeHeight = getViewportIframeFallbackHeight();
this.heightMode = 'viewport-fallback';
this.iframeScrolling = 'auto';
},
cleanupObserversOnly() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
this.resizeObserver = null;
}
if (this.heightCheckTimer) {
clearInterval(this.heightCheckTimer);
this.heightCheckTimer = null;
}
if (this._messageHandler) {
window.removeEventListener('message', this._messageHandler);
this._messageHandler = null;
}
},
cleanup() {
this.cleanupObserversOnly();
},
},
};
</script>
<style lang="less" scoped>
.tzzx-page {
width: 100%;
.tzzx-iframe {
display: block;
width: 100%;
}
.loading,
.empty {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
min-height: 480px;
font-size: 16px;
color: #999;
}
}
</style>