内嵌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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 198 additions and 56 deletions

View File

@ -118,6 +118,7 @@ const frameRouter = {
meta: { meta: {
title: "menus.hsEpDocument", title: "menus.hsEpDocument",
frameSrc: "https://element-plus.org/zh-CN/", frameSrc: "https://element-plus.org/zh-CN/",
keepAlive: true,
roles: ["admin", "common"] roles: ["admin", "common"]
} }
}, },
@ -127,6 +128,7 @@ const frameRouter = {
meta: { meta: {
title: "menus.hsTailwindcssDocument", title: "menus.hsTailwindcssDocument",
frameSrc: "https://tailwindcss.com/docs/installation", frameSrc: "https://tailwindcss.com/docs/installation",
keepAlive: true,
roles: ["admin", "common"] roles: ["admin", "common"]
} }
}, },
@ -136,6 +138,7 @@ const frameRouter = {
meta: { meta: {
title: "menus.hsVueDocument", title: "menus.hsVueDocument",
frameSrc: "https://cn.vuejs.org/", frameSrc: "https://cn.vuejs.org/",
keepAlive: true,
roles: ["admin", "common"] roles: ["admin", "common"]
} }
}, },
@ -145,6 +148,7 @@ const frameRouter = {
meta: { meta: {
title: "menus.hsViteDocument", title: "menus.hsViteDocument",
frameSrc: "https://cn.vitejs.dev/", frameSrc: "https://cn.vitejs.dev/",
keepAlive: true,
roles: ["admin", "common"] roles: ["admin", "common"]
} }
}, },
@ -154,6 +158,7 @@ const frameRouter = {
meta: { meta: {
title: "menus.hsPiniaDocument", title: "menus.hsPiniaDocument",
frameSrc: "https://pinia.vuejs.org/zh/index.html", frameSrc: "https://pinia.vuejs.org/zh/index.html",
keepAlive: true,
roles: ["admin", "common"] roles: ["admin", "common"]
} }
}, },
@ -163,6 +168,7 @@ const frameRouter = {
meta: { meta: {
title: "menus.hsRouterDocument", title: "menus.hsRouterDocument",
frameSrc: "https://router.vuejs.org/zh/", frameSrc: "https://router.vuejs.org/zh/",
keepAlive: true,
roles: ["admin", "common"] roles: ["admin", "common"]
} }
} }

View File

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import Footer from "./footer/index.vue"; import Footer from "./footer/index.vue";
import { useGlobal } from "@pureadmin/utils"; import { useGlobal } from "@pureadmin/utils";
import KeepAliveFrame from "./keepAliveFrame/index.vue";
import backTop from "@/assets/svg/back_top.svg?component"; import backTop from "@/assets/svg/back_top.svg?component";
import { h, computed, Transition, defineComponent } from "vue"; import { h, computed, Transition, defineComponent } from "vue";
import { usePermissionStoreHook } from "@/store/modules/permission"; import { usePermissionStoreHook } from "@/store/modules/permission";
@ -89,64 +90,75 @@ const transitionMain = defineComponent({
> >
<router-view> <router-view>
<template #default="{ Component, route }"> <template #default="{ Component, route }">
<el-scrollbar <KeepAliveFrame :currComp="Component" :currRoute="route">
v-if="props.fixedHeader" <template #default="{ Comp, fullPath, frameInfo }">
:wrap-style="{ <el-scrollbar
display: 'flex', v-if="props.fixedHeader"
'flex-wrap': 'wrap' :wrap-style="{
}" display: 'flex',
:view-style="{ 'flex-wrap': 'wrap'
display: 'flex', }"
flex: 'auto', :view-style="{
overflow: 'auto', display: 'flex',
'flex-direction': 'column' flex: 'auto',
}" overflow: 'auto',
> 'flex-direction': 'column'
<el-backtop title="回到顶部" target=".app-main .el-scrollbar__wrap"> }"
<backTop /> >
</el-backtop> <el-backtop
<div class="grow"> title="回到顶部"
<transitionMain :route="route"> target=".app-main .el-scrollbar__wrap"
<keep-alive
v-if="isKeepAlive"
:include="usePermissionStoreHook().cachePageList"
> >
<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 <component
:is="Component" :is="Comp"
:key="route.fullPath" v-else
:key="fullPath"
:frameInfo="frameInfo"
class="main-content" class="main-content"
/> />
</keep-alive> </transitionMain>
<component </div>
:is="Component" </template>
v-else </KeepAliveFrame>
: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>
</template> </template>
</router-view> </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);
// tagsMAPMAPtags
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"> <script setup lang="ts">
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { ref, unref, onMounted, nextTick } from "vue"; import { ref, unref, watch, onMounted, nextTick } from "vue";
defineOptions({ defineOptions({
name: "FrameView" name: "FrameView"
}); });
const props = defineProps<{
frameInfo?: {
frameSrc?: string;
fullPath?: string;
};
}>();
const { t } = useI18n(); const { t } = useI18n();
const loading = ref(true); const loading = ref(true);
const currentRoute = useRoute(); const currentRoute = useRoute();
const frameSrc = ref<string>(""); const frameSrc = ref<string>("");
const frameRef = ref<HTMLElement | null>(null); const frameRef = ref<HTMLElement | null>(null);
if (unref(currentRoute.meta)?.frameSrc) { if (unref(currentRoute.meta)?.frameSrc) {
frameSrc.value = unref(currentRoute.meta)?.frameSrc as string; 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(() => { onMounted(() => {
init(); init();
}); });