avatar

目录
vue-Router 刷新当前页面解决方案与Vben 刷新页面代码解析

前言

一直比较好奇vue-element-admin 与vban 这些中台集成方案中Tap标签页重新加载(刷新当前页)功能是怎么实现的,经过长期搜索以及查看实现代码再此做出总结,方便日后,直接实现

实现方案

重点讲解第3第4

  1. 使用 location. reload()this.$router.go(0) 缺点会整个页面刷新,页签也会丢失(不建议)
  2. 使用 provide / inject,其实就是在主入口 route-view 中增加v-if 判断,provide 注入事件, 当子页面刷新调用 触发入口文件事件(不建议)子页面要调用方法 不智能
    html
    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>
  3. route-view 绑定Key, 当触发事件,让key++,实现页面刷新(有点不推荐,只要一个页面刷新,会影响其他页面keep-alive)
  4. 使用中间页进行重定向(推荐)

route-view 绑定Key 方法

原理其实很简单,就是为入口main.js 的 标签绑定Key值,这个key值在vuex中维护,当触发vuex中指定mutations 方法,使key++ 或者 key = Date.now()。

html
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>


<!--vuex 文件-->
const tagsView = {
state: {
refreshTime: '',
},
mutations: {
REFRESH_TIME: (state) => {
state.refreshTime = (new Date()).valueOf()
},
},
}

export default tagsView

但是此做法会导致其他页面keep-alive 缓存失效,既已打开标签,在某个已打开页面中执行刷新方法,在切换其他页签页面,其他页面也会重新刷新

使用中间页进行重定向

该方法也很简单,先携带上一个页面的路由地址以及参数跳转到一个中间页面,然后在根据参数使用router.replace重定向回去。下面通过vbanvue-element-admin两个中台示例进行分析

在分析之前要说下,两个中台的vue/pinia都有存储

cacheTabList: 缓存标签(用于keep-alive :include的值)

tabList:展示标签(用于tap标签遍历展示)

vban 实现刷新当前页

vban 实现刷新当前页

我们直接看到/src/layouts目录下,这使存储公共的布局文件,我们直接看向/src/layouts/default/tabs/components/TabContent.vue

html
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
<!--Dropdown组件接收v-bind:dropMenuList数据,数据来源于 
/src/layouts/default/tabs/useTabDropdown.ts
第52行dropMenuList
-->
<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 的事件

Javascript
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:
// refresh page
// 指向const { refreshPage } = useTabs();位于/src/hooks/web/useTabs.ts
refreshPage(); // 看这个就行
break;
// Close current
case MenuEventEnum.CLOSE_CURRENT:
close(tabContentProps.tabItem);
break;
// Close left
case MenuEventEnum.CLOSE_LEFT:
closeLeft();
break;
// Close right
case MenuEventEnum.CLOSE_RIGHT:
closeRight();
break;
// Close other
case MenuEventEnum.CLOSE_OTHER:
closeOther();
break;
// Close all
case MenuEventEnum.CLOSE_ALL:
closeAll();
break;
}

refreshPage 事件是由/src/hooks/web/useTabs.ts 路径下 导出了useTabs 方法,看向导出执行事件为refreshPage()

vban useTabs

tabStore.refreshPage(router);

调用则为pinia中位于

src/store/modules/multipleTab.ts

中抛出来的

import { useMultipleTabStore } from '/@/store/modules/multipleTab';

const tabStore = useMultipleTabStore();

调用了它的refreshPage方法,我们下面直接看pinia 实现

ts
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: {

/**
* Refresh tabs
*/
async refreshPage(router: Router) {
// 获取传入的router对象(既想刷新的路由)
const { currentRoute } = router;
// 脱离响应式
const route = unref(currentRoute);
// 获取路由名称
const name = route.name;
// 查看当前路由是否在缓存列表中,如果是则删除,只需在tabList展示列表中存在
const findTab = this.getCachedTabList.find((item) => item === name);
if (findTab) {
this.cacheTabList.delete(findTab);
}
// 调用路由重定向方法
// useRedo所在位置为src/hooks/web/usePage.ts
const redo = useRedo(router);
await redo();
}
},
});

