UNPKG

@yelon/abc

Version:

Common business components of ng-yunzai.

1,292 lines (1,284 loc) 147 kB
import * as i0 from '@angular/core'; import { Injectable, inject, TemplateRef, Input, Directive, Host, ViewContainerRef, ChangeDetectorRef, EventEmitter, Output, ViewEncapsulation, ChangeDetectionStrategy, Component, ElementRef, DestroyRef, input, booleanAttribute, effect, numberAttribute, ViewChild, NgModule, makeEnvironmentProviders, provideEnvironmentInitializer } from '@angular/core'; import * as i4 from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser'; import { ACLService } from '@yelon/acl'; import * as i1 from '@yelon/theme'; import { YUNZAI_I18N_TOKEN, ModalHelper, DrawerHelper, YelonLocaleService, DatePipe, YNPipe } from '@yelon/theme'; import { warn, deepCopy, deepGet, deepMergeKey } from '@yelon/util/other'; import { HttpParams } from '@angular/common/http'; import { map, of, filter, finalize, catchError, throwError, isObservable, lastValueFrom } from 'rxjs'; import * as i2 from '@angular/common'; import { NgTemplateOutlet, DOCUMENT, DecimalPipe } from '@angular/common'; import * as i3 from '@yelon/util/format'; import { XlsxService } from '@yelon/abc/xlsx'; import * as i6 from '@angular/cdk/drag-drop'; import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import * as i1$1 from '@angular/forms'; import { FormsModule } from '@angular/forms'; import { Router } from '@angular/router'; import { CellComponent } from '@yelon/abc/cell'; import { YunzaiConfigService } from '@yelon/util/config'; import { NzBadgeComponent } from 'ng-zorro-antd/badge'; import { NzCheckboxComponent } from 'ng-zorro-antd/checkbox'; import { NzDividerComponent } from 'ng-zorro-antd/divider'; import * as i3$1 from 'ng-zorro-antd/dropdown'; import { NzDropDownModule, NzContextMenuService } from 'ng-zorro-antd/dropdown'; import { NzIconDirective } from 'ng-zorro-antd/icon'; import * as i2$1 from 'ng-zorro-antd/menu'; import { NzMenuModule } from 'ng-zorro-antd/menu'; import { NzPopconfirmDirective } from 'ng-zorro-antd/popconfirm'; import { NzRadioComponent } from 'ng-zorro-antd/radio'; import * as i5 from 'ng-zorro-antd/resizable'; import { NzResizableModule } from 'ng-zorro-antd/resizable'; import * as i4$1 from 'ng-zorro-antd/table'; import { NzTableModule } from 'ng-zorro-antd/table'; import { NzTagComponent } from 'ng-zorro-antd/tag'; import { NzTooltipDirective } from 'ng-zorro-antd/tooltip'; import { NzRangePickerComponent, NzDatePickerComponent } from 'ng-zorro-antd/date-picker'; import { NzInputDirective } from 'ng-zorro-antd/input'; import { NzInputNumberComponent } from 'ng-zorro-antd/input-number'; class STRowSource { titles = {}; rows = {}; add(type, path, ref) { this[type === 'title' ? 'titles' : 'rows'][path] = ref; } getTitle(path) { return this.titles[path]; } getRow(path) { return this.rows[path]; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STRowSource, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STRowSource }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STRowSource, decorators: [{ type: Injectable }] }); class STRowDirective { source = inject(STRowSource, { host: true }); ref = inject(TemplateRef); id; type; ngOnInit() { this.source.add(this.type, this.id, this.ref); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STRowDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.1.3", type: STRowDirective, isStandalone: true, selector: "[st-row]", inputs: { id: ["st-row", "id"], type: "type" }, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STRowDirective, decorators: [{ type: Directive, args: [{ selector: '[st-row]' }] }], propDecorators: { id: [{ type: Input, args: ['st-row'] }], type: [{ type: Input }] } }); class STWidgetRegistry { _widgets = {}; get widgets() { return this._widgets; } register(type, widget) { this._widgets[type] = widget; } has(type) { return Object.prototype.hasOwnProperty.call(this._widgets, type); } get(type) { return this._widgets[type]; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STWidgetRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STWidgetRegistry, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STWidgetRegistry, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class STColumnSource { dom = inject(DomSanitizer); rowSource = inject(STRowSource, { host: true }); acl = inject(ACLService, { optional: true }); i18nSrv = inject(YUNZAI_I18N_TOKEN, { optional: true }); stWidgetRegistry = inject(STWidgetRegistry); cog; setCog(val) { this.cog = val; } fixPop(i, def) { if (i.pop == null || i.pop === false) { i.pop = false; return; } let pop = { okType: 'primary', ...def }; if (typeof i.pop === 'string') { pop.title = i.pop; } else if (typeof i.pop === 'object') { pop = { ...pop, ...i.pop }; } if (typeof pop.condition !== 'function') { pop.condition = () => false; } if (this.i18nSrv) { if (pop.titleI18n) pop.title = this.i18nSrv.fanyi(pop.titleI18n); if (pop.okTextI18n) pop.okText = this.i18nSrv.fanyi(pop.okTextI18n); if (pop.cancelTextI18n) pop.cancelText = this.i18nSrv.fanyi(pop.cancelTextI18n); } i.pop = pop; } btnCoerce(list) { if (!list) return []; const ret = []; const { modal, drawer, pop, btnIcon } = this.cog; for (const item of list) { if (this.acl && item.acl && !this.acl.can(item.acl)) { continue; } if (item.type === 'modal' || item.type === 'static') { if (item.modal == null || item.modal.component == null) { if (typeof ngDevMode === 'undefined' || ngDevMode) { console.warn(`[st] Should specify modal parameter when type is modal or static`); } item.type = 'none'; } else { item.modal = { paramsName: 'record', size: 'lg', ...modal, ...item.modal }; } } if (item.type === 'drawer') { if (item.drawer == null || item.drawer.component == null) { if (typeof ngDevMode === 'undefined' || ngDevMode) { console.warn(`[st] Should specify drawer parameter when type is drawer`); } item.type = 'none'; } else { item.drawer = { paramsName: 'record', size: 'lg', ...drawer, ...item.drawer }; } } if (item.type === 'del' && typeof item.pop === 'undefined') { item.pop = true; } // pop this.fixPop(item, pop); if (typeof item.icon !== 'function') { item.icon = { ...btnIcon, ...(typeof item.icon === 'string' ? { type: item.icon } : item.icon) }; } item.children = item.children && item.children.length > 0 ? this.btnCoerce(item.children) : []; // i18n if (item.i18n && this.i18nSrv) { item.text = this.i18nSrv.fanyi(item.i18n); } ret.push(item); } this.btnCoerceIf(ret); return ret; } btnCoerceIf(list) { for (const item of list) { item.iifBehavior = item.iifBehavior || this.cog.iifBehavior; if (item.children && item.children.length > 0) { this.btnCoerceIf(item.children); } else { item.children = []; } } } fixedCoerce(list, expand) { const countReduce = (a, b) => a + +b.width.toString().replace('px', ''); const expandWidth = expand ? 50 : 0; // left width list .filter(w => w.fixed && w.fixed === 'left' && w.width) .forEach((item, idx) => (item._left = `${list.slice(0, idx).reduce(countReduce, 0) + expandWidth}px`)); // right width list .filter(w => w.fixed && w.fixed === 'right' && w.width) .reverse() .forEach((item, idx) => (item._right = `${idx > 0 ? list.slice(-idx).reduce(countReduce, 0) : 0}px`)); } sortCoerce(item) { const res = this.fixSortCoerce(item); res.reName = { ...this.cog.sortReName, ...res.reName }; return res; } fixSortCoerce(item) { if (typeof item.sort === 'undefined') { return { enabled: false }; } let res = {}; if (typeof item.sort === 'string') { if (item.sort === 'ascend' || item.sort === 'descend') { res.directions = [item.sort, null]; } else { res.key = item.sort; } } else if (typeof item.sort !== 'boolean') { res = item.sort; } else if (typeof item.sort === 'boolean') { res.compare = (a, b) => a[item.indexKey] - b[item.indexKey]; } if (!res.key) { res.key = item.indexKey; } if (!Array.isArray(res.directions)) { res.directions = this.cog.sortDirections ?? ['ascend', 'descend', null]; } res.enabled = true; return res; } filterCoerce(item) { if (item.filter == null) { return null; } let res = item.filter; res.type = res.type || 'default'; res.showOPArea = res.showOPArea !== false; let icon = 'filter'; let iconTheme = 'fill'; let fixMenus = true; let value = undefined; switch (res.type) { case 'keyword': icon = 'search'; iconTheme = 'outline'; break; case 'number': icon = 'search'; iconTheme = 'outline'; res.number = { step: 1, min: -Infinity, max: Infinity, ...res.number }; break; case 'date': icon = 'calendar'; iconTheme = 'outline'; res.date = { range: false, mode: 'date', showToday: true, showNow: false, ...res.date }; break; case 'custom': break; default: fixMenus = false; break; } if (fixMenus && (res.menus == null || res.menus.length === 0)) { res.menus = [{ value }]; } if (res.menus?.length === 0) { return null; } if (typeof res.multiple === 'undefined') { res.multiple = true; } res.confirmText = res.confirmText || this.cog.filterConfirmText; res.clearText = res.clearText || this.cog.filterClearText; res.key = res.key || item.indexKey; res.icon = res.icon || icon; const baseIcon = { type: icon, theme: iconTheme }; if (typeof res.icon === 'string') { res.icon = { ...baseIcon, type: res.icon }; } else { res.icon = { ...baseIcon, ...res.icon }; } this.updateDefault(res); if (this.acl) { res.menus = res.menus?.filter(w => this.acl.can(w.acl)); } return res.menus?.length === 0 ? null : res; } restoreRender(item) { if (item.renderTitle) { item.__renderTitle = typeof item.renderTitle === 'string' ? this.rowSource.getTitle(item.renderTitle) : item.renderTitle; } if (item.render) { item.__render = typeof item.render === 'string' ? this.rowSource.getRow(item.render) : item.render; } } widgetCoerce(item) { if (item.type !== 'widget') return; if (item.widget == null || !this.stWidgetRegistry.has(item.widget.type)) { delete item.type; if (typeof ngDevMode === 'undefined' || ngDevMode) { warn(`st: No widget for type "${item.widget?.type}"`); } } } genHeaders(rootColumns) { const rows = []; const widths = []; const fillRowCells = (columns, colIndex, rowIndex = 0) => { // Init rows rows[rowIndex] = rows[rowIndex] || []; let currentColIndex = colIndex; const colSpans = columns.map(column => { const cell = { column, colStart: currentColIndex, hasSubColumns: false }; let colSpan = 1; const subColumns = column.children; if (Array.isArray(subColumns) && subColumns.length > 0) { colSpan = fillRowCells(subColumns, currentColIndex, rowIndex + 1).reduce((total, count) => total + count, 0); cell.hasSubColumns = true; } else { widths.push(cell.column.width || ''); } if ('colSpan' in column) { colSpan = column.colSpan; } if ('rowSpan' in column) { cell.rowSpan = column.rowSpan; } cell.colSpan = colSpan; cell.colEnd = cell.colStart + colSpan - 1; rows[rowIndex].push(cell); currentColIndex += colSpan; return colSpan; }); return colSpans; }; fillRowCells(rootColumns, 0); // Handle `rowSpan` const rowCount = rows.length; for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) { rows[rowIndex].forEach(cell => { if (!('rowSpan' in cell) && !cell.hasSubColumns) { cell.rowSpan = rowCount - rowIndex; } }); } return { headers: rows, headerWidths: rowCount > 1 ? widths : null }; } cleanCond(list) { const res = []; const copyList = deepCopy(list); for (const item of copyList) { if (typeof item.iif === 'function' && !item.iif(item)) { continue; } if (this.acl && item.acl && !this.acl.can(item.acl)) { continue; } if (Array.isArray(item.children) && item.children.length > 0) { item.children = this.cleanCond(item.children); } res.push(item); } return res; } mergeClass(item) { const builtInClassNames = []; if (item._isTruncate) { builtInClassNames.push('text-truncate'); } const rawClassName = item.className; if (!rawClassName) { const typeClass = { number: 'text-right', currency: 'text-right', date: 'text-center' }[item.type]; if (typeClass) { builtInClassNames.push(typeClass); } item._className = builtInClassNames; return; } const rawClassNameIsArray = Array.isArray(rawClassName); if (!rawClassNameIsArray && typeof rawClassName === 'object') { const objClassNames = rawClassName; builtInClassNames.forEach(key => (objClassNames[key] = true)); item._className = objClassNames; return; } const arrayClassNames = rawClassNameIsArray ? Array.from(rawClassName) : [rawClassName]; arrayClassNames.splice(0, 0, ...builtInClassNames); item._className = [...new Set(arrayClassNames)].filter(w => !!w); } process(list, options) { if (!list || list.length === 0) { return { columns: [], headers: [], headerWidths: null }; } const { noIndex } = this.cog; let checkboxCount = 0; let radioCount = 0; const columns = []; const processItem = (item) => { // index if (item.index) { if (!Array.isArray(item.index)) { item.index = item.index.toString().split('.'); } item.indexKey = item.index.join('.'); } // #region title const tit = (typeof item.title === 'string' ? { text: item.title } : item.title) || {}; if (tit.i18n && this.i18nSrv) { tit.text = this.i18nSrv.fanyi(tit.i18n); } if (tit.text) { tit._text = this.dom.bypassSecurityTrustHtml(tit.text); } item.title = tit; // #endregion // no if (item.type === 'no') { item.noIndex = item.noIndex == null ? noIndex : item.noIndex; } // checkbox if (item.selections == null) { item.selections = []; } if (item.type === 'checkbox') { ++checkboxCount; if (!item.width) { item.width = `${item.selections.length > 0 ? 62 : 50}px`; } } if (this.acl) { item.selections = item.selections.filter(w => this.acl.can(w.acl)); } // radio if (item.type === 'radio') { ++radioCount; item.selections = []; if (!item.width) { item.width = '50px'; } } // cell if (item.cell != null) { item.type = 'cell'; } // types if (item.type === 'yn') { item.yn = { truth: true, ...this.cog.yn, ...item.yn }; } // date if (item.type === 'date') { item.dateFormat = item.dateFormat || this.cog.date?.format; } if ((item.type === 'link' && typeof item.click !== 'function') || (item.type === 'badge' && item.badge == null) || (item.type === 'tag' && item.tag == null) || (item.type === 'enum' && item.enum == null)) { item.type = ''; } item._isTruncate = !!item.width && options.widthMode.strictBehavior === 'truncate' && item.type !== 'img'; // className this.mergeClass(item); // width if (typeof item.width === 'number') { item._width = item.width; item.width = `${item.width}px`; } item._left = false; item._right = false; item.safeType = item.safeType ?? options.safeType; // sorter item._sort = this.sortCoerce(item); // filter item.filter = this.filterCoerce(item); // buttons item.buttons = this.btnCoerce(item.buttons); // widget this.widgetCoerce(item); // restore custom row this.restoreRender(item); // resizable item.resizable = { disabled: true, bounds: 'window', minWidth: 60, maxWidth: 360, preview: true, ...options.resizable, ...(typeof item.resizable === 'boolean' ? { disabled: !item.resizable } : item.resizable) }; return item; }; const processList = (data) => { for (const item of data) { columns.push(processItem(item)); if (Array.isArray(item.children)) { processList(item.children); } } }; const copyList = this.cleanCond(list); processList(copyList); if (checkboxCount > 1) { throw new Error(`[st]: just only one column checkbox`); } if (radioCount > 1) { throw new Error(`[st]: just only one column radio`); } this.fixedCoerce(columns, options.expand); return { columns: columns.filter(w => !Array.isArray(w.children) || w.children.length === 0), ...this.genHeaders(copyList) }; } restoreAllRender(columns) { columns.forEach(i => this.restoreRender(i)); } updateDefault(filter) { if (filter.menus == null) return this; if (filter.type === 'default') { filter.default = filter.menus.findIndex(w => w.checked) !== -1; } else { filter.default = !!filter.menus[0].value; } return this; } cleanFilter(col) { const f = col.filter; f.default = false; if (f.type === 'default') { f.menus.forEach(i => (i.checked = false)); } else { f.menus[0].value = undefined; } return this; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STColumnSource, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STColumnSource }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STColumnSource, decorators: [{ type: Injectable }] }); class STDataSource { http; datePipe; ynPipe; numberPipe; currencySrv; dom; cog; sortTick = 0; constructor(http, datePipe, ynPipe, numberPipe, currencySrv, dom) { this.http = http; this.datePipe = datePipe; this.ynPipe = ynPipe; this.numberPipe = numberPipe; this.currencySrv = currencySrv; this.dom = dom; } setCog(val) { this.cog = val; } process(options) { let data$; let isRemote = false; const { data, res, total, page, pi, ps, paginator, columns, headers } = options; let retTotal; let retPs; let retList; let retPi; let rawData; let showPage = page.show; if (typeof data === 'string') { isRemote = true; data$ = this.getByRemote(data, options).pipe(map(result => { rawData = result; let ret; if (Array.isArray(result)) { ret = result; retTotal = ret.length; retPs = retTotal; showPage = false; } else { const reName = res.reName; if (typeof reName === 'function') { const fnRes = reName(result, { pi, ps, total }); ret = fnRes.list; retTotal = fnRes.total; } else { // list ret = deepGet(result, reName.list, []); if (ret == null || !Array.isArray(ret)) { ret = []; } // total const resultTotal = reName.total && deepGet(result, reName.total, null); retTotal = resultTotal == null ? total || 0 : +resultTotal; } } return deepCopy(ret); })); } else if (data == null || Array.isArray(data)) { data$ = of(data ?? []); } else { // a cold observable data$ = data; } if (!isRemote) { data$ = data$.pipe( // sort map((result) => { rawData = result; let copyResult = deepCopy(result); const sorterFn = this.getSorterFn(headers); if (sorterFn) { copyResult = copyResult.sort(sorterFn); } return copyResult; }), // filter map((result) => { columns .filter(w => w.filter) .forEach(c => { const filter = c.filter; const values = this.getFilteredData(filter); if (values.length === 0) return; const onFilter = filter.fn; if (typeof onFilter !== 'function') { if (typeof ngDevMode === 'undefined' || ngDevMode) { console.warn(`[st] Muse provide the fn function in filter`); } return; } result = result.filter(record => values.some(v => onFilter(v, record))); }); return result; }), // paging map((result) => { if (paginator && page.front) { const maxPageIndex = Math.ceil(result.length / ps); retPi = Math.max(1, pi > maxPageIndex ? maxPageIndex : pi); retTotal = result.length; if (page.show === true) { return result.slice((retPi - 1) * ps, retPi * ps); } } return result; })); } // pre-process if (typeof res.process === 'function') { data$ = data$.pipe(map(result => res.process(result, rawData))); } data$ = data$.pipe(map(result => this.optimizeData({ result, columns, rowClassName: options.rowClassName }))); return data$.pipe(map(result => { retList = result; const realTotal = retTotal || total; const realPs = retPs || ps; return { pi: retPi, ps: retPs, total: retTotal, list: retList, statistical: this.genStatistical(columns, retList, rawData), pageShow: typeof showPage === 'undefined' ? realTotal > realPs : showPage }; })); } get(item, col, idx) { try { const safeHtml = col.safeType === 'safeHtml'; if (col.format) { const formatRes = col.format(item, col, idx) || ''; return { text: formatRes, _text: safeHtml ? this.dom.bypassSecurityTrustHtml(formatRes) : formatRes, org: formatRes, safeType: col.safeType }; } const value = deepGet(item, col.index, col.default); let text = value; let color; let tooltip; switch (col.type) { case 'no': text = this.getNoIndex(item, col, idx); break; case 'img': text = value ? `<img src="${value}" class="img">` : ''; break; case 'number': text = this.numberPipe.transform(value, col.numberDigits); break; case 'currency': text = this.currencySrv.format(value, col.currency?.format); break; case 'date': text = value == null || value === col.default || (typeof value === 'number' && value <= 0) ? col.default : this.datePipe.transform(value, col.dateFormat); break; case 'yn': text = this.ynPipe.transform(value === col.yn.truth, col.yn.yes, col.yn.no, col.yn.mode, false); break; case 'enum': text = col.enum[value]; break; case 'tag': case 'badge': { const data = col.type === 'tag' ? col.tag : col.badge; if (data && data[text]) { const dataItem = data[text]; text = dataItem.text; color = dataItem.color; tooltip = dataItem.tooltip; } else { text = ''; } break; } } if (text == null) text = ''; return { text, _text: safeHtml ? this.dom.bypassSecurityTrustHtml(text) : text, org: value, color, tooltip, safeType: col.safeType, buttons: [] }; } catch (ex) { const text = `INVALID DATA`; console.error(`Failed to get data`, item, col, ex); return { text, _text: text, org: text, buttons: [], safeType: 'text' }; } } getByRemote(url, options) { const { req, page, paginator, pi, ps, singleSort, multiSort, columns, headers } = options; const method = (req.method || 'GET').toUpperCase(); let params = {}; const reName = req.reName; if (paginator) { if (req.type === 'page') { params = { [reName.pi]: page.zeroIndexed ? pi - 1 : pi, [reName.ps]: ps }; } else { params = { [reName.skip]: (pi - 1) * ps, [reName.limit]: ps }; } } params = { ...params, ...req.params, ...this.getReqSortMap(singleSort, multiSort, headers), ...this.getReqFilterMap(columns) }; if (options.req.ignoreParamNull == true) { Object.keys(params).forEach(key => { if (params[key] == null) delete params[key]; }); } let reqOptions = { params, body: req.body, headers: req.headers }; if (method === 'POST' && req.allInBody === true) { reqOptions = { body: { ...req.body, ...params }, headers: req.headers }; } if (typeof req.process === 'function') { reqOptions = req.process(reqOptions); } if (!(reqOptions.params instanceof HttpParams)) { reqOptions.params = new HttpParams({ fromObject: reqOptions.params }); } if (typeof options.customRequest === 'function') { return options.customRequest({ method, url, options: reqOptions }); } return this.http.request(method, url, reqOptions); } getCell(c, item, idx) { const onCellResult = typeof c.onCell === 'function' ? c.onCell(item, idx) : null; const mergedColSpan = onCellResult?.colSpan ?? 1; const mergedRowSpan = onCellResult?.rowSpan ?? 1; return { colSpan: mergedColSpan <= 0 ? null : mergedColSpan, rowSpan: mergedRowSpan <= 0 ? null : mergedRowSpan }; } optimizeData(options) { const { result, columns, rowClassName } = options; for (let i = 0, len = result.length; i < len; i++) { result[i]._values = columns.map(c => { const props = this.getCell(c, result[i], i); if (Array.isArray(c.buttons) && c.buttons.length > 0) { return { buttons: this.genButtons(c.buttons, result[i], c), _text: '', props }; } let cell; if (typeof c.cell === 'function') { cell = c.cell(result[i], c); } return { ...this.get(result[i], c, i), props, cell }; }); result[i]._rowClassName = [rowClassName ? rowClassName(result[i], i) : null, result[i].className] .filter(w => !!w) .join(' '); } return result; } getNoIndex(item, col, idx) { return typeof col.noIndex === 'function' ? col.noIndex(item, col, idx) : col.noIndex + idx; } genButtons(_btns, item, col) { const fn = (btns) => { return deepCopy(btns).filter(btn => { const result = typeof btn.iif === 'function' ? btn.iif(item, btn, col) : true; const isRenderDisabled = btn.iifBehavior === 'disabled'; btn._result = result; btn._disabled = !result && isRenderDisabled; if (btn.children?.length) { btn.children = fn(btn.children); } return result || isRenderDisabled; }); }; const res = fn(_btns); const fnText = (btns) => { for (const btn of btns) { btn._text = typeof btn.text === 'function' ? btn.text(item, btn) : btn.text || ''; btn._className = typeof btn.className === 'function' ? btn.className(item, btn) : btn.className; btn._icon = typeof btn.icon === 'function' ? btn.icon(item, btn) : btn.icon; if (btn.children?.length) { btn.children = fnText(btn.children); } } return btns; }; return this.fixMaxMultiple(fnText(res), col); } fixMaxMultiple(btns, col) { const curCog = col.maxMultipleButton; const btnSize = btns.length; if (curCog == null || btnSize <= 0) return btns; const cog = { ...this.cog.maxMultipleButton, ...(typeof curCog === 'number' ? { count: curCog } : curCog) }; if (cog.count >= btnSize) return btns; const newBtns = btns.slice(0, cog.count); newBtns.push({ _text: cog.text, children: btns.slice(cog.count) }); return newBtns; } // #region sort getValidSort(headers) { return headers.reduce((a, header) => { const ls = header .map(i => i.column) .filter(item => item._sort && item._sort.enabled && item._sort.default) .map(item => item._sort); return a.concat(...ls); }, []); } getSorterFn(headers) { const sortList = this.getValidSort(headers); if (sortList.length === 0) { return; } const sortItem = sortList[0]; if (sortItem.compare === null) { return; } if (typeof sortItem.compare !== 'function') { if (typeof ngDevMode === 'undefined' || ngDevMode) { console.warn(`[st] Muse provide the compare function in sort`); } return; } return (a, b) => { const result = sortItem.compare(a, b); if (result !== 0) { return sortItem.default === 'descend' ? -result : result; } return 0; }; } get nextSortTick() { return ++this.sortTick; } getReqSortMap(singleSort, multiSort, headers) { let ret = {}; const sortList = this.getValidSort(headers); if (multiSort) { const ms = { key: 'sort', separator: '-', nameSeparator: '.', keepEmptyKey: true, arrayParam: false, ...multiSort }; const sortMap = sortList .sort((a, b) => a.tick - b.tick) .map(item => item.key + ms.nameSeparator + ((item.reName || {})[item.default] || item.default)); ret = { [ms.key]: ms.arrayParam ? sortMap : sortMap.join(ms.separator) }; return sortMap.length === 0 && ms.keepEmptyKey === false ? {} : ret; } if (sortList.length === 0) return ret; const mapData = sortList[0]; let sortFiled = mapData.key; let sortValue = (sortList[0].reName || {})[mapData.default] || mapData.default; if (singleSort) { sortValue = sortFiled + (singleSort.nameSeparator || '.') + sortValue; sortFiled = singleSort.key || 'sort'; } ret[sortFiled] = sortValue; return ret; } // #endregion // #region filter getFilteredData(filter) { return filter.type === 'default' ? filter.menus.filter(f => f.checked === true) : filter.menus.slice(0, 1); } getReqFilterMap(columns) { let ret = {}; columns .filter(w => w.filter && w.filter.default === true) .forEach(col => { const filter = col.filter; const values = this.getFilteredData(filter); let obj = {}; if (filter.reName) { obj = filter.reName(filter.menus, col); } else { obj[filter.key] = values.map(i => i.value).join(','); } ret = { ...ret, ...obj }; }); return ret; } // #endregion // #region statistical genStatistical(columns, list, rawData) { const res = {}; columns.forEach((col, index) => { res[col.key || col.indexKey || index] = col.statistical == null ? {} : this.getStatistical(col, index, list, rawData); }); return res; } getStatistical(col, index, list, rawData) { const val = col.statistical; const item = { digits: 2, currency: undefined, ...(typeof val === 'string' ? { type: val } : val) }; let res = { value: 0 }; let currency = false; if (typeof item.type === 'function') { res = item.type(this.getValues(index, list), col, list, rawData); currency = true; } else { switch (item.type) { case 'count': res.value = list.length; break; case 'distinctCount': res.value = this.getValues(index, list).filter((value, idx, self) => self.indexOf(value) === idx).length; break; case 'sum': res.value = this.toFixed(this.getSum(index, list), item.digits); currency = true; break; case 'average': res.value = this.toFixed(this.getSum(index, list) / list.length, item.digits); currency = true; break; case 'max': res.value = Math.max(...this.getValues(index, list)); currency = true; break; case 'min': res.value = Math.min(...this.getValues(index, list)); currency = true; break; } } if (item.currency === true || (item.currency == null && currency === true)) { res.text = this.currencySrv.format(res.value, col.currency?.format); } else { res.text = String(res.value); } return res; } toFixed(val, digits) { if (isNaN(val) || !isFinite(val)) { return 0; } return parseFloat(val.toFixed(digits)); } getValues(index, list) { return list.map(i => i._values[index].org).map(i => (i === '' || i == null ? 0 : i)); } getSum(index, list) { return this.getValues(index, list).reduce((p, i) => (p += parseFloat(String(i))), 0); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STDataSource, deps: [{ token: i1._HttpClient }, { token: i1.DatePipe, host: true }, { token: i1.YNPipe, host: true }, { token: i2.DecimalPipe, host: true }, { token: i3.CurrencyService }, { token: i4.DomSanitizer }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STDataSource }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STDataSource, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i1._HttpClient }, { type: i1.DatePipe, decorators: [{ type: Host }] }, { type: i1.YNPipe, decorators: [{ type: Host }] }, { type: i2.DecimalPipe, decorators: [{ type: Host }] }, { type: i3.CurrencyService }, { type: i4.DomSanitizer }] }); class STExport { xlsxSrv = inject(XlsxService); _stGet(item, col, index, colIndex) { const ret = { t: 's', v: '' }; if (col.format) { ret.v = col.format(item, col, index); } else { const val = item._values ? item._values[colIndex].text : deepGet(item, col.index, ''); ret.v = val; if (val != null) { switch (col.type) { case 'currency': ret.t = 'n'; break; case 'date': // Can't be a empty value, it will cause `#NULL!` // See https://github.com/SheetJS/sheetjs/blob/master/docbits/52_datatype.md if (`${val}`.length > 0) { ret.t = 'd'; // Number Formats: https://github.com/SheetJS/sheetjs/blob/master/docbits/63_numfmt.md ret.z = col.dateFormat; } break; case 'yn': { const yn = col.yn; ret.v = val === yn.truth ? yn.yes : yn.no; break; } } } } ret.v = ret.v ?? ''; return ret; } genSheet(opt) { const sheets = {}; const sheet = (sheets[opt.sheetname || 'Sheet1'] = {}); const dataLen = opt.data.length; const columns = opt.columens; let validColCount = 0; let wpx = false; const invalidFn = (col) => col.exported === false || !col.index || !(!col.buttons || col.buttons.length === 0); for (const [idx, col] of columns.entries()) { if (invalidFn(col)) continue; if (!wpx && col._width != null) wpx = true; ++validColCount; const columnName = this.xlsxSrv.numberToSchema(validColCount); sheet[`${columnName}1`] = { t: 's', v: typeof col.title === 'object' ? col.title.text : col.title }; for (let dataIdx = 0; dataIdx < dataLen; dataIdx++) { sheet[`${columnName}${dataIdx + 2}`] = this._stGet(opt.data[dataIdx], col, dataIdx, idx); } } if (wpx) { // wpx: width in screen pixels https://github.com/SheetJS/sheetjs#column-properties sheet['!cols'] = columns.filter(col => !invalidFn(col)).map(col => ({ wpx: col._width })); } if (validColCount > 0 && dataLen > 0) { sheet['!ref'] = `A1:${this.xlsxSrv.numberToSchema(validColCount)}${dataLen + 1}`; } return sheets; } async export(opt) { const sheets = this.genSheet(opt); return this.xlsxSrv.export({ sheets, filename: opt.filename, callback: opt.callback }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STExport, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STExport }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STExport, decorators: [{ type: Injectable }] }); class STWidgetHostDirective { stWidgetRegistry = inject(STWidgetRegistry); viewContainerRef = inject(ViewContainerRef); record; column; ngOnInit() { const widget = this.column.widget; const componentType = this.stWidgetRegistry.get(widget.type); this.viewContainerRef.clear(); const componentRef = this.viewContainerRef.createComponent(componentType); const { record, column } = this; const data = widget.params ? widget.params({ record, column }) : { record }; Object.keys(data).forEach(key => { componentRef.instance[key] = data[key]; }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STWidgetHostDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.1.3", type: STWidgetHostDirective, isStandalone: true, selector: "[st-widget-host]", inputs: { record: "record", column: "column" }, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STWidgetHostDirective, decorators: [{ type: Directive, args: [{ selector: '[st-widget-host]' }] }], propDecorators: { record: [{ type: Input }], column: [{ type: Input }] } }); class STFilterComponent { cdr = inject(ChangeDetectorRef); visible = false; col; locale = {}; f; n = new EventEmitter(); handle = new EventEmitter(); get icon() { return this.f.icon; } stopPropagation($event) { $event.stopPropagation(); } checkboxChange() { this.n.emit(this.f.menus?.filter(w => w.checked)); } radioChange(item) { this.f.menus.forEach(i => (i.checked = false)); item.checked = !item.checked; this.n.emit(item); } close(result) { if (result != null) this.handle.emit(result); this.visible = false; this.cdr.detectChanges(); } confirm() { this.handle.emit(true); return this; } reset() { this.handle.emit(false); return this; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: STFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.3", type: STFilterComponent, isStandalone: true, selector: "st-filter", inputs: { col: "col", locale: "locale", f: "f" }, outputs: { n: "n", handle: "handle" }, host: { properties: { "class.ant-table-filter-trigger-container": "true", "class.st__filter": "true", "class.ant-table-filter-trigger-container-open": "visible" } }, ngImport: i0, template: ` <span class="ant-table-filter-trigger" [class.active]="visible || f.default" nz-dropdown [nzDropdownMenu]="filterMenu" nzTrigger="click" [nzClickHide]="false" [(nzVisible)]="visible" nzOverlayClassName="st__filter-wrap" (click)="stopPropagation($event)" > <nz-icon [nzType]="icon.type" [nzTheme]="icon.theme!" /> </span> <nz-dropdown-menu #filterMenu="nzDropdownMenu"> <div class="ant-table-filter-dropdown"> @switch (f.type) { @case ('keyword') { <div class="st__filter-keyword"> <input type="text" nz-input [attr.placeholder]="f.placeholder" [(ngModel)]="f.menus![0]!.value" (ngModelChange)="n.emit($event)" (keyup.enter)="confirm()" /> </div> } @case ('number') { <div class="p-sm st__filter-number"> <nz-input-number [(ngModel)]="f.menus![0]!.value" (ngModelChange)="n.emit($event)" [nzMin]="f.number!.min!" [nzMax]="f.number!.max!" [nzStep]="f.number!.step!" [nzPrecision]="f.number!.precision || null"