mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-06-06 00:18:51 +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,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>
|
||||
|
||||
|
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