This commit is contained in:
parent
d62b8c6c19
commit
d6fc539639
@ -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>
|
||||
|
@ -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';
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
18
src/utils/future.ts
Normal 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);
|
||||
}
|
||||
}
|
2
src/utils/types.d.ts
vendored
2
src/utils/types.d.ts
vendored
@ -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,
|
||||
|
@ -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`,
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user