内嵌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:
otis
2024-01-22 11:10:36 +08:00
committed by GitHub
parent 0887dd46d5
commit b13d745474
5 changed files with 198 additions and 56 deletions

View File

@@ -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,64 +90,75 @@ const transitionMain = defineComponent({
>
<router-view>
<template #default="{ Component, route }">
<el-scrollbar
v-if="props.fixedHeader"
:wrap-style="{
display: 'flex',
'flex-wrap': 'wrap'
}"
:view-style="{
display: 'flex',
flex: 'auto',
overflow: 'auto',
'flex-direction': 'column'
}"
>
<el-backtop title="回到顶部" target=".app-main .el-scrollbar__wrap">
<backTop />
</el-backtop>
<div class="grow">
<transitionMain :route="route">
<keep-alive
v-if="isKeepAlive"
:include="usePermissionStoreHook().cachePageList"
<KeepAliveFrame :currComp="Component" :currRoute="route">
<template #default="{ Comp, fullPath, frameInfo }">
<el-scrollbar
v-if="props.fixedHeader"
:wrap-style="{
display: 'flex',
'flex-wrap': 'wrap'
}"
:view-style="{
display: 'flex',
flex: 'auto',
overflow: 'auto',
'flex-direction': 'column'
}"
>
<el-backtop
title="回到顶部"
target=".app-main .el-scrollbar__wrap"
>
<backTop />
</el-backtop>
<div class="grow">
<transitionMain :route="route">
<keep-alive
v-if="isKeepAlive"
:include="usePermissionStoreHook().cachePageList"
>
<component
:is="Comp"
:key="fullPath"
:frameInfo="frameInfo"
class="main-content"
/>
</keep-alive>
<component
:is="Comp"
v-else
:key="fullPath"
:frameInfo="frameInfo"
class="main-content"
/>
</transitionMain>
</div>
<Footer v-if="!hideFooter" />
</el-scrollbar>
<div v-else class="grow">
<transitionMain :route="route">
<keep-alive
v-if="isKeepAlive"
:include="usePermissionStoreHook().cachePageList"
>
<component
:is="Comp"
:key="fullPath"
:frameInfo="frameInfo"
class="main-content"
/>
</keep-alive>
<component
:is="Component"
:key="route.fullPath"
:is="Comp"
v-else
:key="fullPath"
:frameInfo="frameInfo"
class="main-content"
/>
</keep-alive>
<component
:is="Component"
v-else
:key="route.fullPath"
class="main-content"
/>
</transitionMain>
</div>
<Footer v-if="!hideFooter" />
</el-scrollbar>
<div v-else class="grow">
<transitionMain :route="route">
<keep-alive
v-if="isKeepAlive"
:include="usePermissionStoreHook().cachePageList"
>
<component
:is="Component"
:key="route.fullPath"
class="main-content"
/>
</keep-alive>
<component
:is="Component"
v-else
:key="route.fullPath"
class="main-content"
/>
</transitionMain>
</div>
</transitionMain>
</div>
</template>
</KeepAliveFrame>
</template>
</router-view>

View 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>

View 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
};
};

View File

@@ -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();
});