diff --git a/vue-vben-admin/packages/effects/access/package.json b/vue-vben-admin/packages/effects/access/package.json
new file mode 100644
index 0000000..e9549ca
--- /dev/null
+++ b/vue-vben-admin/packages/effects/access/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "@vben/access",
+ "version": "5.6.0",
+ "homepage": "https://github.com/vbenjs/vue-vben-admin",
+ "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/vbenjs/vue-vben-admin.git",
+ "directory": "packages/effects/permissions"
+ },
+ "license": "MIT",
+ "type": "module",
+ "sideEffects": [
+ "**/*.css"
+ ],
+ "exports": {
+ ".": {
+ "types": "./src/index.ts",
+ "default": "./src/index.ts"
+ }
+ },
+ "dependencies": {
+ "@vben/preferences": "workspace:*",
+ "@vben/stores": "workspace:*",
+ "@vben/types": "workspace:*",
+ "@vben/utils": "workspace:*",
+ "vue": "catalog:"
+ }
+}
diff --git a/vue-vben-admin/packages/effects/access/src/access-control.vue b/vue-vben-admin/packages/effects/access/src/access-control.vue
new file mode 100644
index 0000000..219608e
--- /dev/null
+++ b/vue-vben-admin/packages/effects/access/src/access-control.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
diff --git a/vue-vben-admin/packages/effects/access/src/accessible.ts b/vue-vben-admin/packages/effects/access/src/accessible.ts
new file mode 100644
index 0000000..eb90814
--- /dev/null
+++ b/vue-vben-admin/packages/effects/access/src/accessible.ts
@@ -0,0 +1,156 @@
+import type { Component, DefineComponent } from 'vue';
+
+import type {
+ AccessModeType,
+ GenerateMenuAndRoutesOptions,
+ RouteRecordRaw,
+} from '@vben/types';
+
+import { defineComponent, h } from 'vue';
+
+import {
+ cloneDeep,
+ generateMenus,
+ generateRoutesByBackend,
+ generateRoutesByFrontend,
+ isFunction,
+ isString,
+ 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) => item.path === '/');
+
+ // 获取已有的路由名称列表
+ const names = root?.children?.map((item) => item.name) ?? [];
+
+ // 动态添加到router实例内
+ accessibleRoutes.forEach((route) => {
+ if (root && !route.meta?.noBasicLayout) {
+ // 为了兼容之前的版本用法,如果包含子路由,则将component移除,以免出现多层BasicLayout
+ // 如果你的项目已经跟进了本次修改,移除了所有自定义菜单首级的BasicLayout,可以将这段if代码删除
+ if (route.children && route.children.length > 0) {
+ delete route.component;
+ }
+ // 根据router name判断,如果路由已经存在,则不再添加
+ if (names?.includes(route.name)) {
+ // 找到已存在的路由索引并更新,不更新会造成切换用户时,一级目录未更新,homePath 在二级目录导致的404问题
+ const index = root.children?.findIndex(
+ (item) => item.name === route.name,
+ );
+ if (index !== undefined && index !== -1 && root.children) {
+ root.children[index] = route;
+ }
+ } else {
+ root.children?.push(route);
+ }
+ } else {
+ router.addRoute(route);
+ }
+ });
+
+ if (root) {
+ if (root.name) {
+ router.removeRoute(root.name);
+ }
+ router.addRoute(root);
+ }
+
+ // 生成菜单
+ const accessibleMenus = 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;
+ }
+ case 'mixed': {
+ const [frontend_resultRoutes, backend_resultRoutes] = await Promise.all([
+ generateRoutesByFrontend(routes, roles || [], forbiddenComponent),
+ generateRoutesByBackend(options),
+ ]);
+
+ resultRoutes = [...frontend_resultRoutes, ...backend_resultRoutes];
+ break;
+ }
+ }
+
+ /**
+ * 调整路由树,做以下处理:
+ * 1. 对未添加redirect的路由添加redirect
+ * 2. 将懒加载的组件名称修改为当前路由的名称(如果启用了keep-alive的话)
+ */
+ resultRoutes = mapTree(resultRoutes, (route) => {
+ // 重新包装component,使用与路由名称相同的name以支持keep-alive的条件缓存。
+ if (
+ route.meta?.keepAlive &&
+ isFunction(route.component) &&
+ route.name &&
+ isString(route.name)
+ ) {
+ const originalComponent = route.component as () => Promise<{
+ default: Component | DefineComponent;
+ }>;
+ route.component = async () => {
+ const component = await originalComponent();
+ if (!component.default) return component;
+ return defineComponent({
+ name: route.name as string,
+ setup(props, { attrs, slots }) {
+ return () => h(component.default, { ...props, ...attrs }, slots);
+ },
+ });
+ };
+ }
+
+ // 如果有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 };
diff --git a/vue-vben-admin/packages/effects/access/src/directive.ts b/vue-vben-admin/packages/effects/access/src/directive.ts
new file mode 100644
index 0000000..35d9d51
--- /dev/null
+++ b/vue-vben-admin/packages/effects/access/src/directive.ts
@@ -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,
+) {
+ 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) => {
+ isAccessible(el, binding);
+};
+
+const authDirective: Directive = {
+ mounted,
+};
+
+export function registerAccessDirective(app: App) {
+ app.directive('access', authDirective);
+}
diff --git a/vue-vben-admin/packages/effects/access/src/index.ts b/vue-vben-admin/packages/effects/access/src/index.ts
new file mode 100644
index 0000000..392aa53
--- /dev/null
+++ b/vue-vben-admin/packages/effects/access/src/index.ts
@@ -0,0 +1,4 @@
+export { default as AccessControl } from './access-control.vue';
+export * from './accessible';
+export * from './directive';
+export * from './use-access';
diff --git a/vue-vben-admin/packages/effects/access/src/use-access.ts b/vue-vben-admin/packages/effects/access/src/use-access.ts
new file mode 100644
index 0000000..939cdbe
--- /dev/null
+++ b/vue-vben-admin/packages/effects/access/src/use-access.ts
@@ -0,0 +1,53 @@
+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 permission,The 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 permission,The 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 };
diff --git a/vue-vben-admin/packages/effects/access/tsconfig.json b/vue-vben-admin/packages/effects/access/tsconfig.json
new file mode 100644
index 0000000..ce1a891
--- /dev/null
+++ b/vue-vben-admin/packages/effects/access/tsconfig.json
@@ -0,0 +1,6 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "extends": "@vben/tsconfig/web.json",
+ "include": ["src"],
+ "exclude": ["node_modules"]
+}