Skip to content

主内容区(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}`
}

实际渲染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)
}

最佳实践

推荐做法

  1. 合理使用组件缓存

    typescript
    // 对于数据变化频繁的页面,禁用缓存
    {
      meta: { 
        noCache: true,
        title: '实时监控'
      }
    }
  2. iframe安全配置

    html
    <iframe 
      sandbox="allow-same-origin allow-scripts"
      referrerpolicy="strict-origin-when-cross-origin"
    >
  3. 优化页面切换动画

    typescript
    // 根据设备性能调整动画
    const shouldUseAnimation = computed(() => {
      return !window.matchMedia('(prefers-reduced-motion: reduce)').matches
    })

避免做法

  1. 过度使用keep-alive

    typescript
    // ❌ 缓存所有组件
    const cachedViews = ref(['*'])
    
    // ✅ 选择性缓存
    const cachedViews = computed(() => 
      tagsViewStore.visitedViews
        .filter(view => !view.meta.noCache)
        .map(view => view.name)
    )
  2. 忽略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`)
    }
  })
}

总结

主内容区系统作为应用的核心渲染区域,通过 AppMainParentView 和 iframe 组件的协作,提供了完整的内容展示解决方案。系统支持路由视图渲染、组件缓存、页面动画、外部页面内嵌等功能,为用户提供了流畅的浏览体验。合理配置缓存策略、动画效果和安全策略,能够在保证用户体验的同时确保应用的安全性和性能。