90 lines
2.4 KiB
TypeScript
90 lines
2.4 KiB
TypeScript
import { h, type Component, type VNode } from 'vue';
|
||
import type { RouteRecordRaw } from 'vue-router';
|
||
|
||
/** 路由 meta 类型 */
|
||
export type AppMeta = {
|
||
/** 页面 / 菜单标题,同时用于面包屑 */
|
||
title: string;
|
||
/**
|
||
* 菜单图标(仅一级菜单需要),支持两种写法:
|
||
* - iconfont 的 class 名,如 'icon-changguan1'
|
||
* - 组件,如 ant-design 图标 BarChartOutlined
|
||
*/
|
||
icon?: Component | string;
|
||
/** 是否在侧边栏菜单中隐藏(详情页等) */
|
||
hideInMenu?: boolean;
|
||
/** 高亮的菜单 key(路径不等于菜单 key 的场景,如详情页) */
|
||
activeMenu?: string;
|
||
};
|
||
|
||
/**
|
||
* 快速创建路由记录
|
||
* 根据路径自动生成 PascalCase 路由 name
|
||
*/
|
||
export function createRoute(
|
||
path: string,
|
||
component: RouteRecordRaw['component'],
|
||
meta: AppMeta,
|
||
children: RouteRecordRaw[] = [],
|
||
): RouteRecordRaw {
|
||
const cleanPath = path.replace(/[:/]/g, '-');
|
||
|
||
const name = cleanPath
|
||
.split('-')
|
||
.filter(Boolean)
|
||
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
|
||
.join('');
|
||
|
||
return {
|
||
path,
|
||
name: name || undefined,
|
||
meta,
|
||
component,
|
||
children,
|
||
};
|
||
}
|
||
|
||
/** 侧边栏菜单项(ant-design-vue Menu 的 items 结构) */
|
||
export interface MenuItem {
|
||
key: string;
|
||
label: string;
|
||
icon?: VNode;
|
||
children?: MenuItem[];
|
||
}
|
||
|
||
/**
|
||
* 根据路由表生成侧边栏菜单
|
||
*
|
||
* @description
|
||
* 菜单完全由路由派生,路由是「菜单 / 面包屑」唯一的数据源:
|
||
* - 仅收录带 meta.title 且未标记 hideInMenu 的路由;
|
||
* - 有子路由的生成子菜单(父级本身不跳转,点击展开);
|
||
* - 图标取自 meta.icon。
|
||
*
|
||
* @param routes 需要遍历的路由数组(通常传入主布局的 children)
|
||
*/
|
||
export function generateMenu(routes: RouteRecordRaw[] = []): MenuItem[] {
|
||
const items: MenuItem[] = [];
|
||
|
||
for (const route of routes) {
|
||
const meta = route.meta as AppMeta | undefined;
|
||
if (!meta?.title || meta.hideInMenu) {
|
||
continue;
|
||
}
|
||
|
||
const children = generateMenu(route.children);
|
||
const item: MenuItem = { key: route.path, label: meta.title };
|
||
if (meta.icon) {
|
||
// 字符串 -> iconfont 图标(<i class="iconfont icon-xxx" />);组件 -> 直接渲染
|
||
item.icon =
|
||
typeof meta.icon === 'string' ? h('i', { class: ['iconfont', meta.icon] }) : h(meta.icon);
|
||
}
|
||
if (children.length) {
|
||
item.children = children;
|
||
}
|
||
items.push(item);
|
||
}
|
||
|
||
return items;
|
||
}
|