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: `
`,
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',
});
}
}
}