fix: 修改了退出登录的逻辑,在登录和刷新页面时会重载路由、修改了初始化项目加载动画的逻辑

This commit is contained in:
WANGFAN\wangf 2025-01-12 16:16:50 +08:00
parent 371b86a3dc
commit 9cf79cb079
12 changed files with 357 additions and 320 deletions

View File

@ -4,10 +4,15 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/assets/logo/snow.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/css/loading.css" type="text/css" />
<title><%= title %></title>
</head>
<body>
<div id="app"></div>
<div id="app">
<div class="init-page">
<div class="snow-loader"></div>
</div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

61
public/css/loading.css Normal file
View File

@ -0,0 +1,61 @@
body {
margin: 0;
overflow: hidden;
}
.init-page {
width: 100vw;
height: 100vh;
left: 0;
top: 0;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
}
/* HTML: <div class="snow-loader"></div> */
.snow-loader {
width: 45px;
aspect-ratio: 1;
--c: no-repeat linear-gradient(rgb(22, 93, 255) 0 0);
background:
var(--c) 0% 50%,
var(--c) 50% 50%,
var(--c) 100% 50%;
background-size: 20% 100%;
animation: l1 1s infinite linear;
}
@keyframes l1 {
0% {
background-size:
20% 100%,
20% 100%,
20% 100%;
}
33% {
background-size:
20% 10%,
20% 100%,
20% 100%;
}
50% {
background-size:
20% 100%,
20% 10%,
20% 100%;
}
66% {
background-size:
20% 100%,
20% 100%,
20% 10%;
}
100% {
background-size:
20% 100%,
20% 100%,
20% 100%;
}
}

View File

@ -5,7 +5,6 @@
</template>
<script setup lang="ts">
import { loadingPage } from "@/utils/loading-page";
import { useThemeMethods } from "@/hooks/useThemeMethods";
//
@ -14,12 +13,6 @@ const onTheme = () => {
initTheme();
};
onTheme();
//
loadingPage.start();
onMounted(() => {
loadingPage.done(200);
});
</script>
<style lang="scss" scoped></style>

View File

