前言
一直比较好奇vue-element-admin 与vban 这些中台集成方案中Tap标签页重新加载(刷新当前页)
功能是怎么实现的,经过长期搜索以及查看实现代码再此做出总结,方便日后,直接实现
实现方案
重点讲解第3
与第4
点
- 使用
location. reload()
与this.$router.go(0)
缺点会整个页面刷新,页签也会丢失(不建议)
- 使用
provide / inject
,其实就是在主入口 route-view 中增加v-if 判断,provide 注入事件, 当子页面刷新调用 触发入口文件事件(不建议)子页面要调用方法 不智能
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
| <router-view if="isRouterAlive" />
<srtipt> export default { name: 'app', provide (){ return { reload:this.reload } }, data(){ return { isRouterAlive:true, } }, methods:{ reload (){ this.isRouterAlive = false this.$nextTick(()=>{ this.isRouterAlive = true }) } } } </script>
|
- route-view 绑定Key, 当触发事件,让
key++
,实现页面刷新(有点不推荐,只要一个页面刷新,会影响其他页面keep-alive)
- 使用中间页进行重定向(推荐)
route-view 绑定Key 方法
原理其实很简单,就是为入口main.js 的 标签绑定Key值,这个key值在vuex中维护,当触发vuex中指定mutations 方法,使key++ 或者 key = Date.now()。
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
| <template> <div :key="refreshTime"> <keep-alive> <router-view /> </keep-alive> </div> </template>
<script> export default { data() { return { refreshTime: '' } },
watch: { '$store.state.tagsView.refreshTime': function () { this.refreshTime = this.$store.state.tagsView.refreshTime } }, } </script>
const tagsView = { state: { refreshTime: '', }, mutations: { REFRESH_TIME: (state) => { state.refreshTime = (new Date()).valueOf() }, }, }
export default tagsView
|
但是此做法会导致其他页面keep-alive 缓存失效,既已打开标签,在某个已打开页面中执行刷新方法,在切换其他页签页面,其他页面也会重新刷新
使用中间页进行重定向
该方法也很简单,先携带上一个页面的路由地址以及参数跳转到一个中间页面,然后在根据参数使用router.replace重定向回去。下面通过vban
与vue-element-admin
两个中台示例进行分析
在分析之前要说下,两个中台的vue/pinia都有存储
cacheTabList: 缓存标签(用于keep-alive :include的值)
tabList:展示标签(用于tap标签遍历展示)
vban 实现刷新当前页
我们直接看到/src/layouts目录下,这使存储公共的布局文件,我们直接看向/src/layouts/default/tabs/components/TabContent.vue
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
|
<template> <Dropdown :dropMenuList="getDropMenuList" :trigger="getTrigger" placement="bottom" overlayClassName="multiple-tabs__dropdown" @menu-event="handleMenuEvent" > ... </span> </Dropdown> </template> <script lang="ts"> import { useTabDropdown } from '../useTabDropdown'; export default defineComponent({ setup(props) { const { getDropMenuList } = useTabDropdown( props as TabContentProps, getIsTabs, ); ... return { getDropMenuList, }; }, }); </script>
|
useTabDropdown.ts 配置了右键 tap展开的选项配置,配置中有icon、event触发事件,text:下拉名称,disabled是否展示,最重要的还是event,其中event为MenuEventEnum.REFRESH_PAGE
就是我们的重新加载的事件了,然后通过handleMenuEvent事件执行对应key 的事件
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
| import { useTabs } from '/@/hooks/web/useTabs'; const { refreshPage, closeAll, close, closeLeft, closeOther, closeRight } = useTabs(); const dropMenuList: DropMenu[] = [ { icon: 'ion:reload-sharp', event: MenuEventEnum.REFRESH_PAGE, text: t('layout.multipleTab.reload'), disabled: refreshDisabled, }, { icon: 'clarity:close-line', event: MenuEventEnum.CLOSE_CURRENT, text: t('layout.multipleTab.close'), disabled: !!meta?.affix || disabled, divider: true, }, { icon: 'line-md:arrow-close-left', event: MenuEventEnum.CLOSE_LEFT, text: t('layout.multipleTab.closeLeft'), disabled: closeLeftDisabled, divider: false, }, { icon: 'line-md:arrow-close-right', event: MenuEventEnum.CLOSE_RIGHT, text: t('layout.multipleTab.closeRight'), disabled: closeRightDisabled, divider: true, }, { icon: 'dashicons:align-center', event: MenuEventEnum.CLOSE_OTHER, text: t('layout.multipleTab.closeOther'), disabled: disabled || !isCurItem, }, { icon: 'clarity:minus-line', event: MenuEventEnum.CLOSE_ALL, text: t('layout.multipleTab.closeAll'), disabled: disabled, }, ]; return dropMenuList; }); <!--事件触发--> function handleMenuEvent(menu: DropMenu): void { const { event } = menu; switch (event) { case MenuEventEnum.REFRESH_PAGE: refreshPage(); break; case MenuEventEnum.CLOSE_CURRENT: close(tabContentProps.tabItem); break; case MenuEventEnum.CLOSE_LEFT: closeLeft(); break; case MenuEventEnum.CLOSE_RIGHT: closeRight(); break; case MenuEventEnum.CLOSE_OTHER: closeOther(); break; case MenuEventEnum.CLOSE_ALL: closeAll(); break; }
|
refreshPage 事件是由/src/hooks/web/useTabs.ts 路径下 导出了useTabs 方法,看向导出执行事件为refreshPage()
tabStore.refreshPage(router);
调用则为pinia
中位于
src/store/modules/multipleTab.ts
中抛出来的
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
const tabStore = useMultipleTabStore();
调用了它的refreshPage方法,我们下面直接看pinia 实现
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
| import type { Router } from 'vue-router'; import { unref } from 'vue'; import { useRedo } from '/@/hooks/web/usePage';
export const useMultipleTabStore = defineStore({ id: 'app-multiple-tab', state: (): MultipleTabState => ({ cacheTabList: [], tabList: [], }), getters: { getCachedTabList(): string[] { return Array.from(this.cacheTabList); }, }, actions: {
async refreshPage(router: Router) { const { currentRoute } = router; const route = unref(currentRoute); const name = route.name; const findTab = this.getCachedTabList.find((item) => item === name); if (findTab) { this.cacheTabList.delete(findTab); } const redo = useRedo(router); await redo(); } }, });
|
方法中获取了我们当前要刷新的router 对象获取到router 对象首先要判断舒服在缓存数组中,如果在缓存数组中则删除缓存,然后执行useRedo 抛出来的方法redo();
useRedo所在位置为src/hooks/web/usePage.ts, 直接看redo 实现
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
|
export const useRedo = (_router?: Router) => { const { replace, currentRoute } = _router || useRouter(); const { query, params = {}, name, fullPath } = unref(currentRoute.value); function redo(): Promise<boolean> { return new Promise((resolve) => { if (name === REDIRECT_NAME) { resolve(false); return; } if (name && Object.keys(params).length > 0) { params['_redirect_type'] = 'name'; params['path'] = String(name); } else { params['_redirect_type'] = 'path'; params['path'] = fullPath; } replace({ name: REDIRECT_NAME, params, query }).then(() => resolve(true)); }); } return redo; };
|
- 获取到传入router 对象,如果没传入,则获取当前路由route对象
- 将取消响应式
- 创建redo方法,判断当前页面是否是重置页面,如果是重置页面,不执行任何操作直接resolve(false);
- 如果router.name存在 _redirect_type 跳转模式为name, 跳转path 为 name,如果router.name不存在_redirect_type 跳转模式为path,path 为 fullPath
- 执行router. Replace 放啊进行跳转至中间页面
- 中间页为/redirect,页面存放地方为src/views/sys/redirect/index.vue
中间页将在beforCreate 生命周期直接跳转到 传入信息的path.
vue-element-admin 实现刷新当前页
由于vue-element-admin没使用typeScript,并没有过度封装,直接看src/layout/components/TagsView/index.vue路径文件代码
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
| <template> <div id="tags-view-container" class="tags-view-container"> <scroll-pane ref="scrollPane" class="tags-view-wrapper" @scroll="handleScroll"> ... </scroll-pane> <ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu"> <li @click="refreshSelectedTag(selectedTag)">Refresh</li> <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">Close</li> <li @click="closeOthersTags">Close Others</li> <li @click="closeAllTags(selectedTag)">Close All</li> </ul> </div> </template>
<script> import ScrollPane from './ScrollPane' import path from 'path'
export default { methods: { refreshSelectedTag(view) { this.$store.dispatch('tagsView/delCachedView', view).then(() => { const { fullPath } = view this.$nextTick(() => { this.$router.replace({ path: '/redirect' + fullPath }) }) }) }, } } </script>
|
以上代码主要在vuex tagsView/delCachedView 事件执行,/redirect路由 定义
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
| export const constantRoutes = [ { path: '/redirect', component: Layout, hidden: true, children: [ { path: '/redirect/:path(.*)', component: () => import('@/views/redirect/index') } ] } ]
<script> export default { created() { const { params, query } = this.$route const { path } = params this.$router.replace({ path: '/' + path, query }) }, render: function(h) { return h() } } </script>
|
vuex tagsView/delCachedView
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
| const state = { visitedViews: [], cachedViews: [] }
const mutations = { DEL_CACHED_VIEW: (state, view) => { <!-- 查看当前页面是否在缓存队列中 --> const index = state.cachedViews.indexOf(view.name) <!-- 如果由则删除 --> index > -1 && state.cachedViews.splice(index, 1) }, }
const actions = { delCachedView({ commit, state }, view) { return new Promise(resolve => { commit('DEL_CACHED_VIEW', view) resolve([...state.cachedViews]) }) },
}
export default { namespaced: true, state, mutations, actions }
|