NewLife/cube-front

feat(layout): 添加赛博风格布局及相关组件,支持主题切换功能
何炳宏 authored at 2026-05-13 22:27:54
dbd1656
Tree
1 Parent(s) dad2879
Summary: 19 changed files with 700 additions and 81 deletions.
Modified +0 -25
Modified +12 -9
Modified +31 -3
Added +22 -0
Added +78 -0
Added +269 -0
Added +258 -0
Modified +1 -1
Modified +2 -2
Modified +3 -3
Modified +2 -2
Modified +4 -4
Modified +3 -3
Modified +1 -1
Modified +4 -4
Modified +2 -2
Modified +1 -1
Modified +6 -6
Modified +1 -15
Modified +0 -25
diff --git a/components.d.ts b/components.d.ts
index d694651..99c6524 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -8,32 +8,7 @@ export {}
 /* prettier-ignore */
 declare module 'vue' {
   export interface GlobalComponents {
-    ElAlert: typeof import('element-plus/es')['ElAlert']
-    ElAvatar: typeof import('element-plus/es')['ElAvatar']
-    ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
-    ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
-    ElButton: typeof import('element-plus/es')['ElButton']
-    ElCard: typeof import('element-plus/es')['ElCard']
-    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
-    ElDialog: typeof import('element-plus/es')['ElDialog']
-    ElForm: typeof import('element-plus/es')['ElForm']
-    ElFormItem: typeof import('element-plus/es')['ElFormItem']
-    ElIcon: typeof import('element-plus/es')['ElIcon']
-    ElInput: typeof import('element-plus/es')['ElInput']
-    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
-    ElOption: typeof import('element-plus/es')['ElOption']
-    ElPagination: typeof import('element-plus/es')['ElPagination']
-    ElRadio: typeof import('element-plus/es')['ElRadio']
-    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
-    ElSelect: typeof import('element-plus/es')['ElSelect']
-    ElSwitch: typeof import('element-plus/es')['ElSwitch']
-    ElTable: typeof import('element-plus/es')['ElTable']
-    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
-    ElTag: typeof import('element-plus/es')['ElTag']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
   }
-  export interface GlobalDirectives {
-    vLoading: typeof import('element-plus/es')['ElLoadingDirective']
-  }
 }
Modified +12 -9
diff --git a/core/composables/useLayout.ts b/core/composables/useLayout.ts
index 9b68b7e..b562c65 100644
--- a/core/composables/useLayout.ts
+++ b/core/composables/useLayout.ts
@@ -11,11 +11,20 @@ export interface LayoutOption {
   component: Component;
 }
 
-const STORAGE_KEY = 'cube-layout';
+export const STORAGE_KEY = 'cube-layout';
 
 // 模块级响应式状态(全局单例)