@ -112,7 +112,6 @@ import { useI18n } from "vue-i18n";
import { Modal } from "@arco-design/web-vue";
import { useRouter } from "vue-router";
import { storeToRefs } from "pinia";
import { useRoutesConfigStore } from "@/store/modules/route-config";
import { useUserInfoStore } from "@/store/modules/user-info";
import { useThemeConfig } from "@/store/modules/theme-config";
import { useThemeMethods } from "@/hooks/useThemeMethods";
@ -194,9 +193,6 @@ const logOut = () => {
// 退
const store = useUserInfoStore();
await store.logOut();
//
const route = useRoutesConfigStore();
await route.resetRoute();
router.replace("/login");
return true;
} catch {

View File

@ -9,7 +9,6 @@
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { useThemeConfig } from "@/store/modules/theme-config";
import { loadingPage } from "@/utils/loading-page";
const themeStore = useThemeConfig();
const { layoutType } = storeToRefs(themeStore);
@ -20,11 +19,6 @@ const layouts: any = {
layoutHead: defineAsyncComponent(() => import("@/layout/layout-head/index.vue")),
layoutMixing: defineAsyncComponent(() => import("@/layout/layout-mixing/index.vue"))
};
onMounted(() => {
// loading
loadingPage.done(200);
});
</script>
<style lang="scss" scoped></style>

View File

@ -7,7 +7,6 @@ import { storeToRefs } from "pinia";
import { useUserInfoStore } from "@/store/modules/user-info";
import { useRoutesConfigStore } from "@/store/modules/route-config";
import { useRoutingMethod } from "@/hooks/useRoutingMethod";
import { loadingPage } from "@/utils/loading-page";
/**
* vue的路由示例
@ -32,16 +31,16 @@ const router = createRouter({
* 1token
* 2token
* 3tokenhome页
* 4token
* 4token
*
* routeTree不能持久化缓存
* addRoute动态添加的路由失效
*/
router.beforeEach(async (to, _, next) => {
router.beforeEach(async (to, from, next) => {
NProgress.start(); // 开启进度条
const store = useUserInfoStore(pinia);
const { token } = storeToRefs(store);
// console.log("去", to, "来自", from);
console.log("去", to, "来自", from);
// next()内部加了path等于跳转指定路由会再次触发router.beforeEach内部无参数等于放行不会触发router.beforeEach
if (to.path === "/login" && !token.value) {
// 1、去登录页无token放行
@ -59,19 +58,22 @@ router.beforeEach(async (to, _, next) => {
const routeStore = useRoutesConfigStore(pinia);
const { routeTree } = storeToRefs(routeStore);
// 从登录页跳转过来,需要重置路由(登录后触发)
if (from.path === "/login") {
await routeStore.initSetRouter();
}
// 获取外链路由的处理函数
// 所有的路由正常放行,只不过额外判断是否是外链,如果是,则打开新窗口跳转外链
// 外链的页面依旧正常打开只不过不会参与缓存与tabs显示符合路由跳转的直觉
const { openExternalLinks } = useRoutingMethod();
// 如果缓存的路由是0则说明未动态添加路由先添加再跳转
// 如果缓存的路由是0则说明未动态添加路由先添加再跳转(页面刷新时触发)
// 解决刷新页面404的问题
if (routeTree.value.length == 0) {
loadingPage.start();
await routeStore.initSetRouter();
// 处理外链跳转
openExternalLinks(to);
// 处理完重新跳转
next({ path: to.path, query: to.query });
} else {
// 处理外链跳转

View File

@ -1,110 +1,110 @@
import { defineStore } from "pinia";
import router from "@/router/index";
import { RouteRecordRaw } from "vue-router";
import { getMenuListAPI } from "@/api/modules/system/index";
import { moduleReplacement, linearArray } from "@/router/route-output";
/**
*
* @methods setRouteNames
* @methods setTabs tabs标签页
* @methods setCurrentRoute
* @methods removeTabsList tabs页的指定路由
* @methods removeRouteName
* @methods removeRouteNames
* @methods initSetRouter
*/
export const useRoutesConfigStore = defineStore("route-config", {
state: (): any => ({
routeTree: [], // 有访问权限的路由树
routeList: [], // 有访问权限的一维路由数组
cacheRoutes: [], // 所有可缓存路由的路由名
tabsList: [], // 标签页数据
currentRoute: {} // 当前路由
}),
actions: {
/**
*
* @param {string} name
*/
setRouteNames(name: string) {
let state = this.cacheRoutes.some((item: string) => item === name);
if (state) return;
this.cacheRoutes.push(name);
},
/**
* tabs标签页
* @param {object} data tabs路由
*/
setTabs(data: Menu.MenuOptions) {
// 当前路由在tags中是否存在不存在则缓存
let isExist = this.tabsList.some((item: Menu.MenuOptions) => item.name === data.name);
if (isExist) return;
this.tabsList.push(data);
},
/**
*
* @param {object} data
*/
setCurrentRoute(data: Menu.MenuOptions) {
if (this.currentRoute.name && data.name === this.currentRoute.name) return;
this.currentRoute = data;
},
/**
* tabs页的指定路由
* @param {string} key name
*/
removeTabsList(key: string) {
const index = this.tabsList.findIndex((item: Menu.MenuOptions) => item.name === key);
if (this.tabsList[index].meta.affix) return;
if (index === -1) return;
this.tabsList.splice(index, 1);
},
/**
*
* @param {string} key
*/
removeRouteName(key: string) {
const index = this.cacheRoutes.findIndex((item: string) => item === key);
if (index === -1) return;
this.cacheRoutes.splice(index, 1);
},
/**
*
* @param {Array} list
*/
removeRouteNames(list: Array<string>) {
this.cacheRoutes = this.cacheRoutes.filter((item: string) => !list.includes(item));
},
/**
* routeTree路由树
*/
async resetRoute() {
this.routeTree = [];
},
/**
*
* 1
* 2
* 3
* 4 +
* 5
* 6
*/
async initSetRouter() {
// 1、获取过滤角色权限后的树后端做排序处理
let { data } = await getMenuListAPI();
// 2、将模块设置为真实模块
let tree = moduleReplacement(data);
// 3、存储路由树用于生成菜单
this.routeTree = tree[0].children;
// 4、根据树生成一维路由数组
tree[0].children = linearArray(tree[0].children);
// 5、设置完整的路由二维路由顶层路由 + 二级的一维路由
tree[0].redirect = tree[0].children[0].path;
// 6、动态添加路由
tree.forEach((route: RouteRecordRaw) => router.addRoute(route));
// 7、缓存一维路由
this.routeList = tree[0].children;
}
}
});
import { defineStore } from "pinia";
import router from "@/router/index";
import { RouteRecordRaw } from "vue-router";
import { getMenuListAPI } from "@/api/modules/system/index";
import { moduleReplacement, linearArray } from "@/router/route-output";
/**
*
* @methods setRouteNames
* @methods setTabs tabs标签页
* @methods setCurrentRoute
* @methods removeTabsList tabs页的指定路由
* @methods removeRouteName
* @methods removeRouteNames
* @methods initSetRouter
*/
export const useRoutesConfigStore = defineStore("route-config", {
state: (): any => ({
routeTree: [], // 有访问权限的路由树
routeList: [], // 有访问权限的一维路由数组
cacheRoutes: [], // 所有可缓存路由的路由名
tabsList: [], // 标签页数据
currentRoute: {} // 当前路由
}),
actions: {
/**
*
* @param {string} name
*/
setRouteNames(name: string) {
let state = this.cacheRoutes.some((item: string) => item === name);
if (state) return;
this.cacheRoutes.push(name);
},
/**
* tabs标签页
* @param {object} data tabs路由
*/
setTabs(data: Menu.MenuOptions) {
// 当前路由在tags中是否存在不存在则缓存
let isExist = this.tabsList.some((item: Menu.MenuOptions) => item.name === data.name);
if (isExist) return;
this.tabsList.push(data);
},
/**
*
* @param {object} data
*/
setCurrentRoute(data: Menu.MenuOptions) {
if (this.currentRoute.name && data.name === this.currentRoute.name) return;
this.currentRoute = data;
},
/**
* tabs页的指定路由
* @param {string} key name
*/
removeTabsList(key: string) {
const index = this.tabsList.findIndex((item: Menu.MenuOptions) => item.name === key);
if (this.tabsList[index].meta.affix) return;
if (index === -1) return;
this.tabsList.splice(index, 1);
},
/**
*
* @param {string} key
*/
removeRouteName(key: string) {
const index = this.cacheRoutes.findIndex((item: string) => item === key);
if (index === -1) return;
this.cacheRoutes.splice(index, 1);
},
/**
*
* @param {Array} list
*/
removeRouteNames(list: Array<string>) {
this.cacheRoutes = this.cacheRoutes.filter((item: string) => !list.includes(item));
},
/**
* routeTree路由树
*/
async resetRoute() {
this.routeTree = [];
},
/**
*
* 1
* 2
* 3
* 4 +
* 5
* 6
*/
async initSetRouter() {
// 1、获取过滤角色权限后的树后端做排序处理
let { data } = await getMenuListAPI();
// 2、将模块设置为真实模块
let tree = moduleReplacement(data);
// 3、存储路由树用于生成菜单
this.routeTree = tree[0].children;
// 4、根据树生成一维路由数组
tree[0].children = linearArray(tree[0].children);
// 5、设置完整的路由二维路由顶层路由 + 二级的一维路由
tree[0].redirect = tree[0].children[0].path;
// 6、动态添加路由
tree.forEach((route: RouteRecordRaw) => router.addRoute(route));
// 7、缓存一维路由
this.routeList = tree[0].children;
}
}
});

View File

@ -13,7 +13,7 @@ interface ThemeConfig {
watermark: string;
watermarkStyle: any;
watermarkRotate: number;
watermarkGap: Array<number>;
watermarkGap: [number, number];
layoutType: string;
grayMode: Boolean;
colorWeakMode: Boolean;

View File

@ -1,29 +1,29 @@
/**
* loading-page
* @method start loading
* @method done loading
*/
export const loadingPage = {
// 开始渲染loading
start: () => {
// 获取顶层body
// 将新创建的dc-loader元素div插入到body元素的子元素列表中的指定位置在指定元素之前
// 插入的位置是作为body元素的第一个子元素即页面的最顶部位置
const bodyDom: Element = document.body;
const div = document.createElement("div");
div.className = "loading-page";
const loader = document.createElement("div");
loader.className = "dc-loader";
div.appendChild(loader);
bodyDom.insertBefore(div, bodyDom.firstChild);
},
// 结束渲染loading
done: (time: number = 0) => {
setTimeout(() => {
// 找到第一个匹配对象
// 找到loading-page的父节点移除loading-page
const dom = document.querySelector(".loading-page");
dom?.parentNode?.removeChild(dom);
}, time);
}
};
/**
* loading-page
* @method start loading
* @method done loading
*/
export const loadingPage = {
// 开始渲染loading
start: () => {
// 获取顶层body
// 将新创建的dc-loader元素div插入到body元素的子元素列表中的指定位置在指定元素之前
// 插入的位置是作为body元素的第一个子元素即页面的最顶部位置
const bodyDom: Element = document.body;
const div = document.createElement("div");
div.className = "loading-page";
const loader = document.createElement("div");
loader.className = "dc-loader";
div.appendChild(loader);
bodyDom.insertBefore(div, bodyDom.firstChild);
},
// 结束渲染loading
done: (time: number = 0) => {
setTimeout(() => {
// 找到第一个匹配对象
// 找到loading-page的父节点移除loading-page
const dom = document.querySelector(".loading-page");
dom?.parentNode?.removeChild(dom);
}, time);
}
};

View File

@ -1,50 +1,45 @@
<template>
<div class="page-404">
<div>
<SvgIcon name="暂无权限" :size="500" />
</div>
<div class="prompt">
<div class="title">401</div>
<div class="text">抱歉暂无访问权限~</div>
<a-button type="primary" @click="onBack">立即返回</a-button>
</div>
</div>
</template>
<script setup lang="ts">
import { loadingPage } from "@/utils/loading-page";
import { useRouter } from "vue-router";
const router = useRouter();
const onBack = () => {
router.go(-1);
};
onMounted(() => {
loadingPage.done(200);
});
</script>
<style lang="scss" scoped>
.page-404 {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
padding: $padding;
overflow: hidden;
.prompt {
row-gap: $padding;
width: 250px;
}
.title {
font-size: 80px;
color: $color-text-1;
}
.text {
margin-bottom: $padding;
font-size: $font-size-body-3;
color: $color-text-2;
}
}
</style>
<template>
<div class="page-404">
<div>
<SvgIcon name="暂无权限" :size="500" />
</div>
<div class="prompt">
<div class="title">401</div>
<div class="text">抱歉暂无访问权限~</div>
<a-button type="primary" @click="onBack">立即返回</a-button>
</div>
</div>
</template>
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
const onBack = () => {
router.go(-1);
};
</script>
<style lang="scss" scoped>
.page-404 {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
padding: $padding;
overflow: hidden;
.prompt {
row-gap: $padding;
width: 250px;
}
.title {
font-size: 80px;
color: $color-text-1;
}
.text {
margin-bottom: $padding;
font-size: $font-size-body-3;
color: $color-text-2;
}
}
</style>

View File

@ -1,49 +1,45 @@
<template>
<div class="page-404">
<div>
<SvgIcon name="内容加载失败" :size="500" />
</div>
<div class="prompt">
<div class="title">404</div>
<div class="text">抱歉访问的页面不存在~</div>
<a-button type="primary" @click="onBack">立即返回</a-button>
</div>
</div>
</template>
<script setup lang="ts">
import { loadingPage } from "@/utils/loading-page";
import { useRouter } from "vue-router";
const router = useRouter();
const onBack = () => {
router.go(-1);
};
onMounted(() => {
loadingPage.done(200);
});
</script>
<style lang="scss" scoped>
.page-404 {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
padding: $padding;
overflow: hidden;
.prompt {
row-gap: $padding;
width: 250px;
}
.title {
font-size: 80px;
color: $color-text-1;
}
.text {
margin-bottom: $padding;
font-size: $font-size-body-3;
color: $color-text-2;
}
}
</style>
<template>
<div class="page-404">
<div>
<SvgIcon name="内容加载失败" :size="500" />
</div>
<div class="prompt">
<div class="title">404</div>
<div class="text">抱歉访问的页面不存在~</div>
<a-button type="primary" @click="onBack">立即返回</a-button>
</div>
</div>
</template>
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
const onBack = () => {
router.go(-1);
};
</script>
<style lang="scss" scoped>
.page-404 {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
padding: $padding;
overflow: hidden;
.prompt {
row-gap: $padding;
width: 250px;
}
.title {
font-size: 80px;
color: $color-text-1;
}
.text {
margin-bottom: $padding;
font-size: $font-size-body-3;
color: $color-text-2;
}
}
</style>

View File

@ -1,56 +1,51 @@
<template>
<div class="page-404">
<div>
<SvgIcon name="网络断开" :size="500" />
</div>
<div class="prompt">
<div class="title">500</div>
<div class="text">抱歉无网络连接~</div>
<a-button type="primary" v-throttle="onBack">立即返回</a-button>
</div>
</div>
</template>
<script setup lang="ts">
import { loadingPage } from "@/utils/loading-page";
import { Message } from "@arco-design/web-vue";
import { useRouter } from "vue-router";
const router = useRouter();
const onBack = () => {
if (!navigator.onLine) {
Message.error("网络未连接");
} else {
router.go(-1);
}
};
onMounted(() => {
loadingPage.done(200);
});
</script>
<style lang="scss" scoped>
.page-404 {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
padding: $padding;
overflow: hidden;
.prompt {
row-gap: $padding;
width: 250px;
}
.title {
font-size: 80px;
color: $color-text-1;
}
.text {
margin-bottom: $padding;
font-size: $font-size-body-3;
color: $color-text-2;
}
}
</style>
<template>
<div class="page-404">
<div>
<SvgIcon name="网络断开" :size="500" />
</div>
<div class="prompt">
<div class="title">500</div>
<div class="text">抱歉无网络连接~</div>
<a-button type="primary" v-throttle="onBack">立即返回</a-button>
</div>
</div>
</template>
<script setup lang="ts">
import { Message } from "@arco-design/web-vue";
import { useRouter } from "vue-router";
const router = useRouter();
const onBack = () => {
if (!navigator.onLine) {
Message.error("网络未连接");
} else {
router.go(-1);
}
};
</script>
<style lang="scss" scoped>
.page-404 {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
padding: $padding;
overflow: hidden;
.prompt {
row-gap: $padding;
width: 250px;
}
.title {
font-size: 80px;
color: $color-text-1;
}
.text {
margin-bottom: $padding;
font-size: $font-size-body-3;
color: $color-text-2;
}
}
</style>