🔱: [client] sync upgrade with 7 commits [trident-sync]

chore:
Merge branch 'vben'

# Conflicts:
#	package.json
perf: antdv示例改成使用vben框架
chore: vben
chore: vben
chore: vben
This commit is contained in:
GitHub Actions Bot
2025-03-03 19:24:51 +00:00
parent de26ee9383
commit 335d175d57
649 changed files with 36984 additions and 826 deletions
@@ -0,0 +1,47 @@
<!--
Access control component for fine-grained access control.
TODO: 可以扩展更完善的功能
1. 支持多个权限码只要有一个权限码满足即可 或者 多个权限码全部满足
2. 支持多个角色只要有一个角色满足即可 或者 多个角色全部满足
3. 支持自定义权限码和角色的判断逻辑
-->
<script lang="ts" setup>
import { computed } from "vue";
import { useAccess } from "./use-access";
interface Props {
/**
* Specified codes is visible
* @default []
*/
codes?: string[];
/**
* 通过什么方式来控制组件,如果是 role,则传入角色,如果是 code,则传入权限码
* @default 'role'
*/
type?: "code" | "role";
}
defineOptions({
name: "AccessControl"
});
const props = withDefaults(defineProps<Props>(), {
codes: () => [],
type: "role"
});
const { hasAccessByCodes, hasAccessByRoles } = useAccess();
const hasAuth = computed(() => {
const { codes, type } = props;
return type === "role" ? hasAccessByRoles(codes) : hasAccessByCodes(codes);
});
</script>
<template>
<slot v-if="!codes"></slot>
<slot v-else-if="hasAuth"></slot>
</template>
@@ -0,0 +1,84 @@
import type { AccessModeType, GenerateMenuAndRoutesOptions, RouteRecordRaw } from "/@/vben/types";
import { cloneDeep, generateMenus, generateRoutesByBackend, generateRoutesByFrontend, mapTree } from "/@/vben/utils";
async function generateAccessible(mode: AccessModeType, options: GenerateMenuAndRoutesOptions) {
const { router } = options;
options.routes = cloneDeep(options.routes);
// 生成路由
const accessibleRoutes = await generateRoutes(mode, options);
const root = router.getRoutes().find((item: any) => item.path === "/");
// 动态添加到router实例内
accessibleRoutes.forEach((route) => {
if (root && !route.meta?.noBasicLayout) {
// 为了兼容之前的版本用法,如果包含子路由,则将component移除,以免出现多层BasicLayout
// 如果你的项目已经跟进了本次修改,移除了所有自定义菜单首级的BasicLayout,可以将这段if代码删除
if (route.children && route.children.length > 0) {
delete route.component;
}
root.children?.push(route);
} else {
router.addRoute(route);
}
});
if (root) {
if (root.name) {
router.removeRoute(root.name);
}
router.addRoute(root);
}
// 生成菜单
const accessibleMenus = await generateMenus(accessibleRoutes, options.router);
return { accessibleMenus, accessibleRoutes };
}
/**
* Generate routes
* @param mode
* @param options
*/
async function generateRoutes(mode: AccessModeType, options: GenerateMenuAndRoutesOptions) {
const { forbiddenComponent, roles, routes } = options;
let resultRoutes: RouteRecordRaw[] = routes;
switch (mode) {
case "backend": {
resultRoutes = await generateRoutesByBackend(options);
break;
}
case "frontend": {
resultRoutes = await generateRoutesByFrontend(routes, roles || [], forbiddenComponent);
break;
}
}
/**
* 调整路由树,做以下处理:
* 1. 对未添加redirect的路由添加redirect
*/
resultRoutes = mapTree(resultRoutes, (route) => {
// 如果有redirect或者没有子路由,则直接返回
if (route.redirect || !route.children || route.children.length === 0) {
return route;
}
const firstChild = route.children[0];
// 如果子路由不是以/开头,则直接返回,这种情况需要计算全部父级的path才能得出正确的path,这里不做处理
if (!firstChild?.path || !firstChild.path.startsWith("/")) {
return route;
}
route.redirect = firstChild.path;
return route;
});
return resultRoutes;
}
export { generateAccessible };
@@ -0,0 +1,42 @@
/**
* Global authority directive
* Used for fine-grained control of component permissions
* @Example v-access:role="[ROLE_NAME]" or v-access:role="ROLE_NAME"
* @Example v-access:code="[ROLE_CODE]" or v-access:code="ROLE_CODE"
*/
import type { App, Directive, DirectiveBinding } from 'vue';
import { useAccess } from './use-access';
function isAccessible(
el: Element,
binding: DirectiveBinding<string | string[]>,
) {
const { accessMode, hasAccessByCodes, hasAccessByRoles } = useAccess();
const value = binding.value;
if (!value) return;
const authMethod =
accessMode.value === 'frontend' && binding.arg === 'role'
? hasAccessByRoles
: hasAccessByCodes;
const values = Array.isArray(value) ? value : [value];
if (!authMethod(values)) {
el?.remove();
}
}
const mounted = (el: Element, binding: DirectiveBinding<string | string[]>) => {
isAccessible(el, binding);
};
const authDirective: Directive = {
mounted,
};
export function registerAccessDirective(app: App) {
app.directive('access', authDirective);
}
@@ -0,0 +1,4 @@
export { default as AccessControl } from './access-control.vue';
export * from './accessible';
export * from './directive';
export * from './use-access';
@@ -0,0 +1,52 @@
import { computed } from "vue";
import { preferences, updatePreferences } from "@vben/preferences";
import { useAccessStore, useUserStore } from "@vben/stores";
function useAccess() {
const accessStore = useAccessStore();
const userStore = useUserStore();
const accessMode = computed(() => {
return preferences.app.accessMode;
});
/**
* 基于角色判断是否有权限
* @description: Determine whether there is permissionThe role is judged by the user's role
* @param roles
*/
function hasAccessByRoles(roles: string[]) {
const userRoleSet = new Set(userStore.userRoles);
const intersection = roles.filter((item) => userRoleSet.has(item));
return intersection.length > 0;
}
/**
* 基于权限码判断是否有权限
* @description: Determine whether there is permissionThe permission code is judged by the user's permission code
* @param codes
*/
function hasAccessByCodes(codes: string[]) {
const userCodesSet = new Set(accessStore.accessCodes);
const intersection = codes.filter((item) => userCodesSet.has(item));
return intersection.length > 0;
}
async function toggleAccessMode() {
updatePreferences({
app: {
accessMode: preferences.app.accessMode === "frontend" ? "backend" : "frontend"
}
});
}
return {
accessMode,
hasAccessByCodes,
hasAccessByRoles,
toggleAccessMode
};
}
export { useAccess };