handleClose(tab.path, e)}>
✕
diff --git a/src/config/fallbackRoutes.ts b/src/config/fallbackRoutes.ts
index be5d1c2..88d458b 100644
--- a/src/config/fallbackRoutes.ts
+++ b/src/config/fallbackRoutes.ts
@@ -11,12 +11,11 @@ import type { MenuNode } from '@/types';
*
* ```json
* [
- * { "id": "dashboard", "name": "工作台", "path": "dashboard", "component": "dashboard", "icon": "DashboardOutlined" },
+ * { "id": "dashboard", "name": "工作台", "path": "dashboard", "component": "dashboard", "icon": "AiOutlineDashboard" },
* {
- * "id": "events", "name": "赛事管理", "path": "events", "icon": "TrophyOutlined",
+ * "id": "events", "name": "赛事管理", "path": "events", "icon": "AiOutlineTrophy",
* "children": [
- * { "id": "events_list", "name": "赛事列表", "path": "events/list", "component": "events/list" },
- * { "id": "events_orders", "name": "订单管理", "path": "events/orders", "component": "events/orders" }
+ * { "id": "events_list", "name": "赛事列表", "path": "events/list", "component": "events/list", "icon": "AiOutlineUnorderedList" }
* ]
* }
* ]
@@ -29,7 +28,7 @@ import type { MenuNode } from '@/types';
* | name | string | 是 | 菜单名称/路由 name |
* | path | string | 是 | 路由相对路径(不带 / 前缀) |
* | component | string | 否 | 组件路径(映射 src/pages/ 下目录,如 "events/list") |
- * | icon | string | 否 | antd 图标名(如 DashboardOutlined) |
+ * | icon | string | 否 | vue-icons-plus 图标名(如 AiOutlineTrophy,需在 routeIcon.tsx 注册) |
* | sort | number | 否 | 排序权重(越小越前) |
* | hideInMenu | boolean | 否 | 是否在菜单中隐藏 |
* | externalLink | string | 否 | 外链地址 |
@@ -47,7 +46,7 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
id: 'events',
name: '赛事管理',
path: 'events',
- icon: 'TrophyOutlined',
+ icon: 'AiOutlineTrophy',
children: [
{
id: 'events_list',
@@ -55,6 +54,7 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
path: 'events/list',
component: 'events/list',
componentName: 'EventList',
+ icon: 'AiOutlineUnorderedList',
},
{
id: 'events_orders',
@@ -62,6 +62,7 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
path: 'events/orders',
component: 'events/orders',
componentName: 'EventOrders',
+ icon: 'AiOutlineShoppingCart',
},
{
id: 'events_users',
@@ -69,6 +70,7 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
path: 'events/users',
component: 'events/users',
componentName: 'EventUsers',
+ icon: 'AiOutlineTeam',
},
{
id: 'events_banner',
@@ -76,6 +78,7 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
path: 'events/banner',
component: 'events/banner',
componentName: 'EventBanner',
+ icon: 'AiOutlinePicture',
},
{
id: 'events_logs',
@@ -83,6 +86,7 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
path: 'events/logs',
component: 'events/logs',
componentName: 'EventLogs',
+ icon: 'AiOutlineHistory',
},
],
},
@@ -90,7 +94,7 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
id: 'finance',
name: '账务管理',
path: 'finance',
- icon: 'TransactionOutlined',
+ icon: 'AiOutlineTransaction',
children: [
{
id: 'finance_withdraw',
@@ -98,6 +102,7 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
path: 'finance/withdraw',
component: 'finance/withdraw',
componentName: 'FinanceWithdraw',
+ icon: 'AiOutlineMoneyCollect',
},
{
id: 'finance_wallet',
@@ -105,6 +110,7 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
path: 'finance/wallet',
component: 'finance/wallet',
componentName: 'FinanceWallet',
+ icon: 'AiOutlineWallet',
},
{
id: 'finance_payments',
@@ -112,6 +118,7 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
path: 'finance/payments',
component: 'finance/payments',
componentName: 'FinancePayments',
+ icon: 'AiOutlinePayCircle',
},
{
id: 'finance_reports',
@@ -119,6 +126,7 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
path: 'finance/reports',
component: 'finance/reports',
componentName: 'FinanceReports',
+ icon: 'AiOutlineBarChart',
},
],
},
@@ -126,7 +134,7 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
id: 'system',
name: '系统管理',
path: 'system',
- icon: 'SettingOutlined',
+ icon: 'AiOutlineSetting',
children: [
{
id: 'system_users',
@@ -134,6 +142,7 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
path: 'system/users',
component: 'system/users',
componentName: 'SystemUsers',
+ icon: 'AiOutlineUser',
},
{
id: 'system_roles',
@@ -141,6 +150,7 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
path: 'system/roles',
component: 'system/roles',
componentName: 'SystemRoles',
+ icon: 'AiOutlineSafetyCertificate',
},
{
id: 'system_logs',
@@ -148,6 +158,7 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [
path: 'system/logs',
component: 'system/logs',
componentName: 'SystemLogs',
+ icon: 'AiOutlineAudit',
},
],
},
diff --git a/src/layouts/BasicLayout.tsx b/src/layouts/BasicLayout.tsx
index dc95caf..52fc4ab 100644
--- a/src/layouts/BasicLayout.tsx
+++ b/src/layouts/BasicLayout.tsx
@@ -4,21 +4,17 @@ import { useRouter, useRoute, RouterView } from 'vue-router';
import { Layout, Menu, Modal, Breadcrumb, Dropdown, message } from 'ant-design-vue';
import type { MenuProps } from 'ant-design-vue';
import {
- DashboardOutlined,
EditOutlined,
- InfoCircleOutlined,
LogoutOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
- SettingOutlined,
- TransactionOutlined,
- TrophyOutlined,
} from '@ant-design/icons-vue';
import { useMenuStore } from '@/stores/menuStore';
import { useUserStore } from '@/stores/userStore';
import { useTabsStore } from '@/stores/tabsStore';
import { useState, useBreadcrumb, auth } from '@/hooks';
import { ChangePasswordModal, PageTabs } from '@/components';
+import { renderRouteIcon } from '@/utils/routeIcon';
import logoutSvg from '@/assets/img/icon/logout.svg';
import styles from './BasicLayout.module.less';
@@ -47,22 +43,9 @@ const footerStyle: CSSProperties = {
userSelect: 'none',
};
-/**
- * 图标名 → 组件映射
- * 后端下发的 icon 字符串在此映射为 antd icon 组件
- * 扩展新图标只需在此添加一行
- */
-const ICON_MAP: Record
= {
- DashboardOutlined,
- InfoCircleOutlined,
- SettingOutlined,
- TransactionOutlined,
- TrophyOutlined,
-};
-
/**
* 将 menuStore 中的 menuItems 转为 antd Menu 的 items 格式
- * 递归处理 iconName → icon VNode
+ * 递归处理 iconName → vue-icons-plus 图标 VNode
*/
function resolveMenuItems(items: any[]): MenuProps['items'] {
return items.map((item) => {
@@ -71,9 +54,8 @@ function resolveMenuItems(items: any[]): MenuProps['items'] {
label: item.label,
};
- // iconName 字符串转为 VNode
- if (item.iconName && ICON_MAP[item.iconName]) {
- resolved.icon = () => h(ICON_MAP[item.iconName]);
+ if (item.iconName) {
+ resolved.icon = () => renderRouteIcon(item.iconName, { size: 16 });
}
if (item.children?.length) {
diff --git a/src/pages/system/roles/index.tsx b/src/pages/system/roles/index.tsx
index 501bbed..2153780 100644
--- a/src/pages/system/roles/index.tsx
+++ b/src/pages/system/roles/index.tsx
@@ -56,14 +56,12 @@ function confirmDelete({ record, onOk }: { record: any; onOk: (record: any) => v
icon: null,
class: 'deleteConfirmModal',
content: (
- <>
-
- 确定要删除角色
- {record.roleName}
- 吗?
-
+
+
确定要删除角色
+
{record.roleName}
+
吗?
删除后该角色下的用户将失去对应的权限,请谨慎操作。
- >
+
),
okText: '确认',
cancelText: '取消',
diff --git a/src/router/guard.ts b/src/router/guard.ts
index b722907..87f1589 100644
--- a/src/router/guard.ts
+++ b/src/router/guard.ts
@@ -73,5 +73,6 @@ router.afterEach((to) => {
path: to.path,
name: componentName,
title: to.meta.title,
+ icon: to.meta.icon as string | undefined,
});
});
diff --git a/src/types/index.ts b/src/types/index.ts
index f0b8112..6fda827 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -28,6 +28,8 @@ export interface TabView {
path: string;
name: string;
title: string;
+ /** vue-icons-plus 图标名(如 AiOutlineTrophy) */
+ icon?: string;
}
// ── 菜单 & 权限 ──
@@ -52,7 +54,7 @@ export interface MenuNode {
* 留空或省略 → 纯布局节点(自动重定向到第一个子路由)
*/
component?: string;
- /** 菜单图标(antd icon 名,如 DashboardOutlined) */
+ /** 菜单图标(vue-icons-plus 导出名,如 AiOutlineTrophy) */
icon?: string;
/** 排序权重(越小越靠前) */
sort?: number;
@@ -79,8 +81,9 @@ export interface MenuNode {
*/
export interface MenuItemRaw {
key: string;
+ /** 解析后的图标 VNode(传给 antd Menu) */
icon?: any;
- /** antd icon 名称字符串,用于动态解析图标 */
+ /** vue-icons-plus 图标名(如 AiOutlineTrophy) */
iconName?: string;
label: string;
children?: MenuItemRaw[];
@@ -97,6 +100,7 @@ export type PermissionCode = string;
declare module 'vue-router' {
interface RouteMeta {
title?: string;
+ /** vue-icons-plus 图标名(如 AiOutlineTrophy) */
icon?: string;
/** 是否需要登录 */
requiresAuth?: boolean;
diff --git a/src/utils/routeIcon.tsx b/src/utils/routeIcon.tsx
new file mode 100644
index 0000000..400e0a0
--- /dev/null
+++ b/src/utils/routeIcon.tsx
@@ -0,0 +1,64 @@
+import { h, type VNode } from 'vue';
+import type { IconType } from 'vue-icons-plus/lib';
+import {
+ AiOutlineAudit,
+ AiOutlineBarChart,
+ AiOutlineHistory,
+ AiOutlineMoneyCollect,
+ AiOutlinePayCircle,
+ AiOutlinePicture,
+ AiOutlineSafetyCertificate,
+ AiOutlineSetting,
+ AiOutlineShoppingCart,
+ AiOutlineTeam,
+ AiOutlineTransaction,
+ AiOutlineTrophy,
+ AiOutlineUnorderedList,
+ AiOutlineUser,
+ AiOutlineWallet,
+} from 'vue-icons-plus/ai';
+
+/** 路由 / 菜单 / 标签页使用的图标名 → vue-icons-plus 组件 */
+const ROUTE_ICON_MAP: Record = {
+ AiOutlineTrophy,
+ AiOutlineUnorderedList,
+ AiOutlineShoppingCart,
+ AiOutlineTeam,
+ AiOutlinePicture,
+ AiOutlineHistory,
+ AiOutlineTransaction,
+ AiOutlineMoneyCollect,
+ AiOutlineWallet,
+ AiOutlinePayCircle,
+ AiOutlineBarChart,
+ AiOutlineSetting,
+ AiOutlineUser,
+ AiOutlineSafetyCertificate,
+ AiOutlineAudit,
+};
+
+export type RouteIconName = keyof typeof ROUTE_ICON_MAP;
+
+export interface RouteIconOptions {
+ size?: number | string;
+ class?: string;
+ color?: string;
+}
+
+/** 将路由 meta.icon 字符串渲染为图标 VNode */
+export function renderRouteIcon(iconName?: string, options: RouteIconOptions = {}): VNode | null {
+ if (!iconName) return null;
+
+ const Icon = ROUTE_ICON_MAP[iconName];
+ if (!Icon) {
+ console.warn(`[RouteIcon] 未注册的图标: ${iconName}`);
+ return null;
+ }
+
+ const { size = 14, class: className, color } = options;
+ return h(Icon, {
+ size,
+ class: className,
+ ...(color ? { color } : {}),
+ });
+}