bfend
Version:
Admin template base on ng-zorro-antd
1,715 lines (1,687 loc) • 203 kB
JavaScript
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}">
© {{ 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()) ?