feat: 现在路由可以自定义权限判断
All checks were successful
ci / build (push) Successful in 40s

This commit is contained in:
Litrix 2025-01-13 09:27:17 +08:00
parent d62b8c6c19
commit d6fc539639
9 changed files with 59 additions and 48 deletions

View File

@ -5,6 +5,7 @@
:hide-pager="brief"
:num="10"
:handle-request="getClubList"
@load-complete="$emit('loadComplete')"
>
<template #default="{ data }">
<div class="flex gap-10px">
@ -34,5 +35,6 @@ const { click = false, brief = false } = defineProps<{
}>();
defineEmits<{
click: [club: Club];
loadComplete: [];
}>();
</script>

View File

@ -189,30 +189,11 @@
animation: tile-common-appear 0.2s ease both;
}
</style>
<script lang="ts">
class FutureMap<T = void> extends Map<number, Future<T>> {
add(tileId: number) {
const future = Promise.withResolvers<T>();
this.set(tileId, future);
return future.promise;
}
resolve(tileId: number, value: T) {
this.get(tileId)?.resolve(value);
this.delete(tileId);
}
reject(tileId: number, reason: unknown) {
this.get(tileId)?.reject(reason);
this.delete(tileId);
}
}
</script>
<script lang="ts" setup>
import { useGame2048Store, type GameState, type Tile } from '@/stores/2048';
import { chainIterables } from '@/utils';
import { get2DArrayItem } from '@/utils/array';
import type { Future } from '@/utils/types';
import { type Future, FutureMap } from '@/utils/future';
import { Directions } from '@/views/Game2048Page.vue';
import { useEventListener } from '@vueuse/core';
import { ElNotification } from 'element-plus';

View File

