Vue 项目中如何缓存 Iframe 页面
发表于更新于
字数总计:1.8k阅读时长:8分钟阅读量: 上海
项目背景
在我们日常项目开发中,可能会涉及到其他项目嵌入到本项目中。本人在工作中正好碰到了一个需求,需要将旧系统中大量的 HTML
页面嵌入到 Vue
的项目中,嵌入方式是采用 Iframe
,但是若使用 Iframe 嵌入页面,那么页面缓存就相当重要,否则用户每次切换到 Iframe 页面类型的菜单,都要重新加载页面,这样非常浪费时间,并且很影响用户的体验。经过网上查阅资料以及结合项目情况,这里记录一下本次问题的解决思路及实现方案。
项目框架:jeecgboot/ant-design-vue-jeecg: 基于 Vue2 + AntDesignVue 实现的 Ant Design Pro(github.com)
参考:vue 应用缓存 iframe 页面 - 掘金 (juejin.cn)
解决思路
- 通过自定义规则区分 Iframe 类型路由与非 Iframe 类型路由
- 手动获取 Iframe 类型路由的路由信息,并且手动注册所有的 Iframe 路由组件
- Iframe 页面打开时或页面切换时,设置对应的 Iframe 组件为打开状态
- 获取所有已打开的 Iframe 路由组件,
v-for
循环展示 Iframe 路由组件,并根据当前路由信息结合组件信息判断,使用 v-show
控制组件的显隐
关键:将所有 Iframe 类型页面全部注册为 Vue 组件,并且通过打开状态进行展示(懒加载),通过路由信息来控制组件的显隐状态(缓存)
实现方案
区分 Iframe 路由
本项目中是使用路由信息中 meta
数据中的 componentName
来判断的,只要 componentName 包含 Iframe 就代表是 Iframe
类型的路由(区分规则以实际项目为准)
获取所有的 Iframe 路由组件
本项目中采用了动态路由,数据存放在 Vuex
中,用户登录时从数据库加载当前用户的路由菜单。并且项目脚手架是使用 Ant Design Pro
,采用的是 Tab 多页签模式,所以在关闭 Tab 时也需要修改 Iframe 组件的状态,所以将 Iframe 组件列表也存放在 Vuex 中,方便在不同组件中共享数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| computed: { ...mapState({ mainMenu: state => state.user.allPermissionList, iframeComponentList: state => state.app.iframeComponentList }) }, methods: { getIframeComponentList() { const iframeMenuList = this.handleIframe(this.mainMenu, []) const iframeComponentList = iframeMenuList.map((item, index) => { let component = ""; if(item.component.indexOf("layouts")>=0){ component = "components/"+item.component; }else{ component = "views/"+item.component; } const componentPath = resolve => require(['@/' + component+'.vue'], resolve) const name = `iframe-${index}` return { hasOpen: false, ...item, name, component: componentPath } }) this.$store.dispatch('setIframeComponentList', iframeComponentList) }, handleIframe(menu, newMenu) { menu.forEach((item) => { if (item.meta && item.meta.componentName && item.meta.componentName.includes('Iframe')) { newMenu.push(item) } if (item.children && item.children.length) { this.handleIframe(item.children, newMenu) } }) return newMenu } }
|
设置 Iframe 路由在打开后为打开状态,用于缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| computed: { hasOpenComponentList() { return this.iframeComponentList.filter((item) => item.hasOpen) } }, methods: { isOpenIframePage() { const target = this.iframeComponentList.find((item) => { return this.$route.path.includes(item.path) }) if (target && !target.hasOpen) { let index = this.iframeComponentList.indexOf(target) let hasOpen = true this.$store.dispatch('setIframeComponentHasOpen', { index, hasOpen }) } } }
|
使用 Iframe 路由组件
1 2 3 4 5 6 7
| <component v-for="item in hasOpenComponentList" :key="item.name" :is="item.name" :linkUrl="item.meta.url" v-show="$route.path.includes(item.path)" ></component>
|
Iframe 路由出口组件完整代码
在项目中的菜单页面的路由出口处,将 route-view
组件更换成以下封装的 iframe-route-view
即可使用
注意:这里的 route-view
是 JEECG
框架在 vue-router
官方提供的 router-view
上封装了一层的自定义组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| <template> <div class='main'> <route-view v-if='!$route.meta.componentName.includes("Iframe")'></route-view>
<template v-if='hasOpenComponentList.length'> <component v-for='item in hasOpenComponentList' :key='item.name' :is='item.name' :linkUrl='item.meta.url' v-show='$route.path.includes(item.path)' ></component> </template> </div> </template>
<script> import Vue from 'vue' import { mapState } from 'vuex' import RouteView from '@/components/layouts/RouteView'
export default { name: 'IframeRouteView', components: { RouteView }, created() { if (this.iframeComponentList.length === 0) { this.getIframeComponentList() } this.iframeComponentList.forEach((item) => { Vue.component(item.name, item.component) }) this.isOpenIframePage() }, watch: { $route() { if (this.iframeComponentList.length === 0) { this.getIframeComponentList() } this.isOpenIframePage() } }, computed: { ...mapState({ mainMenu: state => state.user.allPermissionList, iframeComponentList: state => state.app.iframeComponentList }), hasOpenComponentList() { return this.iframeComponentList.filter((item) => item.hasOpen) } }, methods: { isOpenIframePage() { const target = this.iframeComponentList.find((item) => { return this.$route.path.includes(item.path) }) if (target && !target.hasOpen) { let index = this.iframeComponentList.indexOf(target) let hasOpen = true this.$store.dispatch('setIframeComponentHasOpen', { index, hasOpen }) } }, getIframeComponentList() { const iframeMenuList = this.handleIframe(this.mainMenu, []) const iframeComponentList = iframeMenuList.map((item, index) => { let component = ""; if(item.component.indexOf("layouts")>=0){ component = "components/"+item.component; }else{ component = "views/"+item.component; } const componentPath = resolve => require(['@/' + component+'.vue'], resolve) const name = `iframe-${index}` return { hasOpen: false, ...item, name, component: componentPath } }) this.$store.dispatch('setIframeComponentList', iframeComponentList) }, handleIframe(menu, newMenu) { menu.forEach((item) => { if (item.meta && item.meta.componentName && item.meta.componentName.includes('Iframe')) { newMenu.push(item) } if (item.children && item.children.length) { this.handleIframe(item.children, newMenu) } }) return newMenu } } } </script> <style lang='less'> </style>
|
扩展一:使用 Vuex 管理 Iframe 组件列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import Vue from 'vue'
const IFRAME_COMPONENT_LIST = "IFRAME_COMPONENT_LIST"
const iframe = { state: { iframeComponentList: [], }, mutations: { SET_IFRAME_COMPONENT_LIST (state, iframeComponentList) { Vue.ls.set(IFRAME_COMPONENT_LIST, iframeComponentList) state.iframeComponentList = iframeComponentList }, SET_IFRAME_COMPONENT_HAS_OPENED (state, {index, hasOpen}) { state.iframeComponentList[index].hasOpen = hasOpen Vue.ls.set(IFRAME_COMPONENT_LIST, state.iframeComponentList) } }, actions: { setIframeComponentList({ commit }, iframeComponentList) { commit('SET_IFRAME_COMPONENT_LIST', iframeComponentList) }, setIframeComponentHasOpen({ commit }, {index, hasOpen}) { commit('SET_IFRAME_COMPONENT_HAS_OPENED', {index, hasOpen}) } } }
export default iframe
|
扩展二:在关闭 Tab 标签页时改变页面的打开状态
注意:以下修改方式仅适用于当前项目,经供参考
1 2 3 4 5
| close(pageKey) { let index = this.iframeComponentList.findIndex(item => pageKey.includes(item.path)) let hasOpen = false this.$store.dispatch('setIframeComponentHasOpen', { index, hasOpen }) }
|
总结
整体的实现思路并不复杂,但是运用到实际项目中还是会牵动到项目框架本身的结构,可能会引起其他的问题,个人工作记录,仅供参考,如有不足之处,欢迎友友们在文章末尾进行留言交流指正。