UNPKG

bfend

Version:

Admin template base on ng-zorro-antd

1,715 lines (1,687 loc) 203 kB
import { Injectable, InjectionToken, Component, EventEmitter, Output, Input, Directive, ElementRef, Renderer2, Pipe, TemplateRef, Inject, APP_INITIALIZER, NgModule, Injector, defineInjectable, inject, INJECTOR } from '@angular/core'; import { ReplaySubject, Subject, of, merge, throwError, empty, Subscription, BehaviorSubject, animationFrameScheduler } from 'rxjs'; import { scan, tap, map, publishReplay, refCount, distinct, filter, switchMap, delay, combineLatest, finalize, catchError, debounceTime, mapTo, observeOn } from 'rxjs/operators'; import { LocalStorageService, AngularWebStorageModule } from 'angular-web-storage'; import { DOCUMENT, CommonModule } from '@angular/common'; import { Title } from '@angular/platform-browser'; import { JwtHelperService, JWT_OPTIONS, JwtModule } from '@auth0/angular-jwt'; import { JwtHelperService as JwtHelperService$1 } from '@auth0/angular-jwt/src/jwthelper.service'; import { HttpClient, HttpEventType, HttpErrorResponse, HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import 'zone.js'; import { NavigationCancel, NavigationEnd, NavigationError, RouteConfigLoadStart, Router, RouterModule } from '@angular/router'; import { NzMessageService, NzModalRef, NgZorroAntdModule, NZ_I18N, zh_CN } from 'ng-zorro-antd'; import { NgProgress, NgProgressModule } from '@ngx-progressbar/core'; import { FormArray, FormGroup, FormBuilder, Validators, NG_VALIDATORS, FormsModule, ReactiveFormsModule } from '@angular/forms'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ /** * 访问控制服务 */ class BfACLService { constructor() { this.rules = {}; this.updateSubject = new ReplaySubject(); this.setSubject = new Subject(); this.cleanSubject = new Subject(); this.addSubject = new Subject(); this.rules$ = this.updateSubject.pipe(scan((rules, mutaion) => (Object.assign({}, mutaion(rules))), (/** @type {?} */ ({}))), tap(value => this.rules = value), publishReplay(1), refCount()); this.setSubject.pipe(map(value => () => value)) .subscribe(this.updateSubject); this.cleanSubject.pipe(map(() => () => ({}))) .subscribe(this.updateSubject); this.addSubject.pipe(map(this.addMutation.bind(this))) .subscribe(this.updateSubject); } /** * 设置当前用户角色或权限能力(会先清除所有) * @param {?} value * @return {?} */ set(value) { this.setSubject.next(value); } /** * 清除所有权限 * @return {?} */ clean() { this.cleanSubject.next(); } /** * 为当前用户增加角色或权限能力 * @param {?} value * @return {?} */ add(value) { this.addSubject.next(value); } /** * 当前用户是否有对应权限 * 支持 ability.* 通配符 * @param {?} abilityOrAcl * @return {?} */ can(abilityOrAcl) { if (!abilityOrAcl || this.rules.super === true) { return true; } /** @type {?} */ const abilities = typeof abilityOrAcl === 'string' ? [abilityOrAcl] : abilityOrAcl; if (!Array.isArray(abilities)) { /** @type {?} */ const acl = (/** @type {?} */ (abilities)); return (acl.abilities && this.can(acl.abilities)) || (acl.roles && this.is(acl.roles)); } else { if (!this.rules.abilities || this.rules.abilities.length === 0) { return false; } for (const v of abilities) { if (this.rules.abilities.find(a => (v.includes('*') ? a.startsWith(v.substr(0, v.indexOf('*') - 1)) : a === v))) { return true; } } } return false; } /** * 当前用户是否拥有某个角色 * @param {?} role * @return {?} */ is(role) { if (!role || this.rules.super === true) { return true; } if (!this.rules.roles || this.rules.roles.length === 0) { return false; } /** @type {?} */ const roles = typeof role === 'string' ? [role] : role; for (const r of roles) { if (this.rules.roles.indexOf(r) > -1) { return true; } } return false; } /** * @param {?} value * @return {?} */ addMutation(value) { return rules => { if (value.roles && value.roles.length > 0) { rules.roles.push(...value.roles); rules = Object.assign({}, rules, { roles: [...rules.roles, ...value.roles] }); } if (value.abilities && value.abilities.length > 0) { rules.abilities.push(...value.abilities); rules = Object.assign({}, rules, { abilities: [...rules.abilities, ...value.abilities] }); } if (typeof value.super !== 'undefined') { rules.super = value.super; } return rules; }; } } BfACLService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; BfACLService.ctorParameters = () => []; /** @nocollapse */ BfACLService.ngInjectableDef = defineInjectable({ factory: function BfACLService_Factory() { return new BfACLService(); }, token: BfACLService, providedIn: "root" }); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ class BfMenuService { /** * @param {?} aclService */ constructor(aclService) { this.aclService = aclService; this.data = []; this.updateSubject = new ReplaySubject(); this.setSubject = new Subject(); this.toggleOpenSubject = new Subject(); this.currentSubject = new Subject(); this.refreshSubject = new Subject(); // 最新的菜单数据 this.menus$ = this.updateSubject.pipe(scan((menus, mutation) => [...mutation(menus)], []), tap(menus => (this.data = menus)), publishReplay(1), refCount()); this.valid$ = this.menus$.pipe(map(menus => menus && menus.length > 0), filter(state => state), // select true distinct()); // 设置整个菜单数据结构 this.setSubject.pipe(map(this.setMutation.bind(this))) .subscribe(this.updateSubject); // 菜单的打开或关闭 this.toggleOpenSubject.pipe(map(this.toggleOpenMutation.bind(this))) .subscribe(this.updateSubject); // 当前菜单,一般是和 URL 关联起来的 this.currentSubject.pipe(map(this.setCurrentMutation.bind(this))) .subscribe(this.updateSubject); this.refreshSubject.pipe(map(this.refreshMutation.bind(this))) .subscribe(this.updateSubject); // 用户权限改变时,重新计算菜单权限 this.aclService.rules$.subscribe(this.refreshSubject); } /** * @param {?} menus * @param {?} callback * @return {?} */ walk(menus, callback) { /** @type {?} */ const _walk = (list, parentMenu, depth) => { for (const item of list) { callback(item, parentMenu, depth); if (item.children && item.children.length > 0) { _walk(item.children, item, depth + 1); } else { item.children = []; } } }; _walk(menus, null, 0); } /** * @param {?} menus * @return {?} */ prune(menus) { /** @type {?} */ const result = []; for (const item of menus) { if (item.hide) { continue; } if (item.children && item.children.length > 0) { item.children = this.prune(item.children); if ((item._type === 3 || item.group) && item.children.length === 0) { continue; // 父菜单和分组如果没有了孩子,自己也消失 } } result.push(item); } return result; } /** * @param {?} menus * @return {?} */ normalize(menus) { /** @type {?} */ let i = 1; this.walk(menus, (item, parent, depth) => { item.__id = i++; item.__parent = parent; item._depth = depth; // badge if (item.badge) { if (item.badge_dot !== true) { item.badge_dot = false; } if (!item.badge_status) { item.badge_status = 'error'; } } item.hide = item.acl && !this.aclService.can(item.acl); item._type = item.externalLink ? 2 : 1; if (item.children && item.children.length > 0) { item._type = 3; } }); return this.prune(menus); } /** * @param {?} items * @return {?} */ set(items) { this.setSubject.next(items); } /** * @param {?} menu * @return {?} */ setCurrent(menu) { this.currentSubject.next(menu); } /** * @param {?} url * @return {?} */ getByUrl(url) { /** @type {?} */ let findItem = null; this.walk(this.data, item => { item._open = false; if (!item.link) { return; } if (!findItem && new RegExp(`^${url}\\b`).test(item.link)) { findItem = item; } }); return findItem; } /** * @param {?} url * @return {?} */ getPathByUrl(url) { /** @type {?} */ let item = null; this.walk(this.data, (i, parent, depth) => { if (i.link === url) { item = i; } }); /** @type {?} */ const ret = []; if (!item) { return ret; } do { ret.splice(0, 0, item); item = item.__parent; } while (item); return ret; } /** * @param {?} item * @return {?} */ toggleOpen(item) { this.toggleOpenSubject.next(item); } /** * @param {?} menus * @return {?} */ setMutation(menus) { return () => { // 规范化,添加部分计算属性 return this.normalize(menus); }; } /** * @param {?} menu * @return {?} */ toggleOpenMutation(menu) { return menus => { this.walk(menus, m => { if (m.__id !== menu.__id) { m._open = false; return; } menu._open = !menu._open; }); return menus; }; } /** * @param {?} menu * @return {?} */ setCurrentMutation(menu) { return menus => { /** @type {?} */ let p = menu; if (p) { do { p._open = true; p = p.__parent; } while (p); } return menus; }; } /** * @return {?} */ refreshMutation() { return menus => { return this.normalize(menus); }; } } BfMenuService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; BfMenuService.ctorParameters = () => [ { type: BfACLService } ]; /** @nocollapse */ BfMenuService.ngInjectableDef = defineInjectable({ factory: function BfMenuService_Factory() { return new BfMenuService(inject(BfACLService)); }, token: BfMenuService, providedIn: "root" }); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ /** @type {?} */ const BFEND_OPTIONS = new InjectionToken('dfend-app-options'); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ class BfSettingsService { /** * @param {?} storage * @param {?} options */ constructor(storage, options) { this.storage = storage; this.options = options; /** * App 基础信息 */ this._app = { year: new Date().getFullYear() }; /** * 布局设置 */ this._layout = null; } /** * @return {?} */ get app() { return this._app; } /** * @param {?} app * @return {?} */ setApp(app) { this._app = Object.assign({}, this._app, app); } /** * @return {?} */ get layout() { if (this._layout == null) { /** @type {?} */ let layout = null; try { layout = this.storage.get(`${this.options.app_key}-layout`); } catch (err) { } if (layout == null) { this.resetLayout(); } else { this._layout = layout; } } return this._layout; } /** * @param {?} layout * @return {?} */ setLayout(layout) { this._layout = Object.assign({}, this._layout, layout); try { this.storage.set(`${this.options.app_key}-layout`, this._layout); } catch (err) { } } /** * Reset layout * @return {?} */ resetLayout() { this.setLayout({ aside_width: 200, collapsed: false }); } /** * @return {?} */ toggleCollapsed() { this.setLayout({ 'collapsed': !this.layout.collapsed }); } } BfSettingsService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; BfSettingsService.ctorParameters = () => [ { type: LocalStorageService }, { type: undefined, decorators: [{ type: Inject, args: [BFEND_OPTIONS,] }] } ]; /** @nocollapse */ BfSettingsService.ngInjectableDef = defineInjectable({ factory: function BfSettingsService_Factory() { return new BfSettingsService(inject(LocalStorageService), inject(BFEND_OPTIONS)); }, token: BfSettingsService, providedIn: "root" }); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ /** * \@dynamic */ class BfTitleService { /** * @param {?} title * @param {?} doc */ constructor(title, doc) { this.title = title; this.doc = doc; this._prefix = ''; this._suffix = ''; this._separator = ' - '; this._reverse = false; } /** * @param {?} value * @return {?} */ set separator(value) { this._separator = value; } /** * @param {?} value * @return {?} */ set prefix(value) { this._prefix = value; } /** * @param {?} value * @return {?} */ set suffix(value) { this._suffix = value; } /** * @param {?} value * @return {?} */ set reverse(value) { this._reverse = value; } /** * @return {?} */ getFromElement() { /** @type {?} */ const el = this.doc.querySelector('.title__text'); if (el) { return el.firstChild.textContent.trim(); } return ''; } /** * @param {?=} title * @return {?} */ set(title) { if (!title) { title = this.getFromElement(); } if (title && !Array.isArray(title)) { title = [title]; } /** @type {?} */ let newTitles = []; if (this._prefix) { newTitles.push(this._prefix); } if (title && title.length > 0) { newTitles.push(...((/** @type {?} */ (title)))); } if (this._suffix) { newTitles.push(this._suffix); } if (this._reverse) { newTitles = newTitles.reverse(); } this.title.setTitle(newTitles.join(this._separator)); } } BfTitleService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; BfTitleService.ctorParameters = () => [ { type: Title }, { type: Document, decorators: [{ type: Inject, args: [DOCUMENT,] }] } ]; /** @nocollapse */ BfTitleService.ngInjectableDef = defineInjectable({ factory: function BfTitleService_Factory() { return new BfTitleService(inject(Title), inject(DOCUMENT)); }, token: BfTitleService, providedIn: "root" }); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ class BfTokenStorage { /** * @param {?} storage * @param {?} options */ constructor(storage, options) { this.storage = storage; this.options = options; } /** * @return {?} */ get key() { return `${this.options.app_key}-token`; } /** * 保存 * @param {?} token * @return {?} */ set data(token) { this.storage.set(this.key, token); } /** * 获取 * @return {?} */ get data() { try { /** @type {?} */ const data = this.storage.get(this.key); return data && data.access_token ? ((/** @type {?} */ (data))) : null; } catch (err) { return null; } } /** * @return {?} */ get token() { return this.data && this.data.access_token ? this.data.access_token : null; } /** * @return {?} */ clean() { this.storage.remove(this.key); } } BfTokenStorage.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; BfTokenStorage.ctorParameters = () => [ { type: LocalStorageService }, { type: undefined, decorators: [{ type: Inject, args: [BFEND_OPTIONS,] }] } ]; /** @nocollapse */ BfTokenStorage.ngInjectableDef = defineInjectable({ factory: function BfTokenStorage_Factory() { return new BfTokenStorage(inject(LocalStorageService), inject(BFEND_OPTIONS)); }, token: BfTokenStorage, providedIn: "root" }); class BfTokenService { /** * @param {?} injector * @param {?} helper * @param {?} storage */ constructor(injector, helper, storage) { this.injector = injector; this.helper = helper; this.storage = storage; } /** * @return {?} */ get data() { return this.storage.data; } /** * @param {?} data * @return {?} */ set data(data) { this.storage.data = data; } /** * @return {?} */ get token() { return this.storage.token; } /** * @return {?} */ clean() { this.storage.clean(); } /** * @param {?=} token * @return {?} */ isExpired(token) { try { return this.helper.isTokenExpired(token || this.storage.token); } catch (e) { return false; } } /** * @param {?=} token * @return {?} */ isValid(token) { return !this.isExpired(token); } } BfTokenService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; BfTokenService.ctorParameters = () => [ { type: Injector }, { type: JwtHelperService }, { type: BfTokenStorage } ]; /** @nocollapse */ BfTokenService.ngInjectableDef = defineInjectable({ factory: function BfTokenService_Factory() { return new BfTokenService(inject(INJECTOR), inject(JwtHelperService$1), inject(BfTokenStorage)); }, token: BfTokenService, providedIn: "root" }); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ class BfAuthService { /** * @param {?} httpClient * @param {?} aclService * @param {?} tokenService * @param {?} options */ constructor(httpClient, aclService, tokenService, options) { this.httpClient = httpClient; this.aclService = aclService; this.tokenService = tokenService; this.options = options; this.eventSubject = new Subject(); this.event$ = this.eventSubject.asObservable(); this.userSubject = new Subject(); this.user$ = this.userSubject.asObservable().pipe(map(user => (user && user.name && user.name.length > 0 ? user : null)), tap(user => { if (user) { if (user.acl) { this.aclService.set(user.acl); } } else { this.aclService.clean(); this.tokenService.clean(); } }), publishReplay(1), refCount()); this.valid$ = this.user$.pipe(filter(u => u != null)); this.redirectUrl = null; } /** * @return {?} */ isLoggedIn() { return this.tokenService.isValid(); } /** * @param {?} username * @param {?} password * @param {?} remember * @return {?} */ login(username, password, remember) { return this.httpClient.post(`${this.options.api_base_uri}${this.options.url_login}`, { username, password, remember }).pipe(tap(token => { this.tokenService.data = token; this.eventSubject.next('logged-in'); })); } /** * @return {?} */ logout() { this.setUser(null); this.eventSubject.next('logout'); } /** * @param {?} user * @return {?} */ setUser(user) { this.userSubject.next(user); } } BfAuthService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; BfAuthService.ctorParameters = () => [ { type: HttpClient }, { type: BfACLService }, { type: BfTokenService }, { type: undefined, decorators: [{ type: Inject, args: [BFEND_OPTIONS,] }] } ]; /** @nocollapse */ BfAuthService.ngInjectableDef = defineInjectable({ factory: function BfAuthService_Factory() { return new BfAuthService(inject(HttpClient), inject(BfACLService), inject(BfTokenService), inject(BFEND_OPTIONS)); }, token: BfAuthService, providedIn: "root" }); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ /** @type {?} */ const HTTP_MANAGE_ZONE = 'http_manage'; class BfHttpService { /** * @param {?} httpClient * @param {?} options */ constructor(httpClient, options) { this.httpClient = httpClient; this.options = options; this.loadingSubject = new Subject(); this.loading$ = this.loadingSubject.asObservable().pipe(scan(({ diff }, show) => (show ? { diff: diff + 1, show } : { diff: diff - 1, show }), { diff: 0, show: false }), filter(state => state.diff === 0 || (state.diff === 1 && state.show)), switchMap(state => of(state.diff === 1).pipe(delay(10)))); } /** * @param {?} url * @return {?} */ url(url) { return `${this.options.api_base_uri}${url}`; } /** * @param {?} criteria * @return {?} */ search(criteria) { return toSearch(criteria); } /** * @param {?} result * @return {?} */ setLoading(result) { this.loadingSubject.next(result); } /** * @template T * @param {?} cbk * @param {?=} opts * @return {?} */ silent(cbk, opts) { opts = Object.assign((/** @type {?} */ ({ auto_loading: false, handle_error: false })), opts || {}); return Zone.current .fork((/** @type {?} */ ({ name: HTTP_MANAGE_ZONE, properties: opts }))) .run(() => { return cbk(); }); } } BfHttpService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; BfHttpService.ctorParameters = () => [ { type: HttpClient }, { type: undefined, decorators: [{ type: Inject, args: [BFEND_OPTIONS,] }] } ]; /** @nocollapse */ BfHttpService.ngInjectableDef = defineInjectable({ factory: function BfHttpService_Factory() { return new BfHttpService(inject(HttpClient), inject(BFEND_OPTIONS)); }, token: BfHttpService, providedIn: "root" }); /** * @param {?} criteria * @return {?} */ function toSearch(criteria) { /** @type {?} */ const searches = []; for (const k of Object.keys(criteria)) { if (criteria[k] != null) { /** @type {?} */ const key = k.toString().replace(/[:;]/g, ''); /** @type {?} */ let value = criteria[k]; if (Array.isArray(value)) { value = value.map(v => v.toString().replace(/[,;]/g, '')).join(','); } else { value = value.toString().replace(/[;]/g, ''); } searches.push(`${key}:${value}`); } } return searches.join(';'); } /** * @param {?} searches * @return {?} */ function fromSearch(searches) { return searches.split(';').reduce((res, v) => { /** @type {?} */ const i = v.indexOf(':'); if (i === -1) { res[v] = ''; } else if (i > 0) { /** @type {?} */ const key = v.substr(0, i); /** @type {?} */ const value = v.substr(i + 1); if (value.indexOf(',') > -1) { res[key] = value.split(','); } else { res[key] = value; } } return res; }, {}); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ /** * \@dynamic */ class BfAppService { /** * @param {?} injector * @param {?} httpClient * @param {?} httpService * @param {?} menuService * @param {?} settingService * @param {?} aclService * @param {?} titleService * @param {?} authService * @param {?} options */ constructor(injector, httpClient, httpService, menuService, settingService, aclService, titleService, authService, options) { this.injector = injector; this.httpClient = httpClient; this.httpService = httpService; this.menuService = menuService; this.settingService = settingService; this.aclService = aclService; this.titleService = titleService; this.authService = authService; this.options = options; this.isBusySubject = new Subject(); this.loading$ = merge(this.httpService.loading$, this.isBusySubject); } /** * @return {?} */ get user() { return this._user; } /** * 初始化,加载应用基础数据 * @return {?} */ startup() { // only works with promises // https://github.com/angular/angular/issues/15088 return new Promise((resolve, reject) => { // 保存当前用户快照 this.authService.user$.subscribe(user => { this._user = user; }); // 登录或者退出重新获取应用数据 this.authService.event$.subscribe(event => { if (event === 'logged-in' || event === 'logout') { this.httpService.silent(() => this.load().subscribe()); } }); // URL 改变后查找菜单中当前 URL 对应的菜单并将页面标题设置为对应的菜单名 this.injector.get(Router).events.pipe(filter(event => event instanceof NavigationEnd), combineLatest(this.menuService.valid$, (event) => event)).subscribe(event => { /** @type {?} */ const menu = this.menuService.getByUrl(event.url); if (menu) { // 高亮当前菜单 this.menuService.setCurrent(menu); // 根据当前菜单或者页面标题元素设置页面标题 this.titleService.set(menu.text); } else { this.titleService.set(null); } }); // 路由切换行为 this.injector.get(Router).events.subscribe(evt => { if (evt instanceof RouteConfigLoadStart) { this.isBusySubject.next(true); } if (evt instanceof NavigationError || evt instanceof NavigationEnd || evt instanceof NavigationCancel) { this.isBusySubject.next(false); } if (evt instanceof NavigationError) { this.injector .get(NzMessageService) .error(`无法加载${evt.url}路由`, { nzDuration: 1000 * 3 }); } }); // 加载应用基础数据 this.httpService.silent(() => this.load().subscribe(resolve, reject)); }); } /** * @return {?} */ load() { return this.httpClient.get(`${this.options.api_base_uri}${this.options.url_app}`).pipe(tap((res) => { // 设置 APP 配置 this.settingService.setApp(res.app); // 设置当前用户 if (!res.user && this.authService.isLoggedIn()) { // 本地 token 有效, 但是服务器认为无效,退出登录 this.logout(); } this.authService.setUser(res.user); // 初始化菜单 this.menuService.set(res.menus || []); // 设置标题后缀 this.titleService.suffix = res.app.title; })); } /** * @return {?} */ logout() { this.authService.logout(); this.injector.get(Router).navigate(['/login']); } } BfAppService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; BfAppService.ctorParameters = () => [ { type: Injector }, { type: HttpClient }, { type: BfHttpService }, { type: BfMenuService }, { type: BfSettingsService }, { type: BfACLService }, { type: BfTitleService }, { type: BfAuthService }, { type: undefined, decorators: [{ type: Inject, args: [BFEND_OPTIONS,] }] } ]; /** @nocollapse */ BfAppService.ngInjectableDef = defineInjectable({ factory: function BfAppService_Factory() { return new BfAppService(inject(INJECTOR), inject(HttpClient), inject(BfHttpService), inject(BfMenuService), inject(BfSettingsService), inject(BfACLService), inject(BfTitleService), inject(BfAuthService), inject(BFEND_OPTIONS)); }, token: BfAppService, providedIn: "root" }); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ class BfApiError { /** * @param {?} code * @param {?} msg * @param {?=} payload */ constructor(code, msg, payload) { this.code = code; this.msg = msg; this.payload = payload; } /** * @return {?} */ toString() { return this.msg; } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ class BfHttpManagedInterceptor { /** * @param {?} http * @param {?} nzMessage */ constructor(http, nzMessage) { this.http = http; this.nzMessage = nzMessage; } /** * @param {?} req * @param {?} next * @return {?} */ intercept(req, next) { return next.handle(req).pipe(tap(res => { if (res.type === HttpEventType.Sent && this.isAutoLoading(req)) { this.http.setLoading(true); } }), catchError(err => { if (this.isHandleError(req)) { if (err instanceof BfApiError) { this.nzMessage.error(err.toString(), { nzDuration: 3000 }); } else if (err instanceof HttpErrorResponse) { switch (err.status) { case 422: /** @type {?} */ const msg = []; if (err.error && err.error.errors) { for (const i of Object.keys(err.error.errors)) { for (const e of err.error.errors[i]) { msg.push(e); } } } this.nzMessage.error(msg.length > 0 ? msg.join('<br>') : '数据校验失败', { nzDuration: 3000 }); break; default: this.nzMessage.error('网络错误,请稍后再试', { nzDuration: 3000 }); } } } return throwError(err); }), finalize(() => { if (this.isAutoLoading(req)) { // 自动 Loading this.http.setLoading(false); } })); } /** * @param {?} req * @return {?} */ isAutoLoading(req) { return !req.url.startsWith('assets') && (Zone.current.name !== HTTP_MANAGE_ZONE || Zone.current.get('auto_loading')); } /** * @param {?} req * @return {?} */ isHandleError(req) { return !req.url.startsWith('assets') && (Zone.current.name !== HTTP_MANAGE_ZONE || Zone.current.get('handle_error')); } } BfHttpManagedInterceptor.decorators = [ { type: Injectable }, ]; BfHttpManagedInterceptor.ctorParameters = () => [ { type: BfHttpService }, { type: NzMessageService } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ /** * 授权状态拦截器 */ class BfAuthInterceptor { /** * @param {?} router * @param {?} nzMessage * @param {?} app */ constructor(router, nzMessage, app) { this.router = router; this.nzMessage = nzMessage; this.app = app; this.showUnauthenticatedSubject = new Subject(); this.showUnauthenticatedSubject.pipe(debounceTime(500)) .subscribe(() => { this.nzMessage.error('登录信息失效,请重新登录'); this.app.logout(); this.redirectToLogin(); }); } /** * @return {?} */ redirectToLogin() { this.router.navigate(['/login']); } /** * @param {?} req * @param {?} next * @return {?} */ intercept(req, next) { return next.handle(req).pipe(catchError(err => { if (err instanceof HttpErrorResponse) { if (err.status === 401) { // 未登录状态码 this.showUnauthenticatedSubject.next(true); return empty(); // 终止本次请求 } else if (err.status === 403) { // 权限不足 this.nzMessage.error('权限不足'); return empty(); // 终止本次请求 } } // 以错误的形式结束本次请求 return throwError(err); })); } } BfAuthInterceptor.decorators = [ { type: Injectable }, ]; BfAuthInterceptor.ctorParameters = () => [ { type: Router }, { type: NzMessageService }, { type: BfAppService } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ class BfHttpInterceptor { constructor() { } /** * @param {?} req * @param {?} next * @return {?} */ intercept(req, next) { return next.handle(req).pipe(map(res => { if (res.type !== HttpEventType.Response) { return res; } // 处理接口数据返回 if (res && res.body && typeof res.body.code !== 'undefined' && typeof res.body.msg !== 'undefined' && typeof res.body.payload !== 'undefined') { // 将接口错误转为异常 if (res.body.code > 0) { throw new BfApiError(res.body.code, res.body.msg, res.body.payload); } // 将返回结构控制字段去除掉,直接返回数据字段 return res.clone({ body: res.body.payload }); } return res; }), catchError(err => { if (!(err instanceof BfApiError || err instanceof HttpErrorResponse)) { console.error(err); } return throwError(err); })); } } BfHttpInterceptor.decorators = [ { type: Injectable }, ]; BfHttpInterceptor.ctorParameters = () => []; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ class BfLayoutComponent { /** * @param {?} ngProgress * @param {?} app * @param {?} settings */ constructor(ngProgress, app, settings) { this.ngProgress = ngProgress; this.app = app; this.settings = settings; this.sub = new Subscription(); this.bfLogo = 'assets/img/logo-header.svg'; this.bfProgressColor = '#faad14'; this.loading$ = this.app.loading$; } /** * @return {?} */ ngOnInit() { this.sub.add(this.loading$.subscribe(loading => { if (loading) { this.ngProgress.ref().start(); } else { this.ngProgress.ref().complete(); } })); } /** * @return {?} */ ngOnDestroy() { this.sub.unsubscribe(); } } BfLayoutComponent.decorators = [ { type: Component, args: [{ selector: 'bf-layout', template: ` <nz-layout class="bf-layout"> <ng-progress [spinner]="false" [color]="bfProgressColor"></ng-progress> <nz-header> <bf-header [bfNav]="bfNav" [bfLogo]="bfLogo"></bf-header> </nz-header> <nz-layout> <nz-sider [nzWidth]="settings.layout.aside_width" [nzCollapsible]="false" class="bf-aside" [ngClass]="{'bf-aside_collapsed': settings.layout.collapsed}"> <bf-aside></bf-aside> </nz-sider> <nz-layout class="bf-primary"> <nz-content> <router-outlet></router-outlet> </nz-content> <nz-footer> <bf-footer></bf-footer> </nz-footer> </nz-layout> </nz-layout> </nz-layout> ` },] }, ]; BfLayoutComponent.ctorParameters = () => [ { type: NgProgress }, { type: BfAppService }, { type: BfSettingsService } ]; BfLayoutComponent.propDecorators = { bfNav: [{ type: Input }], bfLogo: [{ type: Input }], bfProgressColor: [{ type: Input }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ class BfFullScreenComponent { } BfFullScreenComponent.decorators = [ { type: Component, args: [{ selector: 'bf-layout-fullscreen', template: ` <router-outlet></router-outlet>` },] }, ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ class BfHeaderComponent { /** * @param {?} settings */ constructor(settings) { this.settings = settings; } /** * @return {?} */ toggleAside() { this.settings.toggleCollapsed(); } } BfHeaderComponent.decorators = [ { type: Component, args: [{ selector: 'bf-header', template: ` <div class="bf-header"> <div class="bf-header-title"> <img class="bf-header-title__logo" [src]="bfLogo" alt="logo"> <h1 class="bf-header-title_text">{{settings.app.title}}</h1> </div> <div class="bf-nav"> <ul class="bf-nav__menu"> <li class="bf-nav__menu-item" (click)="toggleAside()"> <i nz-icon class="bf-nav__menu-icon" [type]="settings.layout.collapsed ? 'menu-unfold' : 'menu-fold'"></i> </li> </ul> <ng-template *ngIf="bfNav" [ngTemplateOutlet]="bfNav"></ng-template> </div> </div> ` },] }, ]; BfHeaderComponent.ctorParameters = () => [ { type: BfSettingsService } ]; BfHeaderComponent.propDecorators = { bfNav: [{ type: Input }], bfLogo: [{ type: Input }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ class BfAsideComponent { /** * @param {?} menuService */ constructor(menuService) { this.menuService = menuService; } /** * @return {?} */ ngOnInit() { this.menus$ = this.menuService.menus$; } /** * @param {?} menu * @return {?} */ toggleOpen(menu) { this.menuService.toggleOpen(menu); } } BfAsideComponent.decorators = [ { type: Component, args: [{ selector: 'bf-aside', template: ` <ng-container *ngFor="let m of menus$ | async"> <ng-container *ngIf="m.group"> <section class="bf-aside-group"> <h2 class="bf-aside-group__title">{{m.text}}</h2> <ng-container *ngFor="let m of m.children"> <ng-container *ngTemplateOutlet="tplMenu; context: {menu: m}"></ng-container> </ng-container> </section> </ng-container> <ng-container *ngIf="!m.group"> <ng-container *ngTemplateOutlet="tplMenu; context: {menu: m}"></ng-container> </ng-container> </ng-container> <ng-template #tplMenu let-menu="menu"> <ul class="bf-aside-menu"> <li class="bf-aside-menu__item" routerLinkActive="bf-aside-menu__item_active" [ngClass]="{ 'bf-aside-menu__item_has-children': menu._type == 3, 'bf-aside-menu__item_leaf': menu._type !== 3, 'bf-aside-menu__item_explosed': menu._open && menu._type === 3, 'bf-aside-menu__item_hidden': menu.hide }" > <div class="bf-aside-menu__title" (click)="toggleOpen(menu)"> <i class="bf-aside-menu__icon {{menu.icon}}"></i> <!-- link --> <a class="bf-aside-menu__text" *ngIf="menu._type === 1" [routerLink]="menu.link" target="{{ menu.target }}"> {{ menu.text }} </a> <!-- external link --> <a class="bf-aside-menu__text" *ngIf="menu._type === 2" href="{{ menu.externalLink }}" target="{{ menu.target }}"> {{menu.text }} </a> <!-- has children --> <span class="bf-aside-menu__text" *ngIf="menu._type === 3"> {{ menu.text }} </span> <nz-badge *ngIf="menu.badge" [nzCount]="menu.badge" [nzDot]="menu.badge_dot || false"></nz-badge> <i nz-icon class="bf-aside-menu__arrow" *ngIf="menu._type == 3" [ngClass]="{'bf-aside-menu__arrow_up': menu._open}" type="down"></i> </div> <ul class="bf-aside-menu" *ngIf="menu._type === 3"> <li class="bf-aside-menu__item menu__item_leaf" *ngFor="let m of menu.children" routerLinkActive="bf-aside-menu__item_active" > <div class="bf-aside-menu__title"> <!-- link --> <a class="bf-aside-menu__text" *ngIf="m._type === 1" [routerLink]="m.link" target="{{ m.target }}"> {{ m.text }} </a> <!-- external link --> <a class="bf-aside-menu__text" *ngIf="m._type === 2" href="{{ m.externalLink }}" target="{{ m.target }}"> {{m.text }} </a> <!-- has children --> <span class="bf-aside-menu__text" *ngIf="m._type === 3"> {{ m.text }} </span> <nz-badge *ngIf="m.badge" [nzCount]="m.badge" [nzDot]="m.badge_dot || false"></nz-badge> </div> </li> </ul> </li> </ul> </ng-template> ` },] }, ]; BfAsideComponent.ctorParameters = () => [ { type: BfMenuService } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ class BfFooterComponent { /** * @param {?} settings */ constructor(settings) { this.settings = settings; this.padding = false; } } BfFooterComponent.decorators = [ { type: Component, args: [{ selector: 'bf-footer', template: ` <p class="bf-footer" [ngClass]="{'bf-footer_padding': padding}"> &copy; {{ settings.app.year }} {{ settings.app.name }} 版权所有 <br> {{ settings.app.description }} </p> ` },] }, ]; BfFooterComponent.ctorParameters = () => [ { type: BfSettingsService } ]; BfFooterComponent.propDecorators = { padding: [{ type: Input }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ class BfPageComponent { constructor() { this.isDescriptionString = true; } /** * @param {?} v * @return {?} */ set description(v) { this.isDescriptionString = !(v instanceof TemplateRef); this._description = v; } /** * @return {?} */ get description() { return this._description; } } BfPageComponent.decorators = [ { type: Component, args: [{ selector: 'bf-page', template: ` <ng-template #descTemplate> <ng-template [ngTemplateOutlet]="description"></ng-template> </ng-template> <div class="bf-page-title"> <div class="bf-page-title__breadcrumb" *ngIf="breadcrumb"> <ng-container *ngTemplateOutlet="breadcrumb"></ng-container> </div> <div class="bf-page-title__text">{{caption}}</div> <div class="bf-page-title__description" *ngIf="description"> <ng-container *ngIf="isDescriptionString; else descTemplate"> {{description}} </ng-container> </div> <ng-container *ngTemplateOutlet="extra"></ng-container> </div> <div class="bf-page-content"> <ng-content></ng-content> </div> ` },] }, ]; BfPageComponent.propDecorators = { caption: [{ type: Input }], extra: [{ type: Input }], breadcrumb: [{ type: Input }], description: [{ type: Input }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ class BfModalProxyComponent { /** * @param {?} modalRef */ constructor(modalRef) { this.modalRef = modalRef; this.loading = false; this.submitting = false; this.submit = new EventEmitter(); } /** * @return {?} */ ngOnInit() { this.modal = this.modalRef.getInstance(); ((/** @type {?} */ (this.modal.nzOnOk))).subscribe(() => this.submit.emit()); } /** * @param {?} changes * @return {?} */ ngOnChanges(changes) { if ('submitting' in changes && this.modal) { /** @type {?} */ const c = changes['submitting']; this.modal.nzOkLoading = c.currentValue; } } /** * @param {?=} data * @return {?} */ success(data = 'ok') { this.modalRef.destroy(data); } /** * @return {?} */ close() { this.modalRef.destroy(); } } BfModalProxyComponent.decorators = [ { type: Component, args: [{ selector: 'bf-modal-proxy', template: ` <nz-spin [nzSpinning]="loading"> <ng-content></ng-content> </nz-spin> `, },] }, ]; BfModalProxyComponent.ctorParameters = () => [ { type: NzModalRef } ]; BfModalProxyComponent.propDecorators = { loading: [{ type: Input }], submitting: [{ type: Input }], submit: [{ type: Output }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ class BfStateTextComponent { constructor() { } /** * @return {?} */ ngOnInit() { } } BfStateTextComponent.decorators = [ { type: Component, args: [{ selector: 'bf-state-text', template: ` <ng-container [ngSwitch]="value"> <nz-tag class="cursor-default" *ngSwitchCase="0" nzColor="green">正常</nz-tag> <nz-tag class="cursor-default" *ngSwitchCase="99" nzColor="#ccc">禁用</nz-tag> <span class="cursor-default" *ngSwitchDefault>{{value}}</span> </ng-container> `, styles: [] },] }, ]; BfStateTextComponent.ctorParameters = () => []; BfStateTextComponent.propDecorators = { value: [{ type: Input }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,uselessCode} checked by tsc */ /** * @param {?} form * @return {?} */ function touchForm(form) { if (form instanceof FormGroup) { for (const i of Object.keys(form.controls)) { touchForm(form.controls[i]); } } else if (form instanceof FormArray) { for (const c of form.controls) { touchForm(c); } } else { form.markAsDirty(); form.updateValueAndValidity(); } } /** * @param {?} field * @return {?} */ function confirmationValidator(field) { return (c) => { if (!c.parent) { return null; } return c.value === c.parent.get(field).value ? null : { confirmation: c.value }; }; } /** @type {?} */ const datetimeRexp = /^\d{4}-(0\d|1[0-2])-([0-2]\d|3[01]) (0\d|1\d|2[0-3]):([0-5]\d):([0-5]\d)$/; /** * @return {?} */ function datetimeValidator() { return (c) => { return !c.value || datetimeRexp.test(c.value.toString().trim()) && !isNaN(new Date(c.value.toString()).getTime()) ?