import { Component, OnInit, OnDestroy, ViewChild, ElementRef, AfterViewInit, } from '@angular/core'; import { Router, RouterOutlet, RouterModule, NavigationEnd, } from '@angular/router'; import { CommonModule } from '@angular/common'; import { filter, Subscription } from 'rxjs'; // 导入所有需要的 NG-ZORRO 模块 import { NzLayoutModule } from 'ng-zorro-antd/layout'; import { NzMenuModule } from 'ng-zorro-antd/menu'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzButtonModule } from 'ng-zorro-antd/button'; import { NzTabsModule } from 'ng-zorro-antd/tabs'; import { AuthService } from '../services/auth.service'; interface TabItem { title: string; path: string; } @Component({ selector: 'app-main-layout', template: `
{{ tab.title }}
    • 个人设置
    • 退出登录
`, styles: [ ` :host { display: flex; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .app-layout { min-height: 100vh; display: flex; } /* 侧边栏样式(保持不变) */ .menu-sidebar { position: relative; z-index: 1001; min-height: 100vh; box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); background: #001529; } .menu-sidebar-inner { position: sticky; height: 100vh; top: 0; left: 0; display: flex; flex-direction: column; background: #001529; } .menu { flex: 1; overflow-y: auto; padding-bottom: 20px; } /* 侧边栏Logo */ .sidebar-logo { height: 64px; padding-left: 14px; overflow: hidden; line-height: 64px; } .sidebar-logo img { display: inline-block; height: 32px; width: 32px; vertical-align: middle; } .sidebar-logo h1 { display: inline-block; margin: 0 0 0 20px; color: #fff; font-weight: 600; font-size: 14px; vertical-align: middle; } /* 顶部导航栏核心修复样式 */ .header { padding: 0; width: 100%; z-index: 1000; /* 固定顶部高度,避免元素挤压换行 */ height: 64px; } /* 顶部容器:flex布局确保子元素同行 */ .header-container { display: flex; align-items: center; justify-content: space-between; height: 100%; padding: 0 8px; background: #fff; box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); } /* 1. 折叠按钮容器:固定宽度,不压缩 */ .header-trigger-wrapper { width: 64px; /* 与折叠按钮点击区域匹配 */ display: flex; align-items: center; justify-content: center; } .header-trigger { height: 100%; width: 100%; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s; } .trigger:hover { color: #1890ff; } /* 2. Tab标签页容器:占满中间空间,溢出时横向滚动 */ .tab-container { flex: 1; /* 关键:占满剩余宽度 */ margin: 0 8px; height: 100%; display: flex; align-items: center; overflow: hidden; /* 隐藏超出容器的内容 */ } .tabs-wrapper { width: 100%; height: 100%; overflow-x: auto; /* 横向滚动 */ white-space: nowrap; /* 禁止Tab换行 */ /* 隐藏滚动条,保持美观 */ scrollbar-width: none; -ms-overflow-style: none; } .tabs-wrapper::-webkit-scrollbar { display: none; } /* 优化Tab样式,确保垂直居中 */ :host ::ng-deep .ant-tabs-card { height: 100%; display: flex; flex-direction: column; } :host ::ng-deep .ant-tabs-card .ant-tabs-card-bar { border-bottom: none; flex-shrink: 0; /* 禁止Tab栏被压缩 */ } :host ::ng-deep .ant-tabs-card .ant-tabs-tab { height: 36px; line-height: 36px; margin-top: 8px; /* 与顶部保持间距 */ } /* 3. Admin菜单容器:固定宽度,不压缩 */ .user-menu-wrapper { width: 120px; /* 与Admin菜单宽度匹配 */ display: flex; align-items: center; justify-content: center; } .user-menu { width: 100%; display: flex; align-items: center; justify-content: center; } /* 内容区域样式(保持不变) */ nz-content { margin: 0; background-color: white; } .inner-content { padding: 24px; background: #fff; min-height: calc(100vh - 64px); } /* Tab标签页细节样式 */ .tab-title { cursor: pointer; } .close-icon { margin-left: 8px; font-size: 12px; color: rgba(0, 0, 0, 0.45); cursor: pointer; } .close-icon:hover { color: #f5222d; } `, ], standalone: true, imports: [ CommonModule, RouterOutlet, RouterModule, NzLayoutModule, NzMenuModule, NzIconModule, NzButtonModule, NzTabsModule, ], }) export class MainLayoutComponent implements OnInit, OnDestroy, AfterViewInit { @ViewChild('tabsWrapper') tabsWrapper!: ElementRef; @ViewChild('content') content!: ElementRef; isCollapsed = true; openTabs: TabItem[] = []; activePath: string | null = null; private routerSubscription!: Subscription; private routeTitleMap: { [key: string]: string } = { '/dashboard': '数据看板', '/user': '用户管理', '/message-activity': '消息通知', '/message-record': '推送记录', '/message-template': '消息模板', '/message-strategy': '推送策略', '/message-statistics': '统计分析', }; constructor(private router: Router, private authService: AuthService) {} ngOnInit(): void { // 监听路由变化,更新标签页 this.routerSubscription = this.router.events .pipe(filter((event) => event instanceof NavigationEnd)) .subscribe((event: NavigationEnd) => { const path = event.urlAfterRedirects.split('?')[0]; this.activePath = path; // 新增未打开的标签页 if (!this.openTabs.some((tab) => tab.path === path)) { const title = this.routeTitleMap[path] || '新页面'; this.openTabs.push({ title, path }); } }); // 初始化当前路由标签页 this.activePath = this.router.url.split('?')[0]; if ( this.activePath && !this.openTabs.some((tab) => tab.path === this.activePath) ) { const title = this.routeTitleMap[this.activePath] || '新页面'; this.openTabs.push({ title, path: this.activePath }); } } ngAfterViewInit(): void { this.scrollToActiveTab(); } ngOnDestroy(): void { if (this.routerSubscription) { this.routerSubscription.unsubscribe(); } } // 获取当前标签页索引 getTabIndex(path: string | null): number { return path ? this.openTabs.findIndex((tab) => tab.path === path) : -1; } // 切换标签页 selectTab(path: string | undefined): void { if (path && this.activePath !== path) { this.router.navigateByUrl(path); } } // 关闭标签页 closeTab(path: string, index: number): void { if (this.openTabs.length <= 1) return; this.openTabs = this.openTabs.filter((tab) => tab.path !== path); // 若关闭当前激活标签页,跳转至前一个标签页 if (this.activePath === path) { const newIndex = Math.max(0, index - 1); this.router.navigateByUrl(this.openTabs[newIndex].path); } } // 退出登录 logout(): void { this.authService.logout(); this.router.navigate(['/login']); } // 滚动标签页(左右滚动) scrollTabs(direction: 'left' | 'right'): void { const wrapper = this.tabsWrapper.nativeElement; wrapper.scrollBy({ left: direction === 'left' ? -150 : 150, behavior: 'smooth', }); } // 滚动到当前激活标签页 private scrollToActiveTab(): void { if (!this.tabsWrapper) return; const activeTab = this.tabsWrapper.nativeElement.querySelector( '.ant-tabs-tab-active' ); if (activeTab) { activeTab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center', }); } } }