Skip to content

Commit 3f5b69d

Browse files
authored
Support async permission checks in route menu generator (#7688)
#### What type of PR is this? /area ui /kind feature /milestone 2.21.x #### What this PR does / why we need it: Support async permission checks in route menu generator example: ```ts { path: "", name: "Foo", component: Foo, meta: { title: "Foo", searchable: true, permissions: async () => { const { data } = await checkPermission(); return data; }, menu: { name: "Foo", group: "content", icon: markRaw(MingcuteBook2Line), priority: 4, mobile: false, }, }, } ``` #### Which issue(s) this PR fixes: Fixes # #### Special notes for your reviewer: #### Does this PR introduce a user-facing change? ```release-note 开发者相关:路由的权限检查支持函数 ```
1 parent e6f8783 commit 3f5b69d

File tree

6 files changed

+262
-145
lines changed

6 files changed

+262
-145
lines changed

ui/console-src/layouts/BasicLayout.vue

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts" setup>
22
import GlobalSearchModal from "@/components/global-search/GlobalSearchModal.vue";
3+
import MenuLoading from "@/components/menu/MenuLoading.vue";
34
import { RoutesMenu } from "@/components/menu/RoutesMenu";
45
import { useRouteMenuGenerator } from "@/composables/use-route-menu-generator";
56
import MobileMenu from "@/layouts/MobileMenu.vue";
@@ -29,7 +30,7 @@ useEventListener(document, "keydown", (e: KeyboardEvent) => {
2930
}
3031
});
3132
32-
const { menus, minimenus } = useRouteMenuGenerator(coreMenuGroups);
33+
const { data, isLoading } = useRouteMenuGenerator(coreMenuGroups);
3334
3435
// aside scroll
3536
const navbarScroller = ref();
@@ -95,7 +96,8 @@ onMounted(() => {
9596
</div>
9697
</div>
9798
</div>
98-
<RoutesMenu :menus="menus" />
99+
<MenuLoading v-if="isLoading" />
100+
<RoutesMenu v-else :menus="data?.menus || []" />
99101
</div>
100102
<div class="sidebar__profile">
101103
<UserProfileBanner platform="console" />
@@ -112,7 +114,11 @@ onMounted(() => {
112114
</RouterLink>
113115
</footer>
114116
</main>
115-
<MobileMenu :menus="menus" :minimenus="minimenus" platform="console" />
117+
<MobileMenu
118+
:menus="data?.menus || []"
119+
:minimenus="data?.minimenus || []"
120+
platform="console"
121+
/>
116122
</div>
117123
<GlobalSearchModal
118124
v-if="globalSearchVisible"

ui/console-src/modules/system/tools/Tools.vue

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,63 @@ import {
1111
VEntityField,
1212
VPageHeader,
1313
} from "@halo-dev/components";
14-
import { computed } from "vue";
14+
import { onMounted, ref } from "vue";
1515
import type { RouteRecordRaw } from "vue-router";
1616
import { useRouter } from "vue-router";
1717
1818
const router = useRouter();
1919
const roleStore = useRoleStore();
2020
2121
const { uiPermissions } = roleStore.permissions;
22+
const routes = ref<RouteRecordRaw[]>([]);
2223
23-
function isRouteValid(route?: RouteRecordRaw) {
24+
async function isRouteValid(route?: RouteRecordRaw) {
2425
if (!route) return false;
2526
const { meta } = route;
2627
if (!meta?.menu) return false;
27-
return (
28-
!meta.permissions || hasPermission(uiPermissions, meta.permissions, true)
29-
);
28+
29+
// If permissions doesn't exist or is empty
30+
if (!meta.permissions) return true;
31+
32+
// Check if permissions is a function
33+
if (typeof meta.permissions === "function") {
34+
try {
35+
return await meta.permissions(uiPermissions);
36+
} catch (e) {
37+
console.error(
38+
`Error checking permissions for route ${String(route.name)}:`,
39+
e
40+
);
41+
return false;
42+
}
43+
}
44+
45+
// Default behavior for array of permissions
46+
return hasPermission(uiPermissions, meta.permissions as string[], true);
3047
}
3148
32-
const routes = computed(() => {
49+
// Use async function to set routes
50+
const fetchRoutes = async () => {
3351
const matchedRoute = router.currentRoute.value.matched[0];
52+
const childRoutes =
53+
router
54+
.getRoutes()
55+
.find((route) => route.name === matchedRoute.name)
56+
?.children.filter((route) => route.path !== "") || [];
57+
58+
const validRoutes: RouteRecordRaw[] = [];
59+
for (const route of childRoutes) {
60+
if (await isRouteValid(route)) {
61+
validRoutes.push(route);
62+
}
63+
}
64+
65+
routes.value = validRoutes;
66+
};
3467
35-
return router
36-
.getRoutes()
37-
.find((route) => route.name === matchedRoute.name)
38-
?.children.filter((route) => route.path !== "")
39-
.filter((route) => isRouteValid(route));
68+
// Fetch routes on component mount
69+
onMounted(() => {
70+
fetchRoutes();
4071
});
4172
</script>
4273

ui/env.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ declare module "vue-router" {
2424
title?: string;
2525
description?: string;
2626
searchable?: boolean;
27-
permissions?: string[];
27+
permissions?:
28+
| string[]
29+
| ((uiPermissions: string[]) => boolean | Promise<boolean>);
2830
core?: boolean;
2931
hideFooter?: boolean;
3032
menu?: {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script lang="ts" setup></script>
2+
3+
<template>
4+
<div class="p-3">
5+
<ul v-for="y in 2" :key="y" class="mt-5 first:mt-0">
6+
<li
7+
v-for="x in 5"
8+
:key="x"
9+
class="flex h-[36.8px] items-center rounded-base py-[0.4rem]"
10+
>
11+
<div class="mr-3 size-5 animate-pulse rounded-lg bg-gray-200"></div>
12+
<div
13+
class="h-5 animate-pulse rounded-lg bg-gray-200"
14+
:style="{
15+
width: `${Math.random() * 50 + 10}%`,
16+
}"
17+
></div>
18+
</li>
19+
</ul>
20+
</div>
21+
</template>

0 commit comments

Comments
 (0)