src/component.ts
providers |
ConfirmationService
|
selector | yca-rest-admin |
styleUrls | component.scss |
templateUrl | ./component.html |
Properties |
|
Methods |
Inputs |
constructor(confirmationService: ConfirmationService, auth: Auth)
|
|||||||||
Defined in src/component.ts:91
|
|||||||||
Parameters :
|
params
|
Component params
Type: |
Defined in src/component.ts:24
|
add |
add()
|
Defined in src/component.ts:219
|
Add a row
Returns :
void
|
addDatetimeFilterRange | ||||||||
addDatetimeFilterRange(col: any)
|
||||||||
Defined in src/component.ts:352
|
||||||||
Add Datetime filter range
Parameters :
Returns :
void
|
cancel |
cancel()
|
Defined in src/component.ts:249
|
Close modal and unselect row
Returns :
void
|
delete |
delete()
|
Defined in src/component.ts:310
|
Delete a row
Returns :
void
|
exportCSV | ||||||||
exportCSV(dt: DataTable)
|
||||||||
Defined in src/component.ts:403
|
||||||||
Export csv
Parameters :
Returns :
void
|
getDisplayCols |
getDisplayCols()
|
Defined in src/component.ts:231
|
Columns to display
Returns :
[]
|
getEditorCols |
getEditorCols()
|
Defined in src/component.ts:242
|
Columns to be edited
Returns :
[]
|
getRefOptions | ||||||||
getRefOptions(col: any)
|
||||||||
Defined in src/component.ts:374
|
||||||||
Get reference options
Parameters :
Returns :
any
|
loadData | ||||||||||||
loadData(options: any, filters: any)
|
||||||||||||
Defined in src/component.ts:119
|
||||||||||||
Query paginated data
Parameters :
Returns :
Promise<void>
|
loadLazy | ||||||||||||
loadLazy(event: LazyLoadEvent, dt: DataTable)
|
||||||||||||
Defined in src/component.ts:145
|
||||||||||||
DataTable lazy load
Parameters :
Returns :
Promise<void>
|
ngOnInit |
ngOnInit()
|
Defined in src/component.ts:100
|
Returns :
void
|
reload |
reload()
|
Defined in src/component.ts:365
|
Reload data
Returns :
void
|
save |
save()
|
Defined in src/component.ts:257
|
Save a row
Returns :
Promise<any>
|
select | ||||||||
select(event: any)
|
||||||||
Defined in src/component.ts:207
|
||||||||
Select a row
Parameters :
Returns :
void
|
Public auth |
auth:
|
Type : Auth
|
Defined in src/component.ts:95
|
blocked |
blocked:
|
Type : boolean
|
Defined in src/component.ts:91
|
Private columnOptions |
columnOptions:
|
Type : SelectItem[]
|
Defined in src/component.ts:49
|
Colum options |
Public confirmationService |
confirmationService:
|
Type : ConfirmationService
|
Defined in src/component.ts:94
|
Public data |
data:
|
Type : IPaginateData
|
Defined in src/component.ts:29
|
Paginated data from query |
Private height |
height:
|
Default value : window.innerHeight
|
Defined in src/component.ts:89
|
Modal Height |
Private lastEvent |
lastEvent:
|
Type : any
|
Defined in src/component.ts:79
|
Last LazyLoadEvent event |
Private lastFilters |
lastFilters:
|
Type : any
|
Defined in src/component.ts:74
|
Last filters used for query |
Private lastOptions |
lastOptions:
|
Type : any
|
Defined in src/component.ts:69
|
Last options used for query |
Public msgs |
msgs:
|
Type : Message[]
|
Default value : []
|
Defined in src/component.ts:44
|
Primeng Message array |
Public now |
now:
|
Type : Date
|
Default value : new Date()
|
Defined in src/component.ts:54
|
Current Date |
Public refs |
refs:
|
Type : literal type
|
Default value : {}
|
Defined in src/component.ts:59
|
References |
Public selected |
selected:
|
Type : any
|
Defined in src/component.ts:34
|
Current selected row |
Private self |
self:
|
Type : RestAdminComponent
|
Defined in src/component.ts:64
|
Point to this |
Public showModal |
showModal:
|
Type : boolean
|
Defined in src/component.ts:39
|
Whether the modal is showing |
Private width |
width:
|
Default value : window.innerWidth
|
Defined in src/component.ts:84
|
Modal width |
import { Component, OnInit, Input } from '@angular/core';
import {
ConfirmationService,
Message,
LazyLoadEvent,
SelectItem,
DataTable,
} from 'primeng/primeng';
import { Auth } from '@yca/auth';
import { fetch } from '@yct/utils';
import * as lodash from 'lodash';
import { IPaginateData, IParams, IParamsCol } from './interfaces';
@Component({
selector: 'yca-rest-admin',
templateUrl: './component.html',
styleUrls: ['./component.scss'],
providers: [ConfirmationService],
})
export class RestAdminComponent implements OnInit {
/**
* Component params
*/
@Input() public params: IParams;
/**
* Paginated data from query
*/
public data: IPaginateData;
/**
* Current selected row
*/
public selected: any;
/**
* Whether the modal is showing
*/
public showModal: boolean;
/**
* Primeng Message array
*/
public msgs: Message[] = [];
/**
* Colum options
*/
private columnOptions: SelectItem[];
/**
* Current Date
*/
public now: Date = new Date();
/**
* References
*/
public refs: { [x: string]: any[] } = {};
/**
* Point to this
*/
private self: RestAdminComponent;
/**
* Last options used for query
*/
private lastOptions: any;
/**
* Last filters used for query
*/
private lastFilters: any;
/**
* Last LazyLoadEvent event
*/
private lastEvent: any;
/**
* Modal width
*/
private width = window.innerWidth;
/**
* Modal Height
*/
private height = window.innerHeight;
blocked: boolean;
constructor(
public confirmationService: ConfirmationService,
public auth: Auth
) {
this.self = this;
}
ngOnInit(): void {
this.columnOptions = [];
for (const col of this.params.cols) {
this.columnOptions.push({ label: col.header, value: col });
}
this.loadData(
JSON.stringify({
limit: 0,
page: 1,
}),
JSON.stringify({})
);
}
/**
* Query paginated data
* @param options {any} query options
* @param filters {any} query filters
*/
async loadData(options: any, filters: any): Promise<void> {
const url: string = `${
this.params.api
}?_options=${options}&_filters=${filters}`;
try {
const res = await fetch('GET', url, null, {
Authorization: `Bearer ${this.auth.jwt}`,
});
if (this.params.renderer) {
this.data = this.params.renderer(res.data);
} else {
this.data = res.data;
}
this.lastFilters = filters;
this.lastOptions = options;
if (this.params.dt) this.params.dt.paginator = true;
} catch (e) {
console.error(e);
}
}
/**
* DataTable lazy load
* @param event {LazyLoadEvent} LazyLoadEvent
* @param dt {DataTable} DataTable
*/
async loadLazy(event: LazyLoadEvent, dt: DataTable): Promise<void> {
this.lastEvent = event;
this.params.dt = dt;
const page: number = Math.floor(event.first / event.rows) + 1;
const limit: number = event.rows === 100 ? 99999999 : event.rows;
const options: any = this.params.globalOptions || {};
options.limit = limit;
options.page = page;
if (event.sortField) {
let str: string = event.sortField;
if (event.sortOrder === -1) str = '-' + str;
options.sort = str;
}
const filters: any = this.params.globalFilters || {};
for (const k of Object.keys(event.filters)) {
switch (event.filters[k].matchMode) {
case 'in':
filters[k] = { $in: event.filters[k].value };
break;
case 'startsWith':
filters[k] = { $regex: '^' + event.filters[k].value };
break;
case 'endsWith':
filters[k] = { $regex: event.filters[k].value + '$' };
break;
case 'equals':
filters[k] = event.filters[k].value;
break;
case 'range':
filters[k] = {
$gte: event.filters[k].value[0],
$lt: event.filters[k].value[1],
};
break;
case 'id':
if (event.filters[k].value && event.filters[k].value.length === 24) {
filters[k] = event.filters[k].value;
} else {
delete filters[k];
}
break;
case 'number':
filters[k] = parseFloat(event.filters[k].value);
break;
case 'custom':
const col = this.params.cols.find(x => x.field === k);
if (col) {
filters[k] = await col.filter.custom(event.filters[k].value);
} else {
delete filters[k];
}
break;
}
}
this.loadData(JSON.stringify(options), JSON.stringify(filters));
}
/**
* Select a row
* @param event {any} $event
*/
select(event: any): void {
this.selected = JSON.parse(JSON.stringify(this.selected));
if (this.params.preEdit) this.params.preEdit(this);
this.width = window.innerWidth;
this.height = window.innerHeight;
this.showModal = true;
if (this.params.onShow) this.params.onShow(this);
}
/**
* Add a row
*/
add(): void {
this.selected = {};
if (this.params.preEdit) this.params.preEdit(this);
this.width = window.innerWidth;
this.height = window.innerHeight;
this.showModal = true;
if (this.params.onShow) this.params.onShow(this);
}
/**
* Columns to display
*/
getDisplayCols(): IParamsCol[] {
return this.params.cols.filter(x => {
if (!x.display) return true;
if (x.display.type === 'hide') return false;
return true;
});
}
/**
* Columns to be edited
*/
getEditorCols(): IParamsCol[] {
return this.params.cols.filter(x => x.editor && !x.editor.hidden);
}
/**
* Close modal and unselect row
*/
cancel(): void {
this.showModal = false;
this.selected = null;
}
/**
* Save a row
*/
async save(): Promise<any> {
let url: string;
let method: string;
if (this.selected._id) {
url = this.params.api + '/' + this.selected._id;
method = 'PATCH';
} else {
url = this.params.api;
method = 'POST';
}
const params: any = {};
for (const key of Object.keys(this.selected)) {
params[key] = this.selected[key];
}
if (this.params.preSave) this.params.preSave(params);
this.blocked = true;
try {
const res = await fetch(
method,
url,
params,
{ Authorization: `Bearer ${this.auth.jwt}` },
!this.params.formdata
);
this.blocked = false;
this.loadData(this.lastOptions, this.lastFilters);
this.cancel();
this.msgs = [
...this.msgs,
{
severity: 'success',
summary: '保存成功',
},
];
return res;
} catch (error) {
this.blocked = false;
console.error(error);
this.msgs = [
...this.msgs,
{
severity: 'error',
summary: '保存失败',
detail: error.data.message,
},
];
return null;
}
}
/**
* Delete a row
*/
delete(): void {
this.confirmationService.confirm({
message: '删除后不能返回,确定删除?',
accept: async () => {
this.blocked = true;
try {
const res = await fetch(
'DELETE',
`${this.params.api}/${this.selected._id}`,
null,
{ Authorization: `Bearer ${this.auth.jwt}` }
);
this.blocked = false;
this.cancel();
this.msgs = [
...this.msgs,
{
severity: 'success',
summary: '删除成功',
},
];
this.loadData(this.lastOptions, this.lastFilters);
} catch (error) {
this.blocked = false;
console.error(error);
this.msgs = [
...this.msgs,
{
severity: 'error',
summary: '删除失败',
detail: error.data.message,
},
];
}
},
});
}
/**
* Add Datetime filter range
* @param col {any} column
*/
addDatetimeFilterRange(col: any): void {
const fr: Date = col.filter.fr;
const to: Date = col.filter.to;
if (!fr || !to) return;
const value: number[] = [fr.getTime(), to.getTime()];
const field: Date = col.field;
const mode: string = col.filter.mode;
this.params.dt.filter(value, field, mode);
}
/**
* Reload data
*/
reload(): void {
this.params.dt.filter(null, 'dummy', 'in');
this.params.dt.paginator = false;
}
/**
* Get reference options
* @param col {any} column
*/
getRefOptions(col: any): any {
if (this.refs[col.field]) return this.refs[col.field];
this.refs[col.field] = [];
fetch('GET', col.editor.ref.path, null, {
Authorization: `Bearer ${this.auth.jwt}`,
})
.then(res => {
this.refs[col.field] = res.data.docs.map(x => {
return {
label:
col.editor.ref.label.constructor === String
? x[col.editor.ref.label]
: col.editor.ref.label(x),
value: x._id,
};
});
this.refs[col.field].unshift({
label: '请选择',
value: null,
});
})
.catch(console.error);
return this.refs[col.field];
}
/**
* Export csv
* @param dt {DataTable} DataTable
*/
exportCSV(dt: DataTable): void {
const header: string = dt.columns.map(x => x.header).join(',');
const rows: any = dt.value
.map(obj => {
return dt.columns
.map(x => {
const col: IParamsCol = this.params.cols.find(
y => y.field === x.field
);
if (col.display && col.display.type === 'enum')
return col.display.map(dt.resolveFieldData(obj, x.field));
return dt.resolveFieldData(obj, x.field);
})
.join(',');
})
.join('\n');
let body: string = [header, rows].join('\n');
const blob: Blob = new Blob([body], {
type: 'text/csv;charset=utf-8;',
});
if (window.navigator.msSaveOrOpenBlob) {
navigator.msSaveOrOpenBlob(blob, this.params.title.list + '.csv');
} else {
const link: HTMLAnchorElement = document.createElement('a');
link.style.display = 'none';
document.body.appendChild(link);
if (link.download !== undefined) {
link.setAttribute('href', URL.createObjectURL(blob));
link.setAttribute('download', this.params.title.list + '.csv');
document.body.appendChild(link);
link.click();
} else {
body = 'data:text/csv;charset=utf-8,' + body;
window.open(encodeURI(body));
}
document.body.removeChild(link);
}
}
}
<div *ngIf="data">
<p-toolbar [style]="{'border-radius': 0, 'border-color': 'black', background: 'black', padding: '16px'}">
<div class="ui-toolbar-group-left">
<div style="font-size: 26px; padding-left: 8px; color: white">{{ params.title.list }}</div>
</div>
<div class="ui-toolbar-group-right">
<button *ngFor="let b of params.customButtons" [class]="b.class" type="button" pButton [icon]="b.icon" (click)="b.handler(self)"
[label]="b.label" [hidden]="b.when && !b.when(self)"></button>
<button *ngIf="!params.hideAdd" class="ui-button-success" type="button" pButton icon="fa-plus" (click)="add()"></button>
<button *ngIf="!params.hideExport" type="button" pButton icon="fa-file-o" iconPos="left" label="CSV" (click)="exportCSV(dt)"></button>
</div>
</p-toolbar>
<p-dataTable #dt [value]="data.docs" scrollable="true" selectionMode="single" [(selection)]="selected" (onRowSelect)="select($event)"
[lazy]="true" [rows]="params.rows || 10" [paginator]="true" [totalRecords]="data.total" (onLazyLoad)="loadLazy($event, dt)">
<p-header>
<div style="display: flex; align-items: center; justify-content: space-between">
<p-multiSelect [options]="columnOptions" [(ngModel)]="params.cols"></p-multiSelect>
<div *ngIf="!params.hideCount" style="padding: 0 8px;white-space: nowrap;">
<i class="fa fa-database" aria-hidden="true"></i> {{ data?.total }}</div>
<div style="flex: 0 0 200px">
<div style="padding-bottom: 8px">
<i class="fa fa-th-list" aria-hidden="true"></i> {{ params.rows === 100 ? '' : params.rows }}</div>
<p-slider [(ngModel)]="params.rows" animate="true" [min]="5" (onSlideEnd)="reload()"></p-slider>
</div>
</div>
</p-header>
<p-column *ngFor="let col of getDisplayCols()" [field]="col.field" [header]="col.header" [style]="col.style" [sortable]="col.sortable"
[filter]="col.filter" [filterMatchMode]="col.filter ? col.filter.mode : null">
<ng-template *ngIf="col.display && col.display.type == 'enum'" let-data="rowData" pTemplate="body">
<span>{{ col.display.map(data[col.field]) }}</span>
</ng-template>
<ng-template *ngIf="col.filter && col.filter.type == 'multiple'" pTemplate="filter">
<p-multiSelect [options]="col.filter.options" [defaultLabel]="col.filter.placeholder" (onChange)="dt.filter($event.value,col.field,col.filter.mode)"
styleClass="ui-column-filter"></p-multiSelect>
</ng-template>
<ng-template *ngIf="col.filter && col.filter.type == 'datetime-range'" pTemplate="filter">
<div style="padding-top: 8px; display: flex; align-items: center; justify-content: center">
<div>
<i class="fa fa-circle" aria-hidden="true"></i>
</div>
<p-calendar [defaultDate]="col.filter.fr" selectOtherMonths="true" monthNavigator="true" yearNavigator="true" [yearRange]="col.filter.yearRange || '2016:2020'"
[(ngModel)]="col.filter.fr" showTime="showTime" hourFormat="24" styleClass="ui-column-filter"></p-calendar>
</div>
<div style="display: flex; align-items: center; justify-content: center">
<div>
<i class="fa fa-arrow-right" aria-hidden="true"></i>
</div>
<p-calendar [defaultDate]="col.filter.to" selectOtherMonths="true" monthNavigator="true" yearNavigator="true" [yearRange]="col.filter.yearRange || '2016:2020'"
[(ngModel)]="col.filter.to" showTime="showTime" hourFormat="24" styleClass="ui-column-filter">
</p-calendar>
</div>
<div style="text-align: center;padding-top: 12px">
<button class="ui-button-secondary" pButton type="button" label="筛选" (click)="addDatetimeFilterRange(col)"></button>
</div>
</ng-template>
</p-column>
</p-dataTable>
<p-dialog [header]="params.title.edit" [(visible)]="showModal" modal="modal" [width]="width" [height]="height" [resizable]="false">
<div *ngIf="selected">
<yca-rest-admin-field [cols]="getEditorCols()" [selected]="selected" [rac]="self"></yca-rest-admin-field>
</div>
<p-footer>
<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix">
<button *ngIf="!params.hideDelete" class="ui-button-danger" style="float: left" type="button" pButton icon="fa-trash" (click)="delete()"></button>
<button *ngIf="!params.hideCancel" class="ui-button-secondary" type="button" pButton icon="fa-close" (click)="cancel()"></button>
<button *ngIf="!params.hideSave" class="ui-button-info" type="button" pButton icon="fa-check" (click)="save()"></button>
<button *ngFor="let b of params.editorButtons" [class]="b.class" type="button" pButton [icon]="b.icon" (click)="b.handler(self)"
[label]="b.label" [hidden]="b.when && !b.when(self)"></button>
</div>
</p-footer>
</p-dialog>
<p-confirmDialog header="操作确认" icon="fa fa-exclamation-triangle" width="425"></p-confirmDialog>
<p-growl [life]="2000" [(value)]="msgs"></p-growl>
</div>
<p-blockUI [blocked]="blocked">
<p-progressSpinner></p-progressSpinner>
</p-blockUI>