@robot-admin/layout 2.3.0 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["$slots"],"sources":["../src/constants/index.ts","../src/stores/settings.ts","../src/composables/useLayoutContext.ts","../src/composables/useLayoutCache.ts","../src/components/layouts/SideLayout/index.vue","../src/components/layouts/SideLayout/index.vue","../src/components/ResponsiveMenu/index.vue","../src/components/ResponsiveMenu/index.vue","../src/components/layouts/TopLayout/index.vue","../src/components/layouts/TopLayout/index.vue","../src/composables/useMenuSplit.ts","../src/components/layouts/MixLayout/index.vue","../src/components/layouts/MixLayout/index.vue","../src/components/layouts/MixTopLayout/index.vue","../src/components/layouts/MixTopLayout/index.vue","../src/components/layouts/ReverseHorizontalMixLayout/index.vue","../src/components/layouts/ReverseHorizontalMixLayout/index.vue","../src/components/layouts/CardLayout/index.vue","../src/components/layouts/CardLayout/index.vue","../src/components/C_LayoutContainer/index.vue","../src/components/C_LayoutContainer/index.vue","../src/components/SettingsDrawer/data.ts","../src/components/SettingsDrawer/index.vue","../src/components/SettingsDrawer/index.vue","../src/components/BrandLogo/index.vue","../src/components/BrandLogo/index.vue","../src/components/IconMenu/index.vue","../src/components/IconMenu/index.vue","../src/components/FloatingMenu/index.vue","../src/components/FloatingMenu/index.vue","../src/components/SideMenu/index.vue","../src/components/SideMenu/index.vue","../src/components/DrawerMenu/index.vue","../src/components/DrawerMenu/index.vue","../src/components/MenuTrigger/index.vue","../src/components/MenuTrigger/index.vue","../src/setup.ts"],"sourcesContent":["/**\n * 布局和设置系统 - 常量定义\n */\n\nimport type {\n ThemePreset,\n SettingsState,\n LayoutInfo,\n PresetColor,\n} from \"../types\";\n\n// ============ 主题色预设 ============\nexport const PRESET_COLORS: PresetColor[] = [\n { name: \"拂晓蓝\", value: \"#409eff\" },\n { name: \"薄暮红\", value: \"#f5222d\" },\n { name: \"火山橙\", value: \"#fa541c\" },\n { name: \"日暮黄\", value: \"#faad14\" },\n { name: \"极光绿\", value: \"#52c41a\" },\n { name: \"明青\", value: \"#13c2c2\" },\n { name: \"极客蓝\", value: \"#2f54eb\" },\n { name: \"酱紫\", value: \"#722ed1\" },\n] as const;\n\n// ============ 主题预设方案 ============\nexport const THEME_PRESETS: ThemePreset[] = [\n {\n name: \"科技蓝\",\n icon: \"💙\",\n primaryColor: \"#409eff\",\n },\n {\n name: \"清新绿\",\n icon: \"💚\",\n primaryColor: \"#52c41a\",\n },\n {\n name: \"商务灰\",\n icon: \"🖤\",\n primaryColor: \"#595959\",\n },\n {\n name: \"活力橙\",\n icon: \"🧡\",\n primaryColor: \"#fa8c16\",\n },\n {\n name: \"优雅紫\",\n icon: \"💜\",\n primaryColor: \"#722ed1\",\n },\n {\n name: \"经典红\",\n icon: \"❤️\",\n primaryColor: \"#f5222d\",\n },\n];\n\n// ============ 布局模式信息 ============\nexport const LAYOUT_MODES: LayoutInfo[] = [\n {\n mode: \"side\",\n label: \"左侧菜单\",\n description: \"经典的左侧导航布局\",\n icon: \"i-mdi:page-layout-sidebar-left\",\n },\n {\n mode: \"top\",\n label: \"顶部菜单\",\n description: \"顶部水平导航布局\",\n icon: \"i-mdi:page-layout-header\",\n },\n {\n mode: \"mix\",\n label: \"混合菜单\",\n description: \"顶部 + 侧边混合布局\",\n icon: \"i-mdi:view-dashboard-outline\",\n },\n {\n mode: \"mix-top\",\n label: \"顶部混合\",\n description: \"侧边优先的混合布局\",\n icon: \"i-mdi:view-dashboard-variant-outline\",\n },\n {\n mode: \"reverse-horizontal-mix\",\n label: \"反转混合\",\n description: \"反转的水平混合布局\",\n icon: \"i-mdi:page-layout-header-footer\",\n },\n {\n mode: \"card-layout\",\n label: \"卡片布局\",\n description: \"卡片式布局风格\",\n icon: \"i-mdi:card-outline\",\n },\n];\n\n// ============ 圆角映射 ============\nexport const BORDER_RADIUS_MAP = {\n small: \"4px\",\n medium: \"6px\",\n large: \"8px\",\n} as const;\n\n// ============ 动画映射 ============\nexport const TRANSITION_MAP = {\n fade: \"fade-transform\",\n slide: \"fade-slide\",\n zoom: \"fade-zoom\",\n none: \"\",\n} as const;\n\n// ============ 默认配置 ============\nexport const DEFAULT_SETTINGS: SettingsState = {\n // 外观\n themeMode: \"light\",\n primaryColor: \"#409eff\",\n borderRadius: \"medium\",\n transitionType: \"slide\",\n enableTransition: true,\n\n // 布局\n layoutMode: \"side\",\n menuExpandMode: \"inline\",\n collapsed: false,\n fixedHeader: true,\n showBreadcrumb: true,\n showBreadcrumbIcon: true,\n showTagsView: true,\n tagsViewHeight: 44,\n tagsViewStyle: \"default\",\n showFooter: true,\n sidebarWidth: 220,\n sidebarCollapsedWidth: 64,\n headerHeight: 56,\n\n // 高级\n enableHotkeys: true,\n version: \"2.0.2\",\n};\n\n// ============ 其他常量 ============\n\n/** Color swatches for color picker */\nexport const COLOR_SWATCHES = PRESET_COLORS.map((c) => c.value);\n","import { defineStore } from \"pinia\";\nimport { ref, computed, watch } from \"vue\";\nimport type { ThemeMode } from \"@robot-admin/theme\";\nimport type {\n SettingsState,\n ThemePreset,\n LayoutMode,\n MenuExpandMode,\n TransitionType,\n BorderRadiusSize,\n TagsViewStyle,\n SettingsStoreOptions,\n} from \"../types\";\nimport {\n DEFAULT_SETTINGS,\n BORDER_RADIUS_MAP,\n TRANSITION_MAP,\n} from \"../constants\";\n\n/**\n * 调整颜色亮度\n * @param color - 十六进制颜色值\n * @param amount - 调整量,正数变亮,负数变暗\n */\nexport function adjustColor(color: string, amount: number): string {\n const num = parseInt(color.replace(\"#\", \"\"), 16);\n const r = Math.min(255, Math.max(0, (num >> 16) + amount));\n const g = Math.min(255, Math.max(0, ((num >> 8) & 0x00ff) + amount));\n const b = Math.min(255, Math.max(0, (num & 0x0000ff) + amount));\n return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, \"0\")}`;\n}\n\n/**\n * 创建设置管理 Store\n * @param options - 配置选项\n */\nexport function createSettingsStore(options: SettingsStoreOptions = {}) {\n const { defaults = {}, onThemeModeChange } = options;\n\n // 合并默认配置\n const finalDefaults = {\n ...DEFAULT_SETTINGS,\n ...defaults,\n };\n\n return defineStore(\"settings\", () => {\n // ============ 状态定义 ============\n\n // 外观设置\n const themeMode = ref<ThemeMode>(finalDefaults.themeMode);\n const primaryColor = ref<string>(finalDefaults.primaryColor);\n const borderRadius = ref<BorderRadiusSize>(finalDefaults.borderRadius);\n const transitionType = ref<TransitionType>(finalDefaults.transitionType);\n const enableTransition = ref<boolean>(finalDefaults.enableTransition);\n\n // 布局设置\n const layoutMode = ref<LayoutMode>(finalDefaults.layoutMode);\n const menuExpandMode = ref<MenuExpandMode>(finalDefaults.menuExpandMode);\n const collapsed = ref<boolean>(finalDefaults.collapsed);\n const fixedHeader = ref<boolean>(finalDefaults.fixedHeader);\n const showBreadcrumb = ref<boolean>(finalDefaults.showBreadcrumb);\n const showBreadcrumbIcon = ref<boolean>(finalDefaults.showBreadcrumbIcon);\n const showTagsView = ref<boolean>(finalDefaults.showTagsView);\n const tagsViewHeight = ref<number>(finalDefaults.tagsViewHeight);\n const tagsViewStyle = ref<TagsViewStyle>(finalDefaults.tagsViewStyle);\n const showFooter = ref<boolean>(finalDefaults.showFooter);\n const sidebarWidth = ref<number>(finalDefaults.sidebarWidth);\n const sidebarCollapsedWidth = ref<number>(\n finalDefaults.sidebarCollapsedWidth,\n );\n const headerHeight = ref<number>(finalDefaults.headerHeight);\n\n // 高级设置\n const enableHotkeys = ref<boolean>(finalDefaults.enableHotkeys);\n const version = ref<string>(finalDefaults.version);\n\n // ============ 计算属性 ============\n\n /** 获取圆角值 */\n const borderRadiusValue = computed(\n () => BORDER_RADIUS_MAP[borderRadius.value],\n );\n\n /** 获取过渡动画名称 */\n const transitionName = computed(() => TRANSITION_MAP[transitionType.value]);\n\n /** 是否启用过渡动画 */\n const shouldEnableTransition = computed(\n () => enableTransition.value && transitionType.value !== \"none\",\n );\n\n /** 获取完整的设置状态 */\n const settingsState = computed<SettingsState>(() => ({\n themeMode: themeMode.value,\n primaryColor: primaryColor.value,\n borderRadius: borderRadius.value,\n transitionType: transitionType.value,\n enableTransition: enableTransition.value,\n layoutMode: layoutMode.value,\n menuExpandMode: menuExpandMode.value,\n collapsed: collapsed.value,\n fixedHeader: fixedHeader.value,\n showBreadcrumb: showBreadcrumb.value,\n showBreadcrumbIcon: showBreadcrumbIcon.value,\n showTagsView: showTagsView.value,\n tagsViewHeight: tagsViewHeight.value,\n tagsViewStyle: tagsViewStyle.value,\n showFooter: showFooter.value,\n sidebarWidth: sidebarWidth.value,\n sidebarCollapsedWidth: sidebarCollapsedWidth.value,\n headerHeight: headerHeight.value,\n enableHotkeys: enableHotkeys.value,\n version: version.value,\n }));\n\n // ============ 方法 ============\n\n /**\n * 同步 CSS Variables\n */\n const syncCSSVariables = () => {\n if (typeof document === \"undefined\") return;\n\n const root = document.documentElement;\n\n // 主题色\n root.style.setProperty(\"--primary-color\", primaryColor.value);\n root.style.setProperty(\n \"--primary-color-hover\",\n adjustColor(primaryColor.value, 10),\n );\n root.style.setProperty(\n \"--primary-color-pressed\",\n adjustColor(primaryColor.value, -10),\n );\n\n // 圆角\n root.style.setProperty(\"--border-radius\", borderRadiusValue.value);\n\n // 尺寸\n root.style.setProperty(\"--sidebar-width\", `${sidebarWidth.value}px`);\n root.style.setProperty(\n \"--sidebar-collapsed-width\",\n `${sidebarCollapsedWidth.value}px`,\n );\n root.style.setProperty(\"--header-height\", `${headerHeight.value}px`);\n root.style.setProperty(\"--tags-view-height\", `${tagsViewHeight.value}px`);\n };\n\n /**\n * 应用主题预设方案\n * 注意:只应用颜色配置,不改变布局模式和主题模式\n */\n const applyPreset = async (preset: ThemePreset) => {\n // 只应用主题色\n primaryColor.value = preset.primaryColor;\n\n // 应用其他细节配置(如果存在)\n if (preset.settings) {\n if (preset.settings.borderRadius)\n borderRadius.value = preset.settings.borderRadius;\n if (preset.settings.transitionType)\n transitionType.value = preset.settings.transitionType;\n if (preset.settings.showBreadcrumb !== undefined)\n showBreadcrumb.value = preset.settings.showBreadcrumb;\n if (preset.settings.showTagsView !== undefined)\n showTagsView.value = preset.settings.showTagsView;\n if (preset.settings.tagsViewStyle)\n tagsViewStyle.value = preset.settings.tagsViewStyle;\n }\n };\n\n /**\n * 重置配置\n */\n const resetSettings = () => {\n themeMode.value = finalDefaults.themeMode;\n primaryColor.value = finalDefaults.primaryColor;\n borderRadius.value = finalDefaults.borderRadius;\n transitionType.value = finalDefaults.transitionType;\n enableTransition.value = finalDefaults.enableTransition;\n\n layoutMode.value = finalDefaults.layoutMode;\n menuExpandMode.value = finalDefaults.menuExpandMode;\n collapsed.value = finalDefaults.collapsed;\n fixedHeader.value = finalDefaults.fixedHeader;\n showBreadcrumb.value = finalDefaults.showBreadcrumb;\n showBreadcrumbIcon.value = finalDefaults.showBreadcrumbIcon;\n showTagsView.value = finalDefaults.showTagsView;\n tagsViewHeight.value = finalDefaults.tagsViewHeight;\n tagsViewStyle.value = finalDefaults.tagsViewStyle;\n showFooter.value = finalDefaults.showFooter;\n sidebarWidth.value = finalDefaults.sidebarWidth;\n sidebarCollapsedWidth.value = finalDefaults.sidebarCollapsedWidth;\n headerHeight.value = finalDefaults.headerHeight;\n\n enableHotkeys.value = finalDefaults.enableHotkeys;\n };\n\n /**\n * 更新主题模式\n */\n const updateThemeMode = async (mode: ThemeMode) => {\n themeMode.value = mode;\n if (onThemeModeChange) {\n await onThemeModeChange(mode);\n }\n };\n\n /**\n * 切换侧边栏折叠状态\n */\n const toggleCollapse = () => {\n collapsed.value = !collapsed.value;\n };\n\n // ============ 监听器 ============\n\n // 监听配置变化,同步 CSS Variables\n watch(\n [\n primaryColor,\n borderRadiusValue,\n sidebarWidth,\n sidebarCollapsedWidth,\n headerHeight,\n tagsViewHeight,\n ],\n () => {\n syncCSSVariables();\n },\n { immediate: true },\n );\n\n // ============ 返回 ============\n\n return {\n // 状态\n themeMode,\n primaryColor,\n borderRadius,\n transitionType,\n enableTransition,\n layoutMode,\n menuExpandMode,\n collapsed,\n fixedHeader,\n showBreadcrumb,\n showBreadcrumbIcon,\n showTagsView,\n tagsViewHeight,\n tagsViewStyle,\n showFooter,\n sidebarWidth,\n sidebarCollapsedWidth,\n headerHeight,\n enableHotkeys,\n version,\n\n // 计算属性\n borderRadiusValue,\n transitionName,\n shouldEnableTransition,\n settingsState,\n\n // 方法\n syncCSSVariables,\n applyPreset,\n resetSettings,\n updateThemeMode,\n toggleCollapse,\n };\n });\n}\n\n/**\n * 默认的设置 Store(使用默认配置)\n */\nexport const useSettingsStore = createSettingsStore();\n","/**\r\n * @robot-admin/layout\r\n *\r\n * 布局上下文 - Provide/Inject 机制\r\n * 消费方通过 provide 注入数据,骨架组件通过 inject 读取\r\n * 实现布局骨架与业务数据的完全解耦\r\n */\r\n\r\nimport type { InjectionKey, ComputedRef, Component } from \"vue\";\r\nimport { inject } from \"vue\";\r\nimport type { MenuOptions } from \"../types/menu\";\r\nimport type { LayoutMode, MenuExpandMode } from \"../types\";\r\n\r\n/**\r\n * 品牌配置\r\n */\r\nexport interface LayoutBrandConfig {\r\n /** 品牌名称,如 'Robot Admin' */\r\n name?: string;\r\n /** 品牌副标题,如 '机器人管理系统' */\r\n subtitle?: string;\r\n /** Logo 地址(图片或视频) */\r\n logoSrc?: string;\r\n /** Logo 类型 */\r\n logoType?: \"video\" | \"image\";\r\n /** Logo 尺寸 */\r\n logoSize?: number;\r\n /** 首页路径,默认 '/home' */\r\n homePath?: string;\r\n /** 页脚版权文本 */\r\n copyright?: string;\r\n}\r\n\r\n/**\r\n * 布局上下文数据接口\r\n * 骨架组件通过 inject 获取这些数据\r\n */\r\nexport interface LayoutContext {\r\n // ============ 菜单数据 ============\r\n /** 菜单列表数据 */\r\n menus: ComputedRef<MenuOptions[]>;\r\n\r\n // ============ 主题状态 ============\r\n /** 是否为暗色模式 */\r\n isDark: ComputedRef<boolean>;\r\n\r\n // ============ 布局配置(来自 settings store) ============\r\n /** 当前布局模式 */\r\n layoutMode: ComputedRef<LayoutMode>;\r\n /** 菜单展开方式 */\r\n menuExpandMode?: ComputedRef<MenuExpandMode>;\r\n /** 侧边栏宽度 (px) */\r\n sidebarWidth: ComputedRef<number>;\r\n /** 侧边栏折叠后宽度 (px) */\r\n sidebarCollapsedWidth: ComputedRef<number>;\r\n /** 是否显示页脚 */\r\n showFooter: ComputedRef<boolean>;\r\n /** 是否显示标签页 */\r\n showTagsView: ComputedRef<boolean>;\r\n /** 标签页高度 (px) */\r\n tagsViewHeight: ComputedRef<number>;\r\n /** 头部高度 (px) */\r\n headerHeight: ComputedRef<number>;\r\n /** 过渡动画名称 */\r\n transitionName: ComputedRef<string>;\r\n /** 是否显示面包屑 */\r\n showBreadcrumb: ComputedRef<boolean>;\r\n /** 是否显示面包屑图标 */\r\n showBreadcrumbIcon: ComputedRef<boolean>;\r\n /** 是否固定头部 */\r\n fixedHeader: ComputedRef<boolean>;\r\n\r\n // ============ 品牌配置 ============\r\n /** 品牌信息 */\r\n brand: LayoutBrandConfig;\r\n\r\n // ============ 可选:图标组件 ============\r\n /**\r\n * 自定义图标组件(如 @iconify/vue 的 Icon 组件)\r\n * 用于布局内部组件渲染菜单图标\r\n * 组件需接受 name (string) 和 size (number) 两个 props\r\n * 如果不提供,将使用 CSS class 渲染图标\r\n */\r\n iconComponent?: Component;\r\n}\r\n\r\n/**\r\n * 布局上下文 Injection Key\r\n */\r\nexport const LAYOUT_CONTEXT_KEY: InjectionKey<LayoutContext> =\r\n Symbol(\"layout-context\");\r\n\r\n/**\r\n * 抽屉菜单统一控制器\r\n * 解决 MenuTrigger 与 DrawerMenu 各自拥有独立定时器导致的\r\n * \"鼠标从 trigger 移到 drawer 过程中 drawer 被关闭\"问题\r\n */\r\nexport interface DrawerHandlers {\r\n /** 显示抽屉并取消所有待执行的隐藏操作 */\r\n show: () => void;\r\n /** 启动延迟隐藏(300ms) */\r\n startHide: () => void;\r\n /** 立即隐藏抽屉(如导航跳转后) */\r\n hide: () => void;\r\n}\r\n\r\n/**\r\n * 抽屉菜单控制器 Injection Key\r\n */\r\nexport const DRAWER_HANDLER_KEY: InjectionKey<DrawerHandlers> =\r\n Symbol(\"drawer-handlers\");\r\n\r\n/**\r\n * 在骨架组件中获取布局上下文\r\n *\r\n * @throws 如果没有 provide LayoutContext 会抛出错误\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup>\r\n * // 在包的骨架组件内部使用\r\n * const ctx = useLayoutContext()\r\n * const menus = ctx.menus\r\n * const isDark = ctx.isDark\r\n * </script>\r\n * ```\r\n */\r\nexport function useLayoutContext(): LayoutContext {\r\n const ctx = inject(LAYOUT_CONTEXT_KEY);\r\n if (!ctx) {\r\n throw new Error(\r\n \"[Layout] LayoutContext not provided. \" +\r\n \"请在上层组件中使用 provide(LAYOUT_CONTEXT_KEY, ...) 注入布局数据。\",\r\n );\r\n }\r\n return ctx;\r\n}\r\n\r\n/**\r\n * 默认品牌配置\r\n */\r\nexport const DEFAULT_BRAND_CONFIG: Required<LayoutBrandConfig> = {\r\n name: \"Admin\",\r\n subtitle: \"\",\r\n logoSrc: \"\",\r\n logoType: \"image\",\r\n logoSize: 36,\r\n homePath: \"/home\",\r\n copyright: \"\",\r\n};\r\n","/**\r\n * @robot-admin/layout\r\n *\r\n * 统一的 KeepAlive 缓存管理\r\n * 从主项目提取的通用 composable,零业务依赖\r\n */\r\n\r\nimport { ref, watch } from \"vue\";\r\nimport { useRoute } from \"vue-router\";\r\n\r\n/** KeepAlive 缓存选项 */\r\nexport interface LayoutCacheOptions {\r\n /** 最大缓存数量,默认 20 */\r\n maxCacheCount?: number;\r\n /** 是否在开发模式下输出日志,默认 true */\r\n enableDevLog?: boolean;\r\n /** 是否暴露调试方法到 window,默认 true */\r\n exposeToWindow?: boolean;\r\n}\r\n\r\n/**\r\n * 统一的 KeepAlive 缓存管理 composable\r\n *\r\n * @param options - 缓存配置选项\r\n * @returns 缓存视图列表和管理方法\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup>\r\n * import { useLayoutCache } from '@robot-admin/layout'\r\n *\r\n * const { cachedViews, maxCacheCount } = useLayoutCache({ maxCacheCount: 30 })\r\n * </script>\r\n *\r\n * <template>\r\n * <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\r\n * <RouterView />\r\n * </KeepAlive>\r\n * </template>\r\n * ```\r\n */\r\nexport function useLayoutCache(options: LayoutCacheOptions = {}) {\r\n const {\r\n maxCacheCount: maxCount = 20,\r\n enableDevLog = true,\r\n exposeToWindow: expose = true,\r\n } = options;\r\n\r\n const route = useRoute();\r\n const cachedViews = ref<string[]>([]);\r\n const maxCacheCount = ref(maxCount);\r\n\r\n const isDev =\r\n typeof import.meta !== \"undefined\" &&\r\n (import.meta as any).env?.DEV === true;\r\n\r\n /**\r\n * 判断页面是否应该被缓存\r\n * 极简策略:只有明确配置 meta.keepAlive = true 才缓存\r\n */\r\n const shouldCache = (routeName: string | symbol | undefined | null) => {\r\n if (!routeName || typeof routeName !== \"string\") return false;\r\n return route.meta?.keepAlive === true;\r\n };\r\n\r\n /** 添加缓存 */\r\n const addCache = (name: string) => {\r\n if (!cachedViews.value.includes(name) && shouldCache(name)) {\r\n cachedViews.value.push(name);\r\n\r\n // 控制缓存数量\r\n if (cachedViews.value.length > maxCacheCount.value) {\r\n cachedViews.value.shift();\r\n }\r\n\r\n if (isDev && enableDevLog) {\r\n console.debug(\r\n `[KeepAlive] ✅ 缓存: ${name} (${cachedViews.value.length}/${maxCacheCount.value})`,\r\n );\r\n }\r\n }\r\n };\r\n\r\n /** 移除缓存 */\r\n const removeCache = (name: string) => {\r\n const index = cachedViews.value.indexOf(name);\r\n if (index > -1) {\r\n cachedViews.value.splice(index, 1);\r\n if (isDev && enableDevLog) {\r\n console.debug(`[KeepAlive] ❌ 移除: ${name}`);\r\n }\r\n }\r\n };\r\n\r\n /** 清空所有缓存 */\r\n const clearAllCache = () => {\r\n cachedViews.value = [];\r\n if (isDev && enableDevLog) {\r\n console.debug(\"[KeepAlive] 🗑️ 清空所有缓存\");\r\n }\r\n };\r\n\r\n // 暴露调试方法到 window\r\n if (isDev && expose && typeof window !== \"undefined\") {\r\n (window as any).__clearCache__ = clearAllCache;\r\n (window as any).__removeCache__ = removeCache;\r\n (window as any).__getCachedViews__ = () => cachedViews.value;\r\n }\r\n\r\n // 监听路由变化,动态管理缓存\r\n watch(\r\n () => route.name,\r\n (newName) => {\r\n if (newName && typeof newName === \"string\") {\r\n addCache(newName);\r\n }\r\n },\r\n { immediate: true },\r\n );\r\n\r\n return {\r\n cachedViews,\r\n maxCacheCount,\r\n addCache,\r\n removeCache,\r\n clearAllCache,\r\n };\r\n}\r\n","<!--\n * @robot-admin/layout - SideLayout\n *\n * 左侧菜单布局骨架\n * 经典的左侧导航 + 右侧内容布局,使用 NLayout/NLayoutSider 实现\n *\n * Slots:\n * #logo - Logo/品牌区域(侧边栏顶部)\n * #menu - 垂直菜单区域(侧边栏)\n * #header - 头部区域(含面包屑、导航栏右侧操作等)\n * #default - 页面内容(自动包含 RouterView + KeepAlive + Transition)\n * #footer - 页脚区域\n -->\n<template>\n <NLayout has-sider>\n <NLayoutSider\n bordered\n collapse-mode=\"width\"\n :collapsed-width=\"ctx.sidebarCollapsedWidth.value\"\n :width=\"ctx.sidebarWidth.value\"\n :native-scrollbar=\"false\"\n :collapsed=\"collapsed\"\n @update:collapsed=\"handleCollapsedUpdate\"\n :class=\"[\n 'layout-sider',\n 'no-horizontal-scroll',\n ctx.isDark.value ? 'dark-theme' : 'light-theme',\n ]\"\n >\n <slot name=\"logo\" />\n <slot name=\"menu\" :collapsed=\"collapsed\" />\n </NLayoutSider>\n\n <NLayout>\n <slot name=\"header\" />\n\n <NLayoutContent class=\"content-with-header p16px app-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"ctx.transitionName.value\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </NLayoutContent>\n\n <slot name=\"footer\" />\n </NLayout>\n </NLayout>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref, watch } from \"vue\";\nimport { NLayout, NLayoutSider, NLayoutContent } from \"naive-ui\";\nimport { useLayoutContext } from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\n\ndefineOptions({ name: \"SideLayout\" });\n\nconst props = withDefaults(\n defineProps<{\n /** 控制侧边栏折叠状态 (支持 v-model:collapsed) */\n collapsed?: boolean;\n }>(),\n { collapsed: false },\n);\n\nconst emit = defineEmits<{\n \"update:collapsed\": [value: boolean];\n}>();\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\n// 内部折叠状态,与 prop 双向同步\nconst _collapsed = ref(props.collapsed);\nwatch(\n () => props.collapsed,\n (v) => {\n _collapsed.value = v;\n },\n);\n\nconst collapsed = computed(() => _collapsed.value);\n\nconst handleCollapsedUpdate = (val: boolean) => {\n _collapsed.value = val;\n emit(\"update:collapsed\", val);\n};\n\n// 折叠切换\nconst toggleCollapse = () => {\n const next = !_collapsed.value;\n _collapsed.value = next;\n emit(\"update:collapsed\", next);\n};\n\ndefineExpose({\n collapsed,\n toggleCollapse,\n});\n</script>\n","<!--\n * @robot-admin/layout - SideLayout\n *\n * 左侧菜单布局骨架\n * 经典的左侧导航 + 右侧内容布局,使用 NLayout/NLayoutSider 实现\n *\n * Slots:\n * #logo - Logo/品牌区域(侧边栏顶部)\n * #menu - 垂直菜单区域(侧边栏)\n * #header - 头部区域(含面包屑、导航栏右侧操作等)\n * #default - 页面内容(自动包含 RouterView + KeepAlive + Transition)\n * #footer - 页脚区域\n -->\n<template>\n <NLayout has-sider>\n <NLayoutSider\n bordered\n collapse-mode=\"width\"\n :collapsed-width=\"ctx.sidebarCollapsedWidth.value\"\n :width=\"ctx.sidebarWidth.value\"\n :native-scrollbar=\"false\"\n :collapsed=\"collapsed\"\n @update:collapsed=\"handleCollapsedUpdate\"\n :class=\"[\n 'layout-sider',\n 'no-horizontal-scroll',\n ctx.isDark.value ? 'dark-theme' : 'light-theme',\n ]\"\n >\n <slot name=\"logo\" />\n <slot name=\"menu\" :collapsed=\"collapsed\" />\n </NLayoutSider>\n\n <NLayout>\n <slot name=\"header\" />\n\n <NLayoutContent class=\"content-with-header p16px app-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"ctx.transitionName.value\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </NLayoutContent>\n\n <slot name=\"footer\" />\n </NLayout>\n </NLayout>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref, watch } from \"vue\";\nimport { NLayout, NLayoutSider, NLayoutContent } from \"naive-ui\";\nimport { useLayoutContext } from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\n\ndefineOptions({ name: \"SideLayout\" });\n\nconst props = withDefaults(\n defineProps<{\n /** 控制侧边栏折叠状态 (支持 v-model:collapsed) */\n collapsed?: boolean;\n }>(),\n { collapsed: false },\n);\n\nconst emit = defineEmits<{\n \"update:collapsed\": [value: boolean];\n}>();\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\n// 内部折叠状态,与 prop 双向同步\nconst _collapsed = ref(props.collapsed);\nwatch(\n () => props.collapsed,\n (v) => {\n _collapsed.value = v;\n },\n);\n\nconst collapsed = computed(() => _collapsed.value);\n\nconst handleCollapsedUpdate = (val: boolean) => {\n _collapsed.value = val;\n emit(\"update:collapsed\", val);\n};\n\n// 折叠切换\nconst toggleCollapse = () => {\n const next = !_collapsed.value;\n _collapsed.value = next;\n emit(\"update:collapsed\", next);\n};\n\ndefineExpose({\n collapsed,\n toggleCollapse,\n});\n</script>\n","<!--\r\n * @robot-admin/layout - ResponsiveMenu\r\n *\r\n * 响应式水平菜单\r\n * 用于 TopLayout / MixTopLayout / ReverseHorizontalMixLayout 的水平菜单\r\n * 自动检测容器宽度,溢出项放入“更多”下拉菜单,使用 NMenu (naive-ui) 渲染\r\n -->\r\n<template>\r\n <div ref=\"containerRef\" class=\"responsive-menu\">\r\n <div ref=\"visibleRef\" class=\"responsive-menu__visible\">\r\n <NMenu\r\n v-if=\"visibleOptions.length > 0\"\r\n mode=\"horizontal\"\r\n :value=\"activeKey\"\r\n :options=\"visibleOptions\"\r\n :theme-overrides=\"menuThemeOverrides\"\r\n @update:value=\"handleSelect\"\r\n />\r\n </div>\r\n\r\n <NDropdown\r\n v-if=\"hiddenItems.length > 0\"\r\n :options=\"dropdownOptions\"\r\n placement=\"bottom-end\"\r\n trigger=\"hover\"\r\n @select=\"handleSelect\"\r\n >\r\n <div class=\"responsive-menu__more\">\r\n <NButton text class=\"responsive-menu__more-btn\">\r\n <!-- 纯 CSS 三个点图标 -->\r\n <span class=\"dots-icon\">\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n </span>\r\n </NButton>\r\n </div>\r\n </NDropdown>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, onMounted, onUnmounted, nextTick, h } from \"vue\";\r\nimport { useRoute, useRouter } from \"vue-router\";\r\nimport { NMenu, NDropdown, NButton } from \"naive-ui\";\r\nimport type { MenuOption, DropdownOption } from \"naive-ui\";\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"ResponsiveMenu\" });\r\n\r\nconst props = defineProps<{\r\n /** 菜单数据 */\r\n data: MenuOptions[];\r\n}>();\r\n\r\nconst route = useRoute();\r\nconst router = useRouter();\r\nconst ctx = useLayoutContext();\r\n\r\n// 安全包装\r\nconst menuData = computed(() => props.data || []);\r\nconst activeKey = computed(() => route.path);\r\n\r\n// 根据主题选择菜单样式\r\nconst isDarkMode = ctx.isDark;\r\n\r\nconst lightTheme = {\r\n itemTextColor: \"rgba(51, 65, 85, 0.85)\",\r\n itemTextColorHover: \"rgba(30, 64, 175, 0.95)\",\r\n itemTextColorActive: \"rgba(30, 64, 175, 1)\",\r\n itemTextColorActiveHover: \"rgba(30, 64, 175, 1)\",\r\n itemTextColorChildActive: \"rgba(30, 64, 175, 1)\",\r\n itemIconColor: \"rgba(51, 65, 85, 0.7)\",\r\n itemIconColorHover: \"rgba(30, 64, 175, 0.85)\",\r\n itemIconColorActive: \"rgba(30, 64, 175, 1)\",\r\n itemIconColorActiveHover: \"rgba(30, 64, 175, 1)\",\r\n itemIconColorChildActive: \"rgba(30, 64, 175, 1)\",\r\n itemColorHover: \"rgba(30, 64, 175, 0.06)\",\r\n itemColorActive: \"rgba(30, 64, 175, 0.1)\",\r\n itemColorActiveHover: \"rgba(30, 64, 175, 0.12)\",\r\n itemColorActiveCollapsed: \"rgba(30, 64, 175, 0.1)\",\r\n arrowColor: \"rgba(51, 65, 85, 0.6)\",\r\n arrowColorHover: \"rgba(30, 64, 175, 0.8)\",\r\n arrowColorActive: \"rgba(30, 64, 175, 1)\",\r\n arrowColorChildActive: \"rgba(30, 64, 175, 1)\",\r\n itemHeight: \"42px\",\r\n itemPadding: \"0 18px\",\r\n itemBorderRadius: \"10px\",\r\n groupTextColor: \"rgba(51, 65, 85, 0.65)\",\r\n};\r\n\r\nconst darkTheme = {\r\n itemTextColor: \"rgba(226, 232, 240, 0.85)\",\r\n itemTextColorHover: \"rgba(255, 255, 255, 0.95)\",\r\n itemTextColorActive: \"rgba(147, 197, 253, 1)\",\r\n itemTextColorActiveHover: \"rgba(147, 197, 253, 1)\",\r\n itemTextColorChildActive: \"rgba(147, 197, 253, 1)\",\r\n itemIconColor: \"rgba(226, 232, 240, 0.7)\",\r\n itemIconColorHover: \"rgba(255, 255, 255, 0.85)\",\r\n itemIconColorActive: \"rgba(147, 197, 253, 1)\",\r\n itemIconColorActiveHover: \"rgba(147, 197, 253, 1)\",\r\n itemIconColorChildActive: \"rgba(147, 197, 253, 1)\",\r\n itemColorHover: \"rgba(59, 130, 246, 0.1)\",\r\n itemColorActive: \"rgba(59, 130, 246, 0.15)\",\r\n itemColorActiveHover: \"rgba(59, 130, 246, 0.18)\",\r\n itemColorActiveCollapsed: \"rgba(59, 130, 246, 0.15)\",\r\n arrowColor: \"rgba(226, 232, 240, 0.5)\",\r\n arrowColorHover: \"rgba(255, 255, 255, 0.7)\",\r\n arrowColorActive: \"rgba(147, 197, 253, 1)\",\r\n arrowColorChildActive: \"rgba(147, 197, 253, 1)\",\r\n itemHeight: \"42px\",\r\n itemPadding: \"0 18px\",\r\n itemBorderRadius: \"10px\",\r\n groupTextColor: \"rgba(226, 232, 240, 0.6)\",\r\n};\r\n\r\nconst menuThemeOverrides = computed(() =>\r\n isDarkMode.value ? darkTheme : lightTheme,\r\n);\r\n\r\n// ============ 图标渲染 ============\r\n\r\nconst renderIcon = (icon: string, size = 18) => {\r\n if (ctx.iconComponent) {\r\n return () => h(ctx.iconComponent!, { name: icon, size });\r\n }\r\n return () =>\r\n h(\"i\", {\r\n class: icon,\r\n style: {\r\n fontSize: `${size}px`,\r\n display: \"inline-flex\",\r\n alignItems: \"center\",\r\n },\r\n });\r\n};\r\n\r\n// ============ 菜单转换 ============\r\n\r\nconst convertToMenuOption = (item: MenuOptions): MenuOption => ({\r\n key: item.path || \"\",\r\n label: item.meta?.title || item.name || \"\",\r\n icon: item.meta?.icon ? renderIcon(item.meta.icon, 18) : undefined,\r\n children: item.children?.length\r\n ? item.children.map(convertToMenuOption)\r\n : undefined,\r\n});\r\n\r\n// ============ 响应式计算 ============\r\n\r\nconst containerRef = ref<HTMLElement>();\r\nconst visibleItems = ref<MenuOptions[]>([]);\r\nconst hiddenItems = ref<MenuOptions[]>([]);\r\n\r\nconst visibleOptions = computed<MenuOption[]>(() =>\r\n visibleItems.value.map(convertToMenuOption),\r\n);\r\n\r\nconst dropdownOptions = computed<DropdownOption[]>(() =>\r\n hiddenItems.value.map((item) => ({\r\n key: item.path || \"\",\r\n label: item.meta?.title || item.name || \"\",\r\n icon: item.meta?.icon ? renderIcon(item.meta.icon, 16) : undefined,\r\n children: item.children?.length\r\n ? item.children.map((child) => ({\r\n key: child.path || \"\",\r\n label: child.meta?.title || child.name || \"\",\r\n icon: child.meta?.icon ? renderIcon(child.meta.icon, 16) : undefined,\r\n }))\r\n : undefined,\r\n })),\r\n);\r\n\r\nconst handleSelect = (key: string) => {\r\n if (key) router.push(key);\r\n};\r\n\r\n// ============ 宽度估算 ============\r\n\r\nconst estimateItemWidth = (item: MenuOptions): number => {\r\n const ITEM_PADDING = 36; // itemPadding: \"0 18px\" → 18*2\r\n const ITEM_MARGIN = 8;\r\n const ICON_WIDTH = 26;\r\n const EXTRA = 8;\r\n\r\n const title = item.meta?.title || item.name || \"\";\r\n let textWidth = 0;\r\n for (const char of title) {\r\n const code = char.charCodeAt(0);\r\n // CJK / 全角字符约 15px(14px 字号下的实际宽度)\r\n if (\r\n (code >= 0x4e00 && code <= 0x9fff) || // CJK 统一汉字\r\n (code >= 0x3400 && code <= 0x4dbf) || // CJK 扩展 A\r\n (code >= 0x3000 && code <= 0x303f) || // CJK 符号\r\n (code >= 0xff00 && code <= 0xffef) || // 全角字符\r\n (code >= 0x3040 && code <= 0x309f) || // 平假名\r\n (code >= 0x30a0 && code <= 0x30ff) // 片假名\r\n ) {\r\n textWidth += 15;\r\n } else {\r\n textWidth += 8;\r\n }\r\n }\r\n const iconWidth = item.meta?.icon ? ICON_WIDTH : 0;\r\n\r\n return textWidth + iconWidth + ITEM_PADDING + ITEM_MARGIN + EXTRA;\r\n};\r\n\r\nconst calculateVisibleCount = (containerWidth: number): number => {\r\n if (!menuData.value.length) return 0;\r\n\r\n const MORE_BTN_WIDTH = 80;\r\n const SAFETY = 15;\r\n\r\n let totalWidth = 0;\r\n for (const item of menuData.value) {\r\n totalWidth += estimateItemWidth(item);\r\n }\r\n\r\n if (totalWidth + SAFETY <= containerWidth) {\r\n return menuData.value.length;\r\n }\r\n\r\n let usedWidth = 0;\r\n let count = 0;\r\n const available = containerWidth - MORE_BTN_WIDTH - SAFETY;\r\n\r\n for (const item of menuData.value) {\r\n const w = estimateItemWidth(item);\r\n if (usedWidth + w <= available) {\r\n usedWidth += w;\r\n count++;\r\n } else break;\r\n }\r\n\r\n return Math.max(count, 1);\r\n};\r\n\r\nconst calculateVisibleItems = () => {\r\n if (!containerRef.value || !menuData.value.length) return;\r\n\r\n const containerWidth = containerRef.value.offsetWidth;\r\n if (containerWidth === 0) {\r\n nextTick(() => calculateVisibleItems());\r\n return;\r\n }\r\n\r\n const count = calculateVisibleCount(containerWidth);\r\n visibleItems.value = menuData.value.slice(0, count);\r\n hiddenItems.value = menuData.value.slice(count);\r\n};\r\n\r\n// ============ 防抖 & ResizeObserver ============\r\n\r\nlet resizeTimer: number | null = null;\r\nconst debouncedCalculate = () => {\r\n if (resizeTimer) clearTimeout(resizeTimer);\r\n resizeTimer = window.setTimeout(() => calculateVisibleItems(), 100);\r\n};\r\n\r\nlet resizeObserver: ResizeObserver | null = null;\r\n\r\nonMounted(() => {\r\n nextTick(() => calculateVisibleItems());\r\n if (containerRef.value) {\r\n resizeObserver = new ResizeObserver(() => debouncedCalculate());\r\n resizeObserver.observe(containerRef.value);\r\n }\r\n});\r\n\r\nonUnmounted(() => {\r\n resizeObserver?.disconnect();\r\n if (resizeTimer) clearTimeout(resizeTimer);\r\n});\r\n\r\nwatch(\r\n () => props.data,\r\n () => nextTick(() => calculateVisibleItems()),\r\n {\r\n deep: true,\r\n },\r\n);\r\n</script>\r\n\r\n<style scoped>\r\n.responsive-menu {\r\n display: flex;\r\n align-items: center;\r\n width: 100%;\r\n gap: 0;\r\n}\r\n\r\n.responsive-menu__visible {\r\n flex: 0 1 auto;\r\n min-width: 0;\r\n overflow: hidden;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu) {\r\n background-color: transparent !important;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item) {\r\n position: relative;\r\n font-weight: 500;\r\n letter-spacing: 0.01em;\r\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n/* 防止 NMenu 内部截断菜单项文字 */\r\n.responsive-menu__visible :deep(.n-menu-item-content-header) {\r\n overflow: visible !important;\r\n text-overflow: clip !important;\r\n white-space: nowrap !important;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item-content) {\r\n overflow: visible !important;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item::after) {\r\n content: \"\";\r\n position: absolute;\r\n bottom: 0;\r\n left: 50%;\r\n transform: translateX(-50%) scaleX(0);\r\n width: 60%;\r\n height: 2px;\r\n background: linear-gradient(90deg, transparent, currentColor, transparent);\r\n opacity: 0;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item.n-menu-item--selected::after) {\r\n transform: translateX(-50%) scaleX(1);\r\n opacity: 0.5;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item:hover) {\r\n transform: translateY(-1px);\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item.n-menu-item--selected) {\r\n box-shadow:\r\n 0 2px 8px -2px rgba(30, 64, 175, 0.15),\r\n 0 1px 3px rgba(30, 64, 175, 0.1);\r\n}\r\n\r\n.responsive-menu__more {\r\n flex-shrink: 0;\r\n margin-left: 4px;\r\n}\r\n\r\n.responsive-menu__more-btn {\r\n min-width: 42px;\r\n height: 42px;\r\n padding: 0 18px !important;\r\n border-radius: 10px !important;\r\n background-color: transparent !important;\r\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.responsive-menu__more-btn::before,\r\n.responsive-menu__more-btn :deep(.n-button__border),\r\n.responsive-menu__more-btn :deep(.n-button__state-border) {\r\n display: none !important;\r\n}\r\n\r\n.responsive-menu__more-btn:hover {\r\n background-color: rgba(30, 64, 175, 0.06) !important;\r\n transform: translateY(-1px);\r\n}\r\n\r\n/* 纯 CSS 三个点图标 */\r\n.dots-icon {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 3px;\r\n width: 20px;\r\n height: 20px;\r\n}\r\n\r\n.dots-icon span {\r\n width: 4px;\r\n height: 4px;\r\n border-radius: 50%;\r\n background-color: currentColor;\r\n opacity: 0.85;\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - ResponsiveMenu\r\n *\r\n * 响应式水平菜单\r\n * 用于 TopLayout / MixTopLayout / ReverseHorizontalMixLayout 的水平菜单\r\n * 自动检测容器宽度,溢出项放入“更多”下拉菜单,使用 NMenu (naive-ui) 渲染\r\n -->\r\n<template>\r\n <div ref=\"containerRef\" class=\"responsive-menu\">\r\n <div ref=\"visibleRef\" class=\"responsive-menu__visible\">\r\n <NMenu\r\n v-if=\"visibleOptions.length > 0\"\r\n mode=\"horizontal\"\r\n :value=\"activeKey\"\r\n :options=\"visibleOptions\"\r\n :theme-overrides=\"menuThemeOverrides\"\r\n @update:value=\"handleSelect\"\r\n />\r\n </div>\r\n\r\n <NDropdown\r\n v-if=\"hiddenItems.length > 0\"\r\n :options=\"dropdownOptions\"\r\n placement=\"bottom-end\"\r\n trigger=\"hover\"\r\n @select=\"handleSelect\"\r\n >\r\n <div class=\"responsive-menu__more\">\r\n <NButton text class=\"responsive-menu__more-btn\">\r\n <!-- 纯 CSS 三个点图标 -->\r\n <span class=\"dots-icon\">\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n </span>\r\n </NButton>\r\n </div>\r\n </NDropdown>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, onMounted, onUnmounted, nextTick, h } from \"vue\";\r\nimport { useRoute, useRouter } from \"vue-router\";\r\nimport { NMenu, NDropdown, NButton } from \"naive-ui\";\r\nimport type { MenuOption, DropdownOption } from \"naive-ui\";\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"ResponsiveMenu\" });\r\n\r\nconst props = defineProps<{\r\n /** 菜单数据 */\r\n data: MenuOptions[];\r\n}>();\r\n\r\nconst route = useRoute();\r\nconst router = useRouter();\r\nconst ctx = useLayoutContext();\r\n\r\n// 安全包装\r\nconst menuData = computed(() => props.data || []);\r\nconst activeKey = computed(() => route.path);\r\n\r\n// 根据主题选择菜单样式\r\nconst isDarkMode = ctx.isDark;\r\n\r\nconst lightTheme = {\r\n itemTextColor: \"rgba(51, 65, 85, 0.85)\",\r\n itemTextColorHover: \"rgba(30, 64, 175, 0.95)\",\r\n itemTextColorActive: \"rgba(30, 64, 175, 1)\",\r\n itemTextColorActiveHover: \"rgba(30, 64, 175, 1)\",\r\n itemTextColorChildActive: \"rgba(30, 64, 175, 1)\",\r\n itemIconColor: \"rgba(51, 65, 85, 0.7)\",\r\n itemIconColorHover: \"rgba(30, 64, 175, 0.85)\",\r\n itemIconColorActive: \"rgba(30, 64, 175, 1)\",\r\n itemIconColorActiveHover: \"rgba(30, 64, 175, 1)\",\r\n itemIconColorChildActive: \"rgba(30, 64, 175, 1)\",\r\n itemColorHover: \"rgba(30, 64, 175, 0.06)\",\r\n itemColorActive: \"rgba(30, 64, 175, 0.1)\",\r\n itemColorActiveHover: \"rgba(30, 64, 175, 0.12)\",\r\n itemColorActiveCollapsed: \"rgba(30, 64, 175, 0.1)\",\r\n arrowColor: \"rgba(51, 65, 85, 0.6)\",\r\n arrowColorHover: \"rgba(30, 64, 175, 0.8)\",\r\n arrowColorActive: \"rgba(30, 64, 175, 1)\",\r\n arrowColorChildActive: \"rgba(30, 64, 175, 1)\",\r\n itemHeight: \"42px\",\r\n itemPadding: \"0 18px\",\r\n itemBorderRadius: \"10px\",\r\n groupTextColor: \"rgba(51, 65, 85, 0.65)\",\r\n};\r\n\r\nconst darkTheme = {\r\n itemTextColor: \"rgba(226, 232, 240, 0.85)\",\r\n itemTextColorHover: \"rgba(255, 255, 255, 0.95)\",\r\n itemTextColorActive: \"rgba(147, 197, 253, 1)\",\r\n itemTextColorActiveHover: \"rgba(147, 197, 253, 1)\",\r\n itemTextColorChildActive: \"rgba(147, 197, 253, 1)\",\r\n itemIconColor: \"rgba(226, 232, 240, 0.7)\",\r\n itemIconColorHover: \"rgba(255, 255, 255, 0.85)\",\r\n itemIconColorActive: \"rgba(147, 197, 253, 1)\",\r\n itemIconColorActiveHover: \"rgba(147, 197, 253, 1)\",\r\n itemIconColorChildActive: \"rgba(147, 197, 253, 1)\",\r\n itemColorHover: \"rgba(59, 130, 246, 0.1)\",\r\n itemColorActive: \"rgba(59, 130, 246, 0.15)\",\r\n itemColorActiveHover: \"rgba(59, 130, 246, 0.18)\",\r\n itemColorActiveCollapsed: \"rgba(59, 130, 246, 0.15)\",\r\n arrowColor: \"rgba(226, 232, 240, 0.5)\",\r\n arrowColorHover: \"rgba(255, 255, 255, 0.7)\",\r\n arrowColorActive: \"rgba(147, 197, 253, 1)\",\r\n arrowColorChildActive: \"rgba(147, 197, 253, 1)\",\r\n itemHeight: \"42px\",\r\n itemPadding: \"0 18px\",\r\n itemBorderRadius: \"10px\",\r\n groupTextColor: \"rgba(226, 232, 240, 0.6)\",\r\n};\r\n\r\nconst menuThemeOverrides = computed(() =>\r\n isDarkMode.value ? darkTheme : lightTheme,\r\n);\r\n\r\n// ============ 图标渲染 ============\r\n\r\nconst renderIcon = (icon: string, size = 18) => {\r\n if (ctx.iconComponent) {\r\n return () => h(ctx.iconComponent!, { name: icon, size });\r\n }\r\n return () =>\r\n h(\"i\", {\r\n class: icon,\r\n style: {\r\n fontSize: `${size}px`,\r\n display: \"inline-flex\",\r\n alignItems: \"center\",\r\n },\r\n });\r\n};\r\n\r\n// ============ 菜单转换 ============\r\n\r\nconst convertToMenuOption = (item: MenuOptions): MenuOption => ({\r\n key: item.path || \"\",\r\n label: item.meta?.title || item.name || \"\",\r\n icon: item.meta?.icon ? renderIcon(item.meta.icon, 18) : undefined,\r\n children: item.children?.length\r\n ? item.children.map(convertToMenuOption)\r\n : undefined,\r\n});\r\n\r\n// ============ 响应式计算 ============\r\n\r\nconst containerRef = ref<HTMLElement>();\r\nconst visibleItems = ref<MenuOptions[]>([]);\r\nconst hiddenItems = ref<MenuOptions[]>([]);\r\n\r\nconst visibleOptions = computed<MenuOption[]>(() =>\r\n visibleItems.value.map(convertToMenuOption),\r\n);\r\n\r\nconst dropdownOptions = computed<DropdownOption[]>(() =>\r\n hiddenItems.value.map((item) => ({\r\n key: item.path || \"\",\r\n label: item.meta?.title || item.name || \"\",\r\n icon: item.meta?.icon ? renderIcon(item.meta.icon, 16) : undefined,\r\n children: item.children?.length\r\n ? item.children.map((child) => ({\r\n key: child.path || \"\",\r\n label: child.meta?.title || child.name || \"\",\r\n icon: child.meta?.icon ? renderIcon(child.meta.icon, 16) : undefined,\r\n }))\r\n : undefined,\r\n })),\r\n);\r\n\r\nconst handleSelect = (key: string) => {\r\n if (key) router.push(key);\r\n};\r\n\r\n// ============ 宽度估算 ============\r\n\r\nconst estimateItemWidth = (item: MenuOptions): number => {\r\n const ITEM_PADDING = 36; // itemPadding: \"0 18px\" → 18*2\r\n const ITEM_MARGIN = 8;\r\n const ICON_WIDTH = 26;\r\n const EXTRA = 8;\r\n\r\n const title = item.meta?.title || item.name || \"\";\r\n let textWidth = 0;\r\n for (const char of title) {\r\n const code = char.charCodeAt(0);\r\n // CJK / 全角字符约 15px(14px 字号下的实际宽度)\r\n if (\r\n (code >= 0x4e00 && code <= 0x9fff) || // CJK 统一汉字\r\n (code >= 0x3400 && code <= 0x4dbf) || // CJK 扩展 A\r\n (code >= 0x3000 && code <= 0x303f) || // CJK 符号\r\n (code >= 0xff00 && code <= 0xffef) || // 全角字符\r\n (code >= 0x3040 && code <= 0x309f) || // 平假名\r\n (code >= 0x30a0 && code <= 0x30ff) // 片假名\r\n ) {\r\n textWidth += 15;\r\n } else {\r\n textWidth += 8;\r\n }\r\n }\r\n const iconWidth = item.meta?.icon ? ICON_WIDTH : 0;\r\n\r\n return textWidth + iconWidth + ITEM_PADDING + ITEM_MARGIN + EXTRA;\r\n};\r\n\r\nconst calculateVisibleCount = (containerWidth: number): number => {\r\n if (!menuData.value.length) return 0;\r\n\r\n const MORE_BTN_WIDTH = 80;\r\n const SAFETY = 15;\r\n\r\n let totalWidth = 0;\r\n for (const item of menuData.value) {\r\n totalWidth += estimateItemWidth(item);\r\n }\r\n\r\n if (totalWidth + SAFETY <= containerWidth) {\r\n return menuData.value.length;\r\n }\r\n\r\n let usedWidth = 0;\r\n let count = 0;\r\n const available = containerWidth - MORE_BTN_WIDTH - SAFETY;\r\n\r\n for (const item of menuData.value) {\r\n const w = estimateItemWidth(item);\r\n if (usedWidth + w <= available) {\r\n usedWidth += w;\r\n count++;\r\n } else break;\r\n }\r\n\r\n return Math.max(count, 1);\r\n};\r\n\r\nconst calculateVisibleItems = () => {\r\n if (!containerRef.value || !menuData.value.length) return;\r\n\r\n const containerWidth = containerRef.value.offsetWidth;\r\n if (containerWidth === 0) {\r\n nextTick(() => calculateVisibleItems());\r\n return;\r\n }\r\n\r\n const count = calculateVisibleCount(containerWidth);\r\n visibleItems.value = menuData.value.slice(0, count);\r\n hiddenItems.value = menuData.value.slice(count);\r\n};\r\n\r\n// ============ 防抖 & ResizeObserver ============\r\n\r\nlet resizeTimer: number | null = null;\r\nconst debouncedCalculate = () => {\r\n if (resizeTimer) clearTimeout(resizeTimer);\r\n resizeTimer = window.setTimeout(() => calculateVisibleItems(), 100);\r\n};\r\n\r\nlet resizeObserver: ResizeObserver | null = null;\r\n\r\nonMounted(() => {\r\n nextTick(() => calculateVisibleItems());\r\n if (containerRef.value) {\r\n resizeObserver = new ResizeObserver(() => debouncedCalculate());\r\n resizeObserver.observe(containerRef.value);\r\n }\r\n});\r\n\r\nonUnmounted(() => {\r\n resizeObserver?.disconnect();\r\n if (resizeTimer) clearTimeout(resizeTimer);\r\n});\r\n\r\nwatch(\r\n () => props.data,\r\n () => nextTick(() => calculateVisibleItems()),\r\n {\r\n deep: true,\r\n },\r\n);\r\n</script>\r\n\r\n<style scoped>\r\n.responsive-menu {\r\n display: flex;\r\n align-items: center;\r\n width: 100%;\r\n gap: 0;\r\n}\r\n\r\n.responsive-menu__visible {\r\n flex: 0 1 auto;\r\n min-width: 0;\r\n overflow: hidden;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu) {\r\n background-color: transparent !important;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item) {\r\n position: relative;\r\n font-weight: 500;\r\n letter-spacing: 0.01em;\r\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n/* 防止 NMenu 内部截断菜单项文字 */\r\n.responsive-menu__visible :deep(.n-menu-item-content-header) {\r\n overflow: visible !important;\r\n text-overflow: clip !important;\r\n white-space: nowrap !important;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item-content) {\r\n overflow: visible !important;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item::after) {\r\n content: \"\";\r\n position: absolute;\r\n bottom: 0;\r\n left: 50%;\r\n transform: translateX(-50%) scaleX(0);\r\n width: 60%;\r\n height: 2px;\r\n background: linear-gradient(90deg, transparent, currentColor, transparent);\r\n opacity: 0;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item.n-menu-item--selected::after) {\r\n transform: translateX(-50%) scaleX(1);\r\n opacity: 0.5;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item:hover) {\r\n transform: translateY(-1px);\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item.n-menu-item--selected) {\r\n box-shadow:\r\n 0 2px 8px -2px rgba(30, 64, 175, 0.15),\r\n 0 1px 3px rgba(30, 64, 175, 0.1);\r\n}\r\n\r\n.responsive-menu__more {\r\n flex-shrink: 0;\r\n margin-left: 4px;\r\n}\r\n\r\n.responsive-menu__more-btn {\r\n min-width: 42px;\r\n height: 42px;\r\n padding: 0 18px !important;\r\n border-radius: 10px !important;\r\n background-color: transparent !important;\r\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.responsive-menu__more-btn::before,\r\n.responsive-menu__more-btn :deep(.n-button__border),\r\n.responsive-menu__more-btn :deep(.n-button__state-border) {\r\n display: none !important;\r\n}\r\n\r\n.responsive-menu__more-btn:hover {\r\n background-color: rgba(30, 64, 175, 0.06) !important;\r\n transform: translateY(-1px);\r\n}\r\n\r\n/* 纯 CSS 三个点图标 */\r\n.dots-icon {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 3px;\r\n width: 20px;\r\n height: 20px;\r\n}\r\n\r\n.dots-icon span {\r\n width: 4px;\r\n height: 4px;\r\n border-radius: 50%;\r\n background-color: currentColor;\r\n opacity: 0.85;\r\n}\r\n</style>\r\n","<!--\n * @robot-admin/layout - TopLayout\n *\n * 顶部导航布局骨架\n * Logo+品牌 | 水平菜单 | 操作区 → 标签页 → 内容区 → 页脚\n *\n * Slots:\n * #logo - Logo/品牌区域\n * #menu - 水平菜单(默认使用 ResponsiveMenu)\n * #header-extra - 头部右侧操作区\n * #tags-view - 标签页区域\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"top-layout-container\">\n <!-- 顶部导航栏 -->\n <div\n class=\"top-navbar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- 左侧:Logo 和品牌 -->\n <div class=\"navbar-left\">\n <slot name=\"logo\">\n <div class=\"logo-container\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n />\n </div>\n <div class=\"brand-name\">\n <span class=\"brand-title\">{{ brand.name }}</span>\n <span class=\"brand-subtitle\">{{ brand.subtitle }}</span>\n </div>\n <div class=\"navbar-divider\"></div>\n </slot>\n </div>\n\n <!-- 中间:水平菜单 -->\n <div class=\"navbar-center\">\n <slot name=\"menu\">\n <ResponsiveMenu :data=\"menus\" />\n </slot>\n </div>\n\n <!-- 右侧:操作区 -->\n <slot name=\"header-extra\" />\n </div>\n\n <!-- 标签页区域 -->\n <div\n v-if=\"showTagsView\"\n class=\"tags-view-container\"\n :style=\"{ height: `${tagsViewHeight}px` }\"\n >\n <slot name=\"tags-view\" />\n </div>\n\n <!-- 主内容区域 -->\n <NLayout>\n <NLayoutContent class=\"main-content\">\n <div class=\"page-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </div>\n </NLayoutContent>\n\n <!-- 页脚 -->\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { computed } from \"vue\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport ResponsiveMenu from \"../../ResponsiveMenu/index.vue\";\n\ndefineOptions({ name: \"TopLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showTagsView = computed(() => ctx.showTagsView.value);\nconst tagsViewHeight = computed(() => ctx.tagsViewHeight.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n</script>\n","<!--\n * @robot-admin/layout - TopLayout\n *\n * 顶部导航布局骨架\n * Logo+品牌 | 水平菜单 | 操作区 → 标签页 → 内容区 → 页脚\n *\n * Slots:\n * #logo - Logo/品牌区域\n * #menu - 水平菜单(默认使用 ResponsiveMenu)\n * #header-extra - 头部右侧操作区\n * #tags-view - 标签页区域\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"top-layout-container\">\n <!-- 顶部导航栏 -->\n <div\n class=\"top-navbar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- 左侧:Logo 和品牌 -->\n <div class=\"navbar-left\">\n <slot name=\"logo\">\n <div class=\"logo-container\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n />\n </div>\n <div class=\"brand-name\">\n <span class=\"brand-title\">{{ brand.name }}</span>\n <span class=\"brand-subtitle\">{{ brand.subtitle }}</span>\n </div>\n <div class=\"navbar-divider\"></div>\n </slot>\n </div>\n\n <!-- 中间:水平菜单 -->\n <div class=\"navbar-center\">\n <slot name=\"menu\">\n <ResponsiveMenu :data=\"menus\" />\n </slot>\n </div>\n\n <!-- 右侧:操作区 -->\n <slot name=\"header-extra\" />\n </div>\n\n <!-- 标签页区域 -->\n <div\n v-if=\"showTagsView\"\n class=\"tags-view-container\"\n :style=\"{ height: `${tagsViewHeight}px` }\"\n >\n <slot name=\"tags-view\" />\n </div>\n\n <!-- 主内容区域 -->\n <NLayout>\n <NLayoutContent class=\"main-content\">\n <div class=\"page-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </div>\n </NLayoutContent>\n\n <!-- 页脚 -->\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { computed } from \"vue\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport ResponsiveMenu from \"../../ResponsiveMenu/index.vue\";\n\ndefineOptions({ name: \"TopLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showTagsView = computed(() => ctx.showTagsView.value);\nconst tagsViewHeight = computed(() => ctx.tagsViewHeight.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n</script>\n","/**\r\n * @robot-admin/layout\r\n *\r\n * 菜单拆分 Composable - 通用的一级/二级菜单拆分逻辑\r\n * 用于 Mix / MixTop / ReverseHorizontalMix 等需要菜单拆分的布局\r\n */\r\n\r\nimport {\r\n ref,\r\n computed,\r\n watch,\r\n onMounted,\r\n toValue,\r\n type ComputedRef,\r\n type Ref,\r\n type MaybeRefOrGetter,\r\n} from \"vue\";\r\nimport { useRoute, useRouter } from \"vue-router\";\r\nimport type { MenuOptions } from \"../types/menu\";\r\n\r\nexport interface UseMenuSplitOptions {\r\n /** 菜单数据(响应式) */\r\n menus: ComputedRef<MenuOptions[]>;\r\n /** 是否使用悬浮式二级菜单(MixLayout 的 toggle 模式) */\r\n floatingSecondMenu?: MaybeRefOrGetter<boolean>;\r\n}\r\n\r\nexport interface UseMenuSplitReturn {\r\n /** 当前激活的一级菜单路径 */\r\n activeFirstMenu: Ref<string>;\r\n /** 当前激活的一级菜单完整数据 */\r\n activeFirstMenuItem: ComputedRef<MenuOptions | undefined>;\r\n /** 当前一级菜单对应的二级菜单列表 */\r\n currentSecondMenus: ComputedRef<MenuOptions[]>;\r\n /** 悬浮的菜单项(MixLayout 使用) */\r\n hoveredMenuItem: Ref<MenuOptions | null>;\r\n /** 实际展示的菜单项(hovered 优先,fallback 到 active) */\r\n displayMenuItem: ComputedRef<MenuOptions | null>;\r\n /** 是否显示悬浮二级菜单(MixLayout 使用) */\r\n showSecondMenu: Ref<boolean>;\r\n /** 处理一级菜单点击 */\r\n handleFirstMenuClick: (item: MenuOptions) => void;\r\n /** 处理二级菜单点击 */\r\n handleSecondMenuClick: (item: MenuOptions) => void;\r\n /** 检查路由是否匹配菜单项 */\r\n isMenuItemActive: (menuPath: string | undefined) => boolean;\r\n}\r\n\r\n/**\r\n * 菜单拆分 Composable\r\n *\r\n * 提供一级/二级菜单拆分状态管理、路由自动匹配、菜单点击处理。\r\n * 供 MixLayout / MixTopLayout / ReverseHorizontalMixLayout 共用。\r\n *\r\n * @example\r\n * ```ts\r\n * const menuSplit = useMenuSplit({\r\n * menus: ctx.menus,\r\n * floatingSecondMenu: computed(() => layoutMode.value === 'mix'),\r\n * })\r\n * ```\r\n */\r\nexport function useMenuSplit(options: UseMenuSplitOptions): UseMenuSplitReturn {\r\n const { menus, floatingSecondMenu = false } = options;\r\n const route = useRoute();\r\n const router = useRouter();\r\n\r\n const activeFirstMenu = ref<string>(\"\");\r\n const hoveredMenuItem = ref<MenuOptions | null>(null);\r\n const showSecondMenu = ref(false);\r\n\r\n /** 当前展示在悬浮面板上的菜单项(优先 hovered,fallback 到 active) */\r\n const displayMenuItem = computed<MenuOptions | null>(\r\n () => hoveredMenuItem.value || activeFirstMenuItem.value || null,\r\n );\r\n\r\n // ============ 路径匹配 ============\r\n\r\n const normalizePath = (path: string) =>\r\n path.startsWith(\"/\") ? path : `/${path}`;\r\n\r\n const isMenuItemActive = (menuPath: string | undefined): boolean => {\r\n if (!menuPath) return false;\r\n const currentPath = route.path;\r\n if (currentPath === menuPath) return true;\r\n const normalizedMenuPath = normalizePath(menuPath);\r\n const normalizedCurrentPath = normalizePath(currentPath);\r\n if (normalizedCurrentPath === normalizedMenuPath) return true;\r\n return normalizedCurrentPath.includes(`/${menuPath}`);\r\n };\r\n\r\n // ============ 查找一级菜单 ============\r\n\r\n const findActiveTopMenu = (items: MenuOptions[]): MenuOptions | null => {\r\n for (const item of items) {\r\n if (isMenuItemActive(item.path)) return item;\r\n if (item.children?.length) {\r\n const found = findActiveTopMenu(item.children);\r\n if (found) return item;\r\n }\r\n }\r\n return null;\r\n };\r\n\r\n // ============ 计算属性 ============\r\n\r\n const activeFirstMenuItem = computed(() =>\r\n menus.value.find((item) => item.path === activeFirstMenu.value),\r\n );\r\n\r\n const currentSecondMenus = computed(\r\n () => activeFirstMenuItem.value?.children || [],\r\n );\r\n\r\n // ============ 交互处理 ============\r\n\r\n const handleFirstMenuClick = (item: MenuOptions) => {\r\n if (toValue(floatingSecondMenu)) {\r\n // MixLayout: 纯点击模式\r\n if (item.children && item.children.length > 0) {\r\n if (showSecondMenu.value && activeFirstMenu.value === item.path) {\r\n // 点击同一个已展开的菜单 → 关闭\r\n showSecondMenu.value = false;\r\n } else {\r\n // 点击另一个或未展开 → 展开/切换\r\n activeFirstMenu.value = item.path || \"\";\r\n showSecondMenu.value = true;\r\n }\r\n } else {\r\n // 没有子菜单 → 关闭二级面板并跳转\r\n activeFirstMenu.value = item.path || \"\";\r\n showSecondMenu.value = false;\r\n if (item.path) router.push(item.path);\r\n }\r\n } else {\r\n // MixTop / Reverse: 直接切换\r\n if (!item.children || item.children.length === 0) {\r\n if (item.path) router.push(item.path);\r\n }\r\n }\r\n };\r\n\r\n const handleSecondMenuClick = (item: MenuOptions) => {\r\n if (item.path) router.push(item.path);\r\n };\r\n\r\n // ============ 路由自动匹配 ============\r\n\r\n const updateActiveMenuByRoute = () => {\r\n const menuList = menus.value;\r\n const matchedFirstMenu = findActiveTopMenu(menuList);\r\n if (matchedFirstMenu) {\r\n activeFirstMenu.value = matchedFirstMenu.path || \"\";\r\n // 悬浮模式下,如果当前一级菜单有子菜单,自动展示二级菜单\r\n if (toValue(floatingSecondMenu) && matchedFirstMenu.children?.length) {\r\n showSecondMenu.value = true;\r\n }\r\n } else if (menuList.length > 0 && !activeFirstMenu.value) {\r\n activeFirstMenu.value = menuList[0].path || \"\";\r\n }\r\n };\r\n\r\n watch(\r\n () => route.path,\r\n () => updateActiveMenuByRoute(),\r\n { immediate: true },\r\n );\r\n\r\n watch(\r\n menus,\r\n (newMenus) => {\r\n if (newMenus && newMenus.length > 0) updateActiveMenuByRoute();\r\n },\r\n { immediate: true },\r\n );\r\n\r\n onMounted(() => updateActiveMenuByRoute());\r\n\r\n return {\r\n activeFirstMenu,\r\n activeFirstMenuItem,\r\n currentSecondMenus,\r\n hoveredMenuItem,\r\n /** 实际在悬浮面板上展示的菜单项(hovered 优先,fallback 到 active) */\r\n displayMenuItem,\r\n showSecondMenu,\r\n handleFirstMenuClick,\r\n handleSecondMenuClick,\r\n isMenuItemActive,\r\n };\r\n}\r\n","<!--\n * @robot-admin/layout - MixLayout\n *\n * 混合布局骨架\n * 左侧一级菜单图标栏 + 悬浮二级菜单弹出面板 + 右侧内容区\n *\n * Slots:\n * #logo - Logo/品牌区域(侧边栏顶部)\n * #header - 头部区域\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"mix-layout-container\">\n <!-- 左侧一级菜单 -->\n <div\n class=\"first-level-menu\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- Logo -->\n <div class=\"logo-container\">\n <slot name=\"logo\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n class=\"logo-video\"\n />\n </slot>\n </div>\n\n <!-- 一级菜单列表 -->\n <div class=\"first-menu-list\">\n <div\n v-for=\"item in menus\"\n :key=\"item.path\"\n class=\"first-menu-item\"\n :class=\"{\n active: menuSplit.activeFirstMenu.value === item.path,\n }\"\n @click=\"menuSplit.handleFirstMenuClick(item)\"\n >\n <div class=\"menu-item-content\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"item.meta?.icon\"\n :name=\"item.meta.icon\"\n :size=\"22\"\n />\n <span v-else class=\"menu-item-text\">{{\n (item.meta?.title || \"\")[0]\n }}</span>\n </div>\n <span class=\"menu-item-label\">{{ item.meta?.title }}</span>\n </div>\n </div>\n </div>\n\n <!-- 悬浮二级菜单 -->\n <Transition name=\"slide-fade\">\n <div\n v-if=\"menuSplit.showSecondMenu.value\"\n class=\"second-level-menu-popup\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <div class=\"second-menu-header\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"menuSplit.displayMenuItem.value?.meta?.icon\"\n :name=\"menuSplit.displayMenuItem.value.meta.icon\"\n :size=\"20\"\n />\n <span class=\"menu-title\">{{\n menuSplit.displayMenuItem.value?.meta?.title\n }}</span>\n </div>\n <div class=\"second-menu-list\">\n <div\n v-for=\"child in menuSplit.displayMenuItem.value?.children || []\"\n :key=\"child.path\"\n class=\"second-menu-item\"\n :class=\"{ active: menuSplit.isMenuItemActive(child.path) }\"\n @click=\"menuSplit.handleSecondMenuClick(child)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"child.meta?.icon\"\n :name=\"child.meta.icon\"\n :size=\"16\"\n />\n <span class=\"menu-item-label\">{{ child.meta?.title }}</span>\n </div>\n </div>\n </div>\n </Transition>\n\n <!-- 右侧主内容区 -->\n <div class=\"main-layout\">\n <slot name=\"header\" />\n\n <NLayout>\n <NLayoutContent class=\"content-with-header p16px app-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </NLayoutContent>\n\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { computed, h, defineComponent } from \"vue\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport { useMenuSplit } from \"../../../composables/useMenuSplit\";\n\ndefineOptions({ name: \"MixLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n\nconst menuSplit = useMenuSplit({\n menus: ctx.menus,\n floatingSecondMenu: computed(() => true),\n});\n\n// 图标组件:优先使用消费方提供的,否则用 CSS class 渲染\nconst LayoutIcon =\n ctx.iconComponent ??\n defineComponent({\n name: \"LayoutIcon\",\n props: { name: String, size: { type: Number, default: 18 } },\n setup(props) {\n return () =>\n h(\"i\", {\n class: props.name,\n style: {\n fontSize: `${props.size}px`,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n });\n },\n });\n</script>\n","<!--\n * @robot-admin/layout - MixLayout\n *\n * 混合布局骨架\n * 左侧一级菜单图标栏 + 悬浮二级菜单弹出面板 + 右侧内容区\n *\n * Slots:\n * #logo - Logo/品牌区域(侧边栏顶部)\n * #header - 头部区域\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"mix-layout-container\">\n <!-- 左侧一级菜单 -->\n <div\n class=\"first-level-menu\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- Logo -->\n <div class=\"logo-container\">\n <slot name=\"logo\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n class=\"logo-video\"\n />\n </slot>\n </div>\n\n <!-- 一级菜单列表 -->\n <div class=\"first-menu-list\">\n <div\n v-for=\"item in menus\"\n :key=\"item.path\"\n class=\"first-menu-item\"\n :class=\"{\n active: menuSplit.activeFirstMenu.value === item.path,\n }\"\n @click=\"menuSplit.handleFirstMenuClick(item)\"\n >\n <div class=\"menu-item-content\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"item.meta?.icon\"\n :name=\"item.meta.icon\"\n :size=\"22\"\n />\n <span v-else class=\"menu-item-text\">{{\n (item.meta?.title || \"\")[0]\n }}</span>\n </div>\n <span class=\"menu-item-label\">{{ item.meta?.title }}</span>\n </div>\n </div>\n </div>\n\n <!-- 悬浮二级菜单 -->\n <Transition name=\"slide-fade\">\n <div\n v-if=\"menuSplit.showSecondMenu.value\"\n class=\"second-level-menu-popup\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <div class=\"second-menu-header\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"menuSplit.displayMenuItem.value?.meta?.icon\"\n :name=\"menuSplit.displayMenuItem.value.meta.icon\"\n :size=\"20\"\n />\n <span class=\"menu-title\">{{\n menuSplit.displayMenuItem.value?.meta?.title\n }}</span>\n </div>\n <div class=\"second-menu-list\">\n <div\n v-for=\"child in menuSplit.displayMenuItem.value?.children || []\"\n :key=\"child.path\"\n class=\"second-menu-item\"\n :class=\"{ active: menuSplit.isMenuItemActive(child.path) }\"\n @click=\"menuSplit.handleSecondMenuClick(child)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"child.meta?.icon\"\n :name=\"child.meta.icon\"\n :size=\"16\"\n />\n <span class=\"menu-item-label\">{{ child.meta?.title }}</span>\n </div>\n </div>\n </div>\n </Transition>\n\n <!-- 右侧主内容区 -->\n <div class=\"main-layout\">\n <slot name=\"header\" />\n\n <NLayout>\n <NLayoutContent class=\"content-with-header p16px app-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </NLayoutContent>\n\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { computed, h, defineComponent } from \"vue\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport { useMenuSplit } from \"../../../composables/useMenuSplit\";\n\ndefineOptions({ name: \"MixLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n\nconst menuSplit = useMenuSplit({\n menus: ctx.menus,\n floatingSecondMenu: computed(() => true),\n});\n\n// 图标组件:优先使用消费方提供的,否则用 CSS class 渲染\nconst LayoutIcon =\n ctx.iconComponent ??\n defineComponent({\n name: \"LayoutIcon\",\n props: { name: String, size: { type: Number, default: 18 } },\n setup(props) {\n return () =>\n h(\"i\", {\n class: props.name,\n style: {\n fontSize: `${props.size}px`,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n });\n },\n });\n</script>\n","<!--\n * @robot-admin/layout - MixTopLayout\n *\n * 顶部混合布局骨架\n * 左侧一级菜单 + 顶部二级水平菜单 + 内容区\n *\n * Slots:\n * #logo - Logo/品牌区域(侧边栏顶部)\n * #brand - 顶部导航品牌区\n * #top-menu - 二级水平菜单(默认使用 ResponsiveMenu)\n * #header-extra - 头部右侧操作区\n * #tags-view - 标签页区域\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"mix-top-layout-container\">\n <!-- 左侧一级菜单 -->\n <div\n class=\"first-level-sidebar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- Logo -->\n <div class=\"logo-container\">\n <slot name=\"logo\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n class=\"logo-video\"\n />\n </slot>\n </div>\n\n <!-- 一级菜单列表 -->\n <div class=\"first-menu-list\">\n <div\n v-for=\"item in menus\"\n :key=\"item.path\"\n class=\"first-menu-item\"\n :class=\"{\n active: menuSplit.activeFirstMenu.value === item.path,\n }\"\n @click=\"menuSplit.handleFirstMenuClick(item)\"\n >\n <div class=\"menu-item-content\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"item.meta?.icon\"\n :name=\"item.meta.icon\"\n :size=\"22\"\n />\n <span v-else class=\"menu-item-text\">{{\n (item.meta?.title || \"\")[0]\n }}</span>\n </div>\n <span class=\"menu-item-label\">{{ item.meta?.title }}</span>\n </div>\n </div>\n </div>\n\n <!-- 右侧主区域 -->\n <div class=\"main-area\">\n <!-- 顶部导航栏 -->\n <div\n class=\"top-navbar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- 左侧:品牌 -->\n <div class=\"navbar-left\">\n <slot name=\"brand\">\n <div class=\"brand-name\">\n <span class=\"brand-title\">{{ brand.name }}</span>\n <span class=\"brand-subtitle\">{{ brand.subtitle }}</span>\n </div>\n <div class=\"navbar-divider\"></div>\n </slot>\n </div>\n\n <!-- 中间:二级水平菜单 -->\n <div class=\"navbar-center\">\n <slot name=\"top-menu\">\n <ResponsiveMenu\n v-if=\"menuSplit.currentSecondMenus.value.length > 0\"\n :data=\"menuSplit.currentSecondMenus.value\"\n />\n </slot>\n </div>\n\n <!-- 右侧:操作区 -->\n <slot name=\"header-extra\" />\n </div>\n\n <!-- 标签页区域 -->\n <div\n v-if=\"showTagsView\"\n class=\"tags-view-container\"\n :style=\"{ height: `${tagsViewHeight}px` }\"\n >\n <slot name=\"tags-view\" />\n </div>\n\n <!-- 内容区 -->\n <NLayout class=\"content-layout\">\n <NLayoutContent class=\"main-content\">\n <div class=\"page-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </div>\n </NLayoutContent>\n\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { computed, h, defineComponent } from \"vue\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport { useMenuSplit } from \"../../../composables/useMenuSplit\";\nimport ResponsiveMenu from \"../../ResponsiveMenu/index.vue\";\n\ndefineOptions({ name: \"MixTopLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showTagsView = computed(() => ctx.showTagsView.value);\nconst tagsViewHeight = computed(() => ctx.tagsViewHeight.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n\nconst menuSplit = useMenuSplit({\n menus: ctx.menus,\n floatingSecondMenu: computed(() => false),\n});\n\nconst LayoutIcon =\n ctx.iconComponent ??\n defineComponent({\n name: \"LayoutIcon\",\n props: { name: String, size: { type: Number, default: 18 } },\n setup(props) {\n return () =>\n h(\"i\", {\n class: props.name,\n style: {\n fontSize: `${props.size}px`,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n });\n },\n });\n</script>\n","<!--\n * @robot-admin/layout - MixTopLayout\n *\n * 顶部混合布局骨架\n * 左侧一级菜单 + 顶部二级水平菜单 + 内容区\n *\n * Slots:\n * #logo - Logo/品牌区域(侧边栏顶部)\n * #brand - 顶部导航品牌区\n * #top-menu - 二级水平菜单(默认使用 ResponsiveMenu)\n * #header-extra - 头部右侧操作区\n * #tags-view - 标签页区域\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"mix-top-layout-container\">\n <!-- 左侧一级菜单 -->\n <div\n class=\"first-level-sidebar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- Logo -->\n <div class=\"logo-container\">\n <slot name=\"logo\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n class=\"logo-video\"\n />\n </slot>\n </div>\n\n <!-- 一级菜单列表 -->\n <div class=\"first-menu-list\">\n <div\n v-for=\"item in menus\"\n :key=\"item.path\"\n class=\"first-menu-item\"\n :class=\"{\n active: menuSplit.activeFirstMenu.value === item.path,\n }\"\n @click=\"menuSplit.handleFirstMenuClick(item)\"\n >\n <div class=\"menu-item-content\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"item.meta?.icon\"\n :name=\"item.meta.icon\"\n :size=\"22\"\n />\n <span v-else class=\"menu-item-text\">{{\n (item.meta?.title || \"\")[0]\n }}</span>\n </div>\n <span class=\"menu-item-label\">{{ item.meta?.title }}</span>\n </div>\n </div>\n </div>\n\n <!-- 右侧主区域 -->\n <div class=\"main-area\">\n <!-- 顶部导航栏 -->\n <div\n class=\"top-navbar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- 左侧:品牌 -->\n <div class=\"navbar-left\">\n <slot name=\"brand\">\n <div class=\"brand-name\">\n <span class=\"brand-title\">{{ brand.name }}</span>\n <span class=\"brand-subtitle\">{{ brand.subtitle }}</span>\n </div>\n <div class=\"navbar-divider\"></div>\n </slot>\n </div>\n\n <!-- 中间:二级水平菜单 -->\n <div class=\"navbar-center\">\n <slot name=\"top-menu\">\n <ResponsiveMenu\n v-if=\"menuSplit.currentSecondMenus.value.length > 0\"\n :data=\"menuSplit.currentSecondMenus.value\"\n />\n </slot>\n </div>\n\n <!-- 右侧:操作区 -->\n <slot name=\"header-extra\" />\n </div>\n\n <!-- 标签页区域 -->\n <div\n v-if=\"showTagsView\"\n class=\"tags-view-container\"\n :style=\"{ height: `${tagsViewHeight}px` }\"\n >\n <slot name=\"tags-view\" />\n </div>\n\n <!-- 内容区 -->\n <NLayout class=\"content-layout\">\n <NLayoutContent class=\"main-content\">\n <div class=\"page-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </div>\n </NLayoutContent>\n\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { computed, h, defineComponent } from \"vue\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport { useMenuSplit } from \"../../../composables/useMenuSplit\";\nimport ResponsiveMenu from \"../../ResponsiveMenu/index.vue\";\n\ndefineOptions({ name: \"MixTopLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showTagsView = computed(() => ctx.showTagsView.value);\nconst tagsViewHeight = computed(() => ctx.tagsViewHeight.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n\nconst menuSplit = useMenuSplit({\n menus: ctx.menus,\n floatingSecondMenu: computed(() => false),\n});\n\nconst LayoutIcon =\n ctx.iconComponent ??\n defineComponent({\n name: \"LayoutIcon\",\n props: { name: String, size: { type: Number, default: 18 } },\n setup(props) {\n return () =>\n h(\"i\", {\n class: props.name,\n style: {\n fontSize: `${props.size}px`,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n });\n },\n });\n</script>\n","<!--\n * @robot-admin/layout - ReverseHorizontalMixLayout\n *\n * 反转混合布局骨架\n * 顶部一级菜单 + 右侧二级菜单侧边栏 + 左侧内容区\n *\n * Slots:\n * #logo - Logo/品牌区域\n * #top-menu - 一级水平菜单(默认使用 ResponsiveMenu)\n * #header-extra - 头部右侧操作区\n * #tags-view - 标签页区域\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"reverse-horizontal-mix-layout-container\">\n <!-- 顶部导航栏 -->\n <div\n class=\"top-navbar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- 左侧:Logo 和品牌 -->\n <div class=\"navbar-left\">\n <slot name=\"logo\">\n <div class=\"logo-container\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n />\n </div>\n <div class=\"brand-name\">\n <span class=\"brand-title\">{{ brand.name }}</span>\n <span class=\"brand-subtitle\">{{ brand.subtitle }}</span>\n </div>\n <div class=\"navbar-divider\"></div>\n </slot>\n </div>\n\n <!-- 中间:一级水平菜单 -->\n <div class=\"navbar-center\">\n <slot name=\"top-menu\">\n <ResponsiveMenu :data=\"menus\" />\n </slot>\n </div>\n\n <!-- 右侧:操作区 -->\n <slot name=\"header-extra\" />\n </div>\n\n <!-- 标签页区域 -->\n <div\n v-if=\"showTagsView\"\n class=\"tags-view-container\"\n :style=\"{ height: `${tagsViewHeight}px` }\"\n >\n <slot name=\"tags-view\" />\n </div>\n\n <!-- 主区域:左侧内容 + 右侧菜单 -->\n <div class=\"main-area\">\n <!-- 左侧:主内容区 -->\n <NLayout class=\"content-layout\">\n <NLayoutContent class=\"main-content\">\n <div class=\"page-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </div>\n </NLayoutContent>\n\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n\n <!-- 右侧折叠/展开按钮 - 始终可见 -->\n <div\n v-if=\"menuSplit.currentSecondMenus.value.length > 0\"\n class=\"sidebar-toggle\"\n :class=\"[\n isDarkMode ? 'dark-theme' : 'light-theme',\n { active: !isCollapsed },\n ]\"\n @click=\"toggleCollapse\"\n >\n <span\n :class=\"[\n 'collapse-arrow',\n 'toggle-icon',\n isCollapsed ? 'collapsed' : 'expanded',\n ]\"\n >\n <span class=\"arrow-line\"></span>\n <span class=\"arrow-line\"></span>\n </span>\n </div>\n\n <!-- 右侧:二级菜单侧边栏 -->\n <div\n v-if=\"menuSplit.currentSecondMenus.value.length > 0\"\n class=\"right-sidebar-wrapper\"\n :class=\"{ collapsed: isCollapsed }\"\n >\n <div\n class=\"right-sidebar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- 侧边栏标题 -->\n <div\n class=\"sidebar-header\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <div class=\"header-icon\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"menuSplit.activeFirstMenuItem.value?.meta?.icon\"\n :name=\"menuSplit.activeFirstMenuItem.value.meta.icon\"\n :size=\"20\"\n />\n </div>\n <span class=\"header-title\">{{\n menuSplit.activeFirstMenuItem.value?.meta?.title || \"菜单\"\n }}</span>\n </div>\n\n <!-- 二级菜单列表 -->\n <div class=\"sidebar-menu-list\">\n <template\n v-for=\"child in menuSplit.currentSecondMenus.value\"\n :key=\"child.path\"\n >\n <!-- 有子菜单的项 -->\n <div\n v-if=\"child.children && child.children.length > 0\"\n class=\"menu-group\"\n >\n <div class=\"group-title\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"child.meta?.icon\"\n :name=\"child.meta.icon\"\n :size=\"16\"\n />\n <span>{{ child.meta?.title }}</span>\n </div>\n <div\n v-for=\"subChild in child.children\"\n :key=\"subChild.path\"\n class=\"menu-item sub-item\"\n :class=\"{\n active: menuSplit.isMenuItemActive(subChild.path),\n }\"\n @click=\"menuSplit.handleSecondMenuClick(subChild)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"subChild.meta?.icon\"\n :name=\"subChild.meta.icon\"\n :size=\"16\"\n />\n <span class=\"item-title\">{{ subChild.meta?.title }}</span>\n </div>\n </div>\n <!-- 没有子菜单的项 -->\n <div\n v-else\n class=\"menu-item\"\n :class=\"{\n active: menuSplit.isMenuItemActive(child.path),\n }\"\n @click=\"menuSplit.handleSecondMenuClick(child)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"child.meta?.icon\"\n :name=\"child.meta.icon\"\n :size=\"18\"\n />\n <span class=\"item-title\">{{ child.meta?.title }}</span>\n </div>\n </template>\n </div>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { ref, computed, h, defineComponent } from \"vue\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport { useMenuSplit } from \"../../../composables/useMenuSplit\";\nimport ResponsiveMenu from \"../../ResponsiveMenu/index.vue\";\n\ndefineOptions({ name: \"ReverseHorizontalMixLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showTagsView = computed(() => ctx.showTagsView.value);\nconst tagsViewHeight = computed(() => ctx.tagsViewHeight.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n\nconst menuSplit = useMenuSplit({\n menus: ctx.menus,\n floatingSecondMenu: computed(() => false),\n});\n\n// 右侧边栏折叠状态\nconst isCollapsed = ref(false);\nconst toggleCollapse = () => {\n isCollapsed.value = !isCollapsed.value;\n};\n\nconst LayoutIcon =\n ctx.iconComponent ??\n defineComponent({\n name: \"LayoutIcon\",\n props: { name: String, size: { type: Number, default: 18 } },\n setup(props) {\n return () =>\n h(\"i\", {\n class: props.name,\n style: {\n fontSize: `${props.size}px`,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n });\n },\n });\n</script>\n","<!--\n * @robot-admin/layout - ReverseHorizontalMixLayout\n *\n * 反转混合布局骨架\n * 顶部一级菜单 + 右侧二级菜单侧边栏 + 左侧内容区\n *\n * Slots:\n * #logo - Logo/品牌区域\n * #top-menu - 一级水平菜单(默认使用 ResponsiveMenu)\n * #header-extra - 头部右侧操作区\n * #tags-view - 标签页区域\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"reverse-horizontal-mix-layout-container\">\n <!-- 顶部导航栏 -->\n <div\n class=\"top-navbar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- 左侧:Logo 和品牌 -->\n <div class=\"navbar-left\">\n <slot name=\"logo\">\n <div class=\"logo-container\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n />\n </div>\n <div class=\"brand-name\">\n <span class=\"brand-title\">{{ brand.name }}</span>\n <span class=\"brand-subtitle\">{{ brand.subtitle }}</span>\n </div>\n <div class=\"navbar-divider\"></div>\n </slot>\n </div>\n\n <!-- 中间:一级水平菜单 -->\n <div class=\"navbar-center\">\n <slot name=\"top-menu\">\n <ResponsiveMenu :data=\"menus\" />\n </slot>\n </div>\n\n <!-- 右侧:操作区 -->\n <slot name=\"header-extra\" />\n </div>\n\n <!-- 标签页区域 -->\n <div\n v-if=\"showTagsView\"\n class=\"tags-view-container\"\n :style=\"{ height: `${tagsViewHeight}px` }\"\n >\n <slot name=\"tags-view\" />\n </div>\n\n <!-- 主区域:左侧内容 + 右侧菜单 -->\n <div class=\"main-area\">\n <!-- 左侧:主内容区 -->\n <NLayout class=\"content-layout\">\n <NLayoutContent class=\"main-content\">\n <div class=\"page-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </div>\n </NLayoutContent>\n\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n\n <!-- 右侧折叠/展开按钮 - 始终可见 -->\n <div\n v-if=\"menuSplit.currentSecondMenus.value.length > 0\"\n class=\"sidebar-toggle\"\n :class=\"[\n isDarkMode ? 'dark-theme' : 'light-theme',\n { active: !isCollapsed },\n ]\"\n @click=\"toggleCollapse\"\n >\n <span\n :class=\"[\n 'collapse-arrow',\n 'toggle-icon',\n isCollapsed ? 'collapsed' : 'expanded',\n ]\"\n >\n <span class=\"arrow-line\"></span>\n <span class=\"arrow-line\"></span>\n </span>\n </div>\n\n <!-- 右侧:二级菜单侧边栏 -->\n <div\n v-if=\"menuSplit.currentSecondMenus.value.length > 0\"\n class=\"right-sidebar-wrapper\"\n :class=\"{ collapsed: isCollapsed }\"\n >\n <div\n class=\"right-sidebar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- 侧边栏标题 -->\n <div\n class=\"sidebar-header\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <div class=\"header-icon\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"menuSplit.activeFirstMenuItem.value?.meta?.icon\"\n :name=\"menuSplit.activeFirstMenuItem.value.meta.icon\"\n :size=\"20\"\n />\n </div>\n <span class=\"header-title\">{{\n menuSplit.activeFirstMenuItem.value?.meta?.title || \"菜单\"\n }}</span>\n </div>\n\n <!-- 二级菜单列表 -->\n <div class=\"sidebar-menu-list\">\n <template\n v-for=\"child in menuSplit.currentSecondMenus.value\"\n :key=\"child.path\"\n >\n <!-- 有子菜单的项 -->\n <div\n v-if=\"child.children && child.children.length > 0\"\n class=\"menu-group\"\n >\n <div class=\"group-title\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"child.meta?.icon\"\n :name=\"child.meta.icon\"\n :size=\"16\"\n />\n <span>{{ child.meta?.title }}</span>\n </div>\n <div\n v-for=\"subChild in child.children\"\n :key=\"subChild.path\"\n class=\"menu-item sub-item\"\n :class=\"{\n active: menuSplit.isMenuItemActive(subChild.path),\n }\"\n @click=\"menuSplit.handleSecondMenuClick(subChild)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"subChild.meta?.icon\"\n :name=\"subChild.meta.icon\"\n :size=\"16\"\n />\n <span class=\"item-title\">{{ subChild.meta?.title }}</span>\n </div>\n </div>\n <!-- 没有子菜单的项 -->\n <div\n v-else\n class=\"menu-item\"\n :class=\"{\n active: menuSplit.isMenuItemActive(child.path),\n }\"\n @click=\"menuSplit.handleSecondMenuClick(child)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"child.meta?.icon\"\n :name=\"child.meta.icon\"\n :size=\"18\"\n />\n <span class=\"item-title\">{{ child.meta?.title }}</span>\n </div>\n </template>\n </div>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { ref, computed, h, defineComponent } from \"vue\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport { useMenuSplit } from \"../../../composables/useMenuSplit\";\nimport ResponsiveMenu from \"../../ResponsiveMenu/index.vue\";\n\ndefineOptions({ name: \"ReverseHorizontalMixLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showTagsView = computed(() => ctx.showTagsView.value);\nconst tagsViewHeight = computed(() => ctx.tagsViewHeight.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n\nconst menuSplit = useMenuSplit({\n menus: ctx.menus,\n floatingSecondMenu: computed(() => false),\n});\n\n// 右侧边栏折叠状态\nconst isCollapsed = ref(false);\nconst toggleCollapse = () => {\n isCollapsed.value = !isCollapsed.value;\n};\n\nconst LayoutIcon =\n ctx.iconComponent ??\n defineComponent({\n name: \"LayoutIcon\",\n props: { name: String, size: { type: Number, default: 18 } },\n setup(props) {\n return () =>\n h(\"i\", {\n class: props.name,\n style: {\n fontSize: `${props.size}px`,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n });\n },\n });\n</script>\n","<!--\n * @robot-admin/layout - CardLayout\n *\n * 卡片式布局骨架\n * 顶部导航 + hover 触发抽屉式菜单 + 内容区\n *\n * Slots:\n * #menu-trigger - 菜单触发区域(hover 弹出抽屉)\n * #logo - Logo/品牌区域\n * #header-extra - 头部右侧操作区\n * #tags-view - 标签页区域\n * #drawer-menu - 抽屉式菜单内容\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"card-layout-container\">\n <!-- 顶部导航栏 -->\n <div\n class=\"top-navbar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <div class=\"navbar-left\">\n <!-- 悬停触发区域 -->\n <div\n class=\"hover-trigger-area\"\n @mouseenter=\"cancelHideTimer\"\n @mouseleave=\"hideDrawerMenu\"\n >\n <slot name=\"menu-trigger\">\n <div class=\"menu-indicator\">\n <!-- 纯 CSS 三横线菜单图标 -->\n <span class=\"hamburger-icon\">\n <span></span>\n <span></span>\n <span></span>\n </span>\n </div>\n </slot>\n </div>\n\n <slot name=\"logo\">\n <div class=\"logo-container\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n />\n </div>\n <div class=\"brand-name\">\n <span class=\"brand-title\">{{ brand.name }}</span>\n <span class=\"brand-subtitle\">{{ brand.subtitle }}</span>\n </div>\n <div class=\"navbar-divider\"></div>\n </slot>\n </div>\n\n <!-- 右侧:操作区 -->\n <slot name=\"header-extra\" />\n </div>\n\n <!-- 标签页区域 -->\n <div\n v-if=\"showTagsView\"\n class=\"tags-view-container\"\n :style=\"{ height: `${tagsViewHeight}px` }\"\n >\n <slot name=\"tags-view\" />\n </div>\n\n <!-- 抽屉式菜单 -->\n <div\n class=\"drawer-menu\"\n :class=\"[\n isDarkMode ? 'dark-theme' : 'light-theme',\n { visible: showDrawerMenu },\n ]\"\n @mouseenter=\"cancelHideTimer\"\n @mouseleave=\"hideDrawerMenu\"\n >\n <slot name=\"drawer-menu\">\n <!-- 菜单头部 -->\n <div class=\"drawer-header\">\n <div class=\"drawer-title\">\n <!-- 纯 CSS 网格图标 -->\n <span class=\"grid-icon\">\n <span></span>\n <span></span>\n <span></span>\n <span></span>\n </span>\n <span>功能导航</span>\n </div>\n </div>\n\n <!-- 菜单内容 - 阿里云风格铺开布局 -->\n <div class=\"drawer-content\">\n <div class=\"menu-grid\">\n <div\n v-for=\"category in menus\"\n :key=\"category.path\"\n class=\"menu-category\"\n >\n <!-- 分类标题 -->\n <div class=\"category-header\" @click=\"navigateToPage(category)\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"category.meta?.icon\"\n :name=\"category.meta.icon\"\n :size=\"18\"\n />\n <span>{{ category.meta?.title }}</span>\n </div>\n\n <!-- 分类下的菜单项 -->\n <div\n class=\"category-items\"\n v-if=\"category.children && category.children.length > 0\"\n >\n <div\n v-for=\"item in category.children\"\n :key=\"item.path\"\n class=\"menu-item\"\n @click=\"navigateToPage(item)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"item.meta?.icon\"\n :name=\"item.meta.icon\"\n :size=\"16\"\n />\n <span class=\"item-title\">{{ item.meta?.title }}</span>\n </div>\n\n <!-- 3级菜单项 -->\n <template\n v-for=\"item in category.children\"\n :key=\"'sub-' + item.path\"\n >\n <div\n v-if=\"item.children && item.children.length > 0\"\n class=\"submenu-items\"\n >\n <div class=\"submenu-title\">{{ item.meta?.title }}</div>\n <div\n v-for=\"subItem in item.children\"\n :key=\"subItem.path\"\n class=\"menu-item submenu-item\"\n @click=\"navigateToPage(subItem)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"subItem.meta?.icon\"\n :name=\"subItem.meta.icon\"\n :size=\"14\"\n />\n <span class=\"item-title\">{{ subItem.meta?.title }}</span>\n </div>\n </div>\n </template>\n </div>\n </div>\n </div>\n </div>\n </slot>\n </div>\n\n <!-- 主内容区域 -->\n <div class=\"main-content-area\">\n <NLayout class=\"content-layout\">\n <NLayoutContent class=\"main-content p16px app-content\">\n <div class=\"page-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </div>\n </NLayoutContent>\n\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { ref, computed, onUnmounted, h, defineComponent } from \"vue\";\nimport { useRouter } from \"vue-router\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport type { MenuOptions } from \"../../../types/menu\";\n\ndefineOptions({ name: \"CardLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\nconst router = useRouter();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showTagsView = computed(() => ctx.showTagsView.value);\nconst tagsViewHeight = computed(() => ctx.tagsViewHeight.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n\n// 抽屉菜单状态\nconst showDrawerMenu = ref(false);\nconst hideTimer = ref<ReturnType<typeof setTimeout> | null>(null);\n\nconst hideDrawerMenu = () => {\n if (hideTimer.value) clearTimeout(hideTimer.value);\n hideTimer.value = setTimeout(() => {\n showDrawerMenu.value = false;\n }, 300);\n};\n\nconst cancelHideTimer = () => {\n if (hideTimer.value) {\n clearTimeout(hideTimer.value);\n hideTimer.value = null;\n }\n showDrawerMenu.value = true;\n};\n\nconst navigateToPage = (item: MenuOptions) => {\n if (item.path) {\n router.push(item.path);\n showDrawerMenu.value = false;\n }\n};\n\nconst LayoutIcon =\n ctx.iconComponent ??\n defineComponent({\n name: \"LayoutIcon\",\n props: { name: String, size: { type: Number, default: 18 } },\n setup(props) {\n return () =>\n h(\"i\", {\n class: props.name,\n style: {\n fontSize: `${props.size}px`,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n });\n },\n });\n\nonUnmounted(() => {\n if (hideTimer.value) clearTimeout(hideTimer.value);\n});\n</script>\n\n<style scoped lang=\"scss\">\n/* 纯 CSS 网格图标 (2x2) */\n.grid-icon {\n display: inline-grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 2.5px;\n width: 18px;\n height: 18px;\n padding: 1px;\n}\n\n.grid-icon span {\n background-color: currentColor;\n border-radius: 2px;\n opacity: 0.85;\n}\n\n/* 纯 CSS 三横线汉堡菜单图标 */\n.hamburger-icon {\n display: inline-flex;\n flex-direction: column;\n justify-content: space-between;\n width: 20px;\n height: 16px;\n padding: 2px 0;\n}\n\n.hamburger-icon span {\n display: block;\n width: 100%;\n height: 2px;\n background-color: currentColor;\n border-radius: 1px;\n opacity: 0.85;\n transition: all 0.2s ease;\n}\n</style>\n","<!--\n * @robot-admin/layout - CardLayout\n *\n * 卡片式布局骨架\n * 顶部导航 + hover 触发抽屉式菜单 + 内容区\n *\n * Slots:\n * #menu-trigger - 菜单触发区域(hover 弹出抽屉)\n * #logo - Logo/品牌区域\n * #header-extra - 头部右侧操作区\n * #tags-view - 标签页区域\n * #drawer-menu - 抽屉式菜单内容\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"card-layout-container\">\n <!-- 顶部导航栏 -->\n <div\n class=\"top-navbar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <div class=\"navbar-left\">\n <!-- 悬停触发区域 -->\n <div\n class=\"hover-trigger-area\"\n @mouseenter=\"cancelHideTimer\"\n @mouseleave=\"hideDrawerMenu\"\n >\n <slot name=\"menu-trigger\">\n <div class=\"menu-indicator\">\n <!-- 纯 CSS 三横线菜单图标 -->\n <span class=\"hamburger-icon\">\n <span></span>\n <span></span>\n <span></span>\n </span>\n </div>\n </slot>\n </div>\n\n <slot name=\"logo\">\n <div class=\"logo-container\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n />\n </div>\n <div class=\"brand-name\">\n <span class=\"brand-title\">{{ brand.name }}</span>\n <span class=\"brand-subtitle\">{{ brand.subtitle }}</span>\n </div>\n <div class=\"navbar-divider\"></div>\n </slot>\n </div>\n\n <!-- 右侧:操作区 -->\n <slot name=\"header-extra\" />\n </div>\n\n <!-- 标签页区域 -->\n <div\n v-if=\"showTagsView\"\n class=\"tags-view-container\"\n :style=\"{ height: `${tagsViewHeight}px` }\"\n >\n <slot name=\"tags-view\" />\n </div>\n\n <!-- 抽屉式菜单 -->\n <div\n class=\"drawer-menu\"\n :class=\"[\n isDarkMode ? 'dark-theme' : 'light-theme',\n { visible: showDrawerMenu },\n ]\"\n @mouseenter=\"cancelHideTimer\"\n @mouseleave=\"hideDrawerMenu\"\n >\n <slot name=\"drawer-menu\">\n <!-- 菜单头部 -->\n <div class=\"drawer-header\">\n <div class=\"drawer-title\">\n <!-- 纯 CSS 网格图标 -->\n <span class=\"grid-icon\">\n <span></span>\n <span></span>\n <span></span>\n <span></span>\n </span>\n <span>功能导航</span>\n </div>\n </div>\n\n <!-- 菜单内容 - 阿里云风格铺开布局 -->\n <div class=\"drawer-content\">\n <div class=\"menu-grid\">\n <div\n v-for=\"category in menus\"\n :key=\"category.path\"\n class=\"menu-category\"\n >\n <!-- 分类标题 -->\n <div class=\"category-header\" @click=\"navigateToPage(category)\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"category.meta?.icon\"\n :name=\"category.meta.icon\"\n :size=\"18\"\n />\n <span>{{ category.meta?.title }}</span>\n </div>\n\n <!-- 分类下的菜单项 -->\n <div\n class=\"category-items\"\n v-if=\"category.children && category.children.length > 0\"\n >\n <div\n v-for=\"item in category.children\"\n :key=\"item.path\"\n class=\"menu-item\"\n @click=\"navigateToPage(item)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"item.meta?.icon\"\n :name=\"item.meta.icon\"\n :size=\"16\"\n />\n <span class=\"item-title\">{{ item.meta?.title }}</span>\n </div>\n\n <!-- 3级菜单项 -->\n <template\n v-for=\"item in category.children\"\n :key=\"'sub-' + item.path\"\n >\n <div\n v-if=\"item.children && item.children.length > 0\"\n class=\"submenu-items\"\n >\n <div class=\"submenu-title\">{{ item.meta?.title }}</div>\n <div\n v-for=\"subItem in item.children\"\n :key=\"subItem.path\"\n class=\"menu-item submenu-item\"\n @click=\"navigateToPage(subItem)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"subItem.meta?.icon\"\n :name=\"subItem.meta.icon\"\n :size=\"14\"\n />\n <span class=\"item-title\">{{ subItem.meta?.title }}</span>\n </div>\n </div>\n </template>\n </div>\n </div>\n </div>\n </div>\n </slot>\n </div>\n\n <!-- 主内容区域 -->\n <div class=\"main-content-area\">\n <NLayout class=\"content-layout\">\n <NLayoutContent class=\"main-content p16px app-content\">\n <div class=\"page-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </div>\n </NLayoutContent>\n\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { ref, computed, onUnmounted, h, defineComponent } from \"vue\";\nimport { useRouter } from \"vue-router\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport type { MenuOptions } from \"../../../types/menu\";\n\ndefineOptions({ name: \"CardLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\nconst router = useRouter();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showTagsView = computed(() => ctx.showTagsView.value);\nconst tagsViewHeight = computed(() => ctx.tagsViewHeight.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n\n// 抽屉菜单状态\nconst showDrawerMenu = ref(false);\nconst hideTimer = ref<ReturnType<typeof setTimeout> | null>(null);\n\nconst hideDrawerMenu = () => {\n if (hideTimer.value) clearTimeout(hideTimer.value);\n hideTimer.value = setTimeout(() => {\n showDrawerMenu.value = false;\n }, 300);\n};\n\nconst cancelHideTimer = () => {\n if (hideTimer.value) {\n clearTimeout(hideTimer.value);\n hideTimer.value = null;\n }\n showDrawerMenu.value = true;\n};\n\nconst navigateToPage = (item: MenuOptions) => {\n if (item.path) {\n router.push(item.path);\n showDrawerMenu.value = false;\n }\n};\n\nconst LayoutIcon =\n ctx.iconComponent ??\n defineComponent({\n name: \"LayoutIcon\",\n props: { name: String, size: { type: Number, default: 18 } },\n setup(props) {\n return () =>\n h(\"i\", {\n class: props.name,\n style: {\n fontSize: `${props.size}px`,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n });\n },\n });\n\nonUnmounted(() => {\n if (hideTimer.value) clearTimeout(hideTimer.value);\n});\n</script>\n\n<style scoped lang=\"scss\">\n/* 纯 CSS 网格图标 (2x2) */\n.grid-icon {\n display: inline-grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 2.5px;\n width: 18px;\n height: 18px;\n padding: 1px;\n}\n\n.grid-icon span {\n background-color: currentColor;\n border-radius: 2px;\n opacity: 0.85;\n}\n\n/* 纯 CSS 三横线汉堡菜单图标 */\n.hamburger-icon {\n display: inline-flex;\n flex-direction: column;\n justify-content: space-between;\n width: 20px;\n height: 16px;\n padding: 2px 0;\n}\n\n.hamburger-icon span {\n display: block;\n width: 100%;\n height: 2px;\n background-color: currentColor;\n border-radius: 1px;\n opacity: 0.85;\n transition: all 0.2s ease;\n}\n</style>\n","<!--\r\n * @robot-admin/layout - C_LayoutContainer\r\n *\r\n * 智能布局容器\r\n * 根据 layoutMode 自动切换 6 种布局骨架,消费方仅需提供业务组件\r\n *\r\n * Slots:\r\n * #logo - 品牌 Logo 区域(各骨架有内置默认实现)\r\n * #menu - Side 布局的垂直菜单(需消费方提供)\r\n * #header - Side/Mix 布局的完整头部(需消费方提供)\r\n * #header-extra - Top/MixTop/Reverse/Card 头部右侧操作区\r\n * #tags-view - 标签页区域(需消费方提供)\r\n * #footer - 页脚区域(需消费方提供)\r\n * #top-menu - Top/MixTop/Reverse 水平菜单(骨架有默认)\r\n * #brand - MixTop 顶部导航品牌区(骨架有默认)\r\n * #menu-trigger - Card 菜单触发区域(骨架有默认)\r\n * #drawer-menu - Card 抽屉式菜单(骨架有默认)\r\n -->\r\n<template>\r\n <div :class=\"['layout-container', isDark ? 'dark-mode' : 'light-mode']\">\r\n <!-- ═══════ Side 左侧菜单布局 ═══════ -->\r\n <SideLayout v-if=\"layoutMode === 'side'\" v-model:collapsed=\"sideCollapsed\">\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template #menu>\r\n <slot name=\"menu\" :collapsed=\"sideCollapsed\" />\r\n </template>\r\n <template #header><slot name=\"header\" /></template>\r\n <template #footer><slot name=\"footer\" /></template>\r\n </SideLayout>\r\n\r\n <!-- ═══════ Top 顶部菜单布局 ═══════ -->\r\n <TopLayout v-else-if=\"layoutMode === 'top'\">\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template v-if=\"$slots['top-menu']\" #menu\r\n ><slot name=\"top-menu\"\r\n /></template>\r\n <template v-if=\"$slots['header-extra']\" #header-extra\r\n ><slot name=\"header-extra\"\r\n /></template>\r\n <template v-if=\"$slots['tags-view']\" #tags-view\r\n ><slot name=\"tags-view\"\r\n /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </TopLayout>\r\n\r\n <!-- ═══════ Mix 混合布局 ═══════ -->\r\n <MixLayout v-else-if=\"layoutMode === 'mix'\">\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template #header><slot name=\"header\" /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </MixLayout>\r\n\r\n <!-- ═══════ MixTop 顶部混合布局 ═══════ -->\r\n <MixTopLayout v-else-if=\"layoutMode === 'mix-top'\">\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template v-if=\"$slots.brand\" #brand><slot name=\"brand\" /></template>\r\n <template v-if=\"$slots['top-menu']\" #top-menu\r\n ><slot name=\"top-menu\"\r\n /></template>\r\n <template v-if=\"$slots['header-extra']\" #header-extra\r\n ><slot name=\"header-extra\"\r\n /></template>\r\n <template v-if=\"$slots['tags-view']\" #tags-view\r\n ><slot name=\"tags-view\"\r\n /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </MixTopLayout>\r\n\r\n <!-- ═══════ ReverseHorizontalMix 反转混合布局 ═══════ -->\r\n <ReverseHorizontalMixLayout\r\n v-else-if=\"layoutMode === 'reverse-horizontal-mix'\"\r\n >\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template v-if=\"$slots['top-menu']\" #top-menu\r\n ><slot name=\"top-menu\"\r\n /></template>\r\n <template v-if=\"$slots['header-extra']\" #header-extra\r\n ><slot name=\"header-extra\"\r\n /></template>\r\n <template v-if=\"$slots['tags-view']\" #tags-view\r\n ><slot name=\"tags-view\"\r\n /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </ReverseHorizontalMixLayout>\r\n\r\n <!-- ═══════ CardLayout 卡片布局 ═══════ -->\r\n <CardLayout v-else-if=\"layoutMode === 'card-layout'\">\r\n <template v-if=\"$slots['menu-trigger']\" #menu-trigger\r\n ><slot name=\"menu-trigger\"\r\n /></template>\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template v-if=\"$slots['header-extra']\" #header-extra\r\n ><slot name=\"header-extra\"\r\n /></template>\r\n <template v-if=\"$slots['tags-view']\" #tags-view\r\n ><slot name=\"tags-view\"\r\n /></template>\r\n <template v-if=\"$slots['drawer-menu']\" #drawer-menu\r\n ><slot name=\"drawer-menu\"\r\n /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </CardLayout>\r\n\r\n <!-- ═══════ Fallback ═══════ -->\r\n <div v-else class=\"layout-coming-soon\">\r\n <div class=\"coming-soon-content\">\r\n <div class=\"coming-soon-icon\">🚧</div>\r\n <div class=\"coming-soon-title\">布局开发中</div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, ref, provide } from \"vue\";\r\nimport { useSettingsStore } from \"../../stores/settings\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\n\r\n// 骨架布局全局样式\r\nimport \"../../styles/layouts.scss\";\r\n\r\n// 骨架组件\r\nimport SideLayout from \"../layouts/SideLayout/index.vue\";\r\nimport TopLayout from \"../layouts/TopLayout/index.vue\";\r\nimport MixLayout from \"../layouts/MixLayout/index.vue\";\r\nimport MixTopLayout from \"../layouts/MixTopLayout/index.vue\";\r\nimport ReverseHorizontalMixLayout from \"../layouts/ReverseHorizontalMixLayout/index.vue\";\r\nimport CardLayout from \"../layouts/CardLayout/index.vue\";\r\n\r\ndefineOptions({ name: \"C_LayoutContainer\" });\r\n\r\nconst settingsStore = useSettingsStore();\r\nconst ctx = useLayoutContext();\r\n\r\nconst layoutMode = computed(() => settingsStore.layoutMode);\r\nconst isDark = ctx.isDark;\r\n\r\n// Side 布局折叠状态\r\nconst sideCollapsed = ref(false);\r\n\r\n// 提供折叠状态给子组件(如 C_Header 的汉堡按钮)\r\nprovide(\"menuCollapse\", {\r\n isCollapsed: sideCollapsed,\r\n handleCollapsedChange: (collapsed: boolean) => {\r\n sideCollapsed.value = collapsed;\r\n },\r\n});\r\n</script>\r\n","<!--\r\n * @robot-admin/layout - C_LayoutContainer\r\n *\r\n * 智能布局容器\r\n * 根据 layoutMode 自动切换 6 种布局骨架,消费方仅需提供业务组件\r\n *\r\n * Slots:\r\n * #logo - 品牌 Logo 区域(各骨架有内置默认实现)\r\n * #menu - Side 布局的垂直菜单(需消费方提供)\r\n * #header - Side/Mix 布局的完整头部(需消费方提供)\r\n * #header-extra - Top/MixTop/Reverse/Card 头部右侧操作区\r\n * #tags-view - 标签页区域(需消费方提供)\r\n * #footer - 页脚区域(需消费方提供)\r\n * #top-menu - Top/MixTop/Reverse 水平菜单(骨架有默认)\r\n * #brand - MixTop 顶部导航品牌区(骨架有默认)\r\n * #menu-trigger - Card 菜单触发区域(骨架有默认)\r\n * #drawer-menu - Card 抽屉式菜单(骨架有默认)\r\n -->\r\n<template>\r\n <div :class=\"['layout-container', isDark ? 'dark-mode' : 'light-mode']\">\r\n <!-- ═══════ Side 左侧菜单布局 ═══════ -->\r\n <SideLayout v-if=\"layoutMode === 'side'\" v-model:collapsed=\"sideCollapsed\">\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template #menu>\r\n <slot name=\"menu\" :collapsed=\"sideCollapsed\" />\r\n </template>\r\n <template #header><slot name=\"header\" /></template>\r\n <template #footer><slot name=\"footer\" /></template>\r\n </SideLayout>\r\n\r\n <!-- ═══════ Top 顶部菜单布局 ═══════ -->\r\n <TopLayout v-else-if=\"layoutMode === 'top'\">\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template v-if=\"$slots['top-menu']\" #menu\r\n ><slot name=\"top-menu\"\r\n /></template>\r\n <template v-if=\"$slots['header-extra']\" #header-extra\r\n ><slot name=\"header-extra\"\r\n /></template>\r\n <template v-if=\"$slots['tags-view']\" #tags-view\r\n ><slot name=\"tags-view\"\r\n /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </TopLayout>\r\n\r\n <!-- ═══════ Mix 混合布局 ═══════ -->\r\n <MixLayout v-else-if=\"layoutMode === 'mix'\">\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template #header><slot name=\"header\" /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </MixLayout>\r\n\r\n <!-- ═══════ MixTop 顶部混合布局 ═══════ -->\r\n <MixTopLayout v-else-if=\"layoutMode === 'mix-top'\">\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template v-if=\"$slots.brand\" #brand><slot name=\"brand\" /></template>\r\n <template v-if=\"$slots['top-menu']\" #top-menu\r\n ><slot name=\"top-menu\"\r\n /></template>\r\n <template v-if=\"$slots['header-extra']\" #header-extra\r\n ><slot name=\"header-extra\"\r\n /></template>\r\n <template v-if=\"$slots['tags-view']\" #tags-view\r\n ><slot name=\"tags-view\"\r\n /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </MixTopLayout>\r\n\r\n <!-- ═══════ ReverseHorizontalMix 反转混合布局 ═══════ -->\r\n <ReverseHorizontalMixLayout\r\n v-else-if=\"layoutMode === 'reverse-horizontal-mix'\"\r\n >\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template v-if=\"$slots['top-menu']\" #top-menu\r\n ><slot name=\"top-menu\"\r\n /></template>\r\n <template v-if=\"$slots['header-extra']\" #header-extra\r\n ><slot name=\"header-extra\"\r\n /></template>\r\n <template v-if=\"$slots['tags-view']\" #tags-view\r\n ><slot name=\"tags-view\"\r\n /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </ReverseHorizontalMixLayout>\r\n\r\n <!-- ═══════ CardLayout 卡片布局 ═══════ -->\r\n <CardLayout v-else-if=\"layoutMode === 'card-layout'\">\r\n <template v-if=\"$slots['menu-trigger']\" #menu-trigger\r\n ><slot name=\"menu-trigger\"\r\n /></template>\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template v-if=\"$slots['header-extra']\" #header-extra\r\n ><slot name=\"header-extra\"\r\n /></template>\r\n <template v-if=\"$slots['tags-view']\" #tags-view\r\n ><slot name=\"tags-view\"\r\n /></template>\r\n <template v-if=\"$slots['drawer-menu']\" #drawer-menu\r\n ><slot name=\"drawer-menu\"\r\n /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </CardLayout>\r\n\r\n <!-- ═══════ Fallback ═══════ -->\r\n <div v-else class=\"layout-coming-soon\">\r\n <div class=\"coming-soon-content\">\r\n <div class=\"coming-soon-icon\">🚧</div>\r\n <div class=\"coming-soon-title\">布局开发中</div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, ref, provide } from \"vue\";\r\nimport { useSettingsStore } from \"../../stores/settings\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\n\r\n// 骨架布局全局样式\r\nimport \"../../styles/layouts.scss\";\r\n\r\n// 骨架组件\r\nimport SideLayout from \"../layouts/SideLayout/index.vue\";\r\nimport TopLayout from \"../layouts/TopLayout/index.vue\";\r\nimport MixLayout from \"../layouts/MixLayout/index.vue\";\r\nimport MixTopLayout from \"../layouts/MixTopLayout/index.vue\";\r\nimport ReverseHorizontalMixLayout from \"../layouts/ReverseHorizontalMixLayout/index.vue\";\r\nimport CardLayout from \"../layouts/CardLayout/index.vue\";\r\n\r\ndefineOptions({ name: \"C_LayoutContainer\" });\r\n\r\nconst settingsStore = useSettingsStore();\r\nconst ctx = useLayoutContext();\r\n\r\nconst layoutMode = computed(() => settingsStore.layoutMode);\r\nconst isDark = ctx.isDark;\r\n\r\n// Side 布局折叠状态\r\nconst sideCollapsed = ref(false);\r\n\r\n// 提供折叠状态给子组件(如 C_Header 的汉堡按钮)\r\nprovide(\"menuCollapse\", {\r\n isCollapsed: sideCollapsed,\r\n handleCollapsedChange: (collapsed: boolean) => {\r\n sideCollapsed.value = collapsed;\r\n },\r\n});\r\n</script>\r\n","/**\n * SettingsDrawer - 常量数据\n */\n\nimport { THEME_PRESETS, COLOR_SWATCHES } from \"../../constants\";\nimport type { ThemePreset } from \"../../types\";\n\n// 重新导出(内部使用)\nexport { COLOR_SWATCHES, THEME_PRESETS };\nexport type { ThemePreset };\n\n// 布局模式选项 - 完整的 6 种布局(含 SVG 图标,仅 UI 内部使用)\nexport const LAYOUT_MODE_OPTIONS = [\n {\n label: \"左侧菜单(默认)\",\n value: \"side\",\n disabled: false,\n svg: `\n <rect x=\"0\" y=\"0\" width=\"16\" height=\"48\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.9\"/>\n <rect x=\"18\" y=\"0\" width=\"38\" height=\"10\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.7\"/>\n <rect x=\"18\" y=\"12\" width=\"38\" height=\"34\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.4\"/>\n `,\n },\n {\n label: \"顶部菜单(清爽)\",\n value: \"top\",\n disabled: false,\n svg: `\n <rect x=\"0\" y=\"0\" width=\"56\" height=\"10\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.9\"/>\n <rect x=\"0\" y=\"12\" width=\"56\" height=\"8\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.7\"/>\n <rect x=\"0\" y=\"22\" width=\"56\" height=\"24\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.4\"/>\n `,\n },\n {\n label: \"左侧混合(灵巧)\",\n value: \"mix\",\n disabled: false,\n svg: `\n <rect x=\"0\" y=\"0\" width=\"8\" height=\"48\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.9\"/>\n <rect x=\"10\" y=\"0\" width=\"14\" height=\"48\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.7\"/>\n <rect x=\"26\" y=\"0\" width=\"30\" height=\"10\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.6\"/>\n <rect x=\"26\" y=\"12\" width=\"30\" height=\"34\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.4\"/>\n `,\n },\n {\n label: \"顶部混合(侧优)\",\n value: \"mix-top\",\n disabled: false,\n svg: `\n <rect x=\"0\" y=\"0\" width=\"8\" height=\"48\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.9\"/>\n <rect x=\"10\" y=\"0\" width=\"46\" height=\"10\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.7\"/>\n <rect x=\"10\" y=\"12\" width=\"46\" height=\"34\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.4\"/>\n `,\n },\n {\n label: \"反转混合(另类)\",\n value: \"reverse-horizontal-mix\",\n disabled: false,\n svg: `\n <rect x=\"0\" y=\"0\" width=\"56\" height=\"10\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.9\"/>\n <rect x=\"44\" y=\"12\" width=\"12\" height=\"34\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.7\"/>\n <rect x=\"0\" y=\"12\" width=\"42\" height=\"34\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.4\"/>\n `,\n },\n {\n label: \"卡片网格(新颖)\",\n value: \"card-layout\",\n disabled: false,\n svg: `\n <rect x=\"0\" y=\"0\" width=\"24\" height=\"20\" rx=\"2\" fill=\"currentColor\" fill-opacity=\"0.9\"/>\n <rect x=\"32\" y=\"0\" width=\"24\" height=\"20\" rx=\"2\" fill=\"currentColor\" fill-opacity=\"0.7\"/>\n <rect x=\"0\" y=\"24\" width=\"24\" height=\"20\" rx=\"2\" fill=\"currentColor\" fill-opacity=\"0.6\"/>\n <rect x=\"32\" y=\"24\" width=\"24\" height=\"20\" rx=\"2\" fill=\"currentColor\" fill-opacity=\"0.4\"/>\n `,\n },\n];\n","<!--\n * @robot-admin/layout - SettingsDrawer\n *\n * 布局配置抽屉\n * 包含外观/布局/功能三大模块,支持主题预设、布局切换、配置导入导出等\n -->\n<template>\n <NDrawer\n v-model:show=\"visible\"\n :width=\"380\"\n placement=\"right\"\n :trap-focus=\"false\"\n :block-scroll=\"false\"\n >\n <NDrawerContent title=\"⚙️ 布局配置\" closable>\n <NTabs v-model:value=\"activeTab\" animated class=\"settings-tabs\">\n <!-- 外观 Tab -->\n <NTabPane name=\"appearance\" tab=\"🎨 外观\">\n <!-- 预设方案网格 -->\n <div class=\"settings-section\">\n <div class=\"preset-grid\">\n <div\n v-for=\"preset in THEME_PRESETS\"\n :key=\"preset.name\"\n class=\"preset-card\"\n :class=\"{ active: isCurrentPreset(preset) }\"\n @click=\"handleApplyPreset(preset)\"\n >\n <div class=\"preset-icon\">{{ preset.icon }}</div>\n <div class=\"preset-name\">{{ preset.name }}</div>\n <div class=\"preset-color\">\n <span\n class=\"color-dot\"\n :style=\"{ backgroundColor: preset.primaryColor }\"\n ></span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- 主题模式 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">主题模式</div>\n <NRadioGroup\n v-model:value=\"settingsStore.themeMode\"\n class=\"mode-group\"\n >\n <NRadioButton value=\"light\">\n <span class=\"i-mdi:white-balance-sunny mr-1\"></span>\n 浅色\n </NRadioButton>\n <NRadioButton value=\"dark\">\n <span class=\"i-mdi:moon-waning-crescent mr-1\"></span>\n 深色\n </NRadioButton>\n <NRadioButton value=\"auto\">\n <span class=\"i-mdi:theme-light-dark mr-1\"></span>\n 自动\n </NRadioButton>\n </NRadioGroup>\n </div>\n\n <!-- 主题色 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">主题色</div>\n <div class=\"color-picker-wrapper\">\n <NColorPicker\n v-model:value=\"settingsStore.primaryColor\"\n :show-alpha=\"false\"\n :swatches=\"COLOR_SWATCHES\"\n :actions=\"['confirm']\"\n />\n <span class=\"color-value\">{{ settingsStore.primaryColor }}</span>\n </div>\n </div>\n\n <!-- 圆角大小 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">圆角大小</div>\n <NRadioGroup\n v-model:value=\"settingsStore.borderRadius\"\n class=\"radius-group\"\n >\n <NRadioButton value=\"small\">小 (4px)</NRadioButton>\n <NRadioButton value=\"medium\">中 (6px)</NRadioButton>\n <NRadioButton value=\"large\">大 (8px)</NRadioButton>\n </NRadioGroup>\n </div>\n\n <!-- 页面动画 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">页面动画</div>\n <div class=\"flex items-center justify-between mb-2\">\n <span class=\"text-sm\">启用动画</span>\n <NSwitch v-model:value=\"settingsStore.enableTransition\" />\n </div>\n <NRadioGroup\n v-model:value=\"settingsStore.transitionType\"\n :disabled=\"!settingsStore.enableTransition\"\n class=\"transition-group\"\n >\n <NRadioButton value=\"fade\">淡入</NRadioButton>\n <NRadioButton value=\"slide\">滑动</NRadioButton>\n <NRadioButton value=\"zoom\">缩放</NRadioButton>\n <NRadioButton value=\"none\">无</NRadioButton>\n </NRadioGroup>\n </div>\n\n <!-- 恢复默认 -->\n <div class=\"settings-section\">\n <NButton block secondary @click=\"handleResetAppearance\">\n <template #icon>\n <span class=\"i-mdi:restore\"></span>\n </template>\n 恢复外观默认设置\n </NButton>\n </div>\n </NTabPane>\n\n <!-- 布局 Tab -->\n <NTabPane name=\"layout\" tab=\"📐 布局\">\n <!-- 布局模式 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">布局模式</div>\n <div class=\"layout-grid\">\n <div\n v-for=\"mode in LAYOUT_MODE_OPTIONS\"\n :key=\"mode.value\"\n class=\"layout-item\"\n :class=\"{\n active: settingsStore.layoutMode === mode.value,\n disabled: mode.disabled,\n }\"\n @click.stop=\"handleLayoutChange(mode.value, mode.disabled)\"\n >\n <div class=\"layout-screenshot\">\n <svg\n class=\"layout-svg\"\n viewBox=\"0 0 56 48\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <g v-html=\"mode.svg\"></g>\n </svg>\n </div>\n <div class=\"layout-label\">\n {{ mode.label }}\n <span v-if=\"mode.disabled\" class=\"badge-soon\">敬请期待</span>\n </div>\n </div>\n </div>\n </div>\n\n <div\n class=\"settings-section menu-expand-section\"\n data-menu-expand-mode=\"built-in\"\n >\n <div class=\"section-title\">菜单展开方式</div>\n <NRadioGroup\n v-model:value=\"settingsStore.menuExpandMode\"\n class=\"menu-expand-group\"\n >\n <NRadioButton value=\"inline\">传统展开</NRadioButton>\n <NRadioButton value=\"panel\">右侧面板</NRadioButton>\n </NRadioGroup>\n </div>\n\n <!-- 界面元素 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">界面元素</div>\n <div class=\"setting-item\">\n <span>显示面包屑</span>\n <NSwitch v-model:value=\"settingsStore.showBreadcrumb\" />\n </div>\n <div class=\"setting-item\">\n <span>显示面包屑图标</span>\n <NSwitch\n v-model:value=\"settingsStore.showBreadcrumbIcon\"\n :disabled=\"!settingsStore.showBreadcrumb\"\n />\n </div>\n <div class=\"setting-item\">\n <span>显示标签页</span>\n <NSwitch v-model:value=\"settingsStore.showTagsView\" />\n </div>\n <div class=\"setting-item\">\n <span>显示页脚</span>\n <NSwitch v-model:value=\"settingsStore.showFooter\" />\n </div>\n </div>\n\n <!-- 尺寸调整 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">尺寸调整</div>\n <div class=\"slider-item\">\n <div class=\"slider-label\">\n <span>侧边栏宽度</span>\n <span class=\"slider-value\"\n >{{ settingsStore.sidebarWidth }}px</span\n >\n </div>\n <NSlider\n v-model:value=\"settingsStore.sidebarWidth\"\n :min=\"180\"\n :max=\"280\"\n :step=\"10\"\n :marks=\"{ 180: '180', 220: '220', 280: '280' }\"\n />\n </div>\n <div class=\"slider-item\">\n <div class=\"slider-label\">\n <span>头部高度</span>\n <span class=\"slider-value\"\n >{{ settingsStore.headerHeight }}px</span\n >\n </div>\n <NSlider\n v-model:value=\"settingsStore.headerHeight\"\n :min=\"48\"\n :max=\"64\"\n :step=\"4\"\n :marks=\"{ 48: '48', 56: '56', 64: '64' }\"\n />\n </div>\n </div>\n\n <!-- 恢复默认 -->\n <div class=\"settings-section\">\n <NButton block secondary @click=\"handleResetLayout\">\n <template #icon>\n <span class=\"i-mdi:restore\"></span>\n </template>\n 恢复布局默认设置\n </NButton>\n </div>\n </NTabPane>\n\n <!-- 功能 Tab -->\n <NTabPane name=\"features\" tab=\"🔧 功能\">\n <!-- 缓存管理 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">缓存管理</div>\n <div class=\"action-buttons-grid\">\n <NButton secondary @click=\"handleClearCache\">\n <template #icon>\n <span class=\"i-mdi:delete-sweep\"></span>\n </template>\n 清除缓存\n </NButton>\n <NButton secondary @click=\"handleReload\">\n <template #icon>\n <span class=\"i-mdi:refresh\"></span>\n </template>\n 重新加载\n </NButton>\n </div>\n </div>\n\n <!-- 配置管理 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">配置管理</div>\n <div class=\"action-buttons-grid\">\n <NButton secondary @click=\"handleExportConfig\">\n <template #icon>\n <span class=\"i-mdi:download\"></span>\n </template>\n 导出配置\n </NButton>\n <NButton secondary @click=\"handleImportConfig\">\n <template #icon>\n <span class=\"i-mdi:upload\"></span>\n </template>\n 导入配置\n </NButton>\n </div>\n <NButton\n block\n type=\"error\"\n secondary\n class=\"mt-2\"\n @click=\"handleReset\"\n >\n <template #icon>\n <span class=\"i-mdi:restore\"></span>\n </template>\n 重置所有配置\n </NButton>\n </div>\n\n <!-- 系统模式 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">系统模式</div>\n <div class=\"setting-item\">\n <div class=\"setting-label\">\n <span>灰色模式</span>\n <span class=\"setting-desc\">用于哀悼日场景</span>\n </div>\n <NSwitch v-model:value=\"grayMode\" />\n </div>\n <div class=\"setting-item\">\n <div class=\"setting-label\">\n <span>色弱模式</span>\n <span class=\"setting-desc\">提供无障碍访问支持</span>\n </div>\n <NSwitch v-model:value=\"colorWeakMode\" />\n </div>\n </div>\n\n <!-- 水印设置 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">水印设置</div>\n <div class=\"setting-item\">\n <span>启用水印</span>\n <NSwitch v-model:value=\"watermarkEnabled\" />\n </div>\n <div v-if=\"watermarkEnabled\" class=\"mt-2\">\n <NInput\n v-model:value=\"watermarkText\"\n placeholder=\"请输入水印内容\"\n size=\"small\"\n />\n </div>\n </div>\n\n <!-- 系统信息 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">系统信息</div>\n <div class=\"system-info\">\n <div class=\"info-item\">\n <span class=\"info-label\">浏览器</span>\n <span class=\"info-value\">{{ systemInfo.browser }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">操作系统</span>\n <span class=\"info-value\">{{ systemInfo.os }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">屏幕分辨率</span>\n <span class=\"info-value\">{{ systemInfo.resolution }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">设备像素比</span>\n <span class=\"info-value\">{{ systemInfo.pixelRatio }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">语言</span>\n <span class=\"info-value\">{{ systemInfo.language }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">时区</span>\n <span class=\"info-value\">{{ systemInfo.timezone }}</span>\n </div>\n </div>\n </div>\n </NTabPane>\n </NTabs>\n </NDrawerContent>\n </NDrawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch, onUnmounted } from \"vue\";\nimport {\n NDrawer,\n NDrawerContent,\n NTabs,\n NTabPane,\n NRadioGroup,\n NRadioButton,\n NColorPicker,\n NSwitch,\n NButton,\n NSlider,\n NInput,\n useMessage,\n useDialog,\n} from \"naive-ui\";\nimport { useSettingsStore } from \"../../stores/settings\";\nimport { DEFAULT_SETTINGS } from \"../../constants\";\nimport { COLOR_SWATCHES, LAYOUT_MODE_OPTIONS, THEME_PRESETS } from \"./data\";\nimport type { ThemePreset } from \"../../types\";\n\n// ============ 数据定义 ============\n\nconst message = useMessage();\nconst dialog = useDialog();\nconst settingsStore = useSettingsStore();\nconst visible = defineModel<boolean>(\"show\", { default: false });\nconst activeTab = ref(\"appearance\");\n\n// 功能开关\nconst grayMode = ref(false);\nconst colorWeakMode = ref(false);\nconst watermarkEnabled = ref(false);\nconst watermarkText = ref(\"Robot Admin\");\n\n// 水印元素引用\nlet watermarkEl: HTMLElement | null = null;\n\n// 创建水印函数\nconst createWatermark = (text: string) => {\n // 移除旧水印\n if (watermarkEl) {\n document.body.removeChild(watermarkEl);\n watermarkEl = null;\n }\n\n // 创建 canvas\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\")!;\n canvas.width = 200;\n canvas.height = 150;\n\n ctx.font = \"14px Microsoft JhengHei\";\n ctx.fillStyle = \"rgba(120, 120, 120, 0.15)\";\n ctx.rotate((-20 * Math.PI) / 180);\n ctx.fillText(text, 0, 80);\n\n // 创建水印容器\n watermarkEl = document.createElement(\"div\");\n watermarkEl.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n background-image: url(${canvas.toDataURL()});\n background-repeat: repeat;\n z-index: 9999;\n user-select: none;\n `;\n\n document.body.appendChild(watermarkEl);\n};\n\n// 移除水印函数\nconst removeWatermark = () => {\n if (watermarkEl) {\n document.body.removeChild(watermarkEl);\n watermarkEl = null;\n }\n};\n\n// 监听灰色模式\nwatch(grayMode, (val) => {\n document.documentElement.classList.toggle(\"gray-mode\", val);\n});\n\n// 监听色弱模式\nwatch(colorWeakMode, (val) => {\n document.documentElement.classList.toggle(\"color-weak-mode\", val);\n});\n\n// 监听水印开关\nwatch(watermarkEnabled, (val) => {\n if (val) {\n createWatermark(watermarkText.value);\n } else {\n removeWatermark();\n }\n});\n\n// 监听水印文本\nwatch(watermarkText, (val) => {\n if (watermarkEnabled.value) {\n createWatermark(val);\n }\n});\n\n// 获取系统信息\nconst systemInfo = computed(() => {\n const ua = navigator.userAgent;\n let browser = \"Unknown\";\n let os = \"Unknown\";\n\n // 检测浏览器\n if (ua.includes(\"Chrome\")) browser = \"Chrome\";\n else if (ua.includes(\"Firefox\")) browser = \"Firefox\";\n else if (ua.includes(\"Safari\")) browser = \"Safari\";\n else if (ua.includes(\"Edge\")) browser = \"Edge\";\n\n // 检测操作系统\n if (ua.includes(\"Windows\")) os = \"Windows\";\n else if (ua.includes(\"Mac\")) os = \"macOS\";\n else if (ua.includes(\"Linux\")) os = \"Linux\";\n else if (ua.includes(\"Android\")) os = \"Android\";\n else if (ua.includes(\"iOS\")) os = \"iOS\";\n\n return {\n browser,\n os,\n resolution: `${window.screen.width} × ${window.screen.height}`,\n pixelRatio: window.devicePixelRatio + \"x\",\n language: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n };\n});\n\n// 组件卸载时清理水印\nonUnmounted(() => {\n removeWatermark();\n});\n\n// 处理布局切换 - 阻止抽屉关闭\nconst handleLayoutChange = (value: string, disabled?: boolean) => {\n if (disabled) return;\n settingsStore.layoutMode = value as any;\n};\n\n// ============ 方法 ============\n\n/**\n * 判断是否是当前预设\n */\nconst isCurrentPreset = (preset: ThemePreset) => {\n return settingsStore.primaryColor === preset.primaryColor;\n};\n\n/**\n * 应用预设方案\n */\nconst handleApplyPreset = (preset: ThemePreset) => {\n settingsStore.applyPreset(preset);\n message.success(`已应用「${preset.name}」主题方案`);\n};\n\n/**\n * 恢复外观默认设置\n */\nconst handleResetAppearance = () => {\n settingsStore.themeMode = DEFAULT_SETTINGS.themeMode;\n settingsStore.primaryColor = DEFAULT_SETTINGS.primaryColor;\n settingsStore.borderRadius = DEFAULT_SETTINGS.borderRadius;\n settingsStore.transitionType = DEFAULT_SETTINGS.transitionType;\n settingsStore.enableTransition = DEFAULT_SETTINGS.enableTransition;\n message.success(\"已恢复外观默认设置\");\n};\n\n/**\n * 恢复布局默认设置\n */\nconst handleResetLayout = () => {\n settingsStore.layoutMode = DEFAULT_SETTINGS.layoutMode;\n settingsStore.menuExpandMode = DEFAULT_SETTINGS.menuExpandMode;\n settingsStore.fixedHeader = DEFAULT_SETTINGS.fixedHeader;\n settingsStore.showBreadcrumb = DEFAULT_SETTINGS.showBreadcrumb;\n settingsStore.showBreadcrumbIcon = DEFAULT_SETTINGS.showBreadcrumbIcon;\n settingsStore.showTagsView = DEFAULT_SETTINGS.showTagsView;\n settingsStore.tagsViewHeight = DEFAULT_SETTINGS.tagsViewHeight;\n settingsStore.tagsViewStyle = DEFAULT_SETTINGS.tagsViewStyle;\n settingsStore.showFooter = DEFAULT_SETTINGS.showFooter;\n settingsStore.sidebarWidth = DEFAULT_SETTINGS.sidebarWidth;\n settingsStore.sidebarCollapsedWidth = DEFAULT_SETTINGS.sidebarCollapsedWidth;\n settingsStore.headerHeight = DEFAULT_SETTINGS.headerHeight;\n message.success(\"已恢复布局默认设置\");\n};\n\n/**\n * 重置配置\n */\nconst handleReset = () => {\n dialog.warning({\n title: \"确认重置\",\n content: \"确定要恢复默认配置吗?此操作不可撤销。\",\n positiveText: \"确认\",\n negativeText: \"取消\",\n onPositiveClick: () => {\n settingsStore.resetSettings();\n message.success(\"已恢复默认配置\");\n },\n });\n};\n\n/**\n * 清除缓存\n */\nconst handleClearCache = () => {\n dialog.warning({\n title: \"确认清除\",\n content: \"确定要清除浏览器缓存吗?\",\n positiveText: \"确认\",\n negativeText: \"取消\",\n onPositiveClick: () => {\n // 清除 localStorage\n const keysToKeep = [\"theme-mode\", \"robot-admin-settings\"];\n const allKeys = Object.keys(localStorage);\n allKeys.forEach((key) => {\n if (!keysToKeep.includes(key)) {\n localStorage.removeItem(key);\n }\n });\n // 清除 sessionStorage\n sessionStorage.clear();\n message.success(\"缓存已清除\");\n },\n });\n};\n\n/**\n * 重新加载页面\n */\nconst handleReload = () => {\n location.reload();\n};\n\n/**\n * 导出配置\n */\nconst handleExportConfig = () => {\n const config = {\n settings: settingsStore.settingsState,\n gray: grayMode.value,\n colorWeak: colorWeakMode.value,\n watermark: {\n enabled: watermarkEnabled.value,\n text: watermarkText.value,\n },\n exportTime: new Date().toISOString(),\n };\n const blob = new Blob([JSON.stringify(config, null, 2)], {\n type: \"application/json\",\n });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `robot-admin-config-${Date.now()}.json`;\n a.click();\n URL.revokeObjectURL(url);\n message.success(\"配置已导出\");\n};\n\n/**\n * 导入配置\n */\nconst handleImportConfig = () => {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.accept = \"application/json\";\n input.onchange = async (e: Event) => {\n const file = (e.target as HTMLInputElement).files?.[0];\n if (!file) return;\n\n try {\n const text = await file.text();\n const config = JSON.parse(text);\n\n // 应用配置\n if (config.settings) {\n settingsStore.$patch(config.settings);\n }\n if (config.gray !== undefined) {\n grayMode.value = config.gray;\n }\n if (config.colorWeak !== undefined) {\n colorWeakMode.value = config.colorWeak;\n }\n if (config.watermark) {\n watermarkEnabled.value = config.watermark.enabled;\n watermarkText.value = config.watermark.text;\n }\n\n message.success(\"配置已导入\");\n } catch (error) {\n message.error(\"配置文件格式错误\");\n }\n };\n input.click();\n};\n</script>\n\n<style scoped lang=\"scss\">\n@use \"../../styles/settings.scss\" as *;\n</style>\n","<!--\n * @robot-admin/layout - SettingsDrawer\n *\n * 布局配置抽屉\n * 包含外观/布局/功能三大模块,支持主题预设、布局切换、配置导入导出等\n -->\n<template>\n <NDrawer\n v-model:show=\"visible\"\n :width=\"380\"\n placement=\"right\"\n :trap-focus=\"false\"\n :block-scroll=\"false\"\n >\n <NDrawerContent title=\"⚙️ 布局配置\" closable>\n <NTabs v-model:value=\"activeTab\" animated class=\"settings-tabs\">\n <!-- 外观 Tab -->\n <NTabPane name=\"appearance\" tab=\"🎨 外观\">\n <!-- 预设方案网格 -->\n <div class=\"settings-section\">\n <div class=\"preset-grid\">\n <div\n v-for=\"preset in THEME_PRESETS\"\n :key=\"preset.name\"\n class=\"preset-card\"\n :class=\"{ active: isCurrentPreset(preset) }\"\n @click=\"handleApplyPreset(preset)\"\n >\n <div class=\"preset-icon\">{{ preset.icon }}</div>\n <div class=\"preset-name\">{{ preset.name }}</div>\n <div class=\"preset-color\">\n <span\n class=\"color-dot\"\n :style=\"{ backgroundColor: preset.primaryColor }\"\n ></span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- 主题模式 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">主题模式</div>\n <NRadioGroup\n v-model:value=\"settingsStore.themeMode\"\n class=\"mode-group\"\n >\n <NRadioButton value=\"light\">\n <span class=\"i-mdi:white-balance-sunny mr-1\"></span>\n 浅色\n </NRadioButton>\n <NRadioButton value=\"dark\">\n <span class=\"i-mdi:moon-waning-crescent mr-1\"></span>\n 深色\n </NRadioButton>\n <NRadioButton value=\"auto\">\n <span class=\"i-mdi:theme-light-dark mr-1\"></span>\n 自动\n </NRadioButton>\n </NRadioGroup>\n </div>\n\n <!-- 主题色 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">主题色</div>\n <div class=\"color-picker-wrapper\">\n <NColorPicker\n v-model:value=\"settingsStore.primaryColor\"\n :show-alpha=\"false\"\n :swatches=\"COLOR_SWATCHES\"\n :actions=\"['confirm']\"\n />\n <span class=\"color-value\">{{ settingsStore.primaryColor }}</span>\n </div>\n </div>\n\n <!-- 圆角大小 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">圆角大小</div>\n <NRadioGroup\n v-model:value=\"settingsStore.borderRadius\"\n class=\"radius-group\"\n >\n <NRadioButton value=\"small\">小 (4px)</NRadioButton>\n <NRadioButton value=\"medium\">中 (6px)</NRadioButton>\n <NRadioButton value=\"large\">大 (8px)</NRadioButton>\n </NRadioGroup>\n </div>\n\n <!-- 页面动画 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">页面动画</div>\n <div class=\"flex items-center justify-between mb-2\">\n <span class=\"text-sm\">启用动画</span>\n <NSwitch v-model:value=\"settingsStore.enableTransition\" />\n </div>\n <NRadioGroup\n v-model:value=\"settingsStore.transitionType\"\n :disabled=\"!settingsStore.enableTransition\"\n class=\"transition-group\"\n >\n <NRadioButton value=\"fade\">淡入</NRadioButton>\n <NRadioButton value=\"slide\">滑动</NRadioButton>\n <NRadioButton value=\"zoom\">缩放</NRadioButton>\n <NRadioButton value=\"none\">无</NRadioButton>\n </NRadioGroup>\n </div>\n\n <!-- 恢复默认 -->\n <div class=\"settings-section\">\n <NButton block secondary @click=\"handleResetAppearance\">\n <template #icon>\n <span class=\"i-mdi:restore\"></span>\n </template>\n 恢复外观默认设置\n </NButton>\n </div>\n </NTabPane>\n\n <!-- 布局 Tab -->\n <NTabPane name=\"layout\" tab=\"📐 布局\">\n <!-- 布局模式 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">布局模式</div>\n <div class=\"layout-grid\">\n <div\n v-for=\"mode in LAYOUT_MODE_OPTIONS\"\n :key=\"mode.value\"\n class=\"layout-item\"\n :class=\"{\n active: settingsStore.layoutMode === mode.value,\n disabled: mode.disabled,\n }\"\n @click.stop=\"handleLayoutChange(mode.value, mode.disabled)\"\n >\n <div class=\"layout-screenshot\">\n <svg\n class=\"layout-svg\"\n viewBox=\"0 0 56 48\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <g v-html=\"mode.svg\"></g>\n </svg>\n </div>\n <div class=\"layout-label\">\n {{ mode.label }}\n <span v-if=\"mode.disabled\" class=\"badge-soon\">敬请期待</span>\n </div>\n </div>\n </div>\n </div>\n\n <div\n class=\"settings-section menu-expand-section\"\n data-menu-expand-mode=\"built-in\"\n >\n <div class=\"section-title\">菜单展开方式</div>\n <NRadioGroup\n v-model:value=\"settingsStore.menuExpandMode\"\n class=\"menu-expand-group\"\n >\n <NRadioButton value=\"inline\">传统展开</NRadioButton>\n <NRadioButton value=\"panel\">右侧面板</NRadioButton>\n </NRadioGroup>\n </div>\n\n <!-- 界面元素 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">界面元素</div>\n <div class=\"setting-item\">\n <span>显示面包屑</span>\n <NSwitch v-model:value=\"settingsStore.showBreadcrumb\" />\n </div>\n <div class=\"setting-item\">\n <span>显示面包屑图标</span>\n <NSwitch\n v-model:value=\"settingsStore.showBreadcrumbIcon\"\n :disabled=\"!settingsStore.showBreadcrumb\"\n />\n </div>\n <div class=\"setting-item\">\n <span>显示标签页</span>\n <NSwitch v-model:value=\"settingsStore.showTagsView\" />\n </div>\n <div class=\"setting-item\">\n <span>显示页脚</span>\n <NSwitch v-model:value=\"settingsStore.showFooter\" />\n </div>\n </div>\n\n <!-- 尺寸调整 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">尺寸调整</div>\n <div class=\"slider-item\">\n <div class=\"slider-label\">\n <span>侧边栏宽度</span>\n <span class=\"slider-value\"\n >{{ settingsStore.sidebarWidth }}px</span\n >\n </div>\n <NSlider\n v-model:value=\"settingsStore.sidebarWidth\"\n :min=\"180\"\n :max=\"280\"\n :step=\"10\"\n :marks=\"{ 180: '180', 220: '220', 280: '280' }\"\n />\n </div>\n <div class=\"slider-item\">\n <div class=\"slider-label\">\n <span>头部高度</span>\n <span class=\"slider-value\"\n >{{ settingsStore.headerHeight }}px</span\n >\n </div>\n <NSlider\n v-model:value=\"settingsStore.headerHeight\"\n :min=\"48\"\n :max=\"64\"\n :step=\"4\"\n :marks=\"{ 48: '48', 56: '56', 64: '64' }\"\n />\n </div>\n </div>\n\n <!-- 恢复默认 -->\n <div class=\"settings-section\">\n <NButton block secondary @click=\"handleResetLayout\">\n <template #icon>\n <span class=\"i-mdi:restore\"></span>\n </template>\n 恢复布局默认设置\n </NButton>\n </div>\n </NTabPane>\n\n <!-- 功能 Tab -->\n <NTabPane name=\"features\" tab=\"🔧 功能\">\n <!-- 缓存管理 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">缓存管理</div>\n <div class=\"action-buttons-grid\">\n <NButton secondary @click=\"handleClearCache\">\n <template #icon>\n <span class=\"i-mdi:delete-sweep\"></span>\n </template>\n 清除缓存\n </NButton>\n <NButton secondary @click=\"handleReload\">\n <template #icon>\n <span class=\"i-mdi:refresh\"></span>\n </template>\n 重新加载\n </NButton>\n </div>\n </div>\n\n <!-- 配置管理 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">配置管理</div>\n <div class=\"action-buttons-grid\">\n <NButton secondary @click=\"handleExportConfig\">\n <template #icon>\n <span class=\"i-mdi:download\"></span>\n </template>\n 导出配置\n </NButton>\n <NButton secondary @click=\"handleImportConfig\">\n <template #icon>\n <span class=\"i-mdi:upload\"></span>\n </template>\n 导入配置\n </NButton>\n </div>\n <NButton\n block\n type=\"error\"\n secondary\n class=\"mt-2\"\n @click=\"handleReset\"\n >\n <template #icon>\n <span class=\"i-mdi:restore\"></span>\n </template>\n 重置所有配置\n </NButton>\n </div>\n\n <!-- 系统模式 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">系统模式</div>\n <div class=\"setting-item\">\n <div class=\"setting-label\">\n <span>灰色模式</span>\n <span class=\"setting-desc\">用于哀悼日场景</span>\n </div>\n <NSwitch v-model:value=\"grayMode\" />\n </div>\n <div class=\"setting-item\">\n <div class=\"setting-label\">\n <span>色弱模式</span>\n <span class=\"setting-desc\">提供无障碍访问支持</span>\n </div>\n <NSwitch v-model:value=\"colorWeakMode\" />\n </div>\n </div>\n\n <!-- 水印设置 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">水印设置</div>\n <div class=\"setting-item\">\n <span>启用水印</span>\n <NSwitch v-model:value=\"watermarkEnabled\" />\n </div>\n <div v-if=\"watermarkEnabled\" class=\"mt-2\">\n <NInput\n v-model:value=\"watermarkText\"\n placeholder=\"请输入水印内容\"\n size=\"small\"\n />\n </div>\n </div>\n\n <!-- 系统信息 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">系统信息</div>\n <div class=\"system-info\">\n <div class=\"info-item\">\n <span class=\"info-label\">浏览器</span>\n <span class=\"info-value\">{{ systemInfo.browser }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">操作系统</span>\n <span class=\"info-value\">{{ systemInfo.os }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">屏幕分辨率</span>\n <span class=\"info-value\">{{ systemInfo.resolution }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">设备像素比</span>\n <span class=\"info-value\">{{ systemInfo.pixelRatio }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">语言</span>\n <span class=\"info-value\">{{ systemInfo.language }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">时区</span>\n <span class=\"info-value\">{{ systemInfo.timezone }}</span>\n </div>\n </div>\n </div>\n </NTabPane>\n </NTabs>\n </NDrawerContent>\n </NDrawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch, onUnmounted } from \"vue\";\nimport {\n NDrawer,\n NDrawerContent,\n NTabs,\n NTabPane,\n NRadioGroup,\n NRadioButton,\n NColorPicker,\n NSwitch,\n NButton,\n NSlider,\n NInput,\n useMessage,\n useDialog,\n} from \"naive-ui\";\nimport { useSettingsStore } from \"../../stores/settings\";\nimport { DEFAULT_SETTINGS } from \"../../constants\";\nimport { COLOR_SWATCHES, LAYOUT_MODE_OPTIONS, THEME_PRESETS } from \"./data\";\nimport type { ThemePreset } from \"../../types\";\n\n// ============ 数据定义 ============\n\nconst message = useMessage();\nconst dialog = useDialog();\nconst settingsStore = useSettingsStore();\nconst visible = defineModel<boolean>(\"show\", { default: false });\nconst activeTab = ref(\"appearance\");\n\n// 功能开关\nconst grayMode = ref(false);\nconst colorWeakMode = ref(false);\nconst watermarkEnabled = ref(false);\nconst watermarkText = ref(\"Robot Admin\");\n\n// 水印元素引用\nlet watermarkEl: HTMLElement | null = null;\n\n// 创建水印函数\nconst createWatermark = (text: string) => {\n // 移除旧水印\n if (watermarkEl) {\n document.body.removeChild(watermarkEl);\n watermarkEl = null;\n }\n\n // 创建 canvas\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\")!;\n canvas.width = 200;\n canvas.height = 150;\n\n ctx.font = \"14px Microsoft JhengHei\";\n ctx.fillStyle = \"rgba(120, 120, 120, 0.15)\";\n ctx.rotate((-20 * Math.PI) / 180);\n ctx.fillText(text, 0, 80);\n\n // 创建水印容器\n watermarkEl = document.createElement(\"div\");\n watermarkEl.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n background-image: url(${canvas.toDataURL()});\n background-repeat: repeat;\n z-index: 9999;\n user-select: none;\n `;\n\n document.body.appendChild(watermarkEl);\n};\n\n// 移除水印函数\nconst removeWatermark = () => {\n if (watermarkEl) {\n document.body.removeChild(watermarkEl);\n watermarkEl = null;\n }\n};\n\n// 监听灰色模式\nwatch(grayMode, (val) => {\n document.documentElement.classList.toggle(\"gray-mode\", val);\n});\n\n// 监听色弱模式\nwatch(colorWeakMode, (val) => {\n document.documentElement.classList.toggle(\"color-weak-mode\", val);\n});\n\n// 监听水印开关\nwatch(watermarkEnabled, (val) => {\n if (val) {\n createWatermark(watermarkText.value);\n } else {\n removeWatermark();\n }\n});\n\n// 监听水印文本\nwatch(watermarkText, (val) => {\n if (watermarkEnabled.value) {\n createWatermark(val);\n }\n});\n\n// 获取系统信息\nconst systemInfo = computed(() => {\n const ua = navigator.userAgent;\n let browser = \"Unknown\";\n let os = \"Unknown\";\n\n // 检测浏览器\n if (ua.includes(\"Chrome\")) browser = \"Chrome\";\n else if (ua.includes(\"Firefox\")) browser = \"Firefox\";\n else if (ua.includes(\"Safari\")) browser = \"Safari\";\n else if (ua.includes(\"Edge\")) browser = \"Edge\";\n\n // 检测操作系统\n if (ua.includes(\"Windows\")) os = \"Windows\";\n else if (ua.includes(\"Mac\")) os = \"macOS\";\n else if (ua.includes(\"Linux\")) os = \"Linux\";\n else if (ua.includes(\"Android\")) os = \"Android\";\n else if (ua.includes(\"iOS\")) os = \"iOS\";\n\n return {\n browser,\n os,\n resolution: `${window.screen.width} × ${window.screen.height}`,\n pixelRatio: window.devicePixelRatio + \"x\",\n language: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n };\n});\n\n// 组件卸载时清理水印\nonUnmounted(() => {\n removeWatermark();\n});\n\n// 处理布局切换 - 阻止抽屉关闭\nconst handleLayoutChange = (value: string, disabled?: boolean) => {\n if (disabled) return;\n settingsStore.layoutMode = value as any;\n};\n\n// ============ 方法 ============\n\n/**\n * 判断是否是当前预设\n */\nconst isCurrentPreset = (preset: ThemePreset) => {\n return settingsStore.primaryColor === preset.primaryColor;\n};\n\n/**\n * 应用预设方案\n */\nconst handleApplyPreset = (preset: ThemePreset) => {\n settingsStore.applyPreset(preset);\n message.success(`已应用「${preset.name}」主题方案`);\n};\n\n/**\n * 恢复外观默认设置\n */\nconst handleResetAppearance = () => {\n settingsStore.themeMode = DEFAULT_SETTINGS.themeMode;\n settingsStore.primaryColor = DEFAULT_SETTINGS.primaryColor;\n settingsStore.borderRadius = DEFAULT_SETTINGS.borderRadius;\n settingsStore.transitionType = DEFAULT_SETTINGS.transitionType;\n settingsStore.enableTransition = DEFAULT_SETTINGS.enableTransition;\n message.success(\"已恢复外观默认设置\");\n};\n\n/**\n * 恢复布局默认设置\n */\nconst handleResetLayout = () => {\n settingsStore.layoutMode = DEFAULT_SETTINGS.layoutMode;\n settingsStore.menuExpandMode = DEFAULT_SETTINGS.menuExpandMode;\n settingsStore.fixedHeader = DEFAULT_SETTINGS.fixedHeader;\n settingsStore.showBreadcrumb = DEFAULT_SETTINGS.showBreadcrumb;\n settingsStore.showBreadcrumbIcon = DEFAULT_SETTINGS.showBreadcrumbIcon;\n settingsStore.showTagsView = DEFAULT_SETTINGS.showTagsView;\n settingsStore.tagsViewHeight = DEFAULT_SETTINGS.tagsViewHeight;\n settingsStore.tagsViewStyle = DEFAULT_SETTINGS.tagsViewStyle;\n settingsStore.showFooter = DEFAULT_SETTINGS.showFooter;\n settingsStore.sidebarWidth = DEFAULT_SETTINGS.sidebarWidth;\n settingsStore.sidebarCollapsedWidth = DEFAULT_SETTINGS.sidebarCollapsedWidth;\n settingsStore.headerHeight = DEFAULT_SETTINGS.headerHeight;\n message.success(\"已恢复布局默认设置\");\n};\n\n/**\n * 重置配置\n */\nconst handleReset = () => {\n dialog.warning({\n title: \"确认重置\",\n content: \"确定要恢复默认配置吗?此操作不可撤销。\",\n positiveText: \"确认\",\n negativeText: \"取消\",\n onPositiveClick: () => {\n settingsStore.resetSettings();\n message.success(\"已恢复默认配置\");\n },\n });\n};\n\n/**\n * 清除缓存\n */\nconst handleClearCache = () => {\n dialog.warning({\n title: \"确认清除\",\n content: \"确定要清除浏览器缓存吗?\",\n positiveText: \"确认\",\n negativeText: \"取消\",\n onPositiveClick: () => {\n // 清除 localStorage\n const keysToKeep = [\"theme-mode\", \"robot-admin-settings\"];\n const allKeys = Object.keys(localStorage);\n allKeys.forEach((key) => {\n if (!keysToKeep.includes(key)) {\n localStorage.removeItem(key);\n }\n });\n // 清除 sessionStorage\n sessionStorage.clear();\n message.success(\"缓存已清除\");\n },\n });\n};\n\n/**\n * 重新加载页面\n */\nconst handleReload = () => {\n location.reload();\n};\n\n/**\n * 导出配置\n */\nconst handleExportConfig = () => {\n const config = {\n settings: settingsStore.settingsState,\n gray: grayMode.value,\n colorWeak: colorWeakMode.value,\n watermark: {\n enabled: watermarkEnabled.value,\n text: watermarkText.value,\n },\n exportTime: new Date().toISOString(),\n };\n const blob = new Blob([JSON.stringify(config, null, 2)], {\n type: \"application/json\",\n });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `robot-admin-config-${Date.now()}.json`;\n a.click();\n URL.revokeObjectURL(url);\n message.success(\"配置已导出\");\n};\n\n/**\n * 导入配置\n */\nconst handleImportConfig = () => {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.accept = \"application/json\";\n input.onchange = async (e: Event) => {\n const file = (e.target as HTMLInputElement).files?.[0];\n if (!file) return;\n\n try {\n const text = await file.text();\n const config = JSON.parse(text);\n\n // 应用配置\n if (config.settings) {\n settingsStore.$patch(config.settings);\n }\n if (config.gray !== undefined) {\n grayMode.value = config.gray;\n }\n if (config.colorWeak !== undefined) {\n colorWeakMode.value = config.colorWeak;\n }\n if (config.watermark) {\n watermarkEnabled.value = config.watermark.enabled;\n watermarkText.value = config.watermark.text;\n }\n\n message.success(\"配置已导入\");\n } catch (error) {\n message.error(\"配置文件格式错误\");\n }\n };\n input.click();\n};\n</script>\n\n<style scoped lang=\"scss\">\n@use \"../../styles/settings.scss\" as *;\n</style>\n","<!--\r\n * @robot-admin/layout - BrandLogo\r\n *\r\n * 品牌 Logo 组件\r\n * 从 LayoutContext.brand 读取配置,自动渲染 Logo + 品牌名\r\n * 支持折叠状态、横向/纵向布局\r\n -->\r\n<template>\r\n <div\r\n class=\"brand-logo\"\r\n :class=\"{\r\n 'brand-logo--collapsed': collapsed,\r\n 'brand-logo--horizontal': variant === 'horizontal',\r\n }\"\r\n >\r\n <div class=\"brand-logo__icon\">\r\n <div class=\"brand-logo__glow\"></div>\r\n <video\r\n v-if=\"brandConfig.logoType === 'video' && brandConfig.logoSrc\"\r\n :src=\"brandConfig.logoSrc\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n autoplay\r\n loop\r\n muted\r\n playsinline\r\n class=\"brand-logo__media\"\r\n />\r\n <img\r\n v-else-if=\"brandConfig.logoSrc\"\r\n :src=\"brandConfig.logoSrc\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n class=\"brand-logo__media\"\r\n alt=\"Logo\"\r\n />\r\n </div>\r\n <template v-if=\"showName && !collapsed\">\r\n <div class=\"brand-logo__name\">\r\n <span class=\"brand-logo__title\">{{ brandConfig.name }}</span>\r\n <span v-if=\"brandConfig.subtitle\" class=\"brand-logo__subtitle\">{{\r\n brandConfig.subtitle\r\n }}</span>\r\n </div>\r\n <div v-if=\"showDivider\" class=\"brand-logo__divider\"></div>\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n useLayoutContext,\r\n DEFAULT_BRAND_CONFIG,\r\n} from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"BrandLogo\" });\r\n\r\nwithDefaults(\r\n defineProps<{\r\n /** 是否显示品牌名称 */\r\n showName?: boolean;\r\n /** 是否显示分隔线 */\r\n showDivider?: boolean;\r\n /** Logo 尺寸 */\r\n size?: number;\r\n /** 是否折叠状态 */\r\n collapsed?: boolean;\r\n /** 布局方向 */\r\n variant?: \"vertical\" | \"horizontal\";\r\n }>(),\r\n {\r\n showName: false,\r\n showDivider: false,\r\n size: 36,\r\n collapsed: false,\r\n variant: \"horizontal\",\r\n },\r\n);\r\n\r\nconst ctx = useLayoutContext();\r\nconst brandConfig = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\r\n</script>\r\n\r\n<style scoped>\r\n.brand-logo {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.brand-logo--collapsed {\r\n justify-content: center;\r\n}\r\n\r\n.brand-logo__icon {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex-shrink: 0;\r\n}\r\n\r\n.brand-logo__glow {\r\n position: absolute;\r\n inset: -4px;\r\n border-radius: 50%;\r\n background: radial-gradient(\r\n circle,\r\n rgba(99, 102, 241, 0.15) 0%,\r\n transparent 70%\r\n );\r\n pointer-events: none;\r\n}\r\n\r\n.brand-logo__media {\r\n border-radius: 8px;\r\n object-fit: contain;\r\n}\r\n\r\n.brand-logo__name {\r\n display: flex;\r\n flex-direction: column;\r\n line-height: 1.2;\r\n white-space: nowrap;\r\n}\r\n\r\n.brand-logo__title {\r\n font-size: 15px;\r\n font-weight: 700;\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n background-clip: text;\r\n -webkit-background-clip: text;\r\n -webkit-text-fill-color: transparent;\r\n}\r\n\r\n.brand-logo__subtitle {\r\n font-size: 10px;\r\n opacity: 0.6;\r\n letter-spacing: 0.5px;\r\n}\r\n\r\n.brand-logo__divider {\r\n width: 1px;\r\n height: 24px;\r\n margin: 0 6px;\r\n background: linear-gradient(\r\n 180deg,\r\n transparent 0%,\r\n rgba(128, 128, 128, 0.3) 50%,\r\n transparent 100%\r\n );\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - BrandLogo\r\n *\r\n * 品牌 Logo 组件\r\n * 从 LayoutContext.brand 读取配置,自动渲染 Logo + 品牌名\r\n * 支持折叠状态、横向/纵向布局\r\n -->\r\n<template>\r\n <div\r\n class=\"brand-logo\"\r\n :class=\"{\r\n 'brand-logo--collapsed': collapsed,\r\n 'brand-logo--horizontal': variant === 'horizontal',\r\n }\"\r\n >\r\n <div class=\"brand-logo__icon\">\r\n <div class=\"brand-logo__glow\"></div>\r\n <video\r\n v-if=\"brandConfig.logoType === 'video' && brandConfig.logoSrc\"\r\n :src=\"brandConfig.logoSrc\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n autoplay\r\n loop\r\n muted\r\n playsinline\r\n class=\"brand-logo__media\"\r\n />\r\n <img\r\n v-else-if=\"brandConfig.logoSrc\"\r\n :src=\"brandConfig.logoSrc\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n class=\"brand-logo__media\"\r\n alt=\"Logo\"\r\n />\r\n </div>\r\n <template v-if=\"showName && !collapsed\">\r\n <div class=\"brand-logo__name\">\r\n <span class=\"brand-logo__title\">{{ brandConfig.name }}</span>\r\n <span v-if=\"brandConfig.subtitle\" class=\"brand-logo__subtitle\">{{\r\n brandConfig.subtitle\r\n }}</span>\r\n </div>\r\n <div v-if=\"showDivider\" class=\"brand-logo__divider\"></div>\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n useLayoutContext,\r\n DEFAULT_BRAND_CONFIG,\r\n} from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"BrandLogo\" });\r\n\r\nwithDefaults(\r\n defineProps<{\r\n /** 是否显示品牌名称 */\r\n showName?: boolean;\r\n /** 是否显示分隔线 */\r\n showDivider?: boolean;\r\n /** Logo 尺寸 */\r\n size?: number;\r\n /** 是否折叠状态 */\r\n collapsed?: boolean;\r\n /** 布局方向 */\r\n variant?: \"vertical\" | \"horizontal\";\r\n }>(),\r\n {\r\n showName: false,\r\n showDivider: false,\r\n size: 36,\r\n collapsed: false,\r\n variant: \"horizontal\",\r\n },\r\n);\r\n\r\nconst ctx = useLayoutContext();\r\nconst brandConfig = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\r\n</script>\r\n\r\n<style scoped>\r\n.brand-logo {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.brand-logo--collapsed {\r\n justify-content: center;\r\n}\r\n\r\n.brand-logo__icon {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex-shrink: 0;\r\n}\r\n\r\n.brand-logo__glow {\r\n position: absolute;\r\n inset: -4px;\r\n border-radius: 50%;\r\n background: radial-gradient(\r\n circle,\r\n rgba(99, 102, 241, 0.15) 0%,\r\n transparent 70%\r\n );\r\n pointer-events: none;\r\n}\r\n\r\n.brand-logo__media {\r\n border-radius: 8px;\r\n object-fit: contain;\r\n}\r\n\r\n.brand-logo__name {\r\n display: flex;\r\n flex-direction: column;\r\n line-height: 1.2;\r\n white-space: nowrap;\r\n}\r\n\r\n.brand-logo__title {\r\n font-size: 15px;\r\n font-weight: 700;\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n background-clip: text;\r\n -webkit-background-clip: text;\r\n -webkit-text-fill-color: transparent;\r\n}\r\n\r\n.brand-logo__subtitle {\r\n font-size: 10px;\r\n opacity: 0.6;\r\n letter-spacing: 0.5px;\r\n}\r\n\r\n.brand-logo__divider {\r\n width: 1px;\r\n height: 24px;\r\n margin: 0 6px;\r\n background: linear-gradient(\r\n 180deg,\r\n transparent 0%,\r\n rgba(128, 128, 128, 0.3) 50%,\r\n transparent 100%\r\n );\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - IconMenu\r\n *\r\n * 一级图标菜单\r\n * 用于 Mix / MixTop 布局的左侧一级菜单栏\r\n * 显示图标 + 标签,支持激活状态\r\n -->\r\n<template>\r\n <div class=\"icon-menu\">\r\n <div\r\n v-for=\"item in menus\"\r\n :key=\"item.path\"\r\n class=\"icon-menu__item\"\r\n :class=\"{ 'icon-menu__item--active': active === item.path }\"\r\n @click=\"emit('click', item)\"\r\n >\r\n <div class=\"icon-menu__icon\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && item.meta?.icon\"\r\n :name=\"item.meta.icon\"\r\n :size=\"20\"\r\n />\r\n <span v-else class=\"icon-menu__fallback\">\r\n {{ item.meta?.title?.charAt(0) || \"?\" }}\r\n </span>\r\n </div>\r\n <div class=\"icon-menu__label\">\r\n {{ item.meta?.title }}\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"IconMenu\" });\r\n\r\ndefineProps<{\r\n /** 菜单数据 */\r\n menus: MenuOptions[];\r\n /** 当前激活的菜单路径 */\r\n active?: string;\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n click: [item: MenuOptions];\r\n}>();\r\n\r\nconst ctx = useLayoutContext();\r\n</script>\r\n\r\n<style scoped>\r\n.icon-menu {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n gap: 2px;\r\n padding: 4px 0;\r\n width: 100%;\r\n}\r\n\r\n.icon-menu__item {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 4px;\r\n width: calc(100% - 12px);\r\n padding: 10px 4px 8px;\r\n margin: 0 6px;\r\n border-radius: 10px;\r\n cursor: pointer;\r\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\r\n position: relative;\r\n opacity: 0.7;\r\n}\r\n\r\n.icon-menu__item:hover {\r\n opacity: 1;\r\n background: rgba(99, 102, 241, 0.08);\r\n transform: translateY(-1px);\r\n}\r\n\r\n.icon-menu__item--active {\r\n opacity: 1;\r\n background: linear-gradient(\r\n 135deg,\r\n rgba(99, 102, 241, 0.15) 0%,\r\n rgba(139, 92, 246, 0.1) 100%\r\n );\r\n box-shadow: 0 2px 8px rgba(99, 102, 241, 0.2);\r\n}\r\n\r\n.icon-menu__item--active::before {\r\n content: \"\";\r\n position: absolute;\r\n left: 0;\r\n top: 50%;\r\n transform: translateY(-50%);\r\n width: 3px;\r\n height: 60%;\r\n border-radius: 0 3px 3px 0;\r\n background: linear-gradient(180deg, #667eea, #764ba2);\r\n}\r\n\r\n.icon-menu__icon {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 28px;\r\n height: 28px;\r\n font-size: 20px;\r\n}\r\n\r\n.icon-menu__fallback {\r\n font-size: 14px;\r\n font-weight: 600;\r\n width: 28px;\r\n height: 28px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n border-radius: 8px;\r\n background: rgba(128, 128, 128, 0.1);\r\n}\r\n\r\n.icon-menu__label {\r\n font-size: 11px;\r\n font-weight: 500;\r\n line-height: 1.2;\r\n text-align: center;\r\n max-width: 100%;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - IconMenu\r\n *\r\n * 一级图标菜单\r\n * 用于 Mix / MixTop 布局的左侧一级菜单栏\r\n * 显示图标 + 标签,支持激活状态\r\n -->\r\n<template>\r\n <div class=\"icon-menu\">\r\n <div\r\n v-for=\"item in menus\"\r\n :key=\"item.path\"\r\n class=\"icon-menu__item\"\r\n :class=\"{ 'icon-menu__item--active': active === item.path }\"\r\n @click=\"emit('click', item)\"\r\n >\r\n <div class=\"icon-menu__icon\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && item.meta?.icon\"\r\n :name=\"item.meta.icon\"\r\n :size=\"20\"\r\n />\r\n <span v-else class=\"icon-menu__fallback\">\r\n {{ item.meta?.title?.charAt(0) || \"?\" }}\r\n </span>\r\n </div>\r\n <div class=\"icon-menu__label\">\r\n {{ item.meta?.title }}\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"IconMenu\" });\r\n\r\ndefineProps<{\r\n /** 菜单数据 */\r\n menus: MenuOptions[];\r\n /** 当前激活的菜单路径 */\r\n active?: string;\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n click: [item: MenuOptions];\r\n}>();\r\n\r\nconst ctx = useLayoutContext();\r\n</script>\r\n\r\n<style scoped>\r\n.icon-menu {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n gap: 2px;\r\n padding: 4px 0;\r\n width: 100%;\r\n}\r\n\r\n.icon-menu__item {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 4px;\r\n width: calc(100% - 12px);\r\n padding: 10px 4px 8px;\r\n margin: 0 6px;\r\n border-radius: 10px;\r\n cursor: pointer;\r\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\r\n position: relative;\r\n opacity: 0.7;\r\n}\r\n\r\n.icon-menu__item:hover {\r\n opacity: 1;\r\n background: rgba(99, 102, 241, 0.08);\r\n transform: translateY(-1px);\r\n}\r\n\r\n.icon-menu__item--active {\r\n opacity: 1;\r\n background: linear-gradient(\r\n 135deg,\r\n rgba(99, 102, 241, 0.15) 0%,\r\n rgba(139, 92, 246, 0.1) 100%\r\n );\r\n box-shadow: 0 2px 8px rgba(99, 102, 241, 0.2);\r\n}\r\n\r\n.icon-menu__item--active::before {\r\n content: \"\";\r\n position: absolute;\r\n left: 0;\r\n top: 50%;\r\n transform: translateY(-50%);\r\n width: 3px;\r\n height: 60%;\r\n border-radius: 0 3px 3px 0;\r\n background: linear-gradient(180deg, #667eea, #764ba2);\r\n}\r\n\r\n.icon-menu__icon {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 28px;\r\n height: 28px;\r\n font-size: 20px;\r\n}\r\n\r\n.icon-menu__fallback {\r\n font-size: 14px;\r\n font-weight: 600;\r\n width: 28px;\r\n height: 28px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n border-radius: 8px;\r\n background: rgba(128, 128, 128, 0.1);\r\n}\r\n\r\n.icon-menu__label {\r\n font-size: 11px;\r\n font-weight: 500;\r\n line-height: 1.2;\r\n text-align: center;\r\n max-width: 100%;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - FloatingMenu\r\n *\r\n * 悬浮二级菜单面板\r\n * 用于 MixLayout 的悬浮二级菜单(点击一级菜单后展开)\r\n -->\r\n<template>\r\n <Transition name=\"slide-fade\">\r\n <div\r\n v-if=\"show && menuItem\"\r\n class=\"floating-menu\"\r\n :class=\"[ctx.isDark.value ? 'dark-theme' : 'light-theme']\"\r\n >\r\n <!-- 标题 -->\r\n <div class=\"floating-menu__header\">\r\n <div class=\"floating-menu__brand\">\r\n <span class=\"floating-menu__brand-title\">{{ brand.name }}</span>\r\n <span class=\"floating-menu__brand-subtitle\">{{\r\n brand.subtitle\r\n }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- 菜单列表 -->\r\n <div class=\"floating-menu__list\">\r\n <template v-for=\"child in menuItem.children\" :key=\"child.path\">\r\n <!-- 有子菜单的项 -->\r\n <div\r\n v-if=\"child.children && child.children.length > 0\"\r\n class=\"floating-menu__group\"\r\n >\r\n <div class=\"floating-menu__group-title\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && child.meta?.icon\"\r\n :name=\"child.meta.icon\"\r\n :size=\"16\"\r\n />\r\n <span>{{ child.meta?.title }}</span>\r\n </div>\r\n <div\r\n v-for=\"subChild in child.children\"\r\n :key=\"subChild.path\"\r\n class=\"floating-menu__item floating-menu__item--sub\"\r\n :class=\"{ active: isActive?.(subChild.path) }\"\r\n @click=\"emit('click', subChild)\"\r\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && subChild.meta?.icon\"\r\n :name=\"subChild.meta.icon\"\r\n :size=\"16\"\r\n />\r\n <span class=\"floating-menu__item-text\">{{\r\n subChild.meta?.title\r\n }}</span>\r\n </div>\r\n </div>\r\n <!-- 无子菜单的项 -->\r\n <div\r\n v-else\r\n class=\"floating-menu__item\"\r\n :class=\"{ active: isActive?.(child.path) }\"\r\n @click=\"emit('click', child)\"\r\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && child.meta?.icon\"\r\n :name=\"child.meta.icon\"\r\n :size=\"18\"\r\n />\r\n <span class=\"floating-menu__item-text\">{{\r\n child.meta?.title\r\n }}</span>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n </Transition>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport {\r\n useLayoutContext,\r\n DEFAULT_BRAND_CONFIG,\r\n} from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"FloatingMenu\" });\r\n\r\ndefineProps<{\r\n /** 是否显示 */\r\n show: boolean;\r\n /** 当前悬浮的一级菜单项(包含 children) */\r\n menuItem: MenuOptions | null;\r\n /** 判断菜单项是否激活 */\r\n isActive?: (path: string | undefined) => boolean;\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n click: [item: MenuOptions];\r\n}>();\r\n\r\nconst ctx = useLayoutContext();\r\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\r\n</script>\r\n\r\n<style scoped>\r\n.floating-menu {\r\n position: absolute;\r\n top: 0;\r\n left: 100%;\r\n width: 240px;\r\n height: 100%;\r\n z-index: 100;\r\n overflow-y: auto;\r\n border-right: 1px solid rgba(128, 128, 128, 0.1);\r\n backdrop-filter: blur(20px);\r\n -webkit-backdrop-filter: blur(20px);\r\n}\r\n\r\n.floating-menu.light-theme {\r\n background: rgba(255, 255, 255, 0.95);\r\n box-shadow: 4px 0 24px rgba(0, 0, 0, 0.08);\r\n}\r\n\r\n.floating-menu.dark-theme {\r\n background: rgba(30, 30, 36, 0.95);\r\n box-shadow: 4px 0 24px rgba(0, 0, 0, 0.3);\r\n}\r\n\r\n.floating-menu__header {\r\n padding: 16px 20px 12px;\r\n border-bottom: 1px solid rgba(128, 128, 128, 0.1);\r\n}\r\n\r\n.floating-menu__brand-title {\r\n font-size: 14px;\r\n font-weight: 700;\r\n background: linear-gradient(135deg, #667eea, #764ba2);\r\n background-clip: text;\r\n -webkit-background-clip: text;\r\n -webkit-text-fill-color: transparent;\r\n}\r\n\r\n.floating-menu__brand-subtitle {\r\n display: block;\r\n font-size: 11px;\r\n opacity: 0.5;\r\n margin-top: 2px;\r\n}\r\n\r\n.floating-menu__list {\r\n padding: 8px 12px;\r\n}\r\n\r\n.floating-menu__group {\r\n margin-bottom: 8px;\r\n}\r\n\r\n.floating-menu__group-title {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n padding: 8px 8px 4px;\r\n font-size: 12px;\r\n font-weight: 600;\r\n opacity: 0.5;\r\n text-transform: uppercase;\r\n letter-spacing: 0.5px;\r\n}\r\n\r\n.floating-menu__item {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n padding: 10px 12px;\r\n margin: 2px 0;\r\n border-radius: 8px;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n font-size: 14px;\r\n}\r\n\r\n.floating-menu__item:hover {\r\n background: rgba(99, 102, 241, 0.08);\r\n}\r\n\r\n.floating-menu__item.active {\r\n background: linear-gradient(\r\n 135deg,\r\n rgba(99, 102, 241, 0.12),\r\n rgba(139, 92, 246, 0.08)\r\n );\r\n color: #667eea;\r\n}\r\n\r\n.floating-menu__item--sub {\r\n padding-left: 20px;\r\n font-size: 13px;\r\n}\r\n\r\n.floating-menu__item-text {\r\n flex: 1;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n/* Transition */\r\n.slide-fade-enter-active,\r\n.slide-fade-leave-active {\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.slide-fade-enter-from {\r\n opacity: 0;\r\n transform: translateX(-12px);\r\n}\r\n\r\n.slide-fade-leave-to {\r\n opacity: 0;\r\n transform: translateX(-12px);\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - FloatingMenu\r\n *\r\n * 悬浮二级菜单面板\r\n * 用于 MixLayout 的悬浮二级菜单(点击一级菜单后展开)\r\n -->\r\n<template>\r\n <Transition name=\"slide-fade\">\r\n <div\r\n v-if=\"show && menuItem\"\r\n class=\"floating-menu\"\r\n :class=\"[ctx.isDark.value ? 'dark-theme' : 'light-theme']\"\r\n >\r\n <!-- 标题 -->\r\n <div class=\"floating-menu__header\">\r\n <div class=\"floating-menu__brand\">\r\n <span class=\"floating-menu__brand-title\">{{ brand.name }}</span>\r\n <span class=\"floating-menu__brand-subtitle\">{{\r\n brand.subtitle\r\n }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- 菜单列表 -->\r\n <div class=\"floating-menu__list\">\r\n <template v-for=\"child in menuItem.children\" :key=\"child.path\">\r\n <!-- 有子菜单的项 -->\r\n <div\r\n v-if=\"child.children && child.children.length > 0\"\r\n class=\"floating-menu__group\"\r\n >\r\n <div class=\"floating-menu__group-title\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && child.meta?.icon\"\r\n :name=\"child.meta.icon\"\r\n :size=\"16\"\r\n />\r\n <span>{{ child.meta?.title }}</span>\r\n </div>\r\n <div\r\n v-for=\"subChild in child.children\"\r\n :key=\"subChild.path\"\r\n class=\"floating-menu__item floating-menu__item--sub\"\r\n :class=\"{ active: isActive?.(subChild.path) }\"\r\n @click=\"emit('click', subChild)\"\r\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && subChild.meta?.icon\"\r\n :name=\"subChild.meta.icon\"\r\n :size=\"16\"\r\n />\r\n <span class=\"floating-menu__item-text\">{{\r\n subChild.meta?.title\r\n }}</span>\r\n </div>\r\n </div>\r\n <!-- 无子菜单的项 -->\r\n <div\r\n v-else\r\n class=\"floating-menu__item\"\r\n :class=\"{ active: isActive?.(child.path) }\"\r\n @click=\"emit('click', child)\"\r\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && child.meta?.icon\"\r\n :name=\"child.meta.icon\"\r\n :size=\"18\"\r\n />\r\n <span class=\"floating-menu__item-text\">{{\r\n child.meta?.title\r\n }}</span>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n </Transition>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport {\r\n useLayoutContext,\r\n DEFAULT_BRAND_CONFIG,\r\n} from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"FloatingMenu\" });\r\n\r\ndefineProps<{\r\n /** 是否显示 */\r\n show: boolean;\r\n /** 当前悬浮的一级菜单项(包含 children) */\r\n menuItem: MenuOptions | null;\r\n /** 判断菜单项是否激活 */\r\n isActive?: (path: string | undefined) => boolean;\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n click: [item: MenuOptions];\r\n}>();\r\n\r\nconst ctx = useLayoutContext();\r\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\r\n</script>\r\n\r\n<style scoped>\r\n.floating-menu {\r\n position: absolute;\r\n top: 0;\r\n left: 100%;\r\n width: 240px;\r\n height: 100%;\r\n z-index: 100;\r\n overflow-y: auto;\r\n border-right: 1px solid rgba(128, 128, 128, 0.1);\r\n backdrop-filter: blur(20px);\r\n -webkit-backdrop-filter: blur(20px);\r\n}\r\n\r\n.floating-menu.light-theme {\r\n background: rgba(255, 255, 255, 0.95);\r\n box-shadow: 4px 0 24px rgba(0, 0, 0, 0.08);\r\n}\r\n\r\n.floating-menu.dark-theme {\r\n background: rgba(30, 30, 36, 0.95);\r\n box-shadow: 4px 0 24px rgba(0, 0, 0, 0.3);\r\n}\r\n\r\n.floating-menu__header {\r\n padding: 16px 20px 12px;\r\n border-bottom: 1px solid rgba(128, 128, 128, 0.1);\r\n}\r\n\r\n.floating-menu__brand-title {\r\n font-size: 14px;\r\n font-weight: 700;\r\n background: linear-gradient(135deg, #667eea, #764ba2);\r\n background-clip: text;\r\n -webkit-background-clip: text;\r\n -webkit-text-fill-color: transparent;\r\n}\r\n\r\n.floating-menu__brand-subtitle {\r\n display: block;\r\n font-size: 11px;\r\n opacity: 0.5;\r\n margin-top: 2px;\r\n}\r\n\r\n.floating-menu__list {\r\n padding: 8px 12px;\r\n}\r\n\r\n.floating-menu__group {\r\n margin-bottom: 8px;\r\n}\r\n\r\n.floating-menu__group-title {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n padding: 8px 8px 4px;\r\n font-size: 12px;\r\n font-weight: 600;\r\n opacity: 0.5;\r\n text-transform: uppercase;\r\n letter-spacing: 0.5px;\r\n}\r\n\r\n.floating-menu__item {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n padding: 10px 12px;\r\n margin: 2px 0;\r\n border-radius: 8px;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n font-size: 14px;\r\n}\r\n\r\n.floating-menu__item:hover {\r\n background: rgba(99, 102, 241, 0.08);\r\n}\r\n\r\n.floating-menu__item.active {\r\n background: linear-gradient(\r\n 135deg,\r\n rgba(99, 102, 241, 0.12),\r\n rgba(139, 92, 246, 0.08)\r\n );\r\n color: #667eea;\r\n}\r\n\r\n.floating-menu__item--sub {\r\n padding-left: 20px;\r\n font-size: 13px;\r\n}\r\n\r\n.floating-menu__item-text {\r\n flex: 1;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n/* Transition */\r\n.slide-fade-enter-active,\r\n.slide-fade-leave-active {\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.slide-fade-enter-from {\r\n opacity: 0;\r\n transform: translateX(-12px);\r\n}\r\n\r\n.slide-fade-leave-to {\r\n opacity: 0;\r\n transform: translateX(-12px);\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - SideMenu\r\n *\r\n * 右侧二级菜单侧边栏\r\n * 用于 ReverseHorizontalMixLayout 的右侧二级菜单\r\n -->\r\n<template>\r\n <template v-if=\"menus.length > 0\">\r\n <!-- 折叠/展开按钮 -->\r\n <div\r\n class=\"side-menu-toggle\"\r\n :class=\"[\r\n ctx.isDark.value ? 'dark-theme' : 'light-theme',\r\n { active: !isCollapsed },\r\n ]\"\r\n @click=\"isCollapsed = !isCollapsed\"\r\n >\r\n <i\r\n :class=\"[\r\n 'toggle-icon',\r\n isCollapsed ? 'i-ri:side-bar-line' : 'i-ri:arrow-right-s-line',\r\n ]\"\r\n ></i>\r\n </div>\r\n\r\n <!-- 侧边栏内容 -->\r\n <Transition name=\"glass-slide\">\r\n <div\r\n v-if=\"!isCollapsed\"\r\n class=\"side-menu\"\r\n :class=\"[ctx.isDark.value ? 'dark-theme' : 'light-theme']\"\r\n >\r\n <!-- 标题 -->\r\n <div class=\"side-menu__header\">\r\n <div class=\"side-menu__header-icon\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && activeItem?.meta?.icon\"\r\n :name=\"activeItem.meta.icon\"\r\n :size=\"17\"\r\n />\r\n </div>\r\n <span class=\"side-menu__header-title\">{{\r\n activeItem?.meta?.title || \"菜单\"\r\n }}</span>\r\n </div>\r\n\r\n <!-- 菜单列表 -->\r\n <div class=\"side-menu__list\">\r\n <template v-for=\"child in menus\" :key=\"child.path\">\r\n <!-- 有子菜单 -->\r\n <div\r\n v-if=\"child.children && child.children.length > 0\"\r\n class=\"side-menu__group\"\r\n >\r\n <div class=\"side-menu__group-label\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && child.meta?.icon\"\r\n :name=\"child.meta.icon\"\r\n :size=\"14\"\r\n />\r\n <span>{{ child.meta?.title }}</span>\r\n </div>\r\n <div\r\n v-for=\"subChild in child.children\"\r\n :key=\"subChild.path\"\r\n class=\"side-menu__item\"\r\n :class=\"{ active: isActive?.(subChild.path) }\"\r\n @click=\"emit('click', subChild)\"\r\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && subChild.meta?.icon\"\r\n :name=\"subChild.meta.icon\"\r\n :size=\"15\"\r\n />\r\n <span class=\"side-menu__item-text\">{{\r\n subChild.meta?.title\r\n }}</span>\r\n </div>\r\n </div>\r\n <!-- 无子菜单 -->\r\n <div\r\n v-else\r\n class=\"side-menu__item\"\r\n :class=\"{ active: isActive?.(child.path) }\"\r\n @click=\"emit('click', child)\"\r\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && child.meta?.icon\"\r\n :name=\"child.meta.icon\"\r\n :size=\"16\"\r\n />\r\n <span class=\"side-menu__item-text\">{{ child.meta?.title }}</span>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n </Transition>\r\n </template>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"SideMenu\" });\r\n\r\ndefineProps<{\r\n /** 二级菜单列表 */\r\n menus: MenuOptions[];\r\n /** 当前一级菜单项(显示标题和图标) */\r\n activeItem?: MenuOptions;\r\n /** 判断菜单项是否激活 */\r\n isActive?: (path: string | undefined) => boolean;\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n click: [item: MenuOptions];\r\n}>();\r\n\r\nconst ctx = useLayoutContext();\r\nconst isCollapsed = ref(false);\r\n</script>\r\n\r\n<style scoped>\r\n.side-menu-toggle {\r\n position: absolute;\r\n right: 0;\r\n top: 50%;\r\n transform: translateY(-50%) translateX(50%);\r\n width: 24px;\r\n height: 48px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n border-radius: 12px;\r\n cursor: pointer;\r\n z-index: 10;\r\n transition: all 0.25s ease;\r\n font-size: 14px;\r\n}\r\n\r\n.side-menu-toggle.light-theme {\r\n background: rgba(255, 255, 255, 0.9);\r\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n.side-menu-toggle.dark-theme {\r\n background: rgba(40, 40, 46, 0.9);\r\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\r\n}\r\n\r\n.side-menu-toggle:hover {\r\n transform: translateY(-50%) translateX(50%) scale(1.1);\r\n}\r\n\r\n.side-menu {\r\n width: 100%;\r\n height: 100%;\r\n overflow-y: auto;\r\n padding: 0 8px;\r\n}\r\n\r\n.side-menu.light-theme {\r\n background: rgba(255, 255, 255, 0.95);\r\n border-left: 1px solid rgba(0, 0, 0, 0.06);\r\n}\r\n\r\n.side-menu.dark-theme {\r\n background: rgba(30, 30, 36, 0.95);\r\n border-left: 1px solid rgba(255, 255, 255, 0.06);\r\n}\r\n\r\n.side-menu__header {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 16px 12px 12px;\r\n border-bottom: 1px solid rgba(128, 128, 128, 0.1);\r\n margin-bottom: 8px;\r\n}\r\n\r\n.side-menu__header-icon {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 28px;\r\n height: 28px;\r\n border-radius: 8px;\r\n background: linear-gradient(\r\n 135deg,\r\n rgba(99, 102, 241, 0.12),\r\n rgba(139, 92, 246, 0.08)\r\n );\r\n}\r\n\r\n.side-menu__header-title {\r\n font-size: 14px;\r\n font-weight: 600;\r\n}\r\n\r\n.side-menu__list {\r\n padding: 4px 0;\r\n}\r\n\r\n.side-menu__group {\r\n margin-bottom: 8px;\r\n}\r\n\r\n.side-menu__group-label {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n padding: 6px 12px;\r\n font-size: 12px;\r\n font-weight: 600;\r\n opacity: 0.5;\r\n}\r\n\r\n.side-menu__item {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 9px 12px;\r\n margin: 2px 0;\r\n border-radius: 8px;\r\n cursor: pointer;\r\n font-size: 13px;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.side-menu__item:hover {\r\n background: rgba(99, 102, 241, 0.06);\r\n}\r\n\r\n.side-menu__item.active {\r\n background: linear-gradient(\r\n 135deg,\r\n rgba(99, 102, 241, 0.1),\r\n rgba(139, 92, 246, 0.06)\r\n );\r\n color: #667eea;\r\n font-weight: 500;\r\n}\r\n\r\n.side-menu__item-text {\r\n flex: 1;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n/* Transition */\r\n.glass-slide-enter-active,\r\n.glass-slide-leave-active {\r\n transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.glass-slide-enter-from,\r\n.glass-slide-leave-to {\r\n opacity: 0;\r\n transform: translateX(20px);\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - SideMenu\r\n *\r\n * 右侧二级菜单侧边栏\r\n * 用于 ReverseHorizontalMixLayout 的右侧二级菜单\r\n -->\r\n<template>\r\n <template v-if=\"menus.length > 0\">\r\n <!-- 折叠/展开按钮 -->\r\n <div\r\n class=\"side-menu-toggle\"\r\n :class=\"[\r\n ctx.isDark.value ? 'dark-theme' : 'light-theme',\r\n { active: !isCollapsed },\r\n ]\"\r\n @click=\"isCollapsed = !isCollapsed\"\r\n >\r\n <i\r\n :class=\"[\r\n 'toggle-icon',\r\n isCollapsed ? 'i-ri:side-bar-line' : 'i-ri:arrow-right-s-line',\r\n ]\"\r\n ></i>\r\n </div>\r\n\r\n <!-- 侧边栏内容 -->\r\n <Transition name=\"glass-slide\">\r\n <div\r\n v-if=\"!isCollapsed\"\r\n class=\"side-menu\"\r\n :class=\"[ctx.isDark.value ? 'dark-theme' : 'light-theme']\"\r\n >\r\n <!-- 标题 -->\r\n <div class=\"side-menu__header\">\r\n <div class=\"side-menu__header-icon\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && activeItem?.meta?.icon\"\r\n :name=\"activeItem.meta.icon\"\r\n :size=\"17\"\r\n />\r\n </div>\r\n <span class=\"side-menu__header-title\">{{\r\n activeItem?.meta?.title || \"菜单\"\r\n }}</span>\r\n </div>\r\n\r\n <!-- 菜单列表 -->\r\n <div class=\"side-menu__list\">\r\n <template v-for=\"child in menus\" :key=\"child.path\">\r\n <!-- 有子菜单 -->\r\n <div\r\n v-if=\"child.children && child.children.length > 0\"\r\n class=\"side-menu__group\"\r\n >\r\n <div class=\"side-menu__group-label\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && child.meta?.icon\"\r\n :name=\"child.meta.icon\"\r\n :size=\"14\"\r\n />\r\n <span>{{ child.meta?.title }}</span>\r\n </div>\r\n <div\r\n v-for=\"subChild in child.children\"\r\n :key=\"subChild.path\"\r\n class=\"side-menu__item\"\r\n :class=\"{ active: isActive?.(subChild.path) }\"\r\n @click=\"emit('click', subChild)\"\r\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && subChild.meta?.icon\"\r\n :name=\"subChild.meta.icon\"\r\n :size=\"15\"\r\n />\r\n <span class=\"side-menu__item-text\">{{\r\n subChild.meta?.title\r\n }}</span>\r\n </div>\r\n </div>\r\n <!-- 无子菜单 -->\r\n <div\r\n v-else\r\n class=\"side-menu__item\"\r\n :class=\"{ active: isActive?.(child.path) }\"\r\n @click=\"emit('click', child)\"\r\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && child.meta?.icon\"\r\n :name=\"child.meta.icon\"\r\n :size=\"16\"\r\n />\r\n <span class=\"side-menu__item-text\">{{ child.meta?.title }}</span>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n </Transition>\r\n </template>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"SideMenu\" });\r\n\r\ndefineProps<{\r\n /** 二级菜单列表 */\r\n menus: MenuOptions[];\r\n /** 当前一级菜单项(显示标题和图标) */\r\n activeItem?: MenuOptions;\r\n /** 判断菜单项是否激活 */\r\n isActive?: (path: string | undefined) => boolean;\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n click: [item: MenuOptions];\r\n}>();\r\n\r\nconst ctx = useLayoutContext();\r\nconst isCollapsed = ref(false);\r\n</script>\r\n\r\n<style scoped>\r\n.side-menu-toggle {\r\n position: absolute;\r\n right: 0;\r\n top: 50%;\r\n transform: translateY(-50%) translateX(50%);\r\n width: 24px;\r\n height: 48px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n border-radius: 12px;\r\n cursor: pointer;\r\n z-index: 10;\r\n transition: all 0.25s ease;\r\n font-size: 14px;\r\n}\r\n\r\n.side-menu-toggle.light-theme {\r\n background: rgba(255, 255, 255, 0.9);\r\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n.side-menu-toggle.dark-theme {\r\n background: rgba(40, 40, 46, 0.9);\r\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\r\n}\r\n\r\n.side-menu-toggle:hover {\r\n transform: translateY(-50%) translateX(50%) scale(1.1);\r\n}\r\n\r\n.side-menu {\r\n width: 100%;\r\n height: 100%;\r\n overflow-y: auto;\r\n padding: 0 8px;\r\n}\r\n\r\n.side-menu.light-theme {\r\n background: rgba(255, 255, 255, 0.95);\r\n border-left: 1px solid rgba(0, 0, 0, 0.06);\r\n}\r\n\r\n.side-menu.dark-theme {\r\n background: rgba(30, 30, 36, 0.95);\r\n border-left: 1px solid rgba(255, 255, 255, 0.06);\r\n}\r\n\r\n.side-menu__header {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 16px 12px 12px;\r\n border-bottom: 1px solid rgba(128, 128, 128, 0.1);\r\n margin-bottom: 8px;\r\n}\r\n\r\n.side-menu__header-icon {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 28px;\r\n height: 28px;\r\n border-radius: 8px;\r\n background: linear-gradient(\r\n 135deg,\r\n rgba(99, 102, 241, 0.12),\r\n rgba(139, 92, 246, 0.08)\r\n );\r\n}\r\n\r\n.side-menu__header-title {\r\n font-size: 14px;\r\n font-weight: 600;\r\n}\r\n\r\n.side-menu__list {\r\n padding: 4px 0;\r\n}\r\n\r\n.side-menu__group {\r\n margin-bottom: 8px;\r\n}\r\n\r\n.side-menu__group-label {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n padding: 6px 12px;\r\n font-size: 12px;\r\n font-weight: 600;\r\n opacity: 0.5;\r\n}\r\n\r\n.side-menu__item {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 9px 12px;\r\n margin: 2px 0;\r\n border-radius: 8px;\r\n cursor: pointer;\r\n font-size: 13px;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.side-menu__item:hover {\r\n background: rgba(99, 102, 241, 0.06);\r\n}\r\n\r\n.side-menu__item.active {\r\n background: linear-gradient(\r\n 135deg,\r\n rgba(99, 102, 241, 0.1),\r\n rgba(139, 92, 246, 0.06)\r\n );\r\n color: #667eea;\r\n font-weight: 500;\r\n}\r\n\r\n.side-menu__item-text {\r\n flex: 1;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n/* Transition */\r\n.glass-slide-enter-active,\r\n.glass-slide-leave-active {\r\n transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.glass-slide-enter-from,\r\n.glass-slide-leave-to {\r\n opacity: 0;\r\n transform: translateX(20px);\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - DrawerMenu\r\n *\r\n * 抽屉式网格菜单\r\n * 用于 CardLayout 的 hover 触发的抽屉式功能导航\r\n -->\r\n<template>\r\n <div\r\n class=\"drawer-menu\"\r\n :class=\"[\r\n ctx.isDark.value ? 'dark-theme' : 'light-theme',\r\n { visible: show },\r\n ]\"\r\n @mouseenter=\"handlers.show()\"\r\n @mouseleave=\"handlers.startHide()\"\r\n >\r\n <div class=\"drawer-menu__header\">\r\n <div class=\"drawer-menu__title\">\r\n <!-- 纯 CSS 网格图标 -->\r\n <span class=\"grid-icon\">\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n </span>\r\n <span>功能导航</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"drawer-menu__content\">\r\n <div class=\"drawer-menu__grid\">\r\n <div\r\n v-for=\"category in menus\"\r\n :key=\"category.path\"\r\n class=\"drawer-menu__category\"\r\n >\r\n <div class=\"drawer-menu__category-header\" @click=\"navigate(category)\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && category.meta?.icon\"\r\n :name=\"category.meta.icon\"\r\n :size=\"18\"\r\n />\r\n <span>{{ category.meta?.title }}</span>\r\n </div>\r\n\r\n <div\r\n v-if=\"category.children && category.children.length > 0\"\r\n class=\"drawer-menu__items\"\r\n >\r\n <div\r\n v-for=\"item in category.children\"\r\n :key=\"item.path\"\r\n class=\"drawer-menu__item\"\r\n @click=\"navigate(item)\"\r\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && item.meta?.icon\"\r\n :name=\"item.meta.icon\"\r\n :size=\"16\"\r\n />\r\n <span class=\"drawer-menu__item-text\">{{ item.meta?.title }}</span>\r\n </div>\r\n\r\n <template v-for=\"item in category.children\" :key=\"item.path\">\r\n <div\r\n v-if=\"item.children && item.children.length > 0\"\r\n class=\"drawer-menu__sub-items\"\r\n >\r\n <div class=\"drawer-menu__sub-title\">\r\n {{ item.meta?.title }}\r\n </div>\r\n <div\r\n v-for=\"subItem in item.children\"\r\n :key=\"subItem.path\"\r\n class=\"drawer-menu__item drawer-menu__item--sub\"\r\n @click=\"navigate(subItem)\"\r\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && subItem.meta?.icon\"\r\n :name=\"subItem.meta.icon\"\r\n :size=\"14\"\r\n />\r\n <span class=\"drawer-menu__item-text\">{{\r\n subItem.meta?.title\r\n }}</span>\r\n </div>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { inject } from \"vue\";\r\nimport { useRouter } from \"vue-router\";\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\nimport {\r\n DRAWER_HANDLER_KEY,\r\n type DrawerHandlers,\r\n} from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"DrawerMenu\" });\r\n\r\ndefineProps<{\r\n /** 菜单数据 */\r\n menus: MenuOptions[];\r\n /** 是否显示 */\r\n show: boolean;\r\n}>();\r\n\r\nconst ctx = useLayoutContext();\r\nconst router = useRouter();\r\n\r\n// 使用共享的 drawer 控制器(由 C_LayoutContainer provide)\r\nconst handlers = inject<DrawerHandlers>(DRAWER_HANDLER_KEY, {\r\n show: () => {},\r\n startHide: () => {},\r\n hide: () => {},\r\n});\r\n\r\nconst navigate = (item: MenuOptions) => {\r\n if (item.path) {\r\n router.push(item.path);\r\n handlers.hide();\r\n }\r\n};\r\n</script>\r\n\r\n<style scoped>\r\n.drawer-menu {\r\n position: absolute;\r\n top: 100%;\r\n left: 0;\r\n right: 0;\r\n z-index: 100;\r\n max-height: 60vh;\r\n overflow-y: auto;\r\n opacity: 0;\r\n transform: translateY(-8px);\r\n pointer-events: none;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n backdrop-filter: blur(20px);\r\n -webkit-backdrop-filter: blur(20px);\r\n border-bottom-left-radius: 16px;\r\n border-bottom-right-radius: 16px;\r\n}\r\n\r\n.drawer-menu.visible {\r\n opacity: 1;\r\n transform: translateY(0);\r\n pointer-events: all;\r\n}\r\n\r\n.drawer-menu.light-theme {\r\n background: rgba(255, 255, 255, 0.96);\r\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);\r\n border: 1px solid rgba(0, 0, 0, 0.06);\r\n border-top: none;\r\n}\r\n\r\n.drawer-menu.dark-theme {\r\n background: rgba(30, 30, 36, 0.96);\r\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\r\n border: 1px solid rgba(255, 255, 255, 0.06);\r\n border-top: none;\r\n}\r\n\r\n.drawer-menu__header {\r\n padding: 16px 24px 8px;\r\n}\r\n\r\n.drawer-menu__title {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n font-size: 15px;\r\n font-weight: 600;\r\n}\r\n\r\n.drawer-menu__content {\r\n padding: 8px 24px 20px;\r\n}\r\n\r\n.drawer-menu__grid {\r\n display: grid;\r\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\r\n gap: 16px;\r\n}\r\n\r\n.drawer-menu__category {\r\n padding: 0;\r\n}\r\n\r\n.drawer-menu__category-header {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 8px 12px;\r\n font-size: 14px;\r\n font-weight: 600;\r\n cursor: pointer;\r\n border-radius: 8px;\r\n transition: background 0.2s ease;\r\n}\r\n\r\n.drawer-menu__category-header:hover {\r\n background: rgba(99, 102, 241, 0.06);\r\n}\r\n\r\n.drawer-menu__items {\r\n padding-left: 8px;\r\n}\r\n\r\n.drawer-menu__item {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 7px 12px;\r\n margin: 1px 0;\r\n border-radius: 6px;\r\n font-size: 13px;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.drawer-menu__item:hover {\r\n background: rgba(99, 102, 241, 0.08);\r\n color: #667eea;\r\n}\r\n\r\n.drawer-menu__item--sub {\r\n padding-left: 16px;\r\n font-size: 12px;\r\n}\r\n\r\n.drawer-menu__item-text {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.drawer-menu__sub-items {\r\n padding: 4px 0;\r\n}\r\n\r\n.drawer-menu__sub-title {\r\n padding: 4px 12px;\r\n font-size: 11px;\r\n font-weight: 600;\r\n opacity: 0.4;\r\n}\r\n\r\n/* 纯 CSS 网格图标 (2x2) */\r\n.grid-icon {\r\n display: inline-grid;\r\n grid-template-columns: repeat(2, 1fr);\r\n gap: 2.5px;\r\n width: 18px;\r\n height: 18px;\r\n padding: 1px;\r\n}\r\n\r\n.grid-icon span {\r\n background-color: currentColor;\r\n border-radius: 2px;\r\n opacity: 0.85;\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - DrawerMenu\r\n *\r\n * 抽屉式网格菜单\r\n * 用于 CardLayout 的 hover 触发的抽屉式功能导航\r\n -->\r\n<template>\r\n <div\r\n class=\"drawer-menu\"\r\n :class=\"[\r\n ctx.isDark.value ? 'dark-theme' : 'light-theme',\r\n { visible: show },\r\n ]\"\r\n @mouseenter=\"handlers.show()\"\r\n @mouseleave=\"handlers.startHide()\"\r\n >\r\n <div class=\"drawer-menu__header\">\r\n <div class=\"drawer-menu__title\">\r\n <!-- 纯 CSS 网格图标 -->\r\n <span class=\"grid-icon\">\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n </span>\r\n <span>功能导航</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"drawer-menu__content\">\r\n <div class=\"drawer-menu__grid\">\r\n <div\r\n v-for=\"category in menus\"\r\n :key=\"category.path\"\r\n class=\"drawer-menu__category\"\r\n >\r\n <div class=\"drawer-menu__category-header\" @click=\"navigate(category)\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && category.meta?.icon\"\r\n :name=\"category.meta.icon\"\r\n :size=\"18\"\r\n />\r\n <span>{{ category.meta?.title }}</span>\r\n </div>\r\n\r\n <div\r\n v-if=\"category.children && category.children.length > 0\"\r\n class=\"drawer-menu__items\"\r\n >\r\n <div\r\n v-for=\"item in category.children\"\r\n :key=\"item.path\"\r\n class=\"drawer-menu__item\"\r\n @click=\"navigate(item)\"\r\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && item.meta?.icon\"\r\n :name=\"item.meta.icon\"\r\n :size=\"16\"\r\n />\r\n <span class=\"drawer-menu__item-text\">{{ item.meta?.title }}</span>\r\n </div>\r\n\r\n <template v-for=\"item in category.children\" :key=\"item.path\">\r\n <div\r\n v-if=\"item.children && item.children.length > 0\"\r\n class=\"drawer-menu__sub-items\"\r\n >\r\n <div class=\"drawer-menu__sub-title\">\r\n {{ item.meta?.title }}\r\n </div>\r\n <div\r\n v-for=\"subItem in item.children\"\r\n :key=\"subItem.path\"\r\n class=\"drawer-menu__item drawer-menu__item--sub\"\r\n @click=\"navigate(subItem)\"\r\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && subItem.meta?.icon\"\r\n :name=\"subItem.meta.icon\"\r\n :size=\"14\"\r\n />\r\n <span class=\"drawer-menu__item-text\">{{\r\n subItem.meta?.title\r\n }}</span>\r\n </div>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { inject } from \"vue\";\r\nimport { useRouter } from \"vue-router\";\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\nimport {\r\n DRAWER_HANDLER_KEY,\r\n type DrawerHandlers,\r\n} from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"DrawerMenu\" });\r\n\r\ndefineProps<{\r\n /** 菜单数据 */\r\n menus: MenuOptions[];\r\n /** 是否显示 */\r\n show: boolean;\r\n}>();\r\n\r\nconst ctx = useLayoutContext();\r\nconst router = useRouter();\r\n\r\n// 使用共享的 drawer 控制器(由 C_LayoutContainer provide)\r\nconst handlers = inject<DrawerHandlers>(DRAWER_HANDLER_KEY, {\r\n show: () => {},\r\n startHide: () => {},\r\n hide: () => {},\r\n});\r\n\r\nconst navigate = (item: MenuOptions) => {\r\n if (item.path) {\r\n router.push(item.path);\r\n handlers.hide();\r\n }\r\n};\r\n</script>\r\n\r\n<style scoped>\r\n.drawer-menu {\r\n position: absolute;\r\n top: 100%;\r\n left: 0;\r\n right: 0;\r\n z-index: 100;\r\n max-height: 60vh;\r\n overflow-y: auto;\r\n opacity: 0;\r\n transform: translateY(-8px);\r\n pointer-events: none;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n backdrop-filter: blur(20px);\r\n -webkit-backdrop-filter: blur(20px);\r\n border-bottom-left-radius: 16px;\r\n border-bottom-right-radius: 16px;\r\n}\r\n\r\n.drawer-menu.visible {\r\n opacity: 1;\r\n transform: translateY(0);\r\n pointer-events: all;\r\n}\r\n\r\n.drawer-menu.light-theme {\r\n background: rgba(255, 255, 255, 0.96);\r\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);\r\n border: 1px solid rgba(0, 0, 0, 0.06);\r\n border-top: none;\r\n}\r\n\r\n.drawer-menu.dark-theme {\r\n background: rgba(30, 30, 36, 0.96);\r\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\r\n border: 1px solid rgba(255, 255, 255, 0.06);\r\n border-top: none;\r\n}\r\n\r\n.drawer-menu__header {\r\n padding: 16px 24px 8px;\r\n}\r\n\r\n.drawer-menu__title {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n font-size: 15px;\r\n font-weight: 600;\r\n}\r\n\r\n.drawer-menu__content {\r\n padding: 8px 24px 20px;\r\n}\r\n\r\n.drawer-menu__grid {\r\n display: grid;\r\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\r\n gap: 16px;\r\n}\r\n\r\n.drawer-menu__category {\r\n padding: 0;\r\n}\r\n\r\n.drawer-menu__category-header {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 8px 12px;\r\n font-size: 14px;\r\n font-weight: 600;\r\n cursor: pointer;\r\n border-radius: 8px;\r\n transition: background 0.2s ease;\r\n}\r\n\r\n.drawer-menu__category-header:hover {\r\n background: rgba(99, 102, 241, 0.06);\r\n}\r\n\r\n.drawer-menu__items {\r\n padding-left: 8px;\r\n}\r\n\r\n.drawer-menu__item {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 7px 12px;\r\n margin: 1px 0;\r\n border-radius: 6px;\r\n font-size: 13px;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.drawer-menu__item:hover {\r\n background: rgba(99, 102, 241, 0.08);\r\n color: #667eea;\r\n}\r\n\r\n.drawer-menu__item--sub {\r\n padding-left: 16px;\r\n font-size: 12px;\r\n}\r\n\r\n.drawer-menu__item-text {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.drawer-menu__sub-items {\r\n padding: 4px 0;\r\n}\r\n\r\n.drawer-menu__sub-title {\r\n padding: 4px 12px;\r\n font-size: 11px;\r\n font-weight: 600;\r\n opacity: 0.4;\r\n}\r\n\r\n/* 纯 CSS 网格图标 (2x2) */\r\n.grid-icon {\r\n display: inline-grid;\r\n grid-template-columns: repeat(2, 1fr);\r\n gap: 2.5px;\r\n width: 18px;\r\n height: 18px;\r\n padding: 1px;\r\n}\r\n\r\n.grid-icon span {\r\n background-color: currentColor;\r\n border-radius: 2px;\r\n opacity: 0.85;\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - MenuTrigger\r\n *\r\n * 菜单触发区域\r\n * 用于 CardLayout 的 hover 触发抽屉式菜单\r\n * 使用共享的 drawer 控制器避免与 DrawerMenu 定时器竞争\r\n -->\r\n<template>\r\n <div\r\n class=\"menu-trigger\"\r\n @mouseenter=\"handlers.show()\"\r\n @mouseleave=\"handlers.startHide()\"\r\n >\r\n <div class=\"menu-trigger__indicator\">\r\n <!-- 纯 CSS 三横线菜单图标 -->\r\n <span class=\"hamburger-icon\">\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n </span>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { inject } from \"vue\";\r\nimport {\r\n DRAWER_HANDLER_KEY,\r\n type DrawerHandlers,\r\n} from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"MenuTrigger\" });\r\n\r\ndefineProps<{\r\n show?: boolean;\r\n}>();\r\n\r\n// 使用共享控制器(由 C_LayoutContainer provide)\r\nconst handlers = inject<DrawerHandlers>(DRAWER_HANDLER_KEY, {\r\n show: () => {},\r\n startHide: () => {},\r\n hide: () => {},\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n.menu-trigger {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 0 8px;\r\n height: 100%;\r\n cursor: pointer;\r\n}\r\n\r\n.menu-trigger__indicator {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 36px;\r\n height: 36px;\r\n border-radius: 8px;\r\n font-size: 18px;\r\n transition: all 0.25s ease;\r\n opacity: 0.6;\r\n}\r\n\r\n.menu-trigger__indicator:hover {\r\n opacity: 1;\r\n background: rgba(99, 102, 241, 0.08);\r\n}\r\n\r\n/* 纯 CSS 三横线汉堡菜单图标 */\r\n.hamburger-icon {\r\n display: inline-flex;\r\n flex-direction: column;\r\n justify-content: space-between;\r\n width: 20px;\r\n height: 16px;\r\n padding: 2px 0;\r\n}\r\n\r\n.hamburger-icon span {\r\n display: block;\r\n width: 100%;\r\n height: 2px;\r\n background-color: currentColor;\r\n border-radius: 1px;\r\n opacity: 0.85;\r\n transition: all 0.2s ease;\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - MenuTrigger\r\n *\r\n * 菜单触发区域\r\n * 用于 CardLayout 的 hover 触发抽屉式菜单\r\n * 使用共享的 drawer 控制器避免与 DrawerMenu 定时器竞争\r\n -->\r\n<template>\r\n <div\r\n class=\"menu-trigger\"\r\n @mouseenter=\"handlers.show()\"\r\n @mouseleave=\"handlers.startHide()\"\r\n >\r\n <div class=\"menu-trigger__indicator\">\r\n <!-- 纯 CSS 三横线菜单图标 -->\r\n <span class=\"hamburger-icon\">\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n </span>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { inject } from \"vue\";\r\nimport {\r\n DRAWER_HANDLER_KEY,\r\n type DrawerHandlers,\r\n} from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"MenuTrigger\" });\r\n\r\ndefineProps<{\r\n show?: boolean;\r\n}>();\r\n\r\n// 使用共享控制器(由 C_LayoutContainer provide)\r\nconst handlers = inject<DrawerHandlers>(DRAWER_HANDLER_KEY, {\r\n show: () => {},\r\n startHide: () => {},\r\n hide: () => {},\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n.menu-trigger {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 0 8px;\r\n height: 100%;\r\n cursor: pointer;\r\n}\r\n\r\n.menu-trigger__indicator {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 36px;\r\n height: 36px;\r\n border-radius: 8px;\r\n font-size: 18px;\r\n transition: all 0.25s ease;\r\n opacity: 0.6;\r\n}\r\n\r\n.menu-trigger__indicator:hover {\r\n opacity: 1;\r\n background: rgba(99, 102, 241, 0.08);\r\n}\r\n\r\n/* 纯 CSS 三横线汉堡菜单图标 */\r\n.hamburger-icon {\r\n display: inline-flex;\r\n flex-direction: column;\r\n justify-content: space-between;\r\n width: 20px;\r\n height: 16px;\r\n padding: 2px 0;\r\n}\r\n\r\n.hamburger-icon span {\r\n display: block;\r\n width: 100%;\r\n height: 2px;\r\n background-color: currentColor;\r\n border-radius: 1px;\r\n opacity: 0.85;\r\n transition: all 0.2s ease;\r\n}\r\n</style>\r\n","/**\n * @robot-admin/layout\n *\n * 布局系统一键初始化\n */\n\nimport type { App } from \"vue\";\nimport type { SettingsStoreOptions } from \"./types\";\nimport { createSettingsStore } from \"./stores/settings\";\n\n/**\n * 初始化布局系统\n *\n * @example\n * ```ts\n * import { setupLayout } from '@robot-admin/layout'\n * setupLayout(app, {\n * onThemeModeChange: async (mode) => {\n * const themeStore = useThemeStore()\n * await themeStore.setMode(mode)\n * }\n * })\n * ```\n */\nexport function setupLayout(_app: App, options: SettingsStoreOptions = {}) {\n // 注册 settings store\n const settingsStore = createSettingsStore(options);\n\n // 初始化 CSS Variables\n settingsStore().syncCSSVariables();\n\n return settingsStore;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["$slots"],"sources":["../src/constants/index.ts","../src/stores/settings.ts","../src/composables/useLayoutContext.ts","../src/composables/useLayoutCache.ts","../src/components/layouts/SideLayout/index.vue","../src/components/layouts/SideLayout/index.vue","../src/components/ResponsiveMenu/index.vue","../src/components/ResponsiveMenu/index.vue","../src/components/layouts/TopLayout/index.vue","../src/components/layouts/TopLayout/index.vue","../src/composables/useMenuSplit.ts","../src/components/layouts/MixLayout/index.vue","../src/components/layouts/MixLayout/index.vue","../src/components/layouts/MixTopLayout/index.vue","../src/components/layouts/MixTopLayout/index.vue","../src/components/layouts/ReverseHorizontalMixLayout/index.vue","../src/components/layouts/ReverseHorizontalMixLayout/index.vue","../src/components/layouts/CardLayout/index.vue","../src/components/layouts/CardLayout/index.vue","../src/components/C_LayoutContainer/index.vue","../src/components/C_LayoutContainer/index.vue","../src/components/SettingsDrawer/data.ts","../src/components/SettingsDrawer/index.vue","../src/components/SettingsDrawer/index.vue","../src/components/BrandLogo/index.vue","../src/components/BrandLogo/index.vue","../src/components/IconMenu/index.vue","../src/components/IconMenu/index.vue","../src/components/FloatingMenu/index.vue","../src/components/FloatingMenu/index.vue","../src/components/SideMenu/index.vue","../src/components/SideMenu/index.vue","../src/components/DrawerMenu/index.vue","../src/components/DrawerMenu/index.vue","../src/components/MenuTrigger/index.vue","../src/components/MenuTrigger/index.vue","../src/setup.ts"],"sourcesContent":["/**\n * 布局和设置系统 - 常量定义\n */\n\nimport type {\n ThemePreset,\n SettingsState,\n LayoutInfo,\n PresetColor,\n} from \"../types\";\n\n// ============ 主题色预设 ============\nexport const PRESET_COLORS: PresetColor[] = [\n { name: \"拂晓蓝\", value: \"#409eff\" },\n { name: \"薄暮红\", value: \"#f5222d\" },\n { name: \"火山橙\", value: \"#fa541c\" },\n { name: \"日暮黄\", value: \"#faad14\" },\n { name: \"极光绿\", value: \"#52c41a\" },\n { name: \"明青\", value: \"#13c2c2\" },\n { name: \"极客蓝\", value: \"#2f54eb\" },\n { name: \"酱紫\", value: \"#722ed1\" },\n] as const;\n\n// ============ 主题预设方案 ============\nexport const THEME_PRESETS: ThemePreset[] = [\n {\n name: \"科技蓝\",\n icon: \"💙\",\n primaryColor: \"#409eff\",\n },\n {\n name: \"清新绿\",\n icon: \"💚\",\n primaryColor: \"#52c41a\",\n },\n {\n name: \"商务灰\",\n icon: \"🖤\",\n primaryColor: \"#595959\",\n },\n {\n name: \"活力橙\",\n icon: \"🧡\",\n primaryColor: \"#fa8c16\",\n },\n {\n name: \"优雅紫\",\n icon: \"💜\",\n primaryColor: \"#722ed1\",\n },\n {\n name: \"经典红\",\n icon: \"❤️\",\n primaryColor: \"#f5222d\",\n },\n];\n\n// ============ 布局模式信息 ============\nexport const LAYOUT_MODES: LayoutInfo[] = [\n {\n mode: \"side\",\n label: \"左侧菜单\",\n description: \"经典的左侧导航布局\",\n icon: \"i-mdi:page-layout-sidebar-left\",\n },\n {\n mode: \"top\",\n label: \"顶部菜单\",\n description: \"顶部水平导航布局\",\n icon: \"i-mdi:page-layout-header\",\n },\n {\n mode: \"mix\",\n label: \"混合菜单\",\n description: \"顶部 + 侧边混合布局\",\n icon: \"i-mdi:view-dashboard-outline\",\n },\n {\n mode: \"mix-top\",\n label: \"顶部混合\",\n description: \"侧边优先的混合布局\",\n icon: \"i-mdi:view-dashboard-variant-outline\",\n },\n {\n mode: \"reverse-horizontal-mix\",\n label: \"反转混合\",\n description: \"反转的水平混合布局\",\n icon: \"i-mdi:page-layout-header-footer\",\n },\n {\n mode: \"card-layout\",\n label: \"卡片布局\",\n description: \"卡片式布局风格\",\n icon: \"i-mdi:card-outline\",\n },\n];\n\n// ============ 圆角映射 ============\nexport const BORDER_RADIUS_MAP = {\n small: \"4px\",\n medium: \"6px\",\n large: \"8px\",\n} as const;\n\n// ============ 动画映射 ============\nexport const TRANSITION_MAP = {\n fade: \"fade-transform\",\n slide: \"fade-slide\",\n zoom: \"fade-zoom\",\n none: \"\",\n} as const;\n\n// ============ 默认配置 ============\nexport const DEFAULT_SETTINGS: SettingsState = {\n // 外观\n themeMode: \"light\",\n primaryColor: \"#409eff\",\n borderRadius: \"medium\",\n transitionType: \"slide\",\n enableTransition: true,\n\n // 布局\n layoutMode: \"side\",\n menuExpandMode: \"inline\",\n collapsed: false,\n fixedHeader: true,\n showBreadcrumb: true,\n showBreadcrumbIcon: true,\n showTagsView: true,\n tagsViewHeight: 44,\n tagsViewStyle: \"default\",\n showFooter: true,\n sidebarWidth: 220,\n sidebarCollapsedWidth: 64,\n headerHeight: 56,\n\n // 高级\n enableHotkeys: true,\n version: \"2.3.2\",\n};\n\n// ============ 其他常量 ============\n\n/** Color swatches for color picker */\nexport const COLOR_SWATCHES = PRESET_COLORS.map((c) => c.value);\n","import { defineStore } from \"pinia\";\nimport { ref, computed, watch } from \"vue\";\nimport type { ThemeMode } from \"@robot-admin/theme\";\nimport type {\n SettingsState,\n ThemePreset,\n LayoutMode,\n MenuExpandMode,\n TransitionType,\n BorderRadiusSize,\n TagsViewStyle,\n SettingsStoreOptions,\n} from \"../types\";\nimport {\n DEFAULT_SETTINGS,\n BORDER_RADIUS_MAP,\n TRANSITION_MAP,\n} from \"../constants\";\n\nconst ENUM_SETTINGS = {\n themeMode: new Set([\"light\", \"dark\", \"system\"]),\n borderRadius: new Set([\"small\", \"medium\", \"large\"]),\n transitionType: new Set([\"fade\", \"slide\", \"zoom\", \"none\"]),\n layoutMode: new Set([\n \"side\",\n \"top\",\n \"mix\",\n \"mix-top\",\n \"reverse-horizontal-mix\",\n \"card-layout\",\n ]),\n menuExpandMode: new Set([\"inline\", \"panel\"]),\n tagsViewStyle: new Set([\"default\", \"card\", \"smart\"]),\n} as const;\n\nconst BOOLEAN_SETTINGS: ReadonlySet<keyof SettingsState> = new Set([\n \"enableTransition\",\n \"collapsed\",\n \"fixedHeader\",\n \"showBreadcrumb\",\n \"showBreadcrumbIcon\",\n \"showTagsView\",\n \"showFooter\",\n \"enableHotkeys\",\n]);\n\nconst NUMBER_RANGES: Partial<\n Record<keyof SettingsState, readonly [number, number]>\n> = {\n tagsViewHeight: [24, 100],\n sidebarWidth: [120, 480],\n sidebarCollapsedWidth: [32, 160],\n headerHeight: [32, 160],\n};\n\n/**\n * 校验来自配置文件等不可信来源的设置。\n * 未知键为向前兼容而忽略;已知键的非法值会让整次导入失败。\n */\nexport function sanitizeSettingsPatch(input: unknown): Partial<SettingsState> {\n if (!input || typeof input !== \"object\" || Array.isArray(input)) {\n throw new TypeError(\"settings 必须是对象\");\n }\n\n const source = input as Record<string, unknown>;\n const result: Partial<SettingsState> = {};\n const assign = (key: keyof SettingsState, value: unknown) => {\n (result as Record<string, unknown>)[key] = value;\n };\n\n for (const [key, allowed] of Object.entries(ENUM_SETTINGS)) {\n const value = source[key];\n if (value === undefined) continue;\n if (typeof value !== \"string\" || !allowed.has(value as never)) {\n throw new RangeError(`无效的设置项 ${key}: ${String(value)}`);\n }\n assign(key as keyof SettingsState, value);\n }\n\n for (const key of BOOLEAN_SETTINGS) {\n const value = source[key];\n if (value === undefined) continue;\n if (typeof value !== \"boolean\") {\n throw new TypeError(`设置项 ${key} 必须是布尔值`);\n }\n assign(key, value);\n }\n\n for (const [key, range] of Object.entries(NUMBER_RANGES)) {\n const value = source[key];\n if (value === undefined) continue;\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value) ||\n value < range[0] ||\n value > range[1]\n ) {\n throw new RangeError(`设置项 ${key} 超出允许范围`);\n }\n assign(key as keyof SettingsState, value);\n }\n\n if (source.primaryColor !== undefined) {\n if (\n typeof source.primaryColor !== \"string\" ||\n !/^#[0-9a-fA-F]{6}$/.test(source.primaryColor)\n ) {\n throw new RangeError(\"primaryColor 必须是 #rrggbb 格式\");\n }\n result.primaryColor = source.primaryColor;\n }\n\n if (source.version !== undefined) {\n if (typeof source.version !== \"string\" || source.version.length > 50) {\n throw new TypeError(\"version 必须是长度不超过 50 的字符串\");\n }\n result.version = source.version;\n }\n\n return result;\n}\n\n/**\n * 调整颜色亮度\n * @param color - 十六进制颜色值(支持 #rgb / #rrggbb)\n * @param amount - 调整量,正数变亮,负数变暗\n * @returns 调整后的 #rrggbb;输入非法时原样返回(避免静默变黑)\n */\nexport function adjustColor(color: string, amount: number): string {\n if (typeof color !== \"string\") return \"#000000\";\n if (!Number.isFinite(amount)) return color;\n const normalizedAmount = Math.round(amount);\n const trimmedColor = color.trim();\n let hex = trimmedColor.startsWith(\"#\")\n ? trimmedColor.slice(1)\n : trimmedColor;\n // 支持 3 位简写(#rgb → #rrggbb)\n if (hex.length === 3) {\n hex = hex\n .split(\"\")\n .map((c) => c + c)\n .join(\"\");\n }\n // 仅当为合法的 6 位十六进制时才计算,否则原样返回\n if (!/^[0-9a-fA-F]{6}$/.test(hex)) {\n return color;\n }\n const num = parseInt(hex, 16);\n const r = Math.min(255, Math.max(0, (num >> 16) + normalizedAmount));\n const g = Math.min(\n 255,\n Math.max(0, ((num >> 8) & 0x00ff) + normalizedAmount),\n );\n const b = Math.min(\n 255,\n Math.max(0, (num & 0x0000ff) + normalizedAmount),\n );\n return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, \"0\")}`;\n}\n\n/**\n * 创建设置管理 Store\n * @param options - 配置选项\n */\nexport function createSettingsStore(options: SettingsStoreOptions = {}) {\n const { id = \"settings\", defaults = {}, onThemeModeChange } = options;\n if (typeof id !== \"string\" || !id.trim()) {\n throw new RangeError(\"Settings store id 不能为空\");\n }\n\n // 合并默认配置\n const finalDefaults = {\n ...DEFAULT_SETTINGS,\n ...sanitizeSettingsPatch(defaults),\n };\n\n return defineStore(id, () => {\n // ============ 状态定义 ============\n\n // 外观设置\n const themeMode = ref<ThemeMode>(finalDefaults.themeMode);\n const primaryColor = ref<string>(finalDefaults.primaryColor);\n const borderRadius = ref<BorderRadiusSize>(finalDefaults.borderRadius);\n const transitionType = ref<TransitionType>(finalDefaults.transitionType);\n const enableTransition = ref<boolean>(finalDefaults.enableTransition);\n\n // 布局设置\n const layoutMode = ref<LayoutMode>(finalDefaults.layoutMode);\n const menuExpandMode = ref<MenuExpandMode>(finalDefaults.menuExpandMode);\n const collapsed = ref<boolean>(finalDefaults.collapsed);\n const fixedHeader = ref<boolean>(finalDefaults.fixedHeader);\n const showBreadcrumb = ref<boolean>(finalDefaults.showBreadcrumb);\n const showBreadcrumbIcon = ref<boolean>(finalDefaults.showBreadcrumbIcon);\n const showTagsView = ref<boolean>(finalDefaults.showTagsView);\n const tagsViewHeight = ref<number>(finalDefaults.tagsViewHeight);\n const tagsViewStyle = ref<TagsViewStyle>(finalDefaults.tagsViewStyle);\n const showFooter = ref<boolean>(finalDefaults.showFooter);\n const sidebarWidth = ref<number>(finalDefaults.sidebarWidth);\n const sidebarCollapsedWidth = ref<number>(\n finalDefaults.sidebarCollapsedWidth,\n );\n const headerHeight = ref<number>(finalDefaults.headerHeight);\n\n // 高级设置\n const enableHotkeys = ref<boolean>(finalDefaults.enableHotkeys);\n const version = ref<string>(finalDefaults.version);\n\n // ============ 计算属性 ============\n\n /** 获取圆角值 */\n const borderRadiusValue = computed(\n () => BORDER_RADIUS_MAP[borderRadius.value],\n );\n\n /** 获取过渡动画名称 */\n const transitionName = computed(() => TRANSITION_MAP[transitionType.value]);\n\n /** 是否启用过渡动画 */\n const shouldEnableTransition = computed(\n () => enableTransition.value && transitionType.value !== \"none\",\n );\n\n /** 获取完整的设置状态 */\n const settingsState = computed<SettingsState>(() => ({\n themeMode: themeMode.value,\n primaryColor: primaryColor.value,\n borderRadius: borderRadius.value,\n transitionType: transitionType.value,\n enableTransition: enableTransition.value,\n layoutMode: layoutMode.value,\n menuExpandMode: menuExpandMode.value,\n collapsed: collapsed.value,\n fixedHeader: fixedHeader.value,\n showBreadcrumb: showBreadcrumb.value,\n showBreadcrumbIcon: showBreadcrumbIcon.value,\n showTagsView: showTagsView.value,\n tagsViewHeight: tagsViewHeight.value,\n tagsViewStyle: tagsViewStyle.value,\n showFooter: showFooter.value,\n sidebarWidth: sidebarWidth.value,\n sidebarCollapsedWidth: sidebarCollapsedWidth.value,\n headerHeight: headerHeight.value,\n enableHotkeys: enableHotkeys.value,\n version: version.value,\n }));\n\n // ============ 方法 ============\n\n /**\n * 同步 CSS Variables\n */\n const syncCSSVariables = () => {\n if (typeof document === \"undefined\") return;\n\n const root = document.documentElement;\n\n // 主题色\n root.style.setProperty(\"--primary-color\", primaryColor.value);\n root.style.setProperty(\n \"--primary-color-hover\",\n adjustColor(primaryColor.value, 10),\n );\n root.style.setProperty(\n \"--primary-color-pressed\",\n adjustColor(primaryColor.value, -10),\n );\n\n // 圆角\n root.style.setProperty(\"--border-radius\", borderRadiusValue.value);\n\n // 尺寸\n root.style.setProperty(\"--sidebar-width\", `${sidebarWidth.value}px`);\n root.style.setProperty(\n \"--sidebar-collapsed-width\",\n `${sidebarCollapsedWidth.value}px`,\n );\n root.style.setProperty(\"--header-height\", `${headerHeight.value}px`);\n root.style.setProperty(\"--tags-view-height\", `${tagsViewHeight.value}px`);\n };\n\n /**\n * 应用主题预设方案\n * 注意:只应用颜色配置,不改变布局模式和主题模式\n */\n const applyPreset = async (preset: ThemePreset) => {\n // 只应用主题色\n primaryColor.value = preset.primaryColor;\n\n // 应用其他细节配置(如果存在)\n if (preset.settings) {\n if (preset.settings.borderRadius)\n borderRadius.value = preset.settings.borderRadius;\n if (preset.settings.transitionType)\n transitionType.value = preset.settings.transitionType;\n if (preset.settings.showBreadcrumb !== undefined)\n showBreadcrumb.value = preset.settings.showBreadcrumb;\n if (preset.settings.showTagsView !== undefined)\n showTagsView.value = preset.settings.showTagsView;\n if (preset.settings.tagsViewStyle)\n tagsViewStyle.value = preset.settings.tagsViewStyle;\n }\n };\n\n /**\n * 重置配置\n */\n const resetSettings = () => {\n themeMode.value = finalDefaults.themeMode;\n primaryColor.value = finalDefaults.primaryColor;\n borderRadius.value = finalDefaults.borderRadius;\n transitionType.value = finalDefaults.transitionType;\n enableTransition.value = finalDefaults.enableTransition;\n\n layoutMode.value = finalDefaults.layoutMode;\n menuExpandMode.value = finalDefaults.menuExpandMode;\n collapsed.value = finalDefaults.collapsed;\n fixedHeader.value = finalDefaults.fixedHeader;\n showBreadcrumb.value = finalDefaults.showBreadcrumb;\n showBreadcrumbIcon.value = finalDefaults.showBreadcrumbIcon;\n showTagsView.value = finalDefaults.showTagsView;\n tagsViewHeight.value = finalDefaults.tagsViewHeight;\n tagsViewStyle.value = finalDefaults.tagsViewStyle;\n showFooter.value = finalDefaults.showFooter;\n sidebarWidth.value = finalDefaults.sidebarWidth;\n sidebarCollapsedWidth.value = finalDefaults.sidebarCollapsedWidth;\n headerHeight.value = finalDefaults.headerHeight;\n\n enableHotkeys.value = finalDefaults.enableHotkeys;\n };\n\n /**\n * 更新主题模式\n */\n const updateThemeMode = async (mode: ThemeMode) => {\n if (!ENUM_SETTINGS.themeMode.has(mode)) {\n throw new RangeError(`未知的主题模式: ${String(mode)}`);\n }\n const previousMode = themeMode.value;\n themeMode.value = mode;\n try {\n if (onThemeModeChange) {\n await onThemeModeChange(mode);\n }\n } catch (error) {\n themeMode.value = previousMode;\n throw error;\n }\n };\n\n /**\n * 切换侧边栏折叠状态\n */\n const toggleCollapse = () => {\n collapsed.value = !collapsed.value;\n };\n\n // ============ 监听器 ============\n\n // 监听配置变化,同步 CSS Variables\n watch(\n [\n primaryColor,\n borderRadiusValue,\n sidebarWidth,\n sidebarCollapsedWidth,\n headerHeight,\n tagsViewHeight,\n ],\n () => {\n syncCSSVariables();\n },\n { immediate: true },\n );\n\n // ============ 返回 ============\n\n return {\n // 状态\n themeMode,\n primaryColor,\n borderRadius,\n transitionType,\n enableTransition,\n layoutMode,\n menuExpandMode,\n collapsed,\n fixedHeader,\n showBreadcrumb,\n showBreadcrumbIcon,\n showTagsView,\n tagsViewHeight,\n tagsViewStyle,\n showFooter,\n sidebarWidth,\n sidebarCollapsedWidth,\n headerHeight,\n enableHotkeys,\n version,\n\n // 计算属性\n borderRadiusValue,\n transitionName,\n shouldEnableTransition,\n settingsState,\n\n // 方法\n syncCSSVariables,\n applyPreset,\n resetSettings,\n updateThemeMode,\n toggleCollapse,\n };\n });\n}\n\n/**\n * 默认的设置 Store(使用默认配置)\n */\nexport const useSettingsStore = createSettingsStore();\n","/**\r\n * @robot-admin/layout\r\n *\r\n * 布局上下文 - Provide/Inject 机制\r\n * 消费方通过 provide 注入数据,骨架组件通过 inject 读取\r\n * 实现布局骨架与业务数据的完全解耦\r\n */\r\n\r\nimport type { InjectionKey, ComputedRef, Component } from \"vue\";\r\nimport { inject } from \"vue\";\r\nimport type { MenuOptions } from \"../types/menu\";\r\nimport type { LayoutMode, MenuExpandMode } from \"../types\";\r\n\r\n/**\r\n * 品牌配置\r\n */\r\nexport interface LayoutBrandConfig {\r\n /** 品牌名称,如 'Robot Admin' */\r\n name?: string;\r\n /** 品牌副标题,如 '机器人管理系统' */\r\n subtitle?: string;\r\n /** Logo 地址(图片或视频) */\r\n logoSrc?: string;\r\n /** Logo 类型 */\r\n logoType?: \"video\" | \"image\";\r\n /** Logo 尺寸 */\r\n logoSize?: number;\r\n /** 首页路径,默认 '/home' */\r\n homePath?: string;\r\n /** 页脚版权文本 */\r\n copyright?: string;\r\n}\r\n\r\n/**\r\n * 布局上下文数据接口\r\n * 骨架组件通过 inject 获取这些数据\r\n */\r\nexport interface LayoutContext {\r\n // ============ 菜单数据 ============\r\n /** 菜单列表数据 */\r\n menus: ComputedRef<MenuOptions[]>;\r\n\r\n // ============ 主题状态 ============\r\n /** 是否为暗色模式 */\r\n isDark: ComputedRef<boolean>;\r\n\r\n // ============ 布局配置(来自 settings store) ============\r\n /** 当前布局模式 */\r\n layoutMode: ComputedRef<LayoutMode>;\r\n /** 菜单展开方式 */\r\n menuExpandMode?: ComputedRef<MenuExpandMode>;\r\n /** 侧边栏宽度 (px) */\r\n sidebarWidth: ComputedRef<number>;\r\n /** 侧边栏折叠后宽度 (px) */\r\n sidebarCollapsedWidth: ComputedRef<number>;\r\n /** 是否显示页脚 */\r\n showFooter: ComputedRef<boolean>;\r\n /** 是否显示标签页 */\r\n showTagsView: ComputedRef<boolean>;\r\n /** 标签页高度 (px) */\r\n tagsViewHeight: ComputedRef<number>;\r\n /** 头部高度 (px) */\r\n headerHeight: ComputedRef<number>;\r\n /** 过渡动画名称 */\r\n transitionName: ComputedRef<string>;\r\n /** 是否显示面包屑 */\r\n showBreadcrumb: ComputedRef<boolean>;\r\n /** 是否显示面包屑图标 */\r\n showBreadcrumbIcon: ComputedRef<boolean>;\r\n /** 是否固定头部 */\r\n fixedHeader: ComputedRef<boolean>;\r\n\r\n // ============ 品牌配置 ============\r\n /** 品牌信息 */\r\n brand: LayoutBrandConfig;\r\n\r\n // ============ 可选:图标组件 ============\r\n /**\r\n * 自定义图标组件(如 @iconify/vue 的 Icon 组件)\r\n * 用于布局内部组件渲染菜单图标\r\n * 组件需接受 name (string) 和 size (number) 两个 props\r\n * 如果不提供,将使用 CSS class 渲染图标\r\n */\r\n iconComponent?: Component;\r\n}\r\n\r\n/**\r\n * 布局上下文 Injection Key\r\n */\r\nexport const LAYOUT_CONTEXT_KEY: InjectionKey<LayoutContext> =\r\n Symbol(\"layout-context\");\r\n\r\n/**\r\n * 抽屉菜单统一控制器\r\n * 解决 MenuTrigger 与 DrawerMenu 各自拥有独立定时器导致的\r\n * \"鼠标从 trigger 移到 drawer 过程中 drawer 被关闭\"问题\r\n */\r\nexport interface DrawerHandlers {\r\n /** 显示抽屉并取消所有待执行的隐藏操作 */\r\n show: () => void;\r\n /** 启动延迟隐藏(300ms) */\r\n startHide: () => void;\r\n /** 立即隐藏抽屉(如导航跳转后) */\r\n hide: () => void;\r\n}\r\n\r\n/**\r\n * 抽屉菜单控制器 Injection Key\r\n */\r\nexport const DRAWER_HANDLER_KEY: InjectionKey<DrawerHandlers> =\r\n Symbol(\"drawer-handlers\");\r\n\r\n/**\r\n * 在骨架组件中获取布局上下文\r\n *\r\n * @throws 如果没有 provide LayoutContext 会抛出错误\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup>\r\n * // 在包的骨架组件内部使用\r\n * const ctx = useLayoutContext()\r\n * const menus = ctx.menus\r\n * const isDark = ctx.isDark\r\n * </script>\r\n * ```\r\n */\r\nexport function useLayoutContext(): LayoutContext {\r\n const ctx = inject(LAYOUT_CONTEXT_KEY);\r\n if (!ctx) {\r\n throw new Error(\r\n \"[Layout] LayoutContext not provided. \" +\r\n \"请在上层组件中使用 provide(LAYOUT_CONTEXT_KEY, ...) 注入布局数据。\",\r\n );\r\n }\r\n return ctx;\r\n}\r\n\r\n/**\r\n * 默认品牌配置\r\n */\r\nexport const DEFAULT_BRAND_CONFIG: Required<LayoutBrandConfig> = {\r\n name: \"Admin\",\r\n subtitle: \"\",\r\n logoSrc: \"\",\r\n logoType: \"image\",\r\n logoSize: 36,\r\n homePath: \"/home\",\r\n copyright: \"\",\r\n};\r\n","/**\n * @robot-admin/layout\n *\n * 统一的 KeepAlive 缓存管理\n * 从主项目提取的通用 composable,零业务依赖\n */\n\nimport { onUnmounted, ref, watch } from \"vue\";\nimport { useRoute } from \"vue-router\";\n\n/** KeepAlive 缓存选项 */\nexport interface LayoutCacheOptions {\n /** 最大缓存数量,默认 20 */\n maxCacheCount?: number;\n /** 是否输出缓存调试日志,默认 false */\n enableDevLog?: boolean;\n /** 是否暴露调试方法到 window,默认 false */\n exposeToWindow?: boolean;\n}\n\n/**\n * 统一的 KeepAlive 缓存管理 composable\n *\n * @param options - 缓存配置选项\n * @returns 缓存视图列表和管理方法\n *\n * @example\n * ```vue\n * <script setup>\n * import { useLayoutCache } from '@robot-admin/layout'\n *\n * const { cachedViews, maxCacheCount } = useLayoutCache({ maxCacheCount: 30 })\n * </script>\n *\n * <template>\n * <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n * <RouterView />\n * </KeepAlive>\n * </template>\n * ```\n */\nexport function useLayoutCache(options: LayoutCacheOptions = {}) {\n const {\n maxCacheCount: maxCount = 20,\n enableDevLog = false,\n exposeToWindow: expose = false,\n } = options;\n\n const route = useRoute();\n const cachedViews = ref<string[]>([]);\n const maxCacheCount = ref(maxCount);\n\n /**\n * 判断页面是否应该被缓存\n * 极简策略:只有明确配置 meta.keepAlive = true 才缓存\n */\n const shouldCache = (routeName: string | symbol | undefined | null) => {\n if (!routeName || typeof routeName !== \"string\") return false;\n return route.meta?.keepAlive === true;\n };\n\n /** 添加缓存 */\n const addCache = (name: string) => {\n if (!cachedViews.value.includes(name) && shouldCache(name)) {\n cachedViews.value.push(name);\n\n // 控制缓存数量\n if (cachedViews.value.length > maxCacheCount.value) {\n cachedViews.value.shift();\n }\n\n if (enableDevLog) {\n console.debug(\n `[KeepAlive] ✅ 缓存: ${name} (${cachedViews.value.length}/${maxCacheCount.value})`,\n );\n }\n }\n };\n\n /** 移除缓存 */\n const removeCache = (name: string) => {\n const index = cachedViews.value.indexOf(name);\n if (index > -1) {\n cachedViews.value.splice(index, 1);\n if (enableDevLog) {\n console.debug(`[KeepAlive] ❌ 移除: ${name}`);\n }\n }\n };\n\n /** 清空所有缓存 */\n const clearAllCache = () => {\n cachedViews.value = [];\n if (enableDevLog) {\n console.debug(\"[KeepAlive] 🗑️ 清空所有缓存\");\n }\n };\n\n // 暴露调试方法到 window\n const getCachedViews = () => cachedViews.value;\n if (expose && typeof window !== \"undefined\") {\n (window as any).__clearCache__ = clearAllCache;\n (window as any).__removeCache__ = removeCache;\n (window as any).__getCachedViews__ = getCachedViews;\n\n onUnmounted(() => {\n if ((window as any).__clearCache__ === clearAllCache) {\n delete (window as any).__clearCache__;\n }\n if ((window as any).__removeCache__ === removeCache) {\n delete (window as any).__removeCache__;\n }\n if ((window as any).__getCachedViews__ === getCachedViews) {\n delete (window as any).__getCachedViews__;\n }\n });\n }\n\n // 监听路由变化,动态管理缓存\n watch(\n () => route.name,\n (newName) => {\n if (newName && typeof newName === \"string\") {\n addCache(newName);\n }\n },\n { immediate: true },\n );\n\n return {\n cachedViews,\n maxCacheCount,\n addCache,\n removeCache,\n clearAllCache,\n };\n}\n","<!--\n * @robot-admin/layout - SideLayout\n *\n * 左侧菜单布局骨架\n * 经典的左侧导航 + 右侧内容布局,使用 NLayout/NLayoutSider 实现\n *\n * Slots:\n * #logo - Logo/品牌区域(侧边栏顶部)\n * #menu - 垂直菜单区域(侧边栏)\n * #header - 头部区域(含面包屑、导航栏右侧操作等)\n * #default - 页面内容(自动包含 RouterView + KeepAlive + Transition)\n * #footer - 页脚区域\n -->\n<template>\n <NLayout has-sider>\n <NLayoutSider\n bordered\n collapse-mode=\"width\"\n :collapsed-width=\"ctx.sidebarCollapsedWidth.value\"\n :width=\"ctx.sidebarWidth.value\"\n :native-scrollbar=\"false\"\n :collapsed=\"collapsed\"\n @update:collapsed=\"handleCollapsedUpdate\"\n :class=\"[\n 'layout-sider',\n 'no-horizontal-scroll',\n ctx.isDark.value ? 'dark-theme' : 'light-theme',\n ]\"\n >\n <slot name=\"logo\" />\n <slot name=\"menu\" :collapsed=\"collapsed\" />\n </NLayoutSider>\n\n <NLayout>\n <slot name=\"header\" />\n\n <NLayoutContent class=\"content-with-header p16px app-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"ctx.transitionName.value\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </NLayoutContent>\n\n <slot name=\"footer\" />\n </NLayout>\n </NLayout>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref, watch } from \"vue\";\nimport { NLayout, NLayoutSider, NLayoutContent } from \"naive-ui\";\nimport { useLayoutContext } from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\n\ndefineOptions({ name: \"SideLayout\" });\n\nconst props = withDefaults(\n defineProps<{\n /** 控制侧边栏折叠状态 (支持 v-model:collapsed) */\n collapsed?: boolean;\n }>(),\n { collapsed: false },\n);\n\nconst emit = defineEmits<{\n \"update:collapsed\": [value: boolean];\n}>();\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\n// 内部折叠状态,与 prop 双向同步\nconst _collapsed = ref(props.collapsed);\nwatch(\n () => props.collapsed,\n (v) => {\n _collapsed.value = v;\n },\n);\n\nconst collapsed = computed(() => _collapsed.value);\n\nconst handleCollapsedUpdate = (val: boolean) => {\n _collapsed.value = val;\n emit(\"update:collapsed\", val);\n};\n\n// 折叠切换\nconst toggleCollapse = () => {\n const next = !_collapsed.value;\n _collapsed.value = next;\n emit(\"update:collapsed\", next);\n};\n\ndefineExpose({\n collapsed,\n toggleCollapse,\n});\n</script>\n","<!--\n * @robot-admin/layout - SideLayout\n *\n * 左侧菜单布局骨架\n * 经典的左侧导航 + 右侧内容布局,使用 NLayout/NLayoutSider 实现\n *\n * Slots:\n * #logo - Logo/品牌区域(侧边栏顶部)\n * #menu - 垂直菜单区域(侧边栏)\n * #header - 头部区域(含面包屑、导航栏右侧操作等)\n * #default - 页面内容(自动包含 RouterView + KeepAlive + Transition)\n * #footer - 页脚区域\n -->\n<template>\n <NLayout has-sider>\n <NLayoutSider\n bordered\n collapse-mode=\"width\"\n :collapsed-width=\"ctx.sidebarCollapsedWidth.value\"\n :width=\"ctx.sidebarWidth.value\"\n :native-scrollbar=\"false\"\n :collapsed=\"collapsed\"\n @update:collapsed=\"handleCollapsedUpdate\"\n :class=\"[\n 'layout-sider',\n 'no-horizontal-scroll',\n ctx.isDark.value ? 'dark-theme' : 'light-theme',\n ]\"\n >\n <slot name=\"logo\" />\n <slot name=\"menu\" :collapsed=\"collapsed\" />\n </NLayoutSider>\n\n <NLayout>\n <slot name=\"header\" />\n\n <NLayoutContent class=\"content-with-header p16px app-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"ctx.transitionName.value\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </NLayoutContent>\n\n <slot name=\"footer\" />\n </NLayout>\n </NLayout>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref, watch } from \"vue\";\nimport { NLayout, NLayoutSider, NLayoutContent } from \"naive-ui\";\nimport { useLayoutContext } from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\n\ndefineOptions({ name: \"SideLayout\" });\n\nconst props = withDefaults(\n defineProps<{\n /** 控制侧边栏折叠状态 (支持 v-model:collapsed) */\n collapsed?: boolean;\n }>(),\n { collapsed: false },\n);\n\nconst emit = defineEmits<{\n \"update:collapsed\": [value: boolean];\n}>();\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\n// 内部折叠状态,与 prop 双向同步\nconst _collapsed = ref(props.collapsed);\nwatch(\n () => props.collapsed,\n (v) => {\n _collapsed.value = v;\n },\n);\n\nconst collapsed = computed(() => _collapsed.value);\n\nconst handleCollapsedUpdate = (val: boolean) => {\n _collapsed.value = val;\n emit(\"update:collapsed\", val);\n};\n\n// 折叠切换\nconst toggleCollapse = () => {\n const next = !_collapsed.value;\n _collapsed.value = next;\n emit(\"update:collapsed\", next);\n};\n\ndefineExpose({\n collapsed,\n toggleCollapse,\n});\n</script>\n","<!--\r\n * @robot-admin/layout - ResponsiveMenu\r\n *\r\n * 响应式水平菜单\r\n * 用于 TopLayout / MixTopLayout / ReverseHorizontalMixLayout 的水平菜单\r\n * 自动检测容器宽度,溢出项放入“更多”下拉菜单,使用 NMenu (naive-ui) 渲染\r\n -->\r\n<template>\r\n <div ref=\"containerRef\" class=\"responsive-menu\">\r\n <div ref=\"visibleRef\" class=\"responsive-menu__visible\">\r\n <NMenu\r\n v-if=\"visibleOptions.length > 0\"\r\n mode=\"horizontal\"\r\n :value=\"activeKey\"\r\n :options=\"visibleOptions\"\r\n :theme-overrides=\"menuThemeOverrides\"\r\n @update:value=\"handleSelect\"\r\n />\r\n </div>\r\n\r\n <NDropdown\r\n v-if=\"hiddenItems.length > 0\"\r\n :options=\"dropdownOptions\"\r\n placement=\"bottom-end\"\r\n trigger=\"hover\"\r\n @select=\"handleSelect\"\r\n >\r\n <div class=\"responsive-menu__more\">\r\n <NButton text class=\"responsive-menu__more-btn\">\r\n <!-- 纯 CSS 三个点图标 -->\r\n <span class=\"dots-icon\">\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n </span>\r\n </NButton>\r\n </div>\r\n </NDropdown>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, onMounted, onUnmounted, nextTick, h } from \"vue\";\r\nimport { useRoute, useRouter } from \"vue-router\";\r\nimport { NMenu, NDropdown, NButton } from \"naive-ui\";\r\nimport type { MenuOption, DropdownOption } from \"naive-ui\";\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"ResponsiveMenu\" });\r\n\r\nconst props = defineProps<{\r\n /** 菜单数据 */\r\n data: MenuOptions[];\r\n}>();\r\n\r\nconst route = useRoute();\r\nconst router = useRouter();\r\nconst ctx = useLayoutContext();\r\n\r\n// 安全包装\r\nconst menuData = computed(() => props.data || []);\r\nconst activeKey = computed(() => route.path);\r\n\r\n// 根据主题选择菜单样式\r\nconst isDarkMode = ctx.isDark;\r\n\r\nconst lightTheme = {\r\n itemTextColor: \"rgba(51, 65, 85, 0.85)\",\r\n itemTextColorHover: \"rgba(30, 64, 175, 0.95)\",\r\n itemTextColorActive: \"rgba(30, 64, 175, 1)\",\r\n itemTextColorActiveHover: \"rgba(30, 64, 175, 1)\",\r\n itemTextColorChildActive: \"rgba(30, 64, 175, 1)\",\r\n itemIconColor: \"rgba(51, 65, 85, 0.7)\",\r\n itemIconColorHover: \"rgba(30, 64, 175, 0.85)\",\r\n itemIconColorActive: \"rgba(30, 64, 175, 1)\",\r\n itemIconColorActiveHover: \"rgba(30, 64, 175, 1)\",\r\n itemIconColorChildActive: \"rgba(30, 64, 175, 1)\",\r\n itemColorHover: \"rgba(30, 64, 175, 0.06)\",\r\n itemColorActive: \"rgba(30, 64, 175, 0.1)\",\r\n itemColorActiveHover: \"rgba(30, 64, 175, 0.12)\",\r\n itemColorActiveCollapsed: \"rgba(30, 64, 175, 0.1)\",\r\n arrowColor: \"rgba(51, 65, 85, 0.6)\",\r\n arrowColorHover: \"rgba(30, 64, 175, 0.8)\",\r\n arrowColorActive: \"rgba(30, 64, 175, 1)\",\r\n arrowColorChildActive: \"rgba(30, 64, 175, 1)\",\r\n itemHeight: \"42px\",\r\n itemPadding: \"0 18px\",\r\n itemBorderRadius: \"10px\",\r\n groupTextColor: \"rgba(51, 65, 85, 0.65)\",\r\n};\r\n\r\nconst darkTheme = {\r\n itemTextColor: \"rgba(226, 232, 240, 0.85)\",\r\n itemTextColorHover: \"rgba(255, 255, 255, 0.95)\",\r\n itemTextColorActive: \"rgba(147, 197, 253, 1)\",\r\n itemTextColorActiveHover: \"rgba(147, 197, 253, 1)\",\r\n itemTextColorChildActive: \"rgba(147, 197, 253, 1)\",\r\n itemIconColor: \"rgba(226, 232, 240, 0.7)\",\r\n itemIconColorHover: \"rgba(255, 255, 255, 0.85)\",\r\n itemIconColorActive: \"rgba(147, 197, 253, 1)\",\r\n itemIconColorActiveHover: \"rgba(147, 197, 253, 1)\",\r\n itemIconColorChildActive: \"rgba(147, 197, 253, 1)\",\r\n itemColorHover: \"rgba(59, 130, 246, 0.1)\",\r\n itemColorActive: \"rgba(59, 130, 246, 0.15)\",\r\n itemColorActiveHover: \"rgba(59, 130, 246, 0.18)\",\r\n itemColorActiveCollapsed: \"rgba(59, 130, 246, 0.15)\",\r\n arrowColor: \"rgba(226, 232, 240, 0.5)\",\r\n arrowColorHover: \"rgba(255, 255, 255, 0.7)\",\r\n arrowColorActive: \"rgba(147, 197, 253, 1)\",\r\n arrowColorChildActive: \"rgba(147, 197, 253, 1)\",\r\n itemHeight: \"42px\",\r\n itemPadding: \"0 18px\",\r\n itemBorderRadius: \"10px\",\r\n groupTextColor: \"rgba(226, 232, 240, 0.6)\",\r\n};\r\n\r\nconst menuThemeOverrides = computed(() =>\r\n isDarkMode.value ? darkTheme : lightTheme,\r\n);\r\n\r\n// ============ 图标渲染 ============\r\n\r\nconst renderIcon = (icon: string, size = 18) => {\r\n if (ctx.iconComponent) {\r\n return () => h(ctx.iconComponent!, { name: icon, size });\r\n }\r\n return () =>\r\n h(\"i\", {\r\n class: icon,\r\n style: {\r\n fontSize: `${size}px`,\r\n display: \"inline-flex\",\r\n alignItems: \"center\",\r\n },\r\n });\r\n};\r\n\r\n// ============ 菜单转换 ============\r\n\r\nconst convertToMenuOption = (item: MenuOptions): MenuOption => ({\r\n key: item.path || \"\",\r\n label: item.meta?.title || item.name || \"\",\r\n icon: item.meta?.icon ? renderIcon(item.meta.icon, 18) : undefined,\r\n children: item.children?.length\r\n ? item.children.map(convertToMenuOption)\r\n : undefined,\r\n});\r\n\r\n// ============ 响应式计算 ============\r\n\r\nconst containerRef = ref<HTMLElement>();\r\nconst visibleItems = ref<MenuOptions[]>([]);\r\nconst hiddenItems = ref<MenuOptions[]>([]);\r\n\r\nconst visibleOptions = computed<MenuOption[]>(() =>\r\n visibleItems.value.map(convertToMenuOption),\r\n);\r\n\r\nconst dropdownOptions = computed<DropdownOption[]>(() =>\r\n hiddenItems.value.map((item) => ({\r\n key: item.path || \"\",\r\n label: item.meta?.title || item.name || \"\",\r\n icon: item.meta?.icon ? renderIcon(item.meta.icon, 16) : undefined,\r\n children: item.children?.length\r\n ? item.children.map((child) => ({\r\n key: child.path || \"\",\r\n label: child.meta?.title || child.name || \"\",\r\n icon: child.meta?.icon ? renderIcon(child.meta.icon, 16) : undefined,\r\n }))\r\n : undefined,\r\n })),\r\n);\r\n\r\nconst handleSelect = (key: string) => {\r\n if (key) router.push(key);\r\n};\r\n\r\n// ============ 宽度估算 ============\r\n\r\nconst estimateItemWidth = (item: MenuOptions): number => {\r\n const ITEM_PADDING = 36; // itemPadding: \"0 18px\" → 18*2\r\n const ITEM_MARGIN = 8;\r\n const ICON_WIDTH = 26;\r\n const EXTRA = 8;\r\n\r\n const title = item.meta?.title || item.name || \"\";\r\n let textWidth = 0;\r\n for (const char of title) {\r\n const code = char.charCodeAt(0);\r\n // CJK / 全角字符约 15px(14px 字号下的实际宽度)\r\n if (\r\n (code >= 0x4e00 && code <= 0x9fff) || // CJK 统一汉字\r\n (code >= 0x3400 && code <= 0x4dbf) || // CJK 扩展 A\r\n (code >= 0x3000 && code <= 0x303f) || // CJK 符号\r\n (code >= 0xff00 && code <= 0xffef) || // 全角字符\r\n (code >= 0x3040 && code <= 0x309f) || // 平假名\r\n (code >= 0x30a0 && code <= 0x30ff) // 片假名\r\n ) {\r\n textWidth += 15;\r\n } else {\r\n textWidth += 8;\r\n }\r\n }\r\n const iconWidth = item.meta?.icon ? ICON_WIDTH : 0;\r\n\r\n return textWidth + iconWidth + ITEM_PADDING + ITEM_MARGIN + EXTRA;\r\n};\r\n\r\nconst calculateVisibleCount = (containerWidth: number): number => {\r\n if (!menuData.value.length) return 0;\r\n\r\n const MORE_BTN_WIDTH = 80;\r\n const SAFETY = 15;\r\n\r\n let totalWidth = 0;\r\n for (const item of menuData.value) {\r\n totalWidth += estimateItemWidth(item);\r\n }\r\n\r\n if (totalWidth + SAFETY <= containerWidth) {\r\n return menuData.value.length;\r\n }\r\n\r\n let usedWidth = 0;\r\n let count = 0;\r\n const available = containerWidth - MORE_BTN_WIDTH - SAFETY;\r\n\r\n for (const item of menuData.value) {\r\n const w = estimateItemWidth(item);\r\n if (usedWidth + w <= available) {\r\n usedWidth += w;\r\n count++;\r\n } else break;\r\n }\r\n\r\n return Math.max(count, 1);\r\n};\r\n\r\nconst calculateVisibleItems = () => {\r\n if (!containerRef.value || !menuData.value.length) return;\r\n\r\n const containerWidth = containerRef.value.offsetWidth;\r\n if (containerWidth === 0) {\r\n nextTick(() => calculateVisibleItems());\r\n return;\r\n }\r\n\r\n const count = calculateVisibleCount(containerWidth);\r\n visibleItems.value = menuData.value.slice(0, count);\r\n hiddenItems.value = menuData.value.slice(count);\r\n};\r\n\r\n// ============ 防抖 & ResizeObserver ============\r\n\r\nlet resizeTimer: number | null = null;\r\nconst debouncedCalculate = () => {\r\n if (resizeTimer) clearTimeout(resizeTimer);\r\n resizeTimer = window.setTimeout(() => calculateVisibleItems(), 100);\r\n};\r\n\r\nlet resizeObserver: ResizeObserver | null = null;\r\n\r\nonMounted(() => {\r\n nextTick(() => calculateVisibleItems());\r\n if (containerRef.value) {\r\n resizeObserver = new ResizeObserver(() => debouncedCalculate());\r\n resizeObserver.observe(containerRef.value);\r\n }\r\n});\r\n\r\nonUnmounted(() => {\r\n resizeObserver?.disconnect();\r\n if (resizeTimer) clearTimeout(resizeTimer);\r\n});\r\n\r\nwatch(\r\n () => props.data,\r\n () => nextTick(() => calculateVisibleItems()),\r\n {\r\n deep: true,\r\n },\r\n);\r\n</script>\r\n\r\n<style scoped>\r\n.responsive-menu {\r\n display: flex;\r\n align-items: center;\r\n width: 100%;\r\n gap: 0;\r\n}\r\n\r\n.responsive-menu__visible {\r\n flex: 0 1 auto;\r\n min-width: 0;\r\n overflow: hidden;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu) {\r\n background-color: transparent !important;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item) {\r\n position: relative;\r\n font-weight: 500;\r\n letter-spacing: 0.01em;\r\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n/* 防止 NMenu 内部截断菜单项文字 */\r\n.responsive-menu__visible :deep(.n-menu-item-content-header) {\r\n overflow: visible !important;\r\n text-overflow: clip !important;\r\n white-space: nowrap !important;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item-content) {\r\n overflow: visible !important;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item::after) {\r\n content: \"\";\r\n position: absolute;\r\n bottom: 0;\r\n left: 50%;\r\n transform: translateX(-50%) scaleX(0);\r\n width: 60%;\r\n height: 2px;\r\n background: linear-gradient(90deg, transparent, currentColor, transparent);\r\n opacity: 0;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item.n-menu-item--selected::after) {\r\n transform: translateX(-50%) scaleX(1);\r\n opacity: 0.5;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item:hover) {\r\n transform: translateY(-1px);\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item.n-menu-item--selected) {\r\n box-shadow:\r\n 0 2px 8px -2px rgba(30, 64, 175, 0.15),\r\n 0 1px 3px rgba(30, 64, 175, 0.1);\r\n}\r\n\r\n.responsive-menu__more {\r\n flex-shrink: 0;\r\n margin-left: 4px;\r\n}\r\n\r\n.responsive-menu__more-btn {\r\n min-width: 42px;\r\n height: 42px;\r\n padding: 0 18px !important;\r\n border-radius: 10px !important;\r\n background-color: transparent !important;\r\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.responsive-menu__more-btn::before,\r\n.responsive-menu__more-btn :deep(.n-button__border),\r\n.responsive-menu__more-btn :deep(.n-button__state-border) {\r\n display: none !important;\r\n}\r\n\r\n.responsive-menu__more-btn:hover {\r\n background-color: rgba(30, 64, 175, 0.06) !important;\r\n transform: translateY(-1px);\r\n}\r\n\r\n/* 纯 CSS 三个点图标 */\r\n.dots-icon {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 3px;\r\n width: 20px;\r\n height: 20px;\r\n}\r\n\r\n.dots-icon span {\r\n width: 4px;\r\n height: 4px;\r\n border-radius: 50%;\r\n background-color: currentColor;\r\n opacity: 0.85;\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - ResponsiveMenu\r\n *\r\n * 响应式水平菜单\r\n * 用于 TopLayout / MixTopLayout / ReverseHorizontalMixLayout 的水平菜单\r\n * 自动检测容器宽度,溢出项放入“更多”下拉菜单,使用 NMenu (naive-ui) 渲染\r\n -->\r\n<template>\r\n <div ref=\"containerRef\" class=\"responsive-menu\">\r\n <div ref=\"visibleRef\" class=\"responsive-menu__visible\">\r\n <NMenu\r\n v-if=\"visibleOptions.length > 0\"\r\n mode=\"horizontal\"\r\n :value=\"activeKey\"\r\n :options=\"visibleOptions\"\r\n :theme-overrides=\"menuThemeOverrides\"\r\n @update:value=\"handleSelect\"\r\n />\r\n </div>\r\n\r\n <NDropdown\r\n v-if=\"hiddenItems.length > 0\"\r\n :options=\"dropdownOptions\"\r\n placement=\"bottom-end\"\r\n trigger=\"hover\"\r\n @select=\"handleSelect\"\r\n >\r\n <div class=\"responsive-menu__more\">\r\n <NButton text class=\"responsive-menu__more-btn\">\r\n <!-- 纯 CSS 三个点图标 -->\r\n <span class=\"dots-icon\">\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n </span>\r\n </NButton>\r\n </div>\r\n </NDropdown>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch, onMounted, onUnmounted, nextTick, h } from \"vue\";\r\nimport { useRoute, useRouter } from \"vue-router\";\r\nimport { NMenu, NDropdown, NButton } from \"naive-ui\";\r\nimport type { MenuOption, DropdownOption } from \"naive-ui\";\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"ResponsiveMenu\" });\r\n\r\nconst props = defineProps<{\r\n /** 菜单数据 */\r\n data: MenuOptions[];\r\n}>();\r\n\r\nconst route = useRoute();\r\nconst router = useRouter();\r\nconst ctx = useLayoutContext();\r\n\r\n// 安全包装\r\nconst menuData = computed(() => props.data || []);\r\nconst activeKey = computed(() => route.path);\r\n\r\n// 根据主题选择菜单样式\r\nconst isDarkMode = ctx.isDark;\r\n\r\nconst lightTheme = {\r\n itemTextColor: \"rgba(51, 65, 85, 0.85)\",\r\n itemTextColorHover: \"rgba(30, 64, 175, 0.95)\",\r\n itemTextColorActive: \"rgba(30, 64, 175, 1)\",\r\n itemTextColorActiveHover: \"rgba(30, 64, 175, 1)\",\r\n itemTextColorChildActive: \"rgba(30, 64, 175, 1)\",\r\n itemIconColor: \"rgba(51, 65, 85, 0.7)\",\r\n itemIconColorHover: \"rgba(30, 64, 175, 0.85)\",\r\n itemIconColorActive: \"rgba(30, 64, 175, 1)\",\r\n itemIconColorActiveHover: \"rgba(30, 64, 175, 1)\",\r\n itemIconColorChildActive: \"rgba(30, 64, 175, 1)\",\r\n itemColorHover: \"rgba(30, 64, 175, 0.06)\",\r\n itemColorActive: \"rgba(30, 64, 175, 0.1)\",\r\n itemColorActiveHover: \"rgba(30, 64, 175, 0.12)\",\r\n itemColorActiveCollapsed: \"rgba(30, 64, 175, 0.1)\",\r\n arrowColor: \"rgba(51, 65, 85, 0.6)\",\r\n arrowColorHover: \"rgba(30, 64, 175, 0.8)\",\r\n arrowColorActive: \"rgba(30, 64, 175, 1)\",\r\n arrowColorChildActive: \"rgba(30, 64, 175, 1)\",\r\n itemHeight: \"42px\",\r\n itemPadding: \"0 18px\",\r\n itemBorderRadius: \"10px\",\r\n groupTextColor: \"rgba(51, 65, 85, 0.65)\",\r\n};\r\n\r\nconst darkTheme = {\r\n itemTextColor: \"rgba(226, 232, 240, 0.85)\",\r\n itemTextColorHover: \"rgba(255, 255, 255, 0.95)\",\r\n itemTextColorActive: \"rgba(147, 197, 253, 1)\",\r\n itemTextColorActiveHover: \"rgba(147, 197, 253, 1)\",\r\n itemTextColorChildActive: \"rgba(147, 197, 253, 1)\",\r\n itemIconColor: \"rgba(226, 232, 240, 0.7)\",\r\n itemIconColorHover: \"rgba(255, 255, 255, 0.85)\",\r\n itemIconColorActive: \"rgba(147, 197, 253, 1)\",\r\n itemIconColorActiveHover: \"rgba(147, 197, 253, 1)\",\r\n itemIconColorChildActive: \"rgba(147, 197, 253, 1)\",\r\n itemColorHover: \"rgba(59, 130, 246, 0.1)\",\r\n itemColorActive: \"rgba(59, 130, 246, 0.15)\",\r\n itemColorActiveHover: \"rgba(59, 130, 246, 0.18)\",\r\n itemColorActiveCollapsed: \"rgba(59, 130, 246, 0.15)\",\r\n arrowColor: \"rgba(226, 232, 240, 0.5)\",\r\n arrowColorHover: \"rgba(255, 255, 255, 0.7)\",\r\n arrowColorActive: \"rgba(147, 197, 253, 1)\",\r\n arrowColorChildActive: \"rgba(147, 197, 253, 1)\",\r\n itemHeight: \"42px\",\r\n itemPadding: \"0 18px\",\r\n itemBorderRadius: \"10px\",\r\n groupTextColor: \"rgba(226, 232, 240, 0.6)\",\r\n};\r\n\r\nconst menuThemeOverrides = computed(() =>\r\n isDarkMode.value ? darkTheme : lightTheme,\r\n);\r\n\r\n// ============ 图标渲染 ============\r\n\r\nconst renderIcon = (icon: string, size = 18) => {\r\n if (ctx.iconComponent) {\r\n return () => h(ctx.iconComponent!, { name: icon, size });\r\n }\r\n return () =>\r\n h(\"i\", {\r\n class: icon,\r\n style: {\r\n fontSize: `${size}px`,\r\n display: \"inline-flex\",\r\n alignItems: \"center\",\r\n },\r\n });\r\n};\r\n\r\n// ============ 菜单转换 ============\r\n\r\nconst convertToMenuOption = (item: MenuOptions): MenuOption => ({\r\n key: item.path || \"\",\r\n label: item.meta?.title || item.name || \"\",\r\n icon: item.meta?.icon ? renderIcon(item.meta.icon, 18) : undefined,\r\n children: item.children?.length\r\n ? item.children.map(convertToMenuOption)\r\n : undefined,\r\n});\r\n\r\n// ============ 响应式计算 ============\r\n\r\nconst containerRef = ref<HTMLElement>();\r\nconst visibleItems = ref<MenuOptions[]>([]);\r\nconst hiddenItems = ref<MenuOptions[]>([]);\r\n\r\nconst visibleOptions = computed<MenuOption[]>(() =>\r\n visibleItems.value.map(convertToMenuOption),\r\n);\r\n\r\nconst dropdownOptions = computed<DropdownOption[]>(() =>\r\n hiddenItems.value.map((item) => ({\r\n key: item.path || \"\",\r\n label: item.meta?.title || item.name || \"\",\r\n icon: item.meta?.icon ? renderIcon(item.meta.icon, 16) : undefined,\r\n children: item.children?.length\r\n ? item.children.map((child) => ({\r\n key: child.path || \"\",\r\n label: child.meta?.title || child.name || \"\",\r\n icon: child.meta?.icon ? renderIcon(child.meta.icon, 16) : undefined,\r\n }))\r\n : undefined,\r\n })),\r\n);\r\n\r\nconst handleSelect = (key: string) => {\r\n if (key) router.push(key);\r\n};\r\n\r\n// ============ 宽度估算 ============\r\n\r\nconst estimateItemWidth = (item: MenuOptions): number => {\r\n const ITEM_PADDING = 36; // itemPadding: \"0 18px\" → 18*2\r\n const ITEM_MARGIN = 8;\r\n const ICON_WIDTH = 26;\r\n const EXTRA = 8;\r\n\r\n const title = item.meta?.title || item.name || \"\";\r\n let textWidth = 0;\r\n for (const char of title) {\r\n const code = char.charCodeAt(0);\r\n // CJK / 全角字符约 15px(14px 字号下的实际宽度)\r\n if (\r\n (code >= 0x4e00 && code <= 0x9fff) || // CJK 统一汉字\r\n (code >= 0x3400 && code <= 0x4dbf) || // CJK 扩展 A\r\n (code >= 0x3000 && code <= 0x303f) || // CJK 符号\r\n (code >= 0xff00 && code <= 0xffef) || // 全角字符\r\n (code >= 0x3040 && code <= 0x309f) || // 平假名\r\n (code >= 0x30a0 && code <= 0x30ff) // 片假名\r\n ) {\r\n textWidth += 15;\r\n } else {\r\n textWidth += 8;\r\n }\r\n }\r\n const iconWidth = item.meta?.icon ? ICON_WIDTH : 0;\r\n\r\n return textWidth + iconWidth + ITEM_PADDING + ITEM_MARGIN + EXTRA;\r\n};\r\n\r\nconst calculateVisibleCount = (containerWidth: number): number => {\r\n if (!menuData.value.length) return 0;\r\n\r\n const MORE_BTN_WIDTH = 80;\r\n const SAFETY = 15;\r\n\r\n let totalWidth = 0;\r\n for (const item of menuData.value) {\r\n totalWidth += estimateItemWidth(item);\r\n }\r\n\r\n if (totalWidth + SAFETY <= containerWidth) {\r\n return menuData.value.length;\r\n }\r\n\r\n let usedWidth = 0;\r\n let count = 0;\r\n const available = containerWidth - MORE_BTN_WIDTH - SAFETY;\r\n\r\n for (const item of menuData.value) {\r\n const w = estimateItemWidth(item);\r\n if (usedWidth + w <= available) {\r\n usedWidth += w;\r\n count++;\r\n } else break;\r\n }\r\n\r\n return Math.max(count, 1);\r\n};\r\n\r\nconst calculateVisibleItems = () => {\r\n if (!containerRef.value || !menuData.value.length) return;\r\n\r\n const containerWidth = containerRef.value.offsetWidth;\r\n if (containerWidth === 0) {\r\n nextTick(() => calculateVisibleItems());\r\n return;\r\n }\r\n\r\n const count = calculateVisibleCount(containerWidth);\r\n visibleItems.value = menuData.value.slice(0, count);\r\n hiddenItems.value = menuData.value.slice(count);\r\n};\r\n\r\n// ============ 防抖 & ResizeObserver ============\r\n\r\nlet resizeTimer: number | null = null;\r\nconst debouncedCalculate = () => {\r\n if (resizeTimer) clearTimeout(resizeTimer);\r\n resizeTimer = window.setTimeout(() => calculateVisibleItems(), 100);\r\n};\r\n\r\nlet resizeObserver: ResizeObserver | null = null;\r\n\r\nonMounted(() => {\r\n nextTick(() => calculateVisibleItems());\r\n if (containerRef.value) {\r\n resizeObserver = new ResizeObserver(() => debouncedCalculate());\r\n resizeObserver.observe(containerRef.value);\r\n }\r\n});\r\n\r\nonUnmounted(() => {\r\n resizeObserver?.disconnect();\r\n if (resizeTimer) clearTimeout(resizeTimer);\r\n});\r\n\r\nwatch(\r\n () => props.data,\r\n () => nextTick(() => calculateVisibleItems()),\r\n {\r\n deep: true,\r\n },\r\n);\r\n</script>\r\n\r\n<style scoped>\r\n.responsive-menu {\r\n display: flex;\r\n align-items: center;\r\n width: 100%;\r\n gap: 0;\r\n}\r\n\r\n.responsive-menu__visible {\r\n flex: 0 1 auto;\r\n min-width: 0;\r\n overflow: hidden;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu) {\r\n background-color: transparent !important;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item) {\r\n position: relative;\r\n font-weight: 500;\r\n letter-spacing: 0.01em;\r\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n/* 防止 NMenu 内部截断菜单项文字 */\r\n.responsive-menu__visible :deep(.n-menu-item-content-header) {\r\n overflow: visible !important;\r\n text-overflow: clip !important;\r\n white-space: nowrap !important;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item-content) {\r\n overflow: visible !important;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item::after) {\r\n content: \"\";\r\n position: absolute;\r\n bottom: 0;\r\n left: 50%;\r\n transform: translateX(-50%) scaleX(0);\r\n width: 60%;\r\n height: 2px;\r\n background: linear-gradient(90deg, transparent, currentColor, transparent);\r\n opacity: 0;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item.n-menu-item--selected::after) {\r\n transform: translateX(-50%) scaleX(1);\r\n opacity: 0.5;\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item:hover) {\r\n transform: translateY(-1px);\r\n}\r\n\r\n.responsive-menu__visible :deep(.n-menu-item.n-menu-item--selected) {\r\n box-shadow:\r\n 0 2px 8px -2px rgba(30, 64, 175, 0.15),\r\n 0 1px 3px rgba(30, 64, 175, 0.1);\r\n}\r\n\r\n.responsive-menu__more {\r\n flex-shrink: 0;\r\n margin-left: 4px;\r\n}\r\n\r\n.responsive-menu__more-btn {\r\n min-width: 42px;\r\n height: 42px;\r\n padding: 0 18px !important;\r\n border-radius: 10px !important;\r\n background-color: transparent !important;\r\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.responsive-menu__more-btn::before,\r\n.responsive-menu__more-btn :deep(.n-button__border),\r\n.responsive-menu__more-btn :deep(.n-button__state-border) {\r\n display: none !important;\r\n}\r\n\r\n.responsive-menu__more-btn:hover {\r\n background-color: rgba(30, 64, 175, 0.06) !important;\r\n transform: translateY(-1px);\r\n}\r\n\r\n/* 纯 CSS 三个点图标 */\r\n.dots-icon {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 3px;\r\n width: 20px;\r\n height: 20px;\r\n}\r\n\r\n.dots-icon span {\r\n width: 4px;\r\n height: 4px;\r\n border-radius: 50%;\r\n background-color: currentColor;\r\n opacity: 0.85;\r\n}\r\n</style>\r\n","<!--\n * @robot-admin/layout - TopLayout\n *\n * 顶部导航布局骨架\n * Logo+品牌 | 水平菜单 | 操作区 → 标签页 → 内容区 → 页脚\n *\n * Slots:\n * #logo - Logo/品牌区域\n * #menu - 水平菜单(默认使用 ResponsiveMenu)\n * #header-extra - 头部右侧操作区\n * #tags-view - 标签页区域\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"top-layout-container\">\n <!-- 顶部导航栏 -->\n <div\n class=\"top-navbar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- 左侧:Logo 和品牌 -->\n <div class=\"navbar-left\">\n <slot name=\"logo\">\n <div class=\"logo-container\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n />\n </div>\n <div class=\"brand-name\">\n <span class=\"brand-title\">{{ brand.name }}</span>\n <span class=\"brand-subtitle\">{{ brand.subtitle }}</span>\n </div>\n <div class=\"navbar-divider\"></div>\n </slot>\n </div>\n\n <!-- 中间:水平菜单 -->\n <div class=\"navbar-center\">\n <slot name=\"menu\">\n <ResponsiveMenu :data=\"menus\" />\n </slot>\n </div>\n\n <!-- 右侧:操作区 -->\n <slot name=\"header-extra\" />\n </div>\n\n <!-- 标签页区域 -->\n <div\n v-if=\"showTagsView\"\n class=\"tags-view-container\"\n :style=\"{ height: `${tagsViewHeight}px` }\"\n >\n <slot name=\"tags-view\" />\n </div>\n\n <!-- 主内容区域 -->\n <NLayout>\n <NLayoutContent class=\"main-content\">\n <div class=\"page-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </div>\n </NLayoutContent>\n\n <!-- 页脚 -->\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { computed } from \"vue\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport ResponsiveMenu from \"../../ResponsiveMenu/index.vue\";\n\ndefineOptions({ name: \"TopLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showTagsView = computed(() => ctx.showTagsView.value);\nconst tagsViewHeight = computed(() => ctx.tagsViewHeight.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n</script>\n","<!--\n * @robot-admin/layout - TopLayout\n *\n * 顶部导航布局骨架\n * Logo+品牌 | 水平菜单 | 操作区 → 标签页 → 内容区 → 页脚\n *\n * Slots:\n * #logo - Logo/品牌区域\n * #menu - 水平菜单(默认使用 ResponsiveMenu)\n * #header-extra - 头部右侧操作区\n * #tags-view - 标签页区域\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"top-layout-container\">\n <!-- 顶部导航栏 -->\n <div\n class=\"top-navbar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- 左侧:Logo 和品牌 -->\n <div class=\"navbar-left\">\n <slot name=\"logo\">\n <div class=\"logo-container\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n />\n </div>\n <div class=\"brand-name\">\n <span class=\"brand-title\">{{ brand.name }}</span>\n <span class=\"brand-subtitle\">{{ brand.subtitle }}</span>\n </div>\n <div class=\"navbar-divider\"></div>\n </slot>\n </div>\n\n <!-- 中间:水平菜单 -->\n <div class=\"navbar-center\">\n <slot name=\"menu\">\n <ResponsiveMenu :data=\"menus\" />\n </slot>\n </div>\n\n <!-- 右侧:操作区 -->\n <slot name=\"header-extra\" />\n </div>\n\n <!-- 标签页区域 -->\n <div\n v-if=\"showTagsView\"\n class=\"tags-view-container\"\n :style=\"{ height: `${tagsViewHeight}px` }\"\n >\n <slot name=\"tags-view\" />\n </div>\n\n <!-- 主内容区域 -->\n <NLayout>\n <NLayoutContent class=\"main-content\">\n <div class=\"page-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </div>\n </NLayoutContent>\n\n <!-- 页脚 -->\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { computed } from \"vue\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport ResponsiveMenu from \"../../ResponsiveMenu/index.vue\";\n\ndefineOptions({ name: \"TopLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showTagsView = computed(() => ctx.showTagsView.value);\nconst tagsViewHeight = computed(() => ctx.tagsViewHeight.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n</script>\n","/**\n * @robot-admin/layout\n *\n * 菜单拆分 Composable - 通用的一级/二级菜单拆分逻辑\n * 用于 Mix / MixTop / ReverseHorizontalMix 等需要菜单拆分的布局\n */\n\nimport {\n ref,\n computed,\n watch,\n onMounted,\n toValue,\n type ComputedRef,\n type Ref,\n type MaybeRefOrGetter,\n} from \"vue\";\nimport { useRoute, useRouter } from \"vue-router\";\nimport type { MenuOptions } from \"../types/menu\";\n\nexport interface UseMenuSplitOptions {\n /** 菜单数据(响应式) */\n menus: ComputedRef<MenuOptions[]>;\n /** 是否使用悬浮式二级菜单(MixLayout 的 toggle 模式) */\n floatingSecondMenu?: MaybeRefOrGetter<boolean>;\n}\n\nexport interface UseMenuSplitReturn {\n /** 当前激活的一级菜单路径 */\n activeFirstMenu: Ref<string>;\n /** 当前激活的一级菜单完整数据 */\n activeFirstMenuItem: ComputedRef<MenuOptions | undefined>;\n /** 当前一级菜单对应的二级菜单列表 */\n currentSecondMenus: ComputedRef<MenuOptions[]>;\n /** 悬浮的菜单项(MixLayout 使用) */\n hoveredMenuItem: Ref<MenuOptions | null>;\n /** 实际展示的菜单项(hovered 优先,fallback 到 active) */\n displayMenuItem: ComputedRef<MenuOptions | null>;\n /** 是否显示悬浮二级菜单(MixLayout 使用) */\n showSecondMenu: Ref<boolean>;\n /** 处理一级菜单点击 */\n handleFirstMenuClick: (item: MenuOptions) => void;\n /** 处理二级菜单点击 */\n handleSecondMenuClick: (item: MenuOptions) => void;\n /** 检查路由是否匹配菜单项 */\n isMenuItemActive: (menuPath: string | undefined) => boolean;\n}\n\n/** 判断当前路径是否等于菜单路径或位于其子路径下。 */\nexport function isPathSegmentPrefix(\n menuPath: string | undefined,\n currentPath: string,\n): boolean {\n if (!menuPath) return false;\n const menuSegments = menuPath.split(\"/\").filter(Boolean);\n const currentSegments = currentPath.split(\"/\").filter(Boolean);\n if (menuSegments.length === 0) return currentSegments.length === 0;\n return menuSegments.every(\n (segment, index) => currentSegments[index] === segment,\n );\n}\n\n/**\n * 菜单拆分 Composable\n *\n * 提供一级/二级菜单拆分状态管理、路由自动匹配、菜单点击处理。\n * 供 MixLayout / MixTopLayout / ReverseHorizontalMixLayout 共用。\n *\n * @example\n * ```ts\n * const menuSplit = useMenuSplit({\n * menus: ctx.menus,\n * floatingSecondMenu: computed(() => layoutMode.value === 'mix'),\n * })\n * ```\n */\nexport function useMenuSplit(options: UseMenuSplitOptions): UseMenuSplitReturn {\n const { menus, floatingSecondMenu = false } = options;\n const route = useRoute();\n const router = useRouter();\n\n const activeFirstMenu = ref<string>(\"\");\n const hoveredMenuItem = ref<MenuOptions | null>(null);\n const showSecondMenu = ref(false);\n\n /** 当前展示在悬浮面板上的菜单项(优先 hovered,fallback 到 active) */\n const displayMenuItem = computed<MenuOptions | null>(\n () => hoveredMenuItem.value || activeFirstMenuItem.value || null,\n );\n\n // ============ 路径匹配 ============\n\n const isMenuItemActive = (menuPath: string | undefined): boolean => {\n return isPathSegmentPrefix(menuPath, route.path);\n };\n\n // ============ 查找一级菜单 ============\n\n const findActiveTopMenu = (items: MenuOptions[]): MenuOptions | null => {\n for (const item of items) {\n if (isMenuItemActive(item.path)) return item;\n if (item.children?.length) {\n const found = findActiveTopMenu(item.children);\n if (found) return item;\n }\n }\n return null;\n };\n\n // ============ 计算属性 ============\n\n const activeFirstMenuItem = computed(() =>\n menus.value.find((item) => item.path === activeFirstMenu.value),\n );\n\n const currentSecondMenus = computed(\n () => activeFirstMenuItem.value?.children || [],\n );\n\n // ============ 交互处理 ============\n\n const handleFirstMenuClick = (item: MenuOptions) => {\n if (toValue(floatingSecondMenu)) {\n // MixLayout: 纯点击模式\n if (item.children && item.children.length > 0) {\n if (showSecondMenu.value && activeFirstMenu.value === item.path) {\n // 点击同一个已展开的菜单 → 关闭\n showSecondMenu.value = false;\n } else {\n // 点击另一个或未展开 → 展开/切换\n activeFirstMenu.value = item.path || \"\";\n showSecondMenu.value = true;\n }\n } else {\n // 没有子菜单 → 关闭二级面板并跳转\n activeFirstMenu.value = item.path || \"\";\n showSecondMenu.value = false;\n if (item.path) router.push(item.path);\n }\n } else {\n // MixTop / Reverse: 直接切换\n if (!item.children || item.children.length === 0) {\n if (item.path) router.push(item.path);\n }\n }\n };\n\n const handleSecondMenuClick = (item: MenuOptions) => {\n if (item.path) router.push(item.path);\n };\n\n // ============ 路由自动匹配 ============\n\n const updateActiveMenuByRoute = () => {\n const menuList = menus.value;\n const matchedFirstMenu = findActiveTopMenu(menuList);\n if (matchedFirstMenu) {\n activeFirstMenu.value = matchedFirstMenu.path || \"\";\n // 悬浮模式下,如果当前一级菜单有子菜单,自动展示二级菜单\n if (toValue(floatingSecondMenu) && matchedFirstMenu.children?.length) {\n showSecondMenu.value = true;\n }\n } else if (menuList.length > 0 && !activeFirstMenu.value) {\n activeFirstMenu.value = menuList[0].path || \"\";\n }\n };\n\n watch(\n () => route.path,\n () => updateActiveMenuByRoute(),\n { immediate: true },\n );\n\n watch(\n menus,\n (newMenus) => {\n if (newMenus && newMenus.length > 0) updateActiveMenuByRoute();\n },\n { immediate: true },\n );\n\n onMounted(() => updateActiveMenuByRoute());\n\n return {\n activeFirstMenu,\n activeFirstMenuItem,\n currentSecondMenus,\n hoveredMenuItem,\n /** 实际在悬浮面板上展示的菜单项(hovered 优先,fallback 到 active) */\n displayMenuItem,\n showSecondMenu,\n handleFirstMenuClick,\n handleSecondMenuClick,\n isMenuItemActive,\n };\n}\n","<!--\n * @robot-admin/layout - MixLayout\n *\n * 混合布局骨架\n * 左侧一级菜单图标栏 + 悬浮二级菜单弹出面板 + 右侧内容区\n *\n * Slots:\n * #logo - Logo/品牌区域(侧边栏顶部)\n * #header - 头部区域\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"mix-layout-container\">\n <!-- 左侧一级菜单 -->\n <div\n class=\"first-level-menu\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- Logo -->\n <div class=\"logo-container\">\n <slot name=\"logo\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n class=\"logo-video\"\n />\n </slot>\n </div>\n\n <!-- 一级菜单列表 -->\n <div class=\"first-menu-list\">\n <div\n v-for=\"item in menus\"\n :key=\"item.path\"\n class=\"first-menu-item\"\n :class=\"{\n active: menuSplit.activeFirstMenu.value === item.path,\n }\"\n @click=\"menuSplit.handleFirstMenuClick(item)\"\n >\n <div class=\"menu-item-content\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"item.meta?.icon\"\n :name=\"item.meta.icon\"\n :size=\"22\"\n />\n <span v-else class=\"menu-item-text\">{{\n (item.meta?.title || \"\")[0]\n }}</span>\n </div>\n <span class=\"menu-item-label\">{{ item.meta?.title }}</span>\n </div>\n </div>\n </div>\n\n <!-- 悬浮二级菜单 -->\n <Transition name=\"slide-fade\">\n <div\n v-if=\"menuSplit.showSecondMenu.value\"\n class=\"second-level-menu-popup\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <div class=\"second-menu-header\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"menuSplit.displayMenuItem.value?.meta?.icon\"\n :name=\"menuSplit.displayMenuItem.value.meta.icon\"\n :size=\"20\"\n />\n <span class=\"menu-title\">{{\n menuSplit.displayMenuItem.value?.meta?.title\n }}</span>\n </div>\n <div class=\"second-menu-list\">\n <div\n v-for=\"child in menuSplit.displayMenuItem.value?.children || []\"\n :key=\"child.path\"\n class=\"second-menu-item\"\n :class=\"{ active: menuSplit.isMenuItemActive(child.path) }\"\n @click=\"menuSplit.handleSecondMenuClick(child)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"child.meta?.icon\"\n :name=\"child.meta.icon\"\n :size=\"16\"\n />\n <span class=\"menu-item-label\">{{ child.meta?.title }}</span>\n </div>\n </div>\n </div>\n </Transition>\n\n <!-- 右侧主内容区 -->\n <div class=\"main-layout\">\n <slot name=\"header\" />\n\n <NLayout>\n <NLayoutContent class=\"content-with-header p16px app-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </NLayoutContent>\n\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { computed, h, defineComponent } from \"vue\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport { useMenuSplit } from \"../../../composables/useMenuSplit\";\n\ndefineOptions({ name: \"MixLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n\nconst menuSplit = useMenuSplit({\n menus: ctx.menus,\n floatingSecondMenu: computed(() => true),\n});\n\n// 图标组件:优先使用消费方提供的,否则用 CSS class 渲染\nconst LayoutIcon =\n ctx.iconComponent ??\n defineComponent({\n name: \"LayoutIcon\",\n props: { name: String, size: { type: Number, default: 18 } },\n setup(props) {\n return () =>\n h(\"i\", {\n class: props.name,\n style: {\n fontSize: `${props.size}px`,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n });\n },\n });\n</script>\n","<!--\n * @robot-admin/layout - MixLayout\n *\n * 混合布局骨架\n * 左侧一级菜单图标栏 + 悬浮二级菜单弹出面板 + 右侧内容区\n *\n * Slots:\n * #logo - Logo/品牌区域(侧边栏顶部)\n * #header - 头部区域\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"mix-layout-container\">\n <!-- 左侧一级菜单 -->\n <div\n class=\"first-level-menu\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- Logo -->\n <div class=\"logo-container\">\n <slot name=\"logo\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n class=\"logo-video\"\n />\n </slot>\n </div>\n\n <!-- 一级菜单列表 -->\n <div class=\"first-menu-list\">\n <div\n v-for=\"item in menus\"\n :key=\"item.path\"\n class=\"first-menu-item\"\n :class=\"{\n active: menuSplit.activeFirstMenu.value === item.path,\n }\"\n @click=\"menuSplit.handleFirstMenuClick(item)\"\n >\n <div class=\"menu-item-content\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"item.meta?.icon\"\n :name=\"item.meta.icon\"\n :size=\"22\"\n />\n <span v-else class=\"menu-item-text\">{{\n (item.meta?.title || \"\")[0]\n }}</span>\n </div>\n <span class=\"menu-item-label\">{{ item.meta?.title }}</span>\n </div>\n </div>\n </div>\n\n <!-- 悬浮二级菜单 -->\n <Transition name=\"slide-fade\">\n <div\n v-if=\"menuSplit.showSecondMenu.value\"\n class=\"second-level-menu-popup\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <div class=\"second-menu-header\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"menuSplit.displayMenuItem.value?.meta?.icon\"\n :name=\"menuSplit.displayMenuItem.value.meta.icon\"\n :size=\"20\"\n />\n <span class=\"menu-title\">{{\n menuSplit.displayMenuItem.value?.meta?.title\n }}</span>\n </div>\n <div class=\"second-menu-list\">\n <div\n v-for=\"child in menuSplit.displayMenuItem.value?.children || []\"\n :key=\"child.path\"\n class=\"second-menu-item\"\n :class=\"{ active: menuSplit.isMenuItemActive(child.path) }\"\n @click=\"menuSplit.handleSecondMenuClick(child)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"child.meta?.icon\"\n :name=\"child.meta.icon\"\n :size=\"16\"\n />\n <span class=\"menu-item-label\">{{ child.meta?.title }}</span>\n </div>\n </div>\n </div>\n </Transition>\n\n <!-- 右侧主内容区 -->\n <div class=\"main-layout\">\n <slot name=\"header\" />\n\n <NLayout>\n <NLayoutContent class=\"content-with-header p16px app-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </NLayoutContent>\n\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { computed, h, defineComponent } from \"vue\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport { useMenuSplit } from \"../../../composables/useMenuSplit\";\n\ndefineOptions({ name: \"MixLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n\nconst menuSplit = useMenuSplit({\n menus: ctx.menus,\n floatingSecondMenu: computed(() => true),\n});\n\n// 图标组件:优先使用消费方提供的,否则用 CSS class 渲染\nconst LayoutIcon =\n ctx.iconComponent ??\n defineComponent({\n name: \"LayoutIcon\",\n props: { name: String, size: { type: Number, default: 18 } },\n setup(props) {\n return () =>\n h(\"i\", {\n class: props.name,\n style: {\n fontSize: `${props.size}px`,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n });\n },\n });\n</script>\n","<!--\n * @robot-admin/layout - MixTopLayout\n *\n * 顶部混合布局骨架\n * 左侧一级菜单 + 顶部二级水平菜单 + 内容区\n *\n * Slots:\n * #logo - Logo/品牌区域(侧边栏顶部)\n * #brand - 顶部导航品牌区\n * #top-menu - 二级水平菜单(默认使用 ResponsiveMenu)\n * #header-extra - 头部右侧操作区\n * #tags-view - 标签页区域\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"mix-top-layout-container\">\n <!-- 左侧一级菜单 -->\n <div\n class=\"first-level-sidebar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- Logo -->\n <div class=\"logo-container\">\n <slot name=\"logo\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n class=\"logo-video\"\n />\n </slot>\n </div>\n\n <!-- 一级菜单列表 -->\n <div class=\"first-menu-list\">\n <div\n v-for=\"item in menus\"\n :key=\"item.path\"\n class=\"first-menu-item\"\n :class=\"{\n active: menuSplit.activeFirstMenu.value === item.path,\n }\"\n @click=\"menuSplit.handleFirstMenuClick(item)\"\n >\n <div class=\"menu-item-content\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"item.meta?.icon\"\n :name=\"item.meta.icon\"\n :size=\"22\"\n />\n <span v-else class=\"menu-item-text\">{{\n (item.meta?.title || \"\")[0]\n }}</span>\n </div>\n <span class=\"menu-item-label\">{{ item.meta?.title }}</span>\n </div>\n </div>\n </div>\n\n <!-- 右侧主区域 -->\n <div class=\"main-area\">\n <!-- 顶部导航栏 -->\n <div\n class=\"top-navbar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- 左侧:品牌 -->\n <div class=\"navbar-left\">\n <slot name=\"brand\">\n <div class=\"brand-name\">\n <span class=\"brand-title\">{{ brand.name }}</span>\n <span class=\"brand-subtitle\">{{ brand.subtitle }}</span>\n </div>\n <div class=\"navbar-divider\"></div>\n </slot>\n </div>\n\n <!-- 中间:二级水平菜单 -->\n <div class=\"navbar-center\">\n <slot name=\"top-menu\">\n <ResponsiveMenu\n v-if=\"menuSplit.currentSecondMenus.value.length > 0\"\n :data=\"menuSplit.currentSecondMenus.value\"\n />\n </slot>\n </div>\n\n <!-- 右侧:操作区 -->\n <slot name=\"header-extra\" />\n </div>\n\n <!-- 标签页区域 -->\n <div\n v-if=\"showTagsView\"\n class=\"tags-view-container\"\n :style=\"{ height: `${tagsViewHeight}px` }\"\n >\n <slot name=\"tags-view\" />\n </div>\n\n <!-- 内容区 -->\n <NLayout class=\"content-layout\">\n <NLayoutContent class=\"main-content\">\n <div class=\"page-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </div>\n </NLayoutContent>\n\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { computed, h, defineComponent } from \"vue\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport { useMenuSplit } from \"../../../composables/useMenuSplit\";\nimport ResponsiveMenu from \"../../ResponsiveMenu/index.vue\";\n\ndefineOptions({ name: \"MixTopLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showTagsView = computed(() => ctx.showTagsView.value);\nconst tagsViewHeight = computed(() => ctx.tagsViewHeight.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n\nconst menuSplit = useMenuSplit({\n menus: ctx.menus,\n floatingSecondMenu: computed(() => false),\n});\n\nconst LayoutIcon =\n ctx.iconComponent ??\n defineComponent({\n name: \"LayoutIcon\",\n props: { name: String, size: { type: Number, default: 18 } },\n setup(props) {\n return () =>\n h(\"i\", {\n class: props.name,\n style: {\n fontSize: `${props.size}px`,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n });\n },\n });\n</script>\n","<!--\n * @robot-admin/layout - MixTopLayout\n *\n * 顶部混合布局骨架\n * 左侧一级菜单 + 顶部二级水平菜单 + 内容区\n *\n * Slots:\n * #logo - Logo/品牌区域(侧边栏顶部)\n * #brand - 顶部导航品牌区\n * #top-menu - 二级水平菜单(默认使用 ResponsiveMenu)\n * #header-extra - 头部右侧操作区\n * #tags-view - 标签页区域\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"mix-top-layout-container\">\n <!-- 左侧一级菜单 -->\n <div\n class=\"first-level-sidebar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- Logo -->\n <div class=\"logo-container\">\n <slot name=\"logo\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n class=\"logo-video\"\n />\n </slot>\n </div>\n\n <!-- 一级菜单列表 -->\n <div class=\"first-menu-list\">\n <div\n v-for=\"item in menus\"\n :key=\"item.path\"\n class=\"first-menu-item\"\n :class=\"{\n active: menuSplit.activeFirstMenu.value === item.path,\n }\"\n @click=\"menuSplit.handleFirstMenuClick(item)\"\n >\n <div class=\"menu-item-content\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"item.meta?.icon\"\n :name=\"item.meta.icon\"\n :size=\"22\"\n />\n <span v-else class=\"menu-item-text\">{{\n (item.meta?.title || \"\")[0]\n }}</span>\n </div>\n <span class=\"menu-item-label\">{{ item.meta?.title }}</span>\n </div>\n </div>\n </div>\n\n <!-- 右侧主区域 -->\n <div class=\"main-area\">\n <!-- 顶部导航栏 -->\n <div\n class=\"top-navbar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- 左侧:品牌 -->\n <div class=\"navbar-left\">\n <slot name=\"brand\">\n <div class=\"brand-name\">\n <span class=\"brand-title\">{{ brand.name }}</span>\n <span class=\"brand-subtitle\">{{ brand.subtitle }}</span>\n </div>\n <div class=\"navbar-divider\"></div>\n </slot>\n </div>\n\n <!-- 中间:二级水平菜单 -->\n <div class=\"navbar-center\">\n <slot name=\"top-menu\">\n <ResponsiveMenu\n v-if=\"menuSplit.currentSecondMenus.value.length > 0\"\n :data=\"menuSplit.currentSecondMenus.value\"\n />\n </slot>\n </div>\n\n <!-- 右侧:操作区 -->\n <slot name=\"header-extra\" />\n </div>\n\n <!-- 标签页区域 -->\n <div\n v-if=\"showTagsView\"\n class=\"tags-view-container\"\n :style=\"{ height: `${tagsViewHeight}px` }\"\n >\n <slot name=\"tags-view\" />\n </div>\n\n <!-- 内容区 -->\n <NLayout class=\"content-layout\">\n <NLayoutContent class=\"main-content\">\n <div class=\"page-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </div>\n </NLayoutContent>\n\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { computed, h, defineComponent } from \"vue\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport { useMenuSplit } from \"../../../composables/useMenuSplit\";\nimport ResponsiveMenu from \"../../ResponsiveMenu/index.vue\";\n\ndefineOptions({ name: \"MixTopLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showTagsView = computed(() => ctx.showTagsView.value);\nconst tagsViewHeight = computed(() => ctx.tagsViewHeight.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n\nconst menuSplit = useMenuSplit({\n menus: ctx.menus,\n floatingSecondMenu: computed(() => false),\n});\n\nconst LayoutIcon =\n ctx.iconComponent ??\n defineComponent({\n name: \"LayoutIcon\",\n props: { name: String, size: { type: Number, default: 18 } },\n setup(props) {\n return () =>\n h(\"i\", {\n class: props.name,\n style: {\n fontSize: `${props.size}px`,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n });\n },\n });\n</script>\n","<!--\n * @robot-admin/layout - ReverseHorizontalMixLayout\n *\n * 反转混合布局骨架\n * 顶部一级菜单 + 右侧二级菜单侧边栏 + 左侧内容区\n *\n * Slots:\n * #logo - Logo/品牌区域\n * #top-menu - 一级水平菜单(默认使用 ResponsiveMenu)\n * #header-extra - 头部右侧操作区\n * #tags-view - 标签页区域\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"reverse-horizontal-mix-layout-container\">\n <!-- 顶部导航栏 -->\n <div\n class=\"top-navbar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- 左侧:Logo 和品牌 -->\n <div class=\"navbar-left\">\n <slot name=\"logo\">\n <div class=\"logo-container\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n />\n </div>\n <div class=\"brand-name\">\n <span class=\"brand-title\">{{ brand.name }}</span>\n <span class=\"brand-subtitle\">{{ brand.subtitle }}</span>\n </div>\n <div class=\"navbar-divider\"></div>\n </slot>\n </div>\n\n <!-- 中间:一级水平菜单 -->\n <div class=\"navbar-center\">\n <slot name=\"top-menu\">\n <ResponsiveMenu :data=\"menus\" />\n </slot>\n </div>\n\n <!-- 右侧:操作区 -->\n <slot name=\"header-extra\" />\n </div>\n\n <!-- 标签页区域 -->\n <div\n v-if=\"showTagsView\"\n class=\"tags-view-container\"\n :style=\"{ height: `${tagsViewHeight}px` }\"\n >\n <slot name=\"tags-view\" />\n </div>\n\n <!-- 主区域:左侧内容 + 右侧菜单 -->\n <div class=\"main-area\">\n <!-- 左侧:主内容区 -->\n <NLayout class=\"content-layout\">\n <NLayoutContent class=\"main-content\">\n <div class=\"page-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </div>\n </NLayoutContent>\n\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n\n <!-- 右侧折叠/展开按钮 - 始终可见 -->\n <div\n v-if=\"menuSplit.currentSecondMenus.value.length > 0\"\n class=\"sidebar-toggle\"\n :class=\"[\n isDarkMode ? 'dark-theme' : 'light-theme',\n { active: !isCollapsed },\n ]\"\n @click=\"toggleCollapse\"\n >\n <span\n :class=\"[\n 'collapse-arrow',\n 'toggle-icon',\n isCollapsed ? 'collapsed' : 'expanded',\n ]\"\n >\n <span class=\"arrow-line\"></span>\n <span class=\"arrow-line\"></span>\n </span>\n </div>\n\n <!-- 右侧:二级菜单侧边栏 -->\n <div\n v-if=\"menuSplit.currentSecondMenus.value.length > 0\"\n class=\"right-sidebar-wrapper\"\n :class=\"{ collapsed: isCollapsed }\"\n >\n <div\n class=\"right-sidebar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- 侧边栏标题 -->\n <div\n class=\"sidebar-header\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <div class=\"header-icon\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"menuSplit.activeFirstMenuItem.value?.meta?.icon\"\n :name=\"menuSplit.activeFirstMenuItem.value.meta.icon\"\n :size=\"20\"\n />\n </div>\n <span class=\"header-title\">{{\n menuSplit.activeFirstMenuItem.value?.meta?.title || \"菜单\"\n }}</span>\n </div>\n\n <!-- 二级菜单列表 -->\n <div class=\"sidebar-menu-list\">\n <template\n v-for=\"child in menuSplit.currentSecondMenus.value\"\n :key=\"child.path\"\n >\n <!-- 有子菜单的项 -->\n <div\n v-if=\"child.children && child.children.length > 0\"\n class=\"menu-group\"\n >\n <div class=\"group-title\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"child.meta?.icon\"\n :name=\"child.meta.icon\"\n :size=\"16\"\n />\n <span>{{ child.meta?.title }}</span>\n </div>\n <div\n v-for=\"subChild in child.children\"\n :key=\"subChild.path\"\n class=\"menu-item sub-item\"\n :class=\"{\n active: menuSplit.isMenuItemActive(subChild.path),\n }\"\n @click=\"menuSplit.handleSecondMenuClick(subChild)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"subChild.meta?.icon\"\n :name=\"subChild.meta.icon\"\n :size=\"16\"\n />\n <span class=\"item-title\">{{ subChild.meta?.title }}</span>\n </div>\n </div>\n <!-- 没有子菜单的项 -->\n <div\n v-else\n class=\"menu-item\"\n :class=\"{\n active: menuSplit.isMenuItemActive(child.path),\n }\"\n @click=\"menuSplit.handleSecondMenuClick(child)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"child.meta?.icon\"\n :name=\"child.meta.icon\"\n :size=\"18\"\n />\n <span class=\"item-title\">{{ child.meta?.title }}</span>\n </div>\n </template>\n </div>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { ref, computed, h, defineComponent } from \"vue\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport { useMenuSplit } from \"../../../composables/useMenuSplit\";\nimport ResponsiveMenu from \"../../ResponsiveMenu/index.vue\";\n\ndefineOptions({ name: \"ReverseHorizontalMixLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showTagsView = computed(() => ctx.showTagsView.value);\nconst tagsViewHeight = computed(() => ctx.tagsViewHeight.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n\nconst menuSplit = useMenuSplit({\n menus: ctx.menus,\n floatingSecondMenu: computed(() => false),\n});\n\n// 右侧边栏折叠状态\nconst isCollapsed = ref(false);\nconst toggleCollapse = () => {\n isCollapsed.value = !isCollapsed.value;\n};\n\nconst LayoutIcon =\n ctx.iconComponent ??\n defineComponent({\n name: \"LayoutIcon\",\n props: { name: String, size: { type: Number, default: 18 } },\n setup(props) {\n return () =>\n h(\"i\", {\n class: props.name,\n style: {\n fontSize: `${props.size}px`,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n });\n },\n });\n</script>\n","<!--\n * @robot-admin/layout - ReverseHorizontalMixLayout\n *\n * 反转混合布局骨架\n * 顶部一级菜单 + 右侧二级菜单侧边栏 + 左侧内容区\n *\n * Slots:\n * #logo - Logo/品牌区域\n * #top-menu - 一级水平菜单(默认使用 ResponsiveMenu)\n * #header-extra - 头部右侧操作区\n * #tags-view - 标签页区域\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"reverse-horizontal-mix-layout-container\">\n <!-- 顶部导航栏 -->\n <div\n class=\"top-navbar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- 左侧:Logo 和品牌 -->\n <div class=\"navbar-left\">\n <slot name=\"logo\">\n <div class=\"logo-container\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n />\n </div>\n <div class=\"brand-name\">\n <span class=\"brand-title\">{{ brand.name }}</span>\n <span class=\"brand-subtitle\">{{ brand.subtitle }}</span>\n </div>\n <div class=\"navbar-divider\"></div>\n </slot>\n </div>\n\n <!-- 中间:一级水平菜单 -->\n <div class=\"navbar-center\">\n <slot name=\"top-menu\">\n <ResponsiveMenu :data=\"menus\" />\n </slot>\n </div>\n\n <!-- 右侧:操作区 -->\n <slot name=\"header-extra\" />\n </div>\n\n <!-- 标签页区域 -->\n <div\n v-if=\"showTagsView\"\n class=\"tags-view-container\"\n :style=\"{ height: `${tagsViewHeight}px` }\"\n >\n <slot name=\"tags-view\" />\n </div>\n\n <!-- 主区域:左侧内容 + 右侧菜单 -->\n <div class=\"main-area\">\n <!-- 左侧:主内容区 -->\n <NLayout class=\"content-layout\">\n <NLayoutContent class=\"main-content\">\n <div class=\"page-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </div>\n </NLayoutContent>\n\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n\n <!-- 右侧折叠/展开按钮 - 始终可见 -->\n <div\n v-if=\"menuSplit.currentSecondMenus.value.length > 0\"\n class=\"sidebar-toggle\"\n :class=\"[\n isDarkMode ? 'dark-theme' : 'light-theme',\n { active: !isCollapsed },\n ]\"\n @click=\"toggleCollapse\"\n >\n <span\n :class=\"[\n 'collapse-arrow',\n 'toggle-icon',\n isCollapsed ? 'collapsed' : 'expanded',\n ]\"\n >\n <span class=\"arrow-line\"></span>\n <span class=\"arrow-line\"></span>\n </span>\n </div>\n\n <!-- 右侧:二级菜单侧边栏 -->\n <div\n v-if=\"menuSplit.currentSecondMenus.value.length > 0\"\n class=\"right-sidebar-wrapper\"\n :class=\"{ collapsed: isCollapsed }\"\n >\n <div\n class=\"right-sidebar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <!-- 侧边栏标题 -->\n <div\n class=\"sidebar-header\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <div class=\"header-icon\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"menuSplit.activeFirstMenuItem.value?.meta?.icon\"\n :name=\"menuSplit.activeFirstMenuItem.value.meta.icon\"\n :size=\"20\"\n />\n </div>\n <span class=\"header-title\">{{\n menuSplit.activeFirstMenuItem.value?.meta?.title || \"菜单\"\n }}</span>\n </div>\n\n <!-- 二级菜单列表 -->\n <div class=\"sidebar-menu-list\">\n <template\n v-for=\"child in menuSplit.currentSecondMenus.value\"\n :key=\"child.path\"\n >\n <!-- 有子菜单的项 -->\n <div\n v-if=\"child.children && child.children.length > 0\"\n class=\"menu-group\"\n >\n <div class=\"group-title\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"child.meta?.icon\"\n :name=\"child.meta.icon\"\n :size=\"16\"\n />\n <span>{{ child.meta?.title }}</span>\n </div>\n <div\n v-for=\"subChild in child.children\"\n :key=\"subChild.path\"\n class=\"menu-item sub-item\"\n :class=\"{\n active: menuSplit.isMenuItemActive(subChild.path),\n }\"\n @click=\"menuSplit.handleSecondMenuClick(subChild)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"subChild.meta?.icon\"\n :name=\"subChild.meta.icon\"\n :size=\"16\"\n />\n <span class=\"item-title\">{{ subChild.meta?.title }}</span>\n </div>\n </div>\n <!-- 没有子菜单的项 -->\n <div\n v-else\n class=\"menu-item\"\n :class=\"{\n active: menuSplit.isMenuItemActive(child.path),\n }\"\n @click=\"menuSplit.handleSecondMenuClick(child)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"child.meta?.icon\"\n :name=\"child.meta.icon\"\n :size=\"18\"\n />\n <span class=\"item-title\">{{ child.meta?.title }}</span>\n </div>\n </template>\n </div>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { ref, computed, h, defineComponent } from \"vue\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport { useMenuSplit } from \"../../../composables/useMenuSplit\";\nimport ResponsiveMenu from \"../../ResponsiveMenu/index.vue\";\n\ndefineOptions({ name: \"ReverseHorizontalMixLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showTagsView = computed(() => ctx.showTagsView.value);\nconst tagsViewHeight = computed(() => ctx.tagsViewHeight.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n\nconst menuSplit = useMenuSplit({\n menus: ctx.menus,\n floatingSecondMenu: computed(() => false),\n});\n\n// 右侧边栏折叠状态\nconst isCollapsed = ref(false);\nconst toggleCollapse = () => {\n isCollapsed.value = !isCollapsed.value;\n};\n\nconst LayoutIcon =\n ctx.iconComponent ??\n defineComponent({\n name: \"LayoutIcon\",\n props: { name: String, size: { type: Number, default: 18 } },\n setup(props) {\n return () =>\n h(\"i\", {\n class: props.name,\n style: {\n fontSize: `${props.size}px`,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n });\n },\n });\n</script>\n","<!--\n * @robot-admin/layout - CardLayout\n *\n * 卡片式布局骨架\n * 顶部导航 + hover 触发抽屉式菜单 + 内容区\n *\n * Slots:\n * #menu-trigger - 菜单触发区域(hover 弹出抽屉)\n * #logo - Logo/品牌区域\n * #header-extra - 头部右侧操作区\n * #tags-view - 标签页区域\n * #drawer-menu - 抽屉式菜单内容\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"card-layout-container\">\n <!-- 顶部导航栏 -->\n <div\n class=\"top-navbar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <div class=\"navbar-left\">\n <!-- 悬停触发区域 -->\n <div\n class=\"hover-trigger-area\"\n @mouseenter=\"cancelHideTimer\"\n @mouseleave=\"hideDrawerMenu\"\n >\n <slot name=\"menu-trigger\">\n <div class=\"menu-indicator\">\n <!-- 纯 CSS 三横线菜单图标 -->\n <span class=\"hamburger-icon\">\n <span></span>\n <span></span>\n <span></span>\n </span>\n </div>\n </slot>\n </div>\n\n <slot name=\"logo\">\n <div class=\"logo-container\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n />\n </div>\n <div class=\"brand-name\">\n <span class=\"brand-title\">{{ brand.name }}</span>\n <span class=\"brand-subtitle\">{{ brand.subtitle }}</span>\n </div>\n <div class=\"navbar-divider\"></div>\n </slot>\n </div>\n\n <!-- 右侧:操作区 -->\n <slot name=\"header-extra\" />\n </div>\n\n <!-- 标签页区域 -->\n <div\n v-if=\"showTagsView\"\n class=\"tags-view-container\"\n :style=\"{ height: `${tagsViewHeight}px` }\"\n >\n <slot name=\"tags-view\" />\n </div>\n\n <!-- 抽屉式菜单 -->\n <div\n class=\"drawer-menu\"\n :class=\"[\n isDarkMode ? 'dark-theme' : 'light-theme',\n { visible: showDrawerMenu },\n ]\"\n @mouseenter=\"cancelHideTimer\"\n @mouseleave=\"hideDrawerMenu\"\n >\n <slot name=\"drawer-menu\">\n <!-- 菜单头部 -->\n <div class=\"drawer-header\">\n <div class=\"drawer-title\">\n <!-- 纯 CSS 网格图标 -->\n <span class=\"grid-icon\">\n <span></span>\n <span></span>\n <span></span>\n <span></span>\n </span>\n <span>功能导航</span>\n </div>\n </div>\n\n <!-- 菜单内容 - 阿里云风格铺开布局 -->\n <div class=\"drawer-content\">\n <div class=\"menu-grid\">\n <div\n v-for=\"category in menus\"\n :key=\"category.path\"\n class=\"menu-category\"\n >\n <!-- 分类标题 -->\n <div class=\"category-header\" @click=\"navigateToPage(category)\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"category.meta?.icon\"\n :name=\"category.meta.icon\"\n :size=\"18\"\n />\n <span>{{ category.meta?.title }}</span>\n </div>\n\n <!-- 分类下的菜单项 -->\n <div\n class=\"category-items\"\n v-if=\"category.children && category.children.length > 0\"\n >\n <div\n v-for=\"item in category.children\"\n :key=\"item.path\"\n class=\"menu-item\"\n @click=\"navigateToPage(item)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"item.meta?.icon\"\n :name=\"item.meta.icon\"\n :size=\"16\"\n />\n <span class=\"item-title\">{{ item.meta?.title }}</span>\n </div>\n\n <!-- 3级菜单项 -->\n <template\n v-for=\"item in category.children\"\n :key=\"'sub-' + item.path\"\n >\n <div\n v-if=\"item.children && item.children.length > 0\"\n class=\"submenu-items\"\n >\n <div class=\"submenu-title\">{{ item.meta?.title }}</div>\n <div\n v-for=\"subItem in item.children\"\n :key=\"subItem.path\"\n class=\"menu-item submenu-item\"\n @click=\"navigateToPage(subItem)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"subItem.meta?.icon\"\n :name=\"subItem.meta.icon\"\n :size=\"14\"\n />\n <span class=\"item-title\">{{ subItem.meta?.title }}</span>\n </div>\n </div>\n </template>\n </div>\n </div>\n </div>\n </div>\n </slot>\n </div>\n\n <!-- 主内容区域 -->\n <div class=\"main-content-area\">\n <NLayout class=\"content-layout\">\n <NLayoutContent class=\"main-content p16px app-content\">\n <div class=\"page-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </div>\n </NLayoutContent>\n\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { ref, computed, onUnmounted, h, defineComponent } from \"vue\";\nimport { useRouter } from \"vue-router\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport type { MenuOptions } from \"../../../types/menu\";\n\ndefineOptions({ name: \"CardLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\nconst router = useRouter();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showTagsView = computed(() => ctx.showTagsView.value);\nconst tagsViewHeight = computed(() => ctx.tagsViewHeight.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n\n// 抽屉菜单状态\nconst showDrawerMenu = ref(false);\nconst hideTimer = ref<ReturnType<typeof setTimeout> | null>(null);\n\nconst hideDrawerMenu = () => {\n if (hideTimer.value) clearTimeout(hideTimer.value);\n hideTimer.value = setTimeout(() => {\n showDrawerMenu.value = false;\n }, 300);\n};\n\nconst cancelHideTimer = () => {\n if (hideTimer.value) {\n clearTimeout(hideTimer.value);\n hideTimer.value = null;\n }\n showDrawerMenu.value = true;\n};\n\nconst navigateToPage = (item: MenuOptions) => {\n if (item.path) {\n router.push(item.path);\n showDrawerMenu.value = false;\n }\n};\n\nconst LayoutIcon =\n ctx.iconComponent ??\n defineComponent({\n name: \"LayoutIcon\",\n props: { name: String, size: { type: Number, default: 18 } },\n setup(props) {\n return () =>\n h(\"i\", {\n class: props.name,\n style: {\n fontSize: `${props.size}px`,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n });\n },\n });\n\nonUnmounted(() => {\n if (hideTimer.value) clearTimeout(hideTimer.value);\n});\n</script>\n\n<style scoped lang=\"scss\">\n/* 纯 CSS 网格图标 (2x2) */\n.grid-icon {\n display: inline-grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 2.5px;\n width: 18px;\n height: 18px;\n padding: 1px;\n}\n\n.grid-icon span {\n background-color: currentColor;\n border-radius: 2px;\n opacity: 0.85;\n}\n\n/* 纯 CSS 三横线汉堡菜单图标 */\n.hamburger-icon {\n display: inline-flex;\n flex-direction: column;\n justify-content: space-between;\n width: 20px;\n height: 16px;\n padding: 2px 0;\n}\n\n.hamburger-icon span {\n display: block;\n width: 100%;\n height: 2px;\n background-color: currentColor;\n border-radius: 1px;\n opacity: 0.85;\n transition: all 0.2s ease;\n}\n</style>\n","<!--\n * @robot-admin/layout - CardLayout\n *\n * 卡片式布局骨架\n * 顶部导航 + hover 触发抽屉式菜单 + 内容区\n *\n * Slots:\n * #menu-trigger - 菜单触发区域(hover 弹出抽屉)\n * #logo - Logo/品牌区域\n * #header-extra - 头部右侧操作区\n * #tags-view - 标签页区域\n * #drawer-menu - 抽屉式菜单内容\n * #footer - 页脚区域\n -->\n<template>\n <div class=\"card-layout-container\">\n <!-- 顶部导航栏 -->\n <div\n class=\"top-navbar\"\n :class=\"[isDarkMode ? 'dark-theme' : 'light-theme']\"\n >\n <div class=\"navbar-left\">\n <!-- 悬停触发区域 -->\n <div\n class=\"hover-trigger-area\"\n @mouseenter=\"cancelHideTimer\"\n @mouseleave=\"hideDrawerMenu\"\n >\n <slot name=\"menu-trigger\">\n <div class=\"menu-indicator\">\n <!-- 纯 CSS 三横线菜单图标 -->\n <span class=\"hamburger-icon\">\n <span></span>\n <span></span>\n <span></span>\n </span>\n </div>\n </slot>\n </div>\n\n <slot name=\"logo\">\n <div class=\"logo-container\">\n <div class=\"logo-glow\"></div>\n <video\n v-if=\"brand.logoType === 'video'\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n autoplay\n loop\n muted\n playsinline\n class=\"logo-video\"\n >\n 您的浏览器不支持 video 标签。\n </video>\n <img\n v-else\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n />\n </div>\n <div class=\"brand-name\">\n <span class=\"brand-title\">{{ brand.name }}</span>\n <span class=\"brand-subtitle\">{{ brand.subtitle }}</span>\n </div>\n <div class=\"navbar-divider\"></div>\n </slot>\n </div>\n\n <!-- 右侧:操作区 -->\n <slot name=\"header-extra\" />\n </div>\n\n <!-- 标签页区域 -->\n <div\n v-if=\"showTagsView\"\n class=\"tags-view-container\"\n :style=\"{ height: `${tagsViewHeight}px` }\"\n >\n <slot name=\"tags-view\" />\n </div>\n\n <!-- 抽屉式菜单 -->\n <div\n class=\"drawer-menu\"\n :class=\"[\n isDarkMode ? 'dark-theme' : 'light-theme',\n { visible: showDrawerMenu },\n ]\"\n @mouseenter=\"cancelHideTimer\"\n @mouseleave=\"hideDrawerMenu\"\n >\n <slot name=\"drawer-menu\">\n <!-- 菜单头部 -->\n <div class=\"drawer-header\">\n <div class=\"drawer-title\">\n <!-- 纯 CSS 网格图标 -->\n <span class=\"grid-icon\">\n <span></span>\n <span></span>\n <span></span>\n <span></span>\n </span>\n <span>功能导航</span>\n </div>\n </div>\n\n <!-- 菜单内容 - 阿里云风格铺开布局 -->\n <div class=\"drawer-content\">\n <div class=\"menu-grid\">\n <div\n v-for=\"category in menus\"\n :key=\"category.path\"\n class=\"menu-category\"\n >\n <!-- 分类标题 -->\n <div class=\"category-header\" @click=\"navigateToPage(category)\">\n <component\n :is=\"LayoutIcon\"\n v-if=\"category.meta?.icon\"\n :name=\"category.meta.icon\"\n :size=\"18\"\n />\n <span>{{ category.meta?.title }}</span>\n </div>\n\n <!-- 分类下的菜单项 -->\n <div\n class=\"category-items\"\n v-if=\"category.children && category.children.length > 0\"\n >\n <div\n v-for=\"item in category.children\"\n :key=\"item.path\"\n class=\"menu-item\"\n @click=\"navigateToPage(item)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"item.meta?.icon\"\n :name=\"item.meta.icon\"\n :size=\"16\"\n />\n <span class=\"item-title\">{{ item.meta?.title }}</span>\n </div>\n\n <!-- 3级菜单项 -->\n <template\n v-for=\"item in category.children\"\n :key=\"'sub-' + item.path\"\n >\n <div\n v-if=\"item.children && item.children.length > 0\"\n class=\"submenu-items\"\n >\n <div class=\"submenu-title\">{{ item.meta?.title }}</div>\n <div\n v-for=\"subItem in item.children\"\n :key=\"subItem.path\"\n class=\"menu-item submenu-item\"\n @click=\"navigateToPage(subItem)\"\n >\n <component\n :is=\"LayoutIcon\"\n v-if=\"subItem.meta?.icon\"\n :name=\"subItem.meta.icon\"\n :size=\"14\"\n />\n <span class=\"item-title\">{{ subItem.meta?.title }}</span>\n </div>\n </div>\n </template>\n </div>\n </div>\n </div>\n </div>\n </slot>\n </div>\n\n <!-- 主内容区域 -->\n <div class=\"main-content-area\">\n <NLayout class=\"content-layout\">\n <NLayoutContent class=\"main-content p16px app-content\">\n <div class=\"page-content\">\n <RouterView v-slot=\"{ Component, route }\">\n <Transition :name=\"transitionName\" mode=\"out-in\">\n <KeepAlive :include=\"cachedViews\" :max=\"maxCacheCount\">\n <component :is=\"Component\" :key=\"route.path\" />\n </KeepAlive>\n </Transition>\n </RouterView>\n </div>\n </NLayoutContent>\n\n <template v-if=\"showFooter\">\n <slot name=\"footer\" />\n </template>\n </NLayout>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport \"./index.scss\";\nimport { ref, computed, onUnmounted, h, defineComponent } from \"vue\";\nimport { useRouter } from \"vue-router\";\nimport { NLayout, NLayoutContent } from \"naive-ui\";\nimport {\n useLayoutContext,\n DEFAULT_BRAND_CONFIG,\n} from \"../../../composables/useLayoutContext\";\nimport { useLayoutCache } from \"../../../composables/useLayoutCache\";\nimport type { MenuOptions } from \"../../../types/menu\";\n\ndefineOptions({ name: \"CardLayout\" });\n\nconst ctx = useLayoutContext();\nconst { cachedViews, maxCacheCount } = useLayoutCache();\nconst router = useRouter();\n\nconst isDarkMode = ctx.isDark;\nconst menus = computed(() => ctx.menus.value);\nconst showTagsView = computed(() => ctx.showTagsView.value);\nconst tagsViewHeight = computed(() => ctx.tagsViewHeight.value);\nconst showFooter = computed(() => ctx.showFooter.value);\nconst transitionName = computed(() => ctx.transitionName.value);\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\n\n// 抽屉菜单状态\nconst showDrawerMenu = ref(false);\nconst hideTimer = ref<ReturnType<typeof setTimeout> | null>(null);\n\nconst hideDrawerMenu = () => {\n if (hideTimer.value) clearTimeout(hideTimer.value);\n hideTimer.value = setTimeout(() => {\n showDrawerMenu.value = false;\n }, 300);\n};\n\nconst cancelHideTimer = () => {\n if (hideTimer.value) {\n clearTimeout(hideTimer.value);\n hideTimer.value = null;\n }\n showDrawerMenu.value = true;\n};\n\nconst navigateToPage = (item: MenuOptions) => {\n if (item.path) {\n router.push(item.path);\n showDrawerMenu.value = false;\n }\n};\n\nconst LayoutIcon =\n ctx.iconComponent ??\n defineComponent({\n name: \"LayoutIcon\",\n props: { name: String, size: { type: Number, default: 18 } },\n setup(props) {\n return () =>\n h(\"i\", {\n class: props.name,\n style: {\n fontSize: `${props.size}px`,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n },\n });\n },\n });\n\nonUnmounted(() => {\n if (hideTimer.value) clearTimeout(hideTimer.value);\n});\n</script>\n\n<style scoped lang=\"scss\">\n/* 纯 CSS 网格图标 (2x2) */\n.grid-icon {\n display: inline-grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 2.5px;\n width: 18px;\n height: 18px;\n padding: 1px;\n}\n\n.grid-icon span {\n background-color: currentColor;\n border-radius: 2px;\n opacity: 0.85;\n}\n\n/* 纯 CSS 三横线汉堡菜单图标 */\n.hamburger-icon {\n display: inline-flex;\n flex-direction: column;\n justify-content: space-between;\n width: 20px;\n height: 16px;\n padding: 2px 0;\n}\n\n.hamburger-icon span {\n display: block;\n width: 100%;\n height: 2px;\n background-color: currentColor;\n border-radius: 1px;\n opacity: 0.85;\n transition: all 0.2s ease;\n}\n</style>\n","<!--\r\n * @robot-admin/layout - C_LayoutContainer\r\n *\r\n * 智能布局容器\r\n * 根据 layoutMode 自动切换 6 种布局骨架,消费方仅需提供业务组件\r\n *\r\n * Slots:\r\n * #logo - 品牌 Logo 区域(各骨架有内置默认实现)\r\n * #menu - Side 布局的垂直菜单(需消费方提供)\r\n * #header - Side/Mix 布局的完整头部(需消费方提供)\r\n * #header-extra - Top/MixTop/Reverse/Card 头部右侧操作区\r\n * #tags-view - 标签页区域(需消费方提供)\r\n * #footer - 页脚区域(需消费方提供)\r\n * #top-menu - Top/MixTop/Reverse 水平菜单(骨架有默认)\r\n * #brand - MixTop 顶部导航品牌区(骨架有默认)\r\n * #menu-trigger - Card 菜单触发区域(骨架有默认)\r\n * #drawer-menu - Card 抽屉式菜单(骨架有默认)\r\n -->\r\n<template>\r\n <div :class=\"['layout-container', isDark ? 'dark-mode' : 'light-mode']\">\r\n <!-- ═══════ Side 左侧菜单布局 ═══════ -->\r\n <SideLayout v-if=\"layoutMode === 'side'\" v-model:collapsed=\"sideCollapsed\">\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template #menu>\r\n <slot name=\"menu\" :collapsed=\"sideCollapsed\" />\r\n </template>\r\n <template #header><slot name=\"header\" /></template>\r\n <template #footer><slot name=\"footer\" /></template>\r\n </SideLayout>\r\n\r\n <!-- ═══════ Top 顶部菜单布局 ═══════ -->\r\n <TopLayout v-else-if=\"layoutMode === 'top'\">\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template v-if=\"$slots['top-menu']\" #menu\r\n ><slot name=\"top-menu\"\r\n /></template>\r\n <template v-if=\"$slots['header-extra']\" #header-extra\r\n ><slot name=\"header-extra\"\r\n /></template>\r\n <template v-if=\"$slots['tags-view']\" #tags-view\r\n ><slot name=\"tags-view\"\r\n /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </TopLayout>\r\n\r\n <!-- ═══════ Mix 混合布局 ═══════ -->\r\n <MixLayout v-else-if=\"layoutMode === 'mix'\">\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template #header><slot name=\"header\" /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </MixLayout>\r\n\r\n <!-- ═══════ MixTop 顶部混合布局 ═══════ -->\r\n <MixTopLayout v-else-if=\"layoutMode === 'mix-top'\">\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template v-if=\"$slots.brand\" #brand><slot name=\"brand\" /></template>\r\n <template v-if=\"$slots['top-menu']\" #top-menu\r\n ><slot name=\"top-menu\"\r\n /></template>\r\n <template v-if=\"$slots['header-extra']\" #header-extra\r\n ><slot name=\"header-extra\"\r\n /></template>\r\n <template v-if=\"$slots['tags-view']\" #tags-view\r\n ><slot name=\"tags-view\"\r\n /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </MixTopLayout>\r\n\r\n <!-- ═══════ ReverseHorizontalMix 反转混合布局 ═══════ -->\r\n <ReverseHorizontalMixLayout\r\n v-else-if=\"layoutMode === 'reverse-horizontal-mix'\"\r\n >\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template v-if=\"$slots['top-menu']\" #top-menu\r\n ><slot name=\"top-menu\"\r\n /></template>\r\n <template v-if=\"$slots['header-extra']\" #header-extra\r\n ><slot name=\"header-extra\"\r\n /></template>\r\n <template v-if=\"$slots['tags-view']\" #tags-view\r\n ><slot name=\"tags-view\"\r\n /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </ReverseHorizontalMixLayout>\r\n\r\n <!-- ═══════ CardLayout 卡片布局 ═══════ -->\r\n <CardLayout v-else-if=\"layoutMode === 'card-layout'\">\r\n <template v-if=\"$slots['menu-trigger']\" #menu-trigger\r\n ><slot name=\"menu-trigger\"\r\n /></template>\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template v-if=\"$slots['header-extra']\" #header-extra\r\n ><slot name=\"header-extra\"\r\n /></template>\r\n <template v-if=\"$slots['tags-view']\" #tags-view\r\n ><slot name=\"tags-view\"\r\n /></template>\r\n <template v-if=\"$slots['drawer-menu']\" #drawer-menu\r\n ><slot name=\"drawer-menu\"\r\n /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </CardLayout>\r\n\r\n <!-- ═══════ Fallback ═══════ -->\r\n <div v-else class=\"layout-coming-soon\">\r\n <div class=\"coming-soon-content\">\r\n <div class=\"coming-soon-icon\">🚧</div>\r\n <div class=\"coming-soon-title\">布局开发中</div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, ref, provide } from \"vue\";\r\nimport { useSettingsStore } from \"../../stores/settings\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\n\r\n// 骨架布局全局样式\r\nimport \"../../styles/layouts.scss\";\r\n\r\n// 骨架组件\r\nimport SideLayout from \"../layouts/SideLayout/index.vue\";\r\nimport TopLayout from \"../layouts/TopLayout/index.vue\";\r\nimport MixLayout from \"../layouts/MixLayout/index.vue\";\r\nimport MixTopLayout from \"../layouts/MixTopLayout/index.vue\";\r\nimport ReverseHorizontalMixLayout from \"../layouts/ReverseHorizontalMixLayout/index.vue\";\r\nimport CardLayout from \"../layouts/CardLayout/index.vue\";\r\n\r\ndefineOptions({ name: \"C_LayoutContainer\" });\r\n\r\nconst settingsStore = useSettingsStore();\r\nconst ctx = useLayoutContext();\r\n\r\nconst layoutMode = computed(() => settingsStore.layoutMode);\r\nconst isDark = ctx.isDark;\r\n\r\n// Side 布局折叠状态\r\nconst sideCollapsed = ref(false);\r\n\r\n// 提供折叠状态给子组件(如 C_Header 的汉堡按钮)\r\nprovide(\"menuCollapse\", {\r\n isCollapsed: sideCollapsed,\r\n handleCollapsedChange: (collapsed: boolean) => {\r\n sideCollapsed.value = collapsed;\r\n },\r\n});\r\n</script>\r\n","<!--\r\n * @robot-admin/layout - C_LayoutContainer\r\n *\r\n * 智能布局容器\r\n * 根据 layoutMode 自动切换 6 种布局骨架,消费方仅需提供业务组件\r\n *\r\n * Slots:\r\n * #logo - 品牌 Logo 区域(各骨架有内置默认实现)\r\n * #menu - Side 布局的垂直菜单(需消费方提供)\r\n * #header - Side/Mix 布局的完整头部(需消费方提供)\r\n * #header-extra - Top/MixTop/Reverse/Card 头部右侧操作区\r\n * #tags-view - 标签页区域(需消费方提供)\r\n * #footer - 页脚区域(需消费方提供)\r\n * #top-menu - Top/MixTop/Reverse 水平菜单(骨架有默认)\r\n * #brand - MixTop 顶部导航品牌区(骨架有默认)\r\n * #menu-trigger - Card 菜单触发区域(骨架有默认)\r\n * #drawer-menu - Card 抽屉式菜单(骨架有默认)\r\n -->\r\n<template>\r\n <div :class=\"['layout-container', isDark ? 'dark-mode' : 'light-mode']\">\r\n <!-- ═══════ Side 左侧菜单布局 ═══════ -->\r\n <SideLayout v-if=\"layoutMode === 'side'\" v-model:collapsed=\"sideCollapsed\">\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template #menu>\r\n <slot name=\"menu\" :collapsed=\"sideCollapsed\" />\r\n </template>\r\n <template #header><slot name=\"header\" /></template>\r\n <template #footer><slot name=\"footer\" /></template>\r\n </SideLayout>\r\n\r\n <!-- ═══════ Top 顶部菜单布局 ═══════ -->\r\n <TopLayout v-else-if=\"layoutMode === 'top'\">\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template v-if=\"$slots['top-menu']\" #menu\r\n ><slot name=\"top-menu\"\r\n /></template>\r\n <template v-if=\"$slots['header-extra']\" #header-extra\r\n ><slot name=\"header-extra\"\r\n /></template>\r\n <template v-if=\"$slots['tags-view']\" #tags-view\r\n ><slot name=\"tags-view\"\r\n /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </TopLayout>\r\n\r\n <!-- ═══════ Mix 混合布局 ═══════ -->\r\n <MixLayout v-else-if=\"layoutMode === 'mix'\">\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template #header><slot name=\"header\" /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </MixLayout>\r\n\r\n <!-- ═══════ MixTop 顶部混合布局 ═══════ -->\r\n <MixTopLayout v-else-if=\"layoutMode === 'mix-top'\">\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template v-if=\"$slots.brand\" #brand><slot name=\"brand\" /></template>\r\n <template v-if=\"$slots['top-menu']\" #top-menu\r\n ><slot name=\"top-menu\"\r\n /></template>\r\n <template v-if=\"$slots['header-extra']\" #header-extra\r\n ><slot name=\"header-extra\"\r\n /></template>\r\n <template v-if=\"$slots['tags-view']\" #tags-view\r\n ><slot name=\"tags-view\"\r\n /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </MixTopLayout>\r\n\r\n <!-- ═══════ ReverseHorizontalMix 反转混合布局 ═══════ -->\r\n <ReverseHorizontalMixLayout\r\n v-else-if=\"layoutMode === 'reverse-horizontal-mix'\"\r\n >\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template v-if=\"$slots['top-menu']\" #top-menu\r\n ><slot name=\"top-menu\"\r\n /></template>\r\n <template v-if=\"$slots['header-extra']\" #header-extra\r\n ><slot name=\"header-extra\"\r\n /></template>\r\n <template v-if=\"$slots['tags-view']\" #tags-view\r\n ><slot name=\"tags-view\"\r\n /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </ReverseHorizontalMixLayout>\r\n\r\n <!-- ═══════ CardLayout 卡片布局 ═══════ -->\r\n <CardLayout v-else-if=\"layoutMode === 'card-layout'\">\r\n <template v-if=\"$slots['menu-trigger']\" #menu-trigger\r\n ><slot name=\"menu-trigger\"\r\n /></template>\r\n <template v-if=\"$slots.logo\" #logo><slot name=\"logo\" /></template>\r\n <template v-if=\"$slots['header-extra']\" #header-extra\r\n ><slot name=\"header-extra\"\r\n /></template>\r\n <template v-if=\"$slots['tags-view']\" #tags-view\r\n ><slot name=\"tags-view\"\r\n /></template>\r\n <template v-if=\"$slots['drawer-menu']\" #drawer-menu\r\n ><slot name=\"drawer-menu\"\r\n /></template>\r\n <template v-if=\"$slots.footer\" #footer><slot name=\"footer\" /></template>\r\n </CardLayout>\r\n\r\n <!-- ═══════ Fallback ═══════ -->\r\n <div v-else class=\"layout-coming-soon\">\r\n <div class=\"coming-soon-content\">\r\n <div class=\"coming-soon-icon\">🚧</div>\r\n <div class=\"coming-soon-title\">布局开发中</div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, ref, provide } from \"vue\";\r\nimport { useSettingsStore } from \"../../stores/settings\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\n\r\n// 骨架布局全局样式\r\nimport \"../../styles/layouts.scss\";\r\n\r\n// 骨架组件\r\nimport SideLayout from \"../layouts/SideLayout/index.vue\";\r\nimport TopLayout from \"../layouts/TopLayout/index.vue\";\r\nimport MixLayout from \"../layouts/MixLayout/index.vue\";\r\nimport MixTopLayout from \"../layouts/MixTopLayout/index.vue\";\r\nimport ReverseHorizontalMixLayout from \"../layouts/ReverseHorizontalMixLayout/index.vue\";\r\nimport CardLayout from \"../layouts/CardLayout/index.vue\";\r\n\r\ndefineOptions({ name: \"C_LayoutContainer\" });\r\n\r\nconst settingsStore = useSettingsStore();\r\nconst ctx = useLayoutContext();\r\n\r\nconst layoutMode = computed(() => settingsStore.layoutMode);\r\nconst isDark = ctx.isDark;\r\n\r\n// Side 布局折叠状态\r\nconst sideCollapsed = ref(false);\r\n\r\n// 提供折叠状态给子组件(如 C_Header 的汉堡按钮)\r\nprovide(\"menuCollapse\", {\r\n isCollapsed: sideCollapsed,\r\n handleCollapsedChange: (collapsed: boolean) => {\r\n sideCollapsed.value = collapsed;\r\n },\r\n});\r\n</script>\r\n","/**\n * SettingsDrawer - 常量数据\n */\n\nimport { THEME_PRESETS, COLOR_SWATCHES } from \"../../constants\";\nimport type { ThemePreset } from \"../../types\";\n\n// 重新导出(内部使用)\nexport { COLOR_SWATCHES, THEME_PRESETS };\nexport type { ThemePreset };\n\n// 布局模式选项 - 完整的 6 种布局(含 SVG 图标,仅 UI 内部使用)\nexport const LAYOUT_MODE_OPTIONS = [\n {\n label: \"左侧菜单(默认)\",\n value: \"side\",\n disabled: false,\n svg: `\n <rect x=\"0\" y=\"0\" width=\"16\" height=\"48\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.9\"/>\n <rect x=\"18\" y=\"0\" width=\"38\" height=\"10\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.7\"/>\n <rect x=\"18\" y=\"12\" width=\"38\" height=\"34\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.4\"/>\n `,\n },\n {\n label: \"顶部菜单(清爽)\",\n value: \"top\",\n disabled: false,\n svg: `\n <rect x=\"0\" y=\"0\" width=\"56\" height=\"10\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.9\"/>\n <rect x=\"0\" y=\"12\" width=\"56\" height=\"8\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.7\"/>\n <rect x=\"0\" y=\"22\" width=\"56\" height=\"24\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.4\"/>\n `,\n },\n {\n label: \"左侧混合(灵巧)\",\n value: \"mix\",\n disabled: false,\n svg: `\n <rect x=\"0\" y=\"0\" width=\"8\" height=\"48\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.9\"/>\n <rect x=\"10\" y=\"0\" width=\"14\" height=\"48\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.7\"/>\n <rect x=\"26\" y=\"0\" width=\"30\" height=\"10\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.6\"/>\n <rect x=\"26\" y=\"12\" width=\"30\" height=\"34\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.4\"/>\n `,\n },\n {\n label: \"顶部混合(侧优)\",\n value: \"mix-top\",\n disabled: false,\n svg: `\n <rect x=\"0\" y=\"0\" width=\"8\" height=\"48\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.9\"/>\n <rect x=\"10\" y=\"0\" width=\"46\" height=\"10\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.7\"/>\n <rect x=\"10\" y=\"12\" width=\"46\" height=\"34\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.4\"/>\n `,\n },\n {\n label: \"反转混合(另类)\",\n value: \"reverse-horizontal-mix\",\n disabled: false,\n svg: `\n <rect x=\"0\" y=\"0\" width=\"56\" height=\"10\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.9\"/>\n <rect x=\"44\" y=\"12\" width=\"12\" height=\"34\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.7\"/>\n <rect x=\"0\" y=\"12\" width=\"42\" height=\"34\" rx=\"1\" fill=\"currentColor\" fill-opacity=\"0.4\"/>\n `,\n },\n {\n label: \"卡片网格(新颖)\",\n value: \"card-layout\",\n disabled: false,\n svg: `\n <rect x=\"0\" y=\"0\" width=\"24\" height=\"20\" rx=\"2\" fill=\"currentColor\" fill-opacity=\"0.9\"/>\n <rect x=\"32\" y=\"0\" width=\"24\" height=\"20\" rx=\"2\" fill=\"currentColor\" fill-opacity=\"0.7\"/>\n <rect x=\"0\" y=\"24\" width=\"24\" height=\"20\" rx=\"2\" fill=\"currentColor\" fill-opacity=\"0.6\"/>\n <rect x=\"32\" y=\"24\" width=\"24\" height=\"20\" rx=\"2\" fill=\"currentColor\" fill-opacity=\"0.4\"/>\n `,\n },\n];\n","<!--\n * @robot-admin/layout - SettingsDrawer\n *\n * 布局配置抽屉\n * 包含外观/布局/功能三大模块,支持主题预设、布局切换、配置导入导出等\n -->\n<template>\n <NDrawer\n v-model:show=\"visible\"\n :width=\"380\"\n placement=\"right\"\n :trap-focus=\"true\"\n :block-scroll=\"true\"\n >\n <NDrawerContent title=\"⚙️ 布局配置\" closable>\n <NTabs v-model:value=\"activeTab\" animated class=\"settings-tabs\">\n <!-- 外观 Tab -->\n <NTabPane name=\"appearance\" tab=\"🎨 外观\">\n <!-- 预设方案网格 -->\n <div class=\"settings-section\">\n <div class=\"preset-grid\">\n <div\n v-for=\"preset in THEME_PRESETS\"\n :key=\"preset.name\"\n class=\"preset-card\"\n :class=\"{ active: isCurrentPreset(preset) }\"\n role=\"button\"\n tabindex=\"0\"\n :aria-pressed=\"isCurrentPreset(preset)\"\n @click=\"handleApplyPreset(preset)\"\n @keydown.enter=\"handleApplyPreset(preset)\"\n @keydown.space.prevent=\"handleApplyPreset(preset)\"\n >\n <div class=\"preset-icon\">{{ preset.icon }}</div>\n <div class=\"preset-name\">{{ preset.name }}</div>\n <div class=\"preset-color\">\n <span\n class=\"color-dot\"\n :style=\"{ backgroundColor: preset.primaryColor }\"\n ></span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- 主题模式 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">主题模式</div>\n <NRadioGroup\n :value=\"settingsStore.themeMode\"\n class=\"mode-group\"\n @update:value=\"handleThemeModeChange\"\n >\n <NRadioButton value=\"light\">\n <span class=\"i-mdi:white-balance-sunny mr-1\"></span>\n 浅色\n </NRadioButton>\n <NRadioButton value=\"dark\">\n <span class=\"i-mdi:moon-waning-crescent mr-1\"></span>\n 深色\n </NRadioButton>\n <NRadioButton value=\"system\">\n <span class=\"i-mdi:theme-light-dark mr-1\"></span>\n 自动\n </NRadioButton>\n </NRadioGroup>\n </div>\n\n <!-- 主题色 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">主题色</div>\n <div class=\"color-picker-wrapper\">\n <NColorPicker\n v-model:value=\"settingsStore.primaryColor\"\n :show-alpha=\"false\"\n :swatches=\"COLOR_SWATCHES\"\n :actions=\"['confirm']\"\n />\n <span class=\"color-value\">{{ settingsStore.primaryColor }}</span>\n </div>\n </div>\n\n <!-- 圆角大小 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">圆角大小</div>\n <NRadioGroup\n v-model:value=\"settingsStore.borderRadius\"\n class=\"radius-group\"\n >\n <NRadioButton value=\"small\">小 (4px)</NRadioButton>\n <NRadioButton value=\"medium\">中 (6px)</NRadioButton>\n <NRadioButton value=\"large\">大 (8px)</NRadioButton>\n </NRadioGroup>\n </div>\n\n <!-- 页面动画 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">页面动画</div>\n <div class=\"flex items-center justify-between mb-2\">\n <span class=\"text-sm\">启用动画</span>\n <NSwitch v-model:value=\"settingsStore.enableTransition\" />\n </div>\n <NRadioGroup\n v-model:value=\"settingsStore.transitionType\"\n :disabled=\"!settingsStore.enableTransition\"\n class=\"transition-group\"\n >\n <NRadioButton value=\"fade\">淡入</NRadioButton>\n <NRadioButton value=\"slide\">滑动</NRadioButton>\n <NRadioButton value=\"zoom\">缩放</NRadioButton>\n <NRadioButton value=\"none\">无</NRadioButton>\n </NRadioGroup>\n </div>\n\n <!-- 恢复默认 -->\n <div class=\"settings-section\">\n <NButton block secondary @click=\"handleResetAppearance\">\n <template #icon>\n <span class=\"i-mdi:restore\"></span>\n </template>\n 恢复外观默认设置\n </NButton>\n </div>\n </NTabPane>\n\n <!-- 布局 Tab -->\n <NTabPane name=\"layout\" tab=\"📐 布局\">\n <!-- 布局模式 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">布局模式</div>\n <div class=\"layout-grid\">\n <div\n v-for=\"mode in LAYOUT_MODE_OPTIONS\"\n :key=\"mode.value\"\n class=\"layout-item\"\n :class=\"{\n active: settingsStore.layoutMode === mode.value,\n disabled: mode.disabled,\n }\"\n role=\"button\"\n :tabindex=\"mode.disabled ? -1 : 0\"\n :aria-disabled=\"mode.disabled\"\n :aria-pressed=\"settingsStore.layoutMode === mode.value\"\n @click.stop=\"handleLayoutChange(mode.value, mode.disabled)\"\n @keydown.enter.stop=\"handleLayoutChange(mode.value, mode.disabled)\"\n @keydown.space.stop.prevent=\"\n handleLayoutChange(mode.value, mode.disabled)\n \"\n >\n <div class=\"layout-screenshot\">\n <svg\n class=\"layout-svg\"\n viewBox=\"0 0 56 48\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <g v-html=\"mode.svg\"></g>\n </svg>\n </div>\n <div class=\"layout-label\">\n {{ mode.label }}\n <span v-if=\"mode.disabled\" class=\"badge-soon\">敬请期待</span>\n </div>\n </div>\n </div>\n </div>\n\n <div\n class=\"settings-section menu-expand-section\"\n data-menu-expand-mode=\"built-in\"\n >\n <div class=\"section-title\">菜单展开方式</div>\n <NRadioGroup\n v-model:value=\"settingsStore.menuExpandMode\"\n class=\"menu-expand-group\"\n >\n <NRadioButton value=\"inline\">传统展开</NRadioButton>\n <NRadioButton value=\"panel\">右侧面板</NRadioButton>\n </NRadioGroup>\n </div>\n\n <!-- 界面元素 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">界面元素</div>\n <div class=\"setting-item\">\n <span>显示面包屑</span>\n <NSwitch v-model:value=\"settingsStore.showBreadcrumb\" />\n </div>\n <div class=\"setting-item\">\n <span>显示面包屑图标</span>\n <NSwitch\n v-model:value=\"settingsStore.showBreadcrumbIcon\"\n :disabled=\"!settingsStore.showBreadcrumb\"\n />\n </div>\n <div class=\"setting-item\">\n <span>显示标签页</span>\n <NSwitch v-model:value=\"settingsStore.showTagsView\" />\n </div>\n <div class=\"setting-item\">\n <span>显示页脚</span>\n <NSwitch v-model:value=\"settingsStore.showFooter\" />\n </div>\n </div>\n\n <!-- 尺寸调整 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">尺寸调整</div>\n <div class=\"slider-item\">\n <div class=\"slider-label\">\n <span>侧边栏宽度</span>\n <span class=\"slider-value\"\n >{{ settingsStore.sidebarWidth }}px</span\n >\n </div>\n <NSlider\n v-model:value=\"settingsStore.sidebarWidth\"\n :min=\"180\"\n :max=\"280\"\n :step=\"10\"\n :marks=\"{ 180: '180', 220: '220', 280: '280' }\"\n />\n </div>\n <div class=\"slider-item\">\n <div class=\"slider-label\">\n <span>头部高度</span>\n <span class=\"slider-value\"\n >{{ settingsStore.headerHeight }}px</span\n >\n </div>\n <NSlider\n v-model:value=\"settingsStore.headerHeight\"\n :min=\"48\"\n :max=\"64\"\n :step=\"4\"\n :marks=\"{ 48: '48', 56: '56', 64: '64' }\"\n />\n </div>\n </div>\n\n <!-- 恢复默认 -->\n <div class=\"settings-section\">\n <NButton block secondary @click=\"handleResetLayout\">\n <template #icon>\n <span class=\"i-mdi:restore\"></span>\n </template>\n 恢复布局默认设置\n </NButton>\n </div>\n </NTabPane>\n\n <!-- 功能 Tab -->\n <NTabPane name=\"features\" tab=\"🔧 功能\">\n <!-- 缓存管理 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">缓存管理</div>\n <div class=\"action-buttons-grid\">\n <NButton secondary @click=\"handleClearCache\">\n <template #icon>\n <span class=\"i-mdi:delete-sweep\"></span>\n </template>\n 清除缓存\n </NButton>\n <NButton secondary @click=\"handleReload\">\n <template #icon>\n <span class=\"i-mdi:refresh\"></span>\n </template>\n 重新加载\n </NButton>\n </div>\n </div>\n\n <!-- 配置管理 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">配置管理</div>\n <div class=\"action-buttons-grid\">\n <NButton secondary @click=\"handleExportConfig\">\n <template #icon>\n <span class=\"i-mdi:download\"></span>\n </template>\n 导出配置\n </NButton>\n <NButton secondary @click=\"handleImportConfig\">\n <template #icon>\n <span class=\"i-mdi:upload\"></span>\n </template>\n 导入配置\n </NButton>\n </div>\n <NButton\n block\n type=\"error\"\n secondary\n class=\"mt-2\"\n @click=\"handleReset\"\n >\n <template #icon>\n <span class=\"i-mdi:restore\"></span>\n </template>\n 重置所有配置\n </NButton>\n </div>\n\n <!-- 系统模式 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">系统模式</div>\n <div class=\"setting-item\">\n <div class=\"setting-label\">\n <span>灰色模式</span>\n <span class=\"setting-desc\">用于哀悼日场景</span>\n </div>\n <NSwitch v-model:value=\"grayMode\" />\n </div>\n <div class=\"setting-item\">\n <div class=\"setting-label\">\n <span>色弱模式</span>\n <span class=\"setting-desc\">提供无障碍访问支持</span>\n </div>\n <NSwitch v-model:value=\"colorWeakMode\" />\n </div>\n </div>\n\n <!-- 水印设置 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">水印设置</div>\n <div class=\"setting-item\">\n <span>启用水印</span>\n <NSwitch v-model:value=\"watermarkEnabled\" />\n </div>\n <div v-if=\"watermarkEnabled\" class=\"mt-2\">\n <NInput\n v-model:value=\"watermarkText\"\n placeholder=\"请输入水印内容\"\n size=\"small\"\n />\n </div>\n </div>\n\n <!-- 系统信息 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">系统信息</div>\n <div class=\"system-info\">\n <div class=\"info-item\">\n <span class=\"info-label\">浏览器</span>\n <span class=\"info-value\">{{ systemInfo.browser }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">操作系统</span>\n <span class=\"info-value\">{{ systemInfo.os }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">屏幕分辨率</span>\n <span class=\"info-value\">{{ systemInfo.resolution }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">设备像素比</span>\n <span class=\"info-value\">{{ systemInfo.pixelRatio }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">语言</span>\n <span class=\"info-value\">{{ systemInfo.language }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">时区</span>\n <span class=\"info-value\">{{ systemInfo.timezone }}</span>\n </div>\n </div>\n </div>\n </NTabPane>\n </NTabs>\n </NDrawerContent>\n </NDrawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch, onUnmounted } from \"vue\";\nimport {\n NDrawer,\n NDrawerContent,\n NTabs,\n NTabPane,\n NRadioGroup,\n NRadioButton,\n NColorPicker,\n NSwitch,\n NButton,\n NSlider,\n NInput,\n useMessage,\n useDialog,\n} from \"naive-ui\";\nimport {\n sanitizeSettingsPatch,\n useSettingsStore,\n} from \"../../stores/settings\";\nimport { DEFAULT_SETTINGS } from \"../../constants\";\nimport { COLOR_SWATCHES, LAYOUT_MODE_OPTIONS, THEME_PRESETS } from \"./data\";\nimport type { ThemePreset } from \"../../types\";\nimport type { ThemeMode } from \"@robot-admin/theme\";\n\n// ============ 数据定义 ============\n\nconst message = useMessage();\nconst dialog = useDialog();\nconst settingsStore = useSettingsStore();\nconst visible = defineModel<boolean>(\"show\", { default: false });\nconst activeTab = ref(\"appearance\");\n\n// 功能开关\nconst grayMode = ref(false);\nconst colorWeakMode = ref(false);\nconst watermarkEnabled = ref(false);\nconst watermarkText = ref(\"Robot Admin\");\n\n// 水印元素引用\nlet watermarkEl: HTMLElement | null = null;\n\n// 创建水印函数\nconst createWatermark = (text: string) => {\n // 移除旧水印\n if (watermarkEl) {\n document.body.removeChild(watermarkEl);\n watermarkEl = null;\n }\n\n // 创建 canvas\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n message.error(\"当前浏览器无法创建水印画布\");\n return;\n }\n canvas.width = 200;\n canvas.height = 150;\n\n ctx.font = \"14px Microsoft JhengHei\";\n ctx.fillStyle = \"rgba(120, 120, 120, 0.15)\";\n ctx.rotate((-20 * Math.PI) / 180);\n ctx.fillText(text, 0, 80);\n\n // 创建水印容器\n watermarkEl = document.createElement(\"div\");\n watermarkEl.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n background-image: url(${canvas.toDataURL()});\n background-repeat: repeat;\n z-index: 9999;\n user-select: none;\n `;\n\n document.body.appendChild(watermarkEl);\n};\n\n// 移除水印函数\nconst removeWatermark = () => {\n if (watermarkEl) {\n document.body.removeChild(watermarkEl);\n watermarkEl = null;\n }\n};\n\n// 监听灰色模式\nwatch(grayMode, (val) => {\n document.documentElement.classList.toggle(\"gray-mode\", val);\n});\n\n// 监听色弱模式\nwatch(colorWeakMode, (val) => {\n document.documentElement.classList.toggle(\"color-weak-mode\", val);\n});\n\n// 监听水印开关\nwatch(watermarkEnabled, (val) => {\n if (val) {\n createWatermark(watermarkText.value);\n } else {\n removeWatermark();\n }\n});\n\n// 监听水印文本\nwatch(watermarkText, (val) => {\n if (watermarkEnabled.value) {\n createWatermark(val);\n }\n});\n\n// 获取系统信息\nconst systemInfo = computed(() => {\n if (typeof navigator === \"undefined\" || typeof window === \"undefined\") {\n return {\n browser: \"Unknown\",\n os: \"Unknown\",\n resolution: \"Unknown\",\n pixelRatio: \"Unknown\",\n language: \"Unknown\",\n timezone: \"Unknown\",\n };\n }\n\n const ua = navigator.userAgent;\n let browser = \"Unknown\";\n let os = \"Unknown\";\n\n // 检测浏览器\n if (ua.includes(\"Chrome\")) browser = \"Chrome\";\n else if (ua.includes(\"Firefox\")) browser = \"Firefox\";\n else if (ua.includes(\"Safari\")) browser = \"Safari\";\n else if (ua.includes(\"Edge\")) browser = \"Edge\";\n\n // 检测操作系统\n if (ua.includes(\"Windows\")) os = \"Windows\";\n else if (ua.includes(\"Mac\")) os = \"macOS\";\n else if (ua.includes(\"Linux\")) os = \"Linux\";\n else if (ua.includes(\"Android\")) os = \"Android\";\n else if (ua.includes(\"iOS\")) os = \"iOS\";\n\n return {\n browser,\n os,\n resolution: `${window.screen.width} × ${window.screen.height}`,\n pixelRatio: window.devicePixelRatio + \"x\",\n language: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n };\n});\n\n// 组件卸载时清理水印\nonUnmounted(() => {\n removeWatermark();\n});\n\n// 处理布局切换 - 阻止抽屉关闭\nconst handleLayoutChange = (value: string, disabled?: boolean) => {\n if (disabled) return;\n settingsStore.layoutMode = value as any;\n};\n\n// ============ 方法 ============\n\n/**\n * 判断是否是当前预设\n */\nconst isCurrentPreset = (preset: ThemePreset) => {\n return settingsStore.primaryColor === preset.primaryColor;\n};\n\n/**\n * 应用预设方案\n */\nconst handleApplyPreset = (preset: ThemePreset) => {\n settingsStore.applyPreset(preset);\n message.success(`已应用「${preset.name}」主题方案`);\n};\n\nconst handleThemeModeChange = async (mode: ThemeMode) => {\n try {\n await settingsStore.updateThemeMode(mode);\n } catch {\n message.error(\"主题模式切换失败\");\n }\n};\n\n/**\n * 恢复外观默认设置\n */\nconst handleResetAppearance = () => {\n settingsStore.themeMode = DEFAULT_SETTINGS.themeMode;\n settingsStore.primaryColor = DEFAULT_SETTINGS.primaryColor;\n settingsStore.borderRadius = DEFAULT_SETTINGS.borderRadius;\n settingsStore.transitionType = DEFAULT_SETTINGS.transitionType;\n settingsStore.enableTransition = DEFAULT_SETTINGS.enableTransition;\n message.success(\"已恢复外观默认设置\");\n};\n\n/**\n * 恢复布局默认设置\n */\nconst handleResetLayout = () => {\n settingsStore.layoutMode = DEFAULT_SETTINGS.layoutMode;\n settingsStore.menuExpandMode = DEFAULT_SETTINGS.menuExpandMode;\n settingsStore.fixedHeader = DEFAULT_SETTINGS.fixedHeader;\n settingsStore.showBreadcrumb = DEFAULT_SETTINGS.showBreadcrumb;\n settingsStore.showBreadcrumbIcon = DEFAULT_SETTINGS.showBreadcrumbIcon;\n settingsStore.showTagsView = DEFAULT_SETTINGS.showTagsView;\n settingsStore.tagsViewHeight = DEFAULT_SETTINGS.tagsViewHeight;\n settingsStore.tagsViewStyle = DEFAULT_SETTINGS.tagsViewStyle;\n settingsStore.showFooter = DEFAULT_SETTINGS.showFooter;\n settingsStore.sidebarWidth = DEFAULT_SETTINGS.sidebarWidth;\n settingsStore.sidebarCollapsedWidth = DEFAULT_SETTINGS.sidebarCollapsedWidth;\n settingsStore.headerHeight = DEFAULT_SETTINGS.headerHeight;\n message.success(\"已恢复布局默认设置\");\n};\n\n/**\n * 重置配置\n */\nconst handleReset = () => {\n dialog.warning({\n title: \"确认重置\",\n content: \"确定要恢复默认配置吗?此操作不可撤销。\",\n positiveText: \"确认\",\n negativeText: \"取消\",\n onPositiveClick: () => {\n settingsStore.resetSettings();\n message.success(\"已恢复默认配置\");\n },\n });\n};\n\n/**\n * 清除缓存\n */\nconst handleClearCache = () => {\n dialog.warning({\n title: \"确认清除\",\n content: \"确定要清除浏览器缓存吗?\",\n positiveText: \"确认\",\n negativeText: \"取消\",\n onPositiveClick: () => {\n // 清除 localStorage\n const keysToKeep = [\"theme-mode\", \"robot-admin-settings\"];\n const allKeys = Object.keys(localStorage);\n allKeys.forEach((key) => {\n if (!keysToKeep.includes(key)) {\n localStorage.removeItem(key);\n }\n });\n // 清除 sessionStorage\n sessionStorage.clear();\n message.success(\"缓存已清除\");\n },\n });\n};\n\n/**\n * 重新加载页面\n */\nconst handleReload = () => {\n location.reload();\n};\n\n/**\n * 导出配置\n */\nconst handleExportConfig = () => {\n const config = {\n settings: settingsStore.settingsState,\n gray: grayMode.value,\n colorWeak: colorWeakMode.value,\n watermark: {\n enabled: watermarkEnabled.value,\n text: watermarkText.value,\n },\n exportTime: new Date().toISOString(),\n };\n const blob = new Blob([JSON.stringify(config, null, 2)], {\n type: \"application/json\",\n });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `robot-admin-config-${Date.now()}.json`;\n a.click();\n URL.revokeObjectURL(url);\n message.success(\"配置已导出\");\n};\n\n/**\n * 导入配置\n */\nconst handleImportConfig = () => {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.accept = \"application/json\";\n input.onchange = async (e: Event) => {\n const file = (e.target as HTMLInputElement).files?.[0];\n if (!file) return;\n\n try {\n const text = await file.text();\n const config: unknown = JSON.parse(text);\n if (!config || typeof config !== \"object\" || Array.isArray(config)) {\n throw new TypeError(\"配置文件根节点必须是对象\");\n }\n const imported = config as Record<string, unknown>;\n\n // 应用配置\n if (imported.settings !== undefined) {\n const settingsPatch = sanitizeSettingsPatch(imported.settings);\n if (settingsPatch.themeMode !== undefined) {\n await settingsStore.updateThemeMode(settingsPatch.themeMode);\n delete settingsPatch.themeMode;\n }\n settingsStore.$patch(settingsPatch);\n }\n if (imported.gray !== undefined) {\n if (typeof imported.gray !== \"boolean\") throw new TypeError(\"gray\");\n grayMode.value = imported.gray;\n }\n if (imported.colorWeak !== undefined) {\n if (typeof imported.colorWeak !== \"boolean\")\n throw new TypeError(\"colorWeak\");\n colorWeakMode.value = imported.colorWeak;\n }\n if (imported.watermark !== undefined) {\n if (\n !imported.watermark ||\n typeof imported.watermark !== \"object\" ||\n Array.isArray(imported.watermark)\n ) {\n throw new TypeError(\"watermark\");\n }\n const watermark = imported.watermark as Record<string, unknown>;\n if (\n typeof watermark.enabled !== \"boolean\" ||\n typeof watermark.text !== \"string\" ||\n watermark.text.length > 200\n ) {\n throw new TypeError(\"watermark\");\n }\n watermarkEnabled.value = watermark.enabled;\n watermarkText.value = watermark.text;\n }\n\n message.success(\"配置已导入\");\n } catch (error) {\n message.error(\"配置文件格式错误\");\n }\n };\n input.click();\n};\n</script>\n\n<style scoped lang=\"scss\">\n@use \"../../styles/settings.scss\" as *;\n</style>\n","<!--\n * @robot-admin/layout - SettingsDrawer\n *\n * 布局配置抽屉\n * 包含外观/布局/功能三大模块,支持主题预设、布局切换、配置导入导出等\n -->\n<template>\n <NDrawer\n v-model:show=\"visible\"\n :width=\"380\"\n placement=\"right\"\n :trap-focus=\"true\"\n :block-scroll=\"true\"\n >\n <NDrawerContent title=\"⚙️ 布局配置\" closable>\n <NTabs v-model:value=\"activeTab\" animated class=\"settings-tabs\">\n <!-- 外观 Tab -->\n <NTabPane name=\"appearance\" tab=\"🎨 外观\">\n <!-- 预设方案网格 -->\n <div class=\"settings-section\">\n <div class=\"preset-grid\">\n <div\n v-for=\"preset in THEME_PRESETS\"\n :key=\"preset.name\"\n class=\"preset-card\"\n :class=\"{ active: isCurrentPreset(preset) }\"\n role=\"button\"\n tabindex=\"0\"\n :aria-pressed=\"isCurrentPreset(preset)\"\n @click=\"handleApplyPreset(preset)\"\n @keydown.enter=\"handleApplyPreset(preset)\"\n @keydown.space.prevent=\"handleApplyPreset(preset)\"\n >\n <div class=\"preset-icon\">{{ preset.icon }}</div>\n <div class=\"preset-name\">{{ preset.name }}</div>\n <div class=\"preset-color\">\n <span\n class=\"color-dot\"\n :style=\"{ backgroundColor: preset.primaryColor }\"\n ></span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- 主题模式 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">主题模式</div>\n <NRadioGroup\n :value=\"settingsStore.themeMode\"\n class=\"mode-group\"\n @update:value=\"handleThemeModeChange\"\n >\n <NRadioButton value=\"light\">\n <span class=\"i-mdi:white-balance-sunny mr-1\"></span>\n 浅色\n </NRadioButton>\n <NRadioButton value=\"dark\">\n <span class=\"i-mdi:moon-waning-crescent mr-1\"></span>\n 深色\n </NRadioButton>\n <NRadioButton value=\"system\">\n <span class=\"i-mdi:theme-light-dark mr-1\"></span>\n 自动\n </NRadioButton>\n </NRadioGroup>\n </div>\n\n <!-- 主题色 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">主题色</div>\n <div class=\"color-picker-wrapper\">\n <NColorPicker\n v-model:value=\"settingsStore.primaryColor\"\n :show-alpha=\"false\"\n :swatches=\"COLOR_SWATCHES\"\n :actions=\"['confirm']\"\n />\n <span class=\"color-value\">{{ settingsStore.primaryColor }}</span>\n </div>\n </div>\n\n <!-- 圆角大小 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">圆角大小</div>\n <NRadioGroup\n v-model:value=\"settingsStore.borderRadius\"\n class=\"radius-group\"\n >\n <NRadioButton value=\"small\">小 (4px)</NRadioButton>\n <NRadioButton value=\"medium\">中 (6px)</NRadioButton>\n <NRadioButton value=\"large\">大 (8px)</NRadioButton>\n </NRadioGroup>\n </div>\n\n <!-- 页面动画 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">页面动画</div>\n <div class=\"flex items-center justify-between mb-2\">\n <span class=\"text-sm\">启用动画</span>\n <NSwitch v-model:value=\"settingsStore.enableTransition\" />\n </div>\n <NRadioGroup\n v-model:value=\"settingsStore.transitionType\"\n :disabled=\"!settingsStore.enableTransition\"\n class=\"transition-group\"\n >\n <NRadioButton value=\"fade\">淡入</NRadioButton>\n <NRadioButton value=\"slide\">滑动</NRadioButton>\n <NRadioButton value=\"zoom\">缩放</NRadioButton>\n <NRadioButton value=\"none\">无</NRadioButton>\n </NRadioGroup>\n </div>\n\n <!-- 恢复默认 -->\n <div class=\"settings-section\">\n <NButton block secondary @click=\"handleResetAppearance\">\n <template #icon>\n <span class=\"i-mdi:restore\"></span>\n </template>\n 恢复外观默认设置\n </NButton>\n </div>\n </NTabPane>\n\n <!-- 布局 Tab -->\n <NTabPane name=\"layout\" tab=\"📐 布局\">\n <!-- 布局模式 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">布局模式</div>\n <div class=\"layout-grid\">\n <div\n v-for=\"mode in LAYOUT_MODE_OPTIONS\"\n :key=\"mode.value\"\n class=\"layout-item\"\n :class=\"{\n active: settingsStore.layoutMode === mode.value,\n disabled: mode.disabled,\n }\"\n role=\"button\"\n :tabindex=\"mode.disabled ? -1 : 0\"\n :aria-disabled=\"mode.disabled\"\n :aria-pressed=\"settingsStore.layoutMode === mode.value\"\n @click.stop=\"handleLayoutChange(mode.value, mode.disabled)\"\n @keydown.enter.stop=\"handleLayoutChange(mode.value, mode.disabled)\"\n @keydown.space.stop.prevent=\"\n handleLayoutChange(mode.value, mode.disabled)\n \"\n >\n <div class=\"layout-screenshot\">\n <svg\n class=\"layout-svg\"\n viewBox=\"0 0 56 48\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <g v-html=\"mode.svg\"></g>\n </svg>\n </div>\n <div class=\"layout-label\">\n {{ mode.label }}\n <span v-if=\"mode.disabled\" class=\"badge-soon\">敬请期待</span>\n </div>\n </div>\n </div>\n </div>\n\n <div\n class=\"settings-section menu-expand-section\"\n data-menu-expand-mode=\"built-in\"\n >\n <div class=\"section-title\">菜单展开方式</div>\n <NRadioGroup\n v-model:value=\"settingsStore.menuExpandMode\"\n class=\"menu-expand-group\"\n >\n <NRadioButton value=\"inline\">传统展开</NRadioButton>\n <NRadioButton value=\"panel\">右侧面板</NRadioButton>\n </NRadioGroup>\n </div>\n\n <!-- 界面元素 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">界面元素</div>\n <div class=\"setting-item\">\n <span>显示面包屑</span>\n <NSwitch v-model:value=\"settingsStore.showBreadcrumb\" />\n </div>\n <div class=\"setting-item\">\n <span>显示面包屑图标</span>\n <NSwitch\n v-model:value=\"settingsStore.showBreadcrumbIcon\"\n :disabled=\"!settingsStore.showBreadcrumb\"\n />\n </div>\n <div class=\"setting-item\">\n <span>显示标签页</span>\n <NSwitch v-model:value=\"settingsStore.showTagsView\" />\n </div>\n <div class=\"setting-item\">\n <span>显示页脚</span>\n <NSwitch v-model:value=\"settingsStore.showFooter\" />\n </div>\n </div>\n\n <!-- 尺寸调整 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">尺寸调整</div>\n <div class=\"slider-item\">\n <div class=\"slider-label\">\n <span>侧边栏宽度</span>\n <span class=\"slider-value\"\n >{{ settingsStore.sidebarWidth }}px</span\n >\n </div>\n <NSlider\n v-model:value=\"settingsStore.sidebarWidth\"\n :min=\"180\"\n :max=\"280\"\n :step=\"10\"\n :marks=\"{ 180: '180', 220: '220', 280: '280' }\"\n />\n </div>\n <div class=\"slider-item\">\n <div class=\"slider-label\">\n <span>头部高度</span>\n <span class=\"slider-value\"\n >{{ settingsStore.headerHeight }}px</span\n >\n </div>\n <NSlider\n v-model:value=\"settingsStore.headerHeight\"\n :min=\"48\"\n :max=\"64\"\n :step=\"4\"\n :marks=\"{ 48: '48', 56: '56', 64: '64' }\"\n />\n </div>\n </div>\n\n <!-- 恢复默认 -->\n <div class=\"settings-section\">\n <NButton block secondary @click=\"handleResetLayout\">\n <template #icon>\n <span class=\"i-mdi:restore\"></span>\n </template>\n 恢复布局默认设置\n </NButton>\n </div>\n </NTabPane>\n\n <!-- 功能 Tab -->\n <NTabPane name=\"features\" tab=\"🔧 功能\">\n <!-- 缓存管理 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">缓存管理</div>\n <div class=\"action-buttons-grid\">\n <NButton secondary @click=\"handleClearCache\">\n <template #icon>\n <span class=\"i-mdi:delete-sweep\"></span>\n </template>\n 清除缓存\n </NButton>\n <NButton secondary @click=\"handleReload\">\n <template #icon>\n <span class=\"i-mdi:refresh\"></span>\n </template>\n 重新加载\n </NButton>\n </div>\n </div>\n\n <!-- 配置管理 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">配置管理</div>\n <div class=\"action-buttons-grid\">\n <NButton secondary @click=\"handleExportConfig\">\n <template #icon>\n <span class=\"i-mdi:download\"></span>\n </template>\n 导出配置\n </NButton>\n <NButton secondary @click=\"handleImportConfig\">\n <template #icon>\n <span class=\"i-mdi:upload\"></span>\n </template>\n 导入配置\n </NButton>\n </div>\n <NButton\n block\n type=\"error\"\n secondary\n class=\"mt-2\"\n @click=\"handleReset\"\n >\n <template #icon>\n <span class=\"i-mdi:restore\"></span>\n </template>\n 重置所有配置\n </NButton>\n </div>\n\n <!-- 系统模式 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">系统模式</div>\n <div class=\"setting-item\">\n <div class=\"setting-label\">\n <span>灰色模式</span>\n <span class=\"setting-desc\">用于哀悼日场景</span>\n </div>\n <NSwitch v-model:value=\"grayMode\" />\n </div>\n <div class=\"setting-item\">\n <div class=\"setting-label\">\n <span>色弱模式</span>\n <span class=\"setting-desc\">提供无障碍访问支持</span>\n </div>\n <NSwitch v-model:value=\"colorWeakMode\" />\n </div>\n </div>\n\n <!-- 水印设置 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">水印设置</div>\n <div class=\"setting-item\">\n <span>启用水印</span>\n <NSwitch v-model:value=\"watermarkEnabled\" />\n </div>\n <div v-if=\"watermarkEnabled\" class=\"mt-2\">\n <NInput\n v-model:value=\"watermarkText\"\n placeholder=\"请输入水印内容\"\n size=\"small\"\n />\n </div>\n </div>\n\n <!-- 系统信息 -->\n <div class=\"settings-section\">\n <div class=\"section-title\">系统信息</div>\n <div class=\"system-info\">\n <div class=\"info-item\">\n <span class=\"info-label\">浏览器</span>\n <span class=\"info-value\">{{ systemInfo.browser }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">操作系统</span>\n <span class=\"info-value\">{{ systemInfo.os }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">屏幕分辨率</span>\n <span class=\"info-value\">{{ systemInfo.resolution }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">设备像素比</span>\n <span class=\"info-value\">{{ systemInfo.pixelRatio }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">语言</span>\n <span class=\"info-value\">{{ systemInfo.language }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">时区</span>\n <span class=\"info-value\">{{ systemInfo.timezone }}</span>\n </div>\n </div>\n </div>\n </NTabPane>\n </NTabs>\n </NDrawerContent>\n </NDrawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch, onUnmounted } from \"vue\";\nimport {\n NDrawer,\n NDrawerContent,\n NTabs,\n NTabPane,\n NRadioGroup,\n NRadioButton,\n NColorPicker,\n NSwitch,\n NButton,\n NSlider,\n NInput,\n useMessage,\n useDialog,\n} from \"naive-ui\";\nimport {\n sanitizeSettingsPatch,\n useSettingsStore,\n} from \"../../stores/settings\";\nimport { DEFAULT_SETTINGS } from \"../../constants\";\nimport { COLOR_SWATCHES, LAYOUT_MODE_OPTIONS, THEME_PRESETS } from \"./data\";\nimport type { ThemePreset } from \"../../types\";\nimport type { ThemeMode } from \"@robot-admin/theme\";\n\n// ============ 数据定义 ============\n\nconst message = useMessage();\nconst dialog = useDialog();\nconst settingsStore = useSettingsStore();\nconst visible = defineModel<boolean>(\"show\", { default: false });\nconst activeTab = ref(\"appearance\");\n\n// 功能开关\nconst grayMode = ref(false);\nconst colorWeakMode = ref(false);\nconst watermarkEnabled = ref(false);\nconst watermarkText = ref(\"Robot Admin\");\n\n// 水印元素引用\nlet watermarkEl: HTMLElement | null = null;\n\n// 创建水印函数\nconst createWatermark = (text: string) => {\n // 移除旧水印\n if (watermarkEl) {\n document.body.removeChild(watermarkEl);\n watermarkEl = null;\n }\n\n // 创建 canvas\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n message.error(\"当前浏览器无法创建水印画布\");\n return;\n }\n canvas.width = 200;\n canvas.height = 150;\n\n ctx.font = \"14px Microsoft JhengHei\";\n ctx.fillStyle = \"rgba(120, 120, 120, 0.15)\";\n ctx.rotate((-20 * Math.PI) / 180);\n ctx.fillText(text, 0, 80);\n\n // 创建水印容器\n watermarkEl = document.createElement(\"div\");\n watermarkEl.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n background-image: url(${canvas.toDataURL()});\n background-repeat: repeat;\n z-index: 9999;\n user-select: none;\n `;\n\n document.body.appendChild(watermarkEl);\n};\n\n// 移除水印函数\nconst removeWatermark = () => {\n if (watermarkEl) {\n document.body.removeChild(watermarkEl);\n watermarkEl = null;\n }\n};\n\n// 监听灰色模式\nwatch(grayMode, (val) => {\n document.documentElement.classList.toggle(\"gray-mode\", val);\n});\n\n// 监听色弱模式\nwatch(colorWeakMode, (val) => {\n document.documentElement.classList.toggle(\"color-weak-mode\", val);\n});\n\n// 监听水印开关\nwatch(watermarkEnabled, (val) => {\n if (val) {\n createWatermark(watermarkText.value);\n } else {\n removeWatermark();\n }\n});\n\n// 监听水印文本\nwatch(watermarkText, (val) => {\n if (watermarkEnabled.value) {\n createWatermark(val);\n }\n});\n\n// 获取系统信息\nconst systemInfo = computed(() => {\n if (typeof navigator === \"undefined\" || typeof window === \"undefined\") {\n return {\n browser: \"Unknown\",\n os: \"Unknown\",\n resolution: \"Unknown\",\n pixelRatio: \"Unknown\",\n language: \"Unknown\",\n timezone: \"Unknown\",\n };\n }\n\n const ua = navigator.userAgent;\n let browser = \"Unknown\";\n let os = \"Unknown\";\n\n // 检测浏览器\n if (ua.includes(\"Chrome\")) browser = \"Chrome\";\n else if (ua.includes(\"Firefox\")) browser = \"Firefox\";\n else if (ua.includes(\"Safari\")) browser = \"Safari\";\n else if (ua.includes(\"Edge\")) browser = \"Edge\";\n\n // 检测操作系统\n if (ua.includes(\"Windows\")) os = \"Windows\";\n else if (ua.includes(\"Mac\")) os = \"macOS\";\n else if (ua.includes(\"Linux\")) os = \"Linux\";\n else if (ua.includes(\"Android\")) os = \"Android\";\n else if (ua.includes(\"iOS\")) os = \"iOS\";\n\n return {\n browser,\n os,\n resolution: `${window.screen.width} × ${window.screen.height}`,\n pixelRatio: window.devicePixelRatio + \"x\",\n language: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n };\n});\n\n// 组件卸载时清理水印\nonUnmounted(() => {\n removeWatermark();\n});\n\n// 处理布局切换 - 阻止抽屉关闭\nconst handleLayoutChange = (value: string, disabled?: boolean) => {\n if (disabled) return;\n settingsStore.layoutMode = value as any;\n};\n\n// ============ 方法 ============\n\n/**\n * 判断是否是当前预设\n */\nconst isCurrentPreset = (preset: ThemePreset) => {\n return settingsStore.primaryColor === preset.primaryColor;\n};\n\n/**\n * 应用预设方案\n */\nconst handleApplyPreset = (preset: ThemePreset) => {\n settingsStore.applyPreset(preset);\n message.success(`已应用「${preset.name}」主题方案`);\n};\n\nconst handleThemeModeChange = async (mode: ThemeMode) => {\n try {\n await settingsStore.updateThemeMode(mode);\n } catch {\n message.error(\"主题模式切换失败\");\n }\n};\n\n/**\n * 恢复外观默认设置\n */\nconst handleResetAppearance = () => {\n settingsStore.themeMode = DEFAULT_SETTINGS.themeMode;\n settingsStore.primaryColor = DEFAULT_SETTINGS.primaryColor;\n settingsStore.borderRadius = DEFAULT_SETTINGS.borderRadius;\n settingsStore.transitionType = DEFAULT_SETTINGS.transitionType;\n settingsStore.enableTransition = DEFAULT_SETTINGS.enableTransition;\n message.success(\"已恢复外观默认设置\");\n};\n\n/**\n * 恢复布局默认设置\n */\nconst handleResetLayout = () => {\n settingsStore.layoutMode = DEFAULT_SETTINGS.layoutMode;\n settingsStore.menuExpandMode = DEFAULT_SETTINGS.menuExpandMode;\n settingsStore.fixedHeader = DEFAULT_SETTINGS.fixedHeader;\n settingsStore.showBreadcrumb = DEFAULT_SETTINGS.showBreadcrumb;\n settingsStore.showBreadcrumbIcon = DEFAULT_SETTINGS.showBreadcrumbIcon;\n settingsStore.showTagsView = DEFAULT_SETTINGS.showTagsView;\n settingsStore.tagsViewHeight = DEFAULT_SETTINGS.tagsViewHeight;\n settingsStore.tagsViewStyle = DEFAULT_SETTINGS.tagsViewStyle;\n settingsStore.showFooter = DEFAULT_SETTINGS.showFooter;\n settingsStore.sidebarWidth = DEFAULT_SETTINGS.sidebarWidth;\n settingsStore.sidebarCollapsedWidth = DEFAULT_SETTINGS.sidebarCollapsedWidth;\n settingsStore.headerHeight = DEFAULT_SETTINGS.headerHeight;\n message.success(\"已恢复布局默认设置\");\n};\n\n/**\n * 重置配置\n */\nconst handleReset = () => {\n dialog.warning({\n title: \"确认重置\",\n content: \"确定要恢复默认配置吗?此操作不可撤销。\",\n positiveText: \"确认\",\n negativeText: \"取消\",\n onPositiveClick: () => {\n settingsStore.resetSettings();\n message.success(\"已恢复默认配置\");\n },\n });\n};\n\n/**\n * 清除缓存\n */\nconst handleClearCache = () => {\n dialog.warning({\n title: \"确认清除\",\n content: \"确定要清除浏览器缓存吗?\",\n positiveText: \"确认\",\n negativeText: \"取消\",\n onPositiveClick: () => {\n // 清除 localStorage\n const keysToKeep = [\"theme-mode\", \"robot-admin-settings\"];\n const allKeys = Object.keys(localStorage);\n allKeys.forEach((key) => {\n if (!keysToKeep.includes(key)) {\n localStorage.removeItem(key);\n }\n });\n // 清除 sessionStorage\n sessionStorage.clear();\n message.success(\"缓存已清除\");\n },\n });\n};\n\n/**\n * 重新加载页面\n */\nconst handleReload = () => {\n location.reload();\n};\n\n/**\n * 导出配置\n */\nconst handleExportConfig = () => {\n const config = {\n settings: settingsStore.settingsState,\n gray: grayMode.value,\n colorWeak: colorWeakMode.value,\n watermark: {\n enabled: watermarkEnabled.value,\n text: watermarkText.value,\n },\n exportTime: new Date().toISOString(),\n };\n const blob = new Blob([JSON.stringify(config, null, 2)], {\n type: \"application/json\",\n });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `robot-admin-config-${Date.now()}.json`;\n a.click();\n URL.revokeObjectURL(url);\n message.success(\"配置已导出\");\n};\n\n/**\n * 导入配置\n */\nconst handleImportConfig = () => {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.accept = \"application/json\";\n input.onchange = async (e: Event) => {\n const file = (e.target as HTMLInputElement).files?.[0];\n if (!file) return;\n\n try {\n const text = await file.text();\n const config: unknown = JSON.parse(text);\n if (!config || typeof config !== \"object\" || Array.isArray(config)) {\n throw new TypeError(\"配置文件根节点必须是对象\");\n }\n const imported = config as Record<string, unknown>;\n\n // 应用配置\n if (imported.settings !== undefined) {\n const settingsPatch = sanitizeSettingsPatch(imported.settings);\n if (settingsPatch.themeMode !== undefined) {\n await settingsStore.updateThemeMode(settingsPatch.themeMode);\n delete settingsPatch.themeMode;\n }\n settingsStore.$patch(settingsPatch);\n }\n if (imported.gray !== undefined) {\n if (typeof imported.gray !== \"boolean\") throw new TypeError(\"gray\");\n grayMode.value = imported.gray;\n }\n if (imported.colorWeak !== undefined) {\n if (typeof imported.colorWeak !== \"boolean\")\n throw new TypeError(\"colorWeak\");\n colorWeakMode.value = imported.colorWeak;\n }\n if (imported.watermark !== undefined) {\n if (\n !imported.watermark ||\n typeof imported.watermark !== \"object\" ||\n Array.isArray(imported.watermark)\n ) {\n throw new TypeError(\"watermark\");\n }\n const watermark = imported.watermark as Record<string, unknown>;\n if (\n typeof watermark.enabled !== \"boolean\" ||\n typeof watermark.text !== \"string\" ||\n watermark.text.length > 200\n ) {\n throw new TypeError(\"watermark\");\n }\n watermarkEnabled.value = watermark.enabled;\n watermarkText.value = watermark.text;\n }\n\n message.success(\"配置已导入\");\n } catch (error) {\n message.error(\"配置文件格式错误\");\n }\n };\n input.click();\n};\n</script>\n\n<style scoped lang=\"scss\">\n@use \"../../styles/settings.scss\" as *;\n</style>\n","<!--\r\n * @robot-admin/layout - BrandLogo\r\n *\r\n * 品牌 Logo 组件\r\n * 从 LayoutContext.brand 读取配置,自动渲染 Logo + 品牌名\r\n * 支持折叠状态、横向/纵向布局\r\n -->\r\n<template>\r\n <div\r\n class=\"brand-logo\"\r\n :class=\"{\r\n 'brand-logo--collapsed': collapsed,\r\n 'brand-logo--horizontal': variant === 'horizontal',\r\n }\"\r\n >\r\n <div class=\"brand-logo__icon\">\r\n <div class=\"brand-logo__glow\"></div>\r\n <video\r\n v-if=\"brandConfig.logoType === 'video' && brandConfig.logoSrc\"\r\n :src=\"brandConfig.logoSrc\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n autoplay\r\n loop\r\n muted\r\n playsinline\r\n class=\"brand-logo__media\"\r\n />\r\n <img\r\n v-else-if=\"brandConfig.logoSrc\"\r\n :src=\"brandConfig.logoSrc\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n class=\"brand-logo__media\"\r\n alt=\"Logo\"\r\n />\r\n </div>\r\n <template v-if=\"showName && !collapsed\">\r\n <div class=\"brand-logo__name\">\r\n <span class=\"brand-logo__title\">{{ brandConfig.name }}</span>\r\n <span v-if=\"brandConfig.subtitle\" class=\"brand-logo__subtitle\">{{\r\n brandConfig.subtitle\r\n }}</span>\r\n </div>\r\n <div v-if=\"showDivider\" class=\"brand-logo__divider\"></div>\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n useLayoutContext,\r\n DEFAULT_BRAND_CONFIG,\r\n} from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"BrandLogo\" });\r\n\r\nwithDefaults(\r\n defineProps<{\r\n /** 是否显示品牌名称 */\r\n showName?: boolean;\r\n /** 是否显示分隔线 */\r\n showDivider?: boolean;\r\n /** Logo 尺寸 */\r\n size?: number;\r\n /** 是否折叠状态 */\r\n collapsed?: boolean;\r\n /** 布局方向 */\r\n variant?: \"vertical\" | \"horizontal\";\r\n }>(),\r\n {\r\n showName: false,\r\n showDivider: false,\r\n size: 36,\r\n collapsed: false,\r\n variant: \"horizontal\",\r\n },\r\n);\r\n\r\nconst ctx = useLayoutContext();\r\nconst brandConfig = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\r\n</script>\r\n\r\n<style scoped>\r\n.brand-logo {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.brand-logo--collapsed {\r\n justify-content: center;\r\n}\r\n\r\n.brand-logo__icon {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex-shrink: 0;\r\n}\r\n\r\n.brand-logo__glow {\r\n position: absolute;\r\n inset: -4px;\r\n border-radius: 50%;\r\n background: radial-gradient(\r\n circle,\r\n rgba(99, 102, 241, 0.15) 0%,\r\n transparent 70%\r\n );\r\n pointer-events: none;\r\n}\r\n\r\n.brand-logo__media {\r\n border-radius: 8px;\r\n object-fit: contain;\r\n}\r\n\r\n.brand-logo__name {\r\n display: flex;\r\n flex-direction: column;\r\n line-height: 1.2;\r\n white-space: nowrap;\r\n}\r\n\r\n.brand-logo__title {\r\n font-size: 15px;\r\n font-weight: 700;\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n background-clip: text;\r\n -webkit-background-clip: text;\r\n -webkit-text-fill-color: transparent;\r\n}\r\n\r\n.brand-logo__subtitle {\r\n font-size: 10px;\r\n opacity: 0.6;\r\n letter-spacing: 0.5px;\r\n}\r\n\r\n.brand-logo__divider {\r\n width: 1px;\r\n height: 24px;\r\n margin: 0 6px;\r\n background: linear-gradient(\r\n 180deg,\r\n transparent 0%,\r\n rgba(128, 128, 128, 0.3) 50%,\r\n transparent 100%\r\n );\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - BrandLogo\r\n *\r\n * 品牌 Logo 组件\r\n * 从 LayoutContext.brand 读取配置,自动渲染 Logo + 品牌名\r\n * 支持折叠状态、横向/纵向布局\r\n -->\r\n<template>\r\n <div\r\n class=\"brand-logo\"\r\n :class=\"{\r\n 'brand-logo--collapsed': collapsed,\r\n 'brand-logo--horizontal': variant === 'horizontal',\r\n }\"\r\n >\r\n <div class=\"brand-logo__icon\">\r\n <div class=\"brand-logo__glow\"></div>\r\n <video\r\n v-if=\"brandConfig.logoType === 'video' && brandConfig.logoSrc\"\r\n :src=\"brandConfig.logoSrc\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n autoplay\r\n loop\r\n muted\r\n playsinline\r\n class=\"brand-logo__media\"\r\n />\r\n <img\r\n v-else-if=\"brandConfig.logoSrc\"\r\n :src=\"brandConfig.logoSrc\"\r\n :width=\"size\"\r\n :height=\"size\"\r\n class=\"brand-logo__media\"\r\n alt=\"Logo\"\r\n />\r\n </div>\r\n <template v-if=\"showName && !collapsed\">\r\n <div class=\"brand-logo__name\">\r\n <span class=\"brand-logo__title\">{{ brandConfig.name }}</span>\r\n <span v-if=\"brandConfig.subtitle\" class=\"brand-logo__subtitle\">{{\r\n brandConfig.subtitle\r\n }}</span>\r\n </div>\r\n <div v-if=\"showDivider\" class=\"brand-logo__divider\"></div>\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport {\r\n useLayoutContext,\r\n DEFAULT_BRAND_CONFIG,\r\n} from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"BrandLogo\" });\r\n\r\nwithDefaults(\r\n defineProps<{\r\n /** 是否显示品牌名称 */\r\n showName?: boolean;\r\n /** 是否显示分隔线 */\r\n showDivider?: boolean;\r\n /** Logo 尺寸 */\r\n size?: number;\r\n /** 是否折叠状态 */\r\n collapsed?: boolean;\r\n /** 布局方向 */\r\n variant?: \"vertical\" | \"horizontal\";\r\n }>(),\r\n {\r\n showName: false,\r\n showDivider: false,\r\n size: 36,\r\n collapsed: false,\r\n variant: \"horizontal\",\r\n },\r\n);\r\n\r\nconst ctx = useLayoutContext();\r\nconst brandConfig = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\r\n</script>\r\n\r\n<style scoped>\r\n.brand-logo {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.brand-logo--collapsed {\r\n justify-content: center;\r\n}\r\n\r\n.brand-logo__icon {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex-shrink: 0;\r\n}\r\n\r\n.brand-logo__glow {\r\n position: absolute;\r\n inset: -4px;\r\n border-radius: 50%;\r\n background: radial-gradient(\r\n circle,\r\n rgba(99, 102, 241, 0.15) 0%,\r\n transparent 70%\r\n );\r\n pointer-events: none;\r\n}\r\n\r\n.brand-logo__media {\r\n border-radius: 8px;\r\n object-fit: contain;\r\n}\r\n\r\n.brand-logo__name {\r\n display: flex;\r\n flex-direction: column;\r\n line-height: 1.2;\r\n white-space: nowrap;\r\n}\r\n\r\n.brand-logo__title {\r\n font-size: 15px;\r\n font-weight: 700;\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n background-clip: text;\r\n -webkit-background-clip: text;\r\n -webkit-text-fill-color: transparent;\r\n}\r\n\r\n.brand-logo__subtitle {\r\n font-size: 10px;\r\n opacity: 0.6;\r\n letter-spacing: 0.5px;\r\n}\r\n\r\n.brand-logo__divider {\r\n width: 1px;\r\n height: 24px;\r\n margin: 0 6px;\r\n background: linear-gradient(\r\n 180deg,\r\n transparent 0%,\r\n rgba(128, 128, 128, 0.3) 50%,\r\n transparent 100%\r\n );\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - IconMenu\r\n *\r\n * 一级图标菜单\r\n * 用于 Mix / MixTop 布局的左侧一级菜单栏\r\n * 显示图标 + 标签,支持激活状态\r\n -->\r\n<template>\r\n <div class=\"icon-menu\">\r\n <div\r\n v-for=\"item in menus\"\r\n :key=\"item.path\"\r\n class=\"icon-menu__item\"\r\n :class=\"{ 'icon-menu__item--active': active === item.path }\"\r\n @click=\"emit('click', item)\"\r\n >\r\n <div class=\"icon-menu__icon\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && item.meta?.icon\"\r\n :name=\"item.meta.icon\"\r\n :size=\"20\"\r\n />\r\n <span v-else class=\"icon-menu__fallback\">\r\n {{ item.meta?.title?.charAt(0) || \"?\" }}\r\n </span>\r\n </div>\r\n <div class=\"icon-menu__label\">\r\n {{ item.meta?.title }}\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"IconMenu\" });\r\n\r\ndefineProps<{\r\n /** 菜单数据 */\r\n menus: MenuOptions[];\r\n /** 当前激活的菜单路径 */\r\n active?: string;\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n click: [item: MenuOptions];\r\n}>();\r\n\r\nconst ctx = useLayoutContext();\r\n</script>\r\n\r\n<style scoped>\r\n.icon-menu {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n gap: 2px;\r\n padding: 4px 0;\r\n width: 100%;\r\n}\r\n\r\n.icon-menu__item {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 4px;\r\n width: calc(100% - 12px);\r\n padding: 10px 4px 8px;\r\n margin: 0 6px;\r\n border-radius: 10px;\r\n cursor: pointer;\r\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\r\n position: relative;\r\n opacity: 0.7;\r\n}\r\n\r\n.icon-menu__item:hover {\r\n opacity: 1;\r\n background: rgba(99, 102, 241, 0.08);\r\n transform: translateY(-1px);\r\n}\r\n\r\n.icon-menu__item--active {\r\n opacity: 1;\r\n background: linear-gradient(\r\n 135deg,\r\n rgba(99, 102, 241, 0.15) 0%,\r\n rgba(139, 92, 246, 0.1) 100%\r\n );\r\n box-shadow: 0 2px 8px rgba(99, 102, 241, 0.2);\r\n}\r\n\r\n.icon-menu__item--active::before {\r\n content: \"\";\r\n position: absolute;\r\n left: 0;\r\n top: 50%;\r\n transform: translateY(-50%);\r\n width: 3px;\r\n height: 60%;\r\n border-radius: 0 3px 3px 0;\r\n background: linear-gradient(180deg, #667eea, #764ba2);\r\n}\r\n\r\n.icon-menu__icon {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 28px;\r\n height: 28px;\r\n font-size: 20px;\r\n}\r\n\r\n.icon-menu__fallback {\r\n font-size: 14px;\r\n font-weight: 600;\r\n width: 28px;\r\n height: 28px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n border-radius: 8px;\r\n background: rgba(128, 128, 128, 0.1);\r\n}\r\n\r\n.icon-menu__label {\r\n font-size: 11px;\r\n font-weight: 500;\r\n line-height: 1.2;\r\n text-align: center;\r\n max-width: 100%;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - IconMenu\r\n *\r\n * 一级图标菜单\r\n * 用于 Mix / MixTop 布局的左侧一级菜单栏\r\n * 显示图标 + 标签,支持激活状态\r\n -->\r\n<template>\r\n <div class=\"icon-menu\">\r\n <div\r\n v-for=\"item in menus\"\r\n :key=\"item.path\"\r\n class=\"icon-menu__item\"\r\n :class=\"{ 'icon-menu__item--active': active === item.path }\"\r\n @click=\"emit('click', item)\"\r\n >\r\n <div class=\"icon-menu__icon\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && item.meta?.icon\"\r\n :name=\"item.meta.icon\"\r\n :size=\"20\"\r\n />\r\n <span v-else class=\"icon-menu__fallback\">\r\n {{ item.meta?.title?.charAt(0) || \"?\" }}\r\n </span>\r\n </div>\r\n <div class=\"icon-menu__label\">\r\n {{ item.meta?.title }}\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"IconMenu\" });\r\n\r\ndefineProps<{\r\n /** 菜单数据 */\r\n menus: MenuOptions[];\r\n /** 当前激活的菜单路径 */\r\n active?: string;\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n click: [item: MenuOptions];\r\n}>();\r\n\r\nconst ctx = useLayoutContext();\r\n</script>\r\n\r\n<style scoped>\r\n.icon-menu {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n gap: 2px;\r\n padding: 4px 0;\r\n width: 100%;\r\n}\r\n\r\n.icon-menu__item {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 4px;\r\n width: calc(100% - 12px);\r\n padding: 10px 4px 8px;\r\n margin: 0 6px;\r\n border-radius: 10px;\r\n cursor: pointer;\r\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\r\n position: relative;\r\n opacity: 0.7;\r\n}\r\n\r\n.icon-menu__item:hover {\r\n opacity: 1;\r\n background: rgba(99, 102, 241, 0.08);\r\n transform: translateY(-1px);\r\n}\r\n\r\n.icon-menu__item--active {\r\n opacity: 1;\r\n background: linear-gradient(\r\n 135deg,\r\n rgba(99, 102, 241, 0.15) 0%,\r\n rgba(139, 92, 246, 0.1) 100%\r\n );\r\n box-shadow: 0 2px 8px rgba(99, 102, 241, 0.2);\r\n}\r\n\r\n.icon-menu__item--active::before {\r\n content: \"\";\r\n position: absolute;\r\n left: 0;\r\n top: 50%;\r\n transform: translateY(-50%);\r\n width: 3px;\r\n height: 60%;\r\n border-radius: 0 3px 3px 0;\r\n background: linear-gradient(180deg, #667eea, #764ba2);\r\n}\r\n\r\n.icon-menu__icon {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 28px;\r\n height: 28px;\r\n font-size: 20px;\r\n}\r\n\r\n.icon-menu__fallback {\r\n font-size: 14px;\r\n font-weight: 600;\r\n width: 28px;\r\n height: 28px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n border-radius: 8px;\r\n background: rgba(128, 128, 128, 0.1);\r\n}\r\n\r\n.icon-menu__label {\r\n font-size: 11px;\r\n font-weight: 500;\r\n line-height: 1.2;\r\n text-align: center;\r\n max-width: 100%;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - FloatingMenu\r\n *\r\n * 悬浮二级菜单面板\r\n * 用于 MixLayout 的悬浮二级菜单(点击一级菜单后展开)\r\n -->\r\n<template>\r\n <Transition name=\"slide-fade\">\r\n <div\r\n v-if=\"show && menuItem\"\r\n class=\"floating-menu\"\n :class=\"[ctx.isDark.value ? 'dark-theme' : 'light-theme']\"\n role=\"navigation\"\n aria-label=\"二级功能导航\"\n >\r\n <!-- 标题 -->\r\n <div class=\"floating-menu__header\">\r\n <div class=\"floating-menu__brand\">\r\n <span class=\"floating-menu__brand-title\">{{ brand.name }}</span>\r\n <span class=\"floating-menu__brand-subtitle\">{{\r\n brand.subtitle\r\n }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- 菜单列表 -->\r\n <div class=\"floating-menu__list\">\r\n <template v-for=\"child in menuItem.children\" :key=\"child.path\">\r\n <!-- 有子菜单的项 -->\r\n <div\r\n v-if=\"child.children && child.children.length > 0\"\r\n class=\"floating-menu__group\"\r\n >\r\n <div class=\"floating-menu__group-title\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && child.meta?.icon\"\r\n :name=\"child.meta.icon\"\r\n :size=\"16\"\r\n />\r\n <span>{{ child.meta?.title }}</span>\r\n </div>\r\n <div\r\n v-for=\"subChild in child.children\"\r\n :key=\"subChild.path\"\r\n class=\"floating-menu__item floating-menu__item--sub\"\n :class=\"{ active: isActive?.(subChild.path) }\"\n role=\"link\"\n tabindex=\"0\"\n @click=\"emit('click', subChild)\"\n @keydown.enter=\"emit('click', subChild)\"\n @keydown.space.prevent=\"emit('click', subChild)\"\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && subChild.meta?.icon\"\r\n :name=\"subChild.meta.icon\"\r\n :size=\"16\"\r\n />\r\n <span class=\"floating-menu__item-text\">{{\r\n subChild.meta?.title\r\n }}</span>\r\n </div>\r\n </div>\r\n <!-- 无子菜单的项 -->\r\n <div\r\n v-else\r\n class=\"floating-menu__item\"\n :class=\"{ active: isActive?.(child.path) }\"\n role=\"link\"\n tabindex=\"0\"\n @click=\"emit('click', child)\"\n @keydown.enter=\"emit('click', child)\"\n @keydown.space.prevent=\"emit('click', child)\"\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && child.meta?.icon\"\r\n :name=\"child.meta.icon\"\r\n :size=\"18\"\r\n />\r\n <span class=\"floating-menu__item-text\">{{\r\n child.meta?.title\r\n }}</span>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n </Transition>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport {\r\n useLayoutContext,\r\n DEFAULT_BRAND_CONFIG,\r\n} from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"FloatingMenu\" });\r\n\r\ndefineProps<{\r\n /** 是否显示 */\r\n show: boolean;\r\n /** 当前悬浮的一级菜单项(包含 children) */\r\n menuItem: MenuOptions | null;\r\n /** 判断菜单项是否激活 */\r\n isActive?: (path: string | undefined) => boolean;\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n click: [item: MenuOptions];\r\n}>();\r\n\r\nconst ctx = useLayoutContext();\r\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\r\n</script>\r\n\r\n<style scoped>\r\n.floating-menu {\r\n position: absolute;\r\n top: 0;\r\n left: 100%;\r\n width: 240px;\r\n height: 100%;\r\n z-index: 100;\r\n overflow-y: auto;\r\n border-right: 1px solid rgba(128, 128, 128, 0.1);\r\n backdrop-filter: blur(20px);\r\n -webkit-backdrop-filter: blur(20px);\r\n}\r\n\r\n.floating-menu.light-theme {\r\n background: rgba(255, 255, 255, 0.95);\r\n box-shadow: 4px 0 24px rgba(0, 0, 0, 0.08);\r\n}\r\n\r\n.floating-menu.dark-theme {\r\n background: rgba(30, 30, 36, 0.95);\r\n box-shadow: 4px 0 24px rgba(0, 0, 0, 0.3);\r\n}\r\n\r\n.floating-menu__header {\r\n padding: 16px 20px 12px;\r\n border-bottom: 1px solid rgba(128, 128, 128, 0.1);\r\n}\r\n\r\n.floating-menu__brand-title {\r\n font-size: 14px;\r\n font-weight: 700;\r\n background: linear-gradient(135deg, #667eea, #764ba2);\r\n background-clip: text;\r\n -webkit-background-clip: text;\r\n -webkit-text-fill-color: transparent;\r\n}\r\n\r\n.floating-menu__brand-subtitle {\r\n display: block;\r\n font-size: 11px;\r\n opacity: 0.5;\r\n margin-top: 2px;\r\n}\r\n\r\n.floating-menu__list {\r\n padding: 8px 12px;\r\n}\r\n\r\n.floating-menu__group {\r\n margin-bottom: 8px;\r\n}\r\n\r\n.floating-menu__group-title {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n padding: 8px 8px 4px;\r\n font-size: 12px;\r\n font-weight: 600;\r\n opacity: 0.5;\r\n text-transform: uppercase;\r\n letter-spacing: 0.5px;\r\n}\r\n\r\n.floating-menu__item {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n padding: 10px 12px;\r\n margin: 2px 0;\r\n border-radius: 8px;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n font-size: 14px;\r\n}\r\n\r\n.floating-menu__item:hover {\n background: rgba(99, 102, 241, 0.08);\r\n}\n\n.floating-menu__item:focus-visible {\n outline: 2px solid #667eea;\n outline-offset: 2px;\n}\n\r\n.floating-menu__item.active {\r\n background: linear-gradient(\r\n 135deg,\r\n rgba(99, 102, 241, 0.12),\r\n rgba(139, 92, 246, 0.08)\r\n );\r\n color: #667eea;\r\n}\r\n\r\n.floating-menu__item--sub {\r\n padding-left: 20px;\r\n font-size: 13px;\r\n}\r\n\r\n.floating-menu__item-text {\r\n flex: 1;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n/* Transition */\r\n.slide-fade-enter-active,\r\n.slide-fade-leave-active {\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.slide-fade-enter-from {\r\n opacity: 0;\r\n transform: translateX(-12px);\r\n}\r\n\r\n.slide-fade-leave-to {\r\n opacity: 0;\r\n transform: translateX(-12px);\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - FloatingMenu\r\n *\r\n * 悬浮二级菜单面板\r\n * 用于 MixLayout 的悬浮二级菜单(点击一级菜单后展开)\r\n -->\r\n<template>\r\n <Transition name=\"slide-fade\">\r\n <div\r\n v-if=\"show && menuItem\"\r\n class=\"floating-menu\"\n :class=\"[ctx.isDark.value ? 'dark-theme' : 'light-theme']\"\n role=\"navigation\"\n aria-label=\"二级功能导航\"\n >\r\n <!-- 标题 -->\r\n <div class=\"floating-menu__header\">\r\n <div class=\"floating-menu__brand\">\r\n <span class=\"floating-menu__brand-title\">{{ brand.name }}</span>\r\n <span class=\"floating-menu__brand-subtitle\">{{\r\n brand.subtitle\r\n }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- 菜单列表 -->\r\n <div class=\"floating-menu__list\">\r\n <template v-for=\"child in menuItem.children\" :key=\"child.path\">\r\n <!-- 有子菜单的项 -->\r\n <div\r\n v-if=\"child.children && child.children.length > 0\"\r\n class=\"floating-menu__group\"\r\n >\r\n <div class=\"floating-menu__group-title\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && child.meta?.icon\"\r\n :name=\"child.meta.icon\"\r\n :size=\"16\"\r\n />\r\n <span>{{ child.meta?.title }}</span>\r\n </div>\r\n <div\r\n v-for=\"subChild in child.children\"\r\n :key=\"subChild.path\"\r\n class=\"floating-menu__item floating-menu__item--sub\"\n :class=\"{ active: isActive?.(subChild.path) }\"\n role=\"link\"\n tabindex=\"0\"\n @click=\"emit('click', subChild)\"\n @keydown.enter=\"emit('click', subChild)\"\n @keydown.space.prevent=\"emit('click', subChild)\"\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && subChild.meta?.icon\"\r\n :name=\"subChild.meta.icon\"\r\n :size=\"16\"\r\n />\r\n <span class=\"floating-menu__item-text\">{{\r\n subChild.meta?.title\r\n }}</span>\r\n </div>\r\n </div>\r\n <!-- 无子菜单的项 -->\r\n <div\r\n v-else\r\n class=\"floating-menu__item\"\n :class=\"{ active: isActive?.(child.path) }\"\n role=\"link\"\n tabindex=\"0\"\n @click=\"emit('click', child)\"\n @keydown.enter=\"emit('click', child)\"\n @keydown.space.prevent=\"emit('click', child)\"\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && child.meta?.icon\"\r\n :name=\"child.meta.icon\"\r\n :size=\"18\"\r\n />\r\n <span class=\"floating-menu__item-text\">{{\r\n child.meta?.title\r\n }}</span>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n </Transition>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport {\r\n useLayoutContext,\r\n DEFAULT_BRAND_CONFIG,\r\n} from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"FloatingMenu\" });\r\n\r\ndefineProps<{\r\n /** 是否显示 */\r\n show: boolean;\r\n /** 当前悬浮的一级菜单项(包含 children) */\r\n menuItem: MenuOptions | null;\r\n /** 判断菜单项是否激活 */\r\n isActive?: (path: string | undefined) => boolean;\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n click: [item: MenuOptions];\r\n}>();\r\n\r\nconst ctx = useLayoutContext();\r\nconst brand = { ...DEFAULT_BRAND_CONFIG, ...ctx.brand };\r\n</script>\r\n\r\n<style scoped>\r\n.floating-menu {\r\n position: absolute;\r\n top: 0;\r\n left: 100%;\r\n width: 240px;\r\n height: 100%;\r\n z-index: 100;\r\n overflow-y: auto;\r\n border-right: 1px solid rgba(128, 128, 128, 0.1);\r\n backdrop-filter: blur(20px);\r\n -webkit-backdrop-filter: blur(20px);\r\n}\r\n\r\n.floating-menu.light-theme {\r\n background: rgba(255, 255, 255, 0.95);\r\n box-shadow: 4px 0 24px rgba(0, 0, 0, 0.08);\r\n}\r\n\r\n.floating-menu.dark-theme {\r\n background: rgba(30, 30, 36, 0.95);\r\n box-shadow: 4px 0 24px rgba(0, 0, 0, 0.3);\r\n}\r\n\r\n.floating-menu__header {\r\n padding: 16px 20px 12px;\r\n border-bottom: 1px solid rgba(128, 128, 128, 0.1);\r\n}\r\n\r\n.floating-menu__brand-title {\r\n font-size: 14px;\r\n font-weight: 700;\r\n background: linear-gradient(135deg, #667eea, #764ba2);\r\n background-clip: text;\r\n -webkit-background-clip: text;\r\n -webkit-text-fill-color: transparent;\r\n}\r\n\r\n.floating-menu__brand-subtitle {\r\n display: block;\r\n font-size: 11px;\r\n opacity: 0.5;\r\n margin-top: 2px;\r\n}\r\n\r\n.floating-menu__list {\r\n padding: 8px 12px;\r\n}\r\n\r\n.floating-menu__group {\r\n margin-bottom: 8px;\r\n}\r\n\r\n.floating-menu__group-title {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n padding: 8px 8px 4px;\r\n font-size: 12px;\r\n font-weight: 600;\r\n opacity: 0.5;\r\n text-transform: uppercase;\r\n letter-spacing: 0.5px;\r\n}\r\n\r\n.floating-menu__item {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n padding: 10px 12px;\r\n margin: 2px 0;\r\n border-radius: 8px;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n font-size: 14px;\r\n}\r\n\r\n.floating-menu__item:hover {\n background: rgba(99, 102, 241, 0.08);\r\n}\n\n.floating-menu__item:focus-visible {\n outline: 2px solid #667eea;\n outline-offset: 2px;\n}\n\r\n.floating-menu__item.active {\r\n background: linear-gradient(\r\n 135deg,\r\n rgba(99, 102, 241, 0.12),\r\n rgba(139, 92, 246, 0.08)\r\n );\r\n color: #667eea;\r\n}\r\n\r\n.floating-menu__item--sub {\r\n padding-left: 20px;\r\n font-size: 13px;\r\n}\r\n\r\n.floating-menu__item-text {\r\n flex: 1;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n/* Transition */\r\n.slide-fade-enter-active,\r\n.slide-fade-leave-active {\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.slide-fade-enter-from {\r\n opacity: 0;\r\n transform: translateX(-12px);\r\n}\r\n\r\n.slide-fade-leave-to {\r\n opacity: 0;\r\n transform: translateX(-12px);\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - SideMenu\r\n *\r\n * 右侧二级菜单侧边栏\r\n * 用于 ReverseHorizontalMixLayout 的右侧二级菜单\r\n -->\r\n<template>\r\n <template v-if=\"menus.length > 0\">\r\n <!-- 折叠/展开按钮 -->\r\n <div\r\n class=\"side-menu-toggle\"\r\n :class=\"[\r\n ctx.isDark.value ? 'dark-theme' : 'light-theme',\r\n { active: !isCollapsed },\r\n ]\"\r\n @click=\"isCollapsed = !isCollapsed\"\r\n >\r\n <i\r\n :class=\"[\r\n 'toggle-icon',\r\n isCollapsed ? 'i-ri:side-bar-line' : 'i-ri:arrow-right-s-line',\r\n ]\"\r\n ></i>\r\n </div>\r\n\r\n <!-- 侧边栏内容 -->\r\n <Transition name=\"glass-slide\">\r\n <div\r\n v-if=\"!isCollapsed\"\r\n class=\"side-menu\"\r\n :class=\"[ctx.isDark.value ? 'dark-theme' : 'light-theme']\"\r\n >\r\n <!-- 标题 -->\r\n <div class=\"side-menu__header\">\r\n <div class=\"side-menu__header-icon\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && activeItem?.meta?.icon\"\r\n :name=\"activeItem.meta.icon\"\r\n :size=\"17\"\r\n />\r\n </div>\r\n <span class=\"side-menu__header-title\">{{\r\n activeItem?.meta?.title || \"菜单\"\r\n }}</span>\r\n </div>\r\n\r\n <!-- 菜单列表 -->\r\n <div class=\"side-menu__list\">\r\n <template v-for=\"child in menus\" :key=\"child.path\">\r\n <!-- 有子菜单 -->\r\n <div\r\n v-if=\"child.children && child.children.length > 0\"\r\n class=\"side-menu__group\"\r\n >\r\n <div class=\"side-menu__group-label\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && child.meta?.icon\"\r\n :name=\"child.meta.icon\"\r\n :size=\"14\"\r\n />\r\n <span>{{ child.meta?.title }}</span>\r\n </div>\r\n <div\r\n v-for=\"subChild in child.children\"\r\n :key=\"subChild.path\"\r\n class=\"side-menu__item\"\r\n :class=\"{ active: isActive?.(subChild.path) }\"\r\n @click=\"emit('click', subChild)\"\r\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && subChild.meta?.icon\"\r\n :name=\"subChild.meta.icon\"\r\n :size=\"15\"\r\n />\r\n <span class=\"side-menu__item-text\">{{\r\n subChild.meta?.title\r\n }}</span>\r\n </div>\r\n </div>\r\n <!-- 无子菜单 -->\r\n <div\r\n v-else\r\n class=\"side-menu__item\"\r\n :class=\"{ active: isActive?.(child.path) }\"\r\n @click=\"emit('click', child)\"\r\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && child.meta?.icon\"\r\n :name=\"child.meta.icon\"\r\n :size=\"16\"\r\n />\r\n <span class=\"side-menu__item-text\">{{ child.meta?.title }}</span>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n </Transition>\r\n </template>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"SideMenu\" });\r\n\r\ndefineProps<{\r\n /** 二级菜单列表 */\r\n menus: MenuOptions[];\r\n /** 当前一级菜单项(显示标题和图标) */\r\n activeItem?: MenuOptions;\r\n /** 判断菜单项是否激活 */\r\n isActive?: (path: string | undefined) => boolean;\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n click: [item: MenuOptions];\r\n}>();\r\n\r\nconst ctx = useLayoutContext();\r\nconst isCollapsed = ref(false);\r\n</script>\r\n\r\n<style scoped>\r\n.side-menu-toggle {\r\n position: absolute;\r\n right: 0;\r\n top: 50%;\r\n transform: translateY(-50%) translateX(50%);\r\n width: 24px;\r\n height: 48px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n border-radius: 12px;\r\n cursor: pointer;\r\n z-index: 10;\r\n transition: all 0.25s ease;\r\n font-size: 14px;\r\n}\r\n\r\n.side-menu-toggle.light-theme {\r\n background: rgba(255, 255, 255, 0.9);\r\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n.side-menu-toggle.dark-theme {\r\n background: rgba(40, 40, 46, 0.9);\r\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\r\n}\r\n\r\n.side-menu-toggle:hover {\r\n transform: translateY(-50%) translateX(50%) scale(1.1);\r\n}\r\n\r\n.side-menu {\r\n width: 100%;\r\n height: 100%;\r\n overflow-y: auto;\r\n padding: 0 8px;\r\n}\r\n\r\n.side-menu.light-theme {\r\n background: rgba(255, 255, 255, 0.95);\r\n border-left: 1px solid rgba(0, 0, 0, 0.06);\r\n}\r\n\r\n.side-menu.dark-theme {\r\n background: rgba(30, 30, 36, 0.95);\r\n border-left: 1px solid rgba(255, 255, 255, 0.06);\r\n}\r\n\r\n.side-menu__header {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 16px 12px 12px;\r\n border-bottom: 1px solid rgba(128, 128, 128, 0.1);\r\n margin-bottom: 8px;\r\n}\r\n\r\n.side-menu__header-icon {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 28px;\r\n height: 28px;\r\n border-radius: 8px;\r\n background: linear-gradient(\r\n 135deg,\r\n rgba(99, 102, 241, 0.12),\r\n rgba(139, 92, 246, 0.08)\r\n );\r\n}\r\n\r\n.side-menu__header-title {\r\n font-size: 14px;\r\n font-weight: 600;\r\n}\r\n\r\n.side-menu__list {\r\n padding: 4px 0;\r\n}\r\n\r\n.side-menu__group {\r\n margin-bottom: 8px;\r\n}\r\n\r\n.side-menu__group-label {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n padding: 6px 12px;\r\n font-size: 12px;\r\n font-weight: 600;\r\n opacity: 0.5;\r\n}\r\n\r\n.side-menu__item {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 9px 12px;\r\n margin: 2px 0;\r\n border-radius: 8px;\r\n cursor: pointer;\r\n font-size: 13px;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.side-menu__item:hover {\r\n background: rgba(99, 102, 241, 0.06);\r\n}\r\n\r\n.side-menu__item.active {\r\n background: linear-gradient(\r\n 135deg,\r\n rgba(99, 102, 241, 0.1),\r\n rgba(139, 92, 246, 0.06)\r\n );\r\n color: #667eea;\r\n font-weight: 500;\r\n}\r\n\r\n.side-menu__item-text {\r\n flex: 1;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n/* Transition */\r\n.glass-slide-enter-active,\r\n.glass-slide-leave-active {\r\n transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.glass-slide-enter-from,\r\n.glass-slide-leave-to {\r\n opacity: 0;\r\n transform: translateX(20px);\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - SideMenu\r\n *\r\n * 右侧二级菜单侧边栏\r\n * 用于 ReverseHorizontalMixLayout 的右侧二级菜单\r\n -->\r\n<template>\r\n <template v-if=\"menus.length > 0\">\r\n <!-- 折叠/展开按钮 -->\r\n <div\r\n class=\"side-menu-toggle\"\r\n :class=\"[\r\n ctx.isDark.value ? 'dark-theme' : 'light-theme',\r\n { active: !isCollapsed },\r\n ]\"\r\n @click=\"isCollapsed = !isCollapsed\"\r\n >\r\n <i\r\n :class=\"[\r\n 'toggle-icon',\r\n isCollapsed ? 'i-ri:side-bar-line' : 'i-ri:arrow-right-s-line',\r\n ]\"\r\n ></i>\r\n </div>\r\n\r\n <!-- 侧边栏内容 -->\r\n <Transition name=\"glass-slide\">\r\n <div\r\n v-if=\"!isCollapsed\"\r\n class=\"side-menu\"\r\n :class=\"[ctx.isDark.value ? 'dark-theme' : 'light-theme']\"\r\n >\r\n <!-- 标题 -->\r\n <div class=\"side-menu__header\">\r\n <div class=\"side-menu__header-icon\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && activeItem?.meta?.icon\"\r\n :name=\"activeItem.meta.icon\"\r\n :size=\"17\"\r\n />\r\n </div>\r\n <span class=\"side-menu__header-title\">{{\r\n activeItem?.meta?.title || \"菜单\"\r\n }}</span>\r\n </div>\r\n\r\n <!-- 菜单列表 -->\r\n <div class=\"side-menu__list\">\r\n <template v-for=\"child in menus\" :key=\"child.path\">\r\n <!-- 有子菜单 -->\r\n <div\r\n v-if=\"child.children && child.children.length > 0\"\r\n class=\"side-menu__group\"\r\n >\r\n <div class=\"side-menu__group-label\">\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && child.meta?.icon\"\r\n :name=\"child.meta.icon\"\r\n :size=\"14\"\r\n />\r\n <span>{{ child.meta?.title }}</span>\r\n </div>\r\n <div\r\n v-for=\"subChild in child.children\"\r\n :key=\"subChild.path\"\r\n class=\"side-menu__item\"\r\n :class=\"{ active: isActive?.(subChild.path) }\"\r\n @click=\"emit('click', subChild)\"\r\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && subChild.meta?.icon\"\r\n :name=\"subChild.meta.icon\"\r\n :size=\"15\"\r\n />\r\n <span class=\"side-menu__item-text\">{{\r\n subChild.meta?.title\r\n }}</span>\r\n </div>\r\n </div>\r\n <!-- 无子菜单 -->\r\n <div\r\n v-else\r\n class=\"side-menu__item\"\r\n :class=\"{ active: isActive?.(child.path) }\"\r\n @click=\"emit('click', child)\"\r\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && child.meta?.icon\"\r\n :name=\"child.meta.icon\"\r\n :size=\"16\"\r\n />\r\n <span class=\"side-menu__item-text\">{{ child.meta?.title }}</span>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n </Transition>\r\n </template>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"SideMenu\" });\r\n\r\ndefineProps<{\r\n /** 二级菜单列表 */\r\n menus: MenuOptions[];\r\n /** 当前一级菜单项(显示标题和图标) */\r\n activeItem?: MenuOptions;\r\n /** 判断菜单项是否激活 */\r\n isActive?: (path: string | undefined) => boolean;\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n click: [item: MenuOptions];\r\n}>();\r\n\r\nconst ctx = useLayoutContext();\r\nconst isCollapsed = ref(false);\r\n</script>\r\n\r\n<style scoped>\r\n.side-menu-toggle {\r\n position: absolute;\r\n right: 0;\r\n top: 50%;\r\n transform: translateY(-50%) translateX(50%);\r\n width: 24px;\r\n height: 48px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n border-radius: 12px;\r\n cursor: pointer;\r\n z-index: 10;\r\n transition: all 0.25s ease;\r\n font-size: 14px;\r\n}\r\n\r\n.side-menu-toggle.light-theme {\r\n background: rgba(255, 255, 255, 0.9);\r\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n.side-menu-toggle.dark-theme {\r\n background: rgba(40, 40, 46, 0.9);\r\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\r\n}\r\n\r\n.side-menu-toggle:hover {\r\n transform: translateY(-50%) translateX(50%) scale(1.1);\r\n}\r\n\r\n.side-menu {\r\n width: 100%;\r\n height: 100%;\r\n overflow-y: auto;\r\n padding: 0 8px;\r\n}\r\n\r\n.side-menu.light-theme {\r\n background: rgba(255, 255, 255, 0.95);\r\n border-left: 1px solid rgba(0, 0, 0, 0.06);\r\n}\r\n\r\n.side-menu.dark-theme {\r\n background: rgba(30, 30, 36, 0.95);\r\n border-left: 1px solid rgba(255, 255, 255, 0.06);\r\n}\r\n\r\n.side-menu__header {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 16px 12px 12px;\r\n border-bottom: 1px solid rgba(128, 128, 128, 0.1);\r\n margin-bottom: 8px;\r\n}\r\n\r\n.side-menu__header-icon {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 28px;\r\n height: 28px;\r\n border-radius: 8px;\r\n background: linear-gradient(\r\n 135deg,\r\n rgba(99, 102, 241, 0.12),\r\n rgba(139, 92, 246, 0.08)\r\n );\r\n}\r\n\r\n.side-menu__header-title {\r\n font-size: 14px;\r\n font-weight: 600;\r\n}\r\n\r\n.side-menu__list {\r\n padding: 4px 0;\r\n}\r\n\r\n.side-menu__group {\r\n margin-bottom: 8px;\r\n}\r\n\r\n.side-menu__group-label {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n padding: 6px 12px;\r\n font-size: 12px;\r\n font-weight: 600;\r\n opacity: 0.5;\r\n}\r\n\r\n.side-menu__item {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 9px 12px;\r\n margin: 2px 0;\r\n border-radius: 8px;\r\n cursor: pointer;\r\n font-size: 13px;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.side-menu__item:hover {\r\n background: rgba(99, 102, 241, 0.06);\r\n}\r\n\r\n.side-menu__item.active {\r\n background: linear-gradient(\r\n 135deg,\r\n rgba(99, 102, 241, 0.1),\r\n rgba(139, 92, 246, 0.06)\r\n );\r\n color: #667eea;\r\n font-weight: 500;\r\n}\r\n\r\n.side-menu__item-text {\r\n flex: 1;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n/* Transition */\r\n.glass-slide-enter-active,\r\n.glass-slide-leave-active {\r\n transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.glass-slide-enter-from,\r\n.glass-slide-leave-to {\r\n opacity: 0;\r\n transform: translateX(20px);\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - DrawerMenu\r\n *\r\n * 抽屉式网格菜单\r\n * 用于 CardLayout 的 hover 触发的抽屉式功能导航\r\n -->\r\n<template>\r\n <div\r\n class=\"drawer-menu\"\r\n :class=\"[\n ctx.isDark.value ? 'dark-theme' : 'light-theme',\r\n { visible: show },\r\n ]\"\n role=\"navigation\"\n aria-label=\"功能导航\"\n :aria-hidden=\"!show\"\n @mouseenter=\"handlers.show()\"\r\n @mouseleave=\"handlers.startHide()\"\r\n >\r\n <div class=\"drawer-menu__header\">\r\n <div class=\"drawer-menu__title\">\r\n <!-- 纯 CSS 网格图标 -->\r\n <span class=\"grid-icon\">\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n </span>\r\n <span>功能导航</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"drawer-menu__content\">\r\n <div class=\"drawer-menu__grid\">\r\n <div\r\n v-for=\"category in menus\"\r\n :key=\"category.path\"\r\n class=\"drawer-menu__category\"\r\n >\r\n <div\n class=\"drawer-menu__category-header\"\n role=\"link\"\n tabindex=\"0\"\n @click=\"navigate(category)\"\n @keydown.enter=\"navigate(category)\"\n @keydown.space.prevent=\"navigate(category)\"\n >\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && category.meta?.icon\"\r\n :name=\"category.meta.icon\"\r\n :size=\"18\"\r\n />\r\n <span>{{ category.meta?.title }}</span>\r\n </div>\r\n\r\n <div\r\n v-if=\"category.children && category.children.length > 0\"\r\n class=\"drawer-menu__items\"\r\n >\r\n <div\r\n v-for=\"item in category.children\"\r\n :key=\"item.path\"\r\n class=\"drawer-menu__item\"\n role=\"link\"\n tabindex=\"0\"\n @click=\"navigate(item)\"\n @keydown.enter=\"navigate(item)\"\n @keydown.space.prevent=\"navigate(item)\"\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && item.meta?.icon\"\r\n :name=\"item.meta.icon\"\r\n :size=\"16\"\r\n />\r\n <span class=\"drawer-menu__item-text\">{{ item.meta?.title }}</span>\r\n </div>\r\n\r\n <template v-for=\"item in category.children\" :key=\"item.path\">\r\n <div\r\n v-if=\"item.children && item.children.length > 0\"\r\n class=\"drawer-menu__sub-items\"\r\n >\r\n <div class=\"drawer-menu__sub-title\">\r\n {{ item.meta?.title }}\r\n </div>\r\n <div\r\n v-for=\"subItem in item.children\"\r\n :key=\"subItem.path\"\r\n class=\"drawer-menu__item drawer-menu__item--sub\"\n role=\"link\"\n tabindex=\"0\"\n @click=\"navigate(subItem)\"\n @keydown.enter=\"navigate(subItem)\"\n @keydown.space.prevent=\"navigate(subItem)\"\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && subItem.meta?.icon\"\r\n :name=\"subItem.meta.icon\"\r\n :size=\"14\"\r\n />\r\n <span class=\"drawer-menu__item-text\">{{\r\n subItem.meta?.title\r\n }}</span>\r\n </div>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { inject } from \"vue\";\r\nimport { useRouter } from \"vue-router\";\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\nimport {\r\n DRAWER_HANDLER_KEY,\r\n type DrawerHandlers,\r\n} from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"DrawerMenu\" });\r\n\r\ndefineProps<{\r\n /** 菜单数据 */\r\n menus: MenuOptions[];\r\n /** 是否显示 */\r\n show: boolean;\r\n}>();\r\n\r\nconst ctx = useLayoutContext();\r\nconst router = useRouter();\r\n\r\n// 使用共享的 drawer 控制器(由 C_LayoutContainer provide)\r\nconst handlers = inject<DrawerHandlers>(DRAWER_HANDLER_KEY, {\r\n show: () => {},\r\n startHide: () => {},\r\n hide: () => {},\r\n});\r\n\r\nconst navigate = (item: MenuOptions) => {\r\n if (item.path) {\r\n router.push(item.path);\r\n handlers.hide();\r\n }\r\n};\r\n</script>\r\n\r\n<style scoped>\r\n.drawer-menu {\r\n position: absolute;\r\n top: 100%;\r\n left: 0;\r\n right: 0;\r\n z-index: 100;\r\n max-height: 60vh;\r\n overflow-y: auto;\r\n opacity: 0;\n visibility: hidden;\n transform: translateY(-8px);\r\n pointer-events: none;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n backdrop-filter: blur(20px);\r\n -webkit-backdrop-filter: blur(20px);\r\n border-bottom-left-radius: 16px;\r\n border-bottom-right-radius: 16px;\r\n}\r\n\r\n.drawer-menu.visible {\n opacity: 1;\n visibility: visible;\n transform: translateY(0);\r\n pointer-events: all;\r\n}\r\n\r\n.drawer-menu.light-theme {\r\n background: rgba(255, 255, 255, 0.96);\r\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);\r\n border: 1px solid rgba(0, 0, 0, 0.06);\r\n border-top: none;\r\n}\r\n\r\n.drawer-menu.dark-theme {\r\n background: rgba(30, 30, 36, 0.96);\r\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\r\n border: 1px solid rgba(255, 255, 255, 0.06);\r\n border-top: none;\r\n}\r\n\r\n.drawer-menu__header {\r\n padding: 16px 24px 8px;\r\n}\r\n\r\n.drawer-menu__title {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n font-size: 15px;\r\n font-weight: 600;\r\n}\r\n\r\n.drawer-menu__content {\r\n padding: 8px 24px 20px;\r\n}\r\n\r\n.drawer-menu__grid {\r\n display: grid;\r\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\r\n gap: 16px;\r\n}\r\n\r\n.drawer-menu__category {\r\n padding: 0;\r\n}\r\n\r\n.drawer-menu__category-header {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 8px 12px;\r\n font-size: 14px;\r\n font-weight: 600;\r\n cursor: pointer;\r\n border-radius: 8px;\r\n transition: background 0.2s ease;\r\n}\r\n\r\n.drawer-menu__category-header:hover {\n background: rgba(99, 102, 241, 0.06);\r\n}\n\n.drawer-menu__category-header:focus-visible,\n.drawer-menu__item:focus-visible {\n outline: 2px solid #667eea;\n outline-offset: 2px;\n}\n\r\n.drawer-menu__items {\r\n padding-left: 8px;\r\n}\r\n\r\n.drawer-menu__item {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 7px 12px;\r\n margin: 1px 0;\r\n border-radius: 6px;\r\n font-size: 13px;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.drawer-menu__item:hover {\r\n background: rgba(99, 102, 241, 0.08);\r\n color: #667eea;\r\n}\r\n\r\n.drawer-menu__item--sub {\r\n padding-left: 16px;\r\n font-size: 12px;\r\n}\r\n\r\n.drawer-menu__item-text {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.drawer-menu__sub-items {\r\n padding: 4px 0;\r\n}\r\n\r\n.drawer-menu__sub-title {\r\n padding: 4px 12px;\r\n font-size: 11px;\r\n font-weight: 600;\r\n opacity: 0.4;\r\n}\r\n\r\n/* 纯 CSS 网格图标 (2x2) */\r\n.grid-icon {\r\n display: inline-grid;\r\n grid-template-columns: repeat(2, 1fr);\r\n gap: 2.5px;\r\n width: 18px;\r\n height: 18px;\r\n padding: 1px;\r\n}\r\n\r\n.grid-icon span {\r\n background-color: currentColor;\r\n border-radius: 2px;\r\n opacity: 0.85;\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - DrawerMenu\r\n *\r\n * 抽屉式网格菜单\r\n * 用于 CardLayout 的 hover 触发的抽屉式功能导航\r\n -->\r\n<template>\r\n <div\r\n class=\"drawer-menu\"\r\n :class=\"[\n ctx.isDark.value ? 'dark-theme' : 'light-theme',\r\n { visible: show },\r\n ]\"\n role=\"navigation\"\n aria-label=\"功能导航\"\n :aria-hidden=\"!show\"\n @mouseenter=\"handlers.show()\"\r\n @mouseleave=\"handlers.startHide()\"\r\n >\r\n <div class=\"drawer-menu__header\">\r\n <div class=\"drawer-menu__title\">\r\n <!-- 纯 CSS 网格图标 -->\r\n <span class=\"grid-icon\">\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n </span>\r\n <span>功能导航</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"drawer-menu__content\">\r\n <div class=\"drawer-menu__grid\">\r\n <div\r\n v-for=\"category in menus\"\r\n :key=\"category.path\"\r\n class=\"drawer-menu__category\"\r\n >\r\n <div\n class=\"drawer-menu__category-header\"\n role=\"link\"\n tabindex=\"0\"\n @click=\"navigate(category)\"\n @keydown.enter=\"navigate(category)\"\n @keydown.space.prevent=\"navigate(category)\"\n >\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && category.meta?.icon\"\r\n :name=\"category.meta.icon\"\r\n :size=\"18\"\r\n />\r\n <span>{{ category.meta?.title }}</span>\r\n </div>\r\n\r\n <div\r\n v-if=\"category.children && category.children.length > 0\"\r\n class=\"drawer-menu__items\"\r\n >\r\n <div\r\n v-for=\"item in category.children\"\r\n :key=\"item.path\"\r\n class=\"drawer-menu__item\"\n role=\"link\"\n tabindex=\"0\"\n @click=\"navigate(item)\"\n @keydown.enter=\"navigate(item)\"\n @keydown.space.prevent=\"navigate(item)\"\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && item.meta?.icon\"\r\n :name=\"item.meta.icon\"\r\n :size=\"16\"\r\n />\r\n <span class=\"drawer-menu__item-text\">{{ item.meta?.title }}</span>\r\n </div>\r\n\r\n <template v-for=\"item in category.children\" :key=\"item.path\">\r\n <div\r\n v-if=\"item.children && item.children.length > 0\"\r\n class=\"drawer-menu__sub-items\"\r\n >\r\n <div class=\"drawer-menu__sub-title\">\r\n {{ item.meta?.title }}\r\n </div>\r\n <div\r\n v-for=\"subItem in item.children\"\r\n :key=\"subItem.path\"\r\n class=\"drawer-menu__item drawer-menu__item--sub\"\n role=\"link\"\n tabindex=\"0\"\n @click=\"navigate(subItem)\"\n @keydown.enter=\"navigate(subItem)\"\n @keydown.space.prevent=\"navigate(subItem)\"\n >\r\n <component\r\n :is=\"ctx.iconComponent\"\r\n v-if=\"ctx.iconComponent && subItem.meta?.icon\"\r\n :name=\"subItem.meta.icon\"\r\n :size=\"14\"\r\n />\r\n <span class=\"drawer-menu__item-text\">{{\r\n subItem.meta?.title\r\n }}</span>\r\n </div>\r\n </div>\r\n </template>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { inject } from \"vue\";\r\nimport { useRouter } from \"vue-router\";\r\nimport type { MenuOptions } from \"../../types/menu\";\r\nimport { useLayoutContext } from \"../../composables/useLayoutContext\";\r\nimport {\r\n DRAWER_HANDLER_KEY,\r\n type DrawerHandlers,\r\n} from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"DrawerMenu\" });\r\n\r\ndefineProps<{\r\n /** 菜单数据 */\r\n menus: MenuOptions[];\r\n /** 是否显示 */\r\n show: boolean;\r\n}>();\r\n\r\nconst ctx = useLayoutContext();\r\nconst router = useRouter();\r\n\r\n// 使用共享的 drawer 控制器(由 C_LayoutContainer provide)\r\nconst handlers = inject<DrawerHandlers>(DRAWER_HANDLER_KEY, {\r\n show: () => {},\r\n startHide: () => {},\r\n hide: () => {},\r\n});\r\n\r\nconst navigate = (item: MenuOptions) => {\r\n if (item.path) {\r\n router.push(item.path);\r\n handlers.hide();\r\n }\r\n};\r\n</script>\r\n\r\n<style scoped>\r\n.drawer-menu {\r\n position: absolute;\r\n top: 100%;\r\n left: 0;\r\n right: 0;\r\n z-index: 100;\r\n max-height: 60vh;\r\n overflow-y: auto;\r\n opacity: 0;\n visibility: hidden;\n transform: translateY(-8px);\r\n pointer-events: none;\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n backdrop-filter: blur(20px);\r\n -webkit-backdrop-filter: blur(20px);\r\n border-bottom-left-radius: 16px;\r\n border-bottom-right-radius: 16px;\r\n}\r\n\r\n.drawer-menu.visible {\n opacity: 1;\n visibility: visible;\n transform: translateY(0);\r\n pointer-events: all;\r\n}\r\n\r\n.drawer-menu.light-theme {\r\n background: rgba(255, 255, 255, 0.96);\r\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);\r\n border: 1px solid rgba(0, 0, 0, 0.06);\r\n border-top: none;\r\n}\r\n\r\n.drawer-menu.dark-theme {\r\n background: rgba(30, 30, 36, 0.96);\r\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\r\n border: 1px solid rgba(255, 255, 255, 0.06);\r\n border-top: none;\r\n}\r\n\r\n.drawer-menu__header {\r\n padding: 16px 24px 8px;\r\n}\r\n\r\n.drawer-menu__title {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n font-size: 15px;\r\n font-weight: 600;\r\n}\r\n\r\n.drawer-menu__content {\r\n padding: 8px 24px 20px;\r\n}\r\n\r\n.drawer-menu__grid {\r\n display: grid;\r\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\r\n gap: 16px;\r\n}\r\n\r\n.drawer-menu__category {\r\n padding: 0;\r\n}\r\n\r\n.drawer-menu__category-header {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 8px 12px;\r\n font-size: 14px;\r\n font-weight: 600;\r\n cursor: pointer;\r\n border-radius: 8px;\r\n transition: background 0.2s ease;\r\n}\r\n\r\n.drawer-menu__category-header:hover {\n background: rgba(99, 102, 241, 0.06);\r\n}\n\n.drawer-menu__category-header:focus-visible,\n.drawer-menu__item:focus-visible {\n outline: 2px solid #667eea;\n outline-offset: 2px;\n}\n\r\n.drawer-menu__items {\r\n padding-left: 8px;\r\n}\r\n\r\n.drawer-menu__item {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 7px 12px;\r\n margin: 1px 0;\r\n border-radius: 6px;\r\n font-size: 13px;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n}\r\n\r\n.drawer-menu__item:hover {\r\n background: rgba(99, 102, 241, 0.08);\r\n color: #667eea;\r\n}\r\n\r\n.drawer-menu__item--sub {\r\n padding-left: 16px;\r\n font-size: 12px;\r\n}\r\n\r\n.drawer-menu__item-text {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.drawer-menu__sub-items {\r\n padding: 4px 0;\r\n}\r\n\r\n.drawer-menu__sub-title {\r\n padding: 4px 12px;\r\n font-size: 11px;\r\n font-weight: 600;\r\n opacity: 0.4;\r\n}\r\n\r\n/* 纯 CSS 网格图标 (2x2) */\r\n.grid-icon {\r\n display: inline-grid;\r\n grid-template-columns: repeat(2, 1fr);\r\n gap: 2.5px;\r\n width: 18px;\r\n height: 18px;\r\n padding: 1px;\r\n}\r\n\r\n.grid-icon span {\r\n background-color: currentColor;\r\n border-radius: 2px;\r\n opacity: 0.85;\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - MenuTrigger\r\n *\r\n * 菜单触发区域\r\n * 用于 CardLayout 的 hover 触发抽屉式菜单\r\n * 使用共享的 drawer 控制器避免与 DrawerMenu 定时器竞争\r\n -->\r\n<template>\r\n <div\r\n class=\"menu-trigger\"\r\n @mouseenter=\"handlers.show()\"\r\n @mouseleave=\"handlers.startHide()\"\r\n >\r\n <div class=\"menu-trigger__indicator\">\r\n <!-- 纯 CSS 三横线菜单图标 -->\r\n <span class=\"hamburger-icon\">\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n </span>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { inject } from \"vue\";\r\nimport {\r\n DRAWER_HANDLER_KEY,\r\n type DrawerHandlers,\r\n} from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"MenuTrigger\" });\r\n\r\ndefineProps<{\r\n show?: boolean;\r\n}>();\r\n\r\n// 使用共享控制器(由 C_LayoutContainer provide)\r\nconst handlers = inject<DrawerHandlers>(DRAWER_HANDLER_KEY, {\r\n show: () => {},\r\n startHide: () => {},\r\n hide: () => {},\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n.menu-trigger {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 0 8px;\r\n height: 100%;\r\n cursor: pointer;\r\n}\r\n\r\n.menu-trigger__indicator {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 36px;\r\n height: 36px;\r\n border-radius: 8px;\r\n font-size: 18px;\r\n transition: all 0.25s ease;\r\n opacity: 0.6;\r\n}\r\n\r\n.menu-trigger__indicator:hover {\r\n opacity: 1;\r\n background: rgba(99, 102, 241, 0.08);\r\n}\r\n\r\n/* 纯 CSS 三横线汉堡菜单图标 */\r\n.hamburger-icon {\r\n display: inline-flex;\r\n flex-direction: column;\r\n justify-content: space-between;\r\n width: 20px;\r\n height: 16px;\r\n padding: 2px 0;\r\n}\r\n\r\n.hamburger-icon span {\r\n display: block;\r\n width: 100%;\r\n height: 2px;\r\n background-color: currentColor;\r\n border-radius: 1px;\r\n opacity: 0.85;\r\n transition: all 0.2s ease;\r\n}\r\n</style>\r\n","<!--\r\n * @robot-admin/layout - MenuTrigger\r\n *\r\n * 菜单触发区域\r\n * 用于 CardLayout 的 hover 触发抽屉式菜单\r\n * 使用共享的 drawer 控制器避免与 DrawerMenu 定时器竞争\r\n -->\r\n<template>\r\n <div\r\n class=\"menu-trigger\"\r\n @mouseenter=\"handlers.show()\"\r\n @mouseleave=\"handlers.startHide()\"\r\n >\r\n <div class=\"menu-trigger__indicator\">\r\n <!-- 纯 CSS 三横线菜单图标 -->\r\n <span class=\"hamburger-icon\">\r\n <span></span>\r\n <span></span>\r\n <span></span>\r\n </span>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { inject } from \"vue\";\r\nimport {\r\n DRAWER_HANDLER_KEY,\r\n type DrawerHandlers,\r\n} from \"../../composables/useLayoutContext\";\r\n\r\ndefineOptions({ name: \"MenuTrigger\" });\r\n\r\ndefineProps<{\r\n show?: boolean;\r\n}>();\r\n\r\n// 使用共享控制器(由 C_LayoutContainer provide)\r\nconst handlers = inject<DrawerHandlers>(DRAWER_HANDLER_KEY, {\r\n show: () => {},\r\n startHide: () => {},\r\n hide: () => {},\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n.menu-trigger {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 0 8px;\r\n height: 100%;\r\n cursor: pointer;\r\n}\r\n\r\n.menu-trigger__indicator {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 36px;\r\n height: 36px;\r\n border-radius: 8px;\r\n font-size: 18px;\r\n transition: all 0.25s ease;\r\n opacity: 0.6;\r\n}\r\n\r\n.menu-trigger__indicator:hover {\r\n opacity: 1;\r\n background: rgba(99, 102, 241, 0.08);\r\n}\r\n\r\n/* 纯 CSS 三横线汉堡菜单图标 */\r\n.hamburger-icon {\r\n display: inline-flex;\r\n flex-direction: column;\r\n justify-content: space-between;\r\n width: 20px;\r\n height: 16px;\r\n padding: 2px 0;\r\n}\r\n\r\n.hamburger-icon span {\r\n display: block;\r\n width: 100%;\r\n height: 2px;\r\n background-color: currentColor;\r\n border-radius: 1px;\r\n opacity: 0.85;\r\n transition: all 0.2s ease;\r\n}\r\n</style>\r\n","/**\n * @robot-admin/layout\n *\n * 布局系统一键初始化\n */\n\nimport type { App } from \"vue\";\nimport type { SettingsStoreOptions } from \"./types\";\nimport { createSettingsStore } from \"./stores/settings\";\n\n/**\n * 初始化布局系统\n *\n * @example\n * ```ts\n * import { setupLayout } from '@robot-admin/layout'\n * setupLayout(app, {\n * onThemeModeChange: async (mode) => {\n * const themeStore = useThemeStore()\n * await themeStore.setMode(mode)\n * }\n * })\n * ```\n */\nexport function setupLayout(_app: App, options: SettingsStoreOptions = {}) {\n // 注册 settings store\n const settingsStore = createSettingsStore(options);\n\n // 初始化 CSS Variables\n settingsStore().syncCSSVariables();\n\n return settingsStore;\n}\n"],"mappings":"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"}