@robot-admin/layout 3.0.0 → 3.1.0
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.
- package/CHANGELOG.md +9 -0
- package/README.md +85 -25
- package/dist/core/index.cjs +1 -1
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +214 -25
- package/dist/index.d.ts +214 -25
- package/dist/index.js +48 -6
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["$slots"],"sources":["../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/constants/index.ts","../src/core/settings.ts","../src/stores/settings.ts","../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":["/**\r\n * @robot-admin/layout\r\n *\r\n * 布局上下文 - Provide/Inject 机制\r\n * 消费方通过 provide 注入数据,骨架组件通过 inject 读取\r\n * 实现布局骨架与业务数据的完全解耦\r\n */\r\n\r\nimport type {\n InjectionKey,\n ComputedRef,\n Component,\n WritableComputedRef,\n} from \"vue\";\nimport { inject, provide } from \"vue\";\nimport type { MenuOptions } from \"../types/menu\";\r\nimport type { LayoutMode, MenuExpandMode } from \"../types\";\r\nimport type { SettingsStoreInstance } from \"../stores/settings\";\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 /** 侧边栏折叠状态;提供后由容器和宿主头部共享同一数据源 */\n collapsed?: WritableComputedRef<boolean>;\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/** 当前布局使用的设置 Store,支持自定义 Store id 与多实例。 */\nexport const LAYOUT_SETTINGS_KEY: InjectionKey<SettingsStoreInstance> =\n Symbol(\"layout-settings\");\n\n/** 侧边栏折叠控制器。 */\nexport interface MenuCollapseHandlers {\n isCollapsed: ComputedRef<boolean>;\n handleCollapsedChange: (collapsed: boolean) => void;\n}\n\nexport const MENU_COLLAPSE_KEY: InjectionKey<MenuCollapseHandlers> =\n Symbol(\"menu-collapse\");\n\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/** 在组件 setup 中提供布局上下文,避免消费方直接操作 InjectionKey。 */\nexport function provideLayoutContext(context: LayoutContext): LayoutContext {\n provide(LAYOUT_CONTEXT_KEY, context);\n return context;\n}\n\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\";\nimport type { RouteLocationNormalizedLoaded } 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/** KeepAlive 仅接受具名组件,并由路由 meta.keepAlive 明确开启。 */\nexport function shouldCacheRoute(\n route: Pick<RouteLocationNormalizedLoaded, \"name\" | \"meta\">,\n): route is Pick<RouteLocationNormalizedLoaded, \"name\" | \"meta\"> & {\n name: string;\n} {\n return typeof route.name === \"string\" && route.meta.keepAlive === true;\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 normalizedMaxCount =\n Number.isFinite(maxCount) && maxCount > 0 ? Math.floor(maxCount) : 20;\n const cachedViews = ref<string[]>([]);\n const maxCacheCount = ref(normalizedMaxCount);\n\n /** 添加缓存 */\n const addCache = (name: string) => {\n if (name && !cachedViews.value.includes(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, route.meta.keepAlive] as const,\n () => {\n if (shouldCacheRoute(route)) addCache(route.name);\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 class=\"responsive-menu__visible\">\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 // 隐藏容器交由 ResizeObserver 在重新可见时触发,避免无限 nextTick 循环。\n if (containerWidth <= 0) return;\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: ReturnType<typeof setTimeout> | null = null;\nconst debouncedCalculate = () => {\r\n if (resizeTimer) clearTimeout(resizeTimer);\r\n resizeTimer = setTimeout(() => calculateVisibleItems(), 100);\n};\r\n\r\nlet resizeObserver: ResizeObserver | null = null;\r\n\r\nonMounted(() => {\r\n nextTick(() => calculateVisibleItems());\r\n if (containerRef.value && typeof ResizeObserver !== \"undefined\") {\n resizeObserver = new ResizeObserver(() => debouncedCalculate());\r\n resizeObserver.observe(containerRef.value);\r\n } else if (typeof window !== \"undefined\") {\n window.addEventListener(\"resize\", debouncedCalculate, { passive: true });\n }\r\n});\r\n\r\nonUnmounted(() => {\r\n resizeObserver?.disconnect();\r\n if (typeof window !== \"undefined\") {\n window.removeEventListener(\"resize\", debouncedCalculate);\n }\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 class=\"responsive-menu__visible\">\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 // 隐藏容器交由 ResizeObserver 在重新可见时触发,避免无限 nextTick 循环。\n if (containerWidth <= 0) return;\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: ReturnType<typeof setTimeout> | null = null;\nconst debouncedCalculate = () => {\r\n if (resizeTimer) clearTimeout(resizeTimer);\r\n resizeTimer = setTimeout(() => calculateVisibleItems(), 100);\n};\r\n\r\nlet resizeObserver: ResizeObserver | null = null;\r\n\r\nonMounted(() => {\r\n nextTick(() => calculateVisibleItems());\r\n if (containerRef.value && typeof ResizeObserver !== \"undefined\") {\n resizeObserver = new ResizeObserver(() => debouncedCalculate());\r\n resizeObserver.observe(containerRef.value);\r\n } else if (typeof window !== \"undefined\") {\n window.addEventListener(\"resize\", debouncedCalculate, { passive: true });\n }\r\n});\r\n\r\nonUnmounted(() => {\r\n resizeObserver?.disconnect();\r\n if (typeof window !== \"undefined\") {\n window.removeEventListener(\"resize\", debouncedCalculate);\n }\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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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 (item.disabled) return;\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.disabled && 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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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 role=\"button\"\n :tabindex=\"item.disabled ? -1 : 0\"\n :aria-disabled=\"item.disabled || undefined\"\n @click=\"menuSplit.handleFirstMenuClick(item)\"\n @keydown.enter=\"menuSplit.handleFirstMenuClick(item)\"\n @keydown.space.prevent=\"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 role=\"link\"\n :tabindex=\"child.disabled ? -1 : 0\"\n :aria-disabled=\"child.disabled || undefined\"\n @click=\"menuSplit.handleSecondMenuClick(child)\"\n @keydown.enter=\"menuSplit.handleSecondMenuClick(child)\"\n @keydown.space.prevent=\"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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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 role=\"button\"\n :tabindex=\"item.disabled ? -1 : 0\"\n :aria-disabled=\"item.disabled || undefined\"\n @click=\"menuSplit.handleFirstMenuClick(item)\"\n @keydown.enter=\"menuSplit.handleFirstMenuClick(item)\"\n @keydown.space.prevent=\"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 role=\"link\"\n :tabindex=\"child.disabled ? -1 : 0\"\n :aria-disabled=\"child.disabled || undefined\"\n @click=\"menuSplit.handleSecondMenuClick(child)\"\n @keydown.enter=\"menuSplit.handleSecondMenuClick(child)\"\n @keydown.space.prevent=\"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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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 role=\"button\"\n :tabindex=\"item.disabled ? -1 : 0\"\n :aria-disabled=\"item.disabled || undefined\"\n :aria-current=\"\n menuSplit.activeFirstMenu.value === item.path ? 'page' : undefined\n \"\n @click=\"menuSplit.handleFirstMenuClick(item)\"\n @keydown.enter=\"menuSplit.handleFirstMenuClick(item)\"\n @keydown.space.prevent=\"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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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 role=\"button\"\n :tabindex=\"item.disabled ? -1 : 0\"\n :aria-disabled=\"item.disabled || undefined\"\n :aria-current=\"\n menuSplit.activeFirstMenu.value === item.path ? 'page' : undefined\n \"\n @click=\"menuSplit.handleFirstMenuClick(item)\"\n @keydown.enter=\"menuSplit.handleFirstMenuClick(item)\"\n @keydown.space.prevent=\"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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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 role=\"button\"\n tabindex=\"0\"\n :aria-expanded=\"!isCollapsed\"\n @keydown.enter=\"toggleCollapse\"\n @keydown.space.prevent=\"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 role=\"button\"\n :tabindex=\"subChild.disabled ? -1 : 0\"\n :aria-disabled=\"subChild.disabled || undefined\"\n :aria-current=\"\n menuSplit.isMenuItemActive(subChild.path)\n ? 'page'\n : undefined\n \"\n @click=\"menuSplit.handleSecondMenuClick(subChild)\"\n @keydown.enter=\"menuSplit.handleSecondMenuClick(subChild)\"\n @keydown.space.prevent=\"\n menuSplit.handleSecondMenuClick(subChild)\n \"\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 role=\"button\"\n :tabindex=\"child.disabled ? -1 : 0\"\n :aria-disabled=\"child.disabled || undefined\"\n :aria-current=\"\n menuSplit.isMenuItemActive(child.path) ? 'page' : undefined\n \"\n @click=\"menuSplit.handleSecondMenuClick(child)\"\n @keydown.enter=\"menuSplit.handleSecondMenuClick(child)\"\n @keydown.space.prevent=\"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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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 role=\"button\"\n tabindex=\"0\"\n :aria-expanded=\"!isCollapsed\"\n @keydown.enter=\"toggleCollapse\"\n @keydown.space.prevent=\"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 role=\"button\"\n :tabindex=\"subChild.disabled ? -1 : 0\"\n :aria-disabled=\"subChild.disabled || undefined\"\n :aria-current=\"\n menuSplit.isMenuItemActive(subChild.path)\n ? 'page'\n : undefined\n \"\n @click=\"menuSplit.handleSecondMenuClick(subChild)\"\n @keydown.enter=\"menuSplit.handleSecondMenuClick(subChild)\"\n @keydown.space.prevent=\"\n menuSplit.handleSecondMenuClick(subChild)\n \"\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 role=\"button\"\n :tabindex=\"child.disabled ? -1 : 0\"\n :aria-disabled=\"child.disabled || undefined\"\n :aria-current=\"\n menuSplit.isMenuItemActive(child.path) ? 'page' : undefined\n \"\n @click=\"menuSplit.handleSecondMenuClick(child)\"\n @keydown.enter=\"menuSplit.handleSecondMenuClick(child)\"\n @keydown.space.prevent=\"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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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\n class=\"category-header\"\n role=\"link\"\n :tabindex=\"category.disabled ? -1 : 0\"\n :aria-disabled=\"category.disabled || undefined\"\n @click=\"navigateToPage(category)\"\n @keydown.enter=\"navigateToPage(category)\"\n @keydown.space.prevent=\"navigateToPage(category)\"\n >\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 role=\"link\"\n :tabindex=\"item.disabled ? -1 : 0\"\n :aria-disabled=\"item.disabled || undefined\"\n @click=\"navigateToPage(item)\"\n @keydown.enter=\"navigateToPage(item)\"\n @keydown.space.prevent=\"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 role=\"link\"\n :tabindex=\"subItem.disabled ? -1 : 0\"\n :aria-disabled=\"subItem.disabled || undefined\"\n @click=\"navigateToPage(subItem)\"\n @keydown.enter=\"navigateToPage(subItem)\"\n @keydown.space.prevent=\"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.disabled && 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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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\n class=\"category-header\"\n role=\"link\"\n :tabindex=\"category.disabled ? -1 : 0\"\n :aria-disabled=\"category.disabled || undefined\"\n @click=\"navigateToPage(category)\"\n @keydown.enter=\"navigateToPage(category)\"\n @keydown.space.prevent=\"navigateToPage(category)\"\n >\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 role=\"link\"\n :tabindex=\"item.disabled ? -1 : 0\"\n :aria-disabled=\"item.disabled || undefined\"\n @click=\"navigateToPage(item)\"\n @keydown.enter=\"navigateToPage(item)\"\n @keydown.space.prevent=\"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 role=\"link\"\n :tabindex=\"subItem.disabled ? -1 : 0\"\n :aria-disabled=\"subItem.disabled || undefined\"\n @click=\"navigateToPage(subItem)\"\n @keydown.enter=\"navigateToPage(subItem)\"\n @keydown.space.prevent=\"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.disabled && 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 {\n MENU_COLLAPSE_KEY,\n useLayoutContext,\n} from \"../../composables/useLayoutContext\";\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 ctx = useLayoutContext();\r\n\r\nconst layoutMode = ctx.layoutMode;\nconst isDark = ctx.isDark;\r\n\r\n// 上下文提供 collapsed 时与宿主共享;否则保留内部状态以兼容旧接入。\nconst internalCollapsed = ref(false);\nconst sideCollapsed = computed({\n get: () => ctx.collapsed?.value ?? internalCollapsed.value,\n set: (value: boolean) => {\n if (ctx.collapsed) ctx.collapsed.value = value;\n else internalCollapsed.value = value;\n },\n});\n\r\n// 提供折叠状态给子组件(如 C_Header 的汉堡按钮)\r\nconst collapseHandlers = {\n isCollapsed: sideCollapsed,\r\n handleCollapsedChange: (collapsed: boolean) => {\r\n sideCollapsed.value = collapsed;\r\n },\r\n};\nprovide(MENU_COLLAPSE_KEY, collapseHandlers);\n// 3.x 兼容层:旧消费方仍可使用字符串 key,下个大版本移除。\nprovide(\"menuCollapse\", collapseHandlers);\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 {\n MENU_COLLAPSE_KEY,\n useLayoutContext,\n} from \"../../composables/useLayoutContext\";\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 ctx = useLayoutContext();\r\n\r\nconst layoutMode = ctx.layoutMode;\nconst isDark = ctx.isDark;\r\n\r\n// 上下文提供 collapsed 时与宿主共享;否则保留内部状态以兼容旧接入。\nconst internalCollapsed = ref(false);\nconst sideCollapsed = computed({\n get: () => ctx.collapsed?.value ?? internalCollapsed.value,\n set: (value: boolean) => {\n if (ctx.collapsed) ctx.collapsed.value = value;\n else internalCollapsed.value = value;\n },\n});\n\r\n// 提供折叠状态给子组件(如 C_Header 的汉堡按钮)\r\nconst collapseHandlers = {\n isCollapsed: sideCollapsed,\r\n handleCollapsedChange: (collapsed: boolean) => {\r\n sideCollapsed.value = collapsed;\r\n },\r\n};\nprovide(MENU_COLLAPSE_KEY, collapseHandlers);\n// 3.x 兼容层:旧消费方仍可使用字符串 key,下个大版本移除。\nprovide(\"menuCollapse\", collapseHandlers);\n</script>\r\n","/**\n * 布局和设置系统 - 常量定义\n */\n\nimport type {\n ThemePreset,\n SettingsState,\n LayoutInfo,\n PresetColor,\n} from \"../core/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: \"3.0.0\",\n};\n\n// ============ 其他常量 ============\n\n/** Color swatches for color picker */\nexport const COLOR_SWATCHES = PRESET_COLORS.map((c) => c.value);\n","import type { SettingsState } from \"./types\";\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/** 当前导入导出配置格式版本。 */\nexport const SETTINGS_CONFIG_SCHEMA_VERSION = 1 as const;\n\nexport interface LayoutVisualEffects {\n gray?: boolean;\n colorWeak?: boolean;\n watermark?: {\n enabled: boolean;\n text: string;\n };\n}\n\n/** 经校验后可原子应用的布局配置。 */\nexport interface LayoutSettingsConfig extends LayoutVisualEffects {\n schemaVersion?: number;\n settings?: Partial<SettingsState>;\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 * 一次性校验完整导入文件。此函数不修改任何响应式状态或浏览器 API,\n * 消费方可以在成功返回后再原子应用结果。\n */\nexport function sanitizeLayoutSettingsConfig(\n input: unknown,\n): LayoutSettingsConfig {\n if (!input || typeof input !== \"object\" || Array.isArray(input)) {\n throw new TypeError(\"配置文件根节点必须是对象\");\n }\n\n const source = input as Record<string, unknown>;\n const result: LayoutSettingsConfig = {};\n\n if (source.schemaVersion !== undefined) {\n if (\n typeof source.schemaVersion !== \"number\" ||\n !Number.isInteger(source.schemaVersion) ||\n source.schemaVersion < 1 ||\n source.schemaVersion > SETTINGS_CONFIG_SCHEMA_VERSION\n ) {\n throw new RangeError(\"不支持的配置格式版本\");\n }\n result.schemaVersion = source.schemaVersion;\n }\n\n if (source.settings !== undefined) {\n result.settings = sanitizeSettingsPatch(source.settings);\n }\n\n for (const key of [\"gray\", \"colorWeak\"] as const) {\n const value = source[key];\n if (value === undefined) continue;\n if (typeof value !== \"boolean\") {\n throw new TypeError(`${key} 必须是布尔值`);\n }\n result[key] = value;\n }\n\n if (source.watermark !== undefined) {\n if (\n !source.watermark ||\n typeof source.watermark !== \"object\" ||\n Array.isArray(source.watermark)\n ) {\n throw new TypeError(\"watermark 必须是对象\");\n }\n const watermark = source.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 result.watermark = {\n enabled: watermark.enabled,\n text: watermark.text,\n };\n }\n\n return result;\n}\n\n/**\n * 调整颜色亮度。\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(\"#\") ? trimmedColor.slice(1) : trimmedColor;\n if (hex.length === 3) {\n hex = hex\n .split(\"\")\n .map((character) => character + character)\n .join(\"\");\n }\n if (!/^[0-9a-fA-F]{6}$/.test(hex)) return color;\n\n const value = parseInt(hex, 16);\n const red = Math.min(255, Math.max(0, (value >> 16) + normalizedAmount));\n const green = Math.min(\n 255,\n Math.max(0, ((value >> 8) & 0x00ff) + normalizedAmount),\n );\n const blue = Math.min(\n 255,\n Math.max(0, (value & 0x0000ff) + normalizedAmount),\n );\n return `#${((red << 16) | (green << 8) | blue).toString(16).padStart(6, \"0\")}`;\n}\n","import { defineStore } from \"pinia\";\nimport { ref, computed, watch } from \"vue\";\nimport type {\n SettingsState,\n ThemePreset,\n LayoutMode,\n MenuExpandMode,\n TransitionType,\n BorderRadiusSize,\n TagsViewStyle,\n SettingsStoreOptions,\n ThemeMode,\n} from \"../types\";\nimport {\n DEFAULT_SETTINGS,\n BORDER_RADIUS_MAP,\n TRANSITION_MAP,\n} from \"../constants\";\nexport { adjustColor, sanitizeSettingsPatch } from \"../core/settings\";\nimport { adjustColor, sanitizeSettingsPatch } from \"../core/settings\";\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 shouldEnableTransition = computed(\n () => enableTransition.value && transitionType.value !== \"none\",\n );\n\n /** 获取实际生效的过渡动画名称 */\n const transitionName = computed(() =>\n shouldEnableTransition.value ? TRANSITION_MAP[transitionType.value] : \"\",\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 version.value = finalDefaults.version;\n };\n\n /**\n * 更新主题模式\n */\n const updateThemeMode = async (mode: ThemeMode) => {\n sanitizeSettingsPatch({ themeMode: mode });\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\n/** 默认设置 Store 实例类型,供组件注入或显式传入。 */\nexport type SettingsStoreInstance = ReturnType<typeof useSettingsStore>;\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=\"width\"\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 <slot name=\"appearance-prepend\" :settings=\"settingsStore\" />\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 <slot name=\"appearance-append\" :settings=\"settingsStore\" />\n </NTabPane>\n\n <!-- 布局 Tab -->\n <NTabPane name=\"layout\" tab=\"📐 布局\">\n <slot name=\"layout-prepend\" :settings=\"settingsStore\" />\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=\"\n handleLayoutChange(mode.value, mode.disabled)\n \"\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 <slot name=\"layout-after-mode\" :settings=\"settingsStore\" />\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 <slot name=\"layout-append\" :settings=\"settingsStore\" />\n </NTabPane>\n\n <!-- 功能 Tab -->\n <NTabPane name=\"features\" tab=\"🔧 功能\">\n <slot name=\"features-prepend\" :settings=\"settingsStore\" />\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 <slot name=\"features-append\" :settings=\"settingsStore\" />\n </NTabPane>\n </NTabs>\n </NDrawerContent>\n </NDrawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch, onUnmounted, inject } 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 useSettingsStore,\n type SettingsStoreInstance,\n} from \"../../stores/settings\";\nimport { DEFAULT_SETTINGS } from \"../../constants\";\nimport {\n sanitizeLayoutSettingsConfig,\n SETTINGS_CONFIG_SCHEMA_VERSION,\n} from \"../../core/settings\";\nimport { LAYOUT_SETTINGS_KEY } from \"../../composables/useLayoutContext\";\nimport { COLOR_SWATCHES, LAYOUT_MODE_OPTIONS, THEME_PRESETS } from \"./data\";\nimport type {\n SettingsDrawerActions,\n ThemeMode,\n ThemePreset,\n} from \"../../types\";\n\n// ============ 数据定义 ============\n\nconst props = withDefaults(\n defineProps<{\n /** 抽屉宽度,保持 2.x 默认值 */\n width?: number;\n /** 显式设置 Store;优先级高于 setupLayout 注入和默认 Store */\n store?: SettingsStoreInstance;\n /** 由宿主实现的缓存清理、页面刷新等高副作用操作 */\n actions?: SettingsDrawerActions;\n /** 导入文件最大字节数,默认 1 MiB */\n maxImportBytes?: number;\n }>(),\n {\n width: 380,\n maxImportBytes: 1024 * 1024,\n },\n);\n\nconst message = useMessage();\nconst dialog = useDialog();\nconst injectedSettingsStore = inject(LAYOUT_SETTINGS_KEY, null);\nconst settingsStore =\n props.store ?? injectedSettingsStore ?? useSettingsStore();\nconst visible = defineModel<boolean>(\"show\", { default: false });\nconst activeTab = ref(\"appearance\");\n\n// 功能开关:记录宿主原始状态,卸载时精确恢复,避免污染全局 class。\nconst initialGrayMode =\n typeof document !== \"undefined\" &&\n document.documentElement.classList.contains(\"gray-mode\");\nconst initialColorWeakMode =\n typeof document !== \"undefined\" &&\n document.documentElement.classList.contains(\"color-weak-mode\");\nconst grayMode = ref(initialGrayMode);\nconst colorWeakMode = ref(initialColorWeakMode);\nconst watermarkEnabled = ref(false);\nconst watermarkText = ref(\"Robot Admin\");\n\n// 水印元素引用\nlet watermarkEl: HTMLElement | null = null;\n\n// 创建水印函数\nconst createWatermark = (text: string) => {\n if (typeof document === \"undefined\") return;\n // 移除旧水印\n if (watermarkEl) {\n watermarkEl.remove();\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.dataset.robotAdminLayoutWatermark = \"true\";\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 watermarkEl.remove();\n watermarkEl = null;\n }\n};\n\n// 监听灰色模式\nwatch(grayMode, (val) => {\n if (typeof document !== \"undefined\") {\n document.documentElement.classList.toggle(\"gray-mode\", val);\n }\n});\n\n// 监听色弱模式\nwatch(colorWeakMode, (val) => {\n if (typeof document !== \"undefined\") {\n document.documentElement.classList.toggle(\"color-weak-mode\", val);\n }\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(\"Edg/\")) browser = \"Edge\";\n else if (ua.includes(\"Firefox\")) browser = \"Firefox\";\n else if (ua.includes(\"Chrome\")) browser = \"Chrome\";\n else if (ua.includes(\"Safari\")) browser = \"Safari\";\n\n // 检测操作系统\n if (ua.includes(\"Windows\")) os = \"Windows\";\n else if (ua.includes(\"Mac\")) os = \"macOS\";\n else if (ua.includes(\"Android\")) os = \"Android\";\n else if (ua.includes(\"iOS\")) os = \"iOS\";\n else if (ua.includes(\"Linux\")) os = \"Linux\";\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 if (typeof document !== \"undefined\") {\n document.documentElement.classList.toggle(\"gray-mode\", initialGrayMode);\n document.documentElement.classList.toggle(\n \"color-weak-mode\",\n initialColorWeakMode,\n );\n }\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 = async () => {\n try {\n await settingsStore.updateThemeMode(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 } catch {\n message.error(\"恢复外观默认设置失败\");\n }\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: async () => {\n const previousSettings = { ...settingsStore.settingsState };\n try {\n settingsStore.resetSettings();\n await settingsStore.updateThemeMode(settingsStore.themeMode);\n message.success(\"已恢复默认配置\");\n } catch {\n settingsStore.$patch(previousSettings);\n message.error(\"恢复默认配置失败\");\n }\n },\n });\n};\n\n/**\n * 清除缓存\n */\nconst handleClearCache = () => {\n dialog.warning({\n title: \"确认清除\",\n content: \"确定要清除浏览器缓存吗?\",\n positiveText: \"确认\",\n negativeText: \"取消\",\n onPositiveClick: async () => {\n if (!props.actions?.clearCache) {\n message.warning(\"宿主应用未配置可安全清理的缓存\");\n return;\n }\n try {\n await props.actions.clearCache();\n message.success(\"缓存已清除\");\n } catch {\n message.error(\"缓存清除失败\");\n }\n },\n });\n};\n\n/**\n * 重新加载页面\n */\nconst handleReload = () => {\n if (props.actions?.reloadPage) {\n props.actions.reloadPage();\n } else if (typeof window !== \"undefined\") {\n window.location.reload();\n }\n};\n\n/**\n * 导出配置\n */\nconst handleExportConfig = () => {\n const config = {\n schemaVersion: SETTINGS_CONFIG_SCHEMA_VERSION,\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 if (typeof document === \"undefined\") return;\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 if (file.size > props.maxImportBytes) {\n throw new RangeError(\"配置文件过大\");\n }\n const text = await file.text();\n // 先完成全量校验,成功后再修改任何状态,避免部分配置污染。\n const imported = sanitizeLayoutSettingsConfig(JSON.parse(text));\n\n // 应用配置\n if (imported.settings !== undefined) {\n const { themeMode, ...settingsPatch } = imported.settings;\n if (themeMode !== undefined) {\n await settingsStore.updateThemeMode(themeMode);\n }\n settingsStore.$patch(settingsPatch);\n }\n if (imported.gray !== undefined) {\n grayMode.value = imported.gray;\n }\n if (imported.colorWeak !== undefined) {\n colorWeakMode.value = imported.colorWeak;\n }\n if (imported.watermark !== undefined) {\n watermarkEnabled.value = imported.watermark.enabled;\n watermarkText.value = imported.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=\"width\"\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 <slot name=\"appearance-prepend\" :settings=\"settingsStore\" />\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 <slot name=\"appearance-append\" :settings=\"settingsStore\" />\n </NTabPane>\n\n <!-- 布局 Tab -->\n <NTabPane name=\"layout\" tab=\"📐 布局\">\n <slot name=\"layout-prepend\" :settings=\"settingsStore\" />\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=\"\n handleLayoutChange(mode.value, mode.disabled)\n \"\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 <slot name=\"layout-after-mode\" :settings=\"settingsStore\" />\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 <slot name=\"layout-append\" :settings=\"settingsStore\" />\n </NTabPane>\n\n <!-- 功能 Tab -->\n <NTabPane name=\"features\" tab=\"🔧 功能\">\n <slot name=\"features-prepend\" :settings=\"settingsStore\" />\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 <slot name=\"features-append\" :settings=\"settingsStore\" />\n </NTabPane>\n </NTabs>\n </NDrawerContent>\n </NDrawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch, onUnmounted, inject } 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 useSettingsStore,\n type SettingsStoreInstance,\n} from \"../../stores/settings\";\nimport { DEFAULT_SETTINGS } from \"../../constants\";\nimport {\n sanitizeLayoutSettingsConfig,\n SETTINGS_CONFIG_SCHEMA_VERSION,\n} from \"../../core/settings\";\nimport { LAYOUT_SETTINGS_KEY } from \"../../composables/useLayoutContext\";\nimport { COLOR_SWATCHES, LAYOUT_MODE_OPTIONS, THEME_PRESETS } from \"./data\";\nimport type {\n SettingsDrawerActions,\n ThemeMode,\n ThemePreset,\n} from \"../../types\";\n\n// ============ 数据定义 ============\n\nconst props = withDefaults(\n defineProps<{\n /** 抽屉宽度,保持 2.x 默认值 */\n width?: number;\n /** 显式设置 Store;优先级高于 setupLayout 注入和默认 Store */\n store?: SettingsStoreInstance;\n /** 由宿主实现的缓存清理、页面刷新等高副作用操作 */\n actions?: SettingsDrawerActions;\n /** 导入文件最大字节数,默认 1 MiB */\n maxImportBytes?: number;\n }>(),\n {\n width: 380,\n maxImportBytes: 1024 * 1024,\n },\n);\n\nconst message = useMessage();\nconst dialog = useDialog();\nconst injectedSettingsStore = inject(LAYOUT_SETTINGS_KEY, null);\nconst settingsStore =\n props.store ?? injectedSettingsStore ?? useSettingsStore();\nconst visible = defineModel<boolean>(\"show\", { default: false });\nconst activeTab = ref(\"appearance\");\n\n// 功能开关:记录宿主原始状态,卸载时精确恢复,避免污染全局 class。\nconst initialGrayMode =\n typeof document !== \"undefined\" &&\n document.documentElement.classList.contains(\"gray-mode\");\nconst initialColorWeakMode =\n typeof document !== \"undefined\" &&\n document.documentElement.classList.contains(\"color-weak-mode\");\nconst grayMode = ref(initialGrayMode);\nconst colorWeakMode = ref(initialColorWeakMode);\nconst watermarkEnabled = ref(false);\nconst watermarkText = ref(\"Robot Admin\");\n\n// 水印元素引用\nlet watermarkEl: HTMLElement | null = null;\n\n// 创建水印函数\nconst createWatermark = (text: string) => {\n if (typeof document === \"undefined\") return;\n // 移除旧水印\n if (watermarkEl) {\n watermarkEl.remove();\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.dataset.robotAdminLayoutWatermark = \"true\";\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 watermarkEl.remove();\n watermarkEl = null;\n }\n};\n\n// 监听灰色模式\nwatch(grayMode, (val) => {\n if (typeof document !== \"undefined\") {\n document.documentElement.classList.toggle(\"gray-mode\", val);\n }\n});\n\n// 监听色弱模式\nwatch(colorWeakMode, (val) => {\n if (typeof document !== \"undefined\") {\n document.documentElement.classList.toggle(\"color-weak-mode\", val);\n }\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(\"Edg/\")) browser = \"Edge\";\n else if (ua.includes(\"Firefox\")) browser = \"Firefox\";\n else if (ua.includes(\"Chrome\")) browser = \"Chrome\";\n else if (ua.includes(\"Safari\")) browser = \"Safari\";\n\n // 检测操作系统\n if (ua.includes(\"Windows\")) os = \"Windows\";\n else if (ua.includes(\"Mac\")) os = \"macOS\";\n else if (ua.includes(\"Android\")) os = \"Android\";\n else if (ua.includes(\"iOS\")) os = \"iOS\";\n else if (ua.includes(\"Linux\")) os = \"Linux\";\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 if (typeof document !== \"undefined\") {\n document.documentElement.classList.toggle(\"gray-mode\", initialGrayMode);\n document.documentElement.classList.toggle(\n \"color-weak-mode\",\n initialColorWeakMode,\n );\n }\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 = async () => {\n try {\n await settingsStore.updateThemeMode(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 } catch {\n message.error(\"恢复外观默认设置失败\");\n }\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: async () => {\n const previousSettings = { ...settingsStore.settingsState };\n try {\n settingsStore.resetSettings();\n await settingsStore.updateThemeMode(settingsStore.themeMode);\n message.success(\"已恢复默认配置\");\n } catch {\n settingsStore.$patch(previousSettings);\n message.error(\"恢复默认配置失败\");\n }\n },\n });\n};\n\n/**\n * 清除缓存\n */\nconst handleClearCache = () => {\n dialog.warning({\n title: \"确认清除\",\n content: \"确定要清除浏览器缓存吗?\",\n positiveText: \"确认\",\n negativeText: \"取消\",\n onPositiveClick: async () => {\n if (!props.actions?.clearCache) {\n message.warning(\"宿主应用未配置可安全清理的缓存\");\n return;\n }\n try {\n await props.actions.clearCache();\n message.success(\"缓存已清除\");\n } catch {\n message.error(\"缓存清除失败\");\n }\n },\n });\n};\n\n/**\n * 重新加载页面\n */\nconst handleReload = () => {\n if (props.actions?.reloadPage) {\n props.actions.reloadPage();\n } else if (typeof window !== \"undefined\") {\n window.location.reload();\n }\n};\n\n/**\n * 导出配置\n */\nconst handleExportConfig = () => {\n const config = {\n schemaVersion: SETTINGS_CONFIG_SCHEMA_VERSION,\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 if (typeof document === \"undefined\") return;\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 if (file.size > props.maxImportBytes) {\n throw new RangeError(\"配置文件过大\");\n }\n const text = await file.text();\n // 先完成全量校验,成功后再修改任何状态,避免部分配置污染。\n const imported = sanitizeLayoutSettingsConfig(JSON.parse(text));\n\n // 应用配置\n if (imported.settings !== undefined) {\n const { themeMode, ...settingsPatch } = imported.settings;\n if (themeMode !== undefined) {\n await settingsStore.updateThemeMode(themeMode);\n }\n settingsStore.$patch(settingsPatch);\n }\n if (imported.gray !== undefined) {\n grayMode.value = imported.gray;\n }\n if (imported.colorWeak !== undefined) {\n colorWeakMode.value = imported.colorWeak;\n }\n if (imported.watermark !== undefined) {\n watermarkEnabled.value = imported.watermark.enabled;\n watermarkText.value = imported.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\";\nimport { LAYOUT_SETTINGS_KEY } from \"./composables/useLayoutContext\";\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 const settingsStore = createSettingsStore(options);\n const settings = settingsStore();\n\n // 让所有内置组件读取同一个实例;Store 内的 immediate watcher 负责同步 CSS 变量。\n app.provide(LAYOUT_SETTINGS_KEY, settings);\n\n return settingsStore;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["$slots"],"sources":["../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/constants/index.ts","../src/core/settings.ts","../src/stores/settings.ts","../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","../src/composables/createLayoutContext.ts","../src/index.ts"],"sourcesContent":["/**\r\n * @robot-admin/layout\r\n *\r\n * 布局上下文 - Provide/Inject 机制\r\n * 消费方通过 provide 注入数据,骨架组件通过 inject 读取\r\n * 实现布局骨架与业务数据的完全解耦\r\n */\r\n\r\nimport type {\n InjectionKey,\n ComputedRef,\n Component,\n WritableComputedRef,\n} from \"vue\";\nimport { inject, provide } from \"vue\";\nimport type { MenuOptions } from \"../types/menu\";\r\nimport type { LayoutMode, MenuExpandMode } from \"../types\";\r\nimport type { SettingsStoreInstance } from \"../stores/settings\";\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 /** 侧边栏折叠状态;提供后由容器和宿主头部共享同一数据源 */\n collapsed?: WritableComputedRef<boolean>;\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/** 当前布局使用的设置 Store,支持自定义 Store id 与多实例。 */\nexport const LAYOUT_SETTINGS_KEY: InjectionKey<SettingsStoreInstance> =\n Symbol(\"layout-settings\");\n\n/** 侧边栏折叠控制器。 */\nexport interface MenuCollapseHandlers {\n isCollapsed: ComputedRef<boolean>;\n handleCollapsedChange: (collapsed: boolean) => void;\n}\n\nexport const MENU_COLLAPSE_KEY: InjectionKey<MenuCollapseHandlers> =\n Symbol(\"menu-collapse\");\n\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/** 在组件 setup 中提供布局上下文,避免消费方直接操作 InjectionKey。 */\nexport function provideLayoutContext(context: LayoutContext): LayoutContext {\n provide(LAYOUT_CONTEXT_KEY, context);\n return context;\n}\n\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\";\nimport type { RouteLocationNormalizedLoaded } from \"vue-router\";\n\ninterface LayoutCacheDebugWindow extends Window {\n __clearCache__?: () => void;\n __removeCache__?: (name: string) => void;\n __getCachedViews__?: () => string[];\n}\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/** KeepAlive 仅接受具名组件,并由路由 meta.keepAlive 明确开启。 */\nexport function shouldCacheRoute(\n route: Pick<RouteLocationNormalizedLoaded, \"name\" | \"meta\">,\n): route is Pick<RouteLocationNormalizedLoaded, \"name\" | \"meta\"> & {\n name: string;\n} {\n return typeof route.name === \"string\" && route.meta.keepAlive === true;\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 normalizedMaxCount =\n Number.isFinite(maxCount) && maxCount > 0 ? Math.floor(maxCount) : 20;\n const cachedViews = ref<string[]>([]);\n const maxCacheCount = ref(normalizedMaxCount);\n\n /** 添加缓存 */\n const addCache = (name: string) => {\n if (name && !cachedViews.value.includes(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 const debugWindow = window as LayoutCacheDebugWindow;\n debugWindow.__clearCache__ = clearAllCache;\n debugWindow.__removeCache__ = removeCache;\n debugWindow.__getCachedViews__ = getCachedViews;\n\n onUnmounted(() => {\n if (debugWindow.__clearCache__ === clearAllCache) {\n delete debugWindow.__clearCache__;\n }\n if (debugWindow.__removeCache__ === removeCache) {\n delete debugWindow.__removeCache__;\n }\n if (debugWindow.__getCachedViews__ === getCachedViews) {\n delete debugWindow.__getCachedViews__;\n }\n });\n }\n\n // 监听路由变化,动态管理缓存\n watch(\n () => [route.name, route.meta.keepAlive] as const,\n () => {\n if (shouldCacheRoute(route)) addCache(route.name);\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 class=\"responsive-menu__visible\">\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 // 隐藏容器交由 ResizeObserver 在重新可见时触发,避免无限 nextTick 循环。\n if (containerWidth <= 0) return;\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: ReturnType<typeof setTimeout> | null = null;\nconst debouncedCalculate = () => {\r\n if (resizeTimer) clearTimeout(resizeTimer);\r\n resizeTimer = setTimeout(() => calculateVisibleItems(), 100);\n};\r\n\r\nlet resizeObserver: ResizeObserver | null = null;\r\n\r\nonMounted(() => {\r\n nextTick(() => calculateVisibleItems());\r\n if (containerRef.value && typeof ResizeObserver !== \"undefined\") {\n resizeObserver = new ResizeObserver(() => debouncedCalculate());\r\n resizeObserver.observe(containerRef.value);\r\n } else if (typeof window !== \"undefined\") {\n window.addEventListener(\"resize\", debouncedCalculate, { passive: true });\n }\r\n});\r\n\r\nonUnmounted(() => {\r\n resizeObserver?.disconnect();\r\n if (typeof window !== \"undefined\") {\n window.removeEventListener(\"resize\", debouncedCalculate);\n }\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 class=\"responsive-menu__visible\">\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 // 隐藏容器交由 ResizeObserver 在重新可见时触发,避免无限 nextTick 循环。\n if (containerWidth <= 0) return;\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: ReturnType<typeof setTimeout> | null = null;\nconst debouncedCalculate = () => {\r\n if (resizeTimer) clearTimeout(resizeTimer);\r\n resizeTimer = setTimeout(() => calculateVisibleItems(), 100);\n};\r\n\r\nlet resizeObserver: ResizeObserver | null = null;\r\n\r\nonMounted(() => {\r\n nextTick(() => calculateVisibleItems());\r\n if (containerRef.value && typeof ResizeObserver !== \"undefined\") {\n resizeObserver = new ResizeObserver(() => debouncedCalculate());\r\n resizeObserver.observe(containerRef.value);\r\n } else if (typeof window !== \"undefined\") {\n window.addEventListener(\"resize\", debouncedCalculate, { passive: true });\n }\r\n});\r\n\r\nonUnmounted(() => {\r\n resizeObserver?.disconnect();\r\n if (typeof window !== \"undefined\") {\n window.removeEventListener(\"resize\", debouncedCalculate);\n }\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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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 (item.disabled) return;\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.disabled && 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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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 role=\"button\"\n :tabindex=\"item.disabled ? -1 : 0\"\n :aria-disabled=\"item.disabled || undefined\"\n @click=\"menuSplit.handleFirstMenuClick(item)\"\n @keydown.enter=\"menuSplit.handleFirstMenuClick(item)\"\n @keydown.space.prevent=\"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 role=\"link\"\n :tabindex=\"child.disabled ? -1 : 0\"\n :aria-disabled=\"child.disabled || undefined\"\n @click=\"menuSplit.handleSecondMenuClick(child)\"\n @keydown.enter=\"menuSplit.handleSecondMenuClick(child)\"\n @keydown.space.prevent=\"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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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 role=\"button\"\n :tabindex=\"item.disabled ? -1 : 0\"\n :aria-disabled=\"item.disabled || undefined\"\n @click=\"menuSplit.handleFirstMenuClick(item)\"\n @keydown.enter=\"menuSplit.handleFirstMenuClick(item)\"\n @keydown.space.prevent=\"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 role=\"link\"\n :tabindex=\"child.disabled ? -1 : 0\"\n :aria-disabled=\"child.disabled || undefined\"\n @click=\"menuSplit.handleSecondMenuClick(child)\"\n @keydown.enter=\"menuSplit.handleSecondMenuClick(child)\"\n @keydown.space.prevent=\"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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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 role=\"button\"\n :tabindex=\"item.disabled ? -1 : 0\"\n :aria-disabled=\"item.disabled || undefined\"\n :aria-current=\"\n menuSplit.activeFirstMenu.value === item.path ? 'page' : undefined\n \"\n @click=\"menuSplit.handleFirstMenuClick(item)\"\n @keydown.enter=\"menuSplit.handleFirstMenuClick(item)\"\n @keydown.space.prevent=\"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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 40\"\n :height=\"brand.logoSize || 40\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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 role=\"button\"\n :tabindex=\"item.disabled ? -1 : 0\"\n :aria-disabled=\"item.disabled || undefined\"\n :aria-current=\"\n menuSplit.activeFirstMenu.value === item.path ? 'page' : undefined\n \"\n @click=\"menuSplit.handleFirstMenuClick(item)\"\n @keydown.enter=\"menuSplit.handleFirstMenuClick(item)\"\n @keydown.space.prevent=\"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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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 role=\"button\"\n tabindex=\"0\"\n :aria-expanded=\"!isCollapsed\"\n @keydown.enter=\"toggleCollapse\"\n @keydown.space.prevent=\"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 role=\"button\"\n :tabindex=\"subChild.disabled ? -1 : 0\"\n :aria-disabled=\"subChild.disabled || undefined\"\n :aria-current=\"\n menuSplit.isMenuItemActive(subChild.path)\n ? 'page'\n : undefined\n \"\n @click=\"menuSplit.handleSecondMenuClick(subChild)\"\n @keydown.enter=\"menuSplit.handleSecondMenuClick(subChild)\"\n @keydown.space.prevent=\"\n menuSplit.handleSecondMenuClick(subChild)\n \"\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 role=\"button\"\n :tabindex=\"child.disabled ? -1 : 0\"\n :aria-disabled=\"child.disabled || undefined\"\n :aria-current=\"\n menuSplit.isMenuItemActive(child.path) ? 'page' : undefined\n \"\n @click=\"menuSplit.handleSecondMenuClick(child)\"\n @keydown.enter=\"menuSplit.handleSecondMenuClick(child)\"\n @keydown.space.prevent=\"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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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 role=\"button\"\n tabindex=\"0\"\n :aria-expanded=\"!isCollapsed\"\n @keydown.enter=\"toggleCollapse\"\n @keydown.space.prevent=\"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 role=\"button\"\n :tabindex=\"subChild.disabled ? -1 : 0\"\n :aria-disabled=\"subChild.disabled || undefined\"\n :aria-current=\"\n menuSplit.isMenuItemActive(subChild.path)\n ? 'page'\n : undefined\n \"\n @click=\"menuSplit.handleSecondMenuClick(subChild)\"\n @keydown.enter=\"menuSplit.handleSecondMenuClick(subChild)\"\n @keydown.space.prevent=\"\n menuSplit.handleSecondMenuClick(subChild)\n \"\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 role=\"button\"\n :tabindex=\"child.disabled ? -1 : 0\"\n :aria-disabled=\"child.disabled || undefined\"\n :aria-current=\"\n menuSplit.isMenuItemActive(child.path) ? 'page' : undefined\n \"\n @click=\"menuSplit.handleSecondMenuClick(child)\"\n @keydown.enter=\"menuSplit.handleSecondMenuClick(child)\"\n @keydown.space.prevent=\"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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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\n class=\"category-header\"\n role=\"link\"\n :tabindex=\"category.disabled ? -1 : 0\"\n :aria-disabled=\"category.disabled || undefined\"\n @click=\"navigateToPage(category)\"\n @keydown.enter=\"navigateToPage(category)\"\n @keydown.space.prevent=\"navigateToPage(category)\"\n >\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 role=\"link\"\n :tabindex=\"item.disabled ? -1 : 0\"\n :aria-disabled=\"item.disabled || undefined\"\n @click=\"navigateToPage(item)\"\n @keydown.enter=\"navigateToPage(item)\"\n @keydown.space.prevent=\"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 role=\"link\"\n :tabindex=\"subItem.disabled ? -1 : 0\"\n :aria-disabled=\"subItem.disabled || undefined\"\n @click=\"navigateToPage(subItem)\"\n @keydown.enter=\"navigateToPage(subItem)\"\n @keydown.space.prevent=\"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.disabled && 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' && brand.logoSrc\"\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-if=\"brand.logoSrc\"\n :src=\"brand.logoSrc\"\n :width=\"brand.logoSize || 36\"\n :height=\"brand.logoSize || 36\"\n class=\"logo-video\"\n :alt=\"brand.name || 'Logo'\"\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\n class=\"category-header\"\n role=\"link\"\n :tabindex=\"category.disabled ? -1 : 0\"\n :aria-disabled=\"category.disabled || undefined\"\n @click=\"navigateToPage(category)\"\n @keydown.enter=\"navigateToPage(category)\"\n @keydown.space.prevent=\"navigateToPage(category)\"\n >\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 role=\"link\"\n :tabindex=\"item.disabled ? -1 : 0\"\n :aria-disabled=\"item.disabled || undefined\"\n @click=\"navigateToPage(item)\"\n @keydown.enter=\"navigateToPage(item)\"\n @keydown.space.prevent=\"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 role=\"link\"\n :tabindex=\"subItem.disabled ? -1 : 0\"\n :aria-disabled=\"subItem.disabled || undefined\"\n @click=\"navigateToPage(subItem)\"\n @keydown.enter=\"navigateToPage(subItem)\"\n @keydown.space.prevent=\"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.disabled && 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 {\n MENU_COLLAPSE_KEY,\n useLayoutContext,\n} from \"../../composables/useLayoutContext\";\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 ctx = useLayoutContext();\r\n\r\nconst layoutMode = ctx.layoutMode;\nconst isDark = ctx.isDark;\r\n\r\n// 上下文提供 collapsed 时与宿主共享;否则保留内部状态以兼容旧接入。\nconst internalCollapsed = ref(false);\nconst sideCollapsed = computed({\n get: () => ctx.collapsed?.value ?? internalCollapsed.value,\n set: (value: boolean) => {\n if (ctx.collapsed) ctx.collapsed.value = value;\n else internalCollapsed.value = value;\n },\n});\n\r\n// 提供折叠状态给子组件(如 C_Header 的汉堡按钮)\r\nconst collapseHandlers = {\n isCollapsed: sideCollapsed,\r\n handleCollapsedChange: (collapsed: boolean) => {\r\n sideCollapsed.value = collapsed;\r\n },\r\n};\nprovide(MENU_COLLAPSE_KEY, collapseHandlers);\n// 3.x 兼容层:旧消费方仍可使用字符串 key,计划在 4.0 移除。\nprovide(\"menuCollapse\", collapseHandlers);\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 {\n MENU_COLLAPSE_KEY,\n useLayoutContext,\n} from \"../../composables/useLayoutContext\";\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 ctx = useLayoutContext();\r\n\r\nconst layoutMode = ctx.layoutMode;\nconst isDark = ctx.isDark;\r\n\r\n// 上下文提供 collapsed 时与宿主共享;否则保留内部状态以兼容旧接入。\nconst internalCollapsed = ref(false);\nconst sideCollapsed = computed({\n get: () => ctx.collapsed?.value ?? internalCollapsed.value,\n set: (value: boolean) => {\n if (ctx.collapsed) ctx.collapsed.value = value;\n else internalCollapsed.value = value;\n },\n});\n\r\n// 提供折叠状态给子组件(如 C_Header 的汉堡按钮)\r\nconst collapseHandlers = {\n isCollapsed: sideCollapsed,\r\n handleCollapsedChange: (collapsed: boolean) => {\r\n sideCollapsed.value = collapsed;\r\n },\r\n};\nprovide(MENU_COLLAPSE_KEY, collapseHandlers);\n// 3.x 兼容层:旧消费方仍可使用字符串 key,计划在 4.0 移除。\nprovide(\"menuCollapse\", collapseHandlers);\n</script>\r\n","/**\n * 布局和设置系统 - 常量定义\n */\n\nimport type {\n ThemePreset,\n SettingsState,\n LayoutInfo,\n PresetColor,\n} from \"../core/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: \"3.1.0\",\n};\n\n// ============ 其他常量 ============\n\n/** Color swatches for color picker */\nexport const COLOR_SWATCHES = PRESET_COLORS.map((c) => c.value);\n","import type { SettingsState } from \"./types\";\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/** 当前导入导出配置格式版本。 */\nexport const SETTINGS_CONFIG_SCHEMA_VERSION = 1 as const;\n\nexport interface LayoutVisualEffects {\n gray?: boolean;\n colorWeak?: boolean;\n watermark?: {\n enabled: boolean;\n text: string;\n };\n}\n\n/** 经校验后可原子应用的布局配置。 */\nexport interface LayoutSettingsConfig extends LayoutVisualEffects {\n schemaVersion?: number;\n settings?: Partial<SettingsState>;\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 * 一次性校验完整导入文件。此函数不修改任何响应式状态或浏览器 API,\n * 消费方可以在成功返回后再原子应用结果。\n */\nexport function sanitizeLayoutSettingsConfig(\n input: unknown,\n): LayoutSettingsConfig {\n if (!input || typeof input !== \"object\" || Array.isArray(input)) {\n throw new TypeError(\"配置文件根节点必须是对象\");\n }\n\n const source = input as Record<string, unknown>;\n const result: LayoutSettingsConfig = {};\n\n if (source.schemaVersion !== undefined) {\n if (\n typeof source.schemaVersion !== \"number\" ||\n !Number.isInteger(source.schemaVersion) ||\n source.schemaVersion < 1 ||\n source.schemaVersion > SETTINGS_CONFIG_SCHEMA_VERSION\n ) {\n throw new RangeError(\"不支持的配置格式版本\");\n }\n result.schemaVersion = source.schemaVersion;\n }\n\n if (source.settings !== undefined) {\n result.settings = sanitizeSettingsPatch(source.settings);\n }\n\n for (const key of [\"gray\", \"colorWeak\"] as const) {\n const value = source[key];\n if (value === undefined) continue;\n if (typeof value !== \"boolean\") {\n throw new TypeError(`${key} 必须是布尔值`);\n }\n result[key] = value;\n }\n\n if (source.watermark !== undefined) {\n if (\n !source.watermark ||\n typeof source.watermark !== \"object\" ||\n Array.isArray(source.watermark)\n ) {\n throw new TypeError(\"watermark 必须是对象\");\n }\n const watermark = source.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 result.watermark = {\n enabled: watermark.enabled,\n text: watermark.text,\n };\n }\n\n return result;\n}\n\n/**\n * 调整颜色亮度。\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(\"#\") ? trimmedColor.slice(1) : trimmedColor;\n if (hex.length === 3) {\n hex = hex\n .split(\"\")\n .map((character) => character + character)\n .join(\"\");\n }\n if (!/^[0-9a-fA-F]{6}$/.test(hex)) return color;\n\n const value = parseInt(hex, 16);\n const red = Math.min(255, Math.max(0, (value >> 16) + normalizedAmount));\n const green = Math.min(\n 255,\n Math.max(0, ((value >> 8) & 0x00ff) + normalizedAmount),\n );\n const blue = Math.min(\n 255,\n Math.max(0, (value & 0x0000ff) + normalizedAmount),\n );\n return `#${((red << 16) | (green << 8) | blue).toString(16).padStart(6, \"0\")}`;\n}\n","import { defineStore } from \"pinia\";\nimport { ref, computed, watch } from \"vue\";\nimport type {\n SettingsState,\n ThemePreset,\n LayoutMode,\n MenuExpandMode,\n TransitionType,\n BorderRadiusSize,\n TagsViewStyle,\n SettingsStoreOptions,\n ThemeMode,\n} from \"../types\";\nimport {\n DEFAULT_SETTINGS,\n BORDER_RADIUS_MAP,\n TRANSITION_MAP,\n} from \"../constants\";\nexport { adjustColor, sanitizeSettingsPatch } from \"../core/settings\";\nimport { adjustColor, sanitizeSettingsPatch } from \"../core/settings\";\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 shouldEnableTransition = computed(\n () => enableTransition.value && transitionType.value !== \"none\",\n );\n\n /** 获取实际生效的过渡动画名称 */\n const transitionName = computed(() =>\n shouldEnableTransition.value ? TRANSITION_MAP[transitionType.value] : \"\",\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 version.value = finalDefaults.version;\n };\n\n /**\n * 更新主题模式\n */\n const updateThemeMode = async (mode: ThemeMode) => {\n sanitizeSettingsPatch({ themeMode: mode });\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\n/** 默认设置 Store 实例类型,供组件注入或显式传入。 */\nexport type SettingsStoreInstance = ReturnType<typeof useSettingsStore>;\n","/**\n * SettingsDrawer - 常量数据\n */\n\nimport { THEME_PRESETS, COLOR_SWATCHES } from \"../../constants\";\nimport type { LayoutMode, 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] satisfies Array<{\n label: string;\n value: LayoutMode;\n disabled: boolean;\n svg: string;\n}>;\n","<!--\n * @robot-admin/layout - SettingsDrawer\n *\n * 布局配置抽屉\n * 包含外观/布局/功能三大模块,支持主题预设、布局切换、配置导入导出等\n -->\n<template>\n <NDrawer\n v-model:show=\"visible\"\n :width=\"width\"\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 <slot name=\"appearance-prepend\" :settings=\"settingsStore\" />\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 <slot name=\"appearance-append\" :settings=\"settingsStore\" />\n </NTabPane>\n\n <!-- 布局 Tab -->\n <NTabPane name=\"layout\" tab=\"📐 布局\">\n <slot name=\"layout-prepend\" :settings=\"settingsStore\" />\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=\"\n handleLayoutChange(mode.value, mode.disabled)\n \"\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 <slot name=\"layout-after-mode\" :settings=\"settingsStore\" />\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 <slot name=\"layout-append\" :settings=\"settingsStore\" />\n </NTabPane>\n\n <!-- 功能 Tab -->\n <NTabPane name=\"features\" tab=\"🔧 功能\">\n <slot name=\"features-prepend\" :settings=\"settingsStore\" />\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 <slot name=\"features-append\" :settings=\"settingsStore\" />\n </NTabPane>\n </NTabs>\n </NDrawerContent>\n </NDrawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch, onUnmounted, inject } 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 useSettingsStore,\n type SettingsStoreInstance,\n} from \"../../stores/settings\";\nimport { DEFAULT_SETTINGS } from \"../../constants\";\nimport {\n sanitizeLayoutSettingsConfig,\n SETTINGS_CONFIG_SCHEMA_VERSION,\n} from \"../../core/settings\";\nimport { LAYOUT_SETTINGS_KEY } from \"../../composables/useLayoutContext\";\nimport { COLOR_SWATCHES, LAYOUT_MODE_OPTIONS, THEME_PRESETS } from \"./data\";\nimport type {\n SettingsDrawerActions,\n LayoutMode,\n ThemeMode,\n ThemePreset,\n} from \"../../types\";\n\n// ============ 数据定义 ============\n\nconst props = withDefaults(\n defineProps<{\n /** 抽屉宽度,默认 380px */\n width?: number;\n /** 显式设置 Store;优先级高于 setupLayout 注入和默认 Store */\n store?: SettingsStoreInstance;\n /** 由宿主实现的缓存清理、页面刷新等高副作用操作 */\n actions?: SettingsDrawerActions;\n /** 导入文件最大字节数,默认 1 MiB */\n maxImportBytes?: number;\n }>(),\n {\n width: 380,\n maxImportBytes: 1024 * 1024,\n },\n);\n\nconst message = useMessage();\nconst dialog = useDialog();\nconst injectedSettingsStore = inject(LAYOUT_SETTINGS_KEY, null);\nconst settingsStore =\n props.store ?? injectedSettingsStore ?? useSettingsStore();\nconst visible = defineModel<boolean>(\"show\", { default: false });\nconst activeTab = ref(\"appearance\");\n\n// 功能开关:记录宿主原始状态,卸载时精确恢复,避免污染全局 class。\nconst initialGrayMode =\n typeof document !== \"undefined\" &&\n document.documentElement.classList.contains(\"gray-mode\");\nconst initialColorWeakMode =\n typeof document !== \"undefined\" &&\n document.documentElement.classList.contains(\"color-weak-mode\");\nconst grayMode = ref(initialGrayMode);\nconst colorWeakMode = ref(initialColorWeakMode);\nconst watermarkEnabled = ref(false);\nconst watermarkText = ref(\"Robot Admin\");\n\n// 水印元素引用\nlet watermarkEl: HTMLElement | null = null;\n\n// 创建水印函数\nconst createWatermark = (text: string) => {\n if (typeof document === \"undefined\") return;\n // 移除旧水印\n if (watermarkEl) {\n watermarkEl.remove();\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.dataset.robotAdminLayoutWatermark = \"true\";\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 watermarkEl.remove();\n watermarkEl = null;\n }\n};\n\n// 监听灰色模式\nwatch(grayMode, (val) => {\n if (typeof document !== \"undefined\") {\n document.documentElement.classList.toggle(\"gray-mode\", val);\n }\n});\n\n// 监听色弱模式\nwatch(colorWeakMode, (val) => {\n if (typeof document !== \"undefined\") {\n document.documentElement.classList.toggle(\"color-weak-mode\", val);\n }\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(\"Edg/\")) browser = \"Edge\";\n else if (ua.includes(\"Firefox\")) browser = \"Firefox\";\n else if (ua.includes(\"Chrome\")) browser = \"Chrome\";\n else if (ua.includes(\"Safari\")) browser = \"Safari\";\n\n // 检测操作系统\n if (ua.includes(\"Windows\")) os = \"Windows\";\n else if (ua.includes(\"Mac\")) os = \"macOS\";\n else if (ua.includes(\"Android\")) os = \"Android\";\n else if (ua.includes(\"iOS\")) os = \"iOS\";\n else if (ua.includes(\"Linux\")) os = \"Linux\";\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 if (typeof document !== \"undefined\") {\n document.documentElement.classList.toggle(\"gray-mode\", initialGrayMode);\n document.documentElement.classList.toggle(\n \"color-weak-mode\",\n initialColorWeakMode,\n );\n }\n});\n\n// 处理布局切换 - 阻止抽屉关闭\nconst handleLayoutChange = (value: LayoutMode, disabled?: boolean) => {\n if (disabled) return;\n settingsStore.layoutMode = value;\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 = async () => {\n try {\n await settingsStore.updateThemeMode(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 } catch {\n message.error(\"恢复外观默认设置失败\");\n }\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: async () => {\n const previousSettings = { ...settingsStore.settingsState };\n try {\n settingsStore.resetSettings();\n await settingsStore.updateThemeMode(settingsStore.themeMode);\n message.success(\"已恢复默认配置\");\n } catch {\n settingsStore.$patch(previousSettings);\n message.error(\"恢复默认配置失败\");\n }\n },\n });\n};\n\n/**\n * 清除缓存\n */\nconst handleClearCache = () => {\n dialog.warning({\n title: \"确认清除\",\n content: \"确定要清除浏览器缓存吗?\",\n positiveText: \"确认\",\n negativeText: \"取消\",\n onPositiveClick: async () => {\n if (!props.actions?.clearCache) {\n message.warning(\"宿主应用未配置可安全清理的缓存\");\n return;\n }\n try {\n await props.actions.clearCache();\n message.success(\"缓存已清除\");\n } catch {\n message.error(\"缓存清除失败\");\n }\n },\n });\n};\n\n/**\n * 重新加载页面\n */\nconst handleReload = () => {\n if (props.actions?.reloadPage) {\n props.actions.reloadPage();\n } else if (typeof window !== \"undefined\") {\n window.location.reload();\n }\n};\n\n/**\n * 导出配置\n */\nconst handleExportConfig = () => {\n const config = {\n schemaVersion: SETTINGS_CONFIG_SCHEMA_VERSION,\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 if (typeof document === \"undefined\") return;\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 if (file.size > props.maxImportBytes) {\n throw new RangeError(\"配置文件过大\");\n }\n const text = await file.text();\n // 先完成全量校验,成功后再修改任何状态,避免部分配置污染。\n const imported = sanitizeLayoutSettingsConfig(JSON.parse(text));\n\n // 应用配置\n if (imported.settings !== undefined) {\n const { themeMode, ...settingsPatch } = imported.settings;\n if (themeMode !== undefined) {\n await settingsStore.updateThemeMode(themeMode);\n }\n settingsStore.$patch(settingsPatch);\n }\n if (imported.gray !== undefined) {\n grayMode.value = imported.gray;\n }\n if (imported.colorWeak !== undefined) {\n colorWeakMode.value = imported.colorWeak;\n }\n if (imported.watermark !== undefined) {\n watermarkEnabled.value = imported.watermark.enabled;\n watermarkText.value = imported.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=\"width\"\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 <slot name=\"appearance-prepend\" :settings=\"settingsStore\" />\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 <slot name=\"appearance-append\" :settings=\"settingsStore\" />\n </NTabPane>\n\n <!-- 布局 Tab -->\n <NTabPane name=\"layout\" tab=\"📐 布局\">\n <slot name=\"layout-prepend\" :settings=\"settingsStore\" />\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=\"\n handleLayoutChange(mode.value, mode.disabled)\n \"\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 <slot name=\"layout-after-mode\" :settings=\"settingsStore\" />\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 <slot name=\"layout-append\" :settings=\"settingsStore\" />\n </NTabPane>\n\n <!-- 功能 Tab -->\n <NTabPane name=\"features\" tab=\"🔧 功能\">\n <slot name=\"features-prepend\" :settings=\"settingsStore\" />\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 <slot name=\"features-append\" :settings=\"settingsStore\" />\n </NTabPane>\n </NTabs>\n </NDrawerContent>\n </NDrawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch, onUnmounted, inject } 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 useSettingsStore,\n type SettingsStoreInstance,\n} from \"../../stores/settings\";\nimport { DEFAULT_SETTINGS } from \"../../constants\";\nimport {\n sanitizeLayoutSettingsConfig,\n SETTINGS_CONFIG_SCHEMA_VERSION,\n} from \"../../core/settings\";\nimport { LAYOUT_SETTINGS_KEY } from \"../../composables/useLayoutContext\";\nimport { COLOR_SWATCHES, LAYOUT_MODE_OPTIONS, THEME_PRESETS } from \"./data\";\nimport type {\n SettingsDrawerActions,\n LayoutMode,\n ThemeMode,\n ThemePreset,\n} from \"../../types\";\n\n// ============ 数据定义 ============\n\nconst props = withDefaults(\n defineProps<{\n /** 抽屉宽度,默认 380px */\n width?: number;\n /** 显式设置 Store;优先级高于 setupLayout 注入和默认 Store */\n store?: SettingsStoreInstance;\n /** 由宿主实现的缓存清理、页面刷新等高副作用操作 */\n actions?: SettingsDrawerActions;\n /** 导入文件最大字节数,默认 1 MiB */\n maxImportBytes?: number;\n }>(),\n {\n width: 380,\n maxImportBytes: 1024 * 1024,\n },\n);\n\nconst message = useMessage();\nconst dialog = useDialog();\nconst injectedSettingsStore = inject(LAYOUT_SETTINGS_KEY, null);\nconst settingsStore =\n props.store ?? injectedSettingsStore ?? useSettingsStore();\nconst visible = defineModel<boolean>(\"show\", { default: false });\nconst activeTab = ref(\"appearance\");\n\n// 功能开关:记录宿主原始状态,卸载时精确恢复,避免污染全局 class。\nconst initialGrayMode =\n typeof document !== \"undefined\" &&\n document.documentElement.classList.contains(\"gray-mode\");\nconst initialColorWeakMode =\n typeof document !== \"undefined\" &&\n document.documentElement.classList.contains(\"color-weak-mode\");\nconst grayMode = ref(initialGrayMode);\nconst colorWeakMode = ref(initialColorWeakMode);\nconst watermarkEnabled = ref(false);\nconst watermarkText = ref(\"Robot Admin\");\n\n// 水印元素引用\nlet watermarkEl: HTMLElement | null = null;\n\n// 创建水印函数\nconst createWatermark = (text: string) => {\n if (typeof document === \"undefined\") return;\n // 移除旧水印\n if (watermarkEl) {\n watermarkEl.remove();\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.dataset.robotAdminLayoutWatermark = \"true\";\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 watermarkEl.remove();\n watermarkEl = null;\n }\n};\n\n// 监听灰色模式\nwatch(grayMode, (val) => {\n if (typeof document !== \"undefined\") {\n document.documentElement.classList.toggle(\"gray-mode\", val);\n }\n});\n\n// 监听色弱模式\nwatch(colorWeakMode, (val) => {\n if (typeof document !== \"undefined\") {\n document.documentElement.classList.toggle(\"color-weak-mode\", val);\n }\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(\"Edg/\")) browser = \"Edge\";\n else if (ua.includes(\"Firefox\")) browser = \"Firefox\";\n else if (ua.includes(\"Chrome\")) browser = \"Chrome\";\n else if (ua.includes(\"Safari\")) browser = \"Safari\";\n\n // 检测操作系统\n if (ua.includes(\"Windows\")) os = \"Windows\";\n else if (ua.includes(\"Mac\")) os = \"macOS\";\n else if (ua.includes(\"Android\")) os = \"Android\";\n else if (ua.includes(\"iOS\")) os = \"iOS\";\n else if (ua.includes(\"Linux\")) os = \"Linux\";\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 if (typeof document !== \"undefined\") {\n document.documentElement.classList.toggle(\"gray-mode\", initialGrayMode);\n document.documentElement.classList.toggle(\n \"color-weak-mode\",\n initialColorWeakMode,\n );\n }\n});\n\n// 处理布局切换 - 阻止抽屉关闭\nconst handleLayoutChange = (value: LayoutMode, disabled?: boolean) => {\n if (disabled) return;\n settingsStore.layoutMode = value;\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 = async () => {\n try {\n await settingsStore.updateThemeMode(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 } catch {\n message.error(\"恢复外观默认设置失败\");\n }\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: async () => {\n const previousSettings = { ...settingsStore.settingsState };\n try {\n settingsStore.resetSettings();\n await settingsStore.updateThemeMode(settingsStore.themeMode);\n message.success(\"已恢复默认配置\");\n } catch {\n settingsStore.$patch(previousSettings);\n message.error(\"恢复默认配置失败\");\n }\n },\n });\n};\n\n/**\n * 清除缓存\n */\nconst handleClearCache = () => {\n dialog.warning({\n title: \"确认清除\",\n content: \"确定要清除浏览器缓存吗?\",\n positiveText: \"确认\",\n negativeText: \"取消\",\n onPositiveClick: async () => {\n if (!props.actions?.clearCache) {\n message.warning(\"宿主应用未配置可安全清理的缓存\");\n return;\n }\n try {\n await props.actions.clearCache();\n message.success(\"缓存已清除\");\n } catch {\n message.error(\"缓存清除失败\");\n }\n },\n });\n};\n\n/**\n * 重新加载页面\n */\nconst handleReload = () => {\n if (props.actions?.reloadPage) {\n props.actions.reloadPage();\n } else if (typeof window !== \"undefined\") {\n window.location.reload();\n }\n};\n\n/**\n * 导出配置\n */\nconst handleExportConfig = () => {\n const config = {\n schemaVersion: SETTINGS_CONFIG_SCHEMA_VERSION,\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 if (typeof document === \"undefined\") return;\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 if (file.size > props.maxImportBytes) {\n throw new RangeError(\"配置文件过大\");\n }\n const text = await file.text();\n // 先完成全量校验,成功后再修改任何状态,避免部分配置污染。\n const imported = sanitizeLayoutSettingsConfig(JSON.parse(text));\n\n // 应用配置\n if (imported.settings !== undefined) {\n const { themeMode, ...settingsPatch } = imported.settings;\n if (themeMode !== undefined) {\n await settingsStore.updateThemeMode(themeMode);\n }\n settingsStore.$patch(settingsPatch);\n }\n if (imported.gray !== undefined) {\n grayMode.value = imported.gray;\n }\n if (imported.colorWeak !== undefined) {\n colorWeakMode.value = imported.colorWeak;\n }\n if (imported.watermark !== undefined) {\n watermarkEnabled.value = imported.watermark.enabled;\n watermarkText.value = imported.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\";\nimport { LAYOUT_SETTINGS_KEY } from \"./composables/useLayoutContext\";\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 const settingsStore = createSettingsStore(options);\n const settings = settingsStore();\n\n // 让所有内置组件读取同一个实例;Store 内的 immediate watcher 负责同步 CSS 变量。\n app.provide(LAYOUT_SETTINGS_KEY, settings);\n\n return settingsStore;\n}\n","import {\n computed,\n toValue,\n type Component,\n type MaybeRefOrGetter,\n} from \"vue\";\nimport type { MenuOptions } from \"../types/menu\";\nimport type { SettingsStoreInstance } from \"../stores/settings\";\nimport {\n DEFAULT_BRAND_CONFIG,\n provideLayoutContext,\n type LayoutBrandConfig,\n type LayoutContext,\n} from \"./useLayoutContext\";\n\n/** 创建标准布局上下文所需的最小宿主适配。 */\nexport interface CreateLayoutContextOptions {\n /** 布局设置 Store,可使用默认 Store 或 createSettingsStore() 创建的实例。 */\n settings: SettingsStoreInstance;\n /** 宿主菜单;支持普通值、Ref、ComputedRef 或 getter。 */\n menus: MaybeRefOrGetter<MenuOptions[]>;\n /** 宿主暗色状态;支持普通值、Ref、ComputedRef 或 getter。 */\n isDark: MaybeRefOrGetter<boolean>;\n /** 品牌信息,未提供的字段使用包内安全默认值。 */\n brand?: LayoutBrandConfig;\n /** 布局内部用于渲染菜单图标的宿主组件。 */\n iconComponent?: Component;\n}\n\n/**\n * 将最小宿主输入转换为完整 LayoutContext。\n *\n * 保留底层 LayoutContext 作为高级扩展协议,同时避免普通接入重复桥接 Store 字段。\n */\nexport function createLayoutContext(\n options: CreateLayoutContextOptions,\n): LayoutContext {\n const { settings } = options;\n\n return {\n menus: computed(() => toValue(options.menus)),\n isDark: computed(() => toValue(options.isDark)),\n layoutMode: computed(() => settings.layoutMode),\n collapsed: computed({\n get: () => settings.collapsed,\n set: (value: boolean) => {\n settings.collapsed = value;\n },\n }),\n menuExpandMode: computed(() => settings.menuExpandMode),\n sidebarWidth: computed(() => settings.sidebarWidth),\n sidebarCollapsedWidth: computed(() => settings.sidebarCollapsedWidth),\n showFooter: computed(() => settings.showFooter),\n showTagsView: computed(() => settings.showTagsView),\n tagsViewHeight: computed(() => settings.tagsViewHeight),\n headerHeight: computed(() => settings.headerHeight),\n transitionName: computed(() => settings.transitionName),\n showBreadcrumb: computed(() => settings.showBreadcrumb),\n showBreadcrumbIcon: computed(() => settings.showBreadcrumbIcon),\n fixedHeader: computed(() => settings.fixedHeader),\n brand: { ...DEFAULT_BRAND_CONFIG, ...options.brand },\n iconComponent: options.iconComponent,\n };\n}\n\n/** 在组件 setup 中创建并提供标准布局上下文。 */\nexport function provideLayout(\n options: CreateLayoutContextOptions,\n): LayoutContext {\n return provideLayoutContext(createLayoutContext(options));\n}\n","/**\n * @robot-admin/layout\n *\n * 布局和设置管理系统\n * 提供智能布局容器、6 种布局骨架、设置配置、主题预设等功能\n *\n * 架构:智能容器模式\n * - C_LayoutContainer 根据 layoutMode 自动切换骨架,内置品牌/菜单默认实现\n * - 消费方仅需提供业务组件(垂直菜单、头部、标签页、页脚)\n * - 所有 slot 均可覆盖默认实现\n */\n\n// ============ 主入口组件(C_ 前缀 = 对外暴露) ============\nexport { default as C_LayoutContainer } from \"./components/C_LayoutContainer/index.vue\";\n\n// ============ 配置组件 ============\nexport { default as SettingsDrawer } from \"./components/SettingsDrawer/index.vue\";\n\n// ============ 内置子组件(无 C_ 前缀,高级用户可单独使用) ============\nexport { default as BrandLogo } from \"./components/BrandLogo/index.vue\";\nexport { default as IconMenu } from \"./components/IconMenu/index.vue\";\nexport { default as FloatingMenu } from \"./components/FloatingMenu/index.vue\";\nexport { default as SideMenu } from \"./components/SideMenu/index.vue\";\nexport { default as DrawerMenu } from \"./components/DrawerMenu/index.vue\";\nexport { default as MenuTrigger } from \"./components/MenuTrigger/index.vue\";\nexport { default as ResponsiveMenu } from \"./components/ResponsiveMenu/index.vue\";\n\n// ============ 骨架组件(内部使用,高级用户可单独导入) ============\nexport { default as SideLayout } from \"./components/layouts/SideLayout/index.vue\";\nexport { default as TopLayout } from \"./components/layouts/TopLayout/index.vue\";\nexport { default as MixLayout } from \"./components/layouts/MixLayout/index.vue\";\nexport { default as MixTopLayout } from \"./components/layouts/MixTopLayout/index.vue\";\nexport { default as ReverseHorizontalMixLayout } from \"./components/layouts/ReverseHorizontalMixLayout/index.vue\";\nexport { default as CardLayout } from \"./components/layouts/CardLayout/index.vue\";\n\n// ============ 初始化 ============\nexport { setupLayout } from \"./setup\";\n\n// ============ Store ============\nexport { createSettingsStore, useSettingsStore } from \"./stores/settings\";\n\n// ============ Utils ============\nexport { adjustColor, sanitizeSettingsPatch } from \"./stores/settings\";\nexport {\n sanitizeLayoutSettingsConfig,\n SETTINGS_CONFIG_SCHEMA_VERSION,\n} from \"./core/settings\";\nexport type {\n LayoutSettingsConfig,\n LayoutVisualEffects,\n} from \"./core/settings\";\n\n// ============ Composables ============\nexport { shouldCacheRoute, useLayoutCache } from \"./composables/useLayoutCache\";\nexport type { LayoutCacheOptions } from \"./composables/useLayoutCache\";\nexport {\n useLayoutContext,\n LAYOUT_CONTEXT_KEY,\n DEFAULT_BRAND_CONFIG,\n DRAWER_HANDLER_KEY,\n LAYOUT_SETTINGS_KEY,\n MENU_COLLAPSE_KEY,\n provideLayoutContext,\n} from \"./composables/useLayoutContext\";\nexport type {\n LayoutContext,\n LayoutBrandConfig,\n DrawerHandlers,\n MenuCollapseHandlers,\n} from \"./composables/useLayoutContext\";\nexport {\n createLayoutContext,\n provideLayout,\n} from \"./composables/createLayoutContext\";\nexport type { CreateLayoutContextOptions } from \"./composables/createLayoutContext\";\nexport { useMenuSplit } from \"./composables/useMenuSplit\";\nexport type {\n UseMenuSplitOptions,\n UseMenuSplitReturn,\n} from \"./composables/useMenuSplit\";\n\n// ============ Types ============\nexport type {\n LayoutMode,\n MenuExpandMode,\n TransitionType,\n BorderRadiusSize,\n TagsViewStyle,\n SettingsState,\n ThemePreset,\n SettingsStoreOptions,\n SettingsDrawerActions,\n LayoutInfo,\n PresetColor,\n MenuOptions,\n MenuTag,\n MenuItemType,\n ThemeMode,\n} from \"./types\";\nexport type { SettingsStoreInstance } from \"./stores/settings\";\n\n// ============ Constants ============\nexport {\n PRESET_COLORS,\n THEME_PRESETS,\n LAYOUT_MODES,\n BORDER_RADIUS_MAP,\n TRANSITION_MAP,\n DEFAULT_SETTINGS,\n COLOR_SWATCHES,\n} from \"./constants\";\n\n// ============ 3.x 向后兼容别名(计划在 4.0 移除) ============\nimport SideLayoutCompat from \"./components/layouts/SideLayout/index.vue\";\nimport TopLayoutCompat from \"./components/layouts/TopLayout/index.vue\";\nimport MixLayoutCompat from \"./components/layouts/MixLayout/index.vue\";\nimport MixTopLayoutCompat from \"./components/layouts/MixTopLayout/index.vue\";\nimport ReverseHorizontalMixLayoutCompat from \"./components/layouts/ReverseHorizontalMixLayout/index.vue\";\nimport CardLayoutCompat from \"./components/layouts/CardLayout/index.vue\";\n\n/** @deprecated 请使用 SideLayout;该别名计划在 4.0 移除。 */\nexport const C_SideLayout = SideLayoutCompat;\n/** @deprecated 请使用 TopLayout;该别名计划在 4.0 移除。 */\nexport const C_TopLayout = TopLayoutCompat;\n/** @deprecated 请使用 MixLayout;该别名计划在 4.0 移除。 */\nexport const C_MixLayout = MixLayoutCompat;\n/** @deprecated 请使用 MixTopLayout;该别名计划在 4.0 移除。 */\nexport const C_MixTopLayout = MixTopLayoutCompat;\n/** @deprecated 请使用 ReverseHorizontalMixLayout;该别名计划在 4.0 移除。 */\nexport const C_ReverseHorizontalMixLayout = ReverseHorizontalMixLayoutCompat;\n/** @deprecated 请使用 CardLayout;该别名计划在 4.0 移除。 */\nexport const C_CardLayout = CardLayoutCompat;\n"],"mappings":"yJAiGA,IAAa,EACX,OAAO,iBAAiB,CAGb,EACX,OAAO,kBAAkB,CAQd,EACX,OAAO,gBAAgB,CAmBZ,EACX,OAAO,kBAAkB,CAiB3B,SAAgB,GAAkC,CAChD,IAAM,GAAA,EAAA,EAAA,QAAa,EAAmB,CACtC,GAAI,CAAC,EACH,MAAU,MACR,0FAED,CAEH,OAAO,EAIT,SAAgB,EAAqB,EAAuC,CAE1E,OADA,EAAA,EAAA,SAAQ,EAAoB,EAAQ,CAC7B,EAMT,IAAa,EAAoD,CAC/D,KAAM,QACN,SAAU,GACV,QAAS,GACT,SAAU,QACV,SAAU,GACV,SAAU,QACV,UAAW,GACZ,CCpJD,SAAgB,EACd,EAGA,CACA,OAAO,OAAO,EAAM,MAAS,UAAY,EAAM,KAAK,YAAc,GAwBpE,SAAgB,EAAe,EAA8B,EAAE,CAAE,CAC/D,GAAM,CACJ,cAAe,EAAW,GAC1B,eAAe,GACf,eAAgB,EAAS,IACvB,EAEE,GAAA,EAAA,EAAA,WAAkB,CAClB,EACJ,OAAO,SAAS,EAAS,EAAI,EAAW,EAAI,KAAK,MAAM,EAAS,CAAG,GAC/D,GAAA,EAAA,EAAA,KAA4B,EAAE,CAAC,CAC/B,GAAA,EAAA,EAAA,KAAoB,EAAmB,CAGvC,EAAY,GAAiB,CAC7B,GAAQ,CAAC,EAAY,MAAM,SAAS,EAAK,GAC3C,EAAY,MAAM,KAAK,EAAK,CAGxB,EAAY,MAAM,OAAS,EAAc,OAC3C,EAAY,MAAM,OAAO,CAGvB,GACF,QAAQ,MACN,qBAAqB,EAAK,IAAI,EAAY,MAAM,OAAO,GAAG,EAAc,MAAM,GAC/E,GAMD,EAAe,GAAiB,CACpC,IAAM,EAAQ,EAAY,MAAM,QAAQ,EAAK,CACzC,EAAQ,KACV,EAAY,MAAM,OAAO,EAAO,EAAE,CAC9B,GACF,QAAQ,MAAM,qBAAqB,IAAO,GAM1C,MAAsB,CAC1B,EAAY,MAAQ,EAAE,CAClB,GACF,QAAQ,MAAM,yBAAyB,EAKrC,MAAuB,EAAY,MACzC,GAAI,GAAU,OAAO,OAAW,IAAa,CAC3C,IAAM,EAAc,OACpB,EAAY,eAAiB,EAC7B,EAAY,gBAAkB,EAC9B,EAAY,mBAAqB,GAEjC,EAAA,EAAA,iBAAkB,CACZ,EAAY,iBAAmB,GACjC,OAAO,EAAY,eAEjB,EAAY,kBAAoB,GAClC,OAAO,EAAY,gBAEjB,EAAY,qBAAuB,GACrC,OAAO,EAAY,oBAErB,CAYJ,OARA,EAAA,EAAA,WACQ,CAAC,EAAM,KAAM,EAAM,KAAK,UAAU,KAClC,CACA,EAAiB,EAAM,EAAE,EAAS,EAAM,KAAK,EAEnD,CAAE,UAAW,GAAM,CACpB,CAEM,CACL,cACA,gBACA,WACA,cACA,gBACD,iKCpFH,IAAM,EAAQ,EAQR,EAAO,EAIP,EAAM,GAAkB,CACxB,CAAE,cAAa,iBAAkB,GAAgB,CAGjD,GAAA,EAAA,EAAA,KAAiB,EAAM,UAAU,EACvC,EAAA,EAAA,WACQ,EAAM,UACX,GAAM,CACL,EAAW,MAAQ,GAEtB,CAED,IAAM,GAAA,EAAA,EAAA,cAA2B,EAAW,MAAM,CAE5C,EAAyB,GAAiB,CAC9C,EAAW,MAAQ,EACnB,EAAK,mBAAoB,EAAI,SAU/B,EAAa,CACX,YACA,mBAR2B,CAC3B,IAAM,EAAO,CAAC,EAAW,MACzB,EAAW,MAAQ,EACnB,EAAK,mBAAoB,EAAK,EAM/B,CAAC,0GApDU,EAAA,QAAA,CAAA,CAlCD,YAAA,GAAS,CAAA,2BAiBD,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAAA,EAAA,aAAA,CAAA,CAfb,SAAA,GACA,gBAAc,QACb,mBAAA,EAAA,EAAA,OAAiB,EAAG,CAAC,sBAAsB,MAC3C,OAAA,EAAA,EAAA,OAAO,EAAG,CAAC,aAAa,MACxB,mBAAkB,GAClB,UAAW,EAAA,MACX,qBAAkB,EAClB,OAAA,EAAA,EAAA,gBAAK,mDAAoE,EAAG,CAAC,OAAO,MAAK,aAAA,4CAMtE,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,OAAA,EAAA,EAAA,EAAA,YACuB,EAAA,OAAA,OAAA,CAAxB,UAAW,EAAA,MAAS,CAAA,CAAA,CAAA,uFAiB/B,EAAA,QAAA,CAAA,KAAA,2BAbc,kBAAA,EAAA,OAAA,SAAA,+BAUL,EAAA,eAAA,CAAA,CARD,MAAM,wCAAuC,CAAA,2BAO9C,EAAA,EAAA,EAAA,aAAA,EAAA,KAAA,wBADE,CALO,YAAW,WAAK,EAAA,EAAA,EAAA,aAKvB,EAAA,WAAA,CAJA,MAAA,EAAA,EAAA,OAAM,EAAG,CAAC,eAAe,MAAO,KAAK,qCAGpC,GAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAAA,EAAA,UAAA,CAFA,SAAA,EAAA,EAAA,OAAS,EAAW,CAAG,KAAA,EAAA,EAAA,OAAK,EAAa,sEACnC,EAAS,CAAA,CAAG,IAAK,EAAM,KAAA,CAAA,EAAA,CAAA,KAAA,CAAA,UAAA,MAAA,CAAA,EAAA,CAAA,sDAMzB,EAAA,OAAA,SAAA,mLEK5B,IAAM,EAAQ,EAKR,GAAA,EAAA,EAAA,WAAkB,CAClB,GAAA,EAAA,EAAA,YAAoB,CACpB,EAAM,GAAkB,CAGxB,GAAA,EAAA,EAAA,cAA0B,EAAM,MAAQ,EAAE,CAAC,CAC3C,GAAA,EAAA,EAAA,cAA2B,EAAM,KAAK,CAGtC,EAAa,EAAI,OAEjB,EAAa,CACjB,cAAe,yBACf,mBAAoB,0BACpB,oBAAqB,uBACrB,yBAA0B,uBAC1B,yBAA0B,uBAC1B,cAAe,wBACf,mBAAoB,0BACpB,oBAAqB,uBACrB,yBAA0B,uBAC1B,yBAA0B,uBAC1B,eAAgB,0BAChB,gBAAiB,yBACjB,qBAAsB,0BACtB,yBAA0B,yBAC1B,WAAY,wBACZ,gBAAiB,yBACjB,iBAAkB,uBAClB,sBAAuB,uBACvB,WAAY,OACZ,YAAa,SACb,iBAAkB,OAClB,eAAgB,yBACjB,CAEK,EAAY,CAChB,cAAe,4BACf,mBAAoB,4BACpB,oBAAqB,yBACrB,yBAA0B,yBAC1B,yBAA0B,yBAC1B,cAAe,2BACf,mBAAoB,4BACpB,oBAAqB,yBACrB,yBAA0B,yBAC1B,yBAA0B,yBAC1B,eAAgB,0BAChB,gBAAiB,2BACjB,qBAAsB,2BACtB,yBAA0B,2BAC1B,WAAY,2BACZ,gBAAiB,2BACjB,iBAAkB,yBAClB,sBAAuB,yBACvB,WAAY,OACZ,YAAa,SACb,iBAAkB,OAClB,eAAgB,2BACjB,CAEK,GAAA,EAAA,EAAA,cACJ,EAAW,MAAQ,EAAY,EAChC,CAIK,GAAc,EAAc,EAAO,KACnC,EAAI,mBACN,EAAA,EAAA,GAAe,EAAI,cAAgB,CAAE,KAAM,EAAM,OAAM,CAAC,MAE1D,EAAA,EAAA,GACI,IAAK,CACL,MAAO,EACP,MAAO,CACL,SAAU,GAAG,EAAK,IAClB,QAAS,cACT,WAAY,SACb,CACF,CAAC,CAKA,EAAuB,IAAmC,CAC9D,IAAK,EAAK,MAAQ,GAClB,MAAO,EAAK,MAAM,OAAS,EAAK,MAAQ,GACxC,KAAM,EAAK,MAAM,KAAO,EAAW,EAAK,KAAK,KAAM,GAAG,CAAG,IAAA,GACzD,SAAU,EAAK,UAAU,OACrB,EAAK,SAAS,IAAI,EAAoB,CACtC,IAAA,GACL,EAIK,GAAA,EAAA,EAAA,MAAiC,CACjC,GAAA,EAAA,EAAA,KAAkC,EAAE,CAAC,CACrC,GAAA,EAAA,EAAA,KAAiC,EAAE,CAAC,CAEpC,GAAA,EAAA,EAAA,cACJ,EAAa,MAAM,IAAI,EAAoB,CAC5C,CAEK,GAAA,EAAA,EAAA,cACJ,EAAY,MAAM,IAAK,IAAU,CAC/B,IAAK,EAAK,MAAQ,GAClB,MAAO,EAAK,MAAM,OAAS,EAAK,MAAQ,GACxC,KAAM,EAAK,MAAM,KAAO,EAAW,EAAK,KAAK,KAAM,GAAG,CAAG,IAAA,GACzD,SAAU,EAAK,UAAU,OACrB,EAAK,SAAS,IAAK,IAAW,CAC5B,IAAK,EAAM,MAAQ,GACnB,MAAO,EAAM,MAAM,OAAS,EAAM,MAAQ,GAC1C,KAAM,EAAM,MAAM,KAAO,EAAW,EAAM,KAAK,KAAM,GAAG,CAAG,IAAA,GAC5D,EAAE,CACH,IAAA,GACL,EAAE,CACJ,CAEK,EAAgB,GAAgB,CAChC,GAAK,EAAO,KAAK,EAAI,EAKrB,EAAqB,GAA8B,CACvD,IAKM,EAAQ,EAAK,MAAM,OAAS,EAAK,MAAQ,GAC3C,EAAY,EAChB,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAO,EAAK,WAAW,EAAE,CAG5B,GAAQ,OAAU,GAAQ,OAC1B,GAAQ,OAAU,GAAQ,OAC1B,GAAQ,OAAU,GAAQ,OAC1B,GAAQ,OAAU,GAAQ,OAC1B,GAAQ,OAAU,GAAQ,OAC1B,GAAQ,OAAU,GAAQ,MAE3B,GAAa,GAEb,GAAa,EAGjB,IAAM,EAAY,EAAK,MAAM,KAAO,GAAa,EAEjD,OAAO,EAAY,EAAY,GAAe,EAAc,GAGxD,EAAyB,GAAmC,CAChE,GAAI,CAAC,EAAS,MAAM,OAAQ,MAAO,GAEnC,IAGI,EAAa,EACjB,IAAK,IAAM,KAAQ,EAAS,MAC1B,GAAc,EAAkB,EAAK,CAGvC,GAAI,EAAa,IAAU,EACzB,OAAO,EAAS,MAAM,OAGxB,IAAI,EAAY,EACZ,EAAQ,EACN,EAAY,EAAiB,GAAiB,GAEpD,IAAK,IAAM,KAAQ,EAAS,MAAO,CACjC,IAAM,EAAI,EAAkB,EAAK,CACjC,GAAI,EAAY,GAAK,EACnB,GAAa,EACb,SACK,MAGT,OAAO,KAAK,IAAI,EAAO,EAAE,EAGrB,MAA8B,CAClC,GAAI,CAAC,EAAa,OAAS,CAAC,EAAS,MAAM,OAAQ,OAEnD,IAAM,EAAiB,EAAa,MAAM,YAE1C,GAAI,GAAkB,EAAG,OAEzB,IAAM,EAAQ,EAAsB,EAAe,CACnD,EAAa,MAAQ,EAAS,MAAM,MAAM,EAAG,EAAM,CACnD,EAAY,MAAQ,EAAS,MAAM,MAAM,EAAM,EAK7C,EAAoD,KAClD,MAA2B,CAC3B,GAAa,aAAa,EAAY,CAC1C,EAAc,eAAiB,GAAuB,CAAE,IAAI,EAG1D,EAAwC,YAE5C,EAAA,EAAA,eAAgB,EACd,EAAA,EAAA,cAAe,GAAuB,CAAC,CACnC,EAAa,OAAS,OAAO,eAAmB,KAClD,EAAiB,IAAI,mBAAqB,GAAoB,CAAC,CAC/D,EAAe,QAAQ,EAAa,MAAM,EACjC,OAAO,OAAW,KAC3B,OAAO,iBAAiB,SAAU,EAAoB,CAAE,QAAS,GAAM,CAAC,EAE1E,EAEF,EAAA,EAAA,iBAAkB,CAChB,GAAgB,YAAY,CACxB,OAAO,OAAW,KACpB,OAAO,oBAAoB,SAAU,EAAmB,CAEtD,GAAa,aAAa,EAAY,EAC1C,EAEF,EAAA,EAAA,WACQ,EAAM,UAAA,EAAA,EAAA,cACS,GAAuB,CAAC,CAC7C,CACE,KAAM,GACP,CACF,oDAvPO,MAAA,SA9BG,eAAJ,IAAI,EAAe,MAAM,6CAUtB,MATN,EASM,CAPI,EAAA,MAAe,OAAM,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAM3B,EAAA,MAAA,CAAA,OALA,KAAK,aACJ,MAAO,EAAA,MACP,QAAS,EAAA,MACT,kBAAiB,EAAA,MACjB,iBAAc,oFAKX,EAAA,MAAY,OAAM,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAgBd,EAAA,UAAA,CAAA,OAfT,QAAS,EAAA,MACV,UAAU,aACV,QAAQ,QACP,SAAQ,8BAWH,EAAA,EAAA,EAAA,oBAAA,MATN,EASM,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OADM,EAAA,QAAA,CAAA,CAPD,KAAA,GAAK,MAAM,wDAMX,CAAA,GAAA,AAAA,EAAA,KAAA,EAAA,EAAA,EAAA,oBAAA,OAAA,CAJD,MAAM,YAAW,CAAA,0BACR,OAAA,0BACA,OAAA,0BACA,OAAA,2gBE6EzB,IAAM,EAAM,GAAkB,CACxB,CAAE,cAAa,iBAAkB,GAAgB,CAEjD,EAAa,EAAI,OACjB,GAAA,EAAA,EAAA,cAAuB,EAAI,MAAM,MAAM,CACvC,GAAA,EAAA,EAAA,cAA8B,EAAI,aAAa,MAAM,CACrD,GAAA,EAAA,EAAA,cAAgC,EAAI,eAAe,MAAM,CACzD,GAAA,EAAA,EAAA,cAA4B,EAAI,WAAW,MAAM,CACjD,GAAA,EAAA,EAAA,cAAgC,EAAI,eAAe,MAAM,CACzD,EAAQ,CAAE,GAAG,EAAsB,GAAG,EAAI,MAAO,2GAzB/C,MAhFN,EAgFM,0BA9BE,MAAA,CA/CJ,OAAA,EAAA,EAAA,gBAAK,CAAC,aAAY,EAAA,EAAA,EAAA,OACT,EAAU,CAAA,aAAA,cAAA,CAAA,CAAA,CAAA,CAAA,0BAmCb,MAhCN,EAgCM,EAAA,EAAA,EAAA,YADG,EAAA,OAAA,OAAA,EAAA,KAAA,0BANC,MAvBN,EAuBM,CAAA,AAAA,EAAA,MAAA,EAAA,EAAA,oBAtByB,MAAA,CAAxB,MAAM,YAAW,CAAA,KAAA,GAAA,CAEd,EAAM,WAAQ,SAAgB,EAAM,UAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAWpC,QAAA,OAVL,IAAK,EAAM,QACX,MAAO,EAAM,UAAQ,GACrB,OAAQ,EAAM,UAAQ,GACvB,SAAA,GACA,KAAA,GACA,MAAA,GACA,YAAA,GACA,MAAM,cACP,uBAED,EAAA,EAAA,EAEa,EAAM,UAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAMjB,MAAA,OALC,IAAK,EAAM,QACX,MAAO,EAAM,UAAQ,GACrB,OAAQ,EAAM,UAAQ,GACvB,MAAM,aACL,IAAK,EAAM,MAAI,8EAMd,MAHN,EAGM,EAAA,EAAA,EAAA,oBAF6C,OAAjD,GAAA,EAAA,EAAA,iBAA6B,EAAM,KAAI,CAAA,EAAA,EAAA,EAAA,EAAA,oBACiB,OAAxD,GAAA,EAAA,EAAA,iBAAgC,EAAM,SAAQ,CAAA,EAAA,CAAA,CAAA,iCAEd,MAAA,CAA7B,MAAM,iBAAgB,CAAA,KAAA,GAAA,8BASzB,MAJN,EAIM,EAAA,EAAA,EAAA,YADG,EAAA,OAAA,OAAA,EAAA,KAAA,EAAA,EAAA,EAAA,aAD2B,EAAA,CAAf,KAAM,EAAA,MAAK,CAAA,KAAA,EAAA,CAAA,OAAA,CAAA,CAAA,CAAA,CAAA,CAAA,kBAKJ,EAAA,OAAA,eAAA,KAKtB,EAAA,QAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAKF,MAAA,OAJJ,MAAM,sBACL,OAAA,EAAA,EAAA,gBAAK,CAAA,OAAA,GAAe,EAAA,MAAc,IAAA,CAAA,oBAEV,EAAA,OAAA,YAAA,CAAA,CAAA,EAAA,GAAA,EAAA,EAAA,oBAAA,GAAA,GAAA,+BAqBjB,EAAA,QAAA,CAAA,KAAA,2BANS,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAAA,EAAA,eAAA,CAAA,CAVD,MAAM,eAAc,CAAA,2BAS5B,EAAA,EAAA,EAAA,oBAAA,MARN,EAQM,EAAA,EAAA,EAAA,aADS,EAAA,KAAA,wBADE,CALO,YAAW,WAAK,EAAA,EAAA,EAAA,aAKvB,EAAA,WAAA,CAJA,KAAM,EAAA,MAAgB,KAAK,qCAG1B,GAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAAA,EAAA,UAAA,CAFA,SAAA,EAAA,EAAA,OAAS,EAAW,CAAG,KAAA,EAAA,EAAA,OAAK,EAAa,sEACnC,EAAS,CAAA,CAAG,IAAK,EAAM,KAAA,CAAA,EAAA,CAAA,KAAA,CAAA,UAAA,MAAA,CAAA,EAAA,CAAA,uCAQjC,EAAA,OAAA,EAAA,EAAA,YACQ,EAAA,OAAA,SAAA,EAAA,CAAA,IAAA,GAAA,IAAA,GAAA,EAAA,EAAA,EAAA,EAAA,oBAAA,GAAA,GAAA,CAAA,CAAA,aE1C9B,SAAgB,EACd,EACA,EACS,CACT,GAAI,CAAC,EAAU,MAAO,GACtB,IAAM,EAAe,EAAS,MAAM,IAAI,CAAC,OAAO,QAAQ,CAClD,EAAkB,EAAY,MAAM,IAAI,CAAC,OAAO,QAAQ,CAE9D,OADI,EAAa,SAAW,EAAU,EAAgB,SAAW,EAC1D,EAAa,OACjB,EAAS,IAAU,EAAgB,KAAW,EAChD,CAiBH,SAAgB,EAAa,EAAkD,CAC7E,GAAM,CAAE,QAAO,qBAAqB,IAAU,EACxC,GAAA,EAAA,EAAA,WAAkB,CAClB,GAAA,EAAA,EAAA,YAAoB,CAEpB,GAAA,EAAA,EAAA,KAA8B,GAAG,CACjC,GAAA,EAAA,EAAA,KAA0C,KAAK,CAC/C,GAAA,EAAA,EAAA,KAAqB,GAAM,CAG3B,GAAA,EAAA,EAAA,cACE,EAAgB,OAAS,EAAoB,OAAS,KAC7D,CAIK,EAAoB,GACjB,EAAoB,EAAU,EAAM,KAAK,CAK5C,EAAqB,GAA6C,CACtE,IAAK,IAAM,KAAQ,EAEjB,GADI,EAAiB,EAAK,KAAK,EAC3B,EAAK,UAAU,QACH,EAAkB,EAAK,SAAS,CACnC,OAAO,EAGtB,OAAO,MAKH,GAAA,EAAA,EAAA,cACJ,EAAM,MAAM,KAAM,GAAS,EAAK,OAAS,EAAgB,MAAM,CAChE,CAEK,GAAA,EAAA,EAAA,cACE,EAAoB,OAAO,UAAY,EAAE,CAChD,CAIK,EAAwB,GAAsB,CAC9C,EAAK,YACT,EAAA,EAAA,SAAY,EAAmB,CAEzB,EAAK,UAAY,EAAK,SAAS,OAAS,EACtC,EAAe,OAAS,EAAgB,QAAU,EAAK,KAEzD,EAAe,MAAQ,IAGvB,EAAgB,MAAQ,EAAK,MAAQ,GACrC,EAAe,MAAQ,KAIzB,EAAgB,MAAQ,EAAK,MAAQ,GACrC,EAAe,MAAQ,GACnB,EAAK,MAAM,EAAO,KAAK,EAAK,KAAK,GAInC,CAAC,EAAK,UAAY,EAAK,SAAS,SAAW,IACzC,EAAK,MAAM,EAAO,KAAK,EAAK,KAAK,GAKrC,EAAyB,GAAsB,CAC/C,CAAC,EAAK,UAAY,EAAK,MAAM,EAAO,KAAK,EAAK,KAAK,EAKnD,MAAgC,CACpC,IAAM,EAAW,EAAM,MACjB,EAAmB,EAAkB,EAAS,CAChD,GACF,EAAgB,MAAQ,EAAiB,MAAQ,IAEjD,EAAA,EAAA,SAAY,EAAmB,EAAI,EAAiB,UAAU,SAC5D,EAAe,MAAQ,KAEhB,EAAS,OAAS,GAAK,CAAC,EAAgB,QACjD,EAAgB,MAAQ,EAAS,GAAG,MAAQ,KAoBhD,OAhBA,EAAA,EAAA,WACQ,EAAM,SACN,GAAyB,CAC/B,CAAE,UAAW,GAAM,CACpB,EAED,EAAA,EAAA,OACE,EACC,GAAa,CACR,GAAY,EAAS,OAAS,GAAG,GAAyB,EAEhE,CAAE,UAAW,GAAM,CACpB,EAED,EAAA,EAAA,eAAgB,GAAyB,CAAC,CAEnC,CACL,kBACA,sBACA,qBACA,kBAEA,kBACA,iBACA,uBACA,wBACA,mBACD,sjBCtCH,IAAM,EAAM,GAAkB,CACxB,CAAE,cAAa,iBAAkB,GAAgB,CAEjD,EAAa,EAAI,OACjB,GAAA,EAAA,EAAA,cAAuB,EAAI,MAAM,MAAM,CACvC,GAAA,EAAA,EAAA,cAA4B,EAAI,WAAW,MAAM,CACjD,GAAA,EAAA,EAAA,cAAgC,EAAI,eAAe,MAAM,CACzD,EAAQ,CAAE,GAAG,EAAsB,GAAG,EAAI,MAAO,CAEjD,EAAY,EAAa,CAC7B,MAAO,EAAI,MACX,oBAAA,EAAA,EAAA,cAAmC,GAAK,CACzC,CAAC,CAGI,EACJ,EAAI,gBAAA,EAAA,EAAA,iBACY,CACd,KAAM,aACN,MAAO,CAAE,KAAM,OAAQ,KAAM,CAAE,KAAM,OAAQ,QAAS,GAAI,CAAE,CAC5D,MAAM,EAAO,CACX,WAAA,EAAA,EAAA,GACI,IAAK,CACL,MAAO,EAAM,KACb,MAAO,CACL,SAAU,GAAG,EAAM,KAAK,IACxB,QAAS,cACT,WAAY,SACZ,eAAgB,SACjB,CACF,CAAC,EAEP,CAAC,2GAhDI,MAjIN,GAiIM,0BAjEE,MAAA,CA7DJ,OAAA,EAAA,EAAA,gBAAK,CAAC,mBAAkB,EAAA,EAAA,EAAA,OACf,EAAU,CAAA,aAAA,cAAA,CAAA,CAAA,CAAA,CAAA,EAAA,EAAA,EAAA,oBA4Bb,MAzBN,GAyBM,EAAA,EAAA,EAAA,YADG,EAAA,OAAA,OAAA,EAAA,KAAA,CAAA,AAAA,EAAA,MAAA,EAAA,EAAA,oBAtBwB,MAAA,CAAxB,MAAM,YAAW,CAAA,KAAA,GAAA,CAEd,EAAM,WAAQ,SAAgB,EAAM,UAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAWpC,QAAA,OAVL,IAAK,EAAM,QACX,MAAO,EAAM,UAAQ,GACrB,OAAQ,EAAM,UAAQ,GACvB,SAAA,GACA,KAAA,GACA,MAAA,GACA,YAAA,GACA,MAAM,cACP,uBAED,EAAA,GAAA,EAEa,EAAM,UAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAMjB,MAAA,OALC,IAAK,EAAM,QACX,MAAO,EAAM,UAAQ,GACrB,OAAQ,EAAM,UAAQ,GACvB,MAAM,aACL,IAAK,EAAM,MAAI,iFAkChB,MA5BN,GA4BM,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBADE,EAAA,SAAA,MAAA,EAAA,EAAA,YAzBW,EAAA,MAAR,+CAyBH,MAAA,CAxBH,IAAK,EAAK,KACX,OAAA,EAAA,EAAA,gBAAK,CAAC,kBAAiB,CAAA,QAAA,EAAA,EAAA,OACO,EAAS,CAAC,gBAAgB,QAAU,EAAK,KAAA,CAAA,CAAA,CAGvE,KAAK,SACJ,SAAU,EAAK,SAAQ,GAAA,EACvB,gBAAe,EAAK,UAAY,IAAA,GAChC,QAAK,IAAA,EAAA,EAAA,OAAE,EAAS,CAAC,qBAAqB,EAAI,CAC1C,UAAO,EAAA,EAAA,EAAA,UAAA,IAAA,EAAA,EAAA,OAAQ,EAAS,CAAC,qBAAqB,EAAI,CAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eAAA,IAAA,EAAA,EAAA,OAC3B,EAAS,CAAC,qBAAqB,EAAI,CAAA,CAAA,UAAA,CAAA,CAAA,CAAA,QAAA,CAAA,CAAA,4BAYrD,MAVN,GAUM,CAPI,EAAK,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADZ,EAAU,CAAA,CAAA,OAEd,KAAM,EAAK,KAAK,KAChB,KAAM,kEAIA,OAFT,IAAA,EAAA,EAAA,kBACG,EAAK,MAAM,OAAK,IAAA,GAAA,CAAA,EAAA,EAAA,CAAA,EAAA,EAAA,EAAA,oBAGsC,OAA3D,IAAA,EAAA,EAAA,iBAAiC,EAAK,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,GAAA,iCA8C1C,EAAA,WAAA,CAxCD,KAAK,aAAY,CAAA,2BAuCrB,EAAA,EAAA,EAAA,OArCE,EAAS,CAAC,eAAe,QAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAqC3B,MAAA,OApCJ,OAAA,EAAA,EAAA,gBAAK,CAAC,0BAAyB,EAAA,EAAA,EAAA,OACtB,EAAU,CAAA,aAAA,cAAA,CAAA,CAAA,4BAYb,MAVN,GAUM,EAAA,EAAA,EAAA,OAPI,EAAS,CAAC,gBAAgB,OAAO,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADxC,EAAU,CAAA,CAAA,OAEd,MAAA,EAAA,EAAA,OAAM,EAAS,CAAC,gBAAgB,MAAM,KAAK,KAC3C,KAAM,+EAIA,OAFT,IAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,OACE,EAAS,CAAC,gBAAgB,OAAO,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,EAAA,EAAA,EAAA,oBAwB1C,MArBN,GAqBM,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBADE,EAAA,SAAA,MAAA,EAAA,EAAA,aAAA,EAAA,EAAA,OAlBY,EAAS,CAAC,gBAAgB,OAAO,UAAQ,EAAA,CAAlD,+CAkBH,MAAA,CAjBH,IAAK,EAAM,KACZ,OAAA,EAAA,EAAA,gBAAK,CAAC,mBAAkB,CAAA,QAAA,EAAA,EAAA,OACN,EAAS,CAAC,iBAAiB,EAAM,KAAI,CAAA,CAAA,CAAA,CACvD,KAAK,OACJ,SAAU,EAAM,SAAQ,GAAA,EACxB,gBAAe,EAAM,UAAY,IAAA,GACjC,QAAK,IAAA,EAAA,EAAA,OAAE,EAAS,CAAC,sBAAsB,EAAK,CAC5C,UAAO,EAAA,EAAA,EAAA,UAAA,IAAA,EAAA,EAAA,OAAQ,EAAS,CAAC,sBAAsB,EAAK,CAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eAAA,IAAA,EAAA,EAAA,OAC7B,EAAS,CAAC,sBAAsB,EAAK,CAAA,CAAA,UAAA,CAAA,CAAA,CAAA,QAAA,CAAA,CAAA,GAIrD,EAAM,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADb,EAAU,CAAA,CAAA,OAEd,KAAM,EAAM,KAAK,KACjB,KAAM,+EAEmD,OAA5D,IAAA,EAAA,EAAA,iBAAiC,EAAM,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,GAAA,iFAyBpD,MAlBN,GAkBM,EAAA,EAAA,EAAA,YAjBkB,EAAA,OAAA,SAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAgBZ,EAAA,QAAA,CAAA,KAAA,2BALS,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAAA,EAAA,eAAA,CAAA,CARD,MAAM,wCAAuC,CAAA,2BAO9C,EAAA,EAAA,EAAA,aAAA,EAAA,KAAA,wBADE,CALO,YAAW,WAAK,EAAA,EAAA,EAAA,aAKvB,EAAA,WAAA,CAJA,KAAM,EAAA,MAAgB,KAAK,qCAG1B,GAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAAA,EAAA,UAAA,CAFA,SAAA,EAAA,EAAA,OAAS,EAAW,CAAG,KAAA,EAAA,EAAA,OAAK,EAAa,sEACnC,EAAS,CAAA,CAAG,IAAK,EAAM,KAAA,CAAA,EAAA,CAAA,KAAA,CAAA,UAAA,MAAA,CAAA,EAAA,CAAA,qCAM/B,EAAA,OAAA,EAAA,EAAA,YACQ,EAAA,OAAA,SAAA,EAAA,CAAA,IAAA,GAAA,IAAA,GAAA,EAAA,EAAA,EAAA,EAAA,oBAAA,GAAA,GAAA,CAAA,CAAA,ikBEwBhC,IAAM,EAAM,GAAkB,CACxB,CAAE,cAAa,iBAAkB,GAAgB,CAEjD,EAAa,EAAI,OACjB,GAAA,EAAA,EAAA,cAAuB,EAAI,MAAM,MAAM,CACvC,GAAA,EAAA,EAAA,cAA8B,EAAI,aAAa,MAAM,CACrD,GAAA,EAAA,EAAA,cAAgC,EAAI,eAAe,MAAM,CACzD,GAAA,EAAA,EAAA,cAA4B,EAAI,WAAW,MAAM,CACjD,GAAA,EAAA,EAAA,cAAgC,EAAI,eAAe,MAAM,CACzD,EAAQ,CAAE,GAAG,EAAsB,GAAG,EAAI,MAAO,CAEjD,EAAY,EAAa,CAC7B,MAAO,EAAI,MACX,oBAAA,EAAA,EAAA,cAAmC,GAAM,CAC1C,CAAC,CAEI,EACJ,EAAI,gBAAA,EAAA,EAAA,iBACY,CACd,KAAM,aACN,MAAO,CAAE,KAAM,OAAQ,KAAM,CAAE,KAAM,OAAQ,QAAS,GAAI,CAAE,CAC5D,MAAM,EAAO,CACX,WAAA,EAAA,EAAA,GACI,IAAK,CACL,MAAO,EAAM,KACb,MAAO,CACL,SAAU,GAAG,EAAM,KAAK,IACxB,QAAS,cACT,WAAY,SACZ,eAAgB,SACjB,CACF,CAAC,EAEP,CAAC,2GAlDI,MAjIN,GAiIM,EAAA,EAAA,EAAA,oBA9DE,MAAA,CAhEJ,OAAA,EAAA,EAAA,gBAAK,CAAC,sBAAqB,EAAA,EAAA,EAAA,OAClB,EAAU,CAAA,aAAA,cAAA,CAAA,CAAA,CAAA,CAAA,EAAA,EAAA,EAAA,oBA4Bb,MAzBN,GAyBM,EAAA,EAAA,EAAA,YADG,EAAA,OAAA,OAAA,EAAA,KAAA,CAAA,AAAA,EAAA,MAAA,EAAA,EAAA,oBAtBwB,MAAA,CAAxB,MAAM,YAAW,CAAA,KAAA,GAAA,CAEd,EAAM,WAAQ,SAAgB,EAAM,UAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAWpC,QAAA,OAVL,IAAK,EAAM,QACX,MAAO,EAAM,UAAQ,GACrB,OAAQ,EAAM,UAAQ,GACvB,SAAA,GACA,KAAA,GACA,MAAA,GACA,YAAA,GACA,MAAM,cACP,uBAED,EAAA,EAAA,EAEa,EAAM,UAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAMjB,MAAA,OALC,IAAK,EAAM,QACX,MAAO,EAAM,UAAQ,GACrB,OAAQ,EAAM,UAAQ,GACvB,MAAM,aACL,IAAK,EAAM,MAAI,gFAqChB,MA/BN,EA+BM,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBADE,EAAA,SAAA,MAAA,EAAA,EAAA,YA5BW,EAAA,MAAR,+CA4BH,MAAA,CA3BH,IAAK,EAAK,KACX,OAAA,EAAA,EAAA,gBAAK,CAAC,kBAAiB,CAAA,QAAA,EAAA,EAAA,OACO,EAAS,CAAC,gBAAgB,QAAU,EAAK,KAAA,CAAA,CAAA,CAGvE,KAAK,SACJ,SAAU,EAAK,SAAQ,GAAA,EACvB,gBAAe,EAAK,UAAY,IAAA,GAChC,gBAAA,EAAA,EAAA,OAA2B,EAAS,CAAC,gBAAgB,QAAU,EAAK,KAAI,OAAY,IAAA,GAGpF,QAAK,IAAA,EAAA,EAAA,OAAE,EAAS,CAAC,qBAAqB,EAAI,CAC1C,UAAO,EAAA,EAAA,EAAA,UAAA,IAAA,EAAA,EAAA,OAAQ,EAAS,CAAC,qBAAqB,EAAI,CAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eAAA,IAAA,EAAA,EAAA,OAC3B,EAAS,CAAC,qBAAqB,EAAI,CAAA,CAAA,UAAA,CAAA,CAAA,CAAA,QAAA,CAAA,CAAA,4BAYrD,MAVN,EAUM,CAPI,EAAK,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADZ,EAAU,CAAA,CAAA,OAEd,KAAM,EAAK,KAAK,KAChB,KAAM,kEAIA,OAFT,IAAA,EAAA,EAAA,kBACG,EAAK,MAAM,OAAK,IAAA,GAAA,CAAA,EAAA,EAAA,CAAA,EAAA,EAAA,EAAA,oBAGsC,OAA3D,IAAA,EAAA,EAAA,iBAAiC,EAAK,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,EAAA,wCAgEjD,MA1DN,GA0DM,0BA7BE,MAAA,CA1BJ,OAAA,EAAA,EAAA,gBAAK,CAAC,aAAY,EAAA,EAAA,EAAA,OACT,EAAU,CAAA,aAAA,cAAA,CAAA,CAAA,CAAA,CAAA,0BAWb,MARN,GAQM,EAAA,EAAA,EAAA,YADG,EAAA,OAAA,QAAA,EAAA,KAAA,EAAA,EAAA,EAAA,oBAFC,MAHN,GAGM,EAAA,EAAA,EAAA,oBAF6C,OAAjD,IAAA,EAAA,EAAA,iBAA6B,EAAM,KAAI,CAAA,EAAA,EAAA,EAAA,EAAA,oBACiB,OAAxD,IAAA,EAAA,EAAA,iBAAgC,EAAM,SAAQ,CAAA,EAAA,CAAA,CAAA,CAAA,AAAA,EAAA,MAAA,EAAA,EAAA,oBAEd,MAAA,CAA7B,MAAM,iBAAgB,CAAA,KAAA,GAAA,CAAA,CAAA,CAAA,CAAA,0BAYzB,MAPN,GAOM,EAAA,EAAA,EAAA,YADG,EAAA,OAAA,WAAA,EAAA,KAAA,EAAA,EAAA,EAAA,OAHG,EAAS,CAAC,mBAAmB,MAAM,OAAM,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAE/C,EAAA,OADC,MAAA,EAAA,EAAA,OAAM,EAAS,CAAC,mBAAmB,8EAMd,EAAA,OAAA,eAAA,KAKtB,EAAA,QAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAKF,MAAA,OAJJ,MAAM,sBACL,OAAA,EAAA,EAAA,gBAAK,CAAA,OAAA,GAAe,EAAA,MAAc,IAAA,CAAA,oBAEV,EAAA,OAAA,YAAA,CAAA,CAAA,EAAA,GAAA,EAAA,EAAA,oBAAA,GAAA,GAAA,+BAoBjB,EAAA,QAAA,CAAA,CAhBD,MAAM,iBAAgB,CAAA,2BAWZ,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAAA,EAAA,eAAA,CAAA,CAVD,MAAM,eAAc,CAAA,2BAS5B,EAAA,EAAA,EAAA,oBAAA,MARN,GAQM,EAAA,EAAA,EAAA,aADS,EAAA,KAAA,wBADE,CALO,YAAW,WAAK,EAAA,EAAA,EAAA,aAKvB,EAAA,WAAA,CAJA,KAAM,EAAA,MAAgB,KAAK,qCAG1B,GAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAAA,EAAA,UAAA,CAFA,SAAA,EAAA,EAAA,OAAS,EAAW,CAAG,KAAA,EAAA,EAAA,OAAK,EAAa,sEACnC,EAAS,CAAA,CAAG,IAAK,EAAM,KAAA,CAAA,EAAA,CAAA,KAAA,CAAA,UAAA,MAAA,CAAA,EAAA,CAAA,uCAOjC,EAAA,OAAA,EAAA,EAAA,YACQ,EAAA,OAAA,SAAA,EAAA,CAAA,IAAA,GAAA,IAAA,GAAA,EAAA,EAAA,EAAA,EAAA,oBAAA,GAAA,GAAA,CAAA,CAAA,uwBE4GhC,IAAM,EAAM,GAAkB,CACxB,CAAE,cAAa,iBAAkB,GAAgB,CAEjD,EAAa,EAAI,OACjB,GAAA,EAAA,EAAA,cAAuB,EAAI,MAAM,MAAM,CACvC,GAAA,EAAA,EAAA,cAA8B,EAAI,aAAa,MAAM,CACrD,GAAA,EAAA,EAAA,cAAgC,EAAI,eAAe,MAAM,CACzD,GAAA,EAAA,EAAA,cAA4B,EAAI,WAAW,MAAM,CACjD,GAAA,EAAA,EAAA,cAAgC,EAAI,eAAe,MAAM,CACzD,EAAQ,CAAE,GAAG,EAAsB,GAAG,EAAI,MAAO,CAEjD,EAAY,EAAa,CAC7B,MAAO,EAAI,MACX,oBAAA,EAAA,EAAA,cAAmC,GAAM,CAC1C,CAAC,CAGI,GAAA,EAAA,EAAA,KAAkB,GAAM,CACxB,MAAuB,CAC3B,EAAY,MAAQ,CAAC,EAAY,OAG7B,EACJ,EAAI,gBAAA,EAAA,EAAA,iBACY,CACd,KAAM,aACN,MAAO,CAAE,KAAM,OAAQ,KAAM,CAAE,KAAM,OAAQ,QAAS,GAAI,CAAE,CAC5D,MAAM,EAAO,CACX,WAAA,EAAA,EAAA,GACI,IAAK,CACL,MAAO,EAAM,KACb,MAAO,CACL,SAAU,GAAG,EAAM,KAAK,IACxB,QAAS,cACT,WAAY,SACZ,eAAgB,SACjB,CACF,CAAC,EAEP,CAAC,2GAxDI,MAzNN,GAyNM,0BAvKE,MAAA,CA/CJ,OAAA,EAAA,EAAA,gBAAK,CAAC,aAAY,EAAA,EAAA,EAAA,OACT,EAAU,CAAA,aAAA,cAAA,CAAA,CAAA,CAAA,CAAA,0BAmCb,MAhCN,GAgCM,EAAA,EAAA,EAAA,YADG,EAAA,OAAA,OAAA,EAAA,KAAA,0BANC,MAvBN,GAuBM,CAAA,AAAA,EAAA,MAAA,EAAA,EAAA,oBAtByB,MAAA,CAAxB,MAAM,YAAW,CAAA,KAAA,GAAA,CAEd,EAAM,WAAQ,SAAgB,EAAM,UAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAWpC,QAAA,OAVL,IAAK,EAAM,QACX,MAAO,EAAM,UAAQ,GACrB,OAAQ,EAAM,UAAQ,GACvB,SAAA,GACA,KAAA,GACA,MAAA,GACA,YAAA,GACA,MAAM,cACP,uBAED,EAAA,GAAA,EAEa,EAAM,UAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAMjB,MAAA,OALC,IAAK,EAAM,QACX,MAAO,EAAM,UAAQ,GACrB,OAAQ,EAAM,UAAQ,GACvB,MAAM,aACL,IAAK,EAAM,MAAI,+EAMd,MAHN,GAGM,EAAA,EAAA,EAAA,oBAF6C,OAAjD,IAAA,EAAA,EAAA,iBAA6B,EAAM,KAAI,CAAA,EAAA,EAAA,EAAA,EAAA,oBACiB,OAAxD,IAAA,EAAA,EAAA,iBAAgC,EAAM,SAAQ,CAAA,EAAA,CAAA,CAAA,iCAEd,MAAA,CAA7B,MAAM,iBAAgB,CAAA,KAAA,GAAA,8BASzB,MAJN,GAIM,EAAA,EAAA,EAAA,YADG,EAAA,OAAA,WAAA,EAAA,KAAA,EAAA,EAAA,EAAA,aAD2B,EAAA,CAAf,KAAM,EAAA,MAAK,CAAA,KAAA,EAAA,CAAA,OAAA,CAAA,CAAA,CAAA,CAAA,CAAA,kBAKJ,EAAA,OAAA,eAAA,KAKtB,EAAA,QAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAKF,MAAA,OAJJ,MAAM,sBACL,OAAA,EAAA,EAAA,gBAAK,CAAA,OAAA,GAAe,EAAA,MAAc,IAAA,CAAA,oBAEV,EAAA,OAAA,YAAA,CAAA,CAAA,EAAA,GAAA,EAAA,EAAA,oBAAA,GAAA,GAAA,0BA8JrB,MA1JN,GA0JM,+BAxIM,EAAA,QAAA,CAAA,CAhBD,MAAM,iBAAgB,CAAA,2BAWZ,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAAA,EAAA,eAAA,CAAA,CAVD,MAAM,eAAc,CAAA,2BAS5B,EAAA,EAAA,EAAA,oBAAA,MARN,GAQM,EAAA,EAAA,EAAA,aADS,EAAA,KAAA,wBADE,CALO,YAAW,WAAK,EAAA,EAAA,EAAA,aAKvB,EAAA,WAAA,CAJA,KAAM,EAAA,MAAgB,KAAK,qCAG1B,GAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAAA,EAAA,UAAA,CAFA,SAAA,EAAA,EAAA,OAAS,EAAW,CAAG,KAAA,EAAA,EAAA,OAAK,EAAa,sEACnC,EAAS,CAAA,CAAG,IAAK,EAAM,KAAA,CAAA,EAAA,CAAA,KAAA,CAAA,UAAA,MAAA,CAAA,EAAA,CAAA,uCAOjC,EAAA,OAAA,EAAA,EAAA,YACQ,EAAA,OAAA,SAAA,EAAA,CAAA,IAAA,GAAA,IAAA,GAAA,EAAA,EAAA,EAAA,EAAA,oBAAA,GAAA,GAAA,CAAA,CAAA,mBAMlB,EAAS,CAAC,mBAAmB,MAAM,OAAM,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAuB3C,MAAA,OAtBJ,OAAA,EAAA,EAAA,gBAAK,CAAC,iBAAgB,EAAA,EAAA,EAAA,OACF,EAAU,CAAA,aAAA,cAAA,CAAA,OAAA,CAAsD,EAAA,MAAW,CAAA,CAAA,CAAA,CAI9F,QAAO,EACR,KAAK,SACL,SAAS,IACR,gBAAa,CAAG,EAAA,MAChB,UAAO,EAAA,EAAA,EAAA,UAAQ,EAAc,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eACN,EAAc,CAAA,UAAA,CAAA,CAAA,CAAA,QAAA,CAAA,CAAA,4BAW/B,OAAA,CARJ,OAAA,EAAA,EAAA,gBAAK,gCAAyE,EAAA,MAAW,YAAA,mDAM1D,OAAA,CAA1B,MAAM,aAAY,CAAA,KAAA,GAAA,EAAA,EAAA,EAAA,oBACQ,OAAA,CAA1B,MAAM,aAAY,CAAA,KAAA,GAAA,CAAA,CAAA,CAAA,EAAA,CAAA,CAAA,GAAA,GAAA,GAAA,EAAA,EAAA,oBAAA,GAAA,GAAA,aAMpB,EAAS,CAAC,mBAAmB,MAAM,OAAM,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAwG3C,MAAA,OAvGJ,OAAA,EAAA,EAAA,gBAAK,CAAC,wBAAuB,CAAA,UACR,EAAA,MAAW,CAAA,CAAA,4BAqG1B,MAAA,CAlGJ,OAAA,EAAA,EAAA,gBAAK,CAAC,gBAAe,EAAA,EAAA,EAAA,OACZ,EAAU,CAAA,aAAA,cAAA,CAAA,CAAA,CAAA,CAAA,EAAA,EAAA,EAAA,oBAkBb,MAAA,CAdJ,OAAA,EAAA,EAAA,gBAAK,CAAC,iBAAgB,EAAA,EAAA,EAAA,OACb,EAAU,CAAA,aAAA,cAAA,CAAA,CAAA,CAAA,CAAA,EAAA,EAAA,EAAA,oBASb,MAPN,GAOM,EAAA,EAAA,EAAA,OAJI,EAAS,CAAC,oBAAoB,OAAO,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OAD5C,EAAU,CAAA,CAAA,OAEd,MAAA,EAAA,EAAA,OAAM,EAAS,CAAC,oBAAoB,MAAM,KAAK,KAC/C,KAAM,iFAKF,OAFT,IAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,OACE,EAAS,CAAC,oBAAoB,OAAO,MAAM,OAAK,KAAA,CAAA,EAAA,CAAA,CAAA,EAAA,EAAA,EAAA,EAAA,oBAgF9C,MA3EN,GA2EM,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBADO,EAAA,SAAA,MAAA,EAAA,EAAA,aAAA,EAAA,EAAA,OAxEO,EAAS,CAAC,mBAAmB,MAAtC,+DACD,EAAM,KAAA,CAAA,CAIJ,EAAM,UAAY,EAAM,SAAS,OAAM,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAyCzC,MA1CN,GA0CM,EAAA,EAAA,EAAA,oBA9BE,MARN,GAQM,CALI,EAAM,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADb,EAAU,CAAA,CAAA,OAEd,KAAM,EAAM,KAAK,KACjB,KAAM,+EAE2B,OAAA,MAAA,EAAA,EAAA,iBAA3B,EAAM,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBA8BtB,EAAA,SAAA,MAAA,EAAA,EAAA,YA3Be,EAAM,SAAlB,+CA2BH,MAAA,CA1BH,IAAK,EAAS,KACf,OAAA,EAAA,EAAA,gBAAK,CAAC,qBAAoB,CAAA,QAAA,EAAA,EAAA,OACY,EAAS,CAAC,iBAAiB,EAAS,KAAI,CAAA,CAAA,CAAA,CAG9E,KAAK,SACJ,SAAU,EAAS,SAAQ,GAAA,EAC3B,gBAAe,EAAS,UAAY,IAAA,GACpC,gBAAA,EAAA,EAAA,OAAmC,EAAS,CAAC,iBAAiB,EAAS,KAAI,CAAA,OAAyD,IAAA,GAKpI,QAAK,IAAA,EAAA,EAAA,OAAE,EAAS,CAAC,sBAAsB,EAAQ,CAC/C,UAAO,EAAA,EAAA,EAAA,UAAA,IAAA,EAAA,EAAA,OAAQ,EAAS,CAAC,sBAAsB,EAAQ,CAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eAAA,IAAA,EAAA,EAAA,OACX,EAAS,CAAC,sBAAsB,EAAQ,CAAA,CAAA,UAAA,CAAA,CAAA,CAAA,QAAA,CAAA,CAAA,GAM7E,EAAS,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADhB,EAAU,CAAA,CAAA,OAEd,KAAM,EAAS,KAAK,KACpB,KAAM,+EAEiD,OAA1D,IAAA,EAAA,EAAA,iBAA4B,EAAS,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,GAAA,wDA2B9C,MAAA,OArBJ,OAAA,EAAA,EAAA,gBAAK,CAAC,YAAW,CAAA,QAAA,EAAA,EAAA,OACmB,EAAS,CAAC,iBAAiB,EAAM,KAAI,CAAA,CAAA,CAAA,CAGzE,KAAK,SACJ,SAAU,EAAM,SAAQ,GAAA,EACxB,gBAAe,EAAM,UAAY,IAAA,GACjC,gBAAA,EAAA,EAAA,OAAiC,EAAS,CAAC,iBAAiB,EAAM,KAAI,CAAA,OAAa,IAAA,GAGnF,QAAK,IAAA,EAAA,EAAA,OAAE,EAAS,CAAC,sBAAsB,EAAK,CAC5C,UAAO,EAAA,EAAA,EAAA,UAAA,IAAA,EAAA,EAAA,OAAQ,EAAS,CAAC,sBAAsB,EAAK,CAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eAAA,IAAA,EAAA,EAAA,OAC7B,EAAS,CAAC,sBAAsB,EAAK,CAAA,CAAA,UAAA,CAAA,CAAA,CAAA,QAAA,CAAA,CAAA,GAIrD,EAAM,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADb,EAAU,CAAA,CAAA,OAEd,KAAM,EAAM,KAAK,KACjB,KAAM,+EAE8C,OAAvD,IAAA,EAAA,EAAA,iBAA4B,EAAM,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,GAAA,EAAA,CAAA,GAAA,gwBEc7D,IAAM,EAAM,GAAkB,CACxB,CAAE,cAAa,iBAAkB,GAAgB,CACjD,GAAA,EAAA,EAAA,YAAoB,CAEpB,EAAa,EAAI,OACjB,GAAA,EAAA,EAAA,cAAuB,EAAI,MAAM,MAAM,CACvC,GAAA,EAAA,EAAA,cAA8B,EAAI,aAAa,MAAM,CACrD,GAAA,EAAA,EAAA,cAAgC,EAAI,eAAe,MAAM,CACzD,GAAA,EAAA,EAAA,cAA4B,EAAI,WAAW,MAAM,CACjD,GAAA,EAAA,EAAA,cAAgC,EAAI,eAAe,MAAM,CACzD,EAAQ,CAAE,GAAG,EAAsB,GAAG,EAAI,MAAO,CAGjD,GAAA,EAAA,EAAA,KAAqB,GAAM,CAC3B,GAAA,EAAA,EAAA,KAAsD,KAAK,CAE3D,MAAuB,CACvB,EAAU,OAAO,aAAa,EAAU,MAAM,CAClD,EAAU,MAAQ,eAAiB,CACjC,EAAe,MAAQ,IACtB,IAAI,EAGH,MAAwB,CAC5B,AAEE,EAAU,SADV,aAAa,EAAU,MAAM,CACX,MAEpB,EAAe,MAAQ,IAGnB,EAAkB,GAAsB,CACxC,CAAC,EAAK,UAAY,EAAK,OACzB,EAAO,KAAK,EAAK,KAAK,CACtB,EAAe,MAAQ,KAIrB,EACJ,EAAI,gBAAA,EAAA,EAAA,iBACY,CACd,KAAM,aACN,MAAO,CAAE,KAAM,OAAQ,KAAM,CAAE,KAAM,OAAQ,QAAS,GAAI,CAAE,CAC5D,MAAM,EAAO,CACX,WAAA,EAAA,EAAA,GACI,IAAK,CACL,MAAO,EAAM,KACb,MAAO,CACL,SAAU,GAAG,EAAM,KAAK,IACxB,QAAS,cACT,WAAY,SACZ,eAAgB,SACjB,CACF,CAAC,EAEP,CAAC,QAEJ,EAAA,EAAA,iBAAkB,CACZ,EAAU,OAAO,aAAa,EAAU,MAAM,EAClD,qGA5EM,MA9MN,GA8MM,0BAlJE,MAAA,CAzDJ,OAAA,EAAA,EAAA,gBAAK,CAAC,aAAY,EAAA,EAAA,EAAA,OACT,EAAU,CAAA,aAAA,cAAA,CAAA,CAAA,CAAA,CAAA,EAAA,EAAA,EAAA,oBAoDb,MAlDN,GAkDM,EAAA,EAAA,EAAA,oBAjCE,MAAA,CAdJ,MAAM,qBACL,aAAY,EACZ,aAAY,qBAWN,EAAA,OAAA,eAAA,EAAA,KAAA,CAAA,AAAA,EAAA,MAAA,EAAA,EAAA,oBADC,MAAA,CAPD,MAAM,iBAAgB,CAAA,EAAA,EAAA,EAAA,oBAMlB,OAAA,CAJD,MAAM,iBAAgB,CAAA,0BACb,OAAA,0BACA,OAAA,0BACA,OAAA,mCAoCd,EAAA,OAAA,OAAA,EAAA,KAAA,0BANC,MAvBN,GAuBM,CAAA,AAAA,EAAA,MAAA,EAAA,EAAA,oBAtByB,MAAA,CAAxB,MAAM,YAAW,CAAA,KAAA,GAAA,CAEd,EAAM,WAAQ,SAAgB,EAAM,UAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAWpC,QAAA,OAVL,IAAK,EAAM,QACX,MAAO,EAAM,UAAQ,GACrB,OAAQ,EAAM,UAAQ,GACvB,SAAA,GACA,KAAA,GACA,MAAA,GACA,YAAA,GACA,MAAM,cACP,uBAED,EAAA,GAAA,EAEa,EAAM,UAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAMjB,MAAA,OALC,IAAK,EAAM,QACX,MAAO,EAAM,UAAQ,GACrB,OAAQ,EAAM,UAAQ,GACvB,MAAM,aACL,IAAK,EAAM,MAAI,+EAMd,MAHN,GAGM,EAAA,EAAA,EAAA,oBAF6C,OAAjD,IAAA,EAAA,EAAA,iBAA6B,EAAM,KAAI,CAAA,EAAA,EAAA,EAAA,EAAA,oBACiB,OAAxD,IAAA,EAAA,EAAA,iBAAgC,EAAM,SAAQ,CAAA,EAAA,CAAA,CAAA,iCAEd,MAAA,CAA7B,MAAM,iBAAgB,CAAA,KAAA,GAAA,yBAKH,EAAA,OAAA,eAAA,EAAA,CAAA,IAAA,GAAA,GAAA,CAAA,CAAA,EAAA,CAKtB,EAAA,QAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAKF,MAAA,OAJJ,MAAM,sBACL,OAAA,EAAA,EAAA,gBAAK,CAAA,OAAA,GAAe,EAAA,MAAc,IAAA,CAAA,oBAEV,EAAA,OAAA,YAAA,EAAA,CAAA,IAAA,GAAA,GAAA,CAAA,CAAA,EAAA,GAAA,EAAA,EAAA,oBAAA,GAAA,GAAA,0BAoHrB,MAAA,CA/GJ,OAAA,EAAA,EAAA,gBAAK,CAAC,cAAa,EAAA,EAAA,EAAA,OACD,EAAU,CAAA,aAAA,cAAA,CAAA,QAAoD,EAAA,MAAc,CAAA,CAAA,CAAA,CAI7F,aAAY,EACZ,aAAY,qBAwGN,EAAA,OAAA,cAAA,EAAA,KAAA,CAAA,AAAA,EAAA,MAAA,EAAA,EAAA,oBAzFC,MAAA,CAXD,MAAM,gBAAe,CAAA,EAAA,EAAA,EAAA,oBAUlB,MAAA,CATD,MAAM,eAAc,CAAA,EAAA,EAAA,EAAA,oBAOhB,OAAA,CALD,MAAM,YAAW,CAAA,0BACR,OAAA,0BACA,OAAA,0BACA,OAAA,0BACA,OAAA,4BAEE,OAAA,KAAX,OAAI,CAAA,CAAA,CAAA,CAAA,GAAA,EAAA,EAAA,EAAA,oBA0FR,MArFN,GAqFM,EAAA,EAAA,EAAA,oBADE,MAnFN,GAmFM,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBADE,EAAA,SAAA,MAAA,EAAA,EAAA,YAhFe,EAAA,MAAZ,+CAgFH,MAAA,CA/EH,IAAK,EAAS,KACf,MAAM,2CAmBA,MAAA,CAfJ,MAAM,kBACN,KAAK,OACJ,SAAU,EAAS,SAAQ,GAAA,EAC3B,gBAAe,EAAS,UAAY,IAAA,GACpC,QAAK,GAAE,EAAe,EAAQ,CAC9B,UAAO,EAAA,EAAA,EAAA,UAAA,GAAQ,EAAe,EAAQ,CAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eAAA,GACf,EAAe,EAAQ,CAAA,CAAA,UAAA,CAAA,CAAA,CAAA,QAAA,CAAA,CAAA,GAIvC,EAAS,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADhB,EAAU,CAAA,CAAA,OAEd,KAAM,EAAS,KAAK,KACpB,KAAM,+EAE8B,OAAA,MAAA,EAAA,EAAA,iBAA9B,EAAS,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,GAAA,CAMvB,EAAS,UAAY,EAAS,SAAS,OAAM,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAqD/C,MAvDN,GAuDM,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBAjCE,EAAA,SAAA,MAAA,EAAA,EAAA,YAjBW,EAAS,SAAjB,+CAiBH,MAAA,CAhBH,IAAK,EAAK,KACX,MAAM,YACN,KAAK,OACJ,SAAU,EAAK,SAAQ,GAAA,EACvB,gBAAe,EAAK,UAAY,IAAA,GAChC,QAAK,GAAE,EAAe,EAAI,CAC1B,UAAO,EAAA,EAAA,EAAA,UAAA,GAAQ,EAAe,EAAI,CAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eAAA,GACX,EAAe,EAAI,CAAA,CAAA,UAAA,CAAA,CAAA,CAAA,QAAA,CAAA,CAAA,GAInC,EAAK,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADZ,EAAU,CAAA,CAAA,OAEd,KAAM,EAAK,KAAK,KAChB,KAAM,+EAE6C,OAAtD,IAAA,EAAA,EAAA,iBAA4B,EAAK,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,GAAA,uDAiCnC,EAAA,SAAA,MAAA,EAAA,EAAA,YA5BM,EAAS,SAAjB,sEACQ,EAAK,KAAA,CAAA,CAGZ,EAAK,UAAY,EAAK,SAAS,OAAM,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAuBvC,MAxBN,GAwBM,EAAA,EAAA,EAAA,oBApBmD,MAAvD,IAAA,EAAA,EAAA,iBAA8B,EAAK,MAAM,MAAK,CAAA,EAAA,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBAmBxC,EAAA,SAAA,MAAA,EAAA,EAAA,YAjBc,EAAK,SAAhB,+CAiBH,MAAA,CAhBH,IAAK,EAAQ,KACd,MAAM,yBACN,KAAK,OACJ,SAAU,EAAQ,SAAQ,GAAA,EAC1B,gBAAe,EAAQ,UAAY,IAAA,GACnC,QAAK,GAAE,EAAe,EAAO,CAC7B,UAAO,EAAA,EAAA,EAAA,UAAA,GAAQ,EAAe,EAAO,CAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eAAA,GACd,EAAe,EAAO,CAAA,CAAA,UAAA,CAAA,CAAA,CAAA,QAAA,CAAA,CAAA,GAItC,EAAQ,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADf,EAAU,CAAA,CAAA,OAEd,KAAM,EAAQ,KAAK,KACnB,KAAM,+EAEgD,OAAzD,IAAA,EAAA,EAAA,iBAA4B,EAAQ,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,GAAA,6IA8B3D,MAlBN,GAkBM,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OADM,EAAA,QAAA,CAAA,CAhBD,MAAM,iBAAgB,CAAA,2BAWZ,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAAA,EAAA,eAAA,CAAA,CAVD,MAAM,iCAAgC,CAAA,2BAS9C,EAAA,EAAA,EAAA,oBAAA,MARN,GAQM,EAAA,EAAA,EAAA,aADS,EAAA,KAAA,wBADE,CALO,YAAW,WAAK,EAAA,EAAA,EAAA,aAKvB,EAAA,WAAA,CAJA,KAAM,EAAA,MAAgB,KAAK,qCAG1B,GAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAAA,EAAA,UAAA,CAFA,SAAA,EAAA,EAAA,OAAS,EAAW,CAAG,KAAA,EAAA,EAAA,OAAK,EAAa,sEACnC,EAAS,CAAA,CAAG,IAAK,EAAM,KAAA,CAAA,EAAA,CAAA,KAAA,CAAA,UAAA,MAAA,CAAA,EAAA,CAAA,uCAOjC,EAAA,OAAA,EAAA,EAAA,YACQ,EAAA,OAAA,SAAA,EAAA,CAAA,IAAA,GAAA,GAAA,EAAA,EAAA,EAAA,EAAA,oBAAA,GAAA,GAAA,CAAA,CAAA,mKEpFhC,IAAM,EAAM,GAAkB,CAExB,EAAa,EAAI,WACjB,EAAS,EAAI,OAGb,GAAA,EAAA,EAAA,KAAwB,GAAM,CAC9B,GAAA,EAAA,EAAA,UAAyB,CAC7B,QAAW,EAAI,WAAW,OAAS,EAAkB,MACrD,IAAM,GAAmB,CACnB,EAAI,UAAW,EAAI,UAAU,MAAQ,EACpC,EAAkB,MAAQ,GAElC,CAAC,CAGI,EAAmB,CACvB,YAAa,EACb,sBAAwB,GAAuB,CAC7C,EAAc,MAAQ,GAEzB,QACD,EAAA,EAAA,SAAQ,EAAmB,EAAiB,EAE5C,EAAA,EAAA,SAAQ,eAAgB,EAAiB,oDA/CjC,MAAA,CA3FA,OAAA,EAAA,EAAA,gBAAK,CAAA,oBAAA,EAAA,EAAA,OAAuB,EAAM,CAAA,YAAA,aAAA,CAAA,CAAA,CAAA,EAAA,EAAA,EAAA,OAEpB,EAAU,GAAA,SAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAOf,EAAA,OAPoC,UAAW,EAAA,uCAAa,MAAA,sBAE5D,MAAA,EAAA,EAAA,aACsC,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,OAAA,CAA5B,UAAW,EAAA,MAAa,CAAA,CAAA,CAAA,CAElC,QAAA,EAAA,EAAA,aAA6B,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,SAAA,CAAA,CAAA,CAC7B,QAAA,EAAA,EAAA,aAA6B,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,SAAA,CAAA,CAAA,OALxBA,EAAAA,OAAO,KAAA,MAAO,4BAAyB,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,OAAA,CAAA,CAAA,oDASnC,EAAU,GAAA,QAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAYpB,EAAA,CAAA,IAAA,EAAA,EAAA,EAAA,EAAA,aAAA,CAAA,EAAA,EAAA,CAAA,CAXMA,EAAAA,OAAO,KAAA,MAAO,4BAAyB,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,OAAA,CAAA,CAAA,iBACvCA,EAAAA,OAAM,YAAA,MAAe,4BAEnC,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,WAAA,CAAA,CAAA,iBACcA,EAAAA,OAAM,gBAAA,MAAmB,oCAEvC,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,eAAA,CAAA,CAAA,iBACcA,EAAAA,OAAM,aAAA,MAAgB,iCAEpC,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,YAAA,CAAA,CAAA,iBACcA,EAAAA,OAAO,OAAA,MAAS,8BAA6B,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,SAAA,CAAA,CAAA,sCAIzC,EAAU,GAAA,QAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAIpB,EAAA,CAAA,IAAA,EAAA,EAAA,EAAA,EAAA,aAAA,CAFC,QAAA,EAAA,EAAA,aAA6B,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,SAAA,CAAA,CAAA,OADxBA,EAAAA,OAAO,KAAA,MAAO,4BAAyB,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,OAAA,CAAA,CAAA,iBAEvCA,EAAAA,OAAO,OAAA,MAAS,8BAA6B,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,SAAA,CAAA,CAAA,sCAItC,EAAU,GAAA,YAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAapB,EAAA,CAAA,IAAA,EAAA,EAAA,EAAA,EAAA,aAAA,CAAA,EAAA,EAAA,CAAA,CAZGA,EAAAA,OAAO,KAAA,MAAO,4BAAyB,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,OAAA,CAAA,CAAA,iBACvCA,EAAAA,OAAO,MAAA,MAAQ,6BAA2B,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,QAAA,CAAA,CAAA,iBAC1CA,EAAAA,OAAM,YAAA,MAAe,gCAEnC,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,WAAA,CAAA,CAAA,iBACcA,EAAAA,OAAM,gBAAA,MAAmB,oCAEvC,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,eAAA,CAAA,CAAA,iBACcA,EAAAA,OAAM,aAAA,MAAgB,iCAEpC,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,YAAA,CAAA,CAAA,iBACcA,EAAAA,OAAO,OAAA,MAAS,8BAA6B,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,SAAA,CAAA,CAAA,sCAKlD,EAAU,GAAA,2BAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAaM,EAAA,CAAA,IAAA,EAAA,EAAA,EAAA,EAAA,aAAA,CAAA,EAAA,EAAA,CAAA,CAXXA,EAAAA,OAAO,KAAA,MAAO,4BAAyB,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,OAAA,CAAA,CAAA,iBACvCA,EAAAA,OAAM,YAAA,MAAe,gCAEnC,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,WAAA,CAAA,CAAA,iBACcA,EAAAA,OAAM,gBAAA,MAAmB,oCAEvC,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,eAAA,CAAA,CAAA,iBACcA,EAAAA,OAAM,aAAA,MAAgB,iCAEpC,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,YAAA,CAAA,CAAA,iBACcA,EAAAA,OAAO,OAAA,MAAS,8BAA6B,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,SAAA,CAAA,CAAA,sCAIxC,EAAU,GAAA,gBAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,aAepB,EAAA,CAAA,IAAA,EAAA,EAAA,EAAA,EAAA,aAAA,CAAA,EAAA,EAAA,CAAA,CAdKA,EAAAA,OAAM,gBAAA,MAAmB,oCAEvC,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,eAAA,CAAA,CAAA,iBACcA,EAAAA,OAAO,KAAA,MAAO,4BAAyB,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,OAAA,CAAA,CAAA,iBACvCA,EAAAA,OAAM,gBAAA,MAAmB,oCAEvC,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,eAAA,CAAA,CAAA,iBACcA,EAAAA,OAAM,aAAA,MAAgB,iCAEpC,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,YAAA,CAAA,CAAA,iBACcA,EAAAA,OAAM,eAAA,MAAkB,mCAEtC,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,cAAA,CAAA,CAAA,iBACcA,EAAAA,OAAO,OAAA,MAAS,8BAA6B,EAAA,EAAA,EAAA,YAAA,EAAA,OAAA,SAAA,CAAA,CAAA,sEASzD,MALN,GAKM,CAAA,GAAA,AAAA,EAAA,KAAA,EAAA,EAAA,EAAA,oBADE,MAAA,CAHD,MAAM,sBAAqB,CAAA,EAAA,EAAA,EAAA,oBACQ,MAAA,CAAjC,MAAM,mBAAkB,CAAC,KAAE,EAAA,EAAA,EAAA,oBACU,MAAA,CAArC,MAAM,oBAAmB,CAAC,QAAK,CAAA,CAAA,GAAA,CAAA,CAAA,CAAA,EAAA,CAAA,EAAA,KE/F/B,EAA+B,CAC1C,CAAE,KAAM,MAAO,MAAO,UAAW,CACjC,CAAE,KAAM,MAAO,MAAO,UAAW,CACjC,CAAE,KAAM,MAAO,MAAO,UAAW,CACjC,CAAE,KAAM,MAAO,MAAO,UAAW,CACjC,CAAE,KAAM,MAAO,MAAO,UAAW,CACjC,CAAE,KAAM,KAAM,MAAO,UAAW,CAChC,CAAE,KAAM,MAAO,MAAO,UAAW,CACjC,CAAE,KAAM,KAAM,MAAO,UAAW,CACjC,CAGY,EAA+B,CAC1C,CACE,KAAM,MACN,KAAM,KACN,aAAc,UACf,CACD,CACE,KAAM,MACN,KAAM,KACN,aAAc,UACf,CACD,CACE,KAAM,MACN,KAAM,KACN,aAAc,UACf,CACD,CACE,KAAM,MACN,KAAM,KACN,aAAc,UACf,CACD,CACE,KAAM,MACN,KAAM,KACN,aAAc,UACf,CACD,CACE,KAAM,MACN,KAAM,KACN,aAAc,UACf,CACF,CAGY,GAA6B,CACxC,CACE,KAAM,OACN,MAAO,OACP,YAAa,YACb,KAAM,iCACP,CACD,CACE,KAAM,MACN,MAAO,OACP,YAAa,WACb,KAAM,2BACP,CACD,CACE,KAAM,MACN,MAAO,OACP,YAAa,cACb,KAAM,+BACP,CACD,CACE,KAAM,UACN,MAAO,OACP,YAAa,YACb,KAAM,uCACP,CACD,CACE,KAAM,yBACN,MAAO,OACP,YAAa,YACb,KAAM,kCACP,CACD,CACE,KAAM,cACN,MAAO,OACP,YAAa,UACb,KAAM,qBACP,CACF,CAGY,EAAoB,CAC/B,MAAO,MACP,OAAQ,MACR,MAAO,MACR,CAGY,EAAiB,CAC5B,KAAM,iBACN,MAAO,aACP,KAAM,YACN,KAAM,GACP,CAGY,EAAkC,CAE7C,UAAW,QACX,aAAc,UACd,aAAc,SACd,eAAgB,QAChB,iBAAkB,GAGlB,WAAY,OACZ,eAAgB,SAChB,UAAW,GACX,YAAa,GACb,eAAgB,GAChB,mBAAoB,GACpB,aAAc,GACd,eAAgB,GAChB,cAAe,UACf,WAAY,GACZ,aAAc,IACd,sBAAuB,GACvB,aAAc,GAGd,cAAe,GACf,QAAS,QACV,CAKY,EAAiB,EAAc,IAAK,GAAM,EAAE,MAAM,CC9IzD,GAAgB,CACpB,UAAW,IAAI,IAAI,CAAC,QAAS,OAAQ,SAAS,CAAC,CAC/C,aAAc,IAAI,IAAI,CAAC,QAAS,SAAU,QAAQ,CAAC,CACnD,eAAgB,IAAI,IAAI,CAAC,OAAQ,QAAS,OAAQ,OAAO,CAAC,CAC1D,WAAY,IAAI,IAAI,CAClB,OACA,MACA,MACA,UACA,yBACA,cACD,CAAC,CACF,eAAgB,IAAI,IAAI,CAAC,SAAU,QAAQ,CAAC,CAC5C,cAAe,IAAI,IAAI,CAAC,UAAW,OAAQ,QAAQ,CAAC,CACrD,CAEK,GAAqD,IAAI,IAAI,CACjE,mBACA,YACA,cACA,iBACA,qBACA,eACA,aACA,gBACD,CAAC,CAEI,GAEF,CACF,eAAgB,CAAC,GAAI,IAAI,CACzB,aAAc,CAAC,IAAK,IAAI,CACxB,sBAAuB,CAAC,GAAI,IAAI,CAChC,aAAc,CAAC,GAAI,IAAI,CACxB,CAGY,GAAiC,EAqB9C,SAAgB,EAAsB,EAAwC,CAC5E,GAAI,CAAC,GAAS,OAAO,GAAU,UAAY,MAAM,QAAQ,EAAM,CAC7D,MAAU,UAAU,iBAAiB,CAGvC,IAAM,EAAS,EACT,EAAiC,EAAE,CACnC,GAAU,EAA0B,IAAmB,CAC1D,EAAmC,GAAO,GAG7C,IAAK,GAAM,CAAC,EAAK,KAAY,OAAO,QAAQ,GAAc,CAAE,CAC1D,IAAM,EAAQ,EAAO,GACjB,OAAU,IAAA,GACd,IAAI,OAAO,GAAU,UAAY,CAAC,EAAQ,IAAI,EAAe,CAC3D,MAAU,WAAW,UAAU,EAAI,IAAI,OAAO,EAAM,GAAG,CAEzD,EAAO,EAA4B,EAAM,EAG3C,IAAK,IAAM,KAAO,GAAkB,CAClC,IAAM,EAAQ,EAAO,GACjB,OAAU,IAAA,GACd,IAAI,OAAO,GAAU,UACnB,MAAU,UAAU,OAAO,EAAI,SAAS,CAE1C,EAAO,EAAK,EAAM,EAGpB,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,GAAc,CAAE,CACxD,IAAM,EAAQ,EAAO,GACjB,OAAU,IAAA,GACd,IACE,OAAO,GAAU,UACjB,CAAC,OAAO,SAAS,EAAM,EACvB,EAAQ,EAAM,IACd,EAAQ,EAAM,GAEd,MAAU,WAAW,OAAO,EAAI,SAAS,CAE3C,EAAO,EAA4B,EAAM,EAG3C,GAAI,EAAO,eAAiB,IAAA,GAAW,CACrC,GACE,OAAO,EAAO,cAAiB,UAC/B,CAAC,oBAAoB,KAAK,EAAO,aAAa,CAE9C,MAAU,WAAW,8BAA8B,CAErD,EAAO,aAAe,EAAO,aAG/B,GAAI,EAAO,UAAY,IAAA,GAAW,CAChC,GAAI,OAAO,EAAO,SAAY,UAAY,EAAO,QAAQ,OAAS,GAChE,MAAU,UAAU,2BAA2B,CAEjD,EAAO,QAAU,EAAO,QAG1B,OAAO,EAOT,SAAgB,EACd,EACsB,CACtB,GAAI,CAAC,GAAS,OAAO,GAAU,UAAY,MAAM,QAAQ,EAAM,CAC7D,MAAU,UAAU,eAAe,CAGrC,IAAM,EAAS,EACT,EAA+B,EAAE,CAEvC,GAAI,EAAO,gBAAkB,IAAA,GAAW,CACtC,GACE,OAAO,EAAO,eAAkB,UAChC,CAAC,OAAO,UAAU,EAAO,cAAc,EACvC,EAAO,cAAgB,GACvB,EAAO,cAAA,EAEP,MAAU,WAAW,aAAa,CAEpC,EAAO,cAAgB,EAAO,cAG5B,EAAO,WAAa,IAAA,KACtB,EAAO,SAAW,EAAsB,EAAO,SAAS,EAG1D,IAAK,IAAM,IAAO,CAAC,OAAQ,YAAY,CAAW,CAChD,IAAM,EAAQ,EAAO,GACjB,OAAU,IAAA,GACd,IAAI,OAAO,GAAU,UACnB,MAAU,UAAU,GAAG,EAAI,SAAS,CAEtC,EAAO,GAAO,GAGhB,GAAI,EAAO,YAAc,IAAA,GAAW,CAClC,GACE,CAAC,EAAO,WACR,OAAO,EAAO,WAAc,UAC5B,MAAM,QAAQ,EAAO,UAAU,CAE/B,MAAU,UAAU,kBAAkB,CAExC,IAAM,EAAY,EAAO,UACzB,GACE,OAAO,EAAU,SAAY,WAC7B,OAAO,EAAU,MAAS,UAC1B,EAAU,KAAK,OAAS,IAExB,MAAU,UAAU,iBAAiB,CAEvC,EAAO,UAAY,CACjB,QAAS,EAAU,QACnB,KAAM,EAAU,KACjB,CAGH,OAAO,EAOT,SAAgB,EAAY,EAAe,EAAwB,CACjE,GAAI,OAAO,GAAU,SAAU,MAAO,UACtC,GAAI,CAAC,OAAO,SAAS,EAAO,CAAE,OAAO,EACrC,IAAM,EAAmB,KAAK,MAAM,EAAO,CACrC,EAAe,EAAM,MAAM,CAC7B,EAAM,EAAa,WAAW,IAAI,CAAG,EAAa,MAAM,EAAE,CAAG,EAOjE,GANI,EAAI,SAAW,IACjB,EAAM,EACH,MAAM,GAAG,CACT,IAAK,GAAc,EAAY,EAAU,CACzC,KAAK,GAAG,EAET,CAAC,mBAAmB,KAAK,EAAI,CAAE,OAAO,EAE1C,IAAM,EAAQ,SAAS,EAAK,GAAG,CACzB,EAAM,KAAK,IAAI,IAAK,KAAK,IAAI,GAAI,GAAS,IAAM,EAAiB,CAAC,CAClE,EAAQ,KAAK,IACjB,IACA,KAAK,IAAI,GAAK,GAAS,EAAK,KAAU,EAAiB,CACxD,CACK,EAAO,KAAK,IAChB,IACA,KAAK,IAAI,GAAI,EAAQ,KAAY,EAAiB,CACnD,CACD,MAAO,KAAM,GAAO,GAAO,GAAS,EAAK,GAAM,SAAS,GAAG,CAAC,SAAS,EAAG,IAAI,GC9L9E,SAAgB,EAAoB,EAAgC,EAAE,CAAE,CACtE,GAAM,CAAE,KAAK,WAAY,WAAW,EAAE,CAAE,qBAAsB,EAC9D,GAAI,OAAO,GAAO,UAAY,CAAC,EAAG,MAAM,CACtC,MAAU,WAAW,yBAAyB,CAIhD,IAAM,EAAgB,CACpB,GAAG,EACH,GAAG,EAAsB,EAAS,CACnC,CAED,OAAA,EAAA,EAAA,aAAmB,MAAU,CAI3B,IAAM,GAAA,EAAA,EAAA,KAA2B,EAAc,UAAU,CACnD,GAAA,EAAA,EAAA,KAA2B,EAAc,aAAa,CACtD,GAAA,EAAA,EAAA,KAAqC,EAAc,aAAa,CAChE,GAAA,EAAA,EAAA,KAAqC,EAAc,eAAe,CAClE,GAAA,EAAA,EAAA,KAAgC,EAAc,iBAAiB,CAG/D,GAAA,EAAA,EAAA,KAA6B,EAAc,WAAW,CACtD,GAAA,EAAA,EAAA,KAAqC,EAAc,eAAe,CAClE,GAAA,EAAA,EAAA,KAAyB,EAAc,UAAU,CACjD,GAAA,EAAA,EAAA,KAA2B,EAAc,YAAY,CACrD,GAAA,EAAA,EAAA,KAA8B,EAAc,eAAe,CAC3D,GAAA,EAAA,EAAA,KAAkC,EAAc,mBAAmB,CACnE,GAAA,EAAA,EAAA,KAA4B,EAAc,aAAa,CACvD,GAAA,EAAA,EAAA,KAA6B,EAAc,eAAe,CAC1D,GAAA,EAAA,EAAA,KAAmC,EAAc,cAAc,CAC/D,GAAA,EAAA,EAAA,KAA0B,EAAc,WAAW,CACnD,GAAA,EAAA,EAAA,KAA2B,EAAc,aAAa,CACtD,GAAA,EAAA,EAAA,KACJ,EAAc,sBACf,CACK,GAAA,EAAA,EAAA,KAA2B,EAAc,aAAa,CAGtD,GAAA,EAAA,EAAA,KAA6B,EAAc,cAAc,CACzD,GAAA,EAAA,EAAA,KAAsB,EAAc,QAAQ,CAK5C,GAAA,EAAA,EAAA,cACE,EAAkB,EAAa,OACtC,CAGK,GAAA,EAAA,EAAA,cACE,EAAiB,OAAS,EAAe,QAAU,OAC1D,CAGK,GAAA,EAAA,EAAA,cACJ,EAAuB,MAAQ,EAAe,EAAe,OAAS,GACvE,CAGK,GAAA,EAAA,EAAA,eAA+C,CACnD,UAAW,EAAU,MACrB,aAAc,EAAa,MAC3B,aAAc,EAAa,MAC3B,eAAgB,EAAe,MAC/B,iBAAkB,EAAiB,MACnC,WAAY,EAAW,MACvB,eAAgB,EAAe,MAC/B,UAAW,EAAU,MACrB,YAAa,EAAY,MACzB,eAAgB,EAAe,MAC/B,mBAAoB,EAAmB,MACvC,aAAc,EAAa,MAC3B,eAAgB,EAAe,MAC/B,cAAe,EAAc,MAC7B,WAAY,EAAW,MACvB,aAAc,EAAa,MAC3B,sBAAuB,EAAsB,MAC7C,aAAc,EAAa,MAC3B,cAAe,EAAc,MAC7B,QAAS,EAAQ,MAClB,EAAE,CAOG,MAAyB,CAC7B,GAAI,OAAO,SAAa,IAAa,OAErC,IAAM,EAAO,SAAS,gBAGtB,EAAK,MAAM,YAAY,kBAAmB,EAAa,MAAM,CAC7D,EAAK,MAAM,YACT,wBACA,EAAY,EAAa,MAAO,GAAG,CACpC,CACD,EAAK,MAAM,YACT,0BACA,EAAY,EAAa,MAAO,IAAI,CACrC,CAGD,EAAK,MAAM,YAAY,kBAAmB,EAAkB,MAAM,CAGlE,EAAK,MAAM,YAAY,kBAAmB,GAAG,EAAa,MAAM,IAAI,CACpE,EAAK,MAAM,YACT,4BACA,GAAG,EAAsB,MAAM,IAChC,CACD,EAAK,MAAM,YAAY,kBAAmB,GAAG,EAAa,MAAM,IAAI,CACpE,EAAK,MAAM,YAAY,qBAAsB,GAAG,EAAe,MAAM,IAAI,EAkG3E,OAjBA,EAAA,EAAA,OACE,CACE,EACA,EACA,EACA,EACA,EACA,EACD,KACK,CACJ,GAAkB,EAEpB,CAAE,UAAW,GAAM,CACpB,CAIM,CAEL,YACA,eACA,eACA,iBACA,mBACA,aACA,iBACA,YACA,cACA,iBACA,qBACA,eACA,iBACA,gBACA,aACA,eACA,wBACA,eACA,gBACA,UAGA,oBACA,iBACA,yBACA,gBAGA,mBACA,YA1HkB,KAAO,IAAwB,CAEjD,EAAa,MAAQ,EAAO,aAGxB,EAAO,WACL,EAAO,SAAS,eAClB,EAAa,MAAQ,EAAO,SAAS,cACnC,EAAO,SAAS,iBAClB,EAAe,MAAQ,EAAO,SAAS,gBACrC,EAAO,SAAS,iBAAmB,IAAA,KACrC,EAAe,MAAQ,EAAO,SAAS,gBACrC,EAAO,SAAS,eAAiB,IAAA,KACnC,EAAa,MAAQ,EAAO,SAAS,cACnC,EAAO,SAAS,gBAClB,EAAc,MAAQ,EAAO,SAAS,iBA4G1C,kBArG0B,CAC1B,EAAU,MAAQ,EAAc,UAChC,EAAa,MAAQ,EAAc,aACnC,EAAa,MAAQ,EAAc,aACnC,EAAe,MAAQ,EAAc,eACrC,EAAiB,MAAQ,EAAc,iBAEvC,EAAW,MAAQ,EAAc,WACjC,EAAe,MAAQ,EAAc,eACrC,EAAU,MAAQ,EAAc,UAChC,EAAY,MAAQ,EAAc,YAClC,EAAe,MAAQ,EAAc,eACrC,EAAmB,MAAQ,EAAc,mBACzC,EAAa,MAAQ,EAAc,aACnC,EAAe,MAAQ,EAAc,eACrC,EAAc,MAAQ,EAAc,cACpC,EAAW,MAAQ,EAAc,WACjC,EAAa,MAAQ,EAAc,aACnC,EAAsB,MAAQ,EAAc,sBAC5C,EAAa,MAAQ,EAAc,aAEnC,EAAc,MAAQ,EAAc,cACpC,EAAQ,MAAQ,EAAc,SAgF9B,gBA1EsB,KAAO,IAAoB,CACjD,EAAsB,CAAE,UAAW,EAAM,CAAC,CAC1C,IAAM,EAAe,EAAU,MAC/B,EAAU,MAAQ,EAClB,GAAI,CACE,GACF,MAAM,EAAkB,EAAK,OAExB,EAAO,CAEd,KADA,GAAU,MAAQ,EACZ,IAiER,mBA1D2B,CAC3B,EAAU,MAAQ,CAAC,EAAU,OA0D9B,EACD,CAMJ,IAAa,EAAmB,GAAqB,CC5QxC,GAAsB,CACjC,CACE,MAAO,WACP,MAAO,OACP,SAAU,GACV,IAAK;;;;MAKN,CACD,CACE,MAAO,WACP,MAAO,MACP,SAAU,GACV,IAAK;;;;MAKN,CACD,CACE,MAAO,WACP,MAAO,MACP,SAAU,GACV,IAAK;;;;;MAMN,CACD,CACE,MAAO,WACP,MAAO,UACP,SAAU,GACV,IAAK;;;;MAKN,CACD,CACE,MAAO,WACP,MAAO,yBACP,SAAU,GACV,IAAK;;;;MAKN,CACD,CACE,MAAO,WACP,MAAO,cACP,SAAU,GACV,IAAK;;;;;MAMN,CACF,s/DC0VD,IAAM,EAAQ,EAiBR,GAAA,EAAA,EAAA,aAAsB,CACtB,GAAA,EAAA,EAAA,YAAoB,CACpB,GAAA,EAAA,EAAA,QAA+B,EAAqB,KAAK,CACzD,EACJ,EAAM,OAAS,GAAyB,GAAkB,CACtD,GAAA,EAAA,EAAA,UAA8B,EAAC,OAA2B,CAC1D,GAAA,EAAA,EAAA,KAAgB,aAAa,CAG7B,EACJ,OAAO,SAAa,KACpB,SAAS,gBAAgB,UAAU,SAAS,YAAY,CACpD,EACJ,OAAO,SAAa,KACpB,SAAS,gBAAgB,UAAU,SAAS,kBAAkB,CAC1D,GAAA,EAAA,EAAA,KAAe,EAAgB,CAC/B,GAAA,EAAA,EAAA,KAAoB,EAAqB,CACzC,GAAA,EAAA,EAAA,KAAuB,GAAM,CAC7B,GAAA,EAAA,EAAA,KAAoB,cAAc,CAGpC,EAAkC,KAGhC,EAAmB,GAAiB,CACxC,GAAI,OAAO,SAAa,IAAa,OAErC,AAEE,KADA,EAAY,QAAQ,CACN,MAIhB,IAAM,EAAS,SAAS,cAAc,SAAS,CACzC,EAAM,EAAO,WAAW,KAAK,CACnC,GAAI,CAAC,EAAK,CACR,EAAQ,MAAM,gBAAgB,CAC9B,OAEF,EAAO,MAAQ,IACf,EAAO,OAAS,IAEhB,EAAI,KAAO,0BACX,EAAI,UAAY,4BAChB,EAAI,OAAQ,IAAM,KAAK,GAAM,IAAI,CACjC,EAAI,SAAS,EAAM,EAAG,GAAG,CAGzB,EAAc,SAAS,cAAc,MAAM,CAC3C,EAAY,QAAQ,0BAA4B,OAChD,EAAY,MAAM,QAAU;;;;;;;4BAOF,EAAO,WAAW,CAAC;;;;IAM7C,SAAS,KAAK,YAAY,EAAY,EAIlC,MAAwB,CAC5B,AAEE,KADA,EAAY,QAAQ,CACN,QAKlB,EAAA,EAAA,OAAM,EAAW,GAAQ,CACnB,OAAO,SAAa,KACtB,SAAS,gBAAgB,UAAU,OAAO,YAAa,EAAI,EAE7D,EAGF,EAAA,EAAA,OAAM,EAAgB,GAAQ,CACxB,OAAO,SAAa,KACtB,SAAS,gBAAgB,UAAU,OAAO,kBAAmB,EAAI,EAEnE,EAGF,EAAA,EAAA,OAAM,EAAmB,GAAQ,CAC3B,EACF,EAAgB,EAAc,MAAM,CAEpC,GAAiB,EAEnB,EAGF,EAAA,EAAA,OAAM,EAAgB,GAAQ,CACxB,EAAiB,OACnB,EAAgB,EAAI,EAEtB,CAGF,IAAM,GAAA,EAAA,EAAA,cAA4B,CAChC,GAAI,OAAO,UAAc,KAAe,OAAO,OAAW,IACxD,MAAO,CACL,QAAS,UACT,GAAI,UACJ,WAAY,UACZ,WAAY,UACZ,SAAU,UACV,SAAU,UACX,CAGH,IAAM,EAAK,UAAU,UACjB,EAAU,UACV,EAAK,UAeT,OAZI,EAAG,SAAS,OAAO,CAAE,EAAU,OAC1B,EAAG,SAAS,UAAU,CAAE,EAAU,UAClC,EAAG,SAAS,SAAS,CAAE,EAAU,SACjC,EAAG,SAAS,SAAS,GAAE,EAAU,UAGtC,EAAG,SAAS,UAAU,CAAE,EAAK,UACxB,EAAG,SAAS,MAAM,CAAE,EAAK,QACzB,EAAG,SAAS,UAAU,CAAE,EAAK,UAC7B,EAAG,SAAS,MAAM,CAAE,EAAK,MACzB,EAAG,SAAS,QAAQ,GAAE,EAAK,SAE7B,CACL,UACA,KACA,WAAY,GAAG,OAAO,OAAO,MAAM,KAAK,OAAO,OAAO,SACtD,WAAY,OAAO,iBAAmB,IACtC,SAAU,UAAU,SACpB,SAAU,KAAK,gBAAgB,CAAC,iBAAiB,CAAC,SACnD,EACD,EAGF,EAAA,EAAA,iBAAkB,CAChB,GAAiB,CACb,OAAO,SAAa,MACtB,SAAS,gBAAgB,UAAU,OAAO,YAAa,EAAgB,CACvE,SAAS,gBAAgB,UAAU,OACjC,kBACA,EACD,GAEH,CAGF,IAAM,GAAsB,EAAmB,IAAuB,CAChE,IACJ,EAAc,WAAa,IAQvB,EAAmB,GAChB,EAAc,eAAiB,EAAO,aAMzC,EAAqB,GAAwB,CACjD,EAAc,YAAY,EAAO,CACjC,EAAQ,QAAQ,OAAO,EAAO,KAAK,OAAO,EAGtC,EAAwB,KAAO,IAAoB,CACvD,GAAI,CACF,MAAM,EAAc,gBAAgB,EAAK,MACnC,CACN,EAAQ,MAAM,WAAW,GAOvB,EAAwB,SAAY,CACxC,GAAI,CACF,MAAM,EAAc,gBAAgB,EAAiB,UAAU,CAC/D,EAAc,aAAe,EAAiB,aAC9C,EAAc,aAAe,EAAiB,aAC9C,EAAc,eAAiB,EAAiB,eAChD,EAAc,iBAAmB,EAAiB,iBAClD,EAAQ,QAAQ,YAAY,MACtB,CACN,EAAQ,MAAM,aAAa,GAOzB,MAA0B,CAC9B,EAAc,WAAa,EAAiB,WAC5C,EAAc,eAAiB,EAAiB,eAChD,EAAc,YAAc,EAAiB,YAC7C,EAAc,eAAiB,EAAiB,eAChD,EAAc,mBAAqB,EAAiB,mBACpD,EAAc,aAAe,EAAiB,aAC9C,EAAc,eAAiB,EAAiB,eAChD,EAAc,cAAgB,EAAiB,cAC/C,EAAc,WAAa,EAAiB,WAC5C,EAAc,aAAe,EAAiB,aAC9C,EAAc,sBAAwB,EAAiB,sBACvD,EAAc,aAAe,EAAiB,aAC9C,EAAQ,QAAQ,YAAY,EAMxB,MAAoB,CACxB,EAAO,QAAQ,CACb,MAAO,OACP,QAAS,sBACT,aAAc,KACd,aAAc,KACd,gBAAiB,SAAY,CAC3B,IAAM,EAAmB,CAAE,GAAG,EAAc,cAAe,CAC3D,GAAI,CACF,EAAc,eAAe,CAC7B,MAAM,EAAc,gBAAgB,EAAc,UAAU,CAC5D,EAAQ,QAAQ,UAAU,MACpB,CACN,EAAc,OAAO,EAAiB,CACtC,EAAQ,MAAM,WAAW,GAG9B,CAAC,EAME,MAAyB,CAC7B,EAAO,QAAQ,CACb,MAAO,OACP,QAAS,eACT,aAAc,KACd,aAAc,KACd,gBAAiB,SAAY,CAC3B,GAAI,CAAC,EAAM,SAAS,WAAY,CAC9B,EAAQ,QAAQ,kBAAkB,CAClC,OAEF,GAAI,CACF,MAAM,EAAM,QAAQ,YAAY,CAChC,EAAQ,QAAQ,QAAQ,MAClB,CACN,EAAQ,MAAM,SAAS,GAG5B,CAAC,EAME,MAAqB,CACrB,EAAM,SAAS,WACjB,EAAM,QAAQ,YAAY,CACjB,OAAO,OAAW,KAC3B,OAAO,SAAS,QAAQ,EAOtB,MAA2B,CAC/B,IAAM,EAAS,CACb,cAAA,EACA,SAAU,EAAc,cACxB,KAAM,EAAS,MACf,UAAW,EAAc,MACzB,UAAW,CACT,QAAS,EAAiB,MAC1B,KAAM,EAAc,MACrB,CACD,WAAY,IAAI,MAAM,CAAC,aAAa,CACrC,CACK,EAAO,IAAI,KAAK,CAAC,KAAK,UAAU,EAAQ,KAAM,EAAE,CAAC,CAAE,CACvD,KAAM,mBACP,CAAC,CACI,EAAM,IAAI,gBAAgB,EAAK,CAC/B,EAAI,SAAS,cAAc,IAAI,CACrC,EAAE,KAAO,EACT,EAAE,SAAW,sBAAsB,KAAK,KAAK,CAAC,OAC9C,EAAE,OAAO,CACT,IAAI,gBAAgB,EAAI,CACxB,EAAQ,QAAQ,QAAQ,EAMpB,MAA2B,CAC/B,GAAI,OAAO,SAAa,IAAa,OACrC,IAAM,EAAQ,SAAS,cAAc,QAAQ,CAC7C,EAAM,KAAO,OACb,EAAM,OAAS,mBACf,EAAM,SAAW,KAAO,IAAa,CACnC,IAAM,EAAQ,EAAE,OAA4B,QAAQ,GAC/C,KAEL,GAAI,CACF,GAAI,EAAK,KAAO,EAAM,eACpB,MAAU,WAAW,SAAS,CAEhC,IAAM,EAAO,MAAM,EAAK,MAAM,CAExB,EAAW,EAA6B,KAAK,MAAM,EAAK,CAAC,CAG/D,GAAI,EAAS,WAAa,IAAA,GAAW,CACnC,GAAM,CAAE,YAAW,GAAG,GAAkB,EAAS,SAC7C,IAAc,IAAA,IAChB,MAAM,EAAc,gBAAgB,EAAU,CAEhD,EAAc,OAAO,EAAc,CAEjC,EAAS,OAAS,IAAA,KACpB,EAAS,MAAQ,EAAS,MAExB,EAAS,YAAc,IAAA,KACzB,EAAc,MAAQ,EAAS,WAE7B,EAAS,YAAc,IAAA,KACzB,EAAiB,MAAQ,EAAS,UAAU,QAC5C,EAAc,MAAQ,EAAS,UAAU,MAG3C,EAAQ,QAAQ,QAAQ,MACV,CACd,EAAQ,MAAM,WAAW,GAG7B,EAAM,OAAO,gEAxZH,EAAA,QAAA,CAAA,CArXA,KAAM,EAAA,mCAAO,MAAA,EACpB,MAAO,EAAA,MACR,UAAU,QACT,aAAY,GACZ,eAAc,+BAgXE,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAAA,EAAA,eAAA,CAAA,CA9WD,MAAM,UAAU,SAAA,+BA6WtB,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAAA,EAAA,MAAA,CAAA,CA5WO,MAAO,EAAA,oCAAS,MAAA,EAAE,SAAA,GAAS,MAAM,4CA8GnC,+BAAA,EAAA,SAAA,CAAA,CA5GD,KAAK,aAAa,IAAI,oCAC8B,kBAAA,EAAA,OAAA,qBAAA,CAA3B,UAAA,EAAA,EAAA,OAAU,EAAa,CAAA,CAAA,IAAA,GAAA,GAAA,0BA0BlD,MAxBN,GAwBM,EAAA,EAAA,EAAA,oBADE,MAtBN,GAsBM,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBADE,EAAA,SAAA,MAAA,EAAA,EAAA,aAAA,EAAA,EAAA,OAnBa,EAAa,CAAvB,+CAmBH,MAAA,CAlBH,IAAK,EAAO,KACb,OAAA,EAAA,EAAA,gBAAK,CAAC,cAAa,CAAA,OACD,EAAgB,EAAM,CAAA,CAAA,CAAA,CACxC,KAAK,SACL,SAAS,IACR,eAAc,EAAgB,EAAM,CACpC,QAAK,GAAE,EAAkB,EAAM,CAC/B,UAAO,EAAA,EAAA,EAAA,UAAA,GAAQ,EAAkB,EAAM,CAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eAAA,GAChB,EAAkB,EAAM,CAAA,CAAA,UAAA,CAAA,CAAA,CAAA,QAAA,CAAA,CAAA,4BAEA,MAAhD,IAAA,EAAA,EAAA,iBAA4B,EAAO,KAAI,CAAA,EAAA,0BACS,MAAhD,IAAA,EAAA,EAAA,iBAA4B,EAAO,KAAI,CAAA,EAAA,0BAMjC,MALN,GAKM,EAAA,EAAA,EAAA,oBADI,OAAA,CAFN,MAAM,YACL,OAAA,EAAA,EAAA,gBAAK,CAAA,gBAAqB,EAAO,aAAY,CAAA,yDA4BlD,MApBN,GAoBM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAnBiC,MAAA,CAAhC,MAAM,gBAAe,CAAC,OAAI,GAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAkBjB,EAAA,YAAA,CAAA,CAhBX,OAAA,EAAA,EAAA,OAAO,EAAa,CAAC,UACtB,MAAM,aACL,iBAAc,8BAKA,+BAAA,EAAA,aAAA,CAAA,CAHD,MAAM,QAAO,CAAA,2BAC2B,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,oBAAA,OAAA,CAA9C,MAAM,iCAAgC,CAAA,KAAA,GAAA,EAAA,EAAA,EAAA,iBAAQ,OAEtD,GAAA,CAAA,CAAA,CAAA,qCAIe,EAAA,aAAA,CAAA,CAHD,MAAM,OAAM,CAAA,2BAC6B,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,oBAAA,OAAA,CAA/C,MAAM,kCAAiC,CAAA,KAAA,GAAA,EAAA,EAAA,EAAA,iBAAQ,OAEvD,GAAA,CAAA,CAAA,CAAA,qCAIe,EAAA,aAAA,CAAA,CAHD,MAAM,SAAQ,CAAA,2BACuB,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,oBAAA,OAAA,CAA3C,MAAM,8BAA6B,CAAA,KAAA,GAAA,EAAA,EAAA,EAAA,iBAAQ,OAEnD,GAAA,CAAA,CAAA,CAAA,sDAgBE,MAXN,GAWM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAVgC,MAAA,CAA/B,MAAM,gBAAe,CAAC,MAAG,GAAA,EAAA,EAAA,EAAA,oBASxB,MARN,GAQM,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAFF,EAAA,aAAA,CAAA,CAJQ,OAAA,EAAA,EAAA,OAAO,EAAa,CAAC,oDAAd,EAAa,CAAC,aAAY,EACxC,aAAY,GACZ,UAAA,EAAA,EAAA,OAAU,EAAc,CACxB,QAAS,CAAA,UAAW,wDAE0C,OAAjE,IAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,OAA6B,EAAa,CAAC,aAAY,CAAA,EAAA,CAAA,CAAA,CAAA,CAAA,0BAerD,MAVN,GAUM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBATiC,MAAA,CAAhC,MAAM,gBAAe,CAAC,OAAI,GAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAQjB,EAAA,YAAA,CAAA,CANJ,OAAA,EAAA,EAAA,OAAO,EAAa,CAAC,oDAAd,EAAa,CAAC,aAAY,EACzC,MAAM,2CAE4C,+BAAA,EAAA,aAAA,CAAA,CAApC,MAAM,QAAO,CAAA,2BAAQ,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,iBAAP,UAAO,GAAA,CAAA,CAAA,CAAA,qCACgB,EAAA,aAAA,CAAA,CAArC,MAAM,SAAQ,CAAA,2BAAQ,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,iBAAP,UAAO,GAAA,CAAA,CAAA,CAAA,qCACc,EAAA,aAAA,CAAA,CAApC,MAAM,QAAO,CAAA,2BAAQ,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,iBAAP,UAAO,GAAA,CAAA,CAAA,CAAA,sDAqBjC,MAhBN,GAgBM,kCAfiC,MAAA,CAAhC,MAAM,gBAAe,CAAC,OAAI,GAAA,0BAIzB,MAHN,GAGM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAF6B,OAAA,CAA3B,MAAM,UAAS,CAAC,OAAI,GAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OACgC,EAAA,QAAA,CAAA,CAAzC,OAAA,EAAA,EAAA,OAAO,EAAa,CAAC,wDAAd,EAAa,CAAC,iBAAgB,qDAW1C,EAAA,YAAA,CAAA,CARJ,OAAA,EAAA,EAAA,OAAO,EAAa,CAAC,sDAAd,EAAa,CAAC,eAAc,EAC1C,SAAQ,EAAA,EAAA,EAAA,OAAG,EAAa,CAAC,iBAC1B,MAAM,+CAEsC,+BAAA,EAAA,aAAA,CAAA,CAA9B,MAAM,OAAM,CAAA,2BAAG,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,iBAAF,KAAE,GAAA,CAAA,CAAA,CAAA,qCACgB,EAAA,aAAA,CAAA,CAA/B,MAAM,QAAO,CAAA,2BAAG,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,iBAAF,KAAE,GAAA,CAAA,CAAA,CAAA,qCACc,EAAA,aAAA,CAAA,CAA9B,MAAM,OAAM,CAAA,2BAAG,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,iBAAF,KAAE,GAAA,CAAA,CAAA,CAAA,qCACc,EAAA,aAAA,CAAA,CAA7B,MAAM,OAAM,CAAA,2BAAE,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,iBAAD,IAAC,GAAA,CAAA,CAAA,CAAA,iEAY1B,MAPN,GAOM,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OADM,EAAA,QAAA,CAAA,CALD,MAAA,GAAM,UAAA,GAAW,QAAO,IACpB,MAAA,EAAA,EAAA,aAC0B,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,oBAAA,OAAA,CAA7B,MAAM,gBAAe,CAAA,KAAA,GAAA,CAAA,CAAA,CAAA,2BAG/B,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,iBAFa,aAEb,GAAA,CAAA,CAAA,0BAEyD,EAAA,OAAA,oBAAA,CAA3B,UAAA,EAAA,EAAA,OAAU,EAAa,CAAA,CAAA,IAAA,GAAA,GAAA,uCAqI9C,EAAA,SAAA,CAAA,CAjID,KAAK,SAAS,IAAI,oCAC8B,kBAAA,EAAA,OAAA,iBAAA,CAA3B,UAAA,EAAA,EAAA,OAAU,EAAa,CAAA,CAAA,IAAA,GAAA,GAAA,0BAyC9C,MAvCN,GAuCM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAtCiC,MAAA,CAAhC,MAAM,gBAAe,CAAC,OAAI,GAAA,EAAA,EAAA,EAAA,oBAqCzB,MApCN,GAoCM,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBADE,EAAA,SAAA,MAAA,EAAA,EAAA,aAAA,EAAA,EAAA,OAjCW,GAAmB,CAA3B,+CAiCH,MAAA,CAhCH,IAAK,EAAK,MACX,OAAA,EAAA,EAAA,gBAAK,CAAC,cAAa,oBACiB,EAAa,CAAC,aAAe,EAAK,eAAmC,EAAK,YAI9G,KAAK,SACJ,SAAU,EAAK,SAAQ,GAAA,EACvB,gBAAe,EAAK,SACpB,gBAAA,EAAA,EAAA,OAAc,EAAa,CAAC,aAAe,EAAK,MAChD,SAAA,EAAA,EAAA,eAAK,GAAO,EAAmB,EAAK,MAAO,EAAK,SAAQ,CAAA,CAAA,OAAA,CAAA,CACxD,UAAO,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eAAA,GAAgC,EAAmB,EAAK,MAAO,EAAK,SAAQ,CAAA,CAAA,OAAA,CAAA,CAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eAAA,GAGpC,EAAmB,EAAK,MAAO,EAAK,SAAQ,CAAA,CAAA,OAAA,UAAA,CAAA,CAAA,CAAA,QAAA,CAAA,CAAA,4BAatF,MATN,GASM,GAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBADE,MAPN,GAOM,EAAA,EAAA,EAAA,oBADqB,IAAA,CAAtB,UAAQ,EAAK,IAAA,CAAA,KAAA,EAAA,GAAA,CAAA,CAAA,EAAA,CAAA,EAAA,EAAA,EAAA,oBAMd,MAHN,GAGM,EAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,iBAFD,EAAK,MAAK,CAAG,IAChB,EAAA,CAAY,EAAK,WAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAAwC,OAAzD,GAA8C,OAAI,GAAA,EAAA,EAAA,oBAAA,GAAA,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,GAAA,8BAMC,EAAA,OAAA,oBAAA,CAA3B,UAAA,EAAA,EAAA,OAAU,EAAa,CAAA,CAAA,IAAA,GAAA,GAAA,0BAcjD,MAZN,GAYM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBARmC,MAAA,CAAlC,MAAM,gBAAe,CAAC,SAAM,GAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAOnB,EAAA,YAAA,CAAA,CALJ,OAAA,EAAA,EAAA,OAAO,EAAa,CAAC,sDAAd,EAAa,CAAC,eAAc,EAC3C,MAAM,gDAE0C,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAAA,EAAA,aAAA,CAAA,CAAlC,MAAM,SAAQ,CAAA,2BAAK,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,iBAAJ,OAAI,GAAA,CAAA,CAAA,CAAA,qCACc,EAAA,aAAA,CAAA,CAAjC,MAAM,QAAO,CAAA,2BAAK,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,iBAAJ,OAAI,GAAA,CAAA,CAAA,CAAA,sDA0B9B,MArBN,GAqBM,kCApBiC,MAAA,CAAhC,MAAM,gBAAe,CAAC,OAAI,GAAA,0BAIzB,MAHN,GAGM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAFc,OAAA,KAAZ,QAAK,GAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAC6C,EAAA,QAAA,CAAA,CAAvC,OAAA,EAAA,EAAA,OAAO,EAAa,CAAC,sDAAd,EAAa,CAAC,eAAc,gDAQhD,MANN,GAMM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBALgB,OAAA,KAAd,UAAO,GAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAIX,EAAA,QAAA,CAAA,CAFQ,OAAA,EAAA,EAAA,OAAO,EAAa,CAAC,0DAAd,EAAa,CAAC,mBAAkB,EAC9C,SAAQ,EAAA,EAAA,EAAA,OAAG,EAAa,CAAC,wEAMxB,MAHN,GAGM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAFc,OAAA,KAAZ,QAAK,GAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAC2C,EAAA,QAAA,CAAA,CAArC,OAAA,EAAA,EAAA,OAAO,EAAa,CAAC,oDAAd,EAAa,CAAC,aAAY,gDAK9C,MAHN,GAGM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAFa,OAAA,KAAX,OAAI,GAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAC0C,EAAA,QAAA,CAAA,CAAnC,OAAA,EAAA,EAAA,OAAO,EAAa,CAAC,kDAAd,EAAa,CAAC,WAAU,kDAqC9C,MAhCN,GAgCM,kCA/BiC,MAAA,CAAhC,MAAM,gBAAe,CAAC,OAAI,GAAA,0BAezB,MAdN,GAcM,EAAA,EAAA,EAAA,oBARE,MALN,GAKM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAJc,OAAA,KAAZ,QAAK,GAAA,EAAA,EAAA,EAAA,oBAGV,OAFD,IAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,OACM,EAAa,CAAC,aAAY,CAAG,KAAE,EAAA,CAAA,CAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OASrC,EAAA,QAAA,CAAA,CALQ,OAAA,EAAA,EAAA,OAAO,EAAa,CAAC,oDAAd,EAAa,CAAC,aAAY,EACxC,IAAK,IACL,IAAK,IACL,KAAM,GACN,MAAO,CAAA,IAAA,MAAA,IAAA,MAAA,IAAA,MAAsC,+CAiB5C,MAdN,GAcM,EAAA,EAAA,EAAA,oBARE,MALN,GAKM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAJa,OAAA,KAAX,OAAI,GAAA,EAAA,EAAA,EAAA,oBAGT,OAFD,IAAA,EAAA,EAAA,kBAAA,EAAA,EAAA,OACM,EAAa,CAAC,aAAY,CAAG,KAAE,EAAA,CAAA,CAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OASrC,EAAA,QAAA,CAAA,CALQ,OAAA,EAAA,EAAA,OAAO,EAAa,CAAC,qDAAd,EAAa,CAAC,aAAY,EACxC,IAAK,GACL,IAAK,GACL,KAAM,EACN,MAAO,CAAA,GAAA,KAAA,GAAA,KAAA,GAAA,KAAgC,iDAaxC,MAPN,GAOM,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OADM,EAAA,QAAA,CAAA,CALD,MAAA,GAAM,UAAA,GAAW,QAAO,IACpB,MAAA,EAAA,EAAA,aAC0B,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,oBAAA,OAAA,CAA7B,MAAM,gBAAe,CAAA,KAAA,GAAA,CAAA,CAAA,CAAA,2BAG/B,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,iBAFa,aAEb,GAAA,CAAA,CAAA,0BAEqD,EAAA,OAAA,gBAAA,CAA3B,UAAA,EAAA,EAAA,OAAU,EAAa,CAAA,CAAA,IAAA,GAAA,GAAA,uCA0H1C,EAAA,SAAA,CAAA,CAtHD,KAAK,WAAW,IAAI,oCAC8B,kBAAA,EAAA,OAAA,mBAAA,CAA3B,UAAA,EAAA,EAAA,OAAU,EAAa,CAAA,CAAA,IAAA,GAAA,GAAA,0BAkBhD,MAhBN,GAgBM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAfiC,MAAA,CAAhC,MAAM,gBAAe,CAAC,OAAI,GAAA,EAAA,EAAA,EAAA,oBAczB,MAbN,GAaM,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAPM,EAAA,QAAA,CAAA,CALD,UAAA,GAAW,QAAO,IACd,MAAA,EAAA,EAAA,aAC+B,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,oBAAA,OAAA,CAAlC,MAAM,qBAAoB,CAAA,KAAA,GAAA,CAAA,CAAA,CAAA,2BAGpC,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,iBAFa,SAEb,GAAA,CAAA,CAAA,qCAMU,EAAA,QAAA,CAAA,CALD,UAAA,GAAW,QAAO,IACd,MAAA,EAAA,EAAA,aAC0B,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,oBAAA,OAAA,CAA7B,MAAM,gBAAe,CAAA,KAAA,GAAA,CAAA,CAAA,CAAA,2BAG/B,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,iBAFa,SAEb,GAAA,CAAA,CAAA,oCAiCE,MA5BN,GA4BM,kCA3BiC,MAAA,CAAhC,MAAM,gBAAe,CAAC,OAAI,GAAA,0BAczB,MAbN,GAaM,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAPM,EAAA,QAAA,CAAA,CALD,UAAA,GAAW,QAAO,IACd,MAAA,EAAA,EAAA,aAC2B,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,oBAAA,OAAA,CAA9B,MAAM,iBAAgB,CAAA,KAAA,GAAA,CAAA,CAAA,CAAA,2BAGhC,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,iBAFa,SAEb,GAAA,CAAA,CAAA,qCAMU,EAAA,QAAA,CAAA,CALD,UAAA,GAAW,QAAO,IACd,MAAA,EAAA,EAAA,aACyB,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,oBAAA,OAAA,CAA5B,MAAM,eAAc,CAAA,KAAA,GAAA,CAAA,CAAA,CAAA,2BAG9B,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,iBAFa,SAEb,GAAA,CAAA,CAAA,uCAaQ,EAAA,QAAA,CAAA,CAVR,MAAA,GACA,KAAK,QACL,UAAA,GACA,MAAM,OACL,QAAO,IAEG,MAAA,EAAA,EAAA,aAC0B,CAAA,GAAA,AAAA,EAAA,MAAA,EAAA,EAAA,EAAA,oBAAA,OAAA,CAA7B,MAAM,gBAAe,CAAA,KAAA,GAAA,CAAA,CAAA,CAAA,2BAG/B,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,iBAFa,WAEb,GAAA,CAAA,CAAA,kCAoBI,MAhBN,GAgBM,kCAfiC,MAAA,CAAhC,MAAM,gBAAe,CAAC,OAAI,GAAA,0BAOzB,MANN,GAMM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAFE,MAAA,CAHD,MAAM,gBAAe,CAAA,EAAA,EAAA,EAAA,oBACP,OAAA,KAAX,OAAI,EAAA,EAAA,EAAA,oBAC+B,OAAA,CAAnC,MAAM,eAAc,CAAC,UAAO,CAAA,CAAA,GAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAEA,EAAA,QAAA,CAAA,CAAnB,MAAO,EAAA,oCAAQ,MAAA,gDAQ5B,MANN,GAMM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAFE,MAAA,CAHD,MAAM,gBAAe,CAAA,EAAA,EAAA,EAAA,oBACP,OAAA,KAAX,OAAI,EAAA,EAAA,EAAA,oBACiC,OAAA,CAArC,MAAM,eAAc,CAAC,YAAS,CAAA,CAAA,GAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OAEG,EAAA,QAAA,CAAA,CAAxB,MAAO,EAAA,oCAAa,MAAA,kDAkBnC,MAbN,GAaM,kCAZiC,MAAA,CAAhC,MAAM,gBAAe,CAAC,OAAI,GAAA,0BAIzB,MAHN,GAGM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAFa,OAAA,KAAX,OAAI,GAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OACkC,EAAA,QAAA,CAAA,CAA3B,MAAO,EAAA,oCAAgB,MAAA,uBAE/B,EAAA,QAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAML,MANN,GAMM,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,OADF,EAAA,OAAA,CAAA,CAHQ,MAAO,EAAA,oCAAa,MAAA,EAC5B,YAAY,UACZ,KAAK,yFAkCL,MA5BN,GA4BM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBA3BiC,MAAA,CAAhC,MAAM,gBAAe,CAAC,OAAI,GAAA,EAAA,EAAA,EAAA,oBA0BzB,MAzBN,GAyBM,0BArBE,MAHN,GAGM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAF+B,OAAA,CAA7B,MAAM,aAAY,CAAC,MAAG,GAAA,EAAA,EAAA,EAAA,oBAC4B,OAAxD,IAAA,EAAA,EAAA,iBAA4B,EAAA,MAAW,QAAO,CAAA,EAAA,CAAA,CAAA,0BAK1C,MAHN,GAGM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAFgC,OAAA,CAA9B,MAAM,aAAY,CAAC,OAAI,GAAA,EAAA,EAAA,EAAA,oBACsB,OAAnD,IAAA,EAAA,EAAA,iBAA4B,EAAA,MAAW,GAAE,CAAA,EAAA,CAAA,CAAA,0BAKrC,MAHN,GAGM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAFiC,OAAA,CAA/B,MAAM,aAAY,CAAC,QAAK,GAAA,EAAA,EAAA,EAAA,oBAC6B,OAA3D,IAAA,EAAA,EAAA,iBAA4B,EAAA,MAAW,WAAU,CAAA,EAAA,CAAA,CAAA,0BAK7C,MAHN,GAGM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAFiC,OAAA,CAA/B,MAAM,aAAY,CAAC,QAAK,GAAA,EAAA,EAAA,EAAA,oBAC6B,OAA3D,IAAA,EAAA,EAAA,iBAA4B,EAAA,MAAW,WAAU,CAAA,EAAA,CAAA,CAAA,0BAK7C,MAHN,GAGM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAF8B,OAAA,CAA5B,MAAM,aAAY,CAAC,KAAE,GAAA,EAAA,EAAA,EAAA,oBAC8B,OAAzD,IAAA,EAAA,EAAA,iBAA4B,EAAA,MAAW,SAAQ,CAAA,EAAA,CAAA,CAAA,0BAK3C,MAHN,GAGM,CAAA,AAAA,EAAA,OAAA,EAAA,EAAA,oBAF8B,OAAA,CAA5B,MAAM,aAAY,CAAC,KAAE,GAAA,EAAA,EAAA,EAAA,oBAC8B,OAAzD,IAAA,EAAA,EAAA,iBAA4B,EAAA,MAAW,SAAQ,CAAA,EAAA,CAAA,CAAA,sBAII,EAAA,OAAA,kBAAA,CAA3B,UAAA,EAAA,EAAA,OAAU,EAAa,CAAA,CAAA,IAAA,GAAA,GAAA,ojBE1S/D,IAAM,EAAM,GAAkB,CACxB,EAAc,CAAE,GAAG,EAAsB,GAAG,EAAI,MAAO,0DAlCrD,MAAA,CArCJ,OAAA,EAAA,EAAA,gBAAK,CAAC,aAAY,yBACwB,EAAA,mCAA4C,EAAA,UAAO,2CA0BvF,MArBN,GAqBM,CAAA,AAAA,EAAA,MAAA,EAAA,EAAA,oBApBgC,MAAA,CAA/B,MAAM,mBAAkB,CAAA,KAAA,GAAA,CAErB,EAAY,WAAQ,SAAgB,EAAY,UAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAStD,QAAA,OARC,IAAK,EAAY,QACjB,MAAO,EAAA,KACP,OAAQ,EAAA,KACT,SAAA,GACA,KAAA,GACA,MAAA,GACA,YAAA,GACA,MAAM,iCAGK,EAAY,UAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAMvB,MAAA,OALC,IAAK,EAAY,QACjB,MAAO,EAAA,KACP,OAAQ,EAAA,KACT,MAAM,oBACN,IAAI,sDAGQ,EAAA,UAAQ,CAAK,EAAA,YAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAQlB,EAAA,SAAA,CAAA,IAAA,EAAA,CAAA,EAAA,EAAA,EAAA,oBAFH,MALN,GAKM,EAAA,EAAA,EAAA,oBAJyD,OAA7D,IAAA,EAAA,EAAA,iBAAmC,EAAY,KAAI,CAAA,EAAA,CACvC,EAAY,WAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAEf,OAFT,IAAA,EAAA,EAAA,iBACE,EAAY,SAAQ,CAAA,EAAA,GAAA,EAAA,EAAA,oBAAA,GAAA,GAAA,CAAA,CAAA,CAGb,EAAA,cAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAA+C,MAA1D,GAA0D,GAAA,EAAA,EAAA,oBAAA,GAAA,GAAA,CAAA,CAAA,GAAA,GAAA,EAAA,EAAA,oBAAA,GAAA,GAAA,CAAA,CAAA,EAAA,wSEGhE,IAAM,EAAO,EAIP,EAAM,GAAkB,0DApBtB,MAvBN,GAuBM,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBADE,EAAA,SAAA,MAAA,EAAA,EAAA,YApBW,EAAA,MAAR,+CAoBH,MAAA,CAnBH,IAAK,EAAK,KACX,OAAA,EAAA,EAAA,gBAAK,CAAC,kBAAiB,CAAA,0BACc,EAAA,SAAW,EAAK,KAAI,CAAA,CAAA,CACxD,QAAK,GAAE,EAAI,QAAU,EAAI,4BAYpB,MAVN,GAUM,EAAA,EAAA,EAAA,OAPI,EAAG,CAAC,eAAiB,EAAK,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADjC,EAAG,CAAC,cAAa,CAAA,OAErB,KAAM,EAAK,KAAK,KAChB,KAAM,kEAIF,OAFP,IAAA,EAAA,EAAA,iBACK,EAAK,MAAM,OAAO,OAAM,EAAA,EAAA,IAAA,CAAA,EAAA,EAAA,CAAA,EAAA,EAAA,EAAA,oBAKzB,MAFN,IAAA,EAAA,EAAA,iBACK,EAAK,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,GAAA,+lBEiF3B,IAAM,EAAO,EAIP,EAAM,GAAkB,CACxB,EAAQ,CAAE,GAAG,EAAsB,GAAG,EAAI,MAAO,mDA1BxC,EAAA,WAAA,CAjFD,KAAK,aAAY,CAAA,2BAgFrB,CA9EE,EAAA,MAAQ,EAAA,WAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBA8EV,MAAA,OA7EJ,OAAA,EAAA,EAAA,gBAAK,CAAC,gBAAe,EAAA,EAAA,EAAA,OACZ,EAAG,CAAC,OAAO,MAAK,aAAA,cAAA,CAAA,CAAA,CACzB,KAAK,aACL,aAAW,oCAUL,MAPN,GAOM,EAAA,EAAA,EAAA,oBADE,MALN,GAKM,EAAA,EAAA,EAAA,oBAJ4D,OAAhE,IAAA,EAAA,EAAA,iBAA4C,EAAM,KAAI,CAAA,EAAA,EAAA,EAAA,EAAA,oBAG7C,OAFT,IAAA,EAAA,EAAA,iBACE,EAAM,SAAQ,CAAA,EAAA,CAAA,CAAA,CAAA,CAAA,EAAA,EAAA,EAAA,oBAkEd,MA5DN,GA4DM,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBADO,EAAA,SAAA,MAAA,EAAA,EAAA,YA1De,EAAA,SAAS,SAAlB,+DAAkC,EAAM,KAAA,CAAA,CAG/C,EAAM,UAAY,EAAM,SAAS,OAAM,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAiCzC,MAlCN,GAkCM,EAAA,EAAA,EAAA,oBAtBE,MARN,GAQM,EAAA,EAAA,EAAA,OALI,EAAG,CAAC,eAAiB,EAAM,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADlC,EAAG,CAAC,cAAa,CAAA,OAErB,KAAM,EAAM,KAAK,KACjB,KAAM,+EAE2B,OAAA,MAAA,EAAA,EAAA,iBAA3B,EAAM,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBAsBtB,EAAA,SAAA,MAAA,EAAA,EAAA,YAnBe,EAAM,SAAlB,+CAmBH,MAAA,CAlBH,IAAK,EAAS,KACf,OAAA,EAAA,EAAA,gBAAK,CAAC,+CAA8C,CAAA,OAClC,EAAA,WAAW,EAAS,KAAI,CAAA,CAAA,CAAA,CAC1C,KAAK,OACL,SAAS,IACR,QAAK,GAAE,EAAI,QAAU,EAAQ,CAC7B,UAAO,EAAA,EAAA,EAAA,UAAA,GAAQ,EAAI,QAAU,EAAQ,CAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eAAA,GACd,EAAI,QAAU,EAAQ,CAAA,CAAA,UAAA,CAAA,CAAA,CAAA,QAAA,CAAA,CAAA,eAItC,EAAG,CAAC,eAAiB,EAAS,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADrC,EAAG,CAAC,cAAa,CAAA,OAErB,KAAM,EAAS,KAAK,KACpB,KAAM,+EAIA,OAFT,IAAA,EAAA,EAAA,iBACE,EAAS,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,GAAA,wDAwBpB,MAAA,OAjBJ,OAAA,EAAA,EAAA,gBAAK,CAAC,sBAAqB,CAAA,OACT,EAAA,WAAW,EAAM,KAAI,CAAA,CAAA,CAAA,CACvC,KAAK,OACL,SAAS,IACR,QAAK,GAAE,EAAI,QAAU,EAAK,CAC1B,UAAO,EAAA,EAAA,EAAA,UAAA,GAAQ,EAAI,QAAU,EAAK,CAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eAAA,GACX,EAAI,QAAU,EAAK,CAAA,CAAA,UAAA,CAAA,CAAA,CAAA,QAAA,CAAA,CAAA,eAInC,EAAG,CAAC,eAAiB,EAAM,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADlC,EAAG,CAAC,cAAa,CAAA,OAErB,KAAM,EAAM,KAAK,KACjB,KAAM,+EAIA,OAFT,IAAA,EAAA,EAAA,iBACE,EAAM,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,GAAA,EAAA,CAAA,GAAA,kiBEsC/B,IAAM,EAAO,EAIP,EAAM,GAAkB,CACxB,GAAA,EAAA,EAAA,KAAkB,GAAM,cAtHZ,EAAA,MAAM,OAAM,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBA8FjB,EAAA,SAAA,CAAA,IAAA,EAAA,CAAA,EAAA,EAAA,EAAA,oBA9EH,MAAA,CAbJ,OAAA,EAAA,EAAA,gBAAK,CAAC,mBAAkB,EAAA,EAAA,EAAA,OACL,EAAG,CAAC,OAAO,MAAK,aAAA,cAAA,CAAA,OAAA,CAAqD,EAAA,MAAW,CAAA,CAAA,CAAA,CAIlG,QAAK,AAAA,EAAA,KAAA,GAAE,EAAA,MAAW,CAAI,EAAA,iCAOlB,IAAA,CAJF,OAAA,EAAA,EAAA,gBAAK,CAAA,cAAyC,EAAA,MAAW,qBAAA,0BAAA,CAAA,CAAA,CAAA,KAAA,EAAA,CAAA,CAAA,EAAA,EAAA,EAAA,EAAA,aAkFjD,EAAA,WAAA,CA1ED,KAAK,cAAa,CAAA,2BAyEtB,CAvEG,EAAA,wCAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAuEH,MAAA,OAtEJ,OAAA,EAAA,EAAA,gBAAK,CAAC,YAAW,EAAA,EAAA,EAAA,OACR,EAAG,CAAC,OAAO,MAAK,aAAA,cAAA,CAAA,CAAA,4BAenB,MAZN,GAYM,EAAA,EAAA,EAAA,oBAJE,MAPN,GAOM,EAAA,EAAA,EAAA,OAJI,EAAG,CAAC,eAAiB,EAAA,YAAY,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADxC,EAAG,CAAC,cAAa,CAAA,OAErB,KAAM,EAAA,WAAW,KAAK,KACtB,KAAM,iFAKF,OAFT,IAAA,EAAA,EAAA,iBACE,EAAA,YAAY,MAAM,OAAK,KAAA,CAAA,EAAA,CAAA,CAAA,EAAA,EAAA,EAAA,oBAuDrB,MAlDN,GAkDM,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBADO,EAAA,SAAA,MAAA,EAAA,EAAA,YAhDe,EAAA,MAAT,+DAAsB,EAAM,KAAA,CAAA,CAGnC,EAAM,UAAY,EAAM,SAAS,OAAM,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBA6BzC,MA9BN,GA8BM,EAAA,EAAA,EAAA,oBAlBE,MARN,GAQM,EAAA,EAAA,EAAA,OALI,EAAG,CAAC,eAAiB,EAAM,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADlC,EAAG,CAAC,cAAa,CAAA,OAErB,KAAM,EAAM,KAAK,KACjB,KAAM,+EAE2B,OAAA,MAAA,EAAA,EAAA,iBAA3B,EAAM,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBAkBtB,EAAA,SAAA,MAAA,EAAA,EAAA,YAfe,EAAM,SAAlB,+CAeH,MAAA,CAdH,IAAK,EAAS,KACf,OAAA,EAAA,EAAA,gBAAK,CAAC,kBAAiB,CAAA,OACL,EAAA,WAAW,EAAS,KAAI,CAAA,CAAA,CAAA,CACzC,QAAK,GAAE,EAAI,QAAU,EAAQ,eAItB,EAAG,CAAC,eAAiB,EAAS,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADrC,EAAG,CAAC,cAAa,CAAA,OAErB,KAAM,EAAS,KAAK,KACpB,KAAM,+EAIA,OAFT,IAAA,EAAA,EAAA,iBACE,EAAS,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,GAAA,wDAkBpB,MAAA,OAXJ,OAAA,EAAA,EAAA,gBAAK,CAAC,kBAAiB,CAAA,OACL,EAAA,WAAW,EAAM,KAAI,CAAA,CAAA,CAAA,CACtC,QAAK,GAAE,EAAI,QAAU,EAAK,eAInB,EAAG,CAAC,eAAiB,EAAM,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADlC,EAAG,CAAC,cAAa,CAAA,OAErB,KAAM,EAAM,KAAK,KACjB,KAAM,+EAEwD,OAAjE,IAAA,EAAA,EAAA,iBAAsC,EAAM,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,EAAA,EAAA,CAAA,GAAA,gjBEwCrE,IAAM,EAAM,GAAkB,CACxB,GAAA,EAAA,EAAA,YAAoB,CAGpB,GAAA,EAAA,EAAA,QAAkC,EAAoB,CAC1D,SAAY,GACZ,cAAiB,GACjB,SAAY,GACb,CAAC,CAEI,EAAY,GAAsB,CAClC,EAAK,OACP,EAAO,KAAK,EAAK,KAAK,CACtB,EAAS,MAAM,4DAnCX,MAAA,CAzGJ,OAAA,EAAA,EAAA,gBAAK,CAAC,cAAa,EAAA,EAAA,EAAA,OACH,EAAG,CAAC,OAAO,MAAK,aAAA,cAAA,CAAA,QAAmD,EAAA,KAAI,CAAA,CAAA,CAAA,CAIvF,KAAK,aACL,aAAW,OACV,cAAW,CAAG,EAAA,KACd,aAAU,AAAA,EAAA,KAAA,IAAA,EAAA,EAAA,OAAE,EAAQ,CAAC,MAAI,CACzB,aAAU,AAAA,EAAA,KAAA,IAAA,EAAA,EAAA,OAAE,EAAQ,CAAC,WAAS,mCAazB,MAAA,CAXD,MAAM,sBAAqB,CAAA,EAAA,EAAA,EAAA,oBAUxB,MAAA,CATD,MAAM,qBAAoB,CAAA,EAAA,EAAA,EAAA,oBAOtB,OAAA,CALD,MAAM,YAAW,CAAA,0BACR,OAAA,0BACA,OAAA,0BACA,OAAA,0BACA,OAAA,4BAEE,OAAA,KAAX,OAAI,CAAA,CAAA,CAAA,CAAA,GAAA,EAAA,EAAA,EAAA,oBAoFR,MAhFN,GAgFM,EAAA,EAAA,EAAA,oBADE,MA9EN,GA8EM,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBADE,EAAA,SAAA,MAAA,EAAA,EAAA,YA3Ee,EAAA,MAAZ,+CA2EH,MAAA,CA1EH,IAAK,EAAS,KACf,MAAM,mDAiBA,MAAA,CAdJ,MAAM,+BACN,KAAK,OACL,SAAS,IACR,QAAK,GAAE,EAAS,EAAQ,CACxB,UAAO,EAAA,EAAA,EAAA,UAAA,GAAQ,EAAS,EAAQ,CAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eAAA,GACT,EAAS,EAAQ,CAAA,CAAA,UAAA,CAAA,CAAA,CAAA,QAAA,CAAA,CAAA,eAIjC,EAAG,CAAC,eAAiB,EAAS,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADrC,EAAG,CAAC,cAAa,CAAA,OAErB,KAAM,EAAS,KAAK,KACpB,KAAM,+EAE8B,OAAA,MAAA,EAAA,EAAA,iBAA9B,EAAS,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,GAAA,CAIvB,EAAS,UAAY,EAAS,SAAS,OAAM,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBAoD/C,MArDN,GAqDM,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBAhCE,EAAA,SAAA,MAAA,EAAA,EAAA,YAhBW,EAAS,SAAjB,+CAgBH,MAAA,CAfH,IAAK,EAAK,KACX,MAAM,oBACN,KAAK,OACL,SAAS,IACR,QAAK,GAAE,EAAS,EAAI,CACpB,UAAO,EAAA,EAAA,EAAA,UAAA,GAAQ,EAAS,EAAI,CAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eAAA,GACL,EAAS,EAAI,CAAA,CAAA,UAAA,CAAA,CAAA,CAAA,QAAA,CAAA,CAAA,eAI7B,EAAG,CAAC,eAAiB,EAAK,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADjC,EAAG,CAAC,cAAa,CAAA,OAErB,KAAM,EAAK,KAAK,KAChB,KAAM,+EAEyD,OAAlE,IAAA,EAAA,EAAA,iBAAwC,EAAK,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,GAAA,uDAgC/C,EAAA,SAAA,MAAA,EAAA,EAAA,YA7Bc,EAAS,SAAjB,+DAAiC,EAAK,KAAA,CAAA,CAE7C,EAAK,UAAY,EAAK,SAAS,OAAM,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,oBA0BvC,MA3BN,GA2BM,EAAA,EAAA,EAAA,oBArBE,MAFN,IAAA,EAAA,EAAA,iBACK,EAAK,MAAM,MAAK,CAAA,EAAA,GAAA,EAAA,EAAA,WAAA,GAAA,EAAA,EAAA,EAAA,oBAqBf,EAAA,SAAA,MAAA,EAAA,EAAA,YAlBc,EAAK,SAAhB,+CAkBH,MAAA,CAjBH,IAAK,EAAQ,KACd,MAAM,2CACN,KAAK,OACL,SAAS,IACR,QAAK,GAAE,EAAS,EAAO,CACvB,UAAO,EAAA,EAAA,EAAA,UAAA,GAAQ,EAAS,EAAO,CAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,WAAA,EAAA,EAAA,eAAA,GACR,EAAS,EAAO,CAAA,CAAA,UAAA,CAAA,CAAA,CAAA,QAAA,CAAA,CAAA,eAIhC,EAAG,CAAC,eAAiB,EAAQ,MAAM,OAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,cAAA,EAAA,EAAA,0BAAA,EAAA,EAAA,OADpC,EAAG,CAAC,cAAa,CAAA,OAErB,KAAM,EAAQ,KAAK,KACnB,KAAM,+EAIA,OAFT,IAAA,EAAA,EAAA,iBACE,EAAQ,MAAM,MAAK,CAAA,EAAA,CAAA,CAAA,GAAA,GAAA,4PElEvC,IAAM,GAAA,EAAA,EAAA,QAAkC,EAAoB,CAC1D,SAAY,GACZ,cAAiB,GACjB,SAAY,GACb,CAAC,0DArBM,MAAA,CAZJ,MAAM,eACL,aAAU,AAAA,EAAA,KAAA,IAAA,EAAA,EAAA,OAAE,EAAQ,CAAC,MAAI,CACzB,aAAU,AAAA,EAAA,KAAA,IAAA,EAAA,EAAA,OAAE,EAAQ,CAAC,WAAS,uCASzB,MAAA,CAPD,MAAM,0BAAyB,CAAA,EAAA,EAAA,EAAA,oBAM3B,OAAA,CAJD,MAAM,iBAAgB,CAAA,0BACb,OAAA,0BACA,OAAA,0BACA,OAAA,qDEOrB,SAAgB,GAAY,EAAU,EAAgC,EAAE,CAAE,CACxE,IAAM,EAAgB,EAAoB,EAAQ,CAC5C,EAAW,GAAe,CAKhC,OAFA,EAAI,QAAQ,EAAqB,EAAS,CAEnC,ECET,SAAgB,EACd,EACe,CACf,GAAM,CAAE,YAAa,EAErB,MAAO,CACL,OAAA,EAAA,EAAA,eAAA,EAAA,EAAA,SAA8B,EAAQ,MAAM,CAAC,CAC7C,QAAA,EAAA,EAAA,eAAA,EAAA,EAAA,SAA+B,EAAQ,OAAO,CAAC,CAC/C,YAAA,EAAA,EAAA,cAA2B,EAAS,WAAW,CAC/C,WAAA,EAAA,EAAA,UAAoB,CAClB,QAAW,EAAS,UACpB,IAAM,GAAmB,CACvB,EAAS,UAAY,GAExB,CAAC,CACF,gBAAA,EAAA,EAAA,cAA+B,EAAS,eAAe,CACvD,cAAA,EAAA,EAAA,cAA6B,EAAS,aAAa,CACnD,uBAAA,EAAA,EAAA,cAAsC,EAAS,sBAAsB,CACrE,YAAA,EAAA,EAAA,cAA2B,EAAS,WAAW,CAC/C,cAAA,EAAA,EAAA,cAA6B,EAAS,aAAa,CACnD,gBAAA,EAAA,EAAA,cAA+B,EAAS,eAAe,CACvD,cAAA,EAAA,EAAA,cAA6B,EAAS,aAAa,CACnD,gBAAA,EAAA,EAAA,cAA+B,EAAS,eAAe,CACvD,gBAAA,EAAA,EAAA,cAA+B,EAAS,eAAe,CACvD,oBAAA,EAAA,EAAA,cAAmC,EAAS,mBAAmB,CAC/D,aAAA,EAAA,EAAA,cAA4B,EAAS,YAAY,CACjD,MAAO,CAAE,GAAG,EAAsB,GAAG,EAAQ,MAAO,CACpD,cAAe,EAAQ,cACxB,CAIH,SAAgB,GACd,EACe,CACf,OAAO,EAAqB,EAAoB,EAAQ,CAAC,CCoD3D,IAAa,GAAe,EAEf,GAAc,EAEd,GAAc,EAEd,GAAiB,EAEjB,GAA+B,EAE/B,GAAe"}
|