-const registeredLayouts = ref<LayoutOption[]>([]);
-const currentLayoutId = ref<string>('');
+export const registeredLayouts = ref<LayoutOption[]>([]);
+export const currentLayoutId = ref<string>('');
+
+// 计算当前布局组件
+const currentLayout = computed<LayoutOption | undefined>(() =>
+  registeredLayouts.value.find((l) => l.id === currentLayoutId.value),
+);
+
+export const currentComponent = computed<Component | undefined>(
+  () => currentLayout.value?.component,
+);
 
 /**
  * 注册一个布局
@@ -40,12 +49,6 @@ export function registerLayout(option: LayoutOption): void {
  * 在 Topnav / RootLayout 等组件中使用
  */
 export function useLayout() {
-  const currentLayout = computed<LayoutOption | undefined>(() =>
-    registeredLayouts.value.find((l) => l.id === currentLayoutId.value),
-  );
-
-  const currentComponent = computed<Component | undefined>(() => currentLayout.value?.component);
-
   function setLayout(id: string): void {
     if (!registeredLayouts.value.some((l) => l.id === id)) return;
     currentLayoutId.value = id;
Modified +31 -3
diff --git a/core/initApp.ts b/core/initApp.ts
index c27878d..bcfd679 100644
--- a/core/initApp.ts
+++ b/core/initApp.ts
@@ -42,16 +42,42 @@ import App from './App.vue';
 import router from './router';
 import i18n from './i18n';
 import './global.css';
-import MainLayout from './layouts/MainLayout/index.vue';
+import CyberLayout from './layouts/CyberLayout/index.vue';
+import TopMenuLayout from './layouts/TopMenu/index.vue';
 import {
   appProvide,
   hasProvided,
   LayoutKey,
   getProvidedKeys,
 } from './composables/useProvideInject';
+import { registerLayout, currentComponent, currentLayoutId, registeredLayouts, STORAGE_KEY } from './composables/useLayout';
 import { registerPageSections } from './utils/pageSections';
 import autoSectionModules from 'virtual:cube-front-sections';
 
+// 注册内置布局
+registerLayout({
+  id: 'top-menu',
+  label: '顶部菜单',
+  icon: '⊟',
+  description: '顶部导航栏 + 内容区布局',
+  component: TopMenuLayout,
+});
+
+registerLayout({
+  id: 'cyber',
+  label: '赛博风格',
+  icon: '◉',
+  description: '深色科技风格 + 霓虹发光效果',
+  component: CyberLayout,
+  isDefault: true,
+});
+
+// 所有布局注册完成后,从 localStorage 恢复用户选择
+const storedLayout = localStorage.getItem(STORAGE_KEY);
+if (storedLayout && registeredLayouts.value.some((l) => l.id === storedLayout)) {
+  currentLayoutId.value = storedLayout;
+}
+
 /**
  * 应用配置选项接口
  *
@@ -99,7 +125,7 @@ export type ConfigureFunction = (
  *
  * @example 标准子应用入口(无需配置,插件自动处理)
  * ```typescript
- * import { initApp } from '@core/initApp';
+ * import { initApp } from 'cube-front/core/initApp';
  * initApp();
  * ```
  *
@@ -212,8 +238,10 @@ export const initApp = async (optionsOrConfigure?: InitAppOptions | ConfigureFun
 
   // 提供默认的依赖注入值
   // 只有在外部没有提供的情况下才提供默认值
+  // 使用 currentComponent(根据 localStorage 恢复的布局)而不是硬编码的 CyberLayout
   if (!hasProvided(LayoutKey)) {
-    appProvide(app, LayoutKey, MainLayout, { track: true });
+    const layoutComponent = currentComponent.value || CyberLayout;
+    appProvide(app, LayoutKey, layoutComponent, { track: true });
   }
 
   app.mount('#app');
Added +22 -0
diff --git a/core/layouts/CyberLayout.ts b/core/layouts/CyberLayout.ts
new file mode 100644
index 0000000..7df3df5
--- /dev/null
+++ b/core/layouts/CyberLayout.ts
@@ -0,0 +1,22 @@
+/**
+ * CyberLayout - 赛博风格布局
+ *
+ * 特点:
+ * - 默认深色主题,支持明暗切换
+ * - 绿色/青色霓虹发光效果
+ * - 左侧固定侧边栏 + 右侧内容区
+ *
+ * 使用方式:
+ * ```typescript
+ * import CyberLayout from 'cube-front/core/layouts/CyberLayout';
+ * import { LayoutKey } from 'cube-front/core/composables/useProvideInject';
+ *
+ * initApp((app, { provide }) => {
+ *   provide(app, LayoutKey, CyberLayout, { override: true });
+ * });
+ * ```
+ */
+
+export { default as CyberLayout } from './index.vue';
+export { default as CyberSidebar } from './Sidebar/index.vue';
+export { default as CyberNavbar } from './Navbar/index.vue';
\ No newline at end of file
Added +78 -0
diff --git a/core/layouts/CyberLayout/index.vue b/core/layouts/CyberLayout/index.vue
new file mode 100644
index 0000000..fa37ab1
--- /dev/null
+++ b/core/layouts/CyberLayout/index.vue
@@ -0,0 +1,78 @@
+<script setup lang="ts">
+import { ref } from 'vue';
+import Sidebar from './Sidebar/index.vue';
+import Navbar from './Navbar/index.vue';
+
+const isDark = ref(true);
+
+function toggleTheme() {
+  isDark.value = !isDark.value;
+  if (isDark.value) {
+    document.documentElement.removeAttribute('data-theme');
+  } else {
+    document.documentElement.setAttribute('data-theme', 'cyber-light');
+  }
+}
+</script>
+
+<template>
+  <div class="cyber-layout">
+    <!-- 侧边栏 -->
+    <Sidebar />
+
+    <!-- 主内容区 -->
+    <div class="cyber-main">
+      <!-- 顶部导航 -->
+      <Navbar @theme-toggle="toggleTheme" />
+
+      <!-- 页面内容 -->
+      <main class="cyber-content">
+        <slot></slot>
+      </main>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.cyber-layout {
+  display: flex;
+  height: 100vh;
+  overflow: hidden;
+  background: var(--bg-primary);
+  color: var(--text-primary);
+  font-family: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+}
+
+.cyber-main {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  min-width: 0;
+  overflow: hidden;
+}
+
+.cyber-content {
+  flex: 1;
+  min-width: 1200px;
+  overflow: auto;
+  padding: 16px 20px;
+  background:
+    radial-gradient(ellipse at 10% 10%, var(--accent-muted) 0%, transparent 40%),
+    radial-gradient(ellipse at 90% 90%, var(--color-info-bg) 0%, transparent 40%),
+    var(--bg-primary);
+  scrollbar-width: thin;
+  scrollbar-color: var(--border-subtle) transparent;
+
+  &::-webkit-scrollbar {
+    width: 8px;
+    height: 8px;
+  }
+  &::-webkit-scrollbar-track {
+    background: transparent;
+  }
+  &::-webkit-scrollbar-thumb {
+    background: var(--border-subtle);
+    border-radius: 4px;
+  }
+}
+</style>
\ No newline at end of file
Added +269 -0
diff --git a/core/layouts/CyberLayout/Navbar/index.vue b/core/layouts/CyberLayout/Navbar/index.vue
new file mode 100644
index 0000000..0bb3485
--- /dev/null
+++ b/core/layouts/CyberLayout/Navbar/index.vue
@@ -0,0 +1,269 @@
+<script setup lang="ts">
+import { ref, computed } from 'vue';
+import { useI18n } from 'vue-i18n';
+import LayoutSwitcher from 'cube-front/core/components/LayoutSwitcher.vue';
+import { useUserStore } from 'cube-front/core/stores/user';
+import { useMenuStore } from 'cube-front/core/stores/menu';
+
+const emit = defineEmits<{
+  (e: 'theme-toggle'): void;
+}>();
+
+const userStore = useUserStore();
+const menuStore = useMenuStore();
+
+// 用户信息
+const currentUser = computed(() => userStore.userInfo);
+const userName = computed(() => currentUser.value?.displayName || currentUser.value?.name || '管理员');
+
+// 当前页面标题
+const pageTitle = computed(() => menuStore.activeMenu?.title || menuStore.activeMenu?.name || '仪表盘');
+
+// 当前日期
+const currentDate = computed(() => {
+  const now = new Date();
+  const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
+  const year = now.getFullYear();
+  const month = now.getMonth() + 1;
+  const day = now.getDate();
+  const weekday = weekdays[now.getDay()];
+  return `${year}年${month}月${day}日 ${weekday}`;
+});
+
+function handleLogout() {
+  userStore.logout();
+}
+</script>
+
+<template>
+  <header class="cyber-navbar">
+    <!-- 左侧标题 -->
+    <div class="navbar-left">
+      <h2>{{ pageTitle }}</h2>
+      <p>欢迎回来,{{ userName }} · {{ currentDate }}</p>
+    </div>
+
+    <!-- 右侧操作 -->
+    <div class="navbar-right">
+      <!-- 搜索框 -->
+      <div class="search-box">
+        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+          <circle cx="11" cy="11" r="8" />
+          <path d="M21 21l-4.35-4.35" />
+        </svg>
+        <input type="text" placeholder="搜索设备、告警..." />
+      </div>
+
+      <!-- 主题切换 -->
+      <button class="theme-toggle" @click="emit('theme-toggle')" title="切换主题">
+        <!-- 太阳图标(浅色模式) -->
+        <svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke-width="2">
+          <circle cx="12" cy="12" r="5" />
+          <line x1="12" y1="1" x2="12" y2="3" />
+          <line x1="12" y1="21" x2="12" y2="23" />
+          <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
+          <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
+          <line x1="1" y1="12" x2="3" y2="12" />
+          <line x1="21" y1="12" x2="23" y2="12" />
+          <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
+          <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
+        </svg>
+        <!-- 月亮图标(深色模式) -->
+        <svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke-width="2">
+          <path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" />
+        </svg>
+      </button>
+
+      <!-- 布局切换 -->
+      <LayoutSwitcher>
+        <template #icon>
+          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+            <rect x="3" y="3" width="18" height="4" rx="1" />
+            <rect x="3" y="10" width="7" height="11" rx="1" />
+            <rect x="13" y="10" width="8" height="11" rx="1" />
+          </svg>
+        </template>
+      </LayoutSwitcher>
+
+      <!-- 通知按钮 -->
+      <button class="nav-btn">
+        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+          <path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9" />
+          <path d="M13.73 21a2 2 0 01-3.46 0" />
+        </svg>
+        <span>通知</span>
+      </button>
+
+      <!-- 退出登录 -->
+      <button class="nav-btn" @click="handleLogout">
+        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+          <path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4" />
+          <polyline points="16,17 21,12 16,7" />
+          <line x1="21" y1="12" x2="9" y2="12" />
+        </svg>
+        <span>退出</span>
+      </button>
+    </div>
+  </header>
+</template>
+
+<style lang="scss" scoped>
+.cyber-navbar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  height: var(--layout-nav-height, 64px);
+  padding: 0 32px;
+  background: var(--navbar-bg);
+  border-bottom: 1px solid var(--navbar-border);
+}
+
+.navbar-left {
+  h2 {
+    font-size: 18px;
+    font-weight: 600;
+    letter-spacing: -0.3px;
+    margin: 0 0 2px 0;
+    color: var(--navbar-text);
+  }
+
+  p {
+    font-size: 12px;
+    color: var(--navbar-text-muted);
+    margin: 0;
+  }
+}
+
+.navbar-right {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+}
+
+// 搜索框
+.search-box {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  background: var(--bg-tertiary);
+  border: 1px solid var(--border-subtle);
+  border-radius: var(--radius-sm);
+  padding: 8px 12px;
+  width: 220px;
+  transition: border-color 0.2s;
+
+  &:focus-within {
+    border-color: var(--accent);
+  }
+
+  svg {
+    width: 14px;
+    height: 14px;
+    color: var(--text-muted);
+    flex-shrink: 0;
+  }
+
+  input {
+    background: none;
+    border: none;
+    outline: none;
+    color: var(--text-primary);
+    font-family: inherit;
+    font-size: 13px;
+    width: 100%;
+
+    &::placeholder {
+      color: var(--text-muted);
+    }
+  }
+}
+
+// 主题切换
+.theme-toggle {
+  width: 32px;
+  height: 32px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: var(--bg-tertiary);
+  border: 1px solid var(--border-subtle);
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  transition: all 0.2s;
+
+  &:hover {
+    border-color: var(--accent);
+  }
+
+  svg {
+    width: 14px;
+    height: 14px;
+    position: absolute;
+    transition: all 0.3s;
+  }
+
+  .icon-sun {
+    stroke: var(--color-warning);
+    opacity: 0;
+    transform: rotate(-90deg);
+  }
+
+  .icon-moon {
+    stroke: var(--color-warning);
+    transform: rotate(0deg);
+  }
+
+  // 深色主题时显示月亮
+  :global([data-theme='cyber-dark']) & .icon-sun,
+  :global([data-theme='forest-dark']) & .icon-sun,
+  :global(:root:not([data-theme])) & .icon-sun {
+    opacity: 0;
+  }
+
+  :global([data-theme='cyber-dark']) & .icon-moon,
+  :global([data-theme='forest-dark']) & .icon-moon,
+  :global(:root:not([data-theme])) & .icon-moon {
+    opacity: 1;
+  }
+
+  // 浅色主题时显示太阳
+  :global([data-theme='cyber-light']) & .icon-sun,
+  :global([data-theme='forest-light']) & .icon-sun {
+    opacity: 1;
+    transform: rotate(0deg);
+  }
+
+  :global([data-theme='cyber-light']) & .icon-moon,
+  :global([data-theme='forest-light']) & .icon-moon {
+    opacity: 0;
+    transform: rotate(90deg);
+  }
+}
+
+// 通用按钮
+.nav-btn {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  background: var(--bg-tertiary);
+  border: 1px solid var(--border-subtle);
+  border-radius: 6px;
+  padding: 6px 10px;
+  color: var(--text-secondary);
+  font-family: inherit;
+  font-size: 12px;
+  cursor: pointer;
+  transition: all 0.2s;
+
+  svg {
+    width: 14px;
+    height: 14px;
+  }
+
+  &:hover {
+    border-color: var(--accent);
+    color: var(--text-primary);
+  }
+}
+</style>
\ No newline at end of file
Added +258 -0
diff --git a/core/layouts/CyberLayout/Sidebar/index.vue b/core/layouts/CyberLayout/Sidebar/index.vue
new file mode 100644
index 0000000..4bf64f1
--- /dev/null
+++ b/core/layouts/CyberLayout/Sidebar/index.vue
@@ -0,0 +1,258 @@
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue';
+import { storeToRefs } from 'pinia';
+import { getConfig } from 'cube-front/core/configure';
+import { useMenuStore, type TreeMenuItem } from 'cube-front/core/stores/menu';
+import { useUserStore } from 'cube-front/core/stores/user';
+import MenuItem from 'cube-front/core/components/MenuItem.vue';
+
+const config = getConfig();
+const menuStore = useMenuStore();
+const userStore = useUserStore();
+
+const { treeMenus, activeMenu } = storeToRefs(menuStore);
+
+// Logo配置
+const logo = computed(() => config.base.logo);
+const title = computed(() => config.base.title);
+
+// 检测logo宽高比
+const logoAspectRatio = ref<number | null>(null);
+const logoImgRef = ref<HTMLImageElement | null>(null);
+
+const onLogoLoad = (e: Event) => {
+  const img = e.target as HTMLImageElement;
+  if (img.naturalWidth && img.naturalHeight) {
+    logoAspectRatio.value = img.naturalWidth / img.naturalHeight;
+  }
+};
+
+// 是否显示标题(logo为正方形时显示)
+const showLogoTitle = computed(() => {
+  if (!logo.value) return true;
+  if (logoAspectRatio.value === null) return true;
+  const ratio = logoAspectRatio.value;
+  return ratio > 0.9 && ratio < 1.1;
+});
+
+// 用户信息
+const currentUser = computed(() => userStore.userInfo);
+const userInitial = computed(() =>
+  (currentUser.value?.displayName || currentUser.value?.name || 'U').charAt(0).toUpperCase(),
+);
+const userName = computed(() => currentUser.value?.displayName || currentUser.value?.name || '');
+const userEmail = computed(() => currentUser.value?.mail || '');
+
+// 对菜单进行分组(按顶层菜单分组)
+const menuGroups = computed(() => {
+  const groups: { title: string; menus: TreeMenuItem[] }[] = [];
+
+  if (treeMenus.value && treeMenus.value.length > 0) {
+    treeMenus.value.forEach(menu => {
+      groups.push({
+        title: menu.title || menu.name,
+        menus: menu.children || [],
+      });
+    });
+  }
+
+  return groups;
+});
+</script>
+
+<template>
+  <aside class="cyber-sidebar">
+    <!-- 分割线发光效果 -->
+    <div class="sidebar-glow" />
+
+    <!-- Logo 区域 -->
+    <div class="sidebar-logo" :class="{ 'logo-only': !showLogoTitle }">
+      <div v-if="logo" class="logo-icon" :class="{ 'logo-large': !showLogoTitle }">
+        <img ref="logoImgRef" :src="logo" :alt="title" @load="onLogoLoad" />
+      </div>
+      <div v-if="showLogoTitle" class="logo-text">
+        <h1>{{ title }}</h1>
+      </div>
+    </div>
+
+    <!-- 导航菜单 -->
+    <nav class="sidebar-nav">
+      <template v-for="group in menuGroups" :key="group.title">
+        <div v-if="group.menus.length > 0" class="nav-group">
+          <div class="nav-label">{{ group.title }}</div>
+          <MenuItem
+            v-for="menu in group.menus"
+            :key="menu.id"
+            :menu="menu"
+            :activeMenu="activeMenu"
+          />
+        </div>
+      </template>
+    </nav>
+
+    <!-- 用户信息 -->
+    <div class="sidebar-user">
+      <div class="user-avatar">{{ userInitial }}</div>
+      <div class="user-info">
+        <h3>{{ userName }}</h3>
+        <p>{{ userEmail }}</p>
+      </div>
+    </div>
+  </aside>
+</template>
+
+<style lang="scss" scoped>
+.cyber-sidebar {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  background: var(--sidebar-bg);
+  border-right: 1px solid var(--sidebar-border);
+}
+
+.sidebar-glow {
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 1px;
+  height: 100%;
+  background: linear-gradient(
+    180deg,
+    var(--accent),
+    transparent 50%,
+    var(--accent-secondary)
+  );
+  opacity: 0.3;
+}
+
+// Logo 区域
+.sidebar-logo {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 0 16px;
+  border-bottom: 1px solid var(--sidebar-border);
+  height: var(--layout-nav-height, 64px);
+
+  // 长条形 logo:高度固定,宽度自适应
+  &.logo-only {
+    justify-content: center;
+    padding: 0 12px;
+
+    .logo-icon {
+      height: 48px;
+      width: auto;
+      border-radius: var(--radius-sm);
+    }
+  }
+}
+
+.logo-icon {
+  width: 32px;
+  height: 32px;
+  background: linear-gradient(135deg, var(--accent), var(--accent-secondary));
+  border-radius: var(--radius-sm);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  overflow: hidden;
+
+  img {
+    width: 100%;
+    height: 100%;
+    object-fit: contain;
+  }
+}
+
+.logo-text {
+  h1 {
+    font-size: 14px;
+    font-weight: 600;
+    letter-spacing: -0.2px;
+    margin: 0;
+    color: var(--text-primary);
+  }
+}
+
+// 导航菜单
+.sidebar-nav {
+  flex: 1;
+  padding: 20px 12px;
+  overflow-y: auto;
+  scrollbar-width: thin;
+  scrollbar-color: var(--border-subtle) transparent;
+
+  &::-webkit-scrollbar {
+    width: 4px;
+  }
+  &::-webkit-scrollbar-track {
+    background: transparent;
+  }
+  &::-webkit-scrollbar-thumb {
+    background: var(--border-subtle);
+    border-radius: 4px;
+  }
+}
+
+.nav-group {
+  margin-bottom: 24px;
+}
+
+.nav-label {
+  font-size: 10px;
+  text-transform: uppercase;
+  letter-spacing: 1.5px;
+  color: var(--text-muted);
+  padding: 0 12px;
+  margin-bottom: 10px;
+}
+
+// 用户信息
+.sidebar-user {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 16px;
+  border-top: 1px solid var(--sidebar-border);
+}
+
+.user-avatar {
+  width: 32px;
+  height: 32px;
+  border-radius: var(--radius-sm);
+  background: linear-gradient(135deg, var(--violet-400), var(--rose-400));
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-weight: 600;
+  font-size: 12px;
+  flex-shrink: 0;
+  color: white;
+}
+
+.user-info {
+  flex: 1;
+  min-width: 0;
+
+  h3 {
+    font-size: 12px;
+    font-weight: 600;
+    margin: 0;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    color: var(--text-primary);
+  }
+
+  p {
+    font-size: 11px;
+    color: var(--text-muted);
+    margin: 0;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+}
+</style>
\ No newline at end of file
Modified +1 -1
diff --git a/core/layouts/MainLayout/index.vue b/core/layouts/MainLayout/index.vue
index 5d5fb9f..af057ea 100644
--- a/core/layouts/MainLayout/index.vue
+++ b/core/layouts/MainLayout/index.vue
@@ -3,7 +3,7 @@ import { ref } from 'vue';
 import Navbar from './Navbar/index.vue';
 import Content from './Content/index.vue';
 import Sider from './Sider/index.vue';
-import { useUserStore } from '@core/stores/user';
+import { useUserStore } from 'cube-front/core/stores/user';
 
 const userStore = useUserStore();
 const collapsed = ref(false);
Modified +2 -2
diff --git a/core/layouts/MainLayout/Navbar/CascaderMenu/components/SecondCascaderMenu.vue b/core/layouts/MainLayout/Navbar/CascaderMenu/components/SecondCascaderMenu.vue
index 7ef0e8d..17e5a22 100644
--- a/core/layouts/MainLayout/Navbar/CascaderMenu/components/SecondCascaderMenu.vue
+++ b/core/layouts/MainLayout/Navbar/CascaderMenu/components/SecondCascaderMenu.vue
@@ -1,6 +1,6 @@
 <script setup lang="ts">
-import { type TreeMenuItem } from '@core/stores/menu';
-import { renderMenuTitle } from '@core/utils/menuHelpers';
+import { type TreeMenuItem } from 'cube-front/core/stores/menu';
+import { renderMenuTitle } from 'cube-front/core/utils/menuHelpers';
 import SecondCascaderMenuItem from './SecondCascaderMenuItem.vue';
 import { computed } from 'vue';
 
Modified +3 -3
diff --git a/core/layouts/MainLayout/Navbar/CascaderMenu/components/SecondCascaderMenuItem.vue b/core/layouts/MainLayout/Navbar/CascaderMenu/components/SecondCascaderMenuItem.vue
index a807b69..1ab2fbb 100644
--- a/core/layouts/MainLayout/Navbar/CascaderMenu/components/SecondCascaderMenuItem.vue
+++ b/core/layouts/MainLayout/Navbar/CascaderMenu/components/SecondCascaderMenuItem.vue
@@ -1,7 +1,7 @@
 <script setup lang="ts">
-import { type TreeMenuItem } from '@core/stores/menu';
-import { isChildMenu, renderMenuTitle } from '@core/utils/menuHelpers';
-import TextOverflow from '@core/components/TextOverflow.vue';
+import { type TreeMenuItem } from 'cube-front/core/stores/menu';
+import { isChildMenu, renderMenuTitle } from 'cube-front/core/utils/menuHelpers';
+import TextOverflow from 'cube-front/core/components/TextOverflow.vue';
 
 interface SecondCascaderMenuItemProps {
   menu: TreeMenuItem;
Modified +2 -2
diff --git a/core/layouts/MainLayout/Navbar/CascaderMenu/index.vue b/core/layouts/MainLayout/Navbar/CascaderMenu/index.vue
index 90dda88..2184f61 100644
--- a/core/layouts/MainLayout/Navbar/CascaderMenu/index.vue
+++ b/core/layouts/MainLayout/Navbar/CascaderMenu/index.vue
@@ -1,8 +1,8 @@
 <script setup lang="ts">
 import { computed } from 'vue';
-import { type TreeMenuItem } from '@core/stores/menu';
+import { type TreeMenuItem } from 'cube-front/core/stores/menu';
 import SecondCascaderMenu from './components/SecondCascaderMenu.vue';
-import { isChildMenu, hasChildren } from '@core/utils/menuHelpers';
+import { isChildMenu, hasChildren } from 'cube-front/core/utils/menuHelpers';
 
 interface CascaderMenuProps {
   menu: TreeMenuItem;
Modified +4 -4
diff --git a/core/layouts/MainLayout/Navbar/index.vue b/core/layouts/MainLayout/Navbar/index.vue
index c452b91..93623f5 100644
--- a/core/layouts/MainLayout/Navbar/index.vue
+++ b/core/layouts/MainLayout/Navbar/index.vue
@@ -1,10 +1,10 @@
 <script setup lang="ts">
 import { storeToRefs } from 'pinia';
 import Menu from './Menu/index.vue';
-import { type UserInfo } from '@core/stores/user';
-import { useMenuStore } from '@core/stores/menu';
-import { getConfig } from '@core/configure';
-import { useTheme, THEMES } from '@core/composables/useTheme';
+import { type UserInfo } from 'cube-front/core/stores/user';
+import { useMenuStore } from 'cube-front/core/stores/menu';
+import { getConfig } from 'cube-front/core/configure';
+import { useTheme, THEMES } from 'cube-front/core/composables/useTheme';
 
 interface NavbarProps {
   currentUser?: Partial<UserInfo>;
Modified +3 -3
diff --git a/core/layouts/MainLayout/Navbar/Menu/index.vue b/core/layouts/MainLayout/Navbar/Menu/index.vue
index 0ab7156..93d713a 100644
--- a/core/layouts/MainLayout/Navbar/Menu/index.vue
+++ b/core/layouts/MainLayout/Navbar/Menu/index.vue
@@ -1,11 +1,11 @@
 <script setup lang="tsx">
 import { computed, ref, onMounted, nextTick, watch } from 'vue';
 import { useRouter } from 'vue-router';
-import { type TreeMenuItem, useMenuStore } from '@core/stores/menu';
+import { type TreeMenuItem, useMenuStore } from 'cube-front/core/stores/menu';
 import CascaderMenu from '../CascaderMenu/index.vue';
 import { ElScrollbar } from 'element-plus';
-import { hasChildren } from '@core/utils/menuHelpers';
-import { openMenuTab } from '@core/utils/menuTab';
+import { hasChildren } from 'cube-front/core/utils/menuHelpers';
+import { openMenuTab } from 'cube-front/core/utils/menuTab';
 
 interface MenuProps {
   tabPanes?: Array<TreeMenuItem>;
Modified +1 -1
diff --git a/core/layouts/MainLayout/Sider/index.vue b/core/layouts/MainLayout/Sider/index.vue
index 77761ed..426165d 100644
--- a/core/layouts/MainLayout/Sider/index.vue
+++ b/core/layouts/MainLayout/Sider/index.vue
@@ -1,6 +1,6 @@
 <script setup lang="ts">
 import { computed } from 'vue';
-import { useMenuStore } from '@core/stores/menu';
+import { useMenuStore } from 'cube-front/core/stores/menu';
 import SideMenuItem from './SideMenuItem.vue';
 
 const menuStore = useMenuStore();
Modified +4 -4
diff --git a/core/layouts/MainLayout/Sider/SideMenuItem.vue b/core/layouts/MainLayout/Sider/SideMenuItem.vue
index 606624a..5907a09 100644
--- a/core/layouts/MainLayout/Sider/SideMenuItem.vue
+++ b/core/layouts/MainLayout/Sider/SideMenuItem.vue
@@ -1,9 +1,9 @@
 <script setup lang="ts">
 import { ref, computed, watch } from 'vue';
-import { type TreeMenuItem } from '@core/stores/menu';
-import { isChildMenu, renderMenuTitle, hasChildren } from '@core/utils/menuHelpers';
-import { openMenuTab } from '@core/utils/menuTab';
-import { useMenuStore } from '@core/stores/menu';
+import { type TreeMenuItem } from 'cube-front/core/stores/menu';
+import { isChildMenu, renderMenuTitle, hasChildren } from 'cube-front/core/utils/menuHelpers';
+import { openMenuTab } from 'cube-front/core/utils/menuTab';
+import { useMenuStore } from 'cube-front/core/stores/menu';
 
 const props = withDefaults(
   defineProps<{
Modified +2 -2
diff --git a/core/layouts/TopMenu/IconRail/index.vue b/core/layouts/TopMenu/IconRail/index.vue
index fe510e1..3a97639 100644
--- a/core/layouts/TopMenu/IconRail/index.vue
+++ b/core/layouts/TopMenu/IconRail/index.vue
@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import { storeToRefs } from 'pinia';
-import { useMenuStore, type TreeMenuItem } from '@core/stores/menu';
-import { openMenuTab } from '@core/utils/menuTab';
+import { useMenuStore, type TreeMenuItem } from 'cube-front/core/stores/menu';
+import { openMenuTab } from 'cube-front/core/utils/menuTab';
 
 const menuStore = useMenuStore();
 const { treeMenus, topLevelActiveMenu } = storeToRefs(menuStore);
Modified +1 -1
diff --git a/core/layouts/TopMenu/index.vue b/core/layouts/TopMenu/index.vue
index 9241d04..1459ddd 100644
--- a/core/layouts/TopMenu/index.vue
+++ b/core/layouts/TopMenu/index.vue
@@ -1,6 +1,6 @@
 <script setup lang="ts">
 import { computed } from 'vue';
-import { useMenuStore, type TreeMenuItem } from '@core/stores/menu';
+import { useMenuStore, type TreeMenuItem } from 'cube-front/core/stores/menu';
 import Topnav from './Topnav/index.vue';
 import Content from './Content/index.vue';
 
Modified +6 -6
diff --git a/core/layouts/TopMenu/Topnav/index.vue b/core/layouts/TopMenu/Topnav/index.vue
index f03541a..889d1b2 100644
--- a/core/layouts/TopMenu/Topnav/index.vue
+++ b/core/layouts/TopMenu/Topnav/index.vue
@@ -1,12 +1,12 @@
 <script setup lang="ts">
 import { ref, computed } from 'vue';
 import { storeToRefs } from 'pinia';
-import { useMenuStore, type TreeMenuItem } from '@core/stores/menu';
-import { useUserStore } from '@core/stores/user';
-import { getConfig } from '@core/configure';
-import { openMenuTab } from '@core/utils/menuTab';
-import ThemeSwitcher from '@core/components/ThemeSwitcher.vue';
-import LayoutSwitcher from '@core/components/LayoutSwitcher.vue';
+import { useMenuStore, type TreeMenuItem } from 'cube-front/core/stores/menu';
+import { useUserStore } from 'cube-front/core/stores/user';
+import { getConfig } from 'cube-front/core/configure';
+import { openMenuTab } from 'cube-front/core/utils/menuTab';
+import ThemeSwitcher from 'cube-front/core/components/ThemeSwitcher.vue';
+import LayoutSwitcher from 'cube-front/core/components/LayoutSwitcher.vue';
 
 const menuStore = useMenuStore();
 const userStore = useUserStore();
Modified +1 -15
diff --git a/core/main.ts b/core/main.ts
index b9ea2df..b8c69f1 100644
--- a/core/main.ts
+++ b/core/main.ts
@@ -1,17 +1,3 @@
 import { initApp } from './initApp';
-import TopMenuLayout from './layouts/TopMenu/index.vue';
-import { registerLayout } from './composables/useLayout';
 
-// 注册内置布局(可在应用层继续 registerLayout 添加更多)
-registerLayout({
-  id: 'top-menu',
-  label: '顶部菜单',
-  icon: '⊟',
-  description: '顶部导航栏 + 内容区布局',
-  component: TopMenuLayout,
-});
-
-initApp((app) => {
-  // 应用初始化回调,可在此注册更多布局或进行其他配置
-  void app;
-});
+initApp();
\ No newline at end of file