import { IMDCtrlController, IMDCtrlStore } from '@/core'; import { ICodeListItem, IContext, ICtrlActionResult, IHttpResponse, IMDCtrlAbility, IMDCtrlControllerParams, IMDCtrlModel, IParam, MDCtrlActionType, } from '@/core/interface'; import { DataTypeUtil, deepCopy, ExportExcelUtil, typeOf } from '../../utils'; import { DECtrlController } from './de-ctrl-controller'; /** * 部件控制器 * * @export * @class CtrlController * @implements {IMDCtrlController} */ export class MDCtrlController< T, S extends IMDCtrlStore, A extends IMDCtrlAbility > extends DECtrlController implements IMDCtrlController { /** * 多数据部件模型 * * @protected * @type {IMDCtrlModel} * @memberof MDCtrlController */ protected declare model: IMDCtrlModel; /** * 是否单选 * * @protected * @type {boolean} * @memberof MDCtrlController */ protected singleSelect!: boolean; /** * 默认选择第一项 * * @protected * @type {boolean} * @memberof MDCtrlController */ protected selectFirstDefault = false; /** * 处理部件初始化 * * @protected * @param { IMDCtrlControllerParams} params * @memberof MDCtrlController */ protected processCtrlInit(params: IMDCtrlControllerParams) { super.processCtrlInit(params); this.singleSelect = params.singleSelect === true; this.selectFirstDefault = params.selectFirstDefault === true; Object.assign(this.store, { curPage: 1, limit: params.model.paging?.defaultPageSize ? params.model.paging.defaultPageSize : 20, totalRecord: 20, data: [], selections: [], sort: '', }); } /** * 获取数据 * * @return {*} {IParam[]} * @memberof MDCtrlController */ public getData(): IParam[] { // 分组列不参与数据逻辑交互 const data: Array = this.store.selections.filter( (select: IParam) => !select.isGroupItem ); const exposeData: Array = []; this.computeExposeData(data, exposeData); return exposeData; } /** * 处理加载数据变化 * * @protected * @param {IParam[]} data * @memberof MDCtrlController */ protected handleLoadDataChange(data: IParam[]) { this.store.data = data; } /** * 多数据部件数据加载 * * @param {IParam} [opts={}] * @param {boolean} [pageReset=false] * @return {*} {Promise} * @memberof MDCtrlController */ public async load(opts: IParam = {}): Promise { const { context, viewParams, limit } = this.store; const { fetchAction } = this.actions; if (!fetchAction) { console.warn(App.ts("widget.md.notconfig.fetchaction","未配置加载行为")); return { ok: false, data: this.getData(), rowData: { status: 500 } }; } if (opts && opts.srfpagereset) { this.store.curPage = 1; delete opts.srfpagereset; } const { paging, noSort, sortDir, sortField, name } = this.model; const arg: any = {}; const queryParams: IParam = {}; // 开启分页 if (paging) { Object.assign(queryParams, { page: this.store.curPage - 1, size: limit }); } // 排序 if (!noSort && !opts.srfsortreset) { if (this.store.sort) { Object.assign(queryParams, { sort: this.store.sort }); } else { if (sortField) { Object.assign(queryParams, { sort: `${sortField},${sortDir ? sortDir : 'asc'}`, }); } } } delete opts.srfsortreset; const parentData: IParam = {}; this.emit('beforeLoad', [parentData]); // 视图查询参数 if (parentData && Object.keys(parentData).length > 0) { Object.assign(queryParams, parentData); } // 额外查询参数 if (opts && Object.keys(opts).length > 0) { Object.assign(queryParams, opts); } const tempViewParams = Object.assign(deepCopy(viewParams), queryParams); Object.assign(arg, { viewParams: tempViewParams }); const tempContext = deepCopy(context); const res = await this.beforeAsyncAction('load', tempContext, arg); if(!res.ok){ return { ok: false, data: this.getData(), rowData: { status: 500 } }; } // 合并界面逻辑执行参数 if(res.data && res.data.length > 0){ Object.assign(arg,res.data[0]); } // TODO 行为前 try { const response: IHttpResponse = await this.ctrlService.search( fetchAction, tempContext, arg ); if (!response.success) { await this.afterAsyncAction('load', response); return { ok: false, data: this.getData(), rowData: response, }; } const res = await this.afterAsyncAction('load', response); if(!res.ok){ return { ok: false, data: this.getData(), rowData: { status: 500 } }; } const { total } = response as IParam; if (total || total === 0) { this.store.totalRecord = total; } this.handleLoadDataChange(response.data); this.emit('load', response.data); return { ok: true, data: this.getData(), rowData: response, }; } catch (error: any) { await this.afterAsyncAction('load', error); return { ok: false, data: this.getData(), rowData: error, }; } } /** * 处理分组 * * @protected * @return {*} * @memberof MDCtrlController */ protected handleGroup() { const { enableGroup, groupMode } = this.model; if (!enableGroup) { return; } if (groupMode === 'AUTO') { this.handleAutoGroup(); } else if (groupMode === 'CODELIST') { this.handleCodeListGroup(); } } /** * 自动分组 * * @protected * @memberof MDCtrlController */ protected async handleAutoGroup() { const { data } = this.store; if (data.length === 0) { return; } // 分组属性标识 const field = this.model.groupField as string; // 分组属性代码表 const fieldCodeList = this.model.groupFieldCodeListTag; // 分组列标识 const dataIndex = this.model.groupDataIndex as string; const resultMap: Map = new Map(); const codeItems: IParam[] = []; if (fieldCodeList) { const result = await App.getAppCodeListHelper().getCodeListItems( fieldCodeList ); if (result.ok) { codeItems.push(...result.data); } } data.forEach((item: IParam) => { const items = resultMap.get(item[field]) || []; items.push(item); resultMap.set(item[field], items); }); const resultItems: IParam[] = []; resultMap.forEach((value: IParam[], key: string) => { const item = codeItems.length > 0 ? codeItems.find((item: IParam) => item.value === key) : { text: key }; resultItems.push({ key: `group-item__${key}`, srfkey: `group-item__${key}`, srfmajortext: item ? item.text : App.ts("widget.md.other","其他"), [dataIndex]: item ? item.text : App.ts("widget.md.other","其他"), isGroupItem: true, children: value, }); }); this.store.data = resultItems; } /** * 代码表分组 * * @protected * @return {*} * @memberof MDCtrlController */ protected async handleCodeListGroup() { // 分组代码表标识 const codeListTag = this.model.groupCodeListTag as string; // 分组属性标识 const field = this.model.groupField as string; // 分组列标识 const dataIndex = this.model.groupDataIndex as string; if (!codeListTag) { return; } const { context, viewParams } = this.store; const result = await App.getAppCodeListHelper().getCodeListItems( codeListTag, context, viewParams ); if (!result.ok || result.data.length === 0) { return; } const { data } = this.store; const keys: (string | number)[] = result.data.map( (d: ICodeListItem) => d.value ); const resultItems: IParam[] = []; result.data.forEach((item: ICodeListItem) => { const items: IParam[] = data.filter( (d: IParam) => d[field] === item.value ); resultItems.push({ key: `group-item__${item.value}`, srfkey: `group-item__${item.value}`, srfmajortext: item.text, value: item.value, [dataIndex]: item.text, isGroupItem: true, children: items, }); }); const otherItems = data.filter( (d: IParam) => keys.indexOf(d[field]) === -1 ); if (otherItems.length > 0) { resultItems.push({ key: `group-other-item`, srfkey: `group-other-item`, srfmajortext: App.ts("widget.md.other","其他"), [dataIndex]: App.ts("widget.md.other","其他"), isGroupItem: true, children: otherItems, }); } this.store.data = resultItems; } /** * 设置默认选中 * * @protected * @memberof MDCtrlController */ protected handleDefaultSelect() { const { viewParams } = this.store; // 默认选中第一项 if (this.selectFirstDefault) { const { data } = this.store; if (data && data.length > 0) { this.store.selections = [data[0]]; this.emit('selectionChange', this.getData()); } } // 存在选中数据 if ( viewParams && viewParams.selectedData && viewParams.selectedData.length > 0 ) { const selections: IParam[] = []; viewParams.selectedData.forEach((select: IParam) => { if (select.srfkey) { const item = this.store.data.find( (i: IParam) => i.srfkey === select.srfkey ); if (item) { selections.push(item); } } }); if (selections.length > 0) { this.store.selections = this.singleSelect ? [selections[0]] : selections; this.emit('selectionChange', this.getData()); } } } /** * 处理排序变化 * * @param {string} sortField * @param {('asc' | 'desc' | '')} sortDir * @memberof MDCtrlController */ public handleSortChange(sortField: string, sortDir: 'asc' | 'desc' | '') { // 无排序方向时重置排序 this.store.sort = `${sortField},${sortDir}`; this.load({ srfsortreset: sortDir === '' }); } /** * 处理当前页码变化 * * @param {number} currentPage * @memberof MDCtrlController */ public handlePageChange(currentPage: number) { this.store.curPage = currentPage; this.load(); } /** * 处理分页数变化 * * @param {number} pageSize * @memberof MDCtrlController */ public handlePageSizeChange(pageSize: number) { this.store.limit = pageSize; this.load(); } /** * 处理选中数据改变 * * @param {IParam[]} selections * @memberof MDCtrlController */ public handleSelectionChange(selections: IParam[]) {} /** * 选中全部 * * @memberof MDCtrlController */ public selectAll(): void {} /** * 选中数据 * * @param {IParam[]} items 需要选中的数据 * @param {boolean} [reverse] 是否反选 * @memberof MDCtrlController */ public selectItems(items: IParam[], reverse?: boolean): void {} /** * 处理导出 * * @protected * @param {IParam[]} source * @memberof MDCtrlController */ protected async handleExport(source: IParam[]) { const tHeader: string[] = []; const filterVal: string[] = []; const exportItems = this.model.dataExport ? this.model.dataExport.exportItems : []; // 数据类型 const colTypes: string[] = []; // 值格式化 const colFormat: string[] = []; const getType = (type = ''): string => { // 目前仅支持识别数值、日期,其余类型视为字符串 if (type) { if (DataTypeUtil.isDate(type)) { return 'date'; } else if (DataTypeUtil.isNumber(type)) { return 'number'; } else { return 'string'; } } return 'string'; }; exportItems.forEach((item: IParam) => { if (item.show) { item.label ? tHeader.push(item.label) : '', filterVal.push(item.name.toLowerCase()); colTypes.push(getType(item.dataType)); if (item.valueFormat) { colFormat.push(item.valueFormat); } else { colFormat.push(''); } } }); const data = await this.formatExcelData(exportItems, filterVal, source); const excel = await ExportExcelUtil.getInstance().getExcel(); excel.export_json_to_excel({ header: tHeader, //表头 必填 data, //具体数据 必填 filename: this.model.dataExport ? this.model.dataExport.fileName : '', //非必填 autoWidth: true, //非必填 bookType: 'xlsx', //非必填 colTypes, colFormat, }); } /** * 格式化导出数据 * * @private * @param {IParam[]} exportItems * @param {string[]} filter * @param {IParam[]} source * @return {*} * @memberof MDCtrlController */ private async formatExcelData( exportItems: IParam[], filter: string[], source: IParam[] ) { for (const item of exportItems) { if (item.codeListTag) { const result = await App.getAppCodeListHelper().getCodeListItems( item.codeListTag ); if (result.ok) { source.forEach((row: IParam, index: number) => { row[item.name] = this.parseCodeListValue(result.data, row[item.name], item) }); } } } return source.map((v: any) => filter.map((j: any) => v[j])); } /** * 解析导出项代码表值 * * @private * @param {IParam[]} items 代码表值 * @param {*} value 值 * @param {IParam} exportItem 导出项 * @return {*} {string} * @memberof MDCtrlController */ private parseCodeListValue(items: IParam[], value: any, exportItem: IParam): string { if (value || value === 0 || value === true) { let result: any[] = []; const values = Object.is(typeOf(value), 'string') ? [...value.split(exportItem.valueSeparator || ',')] : [value]; values.forEach((item: any) => { const codeListItem: IParam | undefined = items.find((c: IParam) => c.value == value); if (codeListItem) { result.push(codeListItem.text) } }) if (result.length > 0) { return result.join(exportItem.valueSeparator || ','); } else { return '' } } else { return '' } } /** * 导出Excel * * @param {IParam} [opt={}] * @return {*} {Promise} * @memberof MDCtrlController */ public async exportExcel(opt: IParam = {}): Promise { if (!this.actions.fetchAction) { return { ok: false, data: this.getData(), rowData: {} }; } const { dataExport } = this.model; if (!dataExport) { return { ok: false, data: this.getData(), rowData: { status: 500 }, }; } if (dataExport.enableBackend && !dataExport.enableFront) { return await this.exportExcelByBackend(opt); } const { limit, curPage } = this.store; const queryParams: IParam = {}; const maxCount = import.meta.env.VITE_APP_EXPORTMAXROWCOUNT || 9999; if (Object.is(opt.type, 'maxRowCount')) { Object.assign(queryParams, { page: 0, size: dataExport.maxRowCount ? dataExport.maxRowCount : maxCount, }); } else if (Object.is(opt.type, 'activatedPage')) { Object.assign(queryParams, { page: curPage - 1, size: limit }); } else if (Object.is(opt.type, 'custom')) { Object.assign(queryParams, { page: 0, offset: (opt.startPage - 1) * limit, size: (opt.endPage - opt.startPage + 1) * limit, }); } // 排序 const { noSort, sortField, sortDir } = this.model; if (!noSort && sortField) { Object.assign(queryParams, { sort: `${sortField},${sortDir ? sortDir : 'asc'}`, }); } const parentData: IParam = {}; this.emit('beforeLoad', [parentData]); // 视图查询参数 if (parentData && Object.keys(parentData).length > 0) { Object.assign(queryParams, parentData); } const tempContext: IContext = deepCopy(this.store.context); const arg = { viewParams: queryParams }; const res = await this.beforeAsyncAction('export', tempContext, arg); if(!res.ok){ return { ok: false, data: this.getData(), rowData: { status: 500 } }; } try { const response = await this.ctrlService[dataExport.default ? 'search' : 'searchExportData']( this.actions.fetchAction, tempContext, arg ); const res = await this.afterAsyncAction('export', response); if(!res.ok){ return { ok: false, data: this.getData(), rowData: { status: 500 } }; } if (!response.success) { return { ok: false, data: this.getData(), rowData: response, }; } this.handleExport(response.data); return { ok: true, data: this.getData(), rowData: response }; } catch (error: any) { await this.afterAsyncAction('export', error); return { ok: false, data: this.getData(), rowData: error, }; } } /** * 后台导出 * * @param {IParam} [opt] * @memberof MDCtrlController */ public async exportExcelByBackend(opt?: IParam): Promise { return { ok: false, data: this.getData(), rowData: { status: 200 }, }; } /** * 刷新 * * @param {(IParam | undefined)} [args] * @return {*} {Promise} * @memberof MDCtrlController */ public async refresh(args?: IParam | undefined): Promise { try { const result = await this.load(args); if (result.ok) { return true; } } catch (error) { return false; } return false; } /** * 获取部件能力 * * @return {*} {A} * @memberof MDCtrlController */ public getAbility(): A { return { ...super.getAbility(), load: this.load.bind(this), selectAll: this.selectAll.bind(this), selectItems: this.selectItems.bind(this), exportExcel: this.exportExcel.bind(this), }; } }