2 files deleted
36 files added
10 files modified
| | |
| | | "@angular/platform-browser": "^5.0.0", |
| | | "@angular/platform-browser-dynamic": "^5.0.0", |
| | | "@angular/router": "^5.0.0", |
| | | "core-js": "^2.4.1", |
| | | "rxjs": "^5.5.2", |
| | | "zone.js": "^0.8.14" |
| | | "@antv/data-set": "^0.7.0", |
| | | "@antv/g2": "^3.0.1", |
| | | "@antv/g2-plugin-slider": "^2.0.0", |
| | | "@delon/abc": "^0.3.0-rc.1", |
| | | "@delon/acl": "^0.3.0-rc.1", |
| | | "@delon/theme": "^0.3.0-rc.1", |
| | | "@delon/auth": "^0.3.0-rc.1", |
| | | "@ngx-translate/core": "^9.0.0", |
| | | "@ngx-translate/http-loader": "^2.0.0", |
| | | "angular-baidu-maps": "^1.0.1", |
| | | "angular-qq-maps": "^1.0.1", |
| | | "angular-tree-component": "^6.1.0", |
| | | "core-js": "^2.5.1", |
| | | "file-saver": "^1.3.3", |
| | | "font-awesome": "^4.7.0", |
| | | "moment": "^2.19.3", |
| | | "ng-tree-antd": "^2.0.0", |
| | | "ng-zorro-antd": "^0.6.5", |
| | | "ng-zorro-antd-extra": "^1.1.3", |
| | | "ng2-dnd": "^5.0.0", |
| | | "ng2-file-upload": "^1.2.1", |
| | | "ng2-img-cropper": "^0.9.0", |
| | | "ngx-color-picker": "^5.0.0", |
| | | "ngx-countdown": "^2.0.0", |
| | | "rxjs": "^5.5.5", |
| | | "screenfull": "^3.3.1", |
| | | "simple-line-icons": "^2.4.1", |
| | | "sweetalert2": "^7.0.0", |
| | | "weather-icons": "^1.3.2", |
| | | "zone.js": "^0.8.18" |
| | | }, |
| | | "devDependencies": { |
| | | "@angular/cli": "1.6.0", |
| | | "@angular/cli": "^1.5.2", |
| | | "@angular/compiler-cli": "^5.0.0", |
| | | "@angular/language-service": "^5.0.0", |
| | | "@types/jasmine": "~2.5.53", |
| | | "@types/jasminewd2": "~2.0.2", |
| | | "@angularclass/hmr": "^2.1.3", |
| | | "@angularclass/hmr-loader": "^3.0.4", |
| | | "@types/jasmine": "~2.6.0", |
| | | "@types/jasminewd2": "~2.0.3", |
| | | "@types/node": "~6.0.60", |
| | | "codelyzer": "^4.0.1", |
| | | "jasmine-core": "~2.6.2", |
| | | "jasmine-spec-reporter": "~4.1.0", |
| | | "karma": "~1.7.0", |
| | | "karma-chrome-launcher": "~2.1.1", |
| | | "codecov": "^3.0.0", |
| | | "codelyzer": "~4.0.1", |
| | | "jasmine-core": "~2.8.0", |
| | | "jasmine-spec-reporter": "~4.2.1", |
| | | "karma": "~1.7.1", |
| | | "karma-chrome-launcher": "~2.2.0", |
| | | "karma-cli": "~1.0.1", |
| | | "karma-coverage-istanbul-reporter": "^1.2.1", |
| | | "karma-coverage-istanbul-reporter": "^1.3.0", |
| | | "karma-jasmine": "~1.1.0", |
| | | "karma-jasmine-html-reporter": "^0.2.2", |
| | | "karma-remap-istanbul": "^0.6.0", |
| | | "karma-sauce-launcher": "^1.2.0", |
| | | "lint-staged": "^5.0.0", |
| | | "npm-run-all": "^4.1.1", |
| | | "protractor": "~5.1.2", |
| | | "stylelint": "^8.2.0", |
| | | "stylelint-config-standard": "^17.0.0", |
| | | "ts-node": "~3.2.0", |
| | | "tslint": "~5.7.0", |
| | | "typescript": "~2.4.2" |
| | | "typescript": "~2.5.0", |
| | | "webpack-bundle-analyzer": "^2.9.0" |
| | | }, |
| | | "lint-staged": { |
| | | "src/**/*.ts": "lint:ts", |
| | | "src/**/*.less": "lint:style" |
| | | } |
| | | } |
| | |
| | | import { TestBed, async } from '@angular/core/testing'; |
| | | import { TestBed, TestModuleMetadata } from '@angular/core/testing'; |
| | | import { setUpTestBed } from '../testing/common.spec'; |
| | | |
| | | import { AppComponent } from './app.component'; |
| | | describe('AppComponent', () => { |
| | | beforeEach(async(() => { |
| | | TestBed.configureTestingModule({ |
| | | declarations: [ |
| | | AppComponent |
| | | ], |
| | | }).compileComponents(); |
| | | })); |
| | | it('should create the app', async(() => { |
| | | const fixture = TestBed.createComponent(AppComponent); |
| | | const app = fixture.debugElement.componentInstance; |
| | | expect(app).toBeTruthy(); |
| | | })); |
| | | it(`should have as title 'app'`, async(() => { |
| | | const fixture = TestBed.createComponent(AppComponent); |
| | | const app = fixture.debugElement.componentInstance; |
| | | expect(app.title).toEqual('app'); |
| | | })); |
| | | it('should render title in a h1 tag', async(() => { |
| | | const fixture = TestBed.createComponent(AppComponent); |
| | | fixture.detectChanges(); |
| | | const compiled = fixture.debugElement.nativeElement; |
| | | expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!'); |
| | | })); |
| | | import { APP_BASE_HREF } from '@angular/common'; |
| | | import { ThemesService, TitleService } from '@delon/theme'; |
| | | |
| | | describe('Component: App', () => { |
| | | setUpTestBed(<TestModuleMetadata>{ |
| | | declarations: [ AppComponent ], |
| | | providers: [ |
| | | ThemesService, TitleService, |
| | | { provide: APP_BASE_HREF, useValue: '/' } |
| | | ] |
| | | }); |
| | | |
| | | it('should create the app', () => { |
| | | const fixture = TestBed.createComponent(AppComponent); |
| | | const comp = fixture.debugElement.componentInstance; |
| | | expect(comp).toBeTruthy(); |
| | | }); |
| | | }); |
| | |
| | | import { Component } from '@angular/core'; |
| | | import { Component, HostBinding, OnInit } from '@angular/core'; |
| | | import { Router, NavigationEnd } from '@angular/router'; |
| | | import { ThemesService, SettingsService, TitleService } from '@delon/theme'; |
| | | import { filter, map } from 'rxjs/operators'; |
| | | |
| | | @Component({ |
| | | selector: 'app-root', |
| | | templateUrl: './app.component.html', |
| | | styleUrls: ['./app.component.css'] |
| | | template: `<router-outlet></router-outlet>` |
| | | }) |
| | | export class AppComponent { |
| | | title = 'app'; |
| | | export class AppComponent implements OnInit { |
| | | |
| | | @HostBinding('class.layout-fixed') get isFixed() { return this.settings.layout.fixed; } |
| | | @HostBinding('class.layout-boxed') get isBoxed() { return this.settings.layout.boxed; } |
| | | @HostBinding('class.aside-collapsed') get isCollapsed() { return this.settings.layout.collapsed; } |
| | | |
| | | constructor( |
| | | private theme: ThemesService, |
| | | private settings: SettingsService, |
| | | private router: Router, |
| | | private titleSrv: TitleService) { |
| | | } |
| | | |
| | | ngOnInit() { |
| | | this.router.events.pipe( |
| | | filter(evt => evt instanceof NavigationEnd), |
| | | map(() => this.router.url) |
| | | ) |
| | | .subscribe(url => { |
| | | this.titleSrv.setTitleByUrl(url); |
| | | }); |
| | | } |
| | | } |
| | |
| | | import { BrowserModule } from '@angular/platform-browser'; |
| | | import { NgModule } from '@angular/core'; |
| | | import { NgModule, LOCALE_ID, APP_INITIALIZER, Injector } from '@angular/core'; |
| | | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; |
| | | import { TranslateModule, TranslateLoader, TranslateService } from '@ngx-translate/core'; |
| | | import { TranslateHttpLoader } from '@ngx-translate/http-loader'; |
| | | import { HttpClient, HTTP_INTERCEPTORS } from '@angular/common/http'; |
| | | |
| | | |
| | | import { CoreModule } from './core/core.module'; |
| | | import { SharedModule } from './shared/shared.module'; |
| | | import { AppComponent } from './app.component'; |
| | | import { RoutesModule } from './routes/routes.module'; |
| | | import { LayoutModule } from './layout/layout.module'; |
| | | import { StartupService } from './core/services/startup.service'; |
| | | import { DefaultInterceptor } from '@core/net/default.interceptor'; |
| | | import { AlainAuthModule, SimpleInterceptor } from '@delon/auth'; |
| | | |
| | | // i18n |
| | | import { I18NService } from './core/i18n/i18n.service'; |
| | | import { ALAIN_I18N_TOKEN } from '@delon/theme'; |
| | | |
| | | import { registerLocaleData } from '@angular/common'; |
| | | import localeZhHans from '@angular/common/locales/zh-Hans'; |
| | | registerLocaleData(localeZhHans); |
| | | |
| | | // AoT requires an exported function for factories |
| | | export function HttpLoaderFactory(http: HttpClient) { |
| | | return new TranslateHttpLoader(http, `assets/i18n/`, '.json'); |
| | | } |
| | | |
| | | export function StartupServiceFactory(startupService: StartupService): Function { |
| | | return () => startupService.load(); |
| | | } |
| | | |
| | | @NgModule({ |
| | | declarations: [ |
| | | AppComponent |
| | | ], |
| | | imports: [ |
| | | BrowserModule |
| | | ], |
| | | providers: [], |
| | | bootstrap: [AppComponent] |
| | | declarations: [ |
| | | AppComponent |
| | | ], |
| | | imports: [ |
| | | BrowserModule, |
| | | BrowserAnimationsModule, |
| | | SharedModule.forRoot(), |
| | | CoreModule, |
| | | LayoutModule, |
| | | RoutesModule, |
| | | // auth |
| | | AlainAuthModule.forRoot({ |
| | | login_url: `/pro/user/login` |
| | | }), |
| | | // i18n |
| | | TranslateModule.forRoot({ |
| | | loader: { |
| | | provide: TranslateLoader, |
| | | useFactory: HttpLoaderFactory, |
| | | deps: [HttpClient] |
| | | } |
| | | }) |
| | | ], |
| | | providers: [ |
| | | { provide: LOCALE_ID, useValue: 'zh-Hans' }, |
| | | { provide: HTTP_INTERCEPTORS, useClass: SimpleInterceptor, multi: true}, |
| | | { provide: HTTP_INTERCEPTORS, useClass: DefaultInterceptor, multi: true}, |
| | | { provide: ALAIN_I18N_TOKEN, useClass: I18NService, multi: false }, |
| | | StartupService, |
| | | { |
| | | provide: APP_INITIALIZER, |
| | | useFactory: StartupServiceFactory, |
| | | deps: [StartupService], |
| | | multi: true |
| | | } |
| | | ], |
| | | bootstrap: [AppComponent] |
| | | }) |
| | | export class AppModule { } |
New file |
| | |
| | | import { NgModule, Optional, SkipSelf } from '@angular/core'; |
| | | import { throwIfAlreadyLoaded } from './module-import-guard'; |
| | | import { AlainThemeModule } from '@delon/theme'; |
| | | |
| | | import { I18NService } from './i18n/i18n.service'; |
| | | |
| | | @NgModule({ |
| | | imports: [ |
| | | AlainThemeModule.forRoot() |
| | | ], |
| | | providers: [ |
| | | I18NService |
| | | ] |
| | | }) |
| | | export class CoreModule { |
| | | constructor( @Optional() @SkipSelf() parentModule: CoreModule) { |
| | | throwIfAlreadyLoaded(parentModule, 'CoreModule'); |
| | | } |
| | | } |
New file |
| | |
| | | import { SharedModule } from '@shared/shared.module'; |
| | | import { TestBed, async, inject } from '@angular/core/testing'; |
| | | import { TranslateService, TranslateModule, TranslateLoader } from '@ngx-translate/core'; |
| | | import { HttpClient, HttpClientModule } from '@angular/common/http'; |
| | | |
| | | import { I18NService } from './i18n.service'; |
| | | import { HttpLoaderFactory } from '../../app.module'; |
| | | import { SettingsService } from '@delon/theme'; |
| | | |
| | | describe('Service: I18n', () => { |
| | | beforeEach(() => { |
| | | TestBed.configureTestingModule({ |
| | | imports: [ |
| | | HttpClientModule, |
| | | SharedModule.forRoot(), |
| | | TranslateModule.forRoot({ |
| | | loader: { |
| | | provide: TranslateLoader, |
| | | useFactory: (HttpLoaderFactory), |
| | | deps: [HttpClient] |
| | | } |
| | | }) |
| | | ], |
| | | providers: [I18NService, SettingsService] |
| | | }); |
| | | }); |
| | | |
| | | it('should create an instance', inject([I18NService], (service: I18NService) => { |
| | | expect(service).toBeTruthy(); |
| | | })); |
| | | }); |
New file |
| | |
| | | import { Injectable, Inject, Injector } from '@angular/core'; |
| | | import { Router } from '@angular/router'; |
| | | import { zhCN, enUS, NzLocaleService } from 'ng-zorro-antd'; |
| | | import { TranslateService } from '@ngx-translate/core'; |
| | | import { SettingsService, AlainI18NService } from '@delon/theme'; |
| | | import { Observable } from 'rxjs/Observable'; |
| | | |
| | | @Injectable() |
| | | export class I18NService implements AlainI18NService { |
| | | |
| | | private _default = 'en'; |
| | | |
| | | private _langs = [ |
| | | { code: 'en', text: 'English' }, |
| | | { code: 'zh-CN', text: '中文' } |
| | | ]; |
| | | |
| | | constructor(settings: SettingsService, |
| | | private nzLocalService: NzLocaleService, |
| | | private translate: TranslateService, |
| | | private injector: Injector) { |
| | | this._default = settings.layout.lang || translate.getBrowserLang(); |
| | | const lans = this._langs.map(item => item.code); |
| | | if (!lans.includes(this._default)) { |
| | | this._default = lans[0]; |
| | | } |
| | | translate.addLangs(lans); |
| | | translate.setDefaultLang(this._default); |
| | | } |
| | | |
| | | use(lang: string = null, firstLoad = true): Observable<any> { |
| | | lang = lang || this.translate.getDefaultLang(); |
| | | this.nzLocalService.setLocale(lang === 'en' ? enUS : zhCN); |
| | | // need reload router because of ng-zorro-antd local system |
| | | if (!firstLoad) this.injector.get(Router).navigate([ '/' ]); |
| | | return this.translate.use(lang); |
| | | } |
| | | |
| | | getLangs() { |
| | | return this._langs; |
| | | } |
| | | |
| | | fanyi(key: string) { |
| | | return this.translate.instant(key); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | // https://angular.io/guide/styleguide#style-04-12 |
| | | export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) { |
| | | if (parentModule) { |
| | | throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`); |
| | | } |
| | | } |
New file |
| | |
| | | import { Injectable, Injector } from '@angular/core'; |
| | | import { Router } from '@angular/router'; |
| | | import { HttpInterceptor, HttpRequest, HttpHandler, |
| | | HttpSentEvent, HttpHeaderResponse, HttpProgressEvent, HttpResponse, HttpUserEvent, |
| | | } from '@angular/common/http'; |
| | | import { Observable } from 'rxjs/Observable'; |
| | | import { ErrorObservable } from 'rxjs/observable/ErrorObservable'; |
| | | import { catchError } from 'rxjs/operators'; |
| | | import { map, mergeMap } from 'rxjs/operators'; |
| | | |
| | | import { environment } from '../../../environments/environment'; |
| | | |
| | | /** |
| | | * 默认HTTP拦截器,其注册细节见 `app.module.ts` |
| | | */ |
| | | @Injectable() |
| | | export class DefaultInterceptor implements HttpInterceptor { |
| | | constructor(private injector: Injector) {} |
| | | |
| | | private goLogin() { |
| | | const router = this.injector.get(Router); |
| | | this.injector.get(Router).navigate([ '/login' ]); |
| | | } |
| | | |
| | | intercept(req: HttpRequest<any>, next: HttpHandler): |
| | | Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> { |
| | | |
| | | // TIPS:原TOKEN信息已交由 `@delon/auth` 处理 |
| | | // Document: http://ng-alain.com/docs/auth |
| | | |
| | | // 统一加上服务端前缀 |
| | | let url = req.url; |
| | | if (!url.startsWith('https://') && !url.startsWith('http://')) { |
| | | url = environment.SERVER_URL + url; |
| | | } |
| | | |
| | | const newReq = req.clone({ |
| | | url: url |
| | | }); |
| | | |
| | | return next.handle(newReq).pipe( |
| | | mergeMap((event: any) => { |
| | | // 允许统一对请求错误处理,这是因为一个请求若是业务上错误的情况下其HTTP请求的状态是200的情况下需要 |
| | | if (event instanceof HttpResponse && event.status !== 200) { |
| | | // 业务处理:observer.error 会跳转至后面的 `catch` |
| | | // return ErrorObservable.create(event); |
| | | } |
| | | // 若一切都正常,则后续操作 |
| | | return Observable.create(observer => observer.next(event)); |
| | | }), |
| | | catchError((res: HttpResponse<any>) => { |
| | | // 业务处理:一些通用操作 |
| | | switch (res.status) { |
| | | case 401: // 未登录状态码 |
| | | this.goLogin(); |
| | | break; |
| | | case 200: |
| | | // 业务层级错误处理 |
| | | console.log('业务错误'); |
| | | break; |
| | | case 404: |
| | | // 404 |
| | | break; |
| | | } |
| | | // 以错误的形式结束本次请求 |
| | | return ErrorObservable.create(event); |
| | | }) |
| | | ); |
| | | } |
| | | } |
New file |
| | |
| | | import { Router } from '@angular/router'; |
| | | import { Injectable, Injector } from '@angular/core'; |
| | | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; |
| | | import { MenuService, SettingsService, TitleService } from '@delon/theme'; |
| | | import { ACLService } from '@delon/acl'; |
| | | import { I18NService } from '../i18n/i18n.service'; |
| | | |
| | | /** |
| | | * 用于应用启动时 |
| | | * 一般用来获取应用所需要的基础数据等 |
| | | */ |
| | | @Injectable() |
| | | export class StartupService { |
| | | constructor( |
| | | private menuService: MenuService, |
| | | private i18n: I18NService, |
| | | private settingService: SettingsService, |
| | | private aclService: ACLService, |
| | | private titleService: TitleService, |
| | | private httpClient: HttpClient, |
| | | private injector: Injector) { } |
| | | |
| | | load(): Promise<any> { |
| | | // only works with promises |
| | | // https://github.com/angular/angular/issues/15088 |
| | | return new Promise((resolve, reject) => { |
| | | this.httpClient.get('assets/app-data.json') |
| | | .subscribe((res: any) => { |
| | | // 应用信息:包括站点名、描述、年份 |
| | | this.settingService.setApp(res.app); |
| | | // 用户信息:包括姓名、头像、邮箱地址 |
| | | this.settingService.setUser(res.user); |
| | | // ACL:设置权限为全量 |
| | | this.aclService.setFull(true); |
| | | // 初始化菜单 |
| | | this.menuService.add(res.menu); |
| | | // i18n:设置默认语言 |
| | | this.i18n.use(this.settingService.layout.lang); |
| | | // 设置页面标题的后缀 |
| | | this.titleService.suffix = res.app.name; |
| | | |
| | | resolve(res); |
| | | }, (err: HttpErrorResponse) => { |
| | | resolve(null); |
| | | }); |
| | | }); |
| | | } |
| | | } |
New file |
| | |
| | | <router-outlet></router-outlet> |
New file |
| | |
| | | import { Component } from '@angular/core'; |
| | | |
| | | @Component({ |
| | | selector: 'app-layout-fullscreen', |
| | | templateUrl: './fullscreen.component.html' |
| | | }) |
| | | export class LayoutFullScreenComponent { |
| | | |
| | | } |
New file |
| | |
| | | import { Component, HostListener } from '@angular/core'; |
| | | import * as screenfull from 'screenfull'; |
| | | |
| | | @Component({ |
| | | selector: 'header-fullscreen', |
| | | template: ` |
| | | <i class="anticon anticon-{{status ? 'shrink' : 'arrows-alt'}}"></i> |
| | | {{status ? 'fullscreen-exit' : 'fullscreen' | translate }} |
| | | ` |
| | | }) |
| | | export class HeaderFullScreenComponent { |
| | | |
| | | status = false; |
| | | |
| | | @HostListener('click') |
| | | _click() { |
| | | if (screenfull.enabled) { |
| | | screenfull.toggle(); |
| | | } |
| | | this.status = !screenfull.isFullscreen; |
| | | } |
| | | } |
New file |
| | |
| | | import { Component } from '@angular/core'; |
| | | |
| | | @Component({ |
| | | selector: 'header-icon', |
| | | template: ` |
| | | <nz-dropdown nzTrigger="click" nzPlacement="bottomRight" (nzVisibleChange)="change()"> |
| | | <div class="item" nz-dropdown> |
| | | <i class="anticon anticon-appstore-o"></i> |
| | | </div> |
| | | <div nz-menu class="wd-xl animated jello"> |
| | | <nz-spin [nzSpinning]="loading" [nzTip]="'正在读取数据...'"> |
| | | <div nz-row [nzType]="'flex'" [nzJustify]="'center'" [nzAlign]="'middle'" class="app-icons"> |
| | | <div nz-col [nzSpan]="6"> |
| | | <i class="anticon anticon-calendar bg-error text-white"></i> |
| | | <small>Calendar</small> |
| | | </div> |
| | | <div nz-col [nzSpan]="6"> |
| | | <i class="anticon anticon-file bg-teal text-white"></i> |
| | | <small>Files</small> |
| | | </div> |
| | | <div nz-col [nzSpan]="6"> |
| | | <i class="anticon anticon-cloud bg-success text-white"></i> |
| | | <small>Cloud</small> |
| | | </div> |
| | | <div nz-col [nzSpan]="6"> |
| | | <i class="anticon anticon-star-o bg-pink text-white"></i> |
| | | <small>Star</small> |
| | | </div> |
| | | <div nz-col [nzSpan]="6"> |
| | | <i class="anticon anticon-team bg-purple text-white"></i> |
| | | <small>Team</small> |
| | | </div> |
| | | <div nz-col [nzSpan]="6"> |
| | | <i class="anticon anticon-scan bg-warning text-white"></i> |
| | | <small>QR</small> |
| | | </div> |
| | | <div nz-col [nzSpan]="6"> |
| | | <i class="anticon anticon-pay-circle-o bg-cyan text-white"></i> |
| | | <small>Pay</small> |
| | | </div> |
| | | <div nz-col [nzSpan]="6"> |
| | | <i class="anticon anticon-printer bg-grey text-white"></i> |
| | | <small>Print</small> |
| | | </div> |
| | | </div> |
| | | </nz-spin> |
| | | </div> |
| | | </nz-dropdown> |
| | | ` |
| | | }) |
| | | export class HeaderIconComponent { |
| | | |
| | | loading = true; |
| | | |
| | | change() { |
| | | setTimeout(() => this.loading = false, 500); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | import { Component } from '@angular/core'; |
| | | import { SettingsService, MenuService } from '@delon/theme'; |
| | | import { I18NService } from '@core/i18n/i18n.service'; |
| | | |
| | | @Component({ |
| | | selector: 'header-langs', |
| | | template: ` |
| | | <nz-dropdown> |
| | | <div nz-dropdown> |
| | | <i class="anticon anticon-edit"></i> |
| | | {{ 'language' | translate}} |
| | | <i class="anticon anticon-down"></i> |
| | | </div> |
| | | <ul nz-menu> |
| | | <li nz-menu-item *ngFor="let item of langs" |
| | | [nzSelected]="item.code === settings.layout.lang" |
| | | (click)="change(item.code)">{{item.text}}</li> |
| | | </ul> |
| | | </nz-dropdown> |
| | | ` |
| | | }) |
| | | export class HeaderLangsComponent { |
| | | |
| | | langs: any[]; |
| | | |
| | | constructor( |
| | | private menuService: MenuService, |
| | | public settings: SettingsService, |
| | | public tsServ: I18NService |
| | | ) { |
| | | this.langs = this.tsServ.getLangs(); |
| | | } |
| | | |
| | | change(lang: string) { |
| | | this.tsServ.use(lang, false).subscribe(() => { |
| | | this.menuService.resume(); |
| | | }); |
| | | this.settings.setLayout('lang', lang); |
| | | } |
| | | } |
New file |
| | |
| | | import { NzMessageService } from 'ng-zorro-antd'; |
| | | import { Component, OnInit, Input } from '@angular/core'; |
| | | import { Observable } from 'rxjs/Observable'; |
| | | import { ArrayObservable } from 'rxjs/observable/ArrayObservable'; |
| | | import { map, groupBy, concatMap, mergeMap, flatMap, delay, tap, toArray } from 'rxjs/operators'; |
| | | import * as moment from 'moment'; |
| | | import { NoticeItem } from '@delon/abc'; |
| | | import { SettingsService } from '@delon/theme'; |
| | | |
| | | /** |
| | | * 菜单通知 |
| | | */ |
| | | @Component({ |
| | | selector: 'header-notify', |
| | | template: ` |
| | | <notice-icon |
| | | [data]="data" |
| | | [count]="count" |
| | | [loading]="loading" |
| | | (select)="select($event)" |
| | | (clear)="clear($event)" |
| | | (popupVisibleChange)="loadData($event)"></notice-icon> |
| | | ` |
| | | }) |
| | | export class HeaderNotifyComponent implements OnInit { |
| | | |
| | | data: NoticeItem[] = [ |
| | | { title: '通知', list: [], emptyText: '你已查看所有通知', emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg' }, |
| | | { title: '消息', list: [], emptyText: '您已读完所有消息', emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg' }, |
| | | { title: '待办', list: [], emptyText: '你已完成所有待办', emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/HsIsxMZiWKrNUavQUXqx.svg' } |
| | | ]; |
| | | count = 0; |
| | | loading = false; |
| | | |
| | | constructor(private msg: NzMessageService, private settings: SettingsService) {} |
| | | |
| | | ngOnInit() { |
| | | // mock data |
| | | this.count = this.settings.user.notifyCount || 12; |
| | | } |
| | | |
| | | private parseGroup(data: Observable<any[]>) { |
| | | console.log('parseGroup'); |
| | | data.pipe( |
| | | concatMap((i: any) => i), |
| | | map((i: any) => { |
| | | if (i.datetime) i.datetime = moment(i.datetime).fromNow(); |
| | | // change to color |
| | | if (i.status) { |
| | | i.color = ({ |
| | | todo: '', |
| | | processing: 'blue', |
| | | urgent: 'red', |
| | | doing: 'gold', |
| | | })[i.status]; |
| | | } |
| | | return i; |
| | | }), |
| | | groupBy((x: any) => x.type), |
| | | mergeMap(g => g.pipe(toArray())), |
| | | tap((ls: any) => { |
| | | this.data.find(w => w.title === ls[0].type).list = ls; |
| | | }) |
| | | ).subscribe(res => this.loading = false); |
| | | } |
| | | |
| | | loadData(res) { |
| | | if (!res || this.loading) return; |
| | | this.loading = true; |
| | | // region: mock http request |
| | | this.parseGroup(ArrayObservable.of([{ |
| | | 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: '待办', |
| | | } |
| | | ]).pipe(delay(1000))); |
| | | // endregion |
| | | } |
| | | |
| | | clear(type: string) { |
| | | this.msg.success(`清空了 ${type}`); |
| | | } |
| | | |
| | | select(res: any) { |
| | | this.msg.success(`点击了 ${res.title} 的 ${res.item.title}`); |
| | | } |
| | | } |
New file |
| | |
| | | import { Component, HostBinding, ViewChild, Input, OnInit, ElementRef, AfterViewInit } from '@angular/core'; |
| | | |
| | | @Component({ |
| | | selector: 'header-search', |
| | | template: ` |
| | | <nz-input nzPlaceHolder='{{ "top-search-ph" | translate}}' [(ngModel)]="q" |
| | | (nzFocus)="qFocus()" (nzBlur)="qBlur()"> |
| | | <ng-template #prefix> |
| | | <i class="anticon anticon-search"></i> |
| | | </ng-template> |
| | | </nz-input> |
| | | ` |
| | | }) |
| | | export class HeaderSearchComponent implements AfterViewInit { |
| | | |
| | | q: string; |
| | | |
| | | qIpt: HTMLInputElement; |
| | | |
| | | @HostBinding('class.header-search__focus') |
| | | focus = false; |
| | | |
| | | @HostBinding('class.header-search__toggled') |
| | | searchToggled = false; |
| | | |
| | | @Input() |
| | | set toggleChange(value: boolean) { |
| | | if (typeof value === 'undefined') return; |
| | | console.log('toggleChange', value); |
| | | this.searchToggled = true; |
| | | this.focus = true; |
| | | setTimeout(() => this.qIpt.focus(), 300); |
| | | } |
| | | |
| | | constructor(private el: ElementRef) {} |
| | | |
| | | ngAfterViewInit() { |
| | | this.qIpt = (this.el.nativeElement as HTMLElement).querySelector('.ant-input') as HTMLInputElement; |
| | | } |
| | | |
| | | qFocus() { |
| | | this.focus = true; |
| | | } |
| | | |
| | | qBlur() { |
| | | this.focus = false; |
| | | this.searchToggled = false; |
| | | } |
| | | } |
New file |
| | |
| | | import { Component, HostListener } from '@angular/core'; |
| | | import { NzModalService, NzMessageService } from 'ng-zorro-antd'; |
| | | |
| | | @Component({ |
| | | selector: 'header-storage', |
| | | template: ` |
| | | <i class="anticon anticon-tool"></i> |
| | | {{ 'clear-local-storage' | translate}}` |
| | | }) |
| | | export class HeaderStorageComponent { |
| | | |
| | | constructor( |
| | | private confirmServ: NzModalService, |
| | | private messageServ: NzMessageService |
| | | ) { |
| | | } |
| | | |
| | | @HostListener('click') |
| | | _click() { |
| | | this.confirmServ.confirm({ |
| | | title: 'Make sure clear all local storage?', |
| | | onOk: () => { |
| | | localStorage.clear(); |
| | | this.messageServ.success('Clear Finished!'); |
| | | } |
| | | }); |
| | | } |
| | | } |
New file |
| | |
| | | import { Component } from '@angular/core'; |
| | | |
| | | @Component({ |
| | | selector: 'header-task', |
| | | template: ` |
| | | <nz-dropdown nzTrigger="click" nzPlacement="bottomRight" (nzVisibleChange)="change()"> |
| | | <div class="item" nz-dropdown> |
| | | <nz-badge [nzDot]="true"> |
| | | <ng-template #content> |
| | | <i class="anticon anticon-bell"></i> |
| | | </ng-template> |
| | | </nz-badge> |
| | | </div> |
| | | <div nz-menu class="wd-lg"> |
| | | <nz-card nzTitle="Notifications" [nzLoading]="loading" class="ant-card__body-nopadding"> |
| | | <ng-template #extra><i class="anticon anticon-plus"></i></ng-template> |
| | | <div nz-row [nzType]="'flex'" [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm bg-grey-lighter-h point"> |
| | | <div nz-col [nzSpan]="4" class="text-center"> |
| | | <nz-avatar [nzSrc]="'./assets/img/1.png'" [nzSize]="'large'"></nz-avatar> |
| | | </div> |
| | | <div nz-col [nzSpan]="20"> |
| | | <strong>cipchk</strong> |
| | | <p>Please tell me what happened in a few words, don't go into details.</p> |
| | | </div> |
| | | </div> |
| | | <div nz-row [nzType]="'flex'" [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm bg-grey-lighter-h point"> |
| | | <div nz-col [nzSpan]="4" class="text-center"> |
| | | <nz-avatar [nzSrc]="'./assets/img/2.png'" [nzSize]="'large'"></nz-avatar> |
| | | </div> |
| | | <div nz-col [nzSpan]="20"> |
| | | <strong>はなさき</strong> |
| | | <p>ハルカソラトキヘダツヒカリ </p> |
| | | </div> |
| | | </div> |
| | | <div nz-row [nzType]="'flex'" [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm bg-grey-lighter-h point"> |
| | | <div nz-col [nzSpan]="4" class="text-center"> |
| | | <nz-avatar [nzSrc]="'./assets/img/3.png'" [nzSize]="'large'"></nz-avatar> |
| | | </div> |
| | | <div nz-col [nzSpan]="20"> |
| | | <strong>苏先生</strong> |
| | | <p>请告诉我,我应该说点什么好?</p> |
| | | </div> |
| | | </div> |
| | | <div nz-row [nzType]="'flex'" [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm bg-grey-lighter-h point"> |
| | | <div nz-col [nzSpan]="4" class="text-center"> |
| | | <nz-avatar [nzSrc]="'./assets/img/4.png'" [nzSize]="'large'"></nz-avatar> |
| | | </div> |
| | | <div nz-col [nzSpan]="20"> |
| | | <strong>Kent</strong> |
| | | <p>Please tell me what happened in a few words, don't go into details.</p> |
| | | </div> |
| | | </div> |
| | | <div nz-row [nzType]="'flex'" [nzJustify]="'center'" [nzAlign]="'middle'" class="py-sm bg-grey-lighter-h point"> |
| | | <div nz-col [nzSpan]="4" class="text-center"> |
| | | <nz-avatar [nzSrc]="'./assets/img/5.png'" [nzSize]="'large'"></nz-avatar> |
| | | </div> |
| | | <div nz-col [nzSpan]="20"> |
| | | <strong>Jefferson</strong> |
| | | <p>Please tell me what happened in a few words, don't go into details.</p> |
| | | </div> |
| | | </div> |
| | | <div nz-row class="pt-lg pb-lg"> |
| | | <div nz-col [nzSpan]="24" class="text-center text-grey point"> |
| | | See All |
| | | </div> |
| | | </div> |
| | | </nz-card> |
| | | </div> |
| | | </nz-dropdown> |
| | | ` |
| | | }) |
| | | export class HeaderTaskComponent { |
| | | |
| | | loading = true; |
| | | |
| | | change() { |
| | | setTimeout(() => this.loading = false, 500); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | import { Component } from '@angular/core'; |
| | | import { SettingsService, ThemesService, ThemeType } from '@delon/theme'; |
| | | |
| | | @Component({ |
| | | selector: 'header-theme', |
| | | template: ` |
| | | <strong>{{ 'theme-switch' | translate}}</strong> |
| | | <div class="theme-icons"> |
| | | <label *ngFor="let item of themes" (click)="changeTheme(item.l)" [style.background]="item.bg"> |
| | | <i class="anticon anticon-check" *ngIf="item.l == settings.layout.theme"></i> |
| | | <div class="areas"> |
| | | <span class="nav" [style.background]="item.nav"></span> |
| | | <span class="con" [style.background]="item.con"></span> |
| | | </div> |
| | | </label> |
| | | </div> |
| | | ` |
| | | }) |
| | | export class HeaderThemeComponent { |
| | | |
| | | themes: { l: ThemeType, bg: string, nav: string, con: string }[] = [ |
| | | { l: 'A', bg: '#108ee9', nav: '#fff', con: '#f5f7fa' }, |
| | | { l: 'B', bg: '#00a2ae', nav: '#fff', con: '#f5f7fa' }, |
| | | { l: 'C', bg: '#00a854', nav: '#fff', con: '#f5f7fa' }, |
| | | { l: 'D', bg: '#f04134', nav: '#fff', con: '#f5f7fa' }, |
| | | { l: 'E', bg: '#373d41', nav: '#fff', con: '#f5f7fa' }, |
| | | { l: 'F', bg: '#108ee9', nav: '#404040', con: '#f5f7fa' }, |
| | | { l: 'G', bg: '#00a2ae', nav: '#404040', con: '#f5f7fa' }, |
| | | { l: 'H', bg: '#00a854', nav: '#404040', con: '#f5f7fa' }, |
| | | { l: 'I', bg: '#f04134', nav: '#404040', con: '#f5f7fa' }, |
| | | { l: 'J', bg: '#373d41', nav: '#404040', con: '#f5f7fa' } |
| | | ]; |
| | | |
| | | constructor( |
| | | public settings: SettingsService, |
| | | private themeServ: ThemesService |
| | | ) { |
| | | } |
| | | |
| | | changeTheme(theme: ThemeType) { |
| | | this.themeServ.setTheme(theme); |
| | | this.settings.setLayout('theme', theme); |
| | | } |
| | | } |
New file |
| | |
| | | import { Component, OnInit, Inject } from '@angular/core'; |
| | | import { Router } from '@angular/router'; |
| | | import { SettingsService } from '@delon/theme'; |
| | | import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth'; |
| | | |
| | | @Component({ |
| | | selector: 'header-user', |
| | | template: ` |
| | | <nz-dropdown nzPlacement="bottomRight"> |
| | | <div class="item d-flex align-items-center px-sm" nz-dropdown> |
| | | <nz-avatar [nzSrc]="settings.user.avatar" nzSize="small" class="mr-sm"></nz-avatar> |
| | | {{settings.user.name}} |
| | | </div> |
| | | <div nz-menu class="width-sm"> |
| | | <div nz-menu-item [nzDisable]="true"><i class="anticon anticon-user mr-sm"></i>个人中心</div> |
| | | <div nz-menu-item [nzDisable]="true"><i class="anticon anticon-setting mr-sm"></i>设置</div> |
| | | <li nz-menu-divider></li> |
| | | <div nz-menu-item (click)="logout()"><i class="anticon anticon-setting mr-sm"></i>退出登录</div> |
| | | </div> |
| | | </nz-dropdown> |
| | | ` |
| | | }) |
| | | export class HeaderUserComponent implements OnInit { |
| | | constructor( |
| | | public settings: SettingsService, |
| | | private router: Router, |
| | | @Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService) {} |
| | | |
| | | ngOnInit(): void { |
| | | this.tokenService.change().subscribe((res: any) => { |
| | | this.settings.setUser(res); |
| | | }); |
| | | const token = this.tokenService.get() || { |
| | | token: 'nothing', |
| | | name: 'Admin', |
| | | avatar: './assets/img/zorro.svg', |
| | | email: 'cipchk@qq.com' |
| | | }; |
| | | this.tokenService.set(token); |
| | | } |
| | | |
| | | logout() { |
| | | this.tokenService.clear(); |
| | | this.router.navigateByUrl('/pro/user/login'); |
| | | } |
| | | } |
New file |
| | |
| | | <div class="logo"> |
| | | <a [routerLink]="['/']"> |
| | | <img class="expanded" src="./assets/img/logo-full.svg" alt="{{settings.app.name}}" style="max-height:40px;" /> |
| | | <img class="collapsed" src="./assets/img/logo.svg" alt="{{settings.app.name}}" style="max-height:30px;" /> |
| | | </a> |
| | | </div> |
| | | <div class="top-nav-wrap"> |
| | | <ul class="top-nav"> |
| | | <!-- Menu --> |
| | | <li> |
| | | <div class="item" (click)="toggleCollapsedSideabar()"> |
| | | <i class="anticon anticon-menu-{{settings.layout.collapsed ? 'unfold' : 'fold'}}"></i> |
| | | </div> |
| | | </li> |
| | | <!-- Github Page --> |
| | | <li> |
| | | <a class="item" href="//github.com/cipchk/ng-alain" target="_blank"> |
| | | <i class="anticon anticon-github"></i> |
| | | </a> |
| | | </li> |
| | | <!-- Lock Page --> |
| | | <li class="hidden-xs"> |
| | | <div class="item" [routerLink]="['/lock']"> |
| | | <i class="anticon anticon-lock"></i> |
| | | </div> |
| | | </li> |
| | | <!-- Search Button --> |
| | | <li class="header-search__btn" (click)="searchToggleChange()"> |
| | | <div class="item"> |
| | | <i class="anticon anticon-search"></i> |
| | | </div> |
| | | </li> |
| | | </ul> |
| | | <header-search class="header-search" [toggleChange]="searchToggleStatus"></header-search> |
| | | <ul class="top-nav"> |
| | | <!-- Notify --> |
| | | <li> |
| | | <header-notify></header-notify> |
| | | </li> |
| | | <!-- Task --> |
| | | <li class="hidden-xs"> |
| | | <header-task></header-task> |
| | | </li> |
| | | <!-- App Icons --> |
| | | <li class="hidden-xs"> |
| | | <header-icon></header-icon> |
| | | </li> |
| | | <!-- Settings --> |
| | | <li class="hidden-xs"> |
| | | <nz-dropdown nzTrigger="click" nzPlacement="bottomRight"> |
| | | <div class="item" nz-dropdown> |
| | | <i class="anticon anticon-setting"></i> |
| | | </div> |
| | | <div nz-menu style="width:200px"> |
| | | <div nz-menu-item class="theme-switch"> |
| | | <header-theme></header-theme> |
| | | </div> |
| | | <div nz-menu-item> |
| | | <header-fullscreen></header-fullscreen> |
| | | </div> |
| | | <div nz-menu-item> |
| | | <header-storage></header-storage> |
| | | </div> |
| | | <div nz-menu-item> |
| | | <header-langs></header-langs> |
| | | </div> |
| | | </div> |
| | | </nz-dropdown> |
| | | </li> |
| | | <li class="hidden-xs"> |
| | | <header-user></header-user> |
| | | </li> |
| | | </ul> |
| | | </div> |
New file |
| | |
| | | import { TestBed, TestModuleMetadata } from '@angular/core/testing'; |
| | | |
| | | import { setUpTestBed } from 'testing/common.spec'; |
| | | |
| | | import { HeaderComponent } from './header.component'; |
| | | |
| | | describe('Layout: Header', () => { |
| | | setUpTestBed(<TestModuleMetadata>{ |
| | | declarations: [ HeaderComponent ] |
| | | }); |
| | | |
| | | it('should create an instance', () => { |
| | | const fixture = TestBed.createComponent(HeaderComponent); |
| | | const comp = fixture.debugElement.componentInstance; |
| | | expect(comp).toBeTruthy(); |
| | | }); |
| | | }); |
New file |
| | |
| | | import { Component, ViewChild } from '@angular/core'; |
| | | import { SettingsService } from '@delon/theme'; |
| | | |
| | | @Component({ |
| | | selector: 'app-header', |
| | | templateUrl: './header.component.html' |
| | | }) |
| | | export class HeaderComponent { |
| | | searchToggleStatus: boolean; |
| | | |
| | | constructor(public settings: SettingsService) { } |
| | | |
| | | toggleCollapsedSideabar() { |
| | | this.settings.setLayout('collapsed', !this.settings.layout.collapsed); |
| | | } |
| | | |
| | | searchToggleChange() { |
| | | this.searchToggleStatus = !this.searchToggleStatus; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | --- |
| | | component: app-header |
| | | title: 顶部菜单 |
| | | --- |
| | | |
| | | 顶部菜单组件允许通过 `components` 目录下的组件进行按需组装。 |
| | | |
| | | ## 组件列表 |
| | | |
| | | 组件名 | 说明 |
| | | ----|------ |
| | | `header-fullscreen` | 全屏切换 |
| | | `header-icon` | 应用图标 |
| | | `header-langs` | 语言切换 |
| | | `header-notify` | 菜单通知 |
| | | `header-search` | 搜索框 |
| | | `header-storage` | 清除 LocalStorage 缓存 |
| | | `header-task` | 任务通知 |
| | | `header-theme` | 主题切换 |
| | | `header-user` | 用户菜单 |
New file |
| | |
| | | <div class="wrapper"> |
| | | <div class="router-progress-bar" *ngIf="isFetching"></div> |
| | | <app-header class="header"></app-header> |
| | | <app-sidebar class="aside"></app-sidebar> |
| | | <section class="content"><router-outlet></router-outlet></section> |
| | | </div> |
New file |
| | |
| | | import { TestBed, TestModuleMetadata } from '@angular/core/testing'; |
| | | |
| | | import { setUpTestBed } from 'testing/common.spec'; |
| | | |
| | | import { LayoutComponent } from './layout.component'; |
| | | |
| | | describe('Layout', () => { |
| | | setUpTestBed(<TestModuleMetadata>{ |
| | | declarations: [LayoutComponent] |
| | | }); |
| | | |
| | | it('should create an instance', () => { |
| | | const fixture = TestBed.createComponent(LayoutComponent); |
| | | const comp = fixture.debugElement.componentInstance; |
| | | expect(comp).toBeTruthy(); |
| | | }); |
| | | }); |
New file |
| | |
| | | import { Component } from '@angular/core'; |
| | | import { Router, NavigationEnd, RouteConfigLoadStart, NavigationError } from '@angular/router'; |
| | | import { NzMessageService } from 'ng-zorro-antd'; |
| | | import { ScrollService, MenuService, SettingsService } from '@delon/theme'; |
| | | |
| | | @Component({ |
| | | selector: 'app-layout', |
| | | templateUrl: './layout.component.html' |
| | | }) |
| | | export class LayoutComponent { |
| | | isFetching = false; |
| | | |
| | | constructor( |
| | | router: Router, |
| | | scroll: ScrollService, |
| | | private _message: NzMessageService, |
| | | public menuSrv: MenuService, |
| | | public settings: SettingsService) { |
| | | // 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); |
| | | }); |
| | | } |
| | | } |
New file |
| | |
| | | import { NgModule } from '@angular/core'; |
| | | |
| | | import { SharedModule } from '@shared/shared.module'; |
| | | import { LayoutComponent } from './layout.component'; |
| | | import { LayoutFullScreenComponent } from './fullscreen/fullscreen.component'; |
| | | import { HeaderComponent } from './header/header.component'; |
| | | import { SidebarComponent } from './sidebar/sidebar.component'; |
| | | import { HeaderSearchComponent } from './header/components/search.component'; |
| | | import { HeaderThemeComponent } from './header/components/theme.component'; |
| | | import { HeaderNotifyComponent } from './header/components/notify.component'; |
| | | import { HeaderTaskComponent } from './header/components/task.component'; |
| | | import { HeaderIconComponent } from './header/components/icon.component'; |
| | | import { HeaderFullScreenComponent } from './header/components/fullscreen.component'; |
| | | import { HeaderLangsComponent } from './header/components/langs.component'; |
| | | import { HeaderStorageComponent } from './header/components/storage.component'; |
| | | import { HeaderUserComponent } from './header/components/user.component'; |
| | | |
| | | const COMPONENTS = [ |
| | | LayoutComponent, |
| | | LayoutFullScreenComponent, |
| | | HeaderComponent, |
| | | SidebarComponent |
| | | ]; |
| | | |
| | | const HEADERCOMPONENTS = [ |
| | | HeaderSearchComponent, |
| | | HeaderNotifyComponent, |
| | | HeaderTaskComponent, |
| | | HeaderIconComponent, |
| | | HeaderFullScreenComponent, |
| | | HeaderThemeComponent, |
| | | HeaderLangsComponent, |
| | | HeaderStorageComponent, |
| | | HeaderUserComponent |
| | | ]; |
| | | |
| | | // pro |
| | | import { ProUserLayoutComponent } from './pro/user/user.component'; |
| | | const PRO = [ |
| | | ProUserLayoutComponent |
| | | ]; |
| | | |
| | | @NgModule({ |
| | | imports: [SharedModule], |
| | | providers: [], |
| | | declarations: [ |
| | | ...COMPONENTS, |
| | | ...HEADERCOMPONENTS, |
| | | ...PRO |
| | | ], |
| | | exports: [ |
| | | ...COMPONENTS, |
| | | ...PRO |
| | | ] |
| | | }) |
| | | export class LayoutModule { } |
New file |
| | |
| | | <div class="container"> |
| | | <div class="top"> |
| | | <div class="head"> |
| | | <a [routerLink]="['/']"> |
| | | <img class="logo" src="./assets/img/logo-color.svg"> |
| | | <span class="title">ng-alain</span> |
| | | </a> |
| | | </div> |
| | | <p class="desc">武林中最有影响力的《葵花宝典》;欲练神功,挥刀自宫</p> |
| | | </div> |
| | | <router-outlet></router-outlet> |
| | | <global-footer [links]="links"> |
| | | <ng-template #copyright> |
| | | Copyright <nz-icon nzType="copyright"></nz-icon> 2017 <a href="//github.com/cipchk" target="_blank">卡色</a>出品 |
| | | </ng-template> |
| | | </global-footer> |
| | | </div> |
New file |
| | |
| | | @import '~@delon/theme/styles/antd/themes/default.less'; |
| | | |
| | | :host { |
| | | ::ng-deep { |
| | | |
| | | .container { |
| | | background: #f0f2f5; |
| | | background-image: url("https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg"); |
| | | width: 100%; |
| | | min-height: 100%; |
| | | background-repeat: no-repeat; |
| | | background-position: center; |
| | | background-size: 100%; |
| | | padding: 110px 0 144px; |
| | | position: relative; |
| | | } |
| | | |
| | | .top { |
| | | text-align: center; |
| | | } |
| | | |
| | | .head { |
| | | height: 44px; |
| | | line-height: 44px; |
| | | a { |
| | | text-decoration: none; |
| | | } |
| | | } |
| | | |
| | | .logo { |
| | | height: 44px; |
| | | vertical-align: top; |
| | | margin-right: 16px; |
| | | } |
| | | |
| | | .title { |
| | | font-size: 33px; |
| | | color: @heading-color; |
| | | font-family: "Myriad Pro", "Helvetica Neue", Arial, Helvetica, sans-serif; |
| | | font-weight: 600; |
| | | position: relative; |
| | | top: 2px; |
| | | } |
| | | |
| | | .desc { |
| | | font-size: @font-size-base; |
| | | color: @text-color-secondary; |
| | | margin-top: 12px; |
| | | margin-bottom: 40px; |
| | | } |
| | | |
| | | .footer { |
| | | position: absolute; |
| | | width: 100%; |
| | | bottom: 0; |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | import { Component } from '@angular/core'; |
| | | |
| | | @Component({ |
| | | selector: 'pro-user-layout', |
| | | templateUrl: './user.component.html', |
| | | styleUrls: ['./user.component.less'] |
| | | }) |
| | | export class ProUserLayoutComponent { |
| | | links = [ |
| | | { |
| | | title: '帮助', |
| | | href: '' |
| | | }, |
| | | { |
| | | title: '隐私', |
| | | href: '' |
| | | }, |
| | | { |
| | | title: '条款', |
| | | href: '' |
| | | } |
| | | ]; |
| | | } |
New file |
| | |
| | | <div class="aside-inner"> |
| | | <nz-dropdown nzTrigger="click" class="user-block clearfix"> |
| | | <div nz-dropdown class="user-block-dropdown"> |
| | | <nz-avatar class="avatar" [nzIcon]="'user'" [nzSize]="'large'"></nz-avatar> |
| | | <div class="info"> |
| | | <strong>{{settings.user.name}}</strong> |
| | | <p class="text-truncate">{{settings.user.email}}</p> |
| | | </div> |
| | | </div> |
| | | <ul nz-menu> |
| | | <li nz-menu-item (click)="msgSrv.success('profile')">{{ 'profile' | translate }}</li> |
| | | <li nz-menu-item (click)="msgSrv.success('settings')">{{ 'settings' | translate }}</li> |
| | | <li nz-menu-item (click)="msgSrv.success('logout')">{{ 'logout' | translate }}</li> |
| | | </ul> |
| | | </nz-dropdown> |
| | | <sidebar-nav class="d-block py-lg"></sidebar-nav> |
| | | </div> |
New file |
| | |
| | | import { TestBed, TestModuleMetadata } from '@angular/core/testing'; |
| | | import { setUpTestBed } from '../../../testing/common.spec'; |
| | | |
| | | import { SidebarComponent } from './sidebar.component'; |
| | | |
| | | describe('Layout: Sidebar', () => { |
| | | setUpTestBed(<TestModuleMetadata>{ |
| | | declarations: [ SidebarComponent ] |
| | | }); |
| | | |
| | | it('should create an instance', () => { |
| | | const fixture = TestBed.createComponent(SidebarComponent); |
| | | const comp = fixture.debugElement.componentInstance; |
| | | expect(comp).toBeTruthy(); |
| | | }); |
| | | }); |
New file |
| | |
| | | import { Component } from '@angular/core'; |
| | | import { NzMessageService } from 'ng-zorro-antd'; |
| | | import { SettingsService } from '@delon/theme'; |
| | | |
| | | @Component({ |
| | | selector : 'app-sidebar', |
| | | templateUrl: './sidebar.component.html' |
| | | }) |
| | | export class SidebarComponent { |
| | | constructor(public settings: SettingsService, public msgSrv: NzMessageService) { |
| | | } |
| | | } |
New file |
| | |
| | | import { NgModule } from '@angular/core'; |
| | | import { CommonModule } from '@angular/common'; |
| | | |
| | | @NgModule({ |
| | | imports: [ |
| | | CommonModule |
| | | ], |
| | | declarations: [] |
| | | }) |
| | | export class RoutesModule { } |
New file |
| | |
| | | import { NgModule, ModuleWithProviders } from '@angular/core'; |
| | | import { CommonModule } from '@angular/common'; |
| | | import { ReactiveFormsModule, FormsModule } from '@angular/forms'; |
| | | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; |
| | | import { RouterModule } from '@angular/router'; |
| | | |
| | | import { NgZorroAntdExtraModule } from 'ng-zorro-antd-extra'; |
| | | import { AlainThemeModule } from '@delon/theme'; |
| | | import { AlainABCModule } from '@delon/abc'; |
| | | import { AlainACLModule } from '@delon/acl'; |
| | | |
| | | // third libs |
| | | import { CountdownModule } from 'ngx-countdown'; |
| | | |
| | | // i18n |
| | | import { TranslateModule } from '@ngx-translate/core'; |
| | | import { I18NService } from '@core/i18n/i18n.service'; |
| | | |
| | | // region: zorro modules |
| | | |
| | | import { |
| | | // LoggerModule, |
| | | // NzLocaleModule, |
| | | NzButtonModule, |
| | | NzAlertModule, |
| | | NzBadgeModule, |
| | | // NzCalendarModule, |
| | | NzCascaderModule, |
| | | NzCheckboxModule, |
| | | NzDatePickerModule, |
| | | NzFormModule, |
| | | NzInputModule, |
| | | NzInputNumberModule, |
| | | NzGridModule, |
| | | NzMessageModule, |
| | | NzModalModule, |
| | | NzNotificationModule, |
| | | NzPaginationModule, |
| | | NzPopconfirmModule, |
| | | NzPopoverModule, |
| | | NzRadioModule, |
| | | NzRateModule, |
| | | NzSelectModule, |
| | | NzSpinModule, |
| | | NzSliderModule, |
| | | NzSwitchModule, |
| | | NzProgressModule, |
| | | NzTableModule, |
| | | NzTabsModule, |
| | | NzTagModule, |
| | | NzTimePickerModule, |
| | | NzUtilModule, |
| | | NzStepsModule, |
| | | NzDropDownModule, |
| | | NzMenuModule, |
| | | NzBreadCrumbModule, |
| | | NzLayoutModule, |
| | | NzRootModule, |
| | | NzCarouselModule, |
| | | // NzCardModule, |
| | | NzCollapseModule, |
| | | NzTimelineModule, |
| | | NzToolTipModule, |
| | | // NzBackTopModule, |
| | | // NzAffixModule, |
| | | // NzAnchorModule, |
| | | NzAvatarModule, |
| | | // SERVICES |
| | | NzNotificationService, |
| | | NzMessageService |
| | | } from 'ng-zorro-antd'; |
| | | const ZORROMODULES = [ |
| | | // LoggerModule, |
| | | // NzLocaleModule, |
| | | NzButtonModule, |
| | | NzAlertModule, |
| | | NzBadgeModule, |
| | | // NzCalendarModule, |
| | | NzCascaderModule, |
| | | NzCheckboxModule, |
| | | NzDatePickerModule, |
| | | NzFormModule, |
| | | NzInputModule, |
| | | NzInputNumberModule, |
| | | NzGridModule, |
| | | NzMessageModule, |
| | | NzModalModule, |
| | | NzNotificationModule, |
| | | NzPaginationModule, |
| | | NzPopconfirmModule, |
| | | NzPopoverModule, |
| | | NzRadioModule, |
| | | NzRateModule, |
| | | NzSelectModule, |
| | | NzSpinModule, |
| | | NzSliderModule, |
| | | NzSwitchModule, |
| | | NzProgressModule, |
| | | NzTableModule, |
| | | NzTabsModule, |
| | | NzTagModule, |
| | | NzTimePickerModule, |
| | | NzUtilModule, |
| | | NzStepsModule, |
| | | NzDropDownModule, |
| | | NzMenuModule, |
| | | NzBreadCrumbModule, |
| | | NzLayoutModule, |
| | | NzRootModule, |
| | | NzCarouselModule, |
| | | // NzCardModule, |
| | | NzCollapseModule, |
| | | NzTimelineModule, |
| | | NzToolTipModule, |
| | | // NzBackTopModule, |
| | | // NzAffixModule, |
| | | // NzAnchorModule, |
| | | NzAvatarModule |
| | | ]; |
| | | // endregion |
| | | |
| | | // region: @delon/abc modules |
| | | import { |
| | | AdAvatarListModule, |
| | | AdChartsModule, |
| | | AdCountDownModule, |
| | | AdDescListModule, |
| | | AdEllipsisModule, |
| | | AdErrorCollectModule, |
| | | AdExceptionModule, |
| | | AdFooterToolbarModule, |
| | | AdGlobalFooterModule, |
| | | AdNoticeIconModule, |
| | | AdNumberInfoModule, |
| | | AdProHeaderModule, |
| | | AdResultModule, |
| | | AdSidebarNavModule, |
| | | AdStandardFormRowModule, |
| | | AdTagSelectModule, |
| | | AdTrendModule, |
| | | AdDownFileModule, |
| | | AdImageModule, |
| | | AdUtilsModule |
| | | } from '@delon/abc'; |
| | | const ABCMODULES = [ |
| | | AdAvatarListModule, |
| | | AdChartsModule, |
| | | AdCountDownModule, |
| | | AdDescListModule, |
| | | AdEllipsisModule, |
| | | AdErrorCollectModule, |
| | | AdExceptionModule, |
| | | AdFooterToolbarModule, |
| | | AdGlobalFooterModule, |
| | | AdNoticeIconModule, |
| | | AdNumberInfoModule, |
| | | AdProHeaderModule, |
| | | AdResultModule, |
| | | AdSidebarNavModule, |
| | | AdStandardFormRowModule, |
| | | AdTagSelectModule, |
| | | AdTrendModule, |
| | | AdDownFileModule, |
| | | AdImageModule, |
| | | AdUtilsModule |
| | | ]; |
| | | // endregion |
| | | |
| | | @NgModule({ |
| | | imports: [ |
| | | CommonModule, |
| | | FormsModule, |
| | | RouterModule, |
| | | ReactiveFormsModule, |
| | | HttpClientModule, |
| | | ...ZORROMODULES, |
| | | NgZorroAntdExtraModule.forRoot(), |
| | | AlainThemeModule.forChild(), |
| | | ...ABCMODULES, |
| | | AlainACLModule.forRoot(), |
| | | // third libs |
| | | CountdownModule |
| | | ], |
| | | exports: [ |
| | | CommonModule, |
| | | FormsModule, |
| | | ReactiveFormsModule, |
| | | RouterModule, |
| | | ...ZORROMODULES, |
| | | NgZorroAntdExtraModule, |
| | | AlainThemeModule, |
| | | ...ABCMODULES, |
| | | AlainACLModule, |
| | | // i18n |
| | | TranslateModule, |
| | | // third libs |
| | | CountdownModule |
| | | ] |
| | | }) |
| | | export class SharedModule { |
| | | static forRoot(): ModuleWithProviders { |
| | | return { |
| | | ngModule: SharedModule, |
| | | providers: [ |
| | | // ng-zorro-antd Services |
| | | NzNotificationService, |
| | | NzMessageService |
| | | ] |
| | | }; |
| | | } |
| | | } |
New file |
| | |
| | | { |
| | | "home": "Home", |
| | | "settings": "Settings", |
| | | "profile": "Profile", |
| | | "login": "Login", |
| | | "logout": "Logout", |
| | | "more": "More", |
| | | "full": "Full", |
| | | "top-search-ph": "Search for people, file, photos...", |
| | | "theme": "Theme", |
| | | "theme-switch": "Theme Switch", |
| | | "light": "Light", |
| | | "dark": "Dark", |
| | | "fullscreen": "Fullscreen", |
| | | "fullscreen-exit": "Exit Fullscreen", |
| | | "clear-local-storage": "Clear Local Storage", |
| | | "language": "Language", |
| | | "shortcut": "Shortcut", |
| | | "dashboard": "Dashboard", |
| | | "dashboard_v1": "Dashboard", |
| | | "dashboard_analysis": "Analysis", |
| | | "dashboard_monitor": "Monitor", |
| | | "dashboard_workplace": "Workplace", |
| | | "widgets": "Widgets", |
| | | "main_navigation": "Main Navigation", |
| | | "component": "Component", |
| | | "elements": "Elements", |
| | | "buttons": "Buttons", |
| | | "notification": "Notification", |
| | | "modal": "Modal", |
| | | "sweetalert": "SweetAlert", |
| | | "spin": "Loading Spin", |
| | | "dropdown": "Dropdown", |
| | | "tree-antd": "Tree", |
| | | "sortable": "Sortable", |
| | | "grid": "Grid", |
| | | "gridmasonry": "Grid Masonry", |
| | | "typography": "Typography", |
| | | "iconsfont": "Iconsfont", |
| | | "colors": "Colors", |
| | | "forms": "Forms", |
| | | "extended": "Extended", |
| | | "standard": "Standard", |
| | | "validation": "Validation", |
| | | "upload": "Upload", |
| | | "cropper": "Image Crop", |
| | | "charts": "Charts", |
| | | "tables": "Tables", |
| | | "maps": "Maps", |
| | | "qq": "QQ", |
| | | "baidu": "Baidu", |
| | | "logics": "Logics", |
| | | "guard": "Route Guard", |
| | | "acl": "ACL", |
| | | "downfile": "Down File", |
| | | "report": "Report", |
| | | "relation": "Relation", |
| | | "pages": "Pages", |
| | | "m-login": "Login", |
| | | "m-register": "Register", |
| | | "m-forget": "Forget", |
| | | "m-lock": "Lock", |
| | | "m-maintenance": "Maintenance", |
| | | "extras": "Extras", |
| | | "blog": "Blog", |
| | | "list": "List", |
| | | "comment": "Comment", |
| | | "post": "Post", |
| | | "website": "Web Site (external)", |
| | | "helpcenter": "Help Center", |
| | | "poi": "Poi", |
| | | "pro": "Ant Design Pro", |
| | | "form": "Form Page", |
| | | "step-form": "Step Form", |
| | | "advanced-form": "Advanced Form", |
| | | "pro-list": "List Page", |
| | | "pro-table-list": "Table List", |
| | | "pro-basic-list": "Basic List", |
| | | "pro-card-list": "Card List", |
| | | "pro-cover-card-list": "Cover Card List", |
| | | "pro-filter-card-list": "Filter Card List", |
| | | "pro-search": "Search(Article)", |
| | | "pro-profile": "Profile Page", |
| | | "pro-profile-basic": "Basic", |
| | | "pro-profile-advanced": "Advanced", |
| | | "pro-result": "Result Page", |
| | | "pro-result-success": "Success", |
| | | "pro-result-fail": "Fail", |
| | | "pro-exception": "Exception", |
| | | "pro-user": "Account", |
| | | "pro-login": "Login", |
| | | "pro-register": "Register", |
| | | "pro-register-result": "Register Result" |
| | | } |
New file |
| | |
| | | { |
| | | "home": "主页", |
| | | "settings": "设置", |
| | | "profile": "个人资料", |
| | | "login": "登录", |
| | | "logout": "登出", |
| | | "more": "更多", |
| | | "full": "完整", |
| | | "top-search-ph": "搜索:员工、文件、照片等", |
| | | "theme": "主题", |
| | | "theme-switch": "切换主题", |
| | | "light": "亮", |
| | | "dark": "暗", |
| | | "fullscreen": "全屏", |
| | | "fullscreen-exit": "退出全屏", |
| | | "clear-local-storage": "清理本地缓存", |
| | | "language": "语言", |
| | | "shortcut": "快捷菜单", |
| | | "dashboard": "仪表盘", |
| | | "dashboard_v1": "仪表盘", |
| | | "dashboard_analysis": "分析页", |
| | | "dashboard_monitor": "监控页", |
| | | "dashboard_workplace": "工作台", |
| | | "widgets": "小部件", |
| | | "main_navigation": "主导航", |
| | | "component": "组件", |
| | | "elements": "基础元素", |
| | | "buttons": "按钮", |
| | | "notification": "通知", |
| | | "modal": "模态框", |
| | | "sweetalert": "SweetAlert", |
| | | "spin": "加载中", |
| | | "dropdown": "下拉菜单", |
| | | "tree-antd": "树形控件", |
| | | "sortable": "拖放", |
| | | "grid": "栅格系统", |
| | | "gridmasonry": "瀑布流", |
| | | "typography": "字体排印", |
| | | "iconsfont": "icon图标", |
| | | "colors": "色彩", |
| | | "forms": "表单", |
| | | "standard": "标准", |
| | | "extended": "扩展", |
| | | "validation": "校验", |
| | | "upload": "上传", |
| | | "cropper": "图片裁剪", |
| | | "charts": "图表", |
| | | "tables": "表格", |
| | | "maps": "地图", |
| | | "qq": "QQ", |
| | | "baidu": "百度", |
| | | "logics": "常用逻辑", |
| | | "guard": "路由守卫", |
| | | "acl": "基于角色访问控制", |
| | | "downfile": "下载文件", |
| | | "report": "报表", |
| | | "relation": "全屏关系图", |
| | | "pages": "页面", |
| | | "m-login": "登录页", |
| | | "m-register": "注册页", |
| | | "m-forget": "忘记密码页", |
| | | "m-lock": "锁屏页", |
| | | "m-maintenance": "维护中", |
| | | "extras": "扩展", |
| | | "blog": "博客", |
| | | "list": "列表", |
| | | "comment": "评论", |
| | | "post": "新建", |
| | | "website": "前台(外链)", |
| | | "helpcenter": "帮助中心", |
| | | "poi": "门店", |
| | | "pro": "Ant Design Pro", |
| | | "form": "表单页", |
| | | "step-form": "分步表单", |
| | | "advanced-form": "高级表单", |
| | | "pro-list": "列表页", |
| | | "pro-table-list": "查询表格", |
| | | "pro-basic-list": "标准列表", |
| | | "pro-card-list": "卡片列表", |
| | | "pro-cover-card-list": "搜索列表(项目)", |
| | | "pro-filter-card-list": "搜索列表(应用)", |
| | | "pro-search": "搜索列表(文章)", |
| | | "pro-profile": "详情页", |
| | | "pro-profile-basic": "基础详情页", |
| | | "pro-profile-advanced": "高级详情页", |
| | | "pro-result": "结果", |
| | | "pro-result-success": "成功", |
| | | "pro-result-fail": "失败", |
| | | "pro-exception": "异常", |
| | | "pro-user": "账户", |
| | | "pro-login": "登录", |
| | | "pro-register": "注册", |
| | | "pro-register-result": "注册结果" |
| | | } |
| | |
| | | export const environment = { |
| | | SERVER_URL: `./`, |
| | | production: true |
| | | }; |
| | |
| | | // The list of which env maps to which file can be found in `.angular-cli.json`. |
| | | |
| | | export const environment = { |
| | | SERVER_URL: `./`, |
| | | production: false |
| | | }; |
New file |
| | |
| | | // from: https://github.com/angular/angular/issues/12409 |
| | | |
| | | import { TestBed, async, TestModuleMetadata } from '@angular/core/testing'; |
| | | import { Type, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; |
| | | |
| | | import { CoreModule } from '@core/core.module'; |
| | | import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; |
| | | import { HttpLoaderFactory } from 'app/app.module'; |
| | | import { HttpClient } from '@angular/common/http'; |
| | | import { ALAIN_I18N_TOKEN, ColorsService, SettingsService, MenuService, ScrollService, _HttpClient, ALAIN_THEME_OPTIONS } from '@delon/theme'; |
| | | import { I18NService } from '@core/i18n/i18n.service'; |
| | | import { RouterTestingModule } from '@angular/router/testing'; |
| | | import { SharedModule } from '@shared/shared.module'; |
| | | |
| | | const resetTestingModule = TestBed.resetTestingModule, |
| | | preventAngularFromResetting = () => TestBed.resetTestingModule = () => TestBed; |
| | | const allowAngularToReset = () => TestBed.resetTestingModule = resetTestingModule; |
| | | |
| | | export const setUpTestBed = (moduleDef: TestModuleMetadata) => { |
| | | beforeAll(done => (async () => { |
| | | resetTestingModule(); |
| | | preventAngularFromResetting(); |
| | | |
| | | // region: schemas |
| | | if (!moduleDef.schemas) moduleDef.schemas = []; |
| | | moduleDef.schemas.push(CUSTOM_ELEMENTS_SCHEMA); |
| | | // endregion |
| | | |
| | | // region: imports |
| | | if (!moduleDef.imports) moduleDef.imports = []; |
| | | moduleDef.imports.push(RouterTestingModule); |
| | | moduleDef.imports.push(SharedModule.forRoot()); |
| | | moduleDef.imports.push(TranslateModule.forRoot({ |
| | | loader: { |
| | | provide: TranslateLoader, |
| | | useFactory: (HttpLoaderFactory), |
| | | deps: [HttpClient] |
| | | } |
| | | })); |
| | | // endregion |
| | | |
| | | // region: providers |
| | | if (!moduleDef.providers) moduleDef.providers = []; |
| | | moduleDef.providers.push({ provide: ALAIN_THEME_OPTIONS, useValue: {} }); |
| | | moduleDef.providers.push({ provide: ALAIN_I18N_TOKEN, useClass: I18NService, multi: false }); |
| | | // load full services |
| | | [ SettingsService, MenuService, ScrollService, ColorsService, _HttpClient ].forEach((item: any) => { |
| | | if (moduleDef.providers.includes(item)) return; |
| | | moduleDef.providers.push(item); |
| | | }); |
| | | // endregion |
| | | |
| | | TestBed.configureTestingModule(moduleDef); |
| | | await TestBed.compileComponents(); |
| | | |
| | | // prevent Angular from resetting testing module |
| | | TestBed.resetTestingModule = () => TestBed; |
| | | })().then(done).catch(done.fail)); |
| | | |
| | | afterAll(() => allowAngularToReset()); |
| | | }; |
| | | |
| | | /** |
| | | * get service instance |
| | | */ |
| | | export const getService = <T>(type: Type<T>): T => <T>TestBed.get(type); |
| | |
| | | "outDir": "../out-tsc/app", |
| | | "baseUrl": "./", |
| | | "module": "es2015", |
| | | "types": [] |
| | | "types": [], |
| | | "paths": { |
| | | "@shared": [ "app/shared" ], |
| | | "@shared/*": [ "app/shared/*" ], |
| | | "@core": [ "app/core/" ], |
| | | "@core/*": [ "app/core/*" ] |
| | | } |
| | | }, |
| | | "exclude": [ |
| | | "test.ts", |
| | |
| | | "types": [ |
| | | "jasmine", |
| | | "node" |
| | | ] |
| | | ], |
| | | "paths": { |
| | | "@shared": [ "app/shared" ], |
| | | "@shared/*": [ "app/shared/*" ], |
| | | "@core": [ "app/core/" ], |
| | | "@core/*": [ "app/core/*" ] |
| | | } |
| | | }, |
| | | "files": [ |
| | | "test.ts" |
| | |
| | | "lib": [ |
| | | "es2017", |
| | | "dom" |
| | | ] |
| | | ], |
| | | "baseUrl": "src/", |
| | | "paths": { |
| | | "@shared": [ "app/shared" ], |
| | | "@shared/*": [ "app/shared/*" ], |
| | | "@core": [ "app/core/" ], |
| | | "@core/*": [ "app/core/*" ] |
| | | } |
| | | } |
| | | } |