refactor(project): simplified part of the package, code optimization
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@vben-core/toolkit": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"vue-router": "^4.4.0"
|
||||
}
|
||||
|
230
packages/@core/forward/helpers/src/generate-menus.test.ts
Normal file
230
packages/@core/forward/helpers/src/generate-menus.test.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { generateMenus } from './generate-menus'; // 替换为您的实际路径
|
||||
import {
|
||||
type RouteRecordRaw,
|
||||
type Router,
|
||||
createRouter,
|
||||
createWebHistory,
|
||||
} from 'vue-router';
|
||||
|
||||
// Nested route setup to test child inclusion and hideChildrenInMenu functionality
|
||||
|
||||
describe('generateMenus', () => {
|
||||
// 模拟路由数据
|
||||
const mockRoutes = [
|
||||
{
|
||||
meta: { icon: 'home-icon', title: '首页' },
|
||||
name: 'home',
|
||||
path: '/home',
|
||||
},
|
||||
{
|
||||
meta: { hideChildrenInMenu: true, icon: 'about-icon', title: '关于' },
|
||||
name: 'about',
|
||||
path: '/about',
|
||||
children: [
|
||||
{
|
||||
path: 'team',
|
||||
name: 'team',
|
||||
meta: { icon: 'team-icon', title: '团队' },
|
||||
},
|
||||
],
|
||||
},
|
||||
] as RouteRecordRaw[];
|
||||
|
||||
// 模拟 Vue 路由器实例
|
||||
const mockRouter = {
|
||||
getRoutes: vi.fn(() => [
|
||||
{ name: 'home', path: '/home' },
|
||||
{ name: 'about', path: '/about' },
|
||||
{ name: 'team', path: '/about/team' },
|
||||
]),
|
||||
};
|
||||
|
||||
it('the correct menu list should be generated according to the route', async () => {
|
||||
const expectedMenus = [
|
||||
{
|
||||
badge: undefined,
|
||||
badgeType: undefined,
|
||||
badgeVariants: undefined,
|
||||
icon: 'home-icon',
|
||||
name: '首页',
|
||||
order: undefined,
|
||||
parent: undefined,
|
||||
parents: undefined,
|
||||
path: '/home',
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
badge: undefined,
|
||||
badgeType: undefined,
|
||||
badgeVariants: undefined,
|
||||
icon: 'about-icon',
|
||||
name: '关于',
|
||||
order: undefined,
|
||||
parent: undefined,
|
||||
parents: undefined,
|
||||
path: '/about',
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
|
||||
const menus = await generateMenus(mockRoutes, mockRouter as any);
|
||||
expect(menus).toEqual(expectedMenus);
|
||||
});
|
||||
|
||||
it('includes additional meta properties in menu items', async () => {
|
||||
const mockRoutesWithMeta = [
|
||||
{
|
||||
meta: { icon: 'user-icon', order: 1, title: 'Profile' },
|
||||
name: 'profile',
|
||||
path: '/profile',
|
||||
},
|
||||
] as RouteRecordRaw[];
|
||||
|
||||
const menus = await generateMenus(mockRoutesWithMeta, mockRouter as any);
|
||||
expect(menus).toEqual([
|
||||
{
|
||||
badge: undefined,
|
||||
badgeType: undefined,
|
||||
badgeVariants: undefined,
|
||||
icon: 'user-icon',
|
||||
name: 'Profile',
|
||||
order: 1,
|
||||
parent: undefined,
|
||||
parents: undefined,
|
||||
path: '/profile',
|
||||
children: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('handles dynamic route parameters correctly', async () => {
|
||||
const mockRoutesWithParams = [
|
||||
{
|
||||
meta: { icon: 'details-icon', title: 'User Details' },
|
||||
name: 'userDetails',
|
||||
path: '/users/:userId',
|
||||
},
|
||||
] as RouteRecordRaw[];
|
||||
|
||||
const menus = await generateMenus(mockRoutesWithParams, mockRouter as any);
|
||||
expect(menus).toEqual([
|
||||
{
|
||||
badge: undefined,
|
||||
badgeType: undefined,
|
||||
badgeVariants: undefined,
|
||||
icon: 'details-icon',
|
||||
name: 'User Details',
|
||||
order: undefined,
|
||||
parent: undefined,
|
||||
parents: undefined,
|
||||
path: '/users/:userId',
|
||||
children: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('processes routes with redirects correctly', async () => {
|
||||
const mockRoutesWithRedirect = [
|
||||
{
|
||||
name: 'redirectedRoute',
|
||||
path: '/old-path',
|
||||
redirect: '/new-path',
|
||||
},
|
||||
{
|
||||
meta: { icon: 'path-icon', title: 'New Path' },
|
||||
name: 'newPath',
|
||||
path: '/new-path',
|
||||
},
|
||||
] as RouteRecordRaw[];
|
||||
|
||||
const menus = await generateMenus(
|
||||
mockRoutesWithRedirect,
|
||||
mockRouter as any,
|
||||
);
|
||||
expect(menus).toEqual([
|
||||
// Assuming your generateMenus function excludes redirect routes from the menu
|
||||
{
|
||||
badge: undefined,
|
||||
badgeType: undefined,
|
||||
badgeVariants: undefined,
|
||||
icon: undefined,
|
||||
name: 'redirectedRoute',
|
||||
order: undefined,
|
||||
parent: undefined,
|
||||
parents: undefined,
|
||||
path: '/old-path',
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
badge: undefined,
|
||||
badgeType: undefined,
|
||||
badgeVariants: undefined,
|
||||
icon: 'path-icon',
|
||||
name: 'New Path',
|
||||
order: undefined,
|
||||
parent: undefined,
|
||||
parents: undefined,
|
||||
path: '/new-path',
|
||||
children: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
const routes: any = [
|
||||
{
|
||||
meta: { order: 2, title: 'Home' },
|
||||
name: 'home',
|
||||
path: '/',
|
||||
},
|
||||
{
|
||||
meta: { order: 1, title: 'About' },
|
||||
name: 'about',
|
||||
path: '/about',
|
||||
},
|
||||
];
|
||||
|
||||
const router: Router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
});
|
||||
|
||||
it('should generate menu list with correct order', async () => {
|
||||
const menus = await generateMenus(routes, router);
|
||||
const expectedMenus = [
|
||||
{
|
||||
badge: undefined,
|
||||
badgeType: undefined,
|
||||
badgeVariants: undefined,
|
||||
icon: undefined,
|
||||
name: 'About',
|
||||
order: 1,
|
||||
parent: undefined,
|
||||
parents: undefined,
|
||||
path: '/about',
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
badge: undefined,
|
||||
badgeType: undefined,
|
||||
badgeVariants: undefined,
|
||||
icon: undefined,
|
||||
name: 'Home',
|
||||
order: 2,
|
||||
parent: undefined,
|
||||
parents: undefined,
|
||||
path: '/',
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
|
||||
expect(menus).toEqual(expectedMenus);
|
||||
});
|
||||
|
||||
it('should handle empty routes', async () => {
|
||||
const emptyRoutes: any[] = [];
|
||||
const menus = await generateMenus(emptyRoutes, router);
|
||||
expect(menus).toEqual([]);
|
||||
});
|
||||
});
|
73
packages/@core/forward/helpers/src/generate-menus.ts
Normal file
73
packages/@core/forward/helpers/src/generate-menus.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { ExRouteRecordRaw, MenuRecordRaw } from '@vben-core/typings';
|
||||
import type { RouteRecordRaw, Router } from 'vue-router';
|
||||
|
||||
import { mapTree } from '@vben-core/toolkit';
|
||||
|
||||
/**
|
||||
* 根据 routes 生成菜单列表
|
||||
* @param routes
|
||||
*/
|
||||
async function generateMenus(
|
||||
routes: RouteRecordRaw[],
|
||||
router: Router,
|
||||
): Promise<MenuRecordRaw[]> {
|
||||
// 将路由列表转换为一个以 name 为键的对象映射
|
||||
// 获取所有router最终的path及name
|
||||
const finalRoutesMap: { [key: string]: string } = Object.fromEntries(
|
||||
router.getRoutes().map(({ name, path }) => [name, path]),
|
||||
);
|
||||
|
||||
let menus = mapTree<ExRouteRecordRaw, MenuRecordRaw>(routes, (route) => {
|
||||
// 路由表的路径写法有多种,这里从router获取到最终的path并赋值
|
||||
const path = finalRoutesMap[route.name as string] ?? route.path;
|
||||
|
||||
// 转换为菜单结构
|
||||
// const path = matchRoute?.path ?? route.path;
|
||||
const { meta, name: routeName, redirect, children } = route;
|
||||
const {
|
||||
badge,
|
||||
badgeType,
|
||||
badgeVariants,
|
||||
hideChildrenInMenu = false,
|
||||
icon,
|
||||
link,
|
||||
order,
|
||||
title = '',
|
||||
} = meta || {};
|
||||
|
||||
const name = (title || routeName || '') as string;
|
||||
|
||||
// 隐藏子菜单
|
||||
const resultChildren = hideChildrenInMenu
|
||||
? []
|
||||
: (children as MenuRecordRaw[]);
|
||||
|
||||
// 将菜单的所有父级和父级菜单记录到菜单项内
|
||||
if (resultChildren && resultChildren.length > 0) {
|
||||
resultChildren.forEach((child) => {
|
||||
child.parents = [...(route.parents || []), path];
|
||||
child.parent = path;
|
||||
});
|
||||
}
|
||||
// 隐藏子菜单
|
||||
const resultPath = hideChildrenInMenu ? redirect || path : link || path;
|
||||
return {
|
||||
badge,
|
||||
badgeType,
|
||||
badgeVariants,
|
||||
icon,
|
||||
name,
|
||||
order,
|
||||
parent: route.parent,
|
||||
parents: route.parents,
|
||||
path: resultPath as string,
|
||||
children: resultChildren || [],
|
||||
};
|
||||
});
|
||||
|
||||
// 对菜单进行排序
|
||||
menus = menus.sort((a, b) => (a.order || 999) - (b.order || 999));
|
||||
return menus;
|
||||
}
|
||||
|
||||
export { generateMenus };
|
@@ -0,0 +1,82 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { mapTree } from '@vben-core/toolkit';
|
||||
import {
|
||||
ComponentRecordType,
|
||||
GenerateMenuAndRoutesOptions,
|
||||
RouteRecordStringComponent,
|
||||
} from '@vben-core/typings';
|
||||
|
||||
/**
|
||||
* 动态生成路由 - 后端方式
|
||||
*/
|
||||
async function generateRoutesByBackend(
|
||||
options: GenerateMenuAndRoutesOptions,
|
||||
): Promise<RouteRecordRaw[]> {
|
||||
const { fetchMenuListAsync, layoutMap = {}, pageMap = {} } = options;
|
||||
|
||||
try {
|
||||
const menuRoutes = await fetchMenuListAsync?.();
|
||||
if (!menuRoutes) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const normalizePageMap: ComponentRecordType = {};
|
||||
|
||||
for (const [key, value] of Object.entries(pageMap)) {
|
||||
normalizePageMap[normalizeViewPath(key)] = value;
|
||||
}
|
||||
|
||||
const routes = convertRoutes(menuRoutes, layoutMap, normalizePageMap);
|
||||
|
||||
return routes;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function convertRoutes(
|
||||
routes: RouteRecordStringComponent[],
|
||||
layoutMap: ComponentRecordType,
|
||||
pageMap: ComponentRecordType,
|
||||
): RouteRecordRaw[] {
|
||||
return mapTree(routes, (node) => {
|
||||
const route = node as unknown as RouteRecordRaw;
|
||||
const { component, name } = node;
|
||||
|
||||
if (!name) {
|
||||
console.error('route name is required', route);
|
||||
}
|
||||
|
||||
// layout转换
|
||||
if (component && layoutMap[component]) {
|
||||
route.component = layoutMap[component];
|
||||
// 页面组件转换
|
||||
} else if (component) {
|
||||
const normalizePath = normalizeViewPath(component);
|
||||
route.component =
|
||||
pageMap[
|
||||
normalizePath.endsWith('.vue')
|
||||
? normalizePath
|
||||
: `${normalizePath}.vue`
|
||||
];
|
||||
}
|
||||
|
||||
return route;
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeViewPath(path: string): string {
|
||||
// 去除相对路径前缀
|
||||
const normalizedPath = path.replace(/^(\.\/|\.\.\/)+/, '');
|
||||
|
||||
// 确保路径以 '/' 开头
|
||||
const viewPath = normalizedPath.startsWith('/')
|
||||
? normalizedPath
|
||||
: `/${normalizedPath}`;
|
||||
|
||||
// TODO: 这里耦合了vben-admin的目录结构
|
||||
return viewPath.replace(/^\/views/, '');
|
||||
}
|
||||
export { generateRoutesByBackend };
|
@@ -0,0 +1,136 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
generateRoutesByFrontend,
|
||||
hasAuthority,
|
||||
hasVisible,
|
||||
} from './generate-routes-frontend';
|
||||
|
||||
// Mock 路由数据
|
||||
const mockRoutes = [
|
||||
{
|
||||
meta: {
|
||||
authority: ['admin', 'user'],
|
||||
hideInMenu: false,
|
||||
},
|
||||
path: '/dashboard',
|
||||
children: [
|
||||
{
|
||||
path: '/dashboard/overview',
|
||||
meta: { authority: ['admin'], hideInMenu: false },
|
||||
},
|
||||
{
|
||||
path: '/dashboard/stats',
|
||||
meta: { authority: ['user'], hideInMenu: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
meta: { authority: ['admin'], hideInMenu: false },
|
||||
path: '/settings',
|
||||
},
|
||||
{
|
||||
meta: { hideInMenu: false },
|
||||
path: '/profile',
|
||||
},
|
||||
] as RouteRecordRaw[];
|
||||
|
||||
describe('hasAuthority', () => {
|
||||
it('should return true if there is no authority defined', () => {
|
||||
expect(hasAuthority(mockRoutes[2], ['admin'])).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if the user has the required authority', () => {
|
||||
expect(hasAuthority(mockRoutes[0], ['admin'])).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the user does not have the required authority', () => {
|
||||
expect(hasAuthority(mockRoutes[1], ['user'])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasVisible', () => {
|
||||
it('should return true if hideInMenu is not set or false', () => {
|
||||
expect(hasVisible(mockRoutes[0])).toBe(true);
|
||||
expect(hasVisible(mockRoutes[2])).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if hideInMenu is true', () => {
|
||||
expect(hasVisible(mockRoutes[0].children?.[1])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateRoutesByFrontend', () => {
|
||||
it('should filter routes based on authority and visibility', async () => {
|
||||
const generatedRoutes = await generateRoutesByFrontend(mockRoutes, [
|
||||
'user',
|
||||
]);
|
||||
// The user should have access to /dashboard/stats, but it should be filtered out because it's not visible
|
||||
expect(generatedRoutes).toEqual([
|
||||
{
|
||||
meta: { authority: ['admin', 'user'], hideInMenu: false },
|
||||
path: '/dashboard',
|
||||
children: [],
|
||||
},
|
||||
// Note: We expect /settings to be filtered out because the user does not have 'admin' authority
|
||||
{
|
||||
meta: { hideInMenu: false },
|
||||
path: '/profile',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle routes without children', async () => {
|
||||
const generatedRoutes = await generateRoutesByFrontend(mockRoutes, [
|
||||
'user',
|
||||
]);
|
||||
expect(generatedRoutes).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
path: '/profile', // This route has no children and should be included
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty roles array', async () => {
|
||||
const generatedRoutes = await generateRoutesByFrontend(mockRoutes, []);
|
||||
expect(generatedRoutes).toEqual(
|
||||
expect.arrayContaining([
|
||||
// Only routes without authority should be included
|
||||
expect.objectContaining({
|
||||
path: '/profile',
|
||||
}),
|
||||
]),
|
||||
);
|
||||
expect(generatedRoutes).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
path: '/dashboard',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: '/settings',
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle missing meta fields', async () => {
|
||||
const routesWithMissingMeta = [
|
||||
{ path: '/path1' }, // No meta
|
||||
{ meta: {}, path: '/path2' }, // Empty meta
|
||||
{ meta: { authority: ['admin'] }, path: '/path3' }, // Only authority
|
||||
];
|
||||
const generatedRoutes = await generateRoutesByFrontend(
|
||||
routesWithMissingMeta as RouteRecordRaw[],
|
||||
['admin'],
|
||||
);
|
||||
expect(generatedRoutes).toEqual([
|
||||
{ path: '/path1' },
|
||||
{ meta: {}, path: '/path2' },
|
||||
{ meta: { authority: ['admin'] }, path: '/path3' },
|
||||
]);
|
||||
});
|
||||
});
|
@@ -0,0 +1,66 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { filterTree, mapTree } from '@vben-core/toolkit';
|
||||
|
||||
/**
|
||||
* 动态生成路由 - 前端方式
|
||||
*/
|
||||
async function generateRoutesByFrontend(
|
||||
routes: RouteRecordRaw[],
|
||||
roles: string[],
|
||||
forbiddenComponent?: RouteRecordRaw['component'],
|
||||
): Promise<RouteRecordRaw[]> {
|
||||
// 根据角色标识过滤路由表,判断当前用户是否拥有指定权限
|
||||
const finalRoutes = filterTree(routes, (route) => {
|
||||
return hasVisible(route) && hasAuthority(route, roles);
|
||||
});
|
||||
|
||||
if (!forbiddenComponent) {
|
||||
return finalRoutes;
|
||||
}
|
||||
|
||||
// 如果有禁止访问的页面,将禁止访问的页面替换为403页面
|
||||
return mapTree(finalRoutes, (route) => {
|
||||
if (menuHasVisibleWithForbidden(route)) {
|
||||
route.component = forbiddenComponent;
|
||||
}
|
||||
return route;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断路由是否有权限访问
|
||||
* @param route
|
||||
* @param access
|
||||
*/
|
||||
function hasAuthority(route: RouteRecordRaw, access: string[]) {
|
||||
const authority = route.meta?.authority;
|
||||
if (!authority) {
|
||||
return true;
|
||||
}
|
||||
const canAccess = access.some((value) => authority.includes(value));
|
||||
|
||||
return canAccess || (!canAccess && menuHasVisibleWithForbidden(route));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断路由是否需要在菜单中显示
|
||||
* @param route
|
||||
*/
|
||||
function hasVisible(route?: RouteRecordRaw) {
|
||||
return !route?.meta?.hideInMenu;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断路由是否在菜单中显示,但是访问会被重定向到403
|
||||
* @param route
|
||||
*/
|
||||
function menuHasVisibleWithForbidden(route: RouteRecordRaw) {
|
||||
return (
|
||||
!!route.meta?.authority &&
|
||||
Reflect.has(route.meta || {}, 'menuVisibleWithForbidden') &&
|
||||
!!route.meta?.menuVisibleWithForbidden
|
||||
);
|
||||
}
|
||||
|
||||
export { generateRoutesByFrontend, hasAuthority, hasVisible };
|
@@ -1,2 +1,5 @@
|
||||
export * from './find-menu-by-path';
|
||||
export * from './generate-menus';
|
||||
export * from './generate-routes-backend';
|
||||
export * from './generate-routes-frontend';
|
||||
export * from './merge-route-modules';
|
||||
|
@@ -29,8 +29,6 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@vben-core/cache": "workspace:*",
|
||||
"@vben-core/colorful": "workspace:*",
|
||||
"@vben-core/toolkit": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import type { BuiltinThemeType } from '@vben-core/typings';
|
||||
|
||||
import type { SupportedLanguagesType } from './types';
|
||||
import type {
|
||||
BuiltinThemeType,
|
||||
SupportedLanguagesType,
|
||||
} from '@vben-core/typings';
|
||||
|
||||
interface Language {
|
||||
key: SupportedLanguagesType;
|
||||
|
@@ -5,17 +5,6 @@ import { PreferenceManager, isDarkTheme } from './preferences';
|
||||
|
||||
describe('preferences', () => {
|
||||
let preferenceManager: PreferenceManager;
|
||||
vi.mock('@vben-core/cache', () => {
|
||||
return {
|
||||
StorageManager: vi.fn().mockImplementation(() => {
|
||||
return {
|
||||
getItem: vi.fn(),
|
||||
removeItem: vi.fn(),
|
||||
setItem: vi.fn(),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
// 模拟 window.matchMedia 方法
|
||||
vi.stubGlobal(
|
||||
|
@@ -4,9 +4,12 @@ import type { Preferences } from './types';
|
||||
|
||||
import { markRaw, reactive, readonly, watch } from 'vue';
|
||||
|
||||
import { StorageManager } from '@vben-core/cache';
|
||||
import { generatorColorVariables } from '@vben-core/colorful';
|
||||
import { merge, updateCSSVariables } from '@vben-core/toolkit';
|
||||
import {
|
||||
StorageManager,
|
||||
generatorColorVariables,
|
||||
merge,
|
||||
updateCSSVariables,
|
||||
} from '@vben-core/toolkit';
|
||||
|
||||
import {
|
||||
breakpointsTailwind,
|
||||
|
@@ -1,28 +1,18 @@
|
||||
import type {
|
||||
AccessModeType,
|
||||
AuthPageLayoutType,
|
||||
BreadcrumbStyleType,
|
||||
BuiltinThemeType,
|
||||
ContentCompactType,
|
||||
LayoutHeaderModeType,
|
||||
LayoutType,
|
||||
LoginExpiredModeType,
|
||||
NavigationStyleType,
|
||||
PageTransitionType,
|
||||
SupportedLanguagesType,
|
||||
ThemeModeType,
|
||||
} from '@vben-core/typings';
|
||||
|
||||
/**
|
||||
* 登录过期模式
|
||||
* 'modal' 弹窗模式 | 'page' 页面模式
|
||||
*/
|
||||
type LoginExpiredModeType = 'modal' | 'page';
|
||||
|
||||
type BreadcrumbStyleType = 'background' | 'normal';
|
||||
|
||||
type AccessModeType = 'allow-all' | 'backend' | 'frontend';
|
||||
|
||||
type NavigationStyleType = 'plain' | 'rounded';
|
||||
|
||||
type PageTransitionType = 'fade' | 'fade-down' | 'fade-slide' | 'fade-up';
|
||||
|
||||
type AuthPageLayoutType = 'panel-center' | 'panel-left' | 'panel-right';
|
||||
|
||||
interface AppPreferences {
|
||||
/** 权限模式 */
|
||||
accessMode: AccessModeType;
|
||||
@@ -236,28 +226,17 @@ interface Preferences {
|
||||
type PreferencesKeys = keyof Preferences;
|
||||
|
||||
export type {
|
||||
AccessModeType,
|
||||
AppPreferences,
|
||||
AuthPageLayoutType,
|
||||
BreadcrumbPreferences,
|
||||
BreadcrumbStyleType,
|
||||
ContentCompactType,
|
||||
FooterPreferences,
|
||||
HeaderPreferences,
|
||||
LayoutHeaderModeType,
|
||||
LayoutType,
|
||||
LoginExpiredModeType,
|
||||
LogoPreferences,
|
||||
NavigationPreferences,
|
||||
NavigationStyleType,
|
||||
PageTransitionType,
|
||||
Preferences,
|
||||
PreferencesKeys,
|
||||
ShortcutKeyPreferences,
|
||||
SidebarPreferences,
|
||||
SupportedLanguagesType,
|
||||
TabbarPreferences,
|
||||
ThemeModeType,
|
||||
ThemePreferences,
|
||||
TransitionPreferences,
|
||||
WidgetPreferences,
|
||||
|
7
packages/@core/shared/cache/build.config.ts
vendored
7
packages/@core/shared/cache/build.config.ts
vendored
@@ -1,7 +0,0 @@
|
||||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
40
packages/@core/shared/cache/package.json
vendored
40
packages/@core/shared/cache/package.json
vendored
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"name": "@vben-core/cache",
|
||||
"version": "5.0.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/@vben-core/shared/cache"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm unbuild",
|
||||
"stub": "pnpm unbuild --stub"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {}
|
||||
}
|
6
packages/@core/shared/cache/tsconfig.json
vendored
6
packages/@core/shared/cache/tsconfig.json
vendored
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/library.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
});
|
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"name": "@vben-core/colorful",
|
||||
"version": "5.0.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/@vben-core/shared/colorful"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm unbuild",
|
||||
"stub": "pnpm unbuild --stub"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^7.1.0",
|
||||
"@ctrl/tinycolor": "^4.1.0"
|
||||
}
|
||||
}
|
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/library.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
# @vben-core/design-tokens
|
||||
|
||||
用于维护全局所有的 css 变量,它由 vite 插件在全局注入,不需要手动引入
|
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"name": "@vben-core/design-tokens",
|
||||
"version": "5.0.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/@vben-core/shared/design-tokens"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm vite build",
|
||||
"dts": "vue-tsc --declaration --emitDeclarationOnly --declarationDir dist",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"sideEffects": [
|
||||
"**/*.css"
|
||||
],
|
||||
"main": "./dist/index.css",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"default": "./dist/index.css"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/library.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
import { defineConfig } from '@vben/vite-config';
|
||||
|
||||
export default defineConfig();
|
@@ -1,5 +1,6 @@
|
||||
import './scss/index.scss';
|
||||
import './css/tailwind.css';
|
||||
import './css/nprogress.css';
|
||||
import './design-tokens';
|
||||
|
||||
export {};
|
||||
|
@@ -36,6 +36,8 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^7.1.0",
|
||||
"@ctrl/tinycolor": "^4.1.0",
|
||||
"@vue/shared": "^3.4.31",
|
||||
"clsx": "^2.1.1",
|
||||
"defu": "^6.1.4",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { convertToHsl, convertToHslCssVar, isValidColor } from './utils';
|
||||
import { convertToHsl, convertToHslCssVar, isValidColor } from './convert';
|
||||
|
||||
describe('color conversion functions', () => {
|
||||
it('should correctly convert color to HSL format', () => {
|
@@ -1,6 +1,6 @@
|
||||
import { generate } from '@ant-design/colors';
|
||||
|
||||
import { convertToHslCssVar } from './utils';
|
||||
import { convertToHslCssVar } from './convert';
|
||||
|
||||
export * from '@ant-design/colors';
|
||||
|
@@ -1,2 +1,2 @@
|
||||
export * from './convert';
|
||||
export * from './generator';
|
||||
export * from './utils';
|
@@ -1,4 +1,6 @@
|
||||
export * from './cache';
|
||||
export * from './cn';
|
||||
export * from './colorful';
|
||||
export * from './diff';
|
||||
export * from './dom';
|
||||
export * from './inference';
|
||||
|
22
packages/@core/shared/typings/src/app.d.ts
vendored
22
packages/@core/shared/typings/src/app.d.ts
vendored
@@ -33,11 +33,33 @@ type ContentCompactType = 'compact' | 'wide';
|
||||
|
||||
type LayoutHeaderModeType = 'auto' | 'auto-scroll' | 'fixed' | 'static';
|
||||
|
||||
/**
|
||||
* 登录过期模式
|
||||
* 'modal' 弹窗模式 | 'page' 页面模式
|
||||
*/
|
||||
type LoginExpiredModeType = 'modal' | 'page';
|
||||
|
||||
type BreadcrumbStyleType = 'background' | 'normal';
|
||||
|
||||
type AccessModeType = 'allow-all' | 'backend' | 'frontend';
|
||||
|
||||
type NavigationStyleType = 'plain' | 'rounded';
|
||||
|
||||
type PageTransitionType = 'fade' | 'fade-down' | 'fade-slide' | 'fade-up';
|
||||
|
||||
type AuthPageLayoutType = 'panel-center' | 'panel-left' | 'panel-right';
|
||||
|
||||
export type {
|
||||
AccessModeType,
|
||||
AuthPageLayoutType,
|
||||
BreadcrumbStyleType,
|
||||
BuiltinThemeType,
|
||||
ContentCompactType,
|
||||
LayoutHeaderModeType,
|
||||
LayoutType,
|
||||
LoginExpiredModeType,
|
||||
NavigationStyleType,
|
||||
PageTransitionType,
|
||||
SupportedLanguagesType,
|
||||
ThemeModeType,
|
||||
};
|
||||
|
33
packages/@core/shared/typings/src/basic.d.ts
vendored
Normal file
33
packages/@core/shared/typings/src/basic.d.ts
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
interface BasicOption {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface SelectOption extends BasicOption {}
|
||||
|
||||
interface TabsOption extends BasicOption {}
|
||||
|
||||
interface BasicUserInfo {
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
avatar: string;
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
realName: string;
|
||||
/**
|
||||
* 用户角色
|
||||
*/
|
||||
roles?: string[];
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
userId: string;
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
username: string;
|
||||
}
|
||||
|
||||
export type { BasicOption, BasicUserInfo, SelectOption, TabsOption };
|
@@ -1,4 +1,5 @@
|
||||
export type * from './app';
|
||||
export * from './basic';
|
||||
export type * from './helper';
|
||||
export type * from './menu-record';
|
||||
export type * from './tabs';
|
||||
|
@@ -1,3 +1,7 @@
|
||||
import type { RouteRecordRaw, Router } from 'vue-router';
|
||||
|
||||
import type { Component } from 'vue';
|
||||
|
||||
interface RouteMeta {
|
||||
/**
|
||||
* 是否固定标签页
|
||||
@@ -91,4 +95,27 @@ interface RouteMeta {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export type { RouteMeta };
|
||||
// 定义递归类型以将 RouteRecordRaw 的 component 属性更改为 string
|
||||
type RouteRecordStringComponent<T = string> = {
|
||||
children?: RouteRecordStringComponent<T>[];
|
||||
component: T;
|
||||
} & Omit<RouteRecordRaw, 'children' | 'component'>;
|
||||
|
||||
type ComponentRecordType = Record<string, () => Promise<Component>>;
|
||||
|
||||
interface GenerateMenuAndRoutesOptions {
|
||||
fetchMenuListAsync?: () => Promise<RouteRecordStringComponent[]>;
|
||||
forbiddenComponent?: RouteRecordRaw['component'];
|
||||
layoutMap?: ComponentRecordType;
|
||||
pageMap?: ComponentRecordType;
|
||||
roles?: string[];
|
||||
router: Router;
|
||||
routes: RouteRecordRaw[];
|
||||
}
|
||||
|
||||
export type {
|
||||
ComponentRecordType,
|
||||
GenerateMenuAndRoutesOptions,
|
||||
RouteMeta,
|
||||
RouteRecordStringComponent,
|
||||
};
|
||||
|
@@ -43,7 +43,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-icons/vue": "^1.0.0",
|
||||
"@vben-core/colorful": "workspace:*",
|
||||
"@vben-core/iconify": "workspace:*",
|
||||
"@vben-core/toolkit": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
|
@@ -3,7 +3,7 @@ import type { MenuRecordBadgeRaw } from '@vben-core/typings';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { isValidColor } from '@vben-core/colorful';
|
||||
import { isValidColor } from '@vben-core/toolkit';
|
||||
|
||||
import BadgeDot from './menu-badge-dot.vue';
|
||||
|
||||
|
Reference in New Issue
Block a user