@ -22,8 +22,12 @@ declare module 'vue-router' {
interface RouteMeta {
routeId?: number;
shouldLogin?: boolean;
userPermission?: RoutePermission<UserPermissionId>;
clubPermission?: RoutePermission<ClubPermissionId>;
permission?:
| {
user?: RoutePermission<UserPermissionId>;
club?: RoutePermission<ClubPermissionId>;
}
| ((userStore: ReturnType<typeof useUserStore>) => boolean);
breadcrumb?: string;
}
}
@ -81,7 +85,7 @@ const routes: RouteRecordRaw[] = [
name: 'ClubInfo',
component: () => import('@/views/club/ClubInfoPage.vue'),
meta: {
clubPermission: ClubPermissionId.CLUB_PAGE,
permission: { club: ClubPermissionId.CLUB_PAGE },
},
},
{
@ -128,7 +132,7 @@ const routes: RouteRecordRaw[] = [
name: 'ManageUser',
component: () => import('@/views/manage/ManageUserPage.vue'),
meta: {
userPermission: UserPermissionId.MANAGE_USER,
permission: { user: UserPermissionId.MANAGE_USER },
breadcrumb: '用户管理',
},
},
@ -162,9 +166,7 @@ const routes: RouteRecordRaw[] = [
},
],
meta: {
userPermission: {
id: UserPermissionId.MANAGE_PAGE,
},
permission: { user: UserPermissionId.MANAGE_PAGE },
breadcrumb: '社团管理',
},
},
@ -172,9 +174,11 @@ const routes: RouteRecordRaw[] = [
redirect: { name: 'ManageMain' },
meta: {
breadcrumb: '系统首页',
userPermission: {
id: [UserPermissionId.MANAGE_PAGE, UserPermissionId.MANAGE_USER],
relation: 'OR',
permission: {
user: {
id: [UserPermissionId.MANAGE_PAGE, UserPermissionId.MANAGE_USER],
relation: 'OR',
},
},
},
},
@ -201,11 +205,7 @@ router.beforeEach(async (to) => {
pageStore.setNewRouteId(to);
if (!userStore.userInfo) {
const succeed = await userStore.updateSelfUserInfo();
const { userPermission, clubPermission } = to.meta;
if (!succeed) {
if (userPermission === undefined && clubPermission === undefined) {
return true;
}
return pageStore.createTempErrorRoute(
{
type: PageErrorType.NETWORK_ERROR,
@ -214,6 +214,7 @@ router.beforeEach(async (to) => {
);
}
}
const type = userStore.isRouteAccessible(to.fullPath);
if (type !== true) {
return pageStore.createTempErrorRoute({ type }, to);

View File

@ -12,6 +12,7 @@ import { computed, ref, watch } from 'vue';
import type { RouteLocationAsRelativeGeneric, RouteMeta } from 'vue-router';
import { z } from 'zod';
import { PageErrorType } from '../views/ErrorPage.vue';
import type { ClubPermissionId, UserPermissionId } from '@/router/permissions';
type Permission = z.infer<typeof idAndNameSchema>;
function hasPermission(
permissionMap: Map<number, Permission>,
@ -54,17 +55,22 @@ export const useUserStore = defineStore('user', () => {
]),
),
);
const hasUserPermission = (userPermission: RouteMeta['userPermission']) =>
const hasUserPermission = (userPermission: RoutePermission<UserPermissionId> | undefined) =>
userPermission === undefined || hasPermission(userPermissions.value, userPermission);
const hasClubPermission = (clubPermission: RouteMeta['clubPermission']) =>
const hasClubPermission = (clubPermission: RoutePermission<ClubPermissionId> | undefined) =>
clubPermission === undefined || hasPermission(clubPermissions.value, clubPermission);
function isRouteAccessible(to: string | RouteLocationAsRelativeGeneric): true | PageErrorType {
const {
meta: { shouldLogin, userPermission, clubPermission },
meta: { shouldLogin, permission },
} = router.resolve(to);
if (shouldLogin && !logined.value) {
return PageErrorType.NOT_LOGIN;
}
if (!permission) return true;
if (typeof permission === 'function') {
return permission(useUserStore()) ? true : PageErrorType.NO_PERMISSION;
}
const { user: userPermission, club: clubPermission } = permission;
if (!hasUserPermission(userPermission) || !hasClubPermission(clubPermission)) {
return PageErrorType.NO_PERMISSION;
}

View File

@ -1,5 +1,5 @@
import { DoubleQueue } from './double-queue';
import type { Future } from './types';
import type { Future } from './future';
export class AsyncQueue<T> {
protected _queue: DoubleQueue<T>;
protected _getters = new DoubleQueue<Future<T>>();

18
src/utils/future.ts Normal file
View File

@ -0,0 +1,18 @@
export type Future<T = void> = PromiseWithResolvers<T>;
export class FutureMap<T = void> extends Map<number, Future<T>> {
add(tileId: number) {
const future = Promise.withResolvers<T>();
this.set(tileId, future);
return future.promise;
}
resolve(tileId: number, value: T) {
this.get(tileId)?.resolve(value);
this.delete(tileId);
}
reject(tileId: number, reason: unknown) {
this.get(tileId)?.reject(reason);
this.delete(tileId);
}
}

View File

@ -1,8 +1,6 @@
import type { Primitive } from 'zod';
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];
export type Override<T extends object, R extends Partial<T> = {}, O extends keyof T = never> = Omit<
T,

View File

@ -1,9 +1,5 @@
<template>
<el-main
v-loading="loading"
element-loading-text="正在加载"
class="keep-padding flex justify-center bg-white"
>
<el-main class="keep-padding flex justify-center bg-white">
<div class="w-1200px flex flex-col gap-10px">
<el-carousel class="rounded-10px" height="300px">
<el-carousel-item v-for="g of gallery" class="slider-item" :key="g.id">
@ -14,7 +10,13 @@
<main-card class="max-lg-col-span-2" title="社团资讯" to="/club/press"></main-card>
<main-card class="max-lg-col-span-2" title="社团活动" to="/club"></main-card>
<main-card class="col-span-2" title="社团列表" to="/club">
<club-list brief />
<club-list
class="min-h-100px"
v-loading="loadingClubList"
element-loading-text="正在加载"
brief
@load-complete="loadingClubList = false"
/>
</main-card>
</div>
</div>
@ -36,7 +38,7 @@ import { Neusoft } from '@/schemas/neusoft';
import { useUserStore } from '@/stores/user.js';
import { ref } from 'vue';
const userStore = useUserStore();
const loading = ref(false);
const loadingClubList = ref(true);
const gallery = ref<Neusoft.SmartParty.Gallery[]>();
requestRaw<Neusoft.SmartParty.GalleryResp>({
url: `${Neusoft.SMART_PARTY_BASE_URL}/system/rotation/list`,

View File

@ -5,7 +5,9 @@
<template #header>
<b>社团列表</b>
</template>
<club-list click @click="onClubClick" />
<div v-loading="loading" element-loading-text="正在加载" class="min-h-100px">
<club-list click @click="onClubClick" @load-complete="loading = false" />
</div>
</el-card>
</div>
<club-detail-dialog v-model="showClubDialog" :club="showingClub" />
@ -21,6 +23,7 @@ import ClubDetailDialog from '@/components/club/ClubDetailDialog.vue';
import ClubList from '@/components/club/ClubList.vue';
import type { Club } from '@/schemas/response';
import { ref } from 'vue';
const loading = ref(true);
const showingClub = ref<Club>();
const showClubDialog = ref(false);
function onClubClick(club: Club) {