一、页面效果
1、默认的左右布局
2、切换后的上下布局
二、代码实现
1、布局切换服务类,用于通知需要改变布局的模块去改变样式
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
@Injectable()
export class LayoutChangeService {
constructor() {}
private Source = new Subject<any>();
Status$ = this.Source.asObservable();
/**
*@函数名称:layoutChange
*@参数:message 字符类型参数
*@作用:接受布局切换的变化,用户通知各个模块做相应的样式改变
*@date 2018/5/16
*/
layoutChange(message: any) {
const msg = JSON.parse(message);
this.Source.next(msg);
this.setLayout(msg);
}
/**
* 获取布局方式的缓存 true为上下布局 false为左右布局
* @returns {boolean}
*/
get getLayout() {
const layout = localStorage.getItem('layoutMode');
if (layout === 'true') {
return true;
} else {
return false;
}
}
/**
* 设置默认的布局方式缓存
* @param val
*/
setLayout(val) {
localStorage.setItem('layoutMode', val);
}
}
2、新增HeaderLayoutComponent ,增加切换的入口,切换布局时通知其他需要修改的模块去改变样式。
import { Component, HostListener } from '@angular/core';
import { LayoutChangeService } from '@core/layout/layoutChange.service';
@Component({
selector: 'header-layout',
template: `
<i class="anticon anticon-{{status? 'shrink' : 'arrows-alt'}}"></i>
{{(status ? '左右布局' : '上下布局') | translate }}
`,
host: {
'[class.d-block]': 'true',
},
})
export class HeaderLayoutComponent {
constructor(public changeService: LayoutChangeService) {}
status = this.changeService.getLayout;
@HostListener('window:resize')
_resize() {
// this.status = screenfull.isFullscreen;
}
@HostListener('click', ['$event'])
_changeTheme(e) {
this.changeService.layoutChange(!this.status);
this.status = !this.status;
}
}
3、修改需要改变样式的部分(注意事项:通过俩层dom的click事件来动态改变topMenu的高度,达到显示和隐藏topMenu的效果,且解决topMenu遮罩层覆盖其他模块问题)
1)菜单dom,处理自己的样式变化,大部分样式修改都在该模块完成。
<div class="aside-inner" [ngClass]="{'aside-inner-top' : layoutStatus}">
<sidebar-nav class="d-block py-lg" [ngClass]="{'ul-nav' : layoutStatus}"
(click)="onclick()"
(select)="callDestroy($event)"></sidebar-nav>
</div>
import {
Component,
ElementRef,
AfterViewInit,
ViewChild,
Renderer2,
} from '@angular/core';
import { NzMessageService } from 'ng-zorro-antd';
import { SettingsService } from '@delon/theme';
import { TabChangeService } from '@core/tabChange/tabChange.service';
import { LayoutChangeService } from '@core/layout/layoutChange.service';
import { Subscription } from 'rxjs/Subscription';
import { AppUtil } from '@core/util/util.service';
import { StartupService } from '@core/startup/startup.service';
@Component({
selector: 'app-sidebar',
templateUrl: './sidebar.component.html',
})
export class SidebarComponent implements AfterViewInit {
subscription: Subscription;
layoutStatus = this.layoutService.getLayout;
@ViewChild('sidebarNav') sidebarNav;
constructor(
public settings: SettingsService,
public msgSrv: NzMessageService,
public changeService: TabChangeService,
public layoutService: LayoutChangeService,
public el: ElementRef,
public util: AppUtil,
public renderer2: Renderer2,
public startupService: StartupService,
) {
this.subscription = layoutService.Status$.subscribe(message => {
// 获取当前dashboard的名称 和切换的tab页相互校验
this.layoutStatus = message;
this.setLayout(message);
});
}
ngAfterViewInit() {
this.setLayout(this.layoutService.getLayout);
}
// 设置样式
setLayout(direction) {
// border-left: 3px solid #349ac0;
this.hideULBorder(direction);
const el = this.el.nativeElement;
this.setStyles(el.querySelectorAll('ul'), direction);
this.setStyles(el.querySelectorAll('li'), direction);
// 处理小屏模式下 文字隐藏导致切换后头部菜单无法显示
this.setCollapsed();
this.setTopMenuShow();
}
onclick() {
setTimeout(() => this.setTopMenuShow());
}
// 隐藏ul的有边框
hideULBorder(direction) {
const el = this.el.nativeElement;
const boardRight = direction ? '1px solid #18191d' : '';
el.querySelector('ul').style['border-right'] = boardRight;
}
// 设置屏幕为非pad
setCollapsed() {
if (this.settings.layout.collapsed && this.layoutStatus) {
this.settings.setLayout('collapsed', !this.settings.layout.collapsed);
}
}
// 设置样式属性值
setStyles(dom, direction) {
const len = dom.length;
// 设置默认的样式 切换后改变样式
let float = 'none';
let display = '';
let absolute = '';
let top = '';
let backgroundColor = '';
if (direction) {
float = 'left';
display = 'none';
absolute = 'absolute';
top = '60px';
backgroundColor = this.util.getColor('background');
}
// 循环改变样式
for (let i = 0; i < len; i++) {
const singleItem = dom[i];
// 设置ui和li的左悬浮样式
singleItem.style['float'] = float;
singleItem.style['list-style-type'] = 'none';
const tagName = singleItem.tagName;
// 隐藏图标
const img = singleItem.querySelector('i');
if (img) {
// TODO 是否显示图标 && this.settings.layout.collapsed
img.style['display'] = display;
}
if (tagName === 'LI') {
// 隐藏导航菜单项
const textDom = singleItem.querySelector('span');
if (textDom && textDom.innerText === '导航菜单') {
singleItem.style['display'] = display;
}
// 处理有二级菜单节点的样式
const ULINLI = singleItem.querySelector('ul');
if (ULINLI) {
ULINLI.style['position'] = absolute;
ULINLI.style['top'] = top;
ULINLI.style['background-color'] = backgroundColor;
}
}
}
}
// 隐藏二级菜单
setTopMenuShow() {
this.setTreeMenuHeight(true);
}
// 二级菜单显示
setTopMenuHide() {
this.setTreeMenuHeight(false);
}
// 设置topMenu的高度 防止覆盖到模块中
setTreeMenuHeight(show) {
const me = this;
const dom = this.el.nativeElement;
if (me.layoutStatus) {
if (!show && !dom.style.height) {
dom.style.height = '60px';
} else if (show && dom.style.height) {
dom.style.height = '';
}
} else {
if (show && dom.style.height) {
dom.style.height = '';
}
}
}
/**
*@函数名称:callDestroy
*@参数:val 组件传递的参数
*@作用:给服务传递切换信息
*@date 2018/5/16
*/
callDestroy(val) {
const value = {
title: val.text,
url: val.link,
};
this.changeService.tabChange(JSON.stringify(value));
// 切换将topMeun隐藏
this.setTopMenuHide();
}
}
2)默认布局LayoutDefaultComponent,点击该层dom时,将topMenu隐藏,防止遮住内容显示区域,
<div class="wrapper" (click)="onclick($event)">
<app-sidebar #appSidebar class="aside" [ngClass]="{'aside-top' : layoutStatus}"></app-sidebar>
<div class="router-progress-bar" *ngIf="isFetching"></div>
<app-header class="header" [ngClass]="{'header-top' : layoutStatus}"></app-header>
<section class="content" [ngClass]="{'content-top' : layoutStatus}">
<reuse-tab (change)="tabChange($event)" [ngClass]="{'reuse-tab-fixed-top' : layoutStatus}"></reuse-tab>
<router-outlet></router-outlet>
</section>
</div>
import { Component, OnDestroy, ViewChild } from '@angular/core';
import {
Router,
NavigationEnd,
RouteConfigLoadStart,
NavigationError,
} from '@angular/router';
import { NzMessageService, NzModalService } from 'ng-zorro-antd';
import { ScrollService, MenuService, SettingsService } from '@delon/theme';
import { TabChangeService } from '@core/tabChange/tabChange.service';
import { LayoutChangeService } from '@core/layout/layoutChange.service';
import { Subscription } from 'rxjs/Subscription';
@Component({
selector: 'layout-default',
templateUrl: './default.component.html',
})
export class LayoutDefaultComponent implements OnDestroy {
isFetching = false;
subscription: Subscription;
@ViewChild('appSidebar') appSidebar;
constructor(
router: Router,
scroll: ScrollService,
private _message: NzMessageService,
public menuSrv: MenuService,
public settings: SettingsService,
public changeService: TabChangeService,
public layoutService: LayoutChangeService,
) {
// scroll to top in change page
router.events.subscribe(evt => {
if (!this.isFetching && evt instanceof RouteConfigLoadStart) {
this.isFetching = true;
}
if (evt instanceof NavigationError) {
this.isFetching = false;
_message.error(`无法加载${evt.url}路由`, { nzDuration: 1000 * 3 });
return;
}
if (!(evt instanceof NavigationEnd)) {
return;
}
setTimeout(() => {
scroll.scrollToTop();
this.isFetching = false;
}, 100);
});
this.subscription = layoutService.Status$.subscribe(message => {
// 获取当前dashboard的名称 和切换的tab页相互校验
this.layoutStatus = message;
});
}
/**
*@函数名称:tabChange
*@参数:val获取组件传过来的值,传递下去
*@作用:给服务传递切换信息
*@date 2018/5/16
*/
tabChange(val): void {
const value = {
title: val.title,
url: val.url,
};
this.changeService.tabChange(JSON.stringify(value));
}
layoutStatus = this.layoutService.getLayout;
ngOnDestroy() {
this.subscription.unsubscribe();
}
/**
* 点击将topMenu隐藏
*/
onclick(e) {
this.appSidebar.setTopMenuHide();
}
}
3)需要覆盖的样式
//处理动态切换menu布局样式覆盖
.aside-top {
float: left;
margin-left: 20px;
margin-top: 0 !important;
z-index: 30;
width: @top-menu-width;
left: 170px;
background-color: transparent;
}
// 覆盖组件样式,有边框
.aside-top::after {
border-right: 0 solid @primary-color !important;
}
// 覆盖样式,左边高度处理
.content-top {
margin-left: 27px;
}
// 动态路由左边距离处理
.reuse-tab-fixed-top {
left: 27px !important;
}
// 样式覆盖
.aside-inner-top {
float: left;
margin-left: 20px;
width: 100%;
}
// 样式覆盖
.ul-nav {
padding-top: 12px !important;
}
// 样式覆盖
.header-search-top {
padding-left: @top-menu-Rightwidth;
}