方法中获取了我们当前要刷新的router 对象获取到router 对象首先要判断舒服在缓存数组中,如果在缓存数组中则删除缓存,然后执行useRedo 抛出来的方法redo();
useRedo所在位置为src/hooks/web/usePage.ts, 直接看redo 实现

Javascript
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
/**
* @description: redo current page
*/
export const useRedo = (_router?: Router) => {
// 获取到传入router 对象,如果没传入,则获取当前路由route对象
const { replace, currentRoute } = _router || useRouter();
// 取消响应式
const { query, params = {}, name, fullPath } = unref(currentRoute.value);
// 创建redo方法
function redo(): Promise<boolean> {
return new Promise((resolve) => {
// 判断当前页面是否是重置页面,如果是重置页面,不执行任何操作直接resolve(false);
if (name === REDIRECT_NAME) {
resolve(false);
return;
}
// 如果router.name 存在 _redirect_type 跳转模式为name, 跳转path 为 name,
if (name && Object.keys(params).length > 0) {
params['_redirect_type'] = 'name';
params['path'] = String(name);
} else {
// 如果router.name不存在_redirect_type 跳转模式为path,path 为 fullPath
params['_redirect_type'] = 'path';
params['path'] = fullPath;
}
// 执行router.replace 方法进行跳转至中间页面
// 中间页为/redirect,页面存放地方为src/views/sys/redirect/index.vue
replace({ name: REDIRECT_NAME, params, query }).then(() => resolve(true));
});
}
return redo;
};
  1. 获取到传入router 对象,如果没传入,则获取当前路由route对象
  2. 将取消响应式
  3. 创建redo方法,判断当前页面是否是重置页面,如果是重置页面,不执行任何操作直接resolve(false);
  4. 如果router.name存在 _redirect_type 跳转模式为name, 跳转path 为 name,如果router.name不存在_redirect_type 跳转模式为path,path 为 fullPath
  5. 执行router. Replace 放啊进行跳转至中间页面
  6. 中间页为/redirect,页面存放地方为src/views/sys/redirect/index.vue

vban redirect中间页实现

中间页将在beforCreate 生命周期直接跳转到 传入信息的path.

vue-element-admin 实现刷新当前页

由于vue-element-admin没使用typeScript,并没有过度封装,直接看src/layout/components/TagsView/index.vue路径文件代码

html
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">
<!-- 该按钮为刷新页面按钮,触发refreshSelectedTag事件 -->
<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) {
// 调用vuex tagsView/delCachedView 事件并传参跳转至/redirect 页面
this.$store.dispatch('tagsView/delCachedView', view).then(() => {
const { fullPath } = view
this.$nextTick(() => {
this.$router.replace({
path: '/redirect' + fullPath
})
})
})
},
}
}
</script>

以上代码主要在vuex tagsView/delCachedView 事件执行,/redirect路由 定义

Javascript
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
// /redirect 路由定义
export const constantRoutes = [
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect/index')
}
]
}
]

// /redirect 页面
// src/views/redirect/index.vue
<script>
export default {
created() {
const { params, query } = this.$route
const { path } = params
this.$router.replace({ path: '/' + path, query })
},
render: function(h) {
return h() // avoid warning message
}
}
</script>

vuex tagsView/delCachedView

Javascript
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
}
文章作者: Ming
文章链接: http://www.mgblog.cn/2022/07/16/vue-Router-%E5%88%B7%E6%96%B0%E5%BD%93%E5%89%8D%E9%A1%B5%E9%9D%A2%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E4%B8%8EVben-%E5%88%B7%E6%96%B0%E9%A1%B5%E9%9D%A2%E4%BB%A3%E7%A0%81%E8%A7%A3%E6%9E%90/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ming的博客 - 编程经验分享

评论