🎈 perf: 优化菜单显示逻辑
This commit is contained in:
parent
9f333ecc87
commit
feac940ecc
5
env.d.ts
vendored
5
env.d.ts
vendored
@ -1,4 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="element-plus/global" />
|
||||
/// <reference types="./components.d.ts" />
|
||||
/// <reference types="./auto-imports.d.ts" />
|
||||
type BooleanString = 'true' | 'false';
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_DEBUG: BooleanString;
|
||||
}
|
||||
|
40
src/App.vue
40
src/App.vue
@ -13,11 +13,17 @@
|
||||
<header-user @login="onLoginButtonClick" @logout="logout" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item :icon="UserFilled" @click="jumpToUserPage">个人主页</el-dropdown-item>
|
||||
<el-dropdown-item :icon="CloseBold" @click="logout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<header-user v-else @login="onLoginButtonClick" @logout="logout" @click="onUserClick" />
|
||||
<header-user
|
||||
v-else
|
||||
@login="onLoginButtonClick"
|
||||
@logout="logout"
|
||||
@click="showVerticalHeaderMenu = true"
|
||||
/>
|
||||
</el-header>
|
||||
<el-main class="app-main" v-loading="!!pageStore.pageLoadingCount">
|
||||
<el-container>
|
||||
@ -38,19 +44,14 @@
|
||||
:with-header="false"
|
||||
>
|
||||
<h3 v-show="!showAppTitle" class="app__title text-center">社团展示系统</h3>
|
||||
<header-user v-if="userStore.logined" @login="showLoginRegisterDialog = true" />
|
||||
<template v-if="userStore.logined">
|
||||
<header-user @login="showLoginRegisterDialog = true" />
|
||||
<el-button :icon="UserFilled" @click="jumpToUserPage">个人主页</el-button>
|
||||
<el-button type="danger" :icon="CloseBold" @click="logout">退出登录</el-button>
|
||||
</template>
|
||||
<div v-else class="app-vertical-drawer__not-login-title flex justify-center items-center">
|
||||
尚未登录
|
||||
</div>
|
||||
<el-button
|
||||
v-if="userStore.logined"
|
||||
class="app-vertical-drawer__logout"
|
||||
type="primary"
|
||||
:icon="CloseBold"
|
||||
@click="logout"
|
||||
>
|
||||
退出登录
|
||||
</el-button>
|
||||
<header-menu mode="vertical" @select="showVerticalHeaderMenu = false" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
@ -68,7 +69,7 @@
|
||||
&__not-login-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
&__logout {
|
||||
.el-button {
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
@ -97,7 +98,7 @@
|
||||
}
|
||||
.app-router-view-enter-active,
|
||||
.app-router-view-leave-active {
|
||||
transition: opacity 0.25s ease;
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
|
||||
.app-router-view-enter-from,
|
||||
@ -116,13 +117,14 @@ import LoginRegisterDialog, {
|
||||
import { useMediaStore } from '@/stores/media';
|
||||
import { usePageStore } from '@/stores/page.js';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { CloseBold } from '@element-plus/icons-vue';
|
||||
import { CloseBold, UserFilled } from '@element-plus/icons-vue';
|
||||
import { useMediaQuery } from '@vueuse/core';
|
||||
import type { AxiosError } from 'axios';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { provide, ref, watch } from 'vue';
|
||||
import { loginResponseSchema, registerResponseSchema } from './schemas/response';
|
||||
import router from '@/router';
|
||||
const userStore = useUserStore();
|
||||
const pageStore = usePageStore();
|
||||
const { mdLess } = storeToRefs(useMediaStore());
|
||||
@ -170,8 +172,14 @@ watch(mdLess, (v) => {
|
||||
function onLoginButtonClick() {
|
||||
showLoginRegisterDialog.value = true;
|
||||
}
|
||||
function onUserClick() {
|
||||
showVerticalHeaderMenu.value = true;
|
||||
function jumpToUserPage() {
|
||||
showVerticalHeaderMenu.value = false;
|
||||
router.push({
|
||||
name: 'User',
|
||||
params: {
|
||||
id: userStore.userInfo!.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
// onMounted(() => {
|
||||
// userStore.token = null;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-menu-item class="festival-menu-item flex" :index="to">
|
||||
<img :src="src" class="festival-menu-item__img" />
|
||||
<el-menu-item class="festival-menu-item flex" :index="to.fullPath">
|
||||
<img :src="imgURL" class="festival-menu-item__img" />
|
||||
<div class="festival-menu-item__title">{{ title }}</div>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
@ -16,9 +16,6 @@
|
||||
}
|
||||
</style>
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
src: string;
|
||||
title: string;
|
||||
to?: string;
|
||||
}>();
|
||||
import type { MenuItem } from '@/components/app/HeaderMenu.vue';
|
||||
defineProps<MenuItem>();
|
||||
</script>
|
||||
|
@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<el-menu
|
||||
class="app-header-menu justify-end"
|
||||
:class="[`app-header-menu--${mode}`]"
|
||||
:mode="mode"
|
||||
:default-active="$route.fullPath"
|
||||
router
|
||||
@ -10,8 +9,14 @@
|
||||
<el-menu-item index="/">首页</el-menu-item>
|
||||
<el-sub-menu class="festival-menu" index="festival">
|
||||
<template #title>社团文化节</template>
|
||||
<template v-for="{ src, title, to, vIf } of festivalMenuItems">
|
||||
<festival-menu-item v-if="!vIf || vIf()" :key="to" :src="src" :title="title" :to="to" />
|
||||
<template v-for="{ imgURL, title, to } of festivalMenuItems">
|
||||
<festival-menu-item
|
||||
v-if="isValid(to)"
|
||||
:key="to.fullPath"
|
||||
:imgURL="imgURL"
|
||||
:title="title"
|
||||
:to="to"
|
||||
/>
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
</el-menu>
|
||||
@ -19,33 +24,46 @@
|
||||
<style lang="scss" scoped>
|
||||
.app-header-menu {
|
||||
--el-menu-horizontal-height: var(--header-height);
|
||||
}
|
||||
.app-header-menu--horizontal {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.app-header-menu :is(.el-menu-item, .el-sub-menu__title) {
|
||||
user-select: none;
|
||||
--el-menu-bg-color: transparent;
|
||||
--el-menu-border-color: transparent;
|
||||
&.el-menu--horizontal {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
:is(.el-menu-item, .el-sub-menu__title) {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script lang="ts">
|
||||
export interface MenuItem {
|
||||
imgURL: string;
|
||||
title: string;
|
||||
to: RouteLocationGeneric;
|
||||
}
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import FestivalMenuItem from '@/components/app/FestivalMenuItem.vue';
|
||||
import router from '@/router';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { reactive } from 'vue';
|
||||
import type { RouteLocationGeneric } from 'vue-router';
|
||||
const userStore = useUserStore();
|
||||
const festivalMenuItems = reactive([
|
||||
|
||||
const festivalMenuItems = reactive<MenuItem[]>([
|
||||
{
|
||||
src: '/2048.png',
|
||||
imgURL: '/2048.png',
|
||||
title: '2048',
|
||||
to: '/2048',
|
||||
to: router.resolve('/2048')!,
|
||||
},
|
||||
{
|
||||
src: '/gobang.svg',
|
||||
imgURL: '/gobang.svg',
|
||||
title: '五子棋',
|
||||
to: '/gobang',
|
||||
vIf: () => userStore.logined,
|
||||
to: router.resolve('/gobang')!,
|
||||
},
|
||||
]);
|
||||
const isValid = ({ meta: { shouldLogin, permissionId } }: RouteLocationGeneric) =>
|
||||
(!shouldLogin || userStore.logined) && (!permissionId || userStore.permissions.has(permissionId));
|
||||
defineProps<{
|
||||
mode: 'horizontal' | 'vertical';
|
||||
}>();
|
||||
|
@ -2,7 +2,7 @@
|
||||
<el-dialog
|
||||
v-model="show"
|
||||
:fullscreen="smLess"
|
||||
items-center
|
||||
align-center
|
||||
class="login-register-dialog"
|
||||
width="400"
|
||||
>
|
||||
|
@ -293,7 +293,7 @@ watch(gameStatus, (status) => {
|
||||
message: `单个块分数达到${game2048Store.successNumber}`,
|
||||
type: 'success',
|
||||
duration: 3500,
|
||||
offset: 50,
|
||||
position: 'bottom-left',
|
||||
});
|
||||
break;
|
||||
case 'failed':
|
||||
@ -302,7 +302,7 @@ watch(gameStatus, (status) => {
|
||||
message: '你无路可走',
|
||||
type: 'error',
|
||||
duration: 3500,
|
||||
offset: 50,
|
||||
position: 'bottom-left',
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
@ -3,8 +3,8 @@
|
||||
class="create-gobang-room-dialog"
|
||||
v-model="show"
|
||||
title="创建房间"
|
||||
items-center
|
||||
:fullscreen="smLess"
|
||||
align-center
|
||||
>
|
||||
<el-button type="success" @click="onCreateRoomButtonClick">创建房间</el-button>
|
||||
</el-dialog>
|
||||
|
@ -14,14 +14,17 @@ declare module 'vue-router' {
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: () => import('@/views/MainPage.vue'),
|
||||
},
|
||||
{
|
||||
path: '/user/:id',
|
||||
name: 'User',
|
||||
component: () => import('@/views/UserPage.vue'),
|
||||
},
|
||||
{
|
||||
path: '/club',
|
||||
name: 'Club',
|
||||
component: () => import('@/views/ClubPage.vue'),
|
||||
meta: {
|
||||
permissionId: RoutePermissionId.CLUB_PAGE,
|
||||
@ -29,6 +32,7 @@ const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
{
|
||||
path: '/2048',
|
||||
name: '2048',
|
||||
component: () => import('@/views/Game2048Page.vue'),
|
||||
},
|
||||
{
|
||||
@ -57,7 +61,7 @@ const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
{
|
||||
path: '/:path(.*)*',
|
||||
name: 'notFound',
|
||||
name: 'NotFound',
|
||||
component: () => import('@/views/ErrorPage.vue'),
|
||||
props: {
|
||||
type: PageErrorType.NOT_FOUND,
|
||||
@ -92,7 +96,7 @@ router.beforeEach(async (to) => {
|
||||
return pageStore.createTempErrorRoute({ type: PageErrorType.NOT_LOGIN }, to);
|
||||
}
|
||||
if (permissionId) {
|
||||
if (userStore.hasPermission(permissionId)) {
|
||||
if (userStore.permissions.has(permissionId)) {
|
||||
return true;
|
||||
} else {
|
||||
return pageStore.createTempErrorRoute({ type: PageErrorType.NO_PERMISSION }, to);
|
||||
|
@ -19,7 +19,12 @@ export const useUserStore = defineStore('user', () => {
|
||||
const userInfo = useLocalStorage<UserInfo | null>('user-info', null, {
|
||||
serializer: StorageSerializers.object,
|
||||
});
|
||||
const permissions = computed<Permission[]>(() => userInfo.value?.auth.permissions ?? []);
|
||||
const permissions = computed(
|
||||
() =>
|
||||
new Map<number, Permission>(
|
||||
(userInfo.value?.auth.permissions ?? []).map((permission) => [permission.id, permission]),
|
||||
),
|
||||
);
|
||||
const initializing = ref(false);
|
||||
const logined = computed(() => (userInfo.value && userInfo.value.id !== -1) ?? false);
|
||||
watch(
|
||||
@ -55,9 +60,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
initializing.value = false;
|
||||
}
|
||||
}
|
||||
function hasPermission(permissionId: number) {
|
||||
return permissions.value.find((permission) => permission.id === permissionId);
|
||||
}
|
||||
|
||||
const logoutInterceptors = new Set<LogoutInterceptor>();
|
||||
function addLogoutInterceptor(fn: LogoutInterceptor) {
|
||||
logoutInterceptors.add(fn);
|
||||
@ -79,7 +82,6 @@ export const useUserStore = defineStore('user', () => {
|
||||
permissions,
|
||||
initializing,
|
||||
updateSelfUserInfo,
|
||||
hasPermission,
|
||||
logoutInterceptors,
|
||||
addLogoutInterceptor,
|
||||
removeLogoutInterceptor,
|
||||
|
@ -1,4 +1,4 @@
|
||||
export type Future<T = void> = PromiseWithResolvers<T>;
|
||||
export type MaybePromise<T> = T | Promise<T>;
|
||||
export type MaybeArray<T, R extends boolean = false> = T | (R extends false ? T[] : readonly T[]);
|
||||
export type Future<T = void> = PromiseWithResolvers<T>;
|
||||
export type ValueOf<T extends object> = T[keyof T];
|
||||
|
@ -148,15 +148,15 @@ const relations = {
|
||||
PlaceChessPiece: ['RoomInfo'],
|
||||
ResetRoom: ['RoomInfo'],
|
||||
} as const satisfies Relations<Request['name'], Resp['name']>;
|
||||
const DEBUG = false;
|
||||
export function useGobangSocket(
|
||||
options: Omit<UseGameSocketOptions<Request, Resp, typeof relations>, 'url' | 'relations'>,
|
||||
) {
|
||||
return useGameSocket<Request, Resp>()(
|
||||
Object.assign(options, {
|
||||
url: DEBUG
|
||||
? `ws://172.16.114.84:58080/chess/${useUserStore().token}`
|
||||
: `wss://wzpmc.cn:18080/chess/${useUserStore().token}`,
|
||||
url:
|
||||
import.meta.env.VITE_DEBUG === 'true'
|
||||
? `ws://172.16.114.84:58080/chess/${useUserStore().token}`
|
||||
: `wss://wzpmc.cn:18080/chess/${useUserStore().token}`,
|
||||
relations,
|
||||
}),
|
||||
);
|
||||
|
@ -545,6 +545,7 @@ const { send } = useGobangSocket({
|
||||
},
|
||||
error: {
|
||||
PlayerJoin() {
|
||||
canExit = true;
|
||||
router.back();
|
||||
return false;
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user