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: {
|
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"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
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">
|
<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();
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user