主内容区(AppMain)系统
简介
主内容区系统是整个应用的内容渲染核心,负责根据当前路由动态加载和显示页面组件。该系统由 AppMain.vue
(主内容容器)、ParentView.vue
(父级视图容器)和 iframe
目录下的内嵌页面组件构成,支持路由视图渲染、组件缓存、页面动画、外部页面内嵌等功能。
组件架构
AppMain/
├── AppMain.vue # 主内容容器组件
├── ParentView.vue # 父级视图容器组件
└── iframe/ # iframe内嵌页面系统
├── IframeToggle.vue # iframe切换管理组件
└── InnerLink.vue # 内部链接渲染组件
核心组件详解
AppMain.vue - 主内容容器
应用的主要内容渲染区域,负责路由视图的显示、组件缓存和iframe页面的集成。
组件结构
vue
<template>
<section class="app-main">
<el-scrollbar>
<!-- 路由视图部分 -->
<router-view v-slot="{ Component, route }">
<transition :enter-active-class="animate" mode="out-in">
<keep-alive :include="tagsViewStore.cachedViews">
<component :is="Component" v-if="!route.meta.link" :key="route.path" />
</keep-alive>
</transition>
</router-view>
<!-- iframe处理 -->
<IframeToggle />
</el-scrollbar>
</section>
</template>
核心功能实现
动画系统集成
typescript
// 初始化动画实例
const animation = useAnimation()
const animate = ref<string>('')
// 监听动画启用状态变化
watch(
() => themeStore.animationEnable,
(val: boolean) => {
animate.value = val ? animation.getRandomAnimation() : animation.defaultAnimate
},
{ immediate: true }
)
外部链接路由处理
typescript
// 处理外部链接类型的路由
watchEffect(() => {
if (route.meta.link) {
useTagsViewStore().addIframeView(route)
}
})
组件缓存机制
vue
<keep-alive :include="tagsViewStore.cachedViews">
<component :is="Component" v-if="!route.meta.link" :key="route.path" />
</keep-alive>
ParentView.vue - 父级视图容器
用于嵌套路由的简单容器组件,不添加任何额外的UI元素。
vue
<template>
<router-view />
</template>
<script setup lang="ts" name="ParentView">
/**
* 父级视图组件
*
* 使用场景:
* - 在路由配置中作为父级路由的组件
* - 允许子路由内容直接渲染,不添加额外的包装元素
* - 适用于需要多级路由但不需要父级组件添加UI的情况
*/
</script>
使用场景
typescript
// 路由配置示例
{
path: '/system',
component: ParentView, // 使用ParentView作为父级容器
meta: {
title: '系统管理',
icon: 'system'
},
children: [
{
path: 'user',
component: () => import('@/views/system/user/index.vue'),
meta: { title: '用户管理' }
}
]
}
iframe内嵌页面系统
IframeToggle.vue - iframe切换管理
根据当前路由显示对应的iframe页面,支持多标签页切换和URL参数处理。
组件结构
vue
<template>
<!--
iframe 视图切换组件
根据当前路由显示对应的 iframe 页面,支持多标签页切换
-->
<InnerLink
v-for="(item, index) in tagsViewStore.iframeViews"
v-show="route.path === item.path"
:key="item.path"
:iframe-id="`iframe${index}`"
:src="buildIframeUrl(item.meta?.link, item.query)"
/>
</template>
核心功能实现
URL构建算法
typescript
/**
* 构建 iframe 的完整 URL
*
* @param baseUrl - 基础 URL,来自路由 meta.link
* @param queryParams - 路由查询参数对象
* @returns 拼接查询参数后的完整 URL
*/
const buildIframeUrl = (baseUrl: string | undefined, queryParams: Record<string, any>): string | undefined => {
// 如果基础 URL 为空,直接返回
if (!baseUrl) {
return baseUrl
}
// 使用工具类方法生成查询字符串
const queryString = objectToQuery(queryParams || {})
if (!queryString) {
return baseUrl
}
// 检查原 URL 是否已包含查询参数
const separator = baseUrl.includes('?') ? '&' : '?'
return `${baseUrl}${separator}${queryString}`
}
InnerLink.vue - 内部链接渲染
实际渲染iframe的组件,负责iframe的显示和尺寸自适应。
vue
<template>
<div :style="'height:' + height">
<iframe :id="iframeId" style="width: 100%; height: 100%; border: 0" :src="src"></iframe>
</div>
</template>
响应式高度计算
typescript
const height = ref('')
/**
* 计算并设置 iframe 高度
* 减去顶部导航栏等固定区域的高度
*/
const calculateHeight = () => {
height.value = `${document.documentElement.clientHeight - 94.5}px`
}
// 在组件挂载时计算初始高度
onMounted(() => {
calculateHeight()
// 监听窗口大小变化,动态调整高度
window.addEventListener('resize', calculateHeight)
})
// 组件卸载时移除事件监听,防止内存泄漏
onUnmounted(() => {
window.removeEventListener('resize', calculateHeight)
})
路由集成与缓存策略
路由视图渲染
vue
<router-view v-slot="{ Component, route }">
<transition :enter-active-class="animate" mode="out-in">
<keep-alive :include="tagsViewStore.cachedViews">
<!-- 常规组件渲染 -->
<component :is="Component" v-if="!route.meta.link" :key="route.path" />
</keep-alive>
</transition>
</router-view>
<!-- 外部链接iframe渲染 -->
<IframeToggle />
组件缓存机制
typescript
// TagsViewStore中的缓存管理
export const useTagsViewStore = defineStore('tagsView', () => {
// 缓存的视图组件名称列表
const cachedViews = ref<string[]>([])
// 添加缓存视图
const addCachedView = (view: RouteLocationNormalized) => {
if (view.name && !cachedViews.value.includes(view.name as string)) {
if (!view.meta.noCache) {
cachedViews.value.push(view.name as string)
}
}
}
return {
cachedViews,
addCachedView
}
})
路由配置示例
typescript
// 常规路由配置
{
path: '/dashboard',
name: 'Dashboard', // 用于组件缓存
component: () => import('@/views/dashboard/index.vue'),
meta: {
title: '仪表板',
icon: 'dashboard',
noCache: false // 启用缓存
}
}
// 外部链接路由配置
{
path: '/external-docs',
name: 'ExternalDocs',
meta: {
title: '外部文档',
link: 'https://docs.example.com', // 标识为外部链接
icon: 'documentation'
}
}
// 父级路由配置
{
path: '/system',
component: ParentView, // 使用ParentView作为容器
meta: {
title: '系统管理',
icon: 'system'
},
children: [
// 子路由配置
]
}
动画系统
页面切换动画
typescript
// 动画配置管理
const animation = useAnimation()
const animate = ref<string>('')
// 监听动画开关变化
watch(
() => themeStore.animationEnable,
(val: boolean) => {
animate.value = val ? animation.getRandomAnimation() : animation.defaultAnimate
}
)
响应式适配
容器高度自适应
scss
.app-main {
/* 计算内容区域高度 */
min-height: calc(100vh - 50px);
width: 100%;
position: relative;
overflow: hidden;
background-color: var(--app-bg);
}
/* 当启用标签页视图时的调整 */
.hasTagsView {
.app-main {
min-height: calc(100vh - 84px);
}
}
错误处理
iframe加载错误处理
typescript
const handleIframeError = (iframeId: string, src: string) => {
console.error(`Failed to load iframe: ${iframeId}, src: ${src}`)
// 显示错误提示
ElMessage.error('页面加载失败,请检查网络连接或联系管理员')
// 尝试重新加载
setTimeout(() => {
const iframe = document.getElementById(iframeId) as HTMLIFrameElement
if (iframe) {
iframe.src = src
}
}, 3000)
}
最佳实践
推荐做法
合理使用组件缓存
typescript// 对于数据变化频繁的页面,禁用缓存 { meta: { noCache: true, title: '实时监控' } }
iframe安全配置
html<iframe sandbox="allow-same-origin allow-scripts" referrerpolicy="strict-origin-when-cross-origin" >
优化页面切换动画
typescript// 根据设备性能调整动画 const shouldUseAnimation = computed(() => { return !window.matchMedia('(prefers-reduced-motion: reduce)').matches })
避免做法
过度使用keep-alive
typescript// ❌ 缓存所有组件 const cachedViews = ref(['*']) // ✅ 选择性缓存 const cachedViews = computed(() => tagsViewStore.visitedViews .filter(view => !view.meta.noCache) .map(view => view.name) )
忽略iframe安全
html<!-- ❌ 不设置安全策略 --> <iframe src="http://untrusted-site.com"></iframe> <!-- ✅ 设置安全策略 --> <iframe src="https://trusted-site.com" sandbox="allow-same-origin" ></iframe>
性能监控
组件渲染性能
typescript
// 监控组件渲染时间
const measureRenderTime = () => {
const startTime = performance.now()
nextTick(() => {
const endTime = performance.now()
const renderTime = endTime - startTime
if (renderTime > 100) {
console.warn(`Slow component render: ${renderTime}ms`)
}
})
}
总结
主内容区系统作为应用的核心渲染区域,通过 AppMain
、ParentView
和 iframe 组件的协作,提供了完整的内容展示解决方案。系统支持路由视图渲染、组件缓存、页面动画、外部页面内嵌等功能,为用户提供了流畅的浏览体验。合理配置缓存策略、动画效果和安全策略,能够在保证用户体验的同时确保应用的安全性和性能。