mirror of
				https://github.com/pure-admin/vue-pure-admin.git
				synced 2025-11-03 13:44:47 +08:00 
			
		
		
		
	内嵌iframe页支持设置keepAlive,保持页面状态 (#873)
				
					
				
			* pref: keep alive iframe * pref: default maxCount is 10 item * pref: 渲染iframe时,移除默认的slot渲染frameView组件 * perf: fix frame reload error after hmr * perf: 通过路由配置keepAlive frame * perf: refresh keep alive iframe
This commit is contained in:
		
							parent
							
								
									0887dd46d5
								
							
						
					
					
						commit
						b13d745474
					
				@ -118,6 +118,7 @@ const frameRouter = {
 | 
			
		||||
          meta: {
 | 
			
		||||
            title: "menus.hsEpDocument",
 | 
			
		||||
            frameSrc: "https://element-plus.org/zh-CN/",
 | 
			
		||||
            keepAlive: true,
 | 
			
		||||
            roles: ["admin", "common"]
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
@ -127,6 +128,7 @@ const frameRouter = {
 | 
			
		||||
          meta: {
 | 
			
		||||
            title: "menus.hsTailwindcssDocument",
 | 
			
		||||
            frameSrc: "https://tailwindcss.com/docs/installation",
 | 
			
		||||
            keepAlive: true,
 | 
			
		||||
            roles: ["admin", "common"]
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
@ -136,6 +138,7 @@ const frameRouter = {
 | 
			
		||||
          meta: {
 | 
			
		||||
            title: "menus.hsVueDocument",
 | 
			
		||||
            frameSrc: "https://cn.vuejs.org/",
 | 
			
		||||
            keepAlive: true,
 | 
			
		||||
            roles: ["admin", "common"]
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
@ -145,6 +148,7 @@ const frameRouter = {
 | 
			
		||||
          meta: {
 | 
			
		||||
            title: "menus.hsViteDocument",
 | 
			
		||||
            frameSrc: "https://cn.vitejs.dev/",
 | 
			
		||||
            keepAlive: true,
 | 
			
		||||
            roles: ["admin", "common"]
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
@ -154,6 +158,7 @@ const frameRouter = {
 | 
			
		||||
          meta: {
 | 
			
		||||
            title: "menus.hsPiniaDocument",
 | 
			
		||||
            frameSrc: "https://pinia.vuejs.org/zh/index.html",
 | 
			
		||||
            keepAlive: true,
 | 
			
		||||
            roles: ["admin", "common"]
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
@ -163,6 +168,7 @@ const frameRouter = {
 | 
			
		||||
          meta: {
 | 
			
		||||
            title: "menus.hsRouterDocument",
 | 
			
		||||
            frameSrc: "https://router.vuejs.org/zh/",
 | 
			
		||||
            keepAlive: true,
 | 
			
		||||
            roles: ["admin", "common"]
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import Footer from "./footer/index.vue";
 | 
			
		||||
import { useGlobal } from "@pureadmin/utils";
 | 
			
		||||
import KeepAliveFrame from "./keepAliveFrame/index.vue";
 | 
			
		||||
import backTop from "@/assets/svg/back_top.svg?component";
 | 
			
		||||
import { h, computed, Transition, defineComponent } from "vue";
 | 
			
		||||
import { usePermissionStoreHook } from "@/store/modules/permission";
 | 
			
		||||
@ -89,6 +90,8 @@ const transitionMain = defineComponent({
 | 
			
		||||
  >
 | 
			
		||||
    <router-view>
 | 
			
		||||
      <template #default="{ Component, route }">
 | 
			
		||||
        <KeepAliveFrame :currComp="Component" :currRoute="route">
 | 
			
		||||
          <template #default="{ Comp, fullPath, frameInfo }">
 | 
			
		||||
            <el-scrollbar
 | 
			
		||||
              v-if="props.fixedHeader"
 | 
			
		||||
              :wrap-style="{
 | 
			
		||||
@ -102,7 +105,10 @@ const transitionMain = defineComponent({
 | 
			
		||||
                'flex-direction': 'column'
 | 
			
		||||
              }"
 | 
			
		||||
            >
 | 
			
		||||
          <el-backtop title="回到顶部" target=".app-main .el-scrollbar__wrap">
 | 
			
		||||
              <el-backtop
 | 
			
		||||
                title="回到顶部"
 | 
			
		||||
                target=".app-main .el-scrollbar__wrap"
 | 
			
		||||
              >
 | 
			
		||||
                <backTop />
 | 
			
		||||
              </el-backtop>
 | 
			
		||||
              <div class="grow">
 | 
			
		||||
@ -112,15 +118,17 @@ const transitionMain = defineComponent({
 | 
			
		||||
                    :include="usePermissionStoreHook().cachePageList"
 | 
			
		||||
                  >
 | 
			
		||||
                    <component
 | 
			
		||||
                  :is="Component"
 | 
			
		||||
                  :key="route.fullPath"
 | 
			
		||||
                      :is="Comp"
 | 
			
		||||
                      :key="fullPath"
 | 
			
		||||
                      :frameInfo="frameInfo"
 | 
			
		||||
                      class="main-content"
 | 
			
		||||
                    />
 | 
			
		||||
                  </keep-alive>
 | 
			
		||||
                  <component
 | 
			
		||||
                :is="Component"
 | 
			
		||||
                    :is="Comp"
 | 
			
		||||
                    v-else
 | 
			
		||||
                :key="route.fullPath"
 | 
			
		||||
                    :key="fullPath"
 | 
			
		||||
                    :frameInfo="frameInfo"
 | 
			
		||||
                    class="main-content"
 | 
			
		||||
                  />
 | 
			
		||||
                </transitionMain>
 | 
			
		||||
@ -134,20 +142,24 @@ const transitionMain = defineComponent({
 | 
			
		||||
                  :include="usePermissionStoreHook().cachePageList"
 | 
			
		||||
                >
 | 
			
		||||
                  <component
 | 
			
		||||
                :is="Component"
 | 
			
		||||
                :key="route.fullPath"
 | 
			
		||||
                    :is="Comp"
 | 
			
		||||
                    :key="fullPath"
 | 
			
		||||
                    :frameInfo="frameInfo"
 | 
			
		||||
                    class="main-content"
 | 
			
		||||
                  />
 | 
			
		||||
                </keep-alive>
 | 
			
		||||
                <component
 | 
			
		||||
              :is="Component"
 | 
			
		||||
                  :is="Comp"
 | 
			
		||||
                  v-else
 | 
			
		||||
              :key="route.fullPath"
 | 
			
		||||
                  :key="fullPath"
 | 
			
		||||
                  :frameInfo="frameInfo"
 | 
			
		||||
                  class="main-content"
 | 
			
		||||
                />
 | 
			
		||||
              </transitionMain>
 | 
			
		||||
            </div>
 | 
			
		||||
          </template>
 | 
			
		||||
        </KeepAliveFrame>
 | 
			
		||||
      </template>
 | 
			
		||||
    </router-view>
 | 
			
		||||
 | 
			
		||||
    <!-- 页脚 -->
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										79
									
								
								src/layout/components/keepAliveFrame/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/layout/components/keepAliveFrame/index.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,79 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { getConfig } from "@/config";
 | 
			
		||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
 | 
			
		||||
import { type Component, shallowRef, watch, computed } from "vue";
 | 
			
		||||
import { type RouteRecordRaw, RouteLocationNormalizedLoaded } from "vue-router";
 | 
			
		||||
import { useMultiFrame } from "@/layout/components/keepAliveFrame/useMultiFrame";
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  currRoute: RouteLocationNormalizedLoaded;
 | 
			
		||||
  currComp: Component;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const compList = shallowRef([]);
 | 
			
		||||
const { setMap, getMap, MAP, delMap } = useMultiFrame();
 | 
			
		||||
 | 
			
		||||
const keep = computed(() => {
 | 
			
		||||
  return (
 | 
			
		||||
    getConfig().KeepAlive &&
 | 
			
		||||
    props.currRoute.meta?.keepAlive &&
 | 
			
		||||
    !!props.currRoute.meta?.frameSrc
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
// 避免重新渲染 frameView
 | 
			
		||||
const normalComp = computed(() => !keep.value && props.currComp);
 | 
			
		||||
 | 
			
		||||
watch(useMultiTagsStoreHook().multiTags, (tags: any) => {
 | 
			
		||||
  if (!Array.isArray(tags) || !keep.value) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  const iframeTags = tags.filter(i => i.meta?.frameSrc);
 | 
			
		||||
  // tags必须是小于MAP,才是做了关闭动作,因为MAP插入的顺序在tags变化后发生
 | 
			
		||||
  if (iframeTags.length < MAP.size) {
 | 
			
		||||
    for (const i of MAP.keys()) {
 | 
			
		||||
      if (!tags.some(s => s.path === i)) {
 | 
			
		||||
        delMap(i);
 | 
			
		||||
        compList.value = getMap();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.currRoute.fullPath,
 | 
			
		||||
  path => {
 | 
			
		||||
    const multiTags = useMultiTagsStoreHook().multiTags as RouteRecordRaw[];
 | 
			
		||||
    const iframeTags = multiTags.filter(i => i.meta?.frameSrc);
 | 
			
		||||
    if (keep.value) {
 | 
			
		||||
      if (iframeTags.length !== MAP.size) {
 | 
			
		||||
        const sameKey = [...MAP.keys()].find(i => path === i);
 | 
			
		||||
        if (!sameKey) {
 | 
			
		||||
          // 添加缓存
 | 
			
		||||
          setMap(path, props.currComp);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (MAP.size > 0) {
 | 
			
		||||
      compList.value = getMap();
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    immediate: true
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <template v-for="[fullPath, Comp] in compList" :key="fullPath">
 | 
			
		||||
    <div v-show="fullPath === props.currRoute.fullPath" class="w-full h-full">
 | 
			
		||||
      <slot
 | 
			
		||||
        :fullPath="fullPath"
 | 
			
		||||
        :Comp="Comp"
 | 
			
		||||
        :frameInfo="{ frameSrc: currRoute.meta?.frameSrc, fullPath }"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  </template>
 | 
			
		||||
  <div v-show="!keep" class="w-full h-full">
 | 
			
		||||
    <slot :Comp="normalComp" :fullPath="props.currRoute.fullPath" frameInfo />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
							
								
								
									
										25
									
								
								src/layout/components/keepAliveFrame/useMultiFrame.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/layout/components/keepAliveFrame/useMultiFrame.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
const MAP = new Map();
 | 
			
		||||
 | 
			
		||||
export const useMultiFrame = () => {
 | 
			
		||||
  function setMap(path, Comp) {
 | 
			
		||||
    MAP.set(path, Comp);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getMap(path?) {
 | 
			
		||||
    if (path) {
 | 
			
		||||
      return MAP.get(path);
 | 
			
		||||
    }
 | 
			
		||||
    return [...MAP.entries()];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function delMap(path) {
 | 
			
		||||
    MAP.delete(path);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    setMap,
 | 
			
		||||
    getMap,
 | 
			
		||||
    delMap,
 | 
			
		||||
    MAP
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
@ -1,18 +1,24 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { useI18n } from "vue-i18n";
 | 
			
		||||
import { useRoute } from "vue-router";
 | 
			
		||||
import { ref, unref, onMounted, nextTick } from "vue";
 | 
			
		||||
import { ref, unref, watch, onMounted, nextTick } from "vue";
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "FrameView"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  frameInfo?: {
 | 
			
		||||
    frameSrc?: string;
 | 
			
		||||
    fullPath?: string;
 | 
			
		||||
  };
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const { t } = useI18n();
 | 
			
		||||
const loading = ref(true);
 | 
			
		||||
const currentRoute = useRoute();
 | 
			
		||||
const frameSrc = ref<string>("");
 | 
			
		||||
const frameRef = ref<HTMLElement | null>(null);
 | 
			
		||||
 | 
			
		||||
if (unref(currentRoute.meta)?.frameSrc) {
 | 
			
		||||
  frameSrc.value = unref(currentRoute.meta)?.frameSrc as string;
 | 
			
		||||
}
 | 
			
		||||
@ -39,6 +45,20 @@ function init() {
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => currentRoute.fullPath,
 | 
			
		||||
  path => {
 | 
			
		||||
    if (props.frameInfo?.fullPath === path) {
 | 
			
		||||
      frameSrc.value = props.frameInfo?.frameSrc;
 | 
			
		||||
    }
 | 
			
		||||
    // 重新加载
 | 
			
		||||
    if (path.indexOf("/redirect/") > -1) {
 | 
			
		||||
      frameSrc.value = props.frameInfo?.fullPath;
 | 
			
		||||
      loading.value = true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  init();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user