项目初始化
This commit is contained in:
16
src/app/layout/pro/components/footer/footer.component.html
Normal file
16
src/app/layout/pro/components/footer/footer.component.html
Normal file
@ -0,0 +1,16 @@
|
||||
<global-footer>
|
||||
<global-footer-item href="https://e.ng-alain.com/theme/pro" blankTarget>
|
||||
Pro 首页
|
||||
</global-footer-item>
|
||||
<global-footer-item href="https://github.com/ng-alain" blankTarget>
|
||||
<i nz-icon nzType="github"></i>
|
||||
</global-footer-item>
|
||||
<global-footer-item href="https://ng-alain.github.io/ng-alain/" blankTarget>
|
||||
Alain Pro
|
||||
</global-footer-item>
|
||||
Copyright
|
||||
<i nz-icon nzType="copyright" class="mx-sm"></i>
|
||||
{{ year }}
|
||||
<a href="//github.com/cipchk" target="_blank" class="mx-sm">卡色</a>
|
||||
出品
|
||||
</global-footer>
|
||||
15
src/app/layout/pro/components/footer/footer.component.ts
Normal file
15
src/app/layout/pro/components/footer/footer.component.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { SettingsService } from '@delon/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'layout-pro-footer',
|
||||
templateUrl: './footer.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LayoutProFooterComponent {
|
||||
get year(): number {
|
||||
return this.setting.app.year;
|
||||
}
|
||||
|
||||
constructor(private setting: SettingsService) {}
|
||||
}
|
||||
25
src/app/layout/pro/components/header/header.component.html
Normal file
25
src/app/layout/pro/components/header/header.component.html
Normal file
@ -0,0 +1,25 @@
|
||||
<div *ngIf="pro.isTopMenu" class="alain-pro__top-nav">
|
||||
<div class="alain-pro__top-nav-main" [ngClass]="{ 'alain-pro__top-nav-main-wide': pro.isFixed }">
|
||||
<div class="alain-pro__top-nav-main-left">
|
||||
<layout-pro-logo class="alain-pro__top-nav-logo"></layout-pro-logo>
|
||||
<div class="alain-pro__menu-wrap">
|
||||
<div layout-pro-menu mode="horizontal"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alain-pro__top-nav-main-right" layout-pro-header-widget></div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!pro.isTopMenu" class="alain-pro__header">
|
||||
<div class="d-flex align-items-center">
|
||||
<ng-container *ngIf="pro.isMobile">
|
||||
<a [routerLink]="['/']" class="alain-pro__header-logo">
|
||||
<img src="./assets/logo-color.svg" width="32" />
|
||||
</a>
|
||||
<div class="ant-divider ant-divider-vertical"></div>
|
||||
</ng-container>
|
||||
<div class="alain-pro__header-item alain-pro__header-trigger" (click)="pro.setCollapsed()">
|
||||
<i nz-icon [nzType]="collapsedIcon" class="alain-pro__header-item-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div layout-pro-header-widget></div>
|
||||
</div>
|
||||
68
src/app/layout/pro/components/header/header.component.ts
Normal file
68
src/app/layout/pro/components/header/header.component.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, Inject, OnDestroy, OnInit } from '@angular/core';
|
||||
import { RTL, RTLService } from '@delon/theme';
|
||||
import { combineLatest, fromEvent, Subject } from 'rxjs';
|
||||
import { distinctUntilChanged, takeUntil, tap, throttleTime } from 'rxjs/operators';
|
||||
|
||||
import { BrandService } from '../../pro.service';
|
||||
|
||||
@Component({
|
||||
selector: 'layout-pro-header',
|
||||
templateUrl: './header.component.html',
|
||||
host: {
|
||||
'[class.ant-layout-header]': 'true',
|
||||
'[class.alain-pro__header-fixed]': 'pro.fixedHeader',
|
||||
'[class.alain-pro__header-hide]': 'hideHeader',
|
||||
'[style.padding.px]': '0'
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LayoutProHeaderComponent implements OnInit, OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
hideHeader = false;
|
||||
|
||||
@HostBinding('style.width')
|
||||
get getHeadWidth(): string {
|
||||
const { isMobile, fixedHeader, menu, collapsed, width, widthInCollapsed } = this.pro;
|
||||
if (isMobile || !fixedHeader || menu === 'top') {
|
||||
return '100%';
|
||||
}
|
||||
return collapsed ? `calc(100% - ${widthInCollapsed}px)` : `calc(100% - ${width}px)`;
|
||||
}
|
||||
|
||||
get collapsedIcon(): string {
|
||||
let type = this.pro.collapsed ? 'unfold' : 'fold';
|
||||
if (this.rtl.dir === RTL) {
|
||||
type = this.pro.collapsed ? 'fold' : 'unfold';
|
||||
}
|
||||
return `menu-${type}`;
|
||||
}
|
||||
|
||||
constructor(public pro: BrandService, @Inject(DOCUMENT) private doc: any, private cdr: ChangeDetectorRef, private rtl: RTLService) {}
|
||||
|
||||
private handScroll(): void {
|
||||
if (!this.pro.autoHideHeader) {
|
||||
this.hideHeader = false;
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.hideHeader = this.doc.body.scrollTop + this.doc.documentElement.scrollTop > this.pro.autoHideHeaderTop;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
combineLatest([
|
||||
this.pro.notify.pipe(tap(() => this.cdr.markForCheck())),
|
||||
fromEvent(window, 'scroll', { passive: false }).pipe(throttleTime(50), distinctUntilChanged())
|
||||
])
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(() => this.handScroll());
|
||||
|
||||
this.rtl.change.pipe(takeUntil(this.destroy$)).subscribe(() => this.cdr.detectChanges());
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
4
src/app/layout/pro/components/logo/logo.component.html
Normal file
4
src/app/layout/pro/components/logo/logo.component.html
Normal file
@ -0,0 +1,4 @@
|
||||
<a [routerLink]="['/']" class="d-flex align-items-center">
|
||||
<img src="./assets/logo-color.svg" alt="{{ name }}" height="32" />
|
||||
<h1>{{ name }}</h1>
|
||||
</a>
|
||||
15
src/app/layout/pro/components/logo/logo.component.ts
Normal file
15
src/app/layout/pro/components/logo/logo.component.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { SettingsService } from '@delon/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'layout-pro-logo',
|
||||
templateUrl: './logo.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LayoutProLogoComponent {
|
||||
get name(): string {
|
||||
return this.setting.app.name!;
|
||||
}
|
||||
|
||||
constructor(private setting: SettingsService) {}
|
||||
}
|
||||
101
src/app/layout/pro/components/menu/menu.component.html
Normal file
101
src/app/layout/pro/components/menu/menu.component.html
Normal file
@ -0,0 +1,101 @@
|
||||
<ng-template #icon let-i>
|
||||
<ng-container *ngIf="i" [ngSwitch]="i.type">
|
||||
<i *ngSwitchCase="'icon'" nz-icon [nzType]="i.value" class="alain-pro__menu-icon"></i>
|
||||
<i *ngSwitchCase="'iconfont'" nz-icon [nzIconfont]="i.iconfont" class="alain-pro__menu-icon"></i>
|
||||
<img *ngSwitchCase="'img'" src="{{ i.value }}" class="anticon alain-pro__menu-icon alain-pro__menu-img" />
|
||||
<i *ngSwitchDefault class="anticon alain-pro__menu-icon {{ i.value }}"></i>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template #mainLink let-i>
|
||||
<ng-template [ngTemplateOutlet]="icon" [ngTemplateOutletContext]="{ $implicit: i.icon }"></ng-template>
|
||||
<span class="alain-pro__menu-title-text" *ngIf="!pro.onlyIcon">{{ i.text }}</span>
|
||||
<div *ngIf="i.badge" class="alain-pro__menu-title-badge">
|
||||
<em>{{ i.badge }}</em>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #subLink let-i>
|
||||
<a *ngIf="!i.externalLink" [routerLink]="i.link" [target]="i.target">{{ i.text }} </a>
|
||||
<a *ngIf="i.externalLink" [attr.href]="i.externalLink" [attr.target]="i.target">{{ i.text }} </a>
|
||||
</ng-template>
|
||||
<ul *ngIf="menus" nz-menu [nzMode]="mode" [nzTheme]="pro.theme" [nzInlineCollapsed]="pro.isMobile ? false : pro.collapsed">
|
||||
<ng-container *ngFor="let l1 of menus">
|
||||
<li
|
||||
*ngIf="l1.children!.length === 0"
|
||||
nz-menu-item
|
||||
class="alain-pro__menu-item"
|
||||
[class.alain-pro__menu-item--disabled]="l1.disabled"
|
||||
[nzSelected]="l1._selected"
|
||||
[nzDisabled]="l1.disabled"
|
||||
>
|
||||
<a *ngIf="!l1.externalLink" [routerLink]="l1.link" (click)="closeCollapsed()" class="alain-pro__menu-title">
|
||||
<ng-template [ngTemplateOutlet]="mainLink" [ngTemplateOutletContext]="{ $implicit: l1 }"></ng-template>
|
||||
</a>
|
||||
<a
|
||||
*ngIf="l1.externalLink"
|
||||
[attr.href]="l1.externalLink"
|
||||
[attr.target]="l1.target"
|
||||
(click)="closeCollapsed()"
|
||||
class="alain-pro__menu-title"
|
||||
>
|
||||
<ng-template [ngTemplateOutlet]="mainLink" [ngTemplateOutletContext]="{ $implicit: l1 }"></ng-template>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
*ngIf="l1.children!.length > 0"
|
||||
nz-submenu
|
||||
[nzTitle]="l1TitleTpl"
|
||||
class="alain-pro__menu-item"
|
||||
[class.text-white]="pro.theme === 'dark' && l1._selected"
|
||||
[nzOpen]="l1._open"
|
||||
[nzDisabled]="l1.disabled"
|
||||
(nzOpenChange)="openChange(l1, $event)"
|
||||
>
|
||||
<ng-template #l1TitleTpl>
|
||||
<span title class="alain-pro__menu-title">
|
||||
<ng-template [ngTemplateOutlet]="icon" [ngTemplateOutletContext]="{ $implicit: l1.icon }"></ng-template>
|
||||
<span class="alain-pro__menu-title-text" *ngIf="pro.isMobile || !pro.onlyIcon">{{ l1.text }}</span>
|
||||
<div *ngIf="l1.badge" class="alain-pro__menu-title-badge">
|
||||
<em>{{ l1.badge }}</em>
|
||||
</div>
|
||||
</span>
|
||||
</ng-template>
|
||||
<ul>
|
||||
<ng-container *ngFor="let l2 of l1.children">
|
||||
<li
|
||||
*ngIf="!l2._hidden && l2.children!.length === 0"
|
||||
nz-menu-item
|
||||
[class.alain-pro__menu-item--disabled]="l2.disabled"
|
||||
[nzSelected]="l2._selected"
|
||||
[nzDisabled]="l2.disabled"
|
||||
(click)="closeCollapsed()"
|
||||
>
|
||||
<ng-template [ngTemplateOutlet]="subLink" [ngTemplateOutletContext]="{ $implicit: l2 }"></ng-template>
|
||||
</li>
|
||||
<li
|
||||
*ngIf="!l2._hidden && l2.children!.length > 0"
|
||||
nz-submenu
|
||||
[nzTitle]="l2.text!"
|
||||
[nzOpen]="l2._open"
|
||||
[nzDisabled]="l2.disabled"
|
||||
(nzOpenChange)="openChange(l2, $event)"
|
||||
>
|
||||
<ul>
|
||||
<ng-container *ngFor="let l3 of l2.children">
|
||||
<li
|
||||
*ngIf="!l3._hidden"
|
||||
nz-menu-item
|
||||
[class.alain-pro__menu-item--disabled]="l3.disabled"
|
||||
[nzSelected]="l3._selected"
|
||||
[nzDisabled]="l3.disabled"
|
||||
(click)="closeCollapsed()"
|
||||
>
|
||||
<ng-template [ngTemplateOutlet]="subLink" [ngTemplateOutletContext]="{ $implicit: l3 }"></ng-template>
|
||||
</li>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</li>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</li>
|
||||
</ng-container>
|
||||
</ul>
|
||||
125
src/app/layout/pro/components/menu/menu.component.ts
Normal file
125
src/app/layout/pro/components/menu/menu.component.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { MenuService } from '@delon/theme';
|
||||
import { InputBoolean } from '@delon/util';
|
||||
import { NzMenuModeType } from 'ng-zorro-antd/menu';
|
||||
import { Subject } from 'rxjs';
|
||||
import { filter, takeUntil } from 'rxjs/operators';
|
||||
|
||||
import { BrandService } from '../../pro.service';
|
||||
import { ProMenu } from '../../pro.types';
|
||||
|
||||
@Component({
|
||||
selector: '[layout-pro-menu]',
|
||||
templateUrl: './menu.component.html',
|
||||
host: {
|
||||
'[class.alain-pro__menu]': 'true',
|
||||
'[class.alain-pro__menu-only-icon]': 'pro.onlyIcon'
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LayoutProMenuComponent implements OnInit, OnDestroy {
|
||||
private unsubscribe$ = new Subject<void>();
|
||||
menus?: ProMenu[];
|
||||
|
||||
@Input() @InputBoolean() disabledAcl = false;
|
||||
@Input() mode: NzMenuModeType = 'inline';
|
||||
|
||||
constructor(private menuSrv: MenuService, private router: Router, public pro: BrandService, private cdr: ChangeDetectorRef) {}
|
||||
|
||||
private cd(): void {
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
|
||||
private genMenus(data: ProMenu[]): void {
|
||||
const res: ProMenu[] = [];
|
||||
// ingores category menus
|
||||
const ingoreCategores = data.reduce((prev, cur) => prev.concat(cur.children as ProMenu[]), [] as ProMenu[]);
|
||||
this.menuSrv.visit(ingoreCategores, (item: ProMenu, parent: ProMenu | null) => {
|
||||
if (!item._aclResult) {
|
||||
if (this.disabledAcl) {
|
||||
item.disabled = true;
|
||||
} else {
|
||||
item._hidden = true;
|
||||
}
|
||||
}
|
||||
if (item._hidden === true) {
|
||||
return;
|
||||
}
|
||||
if (parent === null) {
|
||||
res.push(item);
|
||||
}
|
||||
});
|
||||
this.menus = res;
|
||||
|
||||
this.openStatus();
|
||||
}
|
||||
|
||||
private openStatus(): void {
|
||||
const inFn = (list: ProMenu[]) => {
|
||||
for (const i of list) {
|
||||
i._open = false;
|
||||
i._selected = false;
|
||||
if (i.children!.length > 0) {
|
||||
inFn(i.children!);
|
||||
}
|
||||
}
|
||||
};
|
||||
inFn(this.menus!);
|
||||
|
||||
let item = this.menuSrv.getHit(this.menus!, this.router.url, true);
|
||||
if (!item) {
|
||||
this.cd();
|
||||
return;
|
||||
}
|
||||
do {
|
||||
item._selected = true;
|
||||
if (!this.pro.isTopMenu && !this.pro.collapsed) {
|
||||
item._open = true;
|
||||
}
|
||||
item = item._parent!;
|
||||
} while (item);
|
||||
this.cd();
|
||||
}
|
||||
|
||||
openChange(item: ProMenu, statue: boolean): void {
|
||||
const data = item._parent ? item._parent.children : this.menus;
|
||||
if (data && data.length <= 1) {
|
||||
return;
|
||||
}
|
||||
data!.forEach(i => (i._open = false));
|
||||
item._open = statue;
|
||||
}
|
||||
|
||||
closeCollapsed(): void {
|
||||
const { pro } = this;
|
||||
if (pro.isMobile) {
|
||||
setTimeout(() => pro.setCollapsed(true), 25);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
const { unsubscribe$, router, pro } = this;
|
||||
this.menuSrv.change.pipe(takeUntil(unsubscribe$)).subscribe(res => this.genMenus(res));
|
||||
|
||||
router.events
|
||||
.pipe(
|
||||
takeUntil(unsubscribe$),
|
||||
filter(e => e instanceof NavigationEnd)
|
||||
)
|
||||
.subscribe(() => this.openStatus());
|
||||
|
||||
pro.notify
|
||||
.pipe(
|
||||
takeUntil(unsubscribe$),
|
||||
filter(() => !!this.menus)
|
||||
)
|
||||
.subscribe(() => this.cd());
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
const { unsubscribe$ } = this;
|
||||
unsubscribe$.next();
|
||||
unsubscribe$.complete();
|
||||
}
|
||||
}
|
||||
10
src/app/layout/pro/components/notify/notify.component.html
Normal file
10
src/app/layout/pro/components/notify/notify.component.html
Normal file
@ -0,0 +1,10 @@
|
||||
<notice-icon
|
||||
btnClass="alain-pro__header-item"
|
||||
btnIconClass="alain-pro__header-item-icon"
|
||||
[data]="data"
|
||||
[count]="count"
|
||||
[loading]="loading"
|
||||
(select)="select($event)"
|
||||
(clear)="clear($event)"
|
||||
(popoverVisibleChange)="loadData()"
|
||||
></notice-icon>
|
||||
183
src/app/layout/pro/components/notify/notify.component.ts
Normal file
183
src/app/layout/pro/components/notify/notify.component.ts
Normal file
@ -0,0 +1,183 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
|
||||
import { NoticeIconList, NoticeItem } from '@delon/abc/notice-icon';
|
||||
import formatDistanceToNow from 'date-fns/formatDistanceToNow';
|
||||
import parse from 'date-fns/parse';
|
||||
import { NzSafeAny } from 'ng-zorro-antd/core/types';
|
||||
import { NzMessageService } from 'ng-zorro-antd/message';
|
||||
|
||||
@Component({
|
||||
selector: 'layout-pro-notify',
|
||||
templateUrl: './notify.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LayoutProWidgetNotifyComponent {
|
||||
data: NoticeItem[] = [
|
||||
{
|
||||
title: '通知',
|
||||
list: [],
|
||||
emptyText: '你已查看所有通知',
|
||||
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
|
||||
clearText: '清空通知'
|
||||
},
|
||||
{
|
||||
title: '消息',
|
||||
list: [],
|
||||
emptyText: '您已读完所有消息',
|
||||
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg',
|
||||
clearText: '清空消息'
|
||||
},
|
||||
{
|
||||
title: '待办',
|
||||
list: [],
|
||||
emptyText: '你已完成所有待办',
|
||||
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/HsIsxMZiWKrNUavQUXqx.svg',
|
||||
clearText: '清空待办'
|
||||
}
|
||||
];
|
||||
count = 5;
|
||||
loading = false;
|
||||
|
||||
constructor(private msg: NzMessageService, private cdr: ChangeDetectorRef) {}
|
||||
|
||||
updateNoticeData(notices: NoticeIconList[]): NoticeItem[] {
|
||||
const data = this.data.slice();
|
||||
data.forEach(i => (i.list = []));
|
||||
|
||||
notices.forEach(item => {
|
||||
const newItem = { ...item };
|
||||
if (typeof newItem.datetime === 'string') {
|
||||
newItem.datetime = parse(newItem.datetime, 'yyyy-MM-dd', new Date());
|
||||
}
|
||||
if (newItem.datetime) {
|
||||
newItem.datetime = formatDistanceToNow(newItem.datetime as Date);
|
||||
}
|
||||
if (newItem.extra && newItem.status) {
|
||||
newItem.color = (
|
||||
{
|
||||
todo: undefined,
|
||||
processing: 'blue',
|
||||
urgent: 'red',
|
||||
doing: 'gold'
|
||||
} as NzSafeAny
|
||||
)[newItem.status];
|
||||
}
|
||||
data.find(w => w.title === newItem.type)?.list.push(newItem);
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
loadData(): void {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
setTimeout(() => {
|
||||
this.data = this.updateNoticeData([
|
||||
{
|
||||
id: '000000001',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
|
||||
title: '你收到了 14 份新周报',
|
||||
datetime: '2017-08-09',
|
||||
type: '通知'
|
||||
},
|
||||
{
|
||||
id: '000000002',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
|
||||
title: '你推荐的 曲妮妮 已通过第三轮面试',
|
||||
datetime: '2017-08-08',
|
||||
type: '通知'
|
||||
},
|
||||
{
|
||||
id: '000000003',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
|
||||
title: '这种模板可以区分多种通知类型',
|
||||
datetime: '2017-08-07',
|
||||
read: true,
|
||||
type: '通知'
|
||||
},
|
||||
{
|
||||
id: '000000004',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
|
||||
title: '左侧图标用于区分不同的类型',
|
||||
datetime: '2017-08-07',
|
||||
type: '通知'
|
||||
},
|
||||
{
|
||||
id: '000000005',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
|
||||
title: '内容不要超过两行字,超出时自动截断',
|
||||
datetime: '2017-08-07',
|
||||
type: '通知'
|
||||
},
|
||||
{
|
||||
id: '000000006',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '曲丽丽 评论了你',
|
||||
description: '描述信息描述信息描述信息',
|
||||
datetime: '2017-08-07',
|
||||
type: '消息'
|
||||
},
|
||||
{
|
||||
id: '000000007',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '朱偏右 回复了你',
|
||||
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
|
||||
datetime: '2017-08-07',
|
||||
type: '消息'
|
||||
},
|
||||
{
|
||||
id: '000000008',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '标题',
|
||||
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
|
||||
datetime: '2017-08-07',
|
||||
type: '消息'
|
||||
},
|
||||
{
|
||||
id: '000000009',
|
||||
title: '任务名称',
|
||||
description: '任务需要在 2017-01-12 20:00 前启动',
|
||||
extra: '未开始',
|
||||
status: 'todo',
|
||||
type: '待办'
|
||||
},
|
||||
{
|
||||
id: '000000010',
|
||||
title: '第三方紧急代码变更',
|
||||
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
|
||||
extra: '马上到期',
|
||||
status: 'urgent',
|
||||
type: '待办'
|
||||
},
|
||||
{
|
||||
id: '000000011',
|
||||
title: '信息安全考试',
|
||||
description: '指派竹尔于 2017-01-09 前完成更新并发布',
|
||||
extra: '已耗时 8 天',
|
||||
status: 'doing',
|
||||
type: '待办'
|
||||
},
|
||||
{
|
||||
id: '000000012',
|
||||
title: 'ABCD 版本发布',
|
||||
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
|
||||
extra: '进行中',
|
||||
status: 'processing',
|
||||
type: '待办'
|
||||
}
|
||||
]);
|
||||
|
||||
this.loading = false;
|
||||
|
||||
this.cdr.detectChanges();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
clear(type: string): void {
|
||||
this.msg.success(`清空了 ${type}`);
|
||||
}
|
||||
|
||||
select(res: any): void {
|
||||
this.msg.success(`点击了 ${res.title} 的 ${res.item.title}`);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
<div class="alain-pro__header-item position-relative" (click)="show()">
|
||||
<nz-badge class="brand-top-right" style="right: 3px; line-height: 34px" nzShowDot [nzStatus]="status"></nz-badge>
|
||||
<i nz-tooltip="Public Chat" nz-icon nzType="message" class="alain-pro__header-item-icon"></i>
|
||||
</div>
|
||||
@ -0,0 +1,42 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { LayoutProWidgetQuickChatService } from './quick-chat.service';
|
||||
|
||||
@Component({
|
||||
selector: 'quick-chat-status',
|
||||
templateUrl: './quick-chat-status.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LayoutProWidgetQuickChatStatusComponent implements OnInit, OnDestroy {
|
||||
private status$!: Subscription;
|
||||
|
||||
status = 'default';
|
||||
|
||||
constructor(private srv: LayoutProWidgetQuickChatService, private cdr: ChangeDetectorRef) {}
|
||||
|
||||
show(): void {
|
||||
if (this.srv.showDialog) {
|
||||
return;
|
||||
}
|
||||
this.srv.showDialog = true;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.status$ = this.srv.status.subscribe(res => {
|
||||
switch (res) {
|
||||
case 'online':
|
||||
this.status = 'success';
|
||||
break;
|
||||
default:
|
||||
this.status = 'default';
|
||||
break;
|
||||
}
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.status$.unsubscribe();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
<div class="quick-chat__bar">
|
||||
<strong class="quick-chat__bar--title" (click)="toggleCollapsed()">
|
||||
<div [ngClass]="{ 'quick-chat__bar--title-has-message': collapsed && hasMessage }">
|
||||
{{ !collapsed && inited ? 'Connecting...' : 'Ng Alain Pro' }}
|
||||
</div>
|
||||
</strong>
|
||||
<i nz-dropdown [nzDropdownMenu]="quickMenu" nz-icon nzType="ellipsis" class="quick-chat__bar--menu rotate-90"></i>
|
||||
<nz-dropdown-menu #quickMenu="nzDropdownMenu">
|
||||
<ul nz-menu nzSelectable>
|
||||
<li nz-menu-item>Add</li>
|
||||
<li nz-menu-item>Edit</li>
|
||||
<li nz-menu-item>Remove</li>
|
||||
</ul>
|
||||
</nz-dropdown-menu>
|
||||
<i nz-icon nzType="close" class="quick-chat__bar--close" (click)="close()"></i>
|
||||
</div>
|
||||
<div class="quick-chat__body" [ngClass]="{ 'quick-chat__collapsed': collapsed }">
|
||||
<div class="quick-chat__content">
|
||||
<div class="chat__scroll-container chat__message-container" scrollbar #messageScrollbar="scrollbarComp">
|
||||
<div *ngFor="let m of messages" class="chat__message chat__message-{{ m.dir }}">
|
||||
<ng-container [ngSwitch]="m.type">
|
||||
<div *ngSwitchCase="'only-text'" class="chat__message-text" [innerHTML]="m.msg"></div>
|
||||
<ng-container *ngSwitchDefault>
|
||||
<div class="chat__message-avatar" *ngIf="m.dir === 'left'">
|
||||
<img class="chat__user-avatar" src="{{ m.mp }}" />
|
||||
</div>
|
||||
<div class="chat__message-msg">
|
||||
<strong class="chat__message-msg--name" *ngIf="m.name">{{ m.name }}</strong>
|
||||
<div class="chat__message-msg--text" *ngIf="m.type === 'text'" [innerHTML]="m.msg"></div>
|
||||
<div class="chat__message-msg--image" *ngIf="m.type === 'image'">
|
||||
<img height="40" src="{{ m.msg }}" />
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="quick-chat__reply">
|
||||
<textarea
|
||||
class="quick-chat__reply--ipt scrollbar"
|
||||
[(ngModel)]="text"
|
||||
(keydown.enter)="enterSend($event)"
|
||||
placeholder="Type your message..."
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
131
src/app/layout/pro/components/quick-chat/quick-chat.component.ts
Normal file
131
src/app/layout/pro/components/quick-chat/quick-chat.component.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
HostBinding,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { BooleanInput, InputBoolean, InputNumber, NumberInput } from '@delon/util';
|
||||
import { ScrollbarDirective } from '@shared';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
import { LayoutProWidgetQuickChatService } from './quick-chat.service';
|
||||
|
||||
@Component({
|
||||
selector: 'quick-chat',
|
||||
templateUrl: './quick-chat.component.html',
|
||||
host: {
|
||||
'[class.quick-chat]': 'true',
|
||||
'[class.quick-chat__collapsed]': 'collapsed',
|
||||
'[class.d-none]': '!showDialog'
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LayoutProWidgetQuickChatComponent implements OnInit, OnDestroy {
|
||||
static ngAcceptInputType_height: NumberInput;
|
||||
static ngAcceptInputType_width: BooleanInput;
|
||||
static ngAcceptInputType_collapsed: BooleanInput;
|
||||
|
||||
private unsubscribe$ = new Subject<void>();
|
||||
messages: any[] = [
|
||||
{ type: 'only-text', msg: '2018-12-12' },
|
||||
{
|
||||
type: 'text',
|
||||
dir: 'left',
|
||||
mp: './assets/logo-color.svg',
|
||||
msg: '请<span class="text-success">一句话</span>描述您的问题,我们来帮您解决并转到合适的人工服务。😎'
|
||||
}
|
||||
];
|
||||
text = '';
|
||||
inited?: boolean;
|
||||
hasMessage = false;
|
||||
|
||||
@ViewChild('messageScrollbar', { static: true }) messageScrollbar?: ScrollbarDirective;
|
||||
|
||||
// #region fileds
|
||||
@Input() @InputNumber() height = 380;
|
||||
@Input() @InputNumber() @HostBinding('style.width.px') width = 320;
|
||||
@Input() @InputBoolean() collapsed = true;
|
||||
@Output() readonly collapsedChange = new EventEmitter<boolean>();
|
||||
@Output() readonly closed = new EventEmitter<boolean>();
|
||||
// #endregion
|
||||
|
||||
constructor(private srv: LayoutProWidgetQuickChatService, private cdr: ChangeDetectorRef) {}
|
||||
|
||||
get showDialog(): boolean {
|
||||
return this.srv.showDialog;
|
||||
}
|
||||
|
||||
private scrollToBottom(): void {
|
||||
this.cdr.detectChanges();
|
||||
setTimeout(() => this.messageScrollbar!.scrollToBottom());
|
||||
}
|
||||
|
||||
toggleCollapsed(): void {
|
||||
this.hasMessage = false;
|
||||
this.collapsed = !this.collapsed;
|
||||
this.collapsedChange.emit(this.collapsed);
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.srv.close();
|
||||
this.closed.emit(true);
|
||||
}
|
||||
|
||||
enterSend(e: Event): void {
|
||||
if ((e as KeyboardEvent).keyCode !== 13) {
|
||||
return;
|
||||
}
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
this.send();
|
||||
}
|
||||
|
||||
send(): boolean {
|
||||
if (!this.text) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof this.inited === 'undefined') {
|
||||
this.inited = true;
|
||||
}
|
||||
const item = {
|
||||
type: 'text',
|
||||
msg: this.text,
|
||||
dir: 'right'
|
||||
};
|
||||
this.srv.send(item);
|
||||
this.messages.push(item);
|
||||
this.text = '';
|
||||
this.scrollToBottom();
|
||||
return false;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
const { srv, messages, unsubscribe$ } = this;
|
||||
srv.message.pipe(takeUntil(unsubscribe$)).subscribe(res => {
|
||||
if (this.collapsed) {
|
||||
this.hasMessage = true;
|
||||
}
|
||||
messages.push(res);
|
||||
this.scrollToBottom();
|
||||
});
|
||||
srv.status.pipe(takeUntil(unsubscribe$)).subscribe(res => {
|
||||
this.inited = res === 'online' ? false : undefined;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
const { unsubscribe$ } = this;
|
||||
unsubscribe$.next();
|
||||
unsubscribe$.complete();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { Observable, Subject, Subscription } from 'rxjs';
|
||||
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
|
||||
|
||||
export type QuickChatStatus = 'online' | 'offline';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class LayoutProWidgetQuickChatService implements OnDestroy {
|
||||
private url = 'wss://echo.websocket.org/?encoding=text';
|
||||
private _ws!: WebSocketSubject<{}>;
|
||||
private $statusOrg = new Subject();
|
||||
private messageOrg$: Subscription | null = null;
|
||||
private $status = new Subject<QuickChatStatus>();
|
||||
private $message = new Subject<{}>();
|
||||
showDialog = true;
|
||||
|
||||
constructor() {
|
||||
this.$statusOrg.subscribe((res: any) => {
|
||||
this.$status.next(res.type === 'open' ? 'online' : 'offline');
|
||||
});
|
||||
}
|
||||
|
||||
get ws(): WebSocketSubject<{}> {
|
||||
return this._ws!;
|
||||
}
|
||||
|
||||
get message(): Observable<{}> {
|
||||
return this.$message.asObservable();
|
||||
}
|
||||
|
||||
get status(): Observable<QuickChatStatus> {
|
||||
return this.$status.asObservable();
|
||||
}
|
||||
|
||||
open(): WebSocketSubject<{}> {
|
||||
if (this._ws) {
|
||||
return this._ws;
|
||||
}
|
||||
|
||||
this._ws = webSocket({
|
||||
url: this.url,
|
||||
serializer: (value: any) => JSON.stringify(value),
|
||||
deserializer: (e: MessageEvent) => {
|
||||
const res = JSON.parse(e.data);
|
||||
res.dir = 'left';
|
||||
res.mp = './assets/logo-color.svg';
|
||||
return res;
|
||||
},
|
||||
openObserver: this.$statusOrg,
|
||||
closeObserver: this.$statusOrg
|
||||
});
|
||||
return this._ws;
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.showDialog = false;
|
||||
if (this.messageOrg$) {
|
||||
this.messageOrg$.unsubscribe();
|
||||
this.messageOrg$ = null;
|
||||
}
|
||||
}
|
||||
|
||||
send(msg: {}): void {
|
||||
if (!this._ws) {
|
||||
this.open();
|
||||
}
|
||||
if (!this.messageOrg$) {
|
||||
this.messageOrg$ = this._ws.subscribe(res => this.$message.next(res));
|
||||
}
|
||||
this._ws.next(msg);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
const { $statusOrg, $status, $message } = this;
|
||||
this.close();
|
||||
$statusOrg.complete();
|
||||
$status.complete();
|
||||
$message.complete();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
<div class="p-md border-bottom-1">
|
||||
<button (click)="changeType(0)" nz-button [nzType]="type === 0 ? 'primary' : 'default'">Notifications</button>
|
||||
<button (click)="changeType(1)" nz-button [nzType]="type === 1 ? 'primary' : 'default'">Actions</button>
|
||||
<button (click)="changeType(2)" nz-button [nzType]="type === 2 ? 'primary' : 'default'">Settings</button>
|
||||
</div>
|
||||
<nz-spin [nzSpinning]="!data">
|
||||
<div *ngIf="!data" class="brand-page-loading"></div>
|
||||
<div *ngIf="data" class="p-lg min-width-lg">
|
||||
<nz-timeline *ngIf="type === 0" class="d-block pl-md pt-md">
|
||||
<nz-timeline-item *ngFor="let i of data.notifications" [nzDot]="dotTpl">
|
||||
<ng-template #dotTpl>
|
||||
<div class="md-sm p-sm icon-sm rounded-circle text-white bg-{{ i.dot.bg }}">
|
||||
<i nz-icon [nzType]="i.dot.icon"></i>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="pl-lg">
|
||||
<strong>{{ i.time }}</strong>
|
||||
<div class="py-sm" [innerHTML]="i.content | html"></div>
|
||||
<div class="text-grey">{{ i.tags }}</div>
|
||||
</div>
|
||||
</nz-timeline-item>
|
||||
</nz-timeline>
|
||||
<ng-container *ngIf="type === 1">
|
||||
<div
|
||||
*ngFor="let i of data.actions; let last = last"
|
||||
class="rounded-md text-white position-relative bg-{{ i.bg }}"
|
||||
[ngClass]="{ 'mb-md': !last }"
|
||||
>
|
||||
<strong class="d-block p-md">{{ i.title }}</strong>
|
||||
<div class="px-md">{{ i.content }}</div>
|
||||
<div class="p-sm text-right">
|
||||
<button (click)="msg.success('Dismiss')" nz-button class="btn-flat text-white text-hover">Dismiss</button>
|
||||
<button (click)="msg.success('View')" nz-button class="btn-flat text-white text-hover">View</button>
|
||||
</div>
|
||||
<span nz-dropdown [nzDropdownMenu]="actionMenu" nzPlacement="bottomRight" class="dd-btn brand-top-right text-white">
|
||||
<i nz-icon nzType="ellipsis"></i>
|
||||
</span>
|
||||
<nz-dropdown-menu #actionMenu="nzDropdownMenu">
|
||||
<ul nz-menu>
|
||||
<li nz-menu-item (click)="msg.success('Item1')">Item1</li>
|
||||
<li nz-menu-item (click)="msg.success('Item2')">Item2</li>
|
||||
<li nz-menu-divider></li>
|
||||
<li nz-menu-item (click)="msg.success('Item3')">Item3</li>
|
||||
</ul>
|
||||
</nz-dropdown-menu>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="type === 2">
|
||||
<h3 class="setting-drawer__title">Notifications</h3>
|
||||
<div class="setting-drawer__body-item">
|
||||
Enable notifications:
|
||||
<nz-switch [(ngModel)]="data.settings.notification" (ngModelChange)="updateSetting('notification', $event)"></nz-switch>
|
||||
</div>
|
||||
<div class="setting-drawer__body-item">
|
||||
Enable audit log:
|
||||
<nz-switch [(ngModel)]="data.settings.audit_log" (ngModelChange)="updateSetting('audit_log', $event)"></nz-switch>
|
||||
</div>
|
||||
<div class="setting-drawer__body-item">
|
||||
Notify on new orders:
|
||||
<nz-switch [(ngModel)]="data.settings.new_order" (ngModelChange)="updateSetting('new_order', $event)"></nz-switch>
|
||||
</div>
|
||||
<h3 class="setting-drawer__title mt-md">Orders</h3>
|
||||
<div class="setting-drawer__body-item">
|
||||
Enable order tracking:
|
||||
<nz-switch [(ngModel)]="data.settings.tracking_order" (ngModelChange)="updateSetting('tracking_order', $event)"></nz-switch>
|
||||
</div>
|
||||
<div class="setting-drawer__body-item">
|
||||
Enable orders reports:
|
||||
<nz-switch [(ngModel)]="data.settings.reports_order" (ngModelChange)="updateSetting('reports_order', $event)"></nz-switch>
|
||||
</div>
|
||||
<h3 class="setting-drawer__title mt-md">Customers</h3>
|
||||
<div class="setting-drawer__body-item">
|
||||
Enable customer singup:
|
||||
<nz-switch [(ngModel)]="data.settings.new_customer" (ngModelChange)="updateSetting('new_customer', $event)"></nz-switch>
|
||||
</div>
|
||||
<div class="setting-drawer__body-item">
|
||||
Enable customers reporting:
|
||||
<nz-switch [(ngModel)]="data.settings.reporting_customer" (ngModelChange)="updateSetting('reporting_customer', $event)"></nz-switch>
|
||||
</div>
|
||||
<h3 class="setting-drawer__title mt-md">Other</h3>
|
||||
<div class="setting-drawer__body-item">
|
||||
Weak Mode:
|
||||
<nz-switch [(ngModel)]="layout.colorWeak" (ngModelChange)="setLayout('colorWeak', $event)"></nz-switch>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</nz-spin>
|
||||
42
src/app/layout/pro/components/quick/quick-panel.component.ts
Normal file
42
src/app/layout/pro/components/quick/quick-panel.component.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { _HttpClient } from '@delon/theme';
|
||||
import { NzMessageService } from 'ng-zorro-antd/message';
|
||||
|
||||
import { BrandService } from '../../pro.service';
|
||||
import { ProLayout } from '../../pro.types';
|
||||
|
||||
@Component({
|
||||
selector: 'layout-pro-quick-panel',
|
||||
templateUrl: './quick-panel.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LayoutProWidgetQuickPanelComponent implements OnInit {
|
||||
type = 0;
|
||||
data: any;
|
||||
get layout(): ProLayout {
|
||||
return this.pro.layout;
|
||||
}
|
||||
|
||||
constructor(private pro: BrandService, private http: _HttpClient, private cd: ChangeDetectorRef, public msg: NzMessageService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.http.get('/quick').subscribe(res => {
|
||||
this.data = res;
|
||||
this.changeType(0);
|
||||
});
|
||||
}
|
||||
|
||||
changeType(type: number): void {
|
||||
this.type = type;
|
||||
// wait checkbox & switch render
|
||||
setTimeout(() => this.cd.detectChanges());
|
||||
}
|
||||
|
||||
updateSetting(_type: string, _value: any): void {
|
||||
this.msg.success('Success!');
|
||||
}
|
||||
|
||||
setLayout(name: string, value: any): void {
|
||||
this.pro.setLayout(name, value);
|
||||
}
|
||||
}
|
||||
1
src/app/layout/pro/components/quick/quick.component.html
Normal file
1
src/app/layout/pro/components/quick/quick.component.html
Normal file
@ -0,0 +1 @@
|
||||
<i nz-tooltip="Quick panel" nz-icon nzType="appstore" class="alain-pro__header-item-icon"></i>
|
||||
51
src/app/layout/pro/components/quick/quick.component.ts
Normal file
51
src/app/layout/pro/components/quick/quick.component.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Direction, Directionality } from '@angular/cdk/bidi';
|
||||
import { ChangeDetectionStrategy, Component, HostListener, OnDestroy, OnInit, Optional } from '@angular/core';
|
||||
import { DrawerHelper } from '@delon/theme';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
import { LayoutProWidgetQuickPanelComponent } from './quick-panel.component';
|
||||
|
||||
@Component({
|
||||
selector: 'layout-pro-quick',
|
||||
templateUrl: './quick.component.html',
|
||||
host: {
|
||||
'[class.alain-pro__header-item]': 'true'
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LayoutProWidgetQuickComponent implements OnInit, OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
private dir: Direction = 'ltr';
|
||||
|
||||
constructor(private drawerHelper: DrawerHelper, @Optional() private directionality: Directionality) {}
|
||||
|
||||
@HostListener('click')
|
||||
show(): void {
|
||||
this.drawerHelper
|
||||
.create(``, LayoutProWidgetQuickPanelComponent, null, {
|
||||
size: 480,
|
||||
drawerOptions: {
|
||||
nzTitle: undefined,
|
||||
nzPlacement: this.dir === 'rtl' ? 'left' : 'right',
|
||||
nzBodyStyle: {
|
||||
'min-height': '100%',
|
||||
padding: 0
|
||||
}
|
||||
}
|
||||
})
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.dir = this.directionality.value;
|
||||
this.directionality.change?.pipe(takeUntil(this.destroy$)).subscribe((direction: Direction) => {
|
||||
this.dir = direction;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
23
src/app/layout/pro/components/rtl/rtl.component.ts
Normal file
23
src/app/layout/pro/components/rtl/rtl.component.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ChangeDetectionStrategy, Component, HostListener } from '@angular/core';
|
||||
import { RTLService } from '@delon/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'layout-pro-rtl',
|
||||
template: `
|
||||
<button nz-button nzType="link" class="alain-pro__header-item-icon">
|
||||
{{ rtl.nextDir | uppercase }}
|
||||
</button>
|
||||
`,
|
||||
host: {
|
||||
'[class.alain-pro__header-item]': 'true'
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LayoutProWidgetRTLComponent {
|
||||
constructor(public rtl: RTLService) {}
|
||||
|
||||
@HostListener('click')
|
||||
toggleDirection(): void {
|
||||
this.rtl.toggle();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
<i nz-icon nzType="search"></i>
|
||||
<div class="alain-pro__header-search-input ant-select-auto-complete ant-select">
|
||||
<input #ipt placeholder="站内搜索" nz-input [nzAutocomplete]="searchAuto" [(ngModel)]="q" (input)="onSearch()" (blur)="show = false" />
|
||||
</div>
|
||||
<nz-autocomplete #searchAuto>
|
||||
<nz-auto-option *ngFor="let item of list" [nzValue]="item.no">
|
||||
{{ item.no }}
|
||||
</nz-auto-option>
|
||||
</nz-autocomplete>
|
||||
49
src/app/layout/pro/components/search/search.component.ts
Normal file
49
src/app/layout/pro/components/search/search.component.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostListener, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { _HttpClient } from '@delon/theme';
|
||||
import { Subject } from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'layout-pro-search',
|
||||
templateUrl: 'search.component.html',
|
||||
host: {
|
||||
'[class.alain-pro__header-item]': 'true',
|
||||
'[class.alain-pro__header-search]': 'true',
|
||||
'[class.alain-pro__header-search-show]': 'show'
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LayoutProWidgetSearchComponent implements OnDestroy {
|
||||
@ViewChild('ipt', { static: true }) private ipt!: ElementRef<HTMLInputElement>;
|
||||
show = false;
|
||||
q = '';
|
||||
search$ = new Subject<string>();
|
||||
list: any[] = [];
|
||||
|
||||
constructor(http: _HttpClient, cdr: ChangeDetectorRef) {
|
||||
this.search$
|
||||
.pipe(
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
switchMap((q: string) => http.get('/user', { no: q, pi: 1, ps: 5 }))
|
||||
)
|
||||
.subscribe((res: any) => {
|
||||
this.list = res.list;
|
||||
cdr.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
onSearch(): void {
|
||||
this.search$.next(this.ipt.nativeElement.value);
|
||||
}
|
||||
|
||||
@HostListener('click')
|
||||
_click(): void {
|
||||
this.ipt.nativeElement.focus();
|
||||
this.show = true;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.search$.unsubscribe();
|
||||
}
|
||||
}
|
||||
25
src/app/layout/pro/components/user/user.component.html
Normal file
25
src/app/layout/pro/components/user/user.component.html
Normal file
@ -0,0 +1,25 @@
|
||||
<div nz-dropdown [nzDropdownMenu]="userMenu" nzPlacement="bottomRight" class="alain-pro__header-item">
|
||||
<nz-avatar [nzSrc]="settings.user.avatar" nzSize="small" class="mr-sm"></nz-avatar>
|
||||
{{ settings.user.name }}
|
||||
</div>
|
||||
<nz-dropdown-menu #userMenu="nzDropdownMenu">
|
||||
<div nz-menu class="width-sm">
|
||||
<div nz-menu-item routerLink="/pro/account/center">
|
||||
<i nz-icon nzType="user" class="mr-sm"></i>
|
||||
个人中心
|
||||
</div>
|
||||
<div nz-menu-item routerLink="/pro/account/settings">
|
||||
<i nz-icon nzType="setting" class="mr-sm"></i>
|
||||
个人设置
|
||||
</div>
|
||||
<div nz-menu-item routerLink="/exception/trigger">
|
||||
<i nz-icon nzType="close-circle" class="mr-sm"></i>
|
||||
触发错误
|
||||
</div>
|
||||
<li nz-menu-divider></li>
|
||||
<div nz-menu-item (click)="logout()">
|
||||
<i nz-icon nzType="logout" class="mr-sm"></i>
|
||||
退出登录
|
||||
</div>
|
||||
</div>
|
||||
</nz-dropdown-menu>
|
||||
29
src/app/layout/pro/components/user/user.component.ts
Normal file
29
src/app/layout/pro/components/user/user.component.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
|
||||
import { SettingsService } from '@delon/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'layout-pro-user',
|
||||
templateUrl: 'user.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LayoutProWidgetUserComponent implements OnInit {
|
||||
constructor(public settings: SettingsService, private router: Router, @Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
// mock
|
||||
const token = this.tokenService.get() || {
|
||||
token: 'nothing',
|
||||
name: 'Admin',
|
||||
avatar: './assets/logo-color.svg',
|
||||
email: 'cipchk@qq.com'
|
||||
};
|
||||
this.tokenService.set(token);
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.tokenService.clear();
|
||||
this.router.navigateByUrl(this.tokenService.login_url!);
|
||||
}
|
||||
}
|
||||
19
src/app/layout/pro/components/widget/widget.component.html
Normal file
19
src/app/layout/pro/components/widget/widget.component.html
Normal file
@ -0,0 +1,19 @@
|
||||
<!--Search-->
|
||||
<layout-pro-search class="hidden-xs"></layout-pro-search>
|
||||
<!--Link-->
|
||||
<!-- <a nz-tooltip nzTooltipTitle="使用文档" nzTooltipPlacement="bottom" class="hidden-xs" target="_blank"
|
||||
href="https://e.ng-alain.com/theme/pro" rel="noopener noreferrer" class="alain-pro__header-item">
|
||||
<i nz-icon nzType="question-circle"></i>
|
||||
</a> -->
|
||||
<!--Quick chat status-->
|
||||
<!-- <quick-chat-status class="hidden-xs"></quick-chat-status> -->
|
||||
<!--Notify-->
|
||||
<layout-pro-notify class="hidden-xs"></layout-pro-notify>
|
||||
<!--RTL-->
|
||||
<!-- <layout-pro-rtl></layout-pro-rtl> -->
|
||||
<!--User-->
|
||||
<layout-pro-user></layout-pro-user>
|
||||
<!--Languages-->
|
||||
<!-- <pro-langs></pro-langs> -->
|
||||
<!--Quick panel-->
|
||||
<!-- <layout-pro-quick class="hidden-xs"></layout-pro-quick> -->
|
||||
20
src/app/layout/pro/components/widget/widget.component.ts
Normal file
20
src/app/layout/pro/components/widget/widget.component.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
|
||||
|
||||
@Component({
|
||||
selector: '[layout-pro-header-widget]',
|
||||
templateUrl: './widget.component.html',
|
||||
host: {
|
||||
'[class.alain-pro__header-right]': 'true'
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LayoutProHeaderWidgetComponent {
|
||||
constructor(private router: Router, @Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService) {}
|
||||
|
||||
logout(): void {
|
||||
this.tokenService.clear();
|
||||
this.router.navigateByUrl(this.tokenService.login_url!);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user