diff --git a/packages/runtime/CHANGELOG.md b/packages/runtime/CHANGELOG.md index b5073bd1b5de528c62438104858135841cfb91bd..92ebbfad0dc16d5ac6764c154688732a0ad4b199 100644 --- a/packages/runtime/CHANGELOG.md +++ b/packages/runtime/CHANGELOG.md @@ -7,6 +7,10 @@ ## [Unreleased] +### Added + +- 新增BI报表 + ## [0.7.41-alpha.29] - 2025-09-30 ### Added diff --git a/packages/runtime/src/controller/control/report-panel/generator/base-generator.ts b/packages/runtime/src/controller/control/report-panel/generator/base-generator.ts index 0f66bbd317fa2240bdf96b98d31628dcaca3f679..68c270a297568874aa30a65b72bb5474dec94aed 100644 --- a/packages/runtime/src/controller/control/report-panel/generator/base-generator.ts +++ b/packages/runtime/src/controller/control/report-panel/generator/base-generator.ts @@ -85,4 +85,14 @@ export class ReportPanelBaseGenerator { public load(data: IData = {}): Promise { return Promise.resolve(data); } + + /** + * @description 生成 + * @param {IData[]} _items + * @returns {*} {({ model: IModel; options: IData; data: IData[] } | undefined)} + * @memberof ReportPanelBaseGenerator + */ + public generate( + _items: IData[], + ): { model: IModel; options: IData; data: IData[] } | undefined {} } diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/area-converter.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/area-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..ff57e84eeb7a45ad184ebf9cb4cd1cbeb4648300 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/area-converter.ts @@ -0,0 +1,106 @@ +import { MultiSeriesConverter } from './base'; + +/** + * @description 面积图转换器 + * @export + * @class AreaConverter + * @extends {MultiSeriesConverter} + */ +export class AreaConverter extends MultiSeriesConverter { + /** + * @description 仿真模型 + * @type {IModel} + * @memberof AreaConverter + */ + mockModel: IModel = { + userParam: {}, + dechartLegend: { + id: 'legend', + showLegend: false, + appId: this.appBIReport.appId, + }, + dechartTitle: { + id: 'title', + showTitle: false, + appId: this.appBIReport.appId, + }, + chartXAxises: [ + { + id: '0', + type: 'category', + position: 'bottom', + echartsPos: 'xAxis', + name: 'axis_xAxis_0', + echartsType: 'category', + appId: this.appBIReport.appId, + }, + ], + chartYAxises: [ + { + id: '0', + type: 'numeric', + position: 'left', + echartsPos: 'yAxis', + echartsType: 'value', + name: 'axis_yAxis_0', + appId: this.appBIReport.appId, + }, + ], + readOnly: true, + autoLoad: true, + dechartSerieses: [], + controlType: 'CHART', + showBusyIndicator: true, + id: this.appBIReport.id, + name: this.appBIReport.id, + appId: this.appBIReport.appId, + codeName: this.appBIReport.id, + caption: this.appBIReport.name, + logicName: this.appBIReport.name, + appDataEntityId: this.appBIReport.appDataEntityId, + }; + + /** + * @description 仿真序列模型 + * @type {IModel} + * @memberof AreaConverter + */ + mockSerieModel: IModel = { + chartSeriesEncode: { + id: '0', + type: 'XY', + name: '坐标系编码', + chartXAxisId: '0', + chartYAxisId: '0', + appId: this.appBIReport.appId, + }, + seriesType: 'line', + echartsType: 'line', + chartDataSetId: '0', + seriesLayoutBy: 'column', + enableChartDataSet: true, + chartCoordinateSystemId: '0', + appId: this.appBIReport.appId, + }; + + /** + * @description 转化数据到报表 + * @param {IData[]} items + * @returns {*} {({ model: IModel; options: IData; data: IData[] } | undefined)} + * @memberof AreaConverter + */ + translateDataToReport( + items: IData[], + ): { model: IModel; options: IData; data: IData[] } | undefined { + const report = super.translateDataToReport(items); + if (report) { + const { model } = report; + model.dechartSerieses?.forEach((series: IData) => { + Object.assign(series.userParam, { + 'EC.areaStyle': JSON.stringify({}), + }); + }); + } + return report; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/bar-converter-base.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/bar-converter-base.ts new file mode 100644 index 0000000000000000000000000000000000000000..85e95a692c0db9a2adc3f03f9bffce0d0c3727c1 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/bar-converter-base.ts @@ -0,0 +1,122 @@ +/* eslint-disable eqeqeq */ +/* eslint-disable camelcase */ +/* eslint-disable no-unused-expressions */ +import { ChartUtil } from '../utils'; +import { MultiSeriesConverter } from './multi-series-converter'; + +/** + * @description 条形图转换器基类 + * @export + * @class BarConverterBase + * @extends {MultiSeriesConverter} + */ +export class BarConverterBase extends MultiSeriesConverter { + /** + * @description 获取控件参数 + * @returns {*} {IData} + * @memberof BarConverterBase + */ + getChartControlParams(): IData { + const ctrlParams = super.getChartControlParams(); + Object.assign(ctrlParams, { MODE: 'ROW' }); + return ctrlParams; + } + + /** + * @description 计算X轴参数 + * @returns {*} {IData} + * @memberof BarConverterBase + */ + getChartXAxisParams(): IData { + const option = super.getChartXAxisParams(); + const tempOption = JSON.parse(option['EC.xAxis']); + Object.assign(tempOption, { + type: 'value', + nameLocation: 'center', + }); + Object.assign(tempOption.nameTextStyle, { + lineHeight: 60, + }); + tempOption.axisLabel.formatter = null; + return { 'EC.xAxis': JSON.stringify(tempOption) }; + } + + /** + * @description 计算Y轴参数 + * @returns {*} {IData} + * @memberof BarConverterBase + */ + getChartYAxisParams(): IData { + const { yaxis_name } = this.reportUIModel; + const option = super.getChartYAxisParams(); + const tempOption = JSON.parse(option['EC.yAxis']); + // 显示标题时,传undefined,实际标题由模板进行拼接,传递 空字符串 ,不显示标题 + let tempName: string | undefined = ''; + if (yaxis_name == '1') { + tempName = undefined; + } + Object.assign(tempOption, { + type: 'category', + name: tempName, + }); + Object.assign(tempOption.axisLabel, { + ...ChartUtil.xAxisLabel(), + }); + + return { 'EC.yAxis': JSON.stringify(tempOption) }; + } + + /** + * @description 获取标签参数 + * @param {IModel} seriesModel + * @param {IData[]} items + * @returns {*} {IData} + * @memberof BarConverterBase + */ + getChartLabelParams(seriesModel: IModel, items: IData[]): IData { + const { + series_label_show, + series_label_fontsize, + series_label_position, + series_label_fontstyle, + series_label_fontcolor, + series_label_data_range, + } = this.reportUIModel; + + const options: IData = { + show: series_label_show == '1', + position: series_label_position, + }; + + // 标签(字体样式) + if (series_label_fontstyle) { + series_label_fontstyle === 'bold' + ? (options.fontWeight = series_label_fontstyle) + : (options.fontStyle = series_label_fontstyle); + } + + if (series_label_fontsize) { + options.fontSize = Number(series_label_fontsize); + } + if (series_label_fontcolor) { + options.color = series_label_fontcolor; + } + + // 显示数据范围 + if (series_label_data_range && series_label_data_range !== 'all') { + const { min, max } = this.calcMaxMin(seriesModel, items); + options.formatter = `function(param) { + if(param.value[0] === ${max} || param.value[0] === ${min}){ + return param.value[0]; + } + return ''; + }`; + } + if (series_label_data_range && series_label_data_range === 'all') { + options.formatter = `function(param) { + return param.value[0]; + }`; + } + return { 'EC.label': JSON.stringify(options) }; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/converter-base.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/converter-base.ts new file mode 100644 index 0000000000000000000000000000000000000000..6db537970e3129d413e22fc598476e38e76008f0 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/converter-base.ts @@ -0,0 +1,104 @@ +import { + IAppBIReport, + IAppBIReportMeasure, + IAppBIReportDimension, +} from '@ibiz/model-core'; + +/** + * @description 转化器基类 + * @export + * @abstract + * @class ConverterBase + */ +export abstract class ConverterBase { + /** + * @description 仿真模型 + * @type {IModel} + * @memberof ConverterBase + */ + mockModel: IModel = {}; + + /** + * @description 报表前端模型(原始模型) + * @type {IData} + * @memberof ConverterBase + */ + reportUIModel: IData = {}; + + /** + * @description 指标模型集合 + * @type {IAppBIReportMeasure[]} + * @memberof ConverterBase + */ + measures: IAppBIReportMeasure[] = []; + + /** + * @description 维度模型集合 + * @type {IAppBIReportDimension[]} + * @memberof ConverterBase + */ + dimensions: IAppBIReportDimension[] = []; + + /** + * @description 维度分组模型 + * @type {IAppBIReportDimension} + * @memberof ConverterBase + */ + groupDimension?: IAppBIReportDimension; + + /** + * Creates an instance of ConverterBase. + * @param {IAppBIReport} appBIReport 智能报表模型 + * @param {IContext} context 上下文 + * @param {IParams} params 视图参数 + * @memberof ConverterBase + */ + public constructor( + protected appBIReport: IAppBIReport, + protected context: IContext, + protected params: IParams, + ) {} + + /** + * @description 初始化 + * @memberof ConverterBase + */ + async init(): Promise { + try { + const { reportUIModel, appBIReportMeasures, appBIReportDimensions } = + this.appBIReport; + this.reportUIModel = reportUIModel + ? JSON.parse(reportUIModel) + : undefined; + this.measures = appBIReportMeasures || []; + this.dimensions = + appBIReportDimensions?.filter( + dimension => dimension.dimensionTag !== this.reportUIModel.group_tags, + ) || []; + this.groupDimension = appBIReportDimensions?.find( + dimension => dimension.dimensionTag === this.reportUIModel.group_tags, + ); + await this.onInit(); + } catch (error) { + ibiz.log.error(error); + } + } + + /** + * @description 初始化-子类重写 + * @protected + * @returns {*} {Promise} + * @memberof ConverterBase + */ + protected async onInit(): Promise {} + + /** + * @description 转化数据到报表 + * @param {IData[]} _items + * @returns {*} {({ model: IModel; options: IData; data: IData[] } | undefined)} + * @memberof ConverterBase + */ + translateDataToReport( + _items: IData[], + ): { model: IModel; options: IData; data: IData[] } | undefined {} +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/echart-converter-base.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/echart-converter-base.ts new file mode 100644 index 0000000000000000000000000000000000000000..3e03337b2489e4529a26c09bcaab5e7b70ab5c19 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/echart-converter-base.ts @@ -0,0 +1,528 @@ +/* eslint-disable eqeqeq */ +/* eslint-disable camelcase */ +/* eslint-disable no-unused-expressions */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { IAppBIReportMeasure, IAppBIReportDimension } from '@ibiz/model-core'; +import { clone } from 'ramda'; +import { ChartUtil } from '../utils'; +import { ConverterBase } from './converter-base'; + +/** + * @description 图表基类转换器 + * @export + * @abstract + * @class EchartConverterBase + * @extends {ConverterBase} + */ +export abstract class EchartConverterBase extends ConverterBase { + /** + * @description 仿真序列模型 + * @type {IModel} + * @memberof EchartConverterBase + */ + mockSerieModel: IModel = {}; + + /** + * @description 获取图表颜色参数 + * @returns {*} {IData} + * @memberof EchartConverterBase + */ + getChartColorParams(): IData { + const params: IData = {}; + // 图表颜色 + const { chart_color } = this.reportUIModel; + if (chart_color) params[`EC.color`] = JSON.stringify(chart_color); + return params; + } + + /** + * @description 获取图表X轴参数 + * @returns {*} {IData} + * @memberof EchartConverterBase + */ + getChartXAxisParams(): IData { + const { + xaxis_name, + xaxis_nametextstyle_fontstyle, + xaxis_nametextstyle_fontsize, + xaxis_nametextstyle_fontcolor, + xaxis_axislabel_show, + xaxis_axislabel_fontstyle, + xaxis_axislabel_fontsize, + xaxis_axislabel_fontcolor, + xaxis_axisline_show, + xaxis_axisline_linestyle_type, + xaxis_axisline_linestyle_width, + xaxis_axisline_linestyle_color, + xaxis_splitline_show, + xaxis_splitline_linestyle_type, + xaxis_splitline_linestyle_width, + xaxis_splitline_linestyle_color, + xaxis_axislabel_interval, + } = this.reportUIModel; + + // 显示标题时,传undefined,实际标题由模板进行拼接,传递 空字符串 ,不显示标题 + let tempName: string | undefined = ''; + if (xaxis_name == '1') { + tempName = undefined; + } + const options: IData = { + show: true, + showTitle: xaxis_name == '1', // 分层时,模板需要根据这个字段计算对应的标题 + type: 'category', + name: tempName, // 显示轴标题 + minorSplitLine: { + show: false, + }, + }; + // 横轴(轴标题) + const nameTextStyle: IData = {}; + if (xaxis_nametextstyle_fontstyle) { + xaxis_nametextstyle_fontstyle === 'bold' + ? (nameTextStyle.fontWeight = xaxis_nametextstyle_fontstyle) + : (nameTextStyle.fontStyle = xaxis_nametextstyle_fontstyle); + } + if (xaxis_nametextstyle_fontsize) { + nameTextStyle.fontSize = xaxis_nametextstyle_fontsize; + } + if (xaxis_nametextstyle_fontcolor) { + nameTextStyle.color = xaxis_nametextstyle_fontcolor; + } + options.nameTextStyle = nameTextStyle; + + // 横轴(轴标签) + const axisLabel: IData = { + show: xaxis_axislabel_show == '1', + interval: xaxis_axislabel_interval || 'auto', + ...ChartUtil.xAxisLabel(), + ...ChartUtil.computeLabelEllipsis(xaxis_axislabel_interval), + }; + if (xaxis_axislabel_fontstyle) { + xaxis_axislabel_fontstyle === 'bold' + ? (axisLabel.fontWeight = xaxis_axislabel_fontstyle) + : (axisLabel.fontStyle = xaxis_axislabel_fontstyle); + } + + if (xaxis_axislabel_fontsize) { + axisLabel.fontSize = xaxis_axislabel_fontsize; + } + if (xaxis_axislabel_fontcolor) { + axisLabel.color = xaxis_axislabel_fontcolor; + } + options.axisLabel = axisLabel; + + // 横轴(轴线) + const axisLine: IData = { + show: xaxis_axisline_show == '1', + lineStyle: { + type: + xaxis_axisline_linestyle_type === 'doubleDashed' + ? [15] + : xaxis_axisline_linestyle_type, + width: xaxis_axisline_linestyle_width, + color: xaxis_axisline_linestyle_color, // 轴线颜色 + }, + }; + options.axisLine = axisLine; + + // 横轴(网格线) + const splitLine: IData = { + show: xaxis_splitline_show == '1', + lineStyle: { + type: + xaxis_splitline_linestyle_type === 'doubleDashed' + ? [15] + : xaxis_splitline_linestyle_type, + width: xaxis_splitline_linestyle_width, + color: xaxis_splitline_linestyle_color, // 轴线颜色 + }, + }; + options.splitLine = splitLine; + + return { 'EC.xAxis': JSON.stringify(options) }; + } + + /** + * @description 获取图表Y轴参数 + * @returns {*} {IData} + * @memberof EchartConverterBase + */ + getChartYAxisParams(): IData { + const { + yaxis_name, + yaxis_nametextstyle_fontstyle, + yaxis_nametextstyle_fontsize, + yaxis_nametextstyle_fontcolor, + yaxis_axislabel_show, + yaxis_axislabel_fontstyle, + yaxis_axislabel_fontsize, + yaxis_axislabel_fontcolor, + yaxis_axisline_show, + yaxis_axisline_linestyle_type, + yaxis_axisline_linestyle_width, + yaxis_axisline_linestyle_color, + yaxis_splitline_show, + yaxis_splitline_linestyle_type, + yaxis_splitline_linestyle_width, + yaxis_splitline_linestyle_color, + } = this.reportUIModel; + const options: IData = { + show: true, + type: 'value', + showTitle: yaxis_name == '1', + minorSplitLine: { + show: false, + }, + }; + // 纵轴(轴标题) + const nameTextStyle: IData = {}; + yaxis_nametextstyle_fontstyle === 'bold' + ? (nameTextStyle.fontWeight = yaxis_nametextstyle_fontstyle) + : (nameTextStyle.fontStyle = yaxis_nametextstyle_fontstyle); + if (yaxis_nametextstyle_fontsize) { + nameTextStyle.fontSize = yaxis_nametextstyle_fontsize; + } + if (yaxis_nametextstyle_fontcolor) { + nameTextStyle.color = yaxis_nametextstyle_fontcolor; + } + options.nameTextStyle = nameTextStyle; + + // 纵轴(轴标签) + const axisLabel: IData = { + show: yaxis_axislabel_show == '1', + }; + if (yaxis_axislabel_fontstyle) { + yaxis_axislabel_fontstyle === 'bold' + ? (axisLabel.fontWeight = yaxis_axislabel_fontstyle) + : (axisLabel.fontStyle = yaxis_axislabel_fontstyle); + } + + if (yaxis_axislabel_fontsize) { + axisLabel.fontSize = yaxis_axislabel_fontsize; + } + if (yaxis_axislabel_fontcolor) { + axisLabel.color = yaxis_axislabel_fontcolor; + } + options.axisLabel = axisLabel; + + // 纵轴(轴线) + const axisLine: IData = { + show: yaxis_axisline_show == '1', + lineStyle: { + type: + yaxis_axisline_linestyle_type === 'doubleDashed' + ? [15] + : yaxis_axisline_linestyle_type, + width: yaxis_axisline_linestyle_width, + color: yaxis_axisline_linestyle_color, + }, + }; + options.axisLine = axisLine; + + // 纵轴(网格线) + const splitLine: IData = { + show: yaxis_splitline_show == '1', + lineStyle: { + type: + yaxis_splitline_linestyle_type === 'doubleDashed' + ? [15] + : yaxis_splitline_linestyle_type, + width: yaxis_splitline_linestyle_width, + color: yaxis_splitline_linestyle_color, // 轴线颜色 + }, + }; + options.splitLine = splitLine; + + return { 'EC.yAxis': JSON.stringify(options) }; + } + + /** + * @description 计算最大值最小值 + * @param {IModel} seriesModel + * @param {IData[]} items + * @returns {*} {{ max: number; min: number }} + * @memberof EchartConverterBase + */ + calcMaxMin( + seriesModel: IModel, + items: IData[], + ): { max: number; min: number } { + const { valueField, catalogField, seriesField } = seriesModel; + const catalogFields = this.dimensions.map(d => + d.dimensionTag!.toLowerCase(), + ); + const count = items.reduce((acc, item) => { + let catalogName = ''; + if (catalogFields.length) { + catalogFields.forEach(key => { + catalogName += item[key]; + }); + } else { + catalogName = item[catalogField]; + } + const name = seriesField + ? `${item[seriesField]}_${catalogName}` + : `${catalogName}`; + const value = item[valueField]; + // 如果分组不存在,初始化结构 + if (!acc[name]) { + acc[name] = { + sum: 0, + max: -Infinity, // 初始化为极小值 + min: Infinity, // 初始化为极大值 + }; + } + // 更新统计值 + acc[name].sum += value; + acc[name].max = Math.max(acc[name].max, value); + acc[name].min = Math.min(acc[name].min, value); + return acc; + }, {}); + const allSums = Object.values(count).map(item => item.sum); + const max = Math.max(...allSums); + const min = Math.min(...allSums); + return { max, min }; + } + + /** + * @description 获取标签参数 + * @param {IModel} seriesModel + * @param {IData[]} items + * @returns {*} {IData} + * @memberof EchartConverterBase + */ + getChartLabelParams(seriesModel: IModel, items: IData[]): IData { + const { + series_label_show, + series_label_position, + series_label_fontsize, + series_label_fontstyle, + series_label_fontcolor, + series_label_data_range, + } = this.reportUIModel; + + const options: IData = { + show: series_label_show == '1', + position: series_label_position, + }; + + // 标签(字体样式) + if (series_label_fontstyle) { + series_label_fontstyle === 'bold' + ? (options.fontWeight = series_label_fontstyle) + : (options.fontStyle = series_label_fontstyle); + } + + if (series_label_fontsize) { + options.fontSize = Number(series_label_fontsize); + } + if (series_label_fontcolor) { + options.color = series_label_fontcolor; + } + + // 显示数据范围 + if (series_label_data_range && series_label_data_range !== 'all') { + const { min, max } = this.calcMaxMin(seriesModel, items); + options.formatter = `function(param) { + if(param.value[1] === ${max} || param.value[1] === ${min}){ + return param.value[1]; + } + return ''; + }`; + } + if (series_label_data_range && series_label_data_range === 'all') { + options.formatter = `function(param) { + return param.value[1]; + }`; + } + return { 'EC.label': JSON.stringify(options) }; + } + + /** + * @description 获取图例参数 + * @returns {*} {IData} + * @memberof EchartConverterBase + */ + getChartLegendParams(): IData { + const { + legend_show, + legend_fontstyle, + legend_fontsize, + legend_fontcolor, + legend_position, + } = this.reportUIModel; + const options: IData = { + show: legend_show == '1', + icon: 'circle', + formatter: `function(param){ + return param; + }`, + }; + // 文字颜色 + const textStyle: IData = {}; + if (legend_fontstyle) { + legend_fontstyle === 'bold' + ? (textStyle.fontWeight = legend_fontstyle) + : (textStyle.fontStyle = legend_fontstyle); + } + if (legend_fontsize) { + textStyle.fontSize = Number(legend_fontsize); + } + if (legend_fontcolor) { + textStyle.color = legend_fontcolor; + } + options.textStyle = textStyle; + + // 图例位置 + if (legend_position) { + Object.assign(options, ChartUtil.getLegendPosition(legend_position)); + } + return { 'EC.legend': JSON.stringify(options) }; + } + + /** + * @description 获取控件参数 + * @returns {*} {IData} + * @memberof EchartConverterBase + */ + getChartControlParams(): IData { + const ctrlParams: IData = {}; + const tempdimensions = this.dimensions.map(item => { + return { + mode: 'field', + name: item.dimensionName, + codelistId: item.appCodeListId, + codename: item.dimensionTag!.toLowerCase(), + }; + }); + Object.assign(ctrlParams, { + CATALOGFIELDS: JSON.stringify(tempdimensions), + NOSORT: true, + }); + return ctrlParams; + } + + /** + * @description 获取tooltip参数 + * @param {IData} series + * @param {boolean} [isRow=false] + * @returns {*} + * @memberof EchartConverterBase + */ + getTooltipParams(series: IData, isRow: boolean = false) { + // 无分组维度时 + if (!this.groupDimension) { + return { + 'EC.name': series.serieText, + 'EC.tooltip': JSON.stringify({ + formatter: `function(param){ + const tempdata = param.data[2]; + const names = param.name.split('_'); + const catalogData = tempdata._catalogLevelData; + + let value = ${isRow} ? param.value[0] : param.value[1]; + + // 计算维度项分层 + let dimcatalogs = ''; + catalogData.forEach((item) => { + let text = item.valueText || '未定义' + const dimitem = '
' + item.name + ':
' +text + '
' + dimcatalogs += dimitem; + }) + return '
'+ dimcatalogs +'
'+ param.marker + param.seriesName +':
'+ value +'
' + }`, + }), + }; + } + const param = { + 'EC.tooltip': JSON.stringify({ + formatter: `function(param){ + const {data} = param; + const catalogData = data[2]._catalogLevelData; + + let tempName = param.seriesName; + const field = Object.keys(data[2]).find(key => { + return key !== '_groupName' && data[2][key] === param.seriesName; + }) + // 计算维度项分层 + let dimcatalogs = ''; + catalogData.forEach((item) => { + let text = item.valueText || '未定义' + const dimitem = '
' + item.name + ':
' +text + '
' + dimcatalogs += dimitem; + }) + + let value = ${isRow} ? param.value[0] : param.value[1]; + return '
' + dimcatalogs + + '
'+ + '
${this.groupDimension.dimensionName}:
'+ + '
'+ tempName +'
'+ + '
' + + '
'+ + '
' + param.marker + '${series.serieText}:
'+ + '
' + value + '
'+ + '
'+ + '
' + }`, + }), + }; + return param; + } + + /** + * @description 计算序列模型 + * @param {IAppBIReportMeasure[]} measures 指标 + * @param {IAppBIReportDimension} dimension 维度 + * @param {IAppBIReportDimension[]} [groupDimension] 分组维度 + * @returns {*} {IModel[]} + * @memberof EchartConverterBase + */ + calcSeriesModel( + measures: IAppBIReportMeasure[], + dimension: IAppBIReportDimension, + groupDimension?: IAppBIReportDimension, + ): IModel[] { + const seriesModels: IModel[] = []; + measures.forEach((item, index) => { + const seriesModel = clone(this.mockSerieModel); + Object.assign(seriesModel, { + id: `${seriesModel.seriesType}_${index}`, + caption: item.measureName, + serieText: item.measureName, + catalogName: dimension.dimensionName, + valueField: item.measureTag!.toLowerCase(), + catalogCodeListId: dimension.appCodeListId, + catalogField: dimension.dimensionTag!.toLowerCase(), + }); + // 分组维度 + if (groupDimension) + Object.assign(seriesModel, { + seriesCodeListId: groupDimension.appCodeListId, + seriesField: groupDimension.dimensionTag!.toLowerCase(), + }); + seriesModels.push(seriesModel); + }); + return seriesModels; + } + + /** + * @description 转化数据到模型 + * @param {IData[]} items + * @returns {*} {({ model: IModel; options: IData; data: IData[] } | undefined)} + * @memberof EchartConverterBase + */ + translateDataToReport( + items: IData[], + ): { model: IModel; options: IData; data: IData[] } | undefined { + if (!this.measures.length || !this.dimensions.length) return; + const model = clone(this.mockModel); + const seriesModels = this.calcSeriesModel( + this.measures, + this.dimensions[0], + this.groupDimension, + ); + if (model.dechartSerieses) model.dechartSerieses.push(...seriesModels); + return { model, options: {}, data: items }; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/grid-converter-base.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/grid-converter-base.ts new file mode 100644 index 0000000000000000000000000000000000000000..eeb594f4cc23ea18a5ab77b163b796286956d562 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/grid-converter-base.ts @@ -0,0 +1,196 @@ +/* eslint-disable eqeqeq */ +/* eslint-disable camelcase */ +/* eslint-disable no-plusplus */ +import { ConverterBase } from './converter-base'; + +/** + * @description 表格转化器基类 + * @export + * @abstract + * @class GridConverterBase + * @extends {ConverterBase} + */ +export abstract class GridConverterBase extends ConverterBase { + /** + * @description 获取表格样式 + * @returns {*} {IData} + * @memberof GridConverterBase + */ + getGridStyle(): IData { + const result: IData = { + vars: {}, + classList: [], + model: { controlParam: {} }, + }; + const { + grid_show_agg, + grid_header_fontstyle, + grid_header_fontsize, + grid_header_fontcolor, + grid_header_position, + grid_body_fontstyle, + grid_body_fontsize, + grid_body_fontcolor, + grid_agg_row_position, + grid_function_setting, + } = this.reportUIModel; + result.vars = { + '--ibiz-control-grid-header-align': grid_header_position, + '--ibiz-control-grid-header-font-size': `${grid_header_fontsize}px`, + '--ibiz-control-grid-header-text-color': grid_header_fontcolor, + '--ibiz-control-grid-content-font-size': `${grid_body_fontsize}px`, + '--ibiz-control-grid-content-text-color': grid_body_fontcolor, + }; + if (grid_header_fontstyle) { + const key = + grid_header_fontstyle === 'bold' + ? '--ibiz-control-grid-header-font-weight' + : '--ibiz-control-grid-header-font-style'; + result.vars[key] = grid_header_fontstyle; + } + if (grid_body_fontstyle) { + const key = + grid_body_fontstyle === 'bold' + ? '--ibiz-control-grid-content-font-weight' + : '--ibiz-control-grid-content-font-style'; + result.vars[key] = grid_body_fontstyle; + } + if (grid_show_agg == '1') { + Object.assign(result.model, { aggMode: 'PAGE' }); + if (grid_agg_row_position === 'top') { + result.classList.push('el-table--top-agg'); + } + } + if (grid_function_setting) { + const ctrlParams = {}; + if (grid_function_setting.indexOf('fixedGridHeader') === -1) { + result.classList.push('el-table--scroll-header'); + } + if ( + grid_function_setting.indexOf('fixedDimension') !== -1 && + this.dimensions + ) { + Object.assign(result.model, { + frozenFirstColumn: this.dimensions.length, + }); + } + if (grid_function_setting.indexOf('showPercent') !== -1) { + Object.assign(ctrlParams, { + percentkeys: JSON.stringify( + this.measures!.map(x => x.measureTag!.toLowerCase()), + ), + }); + } + Object.assign(result.model.controlParam, { ctrlParams }); + } + return result; + } + + /** + * @description 计算表格列样式 + * @param {boolean} [enableAgg=true] + * @returns {*} {IData} + * @memberof GridConverterBase + */ + calcGridColumnStyle(enableAgg: boolean = true): IData { + const { grid_body_position, grid_show_agg } = this.reportUIModel; + const result: IData = {}; + if (grid_body_position) result.align = grid_body_position; + if (enableAgg && grid_show_agg == '1') result.aggMode = 'SUM'; + return result; + } + + /** + * @description 计算表格列合并 + * @param {IData[]} [items=[]] 表格展示数据 + * @returns {*} {IData} + * @memberof GridConverterBase + */ + calcGridColumnMerge(items: IData[] = []): IData { + const { grid_function_setting } = this.reportUIModel; + // rowspans 是手动添加的用于spanMethod脚本中获取,否则通过序列化的方式写入脚本在数据量大时执行太慢 + const result: IData = { controlAttributes: [], rowspans: [] }; + if (grid_function_setting.indexOf('dimensionMerge') !== -1) { + // 预处理数据,提前计算合并情况 + const columnKeys: string[] = this.dimensions.map(dimension => + dimension.dimensionTag!.toLowerCase(), + ); + const rowspans: number[][] = []; + columnKeys.forEach((columnKey, colIndex) => { + rowspans[colIndex] = []; + let pos = 0; + + // 第一列特殊处理 + if (colIndex === 0) { + while (pos < items.length) { + let count = 1; + const currentValue = items[pos][columnKey]; + + while ( + pos + count < items.length && + items[pos + count][columnKey] === currentValue + ) { + count++; + } + + for (let i = 0; i < count; i++) { + rowspans[colIndex][pos + i] = i === 0 ? count : 0; + } + + pos += count; + } + } else { + // 其他列基于前一列的合并信息处理 + let prevPos = 0; + + while (prevPos < items.length) { + const prevSpan = rowspans[colIndex - 1][prevPos]; + + if (prevSpan > 0) { + // 在前一列的合并块内处理当前列的合并 + let innerPos = prevPos; + + while (innerPos < prevPos + prevSpan) { + let count = 1; + const currentValue = items[innerPos][columnKey]; + + while ( + innerPos + count < prevPos + prevSpan && + items[innerPos + count][columnKey] === currentValue + ) { + count++; + } + + for (let i = 0; i < count; i++) { + rowspans[colIndex][innerPos + i] = i === 0 ? count : 0; + } + + innerPos += count; + } + } else { + rowspans[colIndex][prevPos] = 0; + } + + prevPos += Math.max(1, rowspans[colIndex - 1][prevPos]); + } + } + }); + const spanMethod = { + attrName: 'span-method', + attrValue: `const { row, column, rowIndex, columnIndex } = metadata; + const { rowspans } = ctrl.model; + if (rowspans[columnIndex] && rowspans[columnIndex][rowIndex] !== undefined) { + const span = rowspans[columnIndex][rowIndex]; + return { + rowspan: span > 0 ? span : 0, + colspan: span > 0 ? 1 : 0, + }; + } + return { rowspan: 1, colspan: 1 };`, + }; + result.rowspans = rowspans; + result.controlAttributes.push(spanMethod); + } + return result; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/index.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..f01be82a0889b3c0aa05b206558c2241e29c7c49 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/index.ts @@ -0,0 +1,5 @@ +export { ConverterBase } from './converter-base'; +export { BarConverterBase } from './bar-converter-base'; +export { ZoneConverterBase } from './zone-converter-base'; +export { MultiSeriesConverter } from './multi-series-converter'; +export { GridConverterBase } from './grid-converter-base'; diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/multi-series-converter.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/multi-series-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..3966ca0dd7501f9717622e964fbf428ba3549561 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/multi-series-converter.ts @@ -0,0 +1,55 @@ +/* eslint-disable camelcase */ +import { EchartConverterBase } from './echart-converter-base'; + +/** + * @description 多序列图表转换器 + * @export + * @class MultiSeriesConverter + * @extends {EchartConverterBase} + */ +export class MultiSeriesConverter extends EchartConverterBase { + /** + * @description 转化数据到报表 + * @param {IData[]} items + * @returns {*} {({ model: IModel; options: IData; data: IData[] } | undefined)} + * @memberof MultiSeriesConverter + */ + translateDataToReport( + items: IData[], + ): { model: IModel; options: IData; data: IData[] } | undefined { + const report = super.translateDataToReport(items); + if (!report) return; + const { model } = report; + // 图表颜色 + const chartColorParams = this.getChartColorParams(); + Object.assign(model.userParam, chartColorParams); + // 横轴参数 + const xAxisParams = this.getChartXAxisParams(); + Object.assign(model.userParam, xAxisParams); + // 纵轴参数 + const yAxisParams = this.getChartYAxisParams(); + Object.assign(model.userParam, yAxisParams); + // 图例参数 + const legendParams = this.getChartLegendParams(); + Object.assign(model.userParam, legendParams); + // 处理控件参数 + const controlParams = this.getChartControlParams(); + Object.assign(model, { + controlParam: { + ctrlParams: controlParams, + }, + }); + // 处理序列参数 + model.dechartSerieses?.forEach((series: IData) => { + Object.assign(series, { + userParam: { + 'EC.barWidth': '50%', + 'EC.barMaxWidth': '36', + ...this.getChartLabelParams(series, items), + ...this.getTooltipParams(series, controlParams.MODE === 'ROW'), + }, + }); + }); + return report; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/zone-converter-base.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/zone-converter-base.ts new file mode 100644 index 0000000000000000000000000000000000000000..89cf39f9190b2f83eda512347f7e2ab5f7fe5928 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/base/zone-converter-base.ts @@ -0,0 +1,108 @@ +import { clone } from 'ramda'; +import { IAppBIReportDimension, IAppBIReportMeasure } from '@ibiz/model-core'; +import { MultiSeriesConverter } from './multi-series-converter'; + +/** + * @description 分区图表基类转换器 + * @export + * @class ZoneConverterBase + * @extends {MultiSeriesConverter} + */ +export class ZoneConverterBase extends MultiSeriesConverter { + /** + * @description 获取控件参数 + * @returns {*} {IData} + * @memberof ZoneConverterBase + */ + getChartControlParams(): IData { + const ctrlParams = super.getChartControlParams(); + Object.assign(ctrlParams, { ZONE: true }); + return ctrlParams; + } + + /** + * @description 计算X轴参数 + * @returns {*} {IData} + * @memberof ZoneConverterBase + */ + getChartXAxisParams(): IData { + const option = super.getChartXAxisParams(); + const tempOption = JSON.parse(option['EC.xAxis']); + Object.assign(tempOption, { + axisTick: { + show: true, // 坐标轴刻度线,模板会计算,不是最后一个的坐标系刻度线都会隐藏 + }, + }); + + return { 'EC.xAxis': JSON.stringify(tempOption) }; + } + + /** + * @description 计算Y轴参数 + * @returns {*} {IData} + * @memberof ZoneConverterBase + */ + getChartYAxisParams(): IData { + const option = super.getChartYAxisParams(); + const tempOption = JSON.parse(option['EC.yAxis']); + Object.assign(tempOption, { + nameLocation: 'center', + nameGap: 50, + }); + Object.assign(tempOption.axisLabel, { + width: 100, + lineHeight: 1, + rich: { + top: { + padding: [0, 0, 15, 0], + }, + bottom: { + padding: [10, 0, 0, 0], + }, + }, + }); + + return { 'EC.yAxis': JSON.stringify(tempOption) }; + } + + /** + * @description 计算序列模型 + * @param {IAppBIReportMeasure[]} measures 指标 + * @param {IAppBIReportDimension} dimension 维度 + * @param {IAppBIReportDimension} [groupDimension] 分组维度 + * @returns {*} {IModel[]} + * @memberof ZoneConverterBase + */ + calcSeriesModel( + measures: IAppBIReportMeasure[], + dimension: IAppBIReportDimension, + groupDimension?: IAppBIReportDimension, + ): IModel[] { + const seriesModels: IModel[] = []; + measures.forEach((item, index) => { + const seriesModel = clone(this.mockSerieModel); + Object.assign(seriesModel, { + id: `${seriesModel.seriesType}_${index}`, + caption: item.measureName, + serieText: item.measureName, + catalogName: dimension.dimensionName, + valueField: item.measureTag!.toLowerCase(), + catalogCodeListId: dimension.appCodeListId, + catalogField: dimension.dimensionTag!.toLowerCase(), + }); + // 设置分区坐标轴 + Object.assign(seriesModel.chartSeriesEncode, { + chartXAxisId: index, + chartYAxisId: index, + }); + // 分组维度 + if (groupDimension) + Object.assign(seriesModel, { + seriesCodeListId: groupDimension.appCodeListId, + seriesField: groupDimension.dimensionTag!.toLowerCase(), + }); + seriesModels.push(seriesModel); + }); + return seriesModels; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/converter-factory.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/converter-factory.ts new file mode 100644 index 0000000000000000000000000000000000000000..2392e3624f9324c9716512062316134cf3c6732c --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/converter-factory.ts @@ -0,0 +1,102 @@ +import { IAppBIReport } from '@ibiz/model-core'; +import { ConverterBase } from './base'; +import { NumberConverter } from './number-converter'; +import { StackColConverter } from './stack-col-converter'; +import { MultiSeriesColConverter } from './multi-series-col-converter'; +import { GaugeConverter } from './gauge-converter'; +import { MultiSeriesBarConverter } from './multi-series-bar-converter'; +import { ZoneColConverter } from './zone-col-converter'; +import { StackBarConverter } from './stack-bar-converter'; +import { MultiSeriesLineConverter } from './multi-series-line-converter'; +import { ZoneLineConverter } from './zone-line-converter'; +import { AreaConverter } from './area-converter'; +import { PieConverter } from './pie-converter'; +import { RadarConverter } from './radar-converter'; +import { ScatterConverter } from './scatter-converter'; +import { GridConverter } from './grid-converter'; +import { CrossTableConverter } from './cross-table-converter'; + +/** + * @description 转换器工厂 + * @export + * @class ConverterFactory + */ +export class ConverterFactory { + /** + * @description 创建转换器 + * @static + * @param {('NUMBER' + * | 'GAUGE' + * | 'MULTI_SERIES_COL' + * | 'STACK_COL' + * | 'ZONE_COL' + * | 'MULTI_SERIES_BAR' + * | 'STACK_BAR' + * | 'MULTI_SERIES_LINE' + * | 'ZONE_LINE' + * | 'AREA' + * | 'GRID' + * | 'CROSSTABLE' + * | 'PIE' + * | 'RADAR' + * | 'SCATTER')} chartType + * @returns {*} {(BaseConverter | undefined)} + * @memberof ConverterFactory + */ + static createConverter( + chartType: + | 'NUMBER' + | 'GAUGE' + | 'MULTI_SERIES_COL' + | 'STACK_COL' + | 'ZONE_COL' + | 'MULTI_SERIES_BAR' + | 'STACK_BAR' + | 'MULTI_SERIES_LINE' + | 'ZONE_LINE' + | 'AREA' + | 'GRID' + | 'CROSSTABLE' + | 'PIE' + | 'RADAR' + | 'SCATTER', + model: IAppBIReport, + context: IContext, + params: IParams, + ): ConverterBase | undefined { + switch (chartType) { + case 'NUMBER': + return new NumberConverter(model, context, params); + case 'GAUGE': + return new GaugeConverter(model, context, params); + case 'MULTI_SERIES_COL': + return new MultiSeriesColConverter(model, context, params); + case 'STACK_COL': + return new StackColConverter(model, context, params); + case 'ZONE_COL': + return new ZoneColConverter(model, context, params); + case 'MULTI_SERIES_BAR': + return new MultiSeriesBarConverter(model, context, params); + case 'STACK_BAR': + return new StackBarConverter(model, context, params); + case 'MULTI_SERIES_LINE': + return new MultiSeriesLineConverter(model, context, params); + case 'ZONE_LINE': + return new ZoneLineConverter(model, context, params); + case 'AREA': + return new AreaConverter(model, context, params); + case 'PIE': + return new PieConverter(model, context, params); + case 'RADAR': + return new RadarConverter(model, context, params); + case 'SCATTER': + return new ScatterConverter(model, context, params); + case 'GRID': + return new GridConverter(model, context, params); + case 'CROSSTABLE': + return new CrossTableConverter(model, context, params); + default: + return undefined; + } + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/cross-table-converter.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/cross-table-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..f1f015597eda72d873a105c9d946ba512e37ab72 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/cross-table-converter.ts @@ -0,0 +1,419 @@ +/* eslint-disable eqeqeq */ +/* eslint-disable camelcase */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { clone, plus } from '@ibiz-template/core'; +import { IAppBIReportDimension } from '@ibiz/model-core'; +import { GridConverterBase } from './base'; +import { CodeListItem } from '../../../../../interface'; + +/** + * @description 交叉表转化器 + * @export + * @class CrossTableConverter + * @extends {GridConverterBase} + */ +export class CrossTableConverter extends GridConverterBase { + /** + * @description 仿真模型 + * @type {IModel} + * @memberof CrossTableConverter + */ + mockModel: IModel = { + fetchControlAction: { + id: 'fetch', + appDEMethodId: 'fetchdefault', + appId: this.appBIReport.appId, + appDataEntityId: this.appBIReport.appDataEntityId, + }, + controlParam: { id: this.appBIReport.id, appId: this.appBIReport.appId }, + pagingMode: 1, + pagingSize: 20, + autoLoad: true, + aggMode: 'NONE', + gridStyle: 'USER', + groupMode: 'NONE', + sortMode: 'REMOTE', + singleSelect: true, + controlType: 'GRID', + columnEnableLink: 2, + modelType: 'PSDEGRID', + columnEnableFilter: 2, + enablePagingBar: false, + id: this.appBIReport.id, + showBusyIndicator: true, + enableCustomized: false, + name: this.appBIReport.id, + modelId: this.appBIReport.id, + appId: this.appBIReport.appId, + codeName: this.appBIReport.id, + logicName: this.appBIReport.name, + appDataEntityId: this.appBIReport.appDataEntityId, + }; + + /** + * @description 百分比数据 + * @type {string[]} + * @memberof CrossTableConverter + */ + percentKeys: string[] = []; + + /** + * @description 合计列标识 + * @type {string} + * @memberof CrossTableConverter + */ + totalColTag: string = 'col_sum'; + + /** + * @description 指标总数 + * @type {IData} + * @memberof CrossTableConverter + */ + measuresTotalResult: IData = {}; + + /** + * @description 维度列 + * @type {IAppBIReportDimension} + * @memberof CrossTableConverter + */ + dimensionCol?: IAppBIReportDimension; + + /** + * @description 维度列代码表 + * @type {CodeListItem[]} + * @memberof CrossTableConverter + */ + codelistItems?: readonly CodeListItem[]; + + /** + * @description 初始化 + * @protected + * @returns {*} {Promise} + * @memberof CrossTableConverter + */ + protected async onInit(): Promise { + const { appBIReportDimensions } = this.appBIReport; + this.dimensions = + appBIReportDimensions?.filter(dimension => !dimension.placement) || []; + this.dimensionCol = appBIReportDimensions?.find( + dimension => dimension.placement === 'COLHEADER', + ); + if (this.dimensionCol?.appCodeListId) { + const app = ibiz.hub.getApp(this.appBIReport.appId); + this.codelistItems = await app.codeList.get( + this.dimensionCol.appCodeListId, + this.context, + this.params, + ); + } + } + + /** + * @description 计算维度(列) + * @param {IData[]} items + * @returns {*} {string[]} + * @memberof CrossTableConverter + */ + calcDimensionCols(items: IData[]): string[] { + if (!this.dimensionCol) return []; + + if (this.codelistItems) + return this.codelistItems.map(item => item.value as string); + + const result: Set = new Set(); + const dimensionTag = this.dimensionCol.dimensionTag!.toLowerCase(); + + let hasBlank = false; + for (const item of items) { + const value = item[dimensionTag]; + if (!value) { + hasBlank = true; + } else { + result.add(value); + } + } + const resultArray = Array.from(result); + if (hasBlank) resultArray.push(''); + + return resultArray; + } + + /** + * 通过指标计算表格列 + * + * @author tony001 + * @date 2024-12-19 17:12:52 + * @param {IData} data + * @param {string} [type] + * @return {*} {IModel[]} + */ + clacMeasureColumns(parentName?: string): IModel[] { + return this.measures.map(item => { + let codeName = item.measureTag!.toLowerCase(); + if (parentName) codeName = `${parentName}@${codeName}`; + const totalCodename = `${this.totalColTag}@${codeName}`; + this.percentKeys.push(codeName); + return { + codeName, + width: 150, + id: codeName, + totalCodename, + widthUnit: 'STAR', + valueType: 'SIMPLE', + measureTag: codeName, + dataItemName: codeName, + appDEFieldId: codeName, + format: item.jsonFormat, + caption: item.measureName, + columnType: 'DEFGRIDCOLUMN', + appId: this.appBIReport.appId, + appCodeListId: item.appCodeListId, + ...this.calcGridColumnStyle(), + }; + }); + } + + /** + * @description 计算合计表格列 + * @param {string} position + * @param {IModel[]} degridColumns + * @memberof CrossTableConverter + */ + calcAggGridCol(position: string, degridColumns: IModel[]) { + const { grid_show_agg, grid_agg_col_position } = this.reportUIModel; + if ( + this.dimensionCol && + grid_show_agg == '1' && + grid_agg_col_position === position + ) { + const children = this.clacMeasureColumns(this.totalColTag); + const column = { + caption: '合计', + id: this.totalColTag, + degridColumns: children, + codeName: this.totalColTag, + measureTag: this.totalColTag, + appId: this.appBIReport.appId, + columnType: 'GROUPGRIDCOLUMN', + dataItemName: this.totalColTag, + }; + degridColumns.push(column); + } + } + + /** + * @description 计算合计列数据 + * @param {IData[]} items + * @memberof CrossTableConverter + */ + calcAggTotalData(items: IData[]) { + this.measures.forEach(measure => { + const codeName = `${this.totalColTag}@${measure.measureTag!.toLowerCase()}`; + this.measuresTotalResult[codeName] = 0; + }); + items.forEach((item: IData) => { + this.measures.forEach(measure => { + const codeName = `${ + this.totalColTag + }@${measure.measureTag!.toLowerCase()}`; + const total = this.percentKeys.reduce((a, b) => { + const bvalue = Number(item[b]) || 0; + return plus(a, bvalue); + }, 0); + this.measuresTotalResult[codeName] += total; + item[codeName] = total; + }); + }); + } + + /** + * @description 计算交叉表 + * @param {string[]} dimensionCols 维度列 + * @returns {*} {IModel[]} + * @memberof CrossTableConverter + */ + calcCrossTableColumns(dimensionCols: string[]): IModel[] { + const deGridColumns: IModel[] = []; + if (this.dimensions) { + deGridColumns.push( + ...this.dimensions.map(item => { + const codeName = item.dimensionTag!.toLowerCase(); + return { + codeName, + width: 150, + id: codeName, + widthUnit: 'STAR', + measureTag: codeName, + appDEFieldId: codeName, + dataItemName: codeName, + caption: item.dimensionName, + columnType: 'DEFGRIDCOLUMN', + appId: this.appBIReport.appId, + appCodeListId: item.appCodeListId, + ...this.calcGridColumnStyle(false), + }; + }), + ); + } + this.calcAggGridCol('left', deGridColumns); + if (this.dimensionCol) { + const codeNme = this.dimensionCol!.dimensionTag!.toLowerCase(); + const groupColumns = dimensionCols.map((key, index) => { + const children = this.clacMeasureColumns(key); + return { + degridColumns: children, + id: `${codeNme}-${index}`, + appId: this.appBIReport.appId, + columnType: 'GROUPGRIDCOLUMN', + codeName: `${codeNme}-${index}`, + measureTag: `${codeNme}-${index}`, + dataItemName: `${codeNme}-${index}`, + appDEFieldId: `${codeNme}-${index}`, + appCodeListId: this.dimensionCol!.appCodeListId, + caption: + this.codelistItems?.find(item => item.value == key)?.text || key, + }; + }); + deGridColumns.push(...groupColumns); + } else { + const children = this.clacMeasureColumns(); + deGridColumns.push(...children); + } + this.calcAggGridCol('right', deGridColumns); + return deGridColumns; + } + + /** + * @description 获取数据项 + * @param {IData[]} items + * @param {IData} item + * @returns {*} {(IData | undefined)} + * @memberof CrossTableConverter + */ + getItem(items: IData[], item: IData): IData | undefined { + return items.find(x => { + return ( + this.dimensions.findIndex( + c => + x[c.dimensionTag!.toLowerCase()] !== + item[c.dimensionTag!.toLowerCase()], + ) === -1 + ); + }); + } + + /** + * @description 计算交叉表数据 + * @param {IData[]} items + * @returns {*} {IData[]} + * @memberof CrossTableConverter + */ + calcCrossTableData(items: IData[]): IData[] { + if (this.dimensionCol) { + const result: IData[] = []; + const codeName = this.dimensionCol.dimensionTag!.toLowerCase(); + items.forEach((item: IData) => { + this.measures.forEach(_item => { + const type = _item.measureTag!.toLowerCase() || ''; + const value = item[codeName]; + let key = type || ''; + if (value) key = `${value}@${type}`; + const oldData = this.getItem(result, item); + if (oldData) { + oldData[key] = item[type]; + } else { + const newItem = { [key]: item[type] }; + this.dimensions.forEach(x => { + newItem[x.dimensionTag!.toLowerCase()] = + item[x.dimensionTag!.toLowerCase()]; + }); + result.push(newItem); + } + }); + }); + return result; + } + return items; + } + + /** + * @description 计算交叉表数据项 + * @param {IData[]} columns + * @returns {*} {IModel[]} + * @memberof CrossTableConverter + */ + calcCrossTableDataItems(columns: IData[]): IModel[] { + const degridDataItems: IModel[] = []; + const { grid_function_setting } = this.reportUIModel; + columns.forEach((column: IData) => { + const otherParams: IData = {}; + if ( + column.totalCodename && + this.measuresTotalResult[column.totalCodename] + ) { + if ( + grid_function_setting && + grid_function_setting.indexOf('showPercent') !== -1 + ) { + otherParams.customCode = true; + otherParams.scriptCode = ` + if (Object.prototype.hasOwnProperty.call(data, '${ + column.measureTag + }')) { + const value = data['${column.measureTag}'] / ${ + this.measuresTotalResult[column.totalCodename] + }; + let formatValue = data['${column.measureTag}']; + return formatValue + '(' + ibiz.util.text.format(value, '0.##%') + ')' + }`; + } else { + otherParams.customCode = true; + otherParams.scriptCode = ` + if (Object.prototype.hasOwnProperty.call(data, '${column.measureTag}')) { + let formatValue = data['${column.measureTag}']; + return formatValue + }`; + } + } + degridDataItems.push({ + dataType: 25, + id: column.id, + valueType: 'SIMPLE', + format: column.format, + measureTag: column.measureTag, + appId: this.appBIReport.appId, + ...otherParams, + }); + if (column.columnType === 'GROUPGRIDCOLUMN') + degridDataItems.push( + ...this.calcCrossTableDataItems(column.degridColumns), + ); + }); + return degridDataItems; + } + + /** + * @description 转化数据到报表 + * @param {IData[]} items + * @returns {*} {({ model: IModel; options: IData; data: IData[] } | undefined)} + * @memberof CrossTableConverter + */ + translateDataToReport( + items: IData[], + ): { model: IModel; options: IData; data: IData[] } | undefined { + if (!this.measures) return; + const tempModel = clone(this.mockModel); + const dimensionCols = this.calcDimensionCols(items); + const degridColumns = this.calcCrossTableColumns(dimensionCols); + const mockDatas = this.calcCrossTableData(items); + this.calcAggTotalData(mockDatas); + const degridDataItems = this.calcCrossTableDataItems(degridColumns); + Object.assign(tempModel, { degridColumns, degridDataItems }); + const { vars, classList, model } = this.getGridStyle(); + Object.assign(tempModel, model); + const spanMethod = this.calcGridColumnMerge(mockDatas); + Object.assign(tempModel, spanMethod); + return { model: tempModel, options: { vars, classList }, data: mockDatas }; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/gauge-converter.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/gauge-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..394395f1f807132ca03b37364981fc8fec7bd392 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/gauge-converter.ts @@ -0,0 +1,193 @@ +/* eslint-disable camelcase */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { clone } from 'ramda'; +import { IAppBIReportDimension, IAppBIReportMeasure } from '@ibiz/model-core'; +import { EchartConverterBase } from './base/echart-converter-base'; + +/** + * @description 仪表盘转换器 + * @export + * @class GaugeConverter + * @extends {EchartConverterBase} + */ +export class GaugeConverter extends EchartConverterBase { + /** + * @description 仿真模型 + * @type {IModel} + * @memberof GaugeConverter + */ + mockModel: IModel = { + userParam: {}, + dechartLegend: { + id: 'legend', + showLegend: false, + appId: this.appBIReport.appId, + }, + dechartTitle: { + id: 'title', + showTitle: false, + appId: this.appBIReport.appId, + }, + readOnly: true, + autoLoad: true, + dechartSerieses: [], + controlType: 'CHART', + showBusyIndicator: true, + id: this.appBIReport.id, + name: this.appBIReport.id, + appId: this.appBIReport.appId, + codeName: this.appBIReport.id, + caption: this.appBIReport.name, + logicName: this.appBIReport.name, + appDataEntityId: this.appBIReport.appDataEntityId, + }; + + /** + * @description 仿真序列模型 + * @type {IModel} + * @memberof GaugeConverter + */ + mockSerieModel: IModel = { + seriesType: 'gauge', + echartsType: 'gauge', + chartDataSetId: '0', + seriesLayoutBy: 'column', + enableChartDataSet: true, + chartCoordinateSystemId: '0', + appId: this.appBIReport.appId, + }; + + /** + * @description 计算序列模型 + * @param {IAppBIReportMeasure[]} measures + * @param {IAppBIReportDimension} dimension + * @param {IAppBIReportDimension} [groupDimension] + * @returns {*} {IModel[]} + * @memberof GaugeConverter + */ + calcSeriesModel( + measures: IAppBIReportMeasure[], + dimension: IAppBIReportDimension, + groupDimension?: IAppBIReportDimension, + ): IModel[] { + const seriesModels: IModel[] = []; + measures.forEach((item, index) => { + const seriesModel = clone(this.mockSerieModel); + const seriesInput = { + caption: item.measureName, + serieText: item.measureName, + catalogName: item.measureName, + value: item.measureTag!.toLowerCase(), + catalogField: item.measureTag!.toLowerCase(), + }; + Object.assign(seriesModel, { + id: `${seriesModel.seriesType}_${index}`, + caption: item.measureName, + serieText: item.measureName, + catalogName: item.measureName, + valueField: item.measureTag!.toLowerCase(), + catalogField: item.measureTag!.toLowerCase(), + }); + // 分组维度 + if (groupDimension) + Object.assign(seriesModel, { + seriesCodeListId: groupDimension.appCodeListId, + seriesField: groupDimension.dimensionTag!.toLowerCase(), + }); + seriesModels.push(seriesModel); + }); + return seriesModels; + } + + /** + * @description 转化数据到报表 + * @param {IData[]} items + * @returns {*} {({ model: IModel; options: IData; data: IData[] } | undefined)} + * @memberof GaugeConverter + */ + translateDataToReport( + items: IData[], + ): { model: IModel; options: IData; data: IData[] } | undefined { + if (!this.measures.length) return; + const model = clone(this.mockModel); + const seriesModels = this.calcSeriesModel( + this.measures, + this.dimensions[0], + this.groupDimension, + ); + if (model.dechartSerieses) model.dechartSerieses.push(...seriesModels); + // 图表颜色 + const chartColorParams = this.getChartColorParams(); + Object.assign(model.userParam, chartColorParams); + // 处理序列参数 + const serieParam = this.commputeSeriesParam(); + model.dechartSerieses?.forEach((series: IData) => { + Object.assign(series, { + userParam: serieParam, + }); + }); + return { model, options: {}, data: items }; + } + + /** + * @description 计算序列参数 + * @returns {*} {IData} + * @memberof GaugeConverter + */ + commputeSeriesParam(): IData { + const { + series_max, + series_detail_fontsize, + series_detail_fontstyle, + series_detail_fontcolor, + } = this.reportUIModel; + const seriesParams: IData = { + 'EC.radius': '90%', + 'EC.startAngle': '220', + 'EC.endAngle': '-40', + 'EC.splitNumber': '4', + 'EC.tooltip': `{ + formatter:function(param){ + return "
"+ param.seriesId +"
"+ param.marker + param.name+""+ param.value+"
" + } + }`, + 'EC.axisTick': + '{"splitNumber":5,"distance":5,"lineStyle":{"width":2,"color":"#ddd"}}', + 'EC.splitLine': + '{"length":10,"distance":5,"lineStyle":{"width":2,"color":"#ddd"}}', + 'EC.title': '{"show":false}', + 'EC.pointer': '{"length":"50%"}', + 'EC.detail': '{"offsetCenter":[0,"60%"]}', + 'EC.progress': '{"show":true,"width":60}', + 'EC.axisLine': + '{"lineStyle":{"width":60,"color":[[1,"rgb(245, 245, 245)"]]}}', + 'EC.axisLabel': + '{"distance":76,"color":"#333","fontSize":12,"formatter": "function (num){return (num * 100) / 100 + `%` }"}', + }; + + const options: IData = { + offsetCenter: [0, '60%'], + fontSize: series_detail_fontsize, + color: series_detail_fontcolor, + }; + if (series_detail_fontstyle) { + if (series_detail_fontstyle === 'bold') { + options.fontWeight = series_detail_fontstyle; + } else { + options.fontWeight = 'normal'; + options.fontStyle = series_detail_fontstyle; + } + } + seriesParams['EC.detail'] = JSON.stringify(options); + if (series_max) { + seriesParams['EC.max'] = JSON.stringify(series_max); + seriesParams['EC.axisLabel'] = JSON.stringify({ + distance: 76, + color: '#333', + fontSize: 12, + formatter: `function (num){return (num * 100) / '${series_max}' + '%' }`, + }); + } + return seriesParams; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/grid-converter.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/grid-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..01207281fc5cdcd5fe4fc7c6a5081d07825dcdd7 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/grid-converter.ts @@ -0,0 +1,150 @@ +import { clone } from 'ramda'; +import { GridConverterBase } from './base'; + +/** + * @description 表格转化器 + * @export + * @class GridConverter + * @extends {GridConverterBase} + */ +export class GridConverter extends GridConverterBase { + /** + * @description 仿真模型 + * @type {IModel} + * @memberof GridConverter + */ + mockModel: IModel = { + controlParam: { id: this.appBIReport.id, appId: this.appBIReport.appId }, + fetchControlAction: { + id: 'fetch', + appDEMethodId: 'fetchdefault', + appId: this.appBIReport.appId, + appDataEntityId: this.appBIReport.appDataEntityId, + }, + pagingMode: 1, + autoLoad: true, + pagingSize: 20, + aggMode: 'NONE', + groupMode: 'NONE', + sortMode: 'REMOTE', + singleSelect: true, + columnEnableLink: 2, + controlType: 'GRID', + columnEnableFilter: 2, + modelType: 'PSDEGRID', + enablePagingBar: false, + enableCustomized: false, + showBusyIndicator: true, + id: this.appBIReport.id, + name: this.appBIReport.id, + modelId: this.appBIReport.id, + codeName: this.appBIReport.id, + appId: this.appBIReport.appId, + logicName: this.appBIReport.name, + appDataEntityId: this.appBIReport.appDataEntityId, + }; + + /** + * @description 计算表格列模型 + * @returns {*} {IModel[]} + * @memberof GridConverter + */ + calcGridColumns(): IModel[] { + const deGridColumns: IModel[] = []; + if (this.dimensions.length > 0) { + deGridColumns.push( + ...this.dimensions.map(item => { + const codeName = item.dimensionTag!.toLowerCase(); + return { + width: 150, + id: codeName, + widthUnit: 'STAR', + enableSort: false, + codeName, + measureTag: codeName, + dataItemName: codeName, + appDEFieldId: codeName, + columnType: 'DEFGRIDCOLUMN', + caption: item.dimensionName, + appId: this.appBIReport.appId, + appCodeListId: item.appCodeListId, + ...this.calcGridColumnStyle(false), + }; + }), + ); + } + if (this.measures.length > 0) { + deGridColumns.push( + ...this.measures.map(item => { + const codeName = item.measureTag!.toLowerCase(); + return { + width: 150, + id: codeName, + widthUnit: 'STAR', + enableSort: false, + codeName, + valueType: 'SIMPLE', + measureTag: codeName, + dataItemName: codeName, + appDEFieldId: codeName, + format: item.jsonFormat, + caption: item.measureName, + columnType: 'DEFGRIDCOLUMN', + appId: this.appBIReport.appId, + appCodeListId: item.appCodeListId, + ...this.calcGridColumnStyle(), + }; + }), + ); + } + return deGridColumns; + } + + /** + * @description 计算表格数据项 + * @param {IModel[]} gridColumns + * @returns {*} {IModel[]} + * @memberof GridConverter + */ + calcGridDataItems(gridColumns: IModel[]): IModel[] { + const deGridDataItems: IData[] = []; + gridColumns.forEach((column: IData) => { + deGridDataItems.push({ + dataType: 25, + id: column.id, + valueType: 'SIMPLE', + format: column.format, + appId: this.appBIReport.appId, + measureTag: column.measureTag, + }); + if (column.columnType === 'GROUPGRIDCOLUMN') + deGridDataItems.push(...this.calcGridDataItems(column.degridColumns)); + }); + return deGridDataItems; + } + + /** + * @description 转化数据到报表 + * @param {IData[]} items + * @returns {*} {({ model: IModel; options: IData; data: IData[] } | undefined)} + * @memberof GridConverter + */ + translateDataToReport( + items: IData[], + ): { model: IModel; options: IData; data: IData[] } | undefined { + if (!this.measures) return; + const tempModel = clone(this.mockModel); + const degridColumns = this.calcGridColumns(); + const degridDataItems = this.calcGridDataItems(degridColumns); + Object.assign(tempModel, { degridColumns, degridDataItems }); + const { vars, classList, model } = this.getGridStyle(); + Object.assign(tempModel, model); + const spanMethod = this.calcGridColumnMerge(items); + Object.assign(tempModel, spanMethod); + return { + data: items, + model: tempModel, + options: { vars, classList }, + }; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/index.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..c848d818c6d30f5410ac1974602503d7f98d2995 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/index.ts @@ -0,0 +1,16 @@ +export * from './base'; +export { AreaConverter } from './area-converter'; +export { CrossTableConverter } from './cross-table-converter'; +export { GaugeConverter } from './gauge-converter'; +export { GridConverter } from './grid-converter'; +export { MultiSeriesBarConverter } from './multi-series-bar-converter'; +export { MultiSeriesColConverter } from './multi-series-col-converter'; +export { MultiSeriesLineConverter } from './multi-series-line-converter'; +export { NumberConverter } from './number-converter'; +export { PieConverter } from './pie-converter'; +export { RadarConverter } from './radar-converter'; +export { ScatterConverter } from './scatter-converter'; +export { StackBarConverter } from './stack-bar-converter'; +export { StackColConverter } from './stack-col-converter'; +export { ZoneColConverter } from './zone-col-converter'; +export { ZoneLineConverter } from './zone-line-converter'; diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/multi-series-bar-converter.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/multi-series-bar-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..a994a19b540ebe4a9303197ee1c954f611ecb103 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/multi-series-bar-converter.ts @@ -0,0 +1,85 @@ +import { BarConverterBase } from './base'; + +/** + * @description 多序列条形图转换器 + * @export + * @class MultiSeriesBarConverter + * @extends {BarConverterBase} + */ +export class MultiSeriesBarConverter extends BarConverterBase { + /** + * @description 仿真模型 + * @type {IModel} + * @memberof MultiSeriesBarConverter + */ + mockModel: IModel = { + dechartLegend: { + id: 'legend', + showLegend: false, + appId: this.appBIReport.appId, + }, + dechartTitle: { + id: 'title', + showTitle: false, + appId: this.appBIReport.appId, + }, + chartXAxises: [ + { + id: '0', + type: 'category', + position: 'bottom', + echartsPos: 'xAxis', + name: 'axis_xAxis_0', + echartsType: 'category', + appId: this.appBIReport.appId, + }, + ], + chartYAxises: [ + { + id: '0', + type: 'numeric', + position: 'left', + echartsPos: 'yAxis', + echartsType: 'value', + name: 'axis_yAxis_0', + appId: this.appBIReport.appId, + }, + ], + readOnly: true, + autoLoad: true, + dechartSerieses: [], + controlType: 'CHART', + showBusyIndicator: true, + id: this.appBIReport.id, + name: this.appBIReport.id, + appId: this.appBIReport.appId, + codeName: this.appBIReport.id, + caption: this.appBIReport.name, + logicName: this.appBIReport.name, + appDataEntityId: this.appBIReport.appDataEntityId, + userParam: {}, + }; + + /** + * @description 仿真序列模型 + * @type {IModel} + * @memberof MultiSeriesBarConverter + */ + mockSerieModel: IModel = { + chartSeriesEncode: { + id: '0', + type: 'XY', + name: '坐标系编码', + chartXAxisId: '0', + chartYAxisId: '0', + appId: this.appBIReport.appId, + }, + seriesType: 'bar', + echartsType: 'bar', + chartDataSetId: '0', + seriesLayoutBy: 'column', + enableChartDataSet: true, + chartCoordinateSystemId: '0', + appId: this.appBIReport.appId, + }; +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/multi-series-col-converter.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/multi-series-col-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..8196c23acd84665386351380de92393f5d29dd8b --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/multi-series-col-converter.ts @@ -0,0 +1,85 @@ +import { MultiSeriesConverter } from './base'; + +/** + * @description 多序列柱状图转换器 + * @export + * @class MultiSeriesColConverter + * @extends {MultiSeriesConverter} + */ +export class MultiSeriesColConverter extends MultiSeriesConverter { + /** + * @description 仿真模型 + * @type {IModel} + * @memberof MultiSeriesColConverter + */ + mockModel: IModel = { + userParam: {}, + dechartLegend: { + id: 'legend', + showLegend: false, + appId: this.appBIReport.appId, + }, + dechartTitle: { + id: 'title', + showTitle: false, + appId: this.appBIReport.appId, + }, + chartXAxises: [ + { + id: '0', + type: 'category', + echartsPos: 'xAxis', + position: 'bottom', + name: 'axis_xAxis_0', + echartsType: 'category', + appId: this.appBIReport.appId, + }, + ], + chartYAxises: [ + { + id: '0', + type: 'numeric', + position: 'left', + echartsPos: 'yAxis', + name: 'axis_yAxis_0', + echartsType: 'value', + appId: this.appBIReport.appId, + }, + ], + readOnly: true, + autoLoad: true, + dechartSerieses: [], + controlType: 'CHART', + showBusyIndicator: true, + id: this.appBIReport.id, + name: this.appBIReport.id, + appId: this.appBIReport.appId, + codeName: this.appBIReport.id, + caption: this.appBIReport.name, + logicName: this.appBIReport.name, + appDataEntityId: this.appBIReport.appDataEntityId, + }; + + /** + * @description 仿真序列模型 + * @type {IModel} + * @memberof MultiSeriesColConverter + */ + mockSerieModel: IModel = { + chartSeriesEncode: { + id: '0', + type: 'XY', + name: '坐标系编码', + chartXAxisId: '0', + chartYAxisId: '0', + appId: this.appBIReport.appId, + }, + seriesType: 'bar', + echartsType: 'bar', + chartDataSetId: '0', + seriesLayoutBy: 'column', + enableChartDataSet: true, + chartCoordinateSystemId: '0', + appId: this.appBIReport.appId, + }; +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/multi-series-line-converter.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/multi-series-line-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..88ca58f7c53272f2b8bfaba3c258564da6c719cd --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/multi-series-line-converter.ts @@ -0,0 +1,103 @@ +/* eslint-disable camelcase */ +import { MultiSeriesConverter } from './base'; + +/** + * @description 多序列折线图转换器 + * @export + * @class MultiSeriesLineConverter + * @extends {MultiSeriesConverter} + */ +export class MultiSeriesLineConverter extends MultiSeriesConverter { + /** + * @description 仿真模型 + * @type {IModel} + * @memberof MultiSeriesLineConverter + */ + mockModel: IModel = { + userParam: {}, + dechartLegend: { + id: 'legend', + showLegend: false, + appId: this.appBIReport.appId, + }, + dechartTitle: { + id: 'title', + showTitle: false, + appId: this.appBIReport.appId, + }, + chartXAxises: [ + { + id: '0', + type: 'category', + position: 'bottom', + echartsPos: 'xAxis', + name: 'axis_xAxis_0', + echartsType: 'category', + appId: this.appBIReport.appId, + }, + ], + chartYAxises: [ + { + id: '0', + type: 'numeric', + position: 'left', + echartsPos: 'yAxis', + echartsType: 'value', + name: 'axis_yAxis_0', + appId: this.appBIReport.appId, + }, + ], + readOnly: true, + autoLoad: true, + dechartSerieses: [], + controlType: 'CHART', + showBusyIndicator: true, + id: this.appBIReport.id, + name: this.appBIReport.id, + appId: this.appBIReport.appId, + codeName: this.appBIReport.id, + caption: this.appBIReport.name, + logicName: this.appBIReport.name, + appDataEntityId: this.appBIReport.appDataEntityId, + }; + + /** + * @description 仿真序列模型 + * @type {IModel} + * @memberof MultiSeriesLineConverter + */ + mockSerieModel: IModel = { + chartSeriesEncode: { + id: '0', + type: 'XY', + name: '坐标系编码', + chartXAxisId: '0', + chartYAxisId: '0', + appId: this.appBIReport.appId, + }, + seriesType: 'line', + echartsType: 'line', + chartDataSetId: '0', + seriesLayoutBy: 'column', + enableChartDataSet: true, + chartCoordinateSystemId: '0', + appId: this.appBIReport.appId, + }; + + /** + * @description 获取标签参数 + * @param {IModel} seriesModel + * @param {IData[]} items + * @returns {*} {IData} + * @memberof MultiSeriesLineConverter + */ + getChartLabelParams(seriesModel: IModel, items: IData[]): IData { + const { series_label_position } = this.reportUIModel; + const options = super.getChartLabelParams(seriesModel, items); + const tempObj = JSON.parse(options['EC.label']); + // 折线类图标签位置只有顶部或者底部两种选择 + tempObj.position = + series_label_position === 'bottom' ? series_label_position : 'top'; + return { 'EC.label': JSON.stringify(tempObj) }; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/number-converter.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/number-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..f52363b9762e8487af60456dc65af340e7ddeade --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/number-converter.ts @@ -0,0 +1,44 @@ +/* eslint-disable camelcase */ +import { ConverterBase } from './base/converter-base'; +/** + * @description 数值图表转换器 + * @export + * @class NumberConverter + * @extends {ConverterBase} + */ +export class NumberConverter extends ConverterBase { + /** + * @description 转化数据到报表 + * @param {IData[]} items + * @returns {*} {({ model: IModel; options: IData; data: IData[] } | undefined)} + * @memberof NumberConverter + */ + translateDataToReport( + items: IData[], + ): { model: IModel; options: IData; data: IData[] } | undefined { + const { + period, + chart_type, + number_fontsize, + number_yoy_show, + number_qoq_show, + number_fontcolor, + number_yoy_value, + number_qoq_value, + number_fontstyle, + } = this.reportUIModel; + const model: IModel = { + period, + chart_type, + number_fontsize, + number_yoy_show, + number_qoq_show, + number_fontcolor, + number_yoy_value, + number_qoq_value, + number_fontstyle, + measure: this.measures[0], + }; + return { model, options: {}, data: items }; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/pie-converter.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/pie-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..0980fc6ed6897ab774cd30ef83223676572cb72e --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/pie-converter.ts @@ -0,0 +1,144 @@ +/* eslint-disable eqeqeq */ +/* eslint-disable camelcase */ +/* eslint-disable func-names */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { MultiSeriesConverter } from './base'; + +/** + * @description 饼图转换器 + * @export + * @class PieConverter + * @extends {MultiSeriesConverter} + */ +export class PieConverter extends MultiSeriesConverter { + /** + * @description 仿真模型 + * @type {IModel} + * @memberof PieConverter + */ + mockModel: IModel = { + userParam: {}, + dechartLegend: { + id: 'legend', + showLegend: false, + appId: this.appBIReport.appId, + }, + dechartTitle: { + id: 'title', + showTitle: false, + appId: this.appBIReport.appId, + }, + readOnly: true, + autoLoad: true, + dechartSerieses: [], + controlType: 'CHART', + id: this.appBIReport.id, + showBusyIndicator: true, + name: this.appBIReport.id, + appId: this.appBIReport.appId, + codeName: this.appBIReport.id, + caption: this.appBIReport.name, + logicName: this.appBIReport.name, + appDataEntityId: this.appBIReport.appDataEntityId, + }; + + /** + * @description 仿真序列模型 + * @type {IModel} + * @memberof PieConverter + */ + mockSerieModel: IModel = { + seriesType: 'pie', + echartsType: 'pie', + chartCoordinateSystemId: '0', + chartDataSetId: '0', + seriesLayoutBy: 'column', + enableChartDataSet: true, + appId: this.appBIReport.appId, + }; + + /** + * @description 转化数据到报表 + * @param {IData[]} items + * @returns {*} {({ model: IModel; options: IData; data: IData[] } | undefined)} + * @memberof PieConverter + */ + translateDataToReport( + items: IData[], + ): { model: IModel; options: IData; data: IData[] } | undefined { + const report = super.translateDataToReport(items); + if (!report) return; + const { model } = report; + // 图例参数 + const legendParams = this.getChartLegendParams(); + Object.assign(model.userParam, legendParams); + + // 图表颜色 + const chartColorParams = this.getChartColorParams(); + Object.assign(model.userParam, chartColorParams); + + // 处理序列参数 + model.dechartSerieses?.forEach((series: IData) => { + Object.assign(series, { + userParam: { + ...this.getChartLabelParams(series, items), + }, + }); + }); + return report; + } + + /** + * @description 获取标签参数 + * @param {IModel} seriesModel + * @param {IData[]} items + * @returns {*} {IData} + * @memberof PieConverter + */ + getChartLabelParams(seriesModel: IModel, items: IData[]): IData { + const { series_label_data_range, series_label_percentage } = + this.reportUIModel; + const tempOption = super.getChartLabelParams(seriesModel, items); + const options = JSON.parse(tempOption['EC.label']); + const labelLayoutOption: IData = {}; + delete options.position; + if (series_label_data_range && series_label_data_range !== 'all') { + // 展示最大最小值的时候没有展示的label要连同引导线一起不展示 + const labelLayout = function (_params: IData) { + if (_params.text == '') { + return { + height: 0, + labelLinePoints: [0, 0], + }; + } + }; + Object.assign(labelLayoutOption, { 'EC.labelLayout': labelLayout }); + const { min, max } = this.calcMaxMin(seriesModel, items); + options.formatter = `function(param) { + let tempName = param.name; + const { data } = param; + if(param.value[0] === ${max} || param.value[0] === ${min}){ + if(${series_label_percentage} == '1'){ + return tempName + ':(' + param.percent + '%' + ')'; + } + return tempName + ':' + param.value[0]; + } + return ''; + }`; + } + if (series_label_data_range && series_label_data_range === 'all') { + options.formatter = `function(param) { + let tempName = param.name; + const { data } = param; + if(${series_label_percentage} == '1'){ + return tempName + ':(' + param.percent + '%' + ')'; + } + return tempName + ':' + param.value[0]; + }`; + } + return { + 'EC.label': JSON.stringify(options), + ...labelLayoutOption, + }; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/radar-converter.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/radar-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..6560947d31d520ade9ef6b78f5d1768113b5580c --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/radar-converter.ts @@ -0,0 +1,142 @@ +/* eslint-disable eqeqeq */ +/* eslint-disable camelcase */ +/* eslint-disable no-unused-expressions */ +import { MultiSeriesConverter } from './base'; + +/** + * @description 雷达图转换器 + * @export + * @class RadarConverter + * @extends {MultiSeriesConverter} + */ +export class RadarConverter extends MultiSeriesConverter { + /** + * @description 仿真模型 + * @type {IModel} + * @memberof RadarConverter + */ + mockModel: IModel = { + userParam: {}, + dechartLegend: { + id: 'legend', + showLegend: false, + appId: this.appBIReport.appId, + }, + dechartTitle: { + id: 'title', + showTitle: false, + appId: this.appBIReport.appId, + }, + readOnly: true, + autoLoad: true, + dechartSerieses: [], + controlType: 'CHART', + showBusyIndicator: true, + id: this.appBIReport.id, + name: this.appBIReport.id, + appId: this.appBIReport.appId, + codeName: this.appBIReport.id, + caption: this.appBIReport.name, + logicName: this.appBIReport.name, + appDataEntityId: this.appBIReport.appDataEntityId, + }; + + /** + * @description 仿真序列模型 + * @type {IModel} + * @memberof RadarConverter + */ + mockSerieModel: IModel = { + echartsType: 'radar', + seriesType: 'radar', + chartDataSetId: '0', + seriesLayoutBy: 'column', + enableChartDataSet: true, + chartCoordinateSystemId: '0', + appId: this.appBIReport.appId, + }; + + /** + * @description 获取标签参数 + * @param {IModel} seriesModel + * @param {IData[]} items + * @returns {*} {IData} + * @memberof RadarConverter + */ + getChartLabelParams(seriesModel: IModel, items: IData[]): IData { + const { + series_label_show, + series_label_fontstyle, + series_label_fontsize, + series_label_fontcolor, + series_label_position, + series_label_data_range, + } = this.reportUIModel; + + const options: IData = { + show: series_label_show == '1', + position: series_label_position, + }; + + // 标签(字体样式) + if (series_label_fontstyle) { + series_label_fontstyle === 'bold' + ? (options.fontWeight = series_label_fontstyle) + : (options.fontStyle = series_label_fontstyle); + } + + if (series_label_fontsize) { + options.fontSize = Number(series_label_fontsize); + } + if (series_label_fontcolor) { + options.color = series_label_fontcolor; + } + + // 显示数据范围 + if (series_label_data_range && series_label_data_range !== 'all') { + const { min, max } = this.calcMaxMin(seriesModel, items); + options.formatter = `function(param) { + if(param.value === ${max} || param.value === ${min}){ + return param.value; + } + return ''; + }`; + } + if (series_label_data_range && series_label_data_range === 'all') { + options.formatter = `function(param) { + return param.value; + }`; + } + return { 'EC.label': JSON.stringify(options) }; + } + + /** + * @description 转化数据到报表 + * @param {IData[]} items + * @returns {*} {({ model: IModel; options: IData; data: IData[] } | undefined)} + * @memberof RadarConverter + */ + translateDataToReport( + items: IData[], + ): { model: IModel; options: IData; data: IData[] } | undefined { + const report = super.translateDataToReport(items); + if (!report) return; + const { model } = report; + // 图表颜色 + const chartColorParams = this.getChartColorParams(); + Object.assign(model.userParam, chartColorParams); + // 图例参数 + const legendParams = this.getChartLegendParams(); + Object.assign(model.userParam, legendParams); + // 处理序列参数 + model.dechartSerieses?.forEach((series: IData) => { + Object.assign(series, { + userParam: { + ...this.getChartLabelParams(series, items), + 'EC.name': series.serieText, + }, + }); + }); + return report; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/scatter-converter.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/scatter-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..97d10b4f5dcbe3ee926f4194ca7c2db892fa88be --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/scatter-converter.ts @@ -0,0 +1,100 @@ +import { MultiSeriesConverter } from './base'; + +/** + * @description 散点图转换器 + * @export + * @class ScatterConverter + * @extends {MultiSeriesConverter} + */ +export class ScatterConverter extends MultiSeriesConverter { + /** + * @description 仿真模型 + * @type {IModel} + * @memberof ScatterConverter + */ + mockModel: IModel = { + userParam: {}, + dechartLegend: { + id: 'legend', + showLegend: false, + appId: this.appBIReport.appId, + }, + dechartTitle: { + id: 'title', + showTitle: false, + appId: this.appBIReport.appId, + }, + chartXAxises: [ + { + id: '0', + type: 'category', + position: 'bottom', + echartsPos: 'xAxis', + name: 'axis_xAxis_0', + echartsType: 'category', + appId: this.appBIReport.appId, + }, + ], + chartYAxises: [ + { + id: '0', + type: 'numeric', + position: 'left', + echartsPos: 'yAxis', + echartsType: 'value', + name: 'axis_yAxis_0', + appId: this.appBIReport.appId, + }, + ], + readOnly: true, + autoLoad: true, + dechartSerieses: [], + controlType: 'CHART', + id: this.appBIReport.id, + showBusyIndicator: true, + name: this.appBIReport.id, + appId: this.appBIReport.appId, + codeName: this.appBIReport.id, + caption: this.appBIReport.name, + logicName: this.appBIReport.name, + appDataEntityId: this.appBIReport.appDataEntityId, + }; + + /** + * @description 仿真序列模型 + * @type {IModel} + * @memberof ScatterConverter + */ + mockSerieModel: IModel = { + chartSeriesEncode: { + id: '0', + type: 'XY', + name: '坐标系编码', + chartXAxisId: '0', + chartYAxisId: '0', + appId: this.appBIReport.appId, + }, + chartDataSetId: '0', + seriesType: 'scatter', + echartsType: 'scatter', + seriesLayoutBy: 'column', + enableChartDataSet: true, + chartCoordinateSystemId: '0', + appId: this.appBIReport.appId, + }; + + /** + * @description 获取标签参数 + * @param {IModel} seriesModel + * @param {IData[]} items + * @returns {*} {IData} + * @memberof ScatterConverter + */ + getChartLabelParams(seriesModel: IModel, items: IData[]): IData { + const options = super.getChartLabelParams(seriesModel, items); + const tempObj = JSON.parse(options['EC.label']); + // 散点图标签位置固定在顶部 + tempObj.position = 'top'; + return { 'EC.label': JSON.stringify(tempObj) }; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/stack-bar-converter.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/stack-bar-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3751fd55c67ff279751e8aec4d3b0acdb83a883 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/stack-bar-converter.ts @@ -0,0 +1,108 @@ +import { BarConverterBase } from './base'; + +/** + * @description 堆叠条形图转换器 + * @export + * @class StackBarConverter + * @extends {BarConverterBase} + */ +export class StackBarConverter extends BarConverterBase { + /** + * @description 仿真模型 + * @type {IModel} + * @memberof StackBarConverter + */ + mockModel: IModel = { + userParam: {}, + dechartLegend: { + id: 'legend', + showLegend: false, + appId: this.appBIReport.appId, + }, + dechartTitle: { + id: 'title', + showTitle: false, + appId: this.appBIReport.appId, + }, + + chartXAxises: [ + { + id: '0', + type: 'category', + position: 'bottom', + echartsPos: 'xAxis', + name: 'axis_xAxis_0', + echartsType: 'category', + appId: this.appBIReport.appId, + }, + ], + chartYAxises: [ + { + id: '0', + type: 'numeric', + position: 'left', + echartsPos: 'yAxis', + echartsType: 'value', + name: 'axis_yAxis_0', + appId: this.appBIReport.appId, + }, + ], + readOnly: true, + autoLoad: true, + dechartSerieses: [], + controlType: 'CHART', + showBusyIndicator: true, + id: this.appBIReport.id, + name: this.appBIReport.id, + appId: this.appBIReport.appId, + codeName: this.appBIReport.id, + caption: this.appBIReport.name, + logicName: this.appBIReport.name, + appDataEntityId: this.appBIReport.appDataEntityId, + }; + + /** + * @description 仿真序列模型 + * @type {IModel} + * @memberof StackBarConverter + */ + mockSerieModel: IModel = { + chartSeriesEncode: { + id: '0', + type: 'XY', + name: '坐标系编码', + chartXAxisId: '0', + chartYAxisId: '0', + appId: this.appBIReport.appId, + }, + seriesType: 'bar', + echartsType: 'bar', + chartDataSetId: '0', + seriesLayoutBy: 'column', + enableChartDataSet: true, + chartCoordinateSystemId: '0', + appId: this.appBIReport.appId, + }; + + /** + * @description 转化数据到报表 + * @param {IData[]} items + * @returns {*} {({ model: IModel; options: IData; data: IData[] } | undefined)} + * @memberof StackBarConverter + */ + translateDataToReport( + items: IData[], + ): { model: IModel; options: IData; data: IData[] } | undefined { + const report = super.translateDataToReport(items); + if (report) { + const { model } = report; + // 处理序列参数-添加堆叠参数 + model.dechartSerieses?.forEach((series: IData) => { + Object.assign(series.userParam, { + 'EC.stack': 'stackcol', + }); + }); + } + return report; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/stack-col-converter.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/stack-col-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..c5159daed93930c88f27b6731f57b499f2edc771 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/stack-col-converter.ts @@ -0,0 +1,108 @@ +import { MultiSeriesConverter } from './base'; + +/** + * @description 堆叠柱状图转换器 + * @export + * @class StackColConverter + * @extends {MultiSeriesConverter} + */ +export class StackColConverter extends MultiSeriesConverter { + /** + * @description 仿真模型 + * @type {IModel} + * @memberof StackColConverter + */ + mockModel: IModel = { + userParam: {}, + dechartLegend: { + id: 'legend', + showLegend: false, + appId: this.appBIReport.appId, + }, + dechartTitle: { + id: 'title', + showTitle: false, + appId: this.appBIReport.appId, + }, + + chartXAxises: [ + { + id: '0', + type: 'category', + position: 'bottom', + echartsPos: 'xAxis', + name: 'axis_xAxis_0', + echartsType: 'category', + appId: this.appBIReport.appId, + }, + ], + chartYAxises: [ + { + id: '0', + type: 'numeric', + position: 'left', + echartsPos: 'yAxis', + echartsType: 'value', + name: 'axis_yAxis_0', + appId: this.appBIReport.appId, + }, + ], + readOnly: true, + autoLoad: true, + dechartSerieses: [], + controlType: 'CHART', + id: this.appBIReport.id, + showBusyIndicator: true, + name: this.appBIReport.id, + appId: this.appBIReport.appId, + codeName: this.appBIReport.id, + caption: this.appBIReport.name, + logicName: this.appBIReport.name, + appDataEntityId: this.appBIReport.appDataEntityId, + }; + + /** + * @description 仿真序列模型 + * @type {IModel} + * @memberof StackColConverter + */ + mockSerieModel: IModel = { + chartSeriesEncode: { + id: '0', + type: 'XY', + name: '坐标系编码', + chartXAxisId: '0', + chartYAxisId: '0', + appId: this.appBIReport.appId, + }, + seriesType: 'bar', + echartsType: 'bar', + chartDataSetId: '0', + seriesLayoutBy: 'column', + enableChartDataSet: true, + chartCoordinateSystemId: '0', + appId: this.appBIReport.appId, + }; + + /** + * @description 转化数据到报表 + * @param {IData[]} items + * @returns {*} {({ model: IModel; options: IData; data: IData[] } | undefined)} + * @memberof StackColConverter + */ + translateDataToReport( + items: IData[], + ): { model: IModel; options: IData; data: IData[] } | undefined { + const report = super.translateDataToReport(items); + if (report) { + const { model } = report; + // 处理序列参数-添加堆叠参数 + model.dechartSerieses?.forEach((series: IData) => { + Object.assign(series.userParam, { + 'EC.stack': 'stackcol', + }); + }); + } + return report; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/utils/chart-util.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/utils/chart-util.ts new file mode 100644 index 0000000000000000000000000000000000000000..753cca82bc6b37f618db1cf7ad444d005adc221a --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/utils/chart-util.ts @@ -0,0 +1,85 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +export class ChartUtil { + /** + * @description x轴标签标题 + * @static + * @returns {*} + * @memberof ChartUtil + */ + static xAxisLabel() { + const options: IData = {}; + options.formatter = `function(param) { + if (param && param.includes(' ')) { + const time = new Date(param); + if (time.toString() !== 'Invalid Date') { + return time.toLocaleDateString().replaceAll('/','-'); + } + } + if(param.indexOf('_') < 0){ + return param; + } + const str = param.split('_').pop(); + if(str.length > 4){ + return str.slice(0,4) + '...' + } + return str; + }`; + return options; + } + + /** + * @description 使用间隔的时候加上省略限制 + * @static + * @param {number} [labelInterval=1] + * @returns {*} + * @memberof ChartUtil + */ + static computeLabelEllipsis(labelInterval: number = 1) { + return { + width: 60 * (labelInterval > 0 ? labelInterval : 1), + overflow: 'truncate', + ellipsis: '...', + }; + } + + /** + * @description 获取图例位置 + * @static + * @param {string} position + * @returns {*} {IData} + * @memberof ChartUtil + */ + static getLegendPosition(position: string): IData { + const legendGap: number = 20; + const options: IData = { + type: 'scroll', + }; + if (position === 'left' || position === 'right') { + options.orient = 'vertical'; + options[position] = legendGap; + options.top = 'middle'; + } + if (position === 'top' || position === 'bottom') { + options.left = 'center'; + options.top = position; + } + if (position === 'left-top') { + options.left = legendGap; + options.top = 'top'; + } + if (position === 'right-top') { + options.right = legendGap; + options.top = 'top'; + } + if (position === 'left-bottom') { + options.left = legendGap; + options.top = 'bottom'; + } + if (position === 'right-bottom') { + options.right = legendGap; + options.top = 'bottom'; + } + return options; + } +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/utils/index.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/utils/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..75f1201a6cd89c30fc26894def511f19bb580238 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/utils/index.ts @@ -0,0 +1 @@ +export { ChartUtil } from './chart-util'; diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/zone-col-converter.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/zone-col-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..abf9603ff48568ce940b1246aab46085c18a14d4 --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/zone-col-converter.ts @@ -0,0 +1,86 @@ +import { ZoneConverterBase } from './base'; + +/** + * @description 分区柱状图转换器 + * @export + * @class ZoneColConverter + * @extends {ZoneConverterBase} + */ +export class ZoneColConverter extends ZoneConverterBase { + /** + * @description 仿真模型 + * @type {IModel} + * @memberof ZoneColConverter + */ + mockModel: IModel = { + userParam: {}, + dechartLegend: { + id: 'legend', + showLegend: false, + appId: this.appBIReport.appId, + }, + dechartTitle: { + id: 'title', + showTitle: false, + appId: this.appBIReport.appId, + }, + + chartXAxises: [ + { + id: '0', + type: 'category', + position: 'bottom', + echartsPos: 'xAxis', + name: 'axis_xAxis_0', + echartsType: 'category', + appId: this.appBIReport.appId, + }, + ], + chartYAxises: [ + { + id: '0', + type: 'numeric', + position: 'left', + echartsPos: 'yAxis', + echartsType: 'value', + name: 'axis_yAxis_0', + appId: this.appBIReport.appId, + }, + ], + readOnly: true, + autoLoad: true, + dechartSerieses: [], + controlType: 'CHART', + id: this.appBIReport.id, + showBusyIndicator: true, + name: this.appBIReport.id, + appId: this.appBIReport.appId, + codeName: this.appBIReport.id, + caption: this.appBIReport.name, + logicName: this.appBIReport.name, + appDataEntityId: this.appBIReport.appDataEntityId, + }; + + /** + * @description 仿真序列模型 + * @type {IModel} + * @memberof ZoneColConverter + */ + mockSerieModel: IModel = { + chartSeriesEncode: { + id: '0', + type: 'XY', + name: '坐标系编码', + chartXAxisId: '0', + chartYAxisId: '0', + appId: this.appBIReport.appId, + }, + seriesType: 'bar', + echartsType: 'bar', + chartDataSetId: '0', + seriesLayoutBy: 'column', + enableChartDataSet: true, + chartCoordinateSystemId: '0', + appId: this.appBIReport.appId, + }; +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-converter/zone-line-converter.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/zone-line-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..734b39c1d58686bfb7c3e499b9548846a94da49c --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-converter/zone-line-converter.ts @@ -0,0 +1,86 @@ +import { ZoneConverterBase } from './base'; + +/** + * @description 分区折线图图转换器 + * @export + * @class ZoneLineConverter + * @extends {ZoneConverterBase} + */ +export class ZoneLineConverter extends ZoneConverterBase { + /** + * @description 仿真模型 + * @type {IModel} + * @memberof ZoneLineConverter + */ + mockModel: IModel = { + userParam: {}, + dechartLegend: { + id: 'legend', + showLegend: false, + appId: this.appBIReport.appId, + }, + dechartTitle: { + id: 'title', + showTitle: false, + appId: this.appBIReport.appId, + }, + + chartXAxises: [ + { + id: '0', + type: 'category', + position: 'bottom', + echartsPos: 'xAxis', + name: 'axis_xAxis_0', + echartsType: 'category', + appId: this.appBIReport.appId, + }, + ], + chartYAxises: [ + { + id: '0', + type: 'numeric', + position: 'left', + echartsPos: 'yAxis', + echartsType: 'value', + name: 'axis_yAxis_0', + appId: this.appBIReport.appId, + }, + ], + readOnly: true, + autoLoad: true, + dechartSerieses: [], + controlType: 'CHART', + id: this.appBIReport.id, + showBusyIndicator: true, + name: this.appBIReport.id, + appId: this.appBIReport.appId, + codeName: this.appBIReport.id, + caption: this.appBIReport.name, + logicName: this.appBIReport.name, + appDataEntityId: this.appBIReport.appDataEntityId, + }; + + /** + * @description 仿真序列模型 + * @type {IModel} + * @memberof ZoneLineConverter + */ + mockSerieModel: IModel = { + chartSeriesEncode: { + id: '0', + type: 'XY', + name: '坐标系编码', + chartXAxisId: '0', + chartYAxisId: '0', + appId: this.appBIReport.appId, + }, + seriesType: 'line', + echartsType: 'line', + chartDataSetId: '0', + seriesLayoutBy: 'column', + enableChartDataSet: true, + chartCoordinateSystemId: '0', + appId: this.appBIReport.appId, + }; +} diff --git a/packages/runtime/src/controller/control/report-panel/generator/bi-generator.ts b/packages/runtime/src/controller/control/report-panel/generator/bi-generator.ts index b1a1c887983d23465e3bc0561049cd5bd6be4294..a6c61722f7d1e4e58853d0771daddf4c7772bc95 100644 --- a/packages/runtime/src/controller/control/report-panel/generator/bi-generator.ts +++ b/packages/runtime/src/controller/control/report-panel/generator/bi-generator.ts @@ -1,41 +1,80 @@ +import { ConverterBase } from './bi-converter/base'; import { ReportPanelBaseGenerator } from './base-generator'; +import { ConverterFactory } from './bi-converter/converter-factory'; + /** - * BI报表相关 - * - * @author tony001 - * @date 2024-06-18 13:06:30 + * @description BI报表生成器 * @export * @class BIReportPanelGenerator * @extends {ReportPanelBaseGenerator} */ export class BIReportPanelGenerator extends ReportPanelBaseGenerator { /** - * 初始化配置 - * - * @author tony001 - * @date 2024-06-26 16:06:54 - * @return {*} {Promise} + * @description 转换器 + * @type {BaseConverter} + * @memberof BIReportPanelGenerator + */ + converter?: ConverterBase; + + /** + * @description 报表类型 + * @type {string} + * @memberof ReportPanelBaseGenerator + */ + reportType?: string; + + /** + * @description 初始化配置 + * @returns {*} {Promise} + * @memberof BIReportPanelGenerator */ public async initConfig(): Promise { - const { appDEReport } = this.model; - const { appBIReport } = appDEReport!; - const appBISchemeId = appDEReport!.appBISchemeId!.split('.').pop(); - (appBIReport as IData).appBISchemeId = appBISchemeId; - this.config = appBIReport!; + await this.initConverter(); } /** - * 加载数据 - * - * @author tony001 - * @date 2024-06-20 11:06:52 - * @param {IData} data - * @return {*} {Promise} + * @description 初始化转换器 + * @private + * @memberof BIReportPanelGenerator */ - public load(data: IData = {}): Promise { - if (this.protoRef) { - this.protoRef.refresh('ALL'); + private async initConverter(): Promise { + const { appDEReport } = this.model; + if (!appDEReport || !appDEReport.appBIReport) return; + const { reportUIModel } = appDEReport.appBIReport; + if (reportUIModel) { + const tempReportUIModel = JSON.parse(reportUIModel); + // 仅处理建模平台创建出来报表 + if (tempReportUIModel.chart_type) { + this.reportType = tempReportUIModel.chart_type; + this.converter = ConverterFactory.createConverter( + tempReportUIModel.chart_type, + appDEReport.appBIReport, + this.reportPanel.context, + this.reportPanel.params, + ); + await this.converter?.init(); + } } - return Promise.resolve(data); + } + + /** + * @description 生成 + * @param {IData[]} items + * @returns {*} {({ + * model: IModel; + * options: IData; + * data: IData[]; + * } + * | undefined)} + * @memberof BIReportPanelGenerator + */ + public generate(items: IData[]): + | { + model: IModel; + options: IData; + data: IData[]; + } + | undefined { + return this.converter?.translateDataToReport(items); } } diff --git a/packages/runtime/src/controller/control/report-panel/generator/index.ts b/packages/runtime/src/controller/control/report-panel/generator/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..6dcd011f2cf0185f38b80a6684db68a86abb518b --- /dev/null +++ b/packages/runtime/src/controller/control/report-panel/generator/index.ts @@ -0,0 +1,5 @@ +export { BIReportPanelGenerator } from './bi-generator'; +export { UserReportPanelGenerator } from './user-generator'; +export { ReportPanelBaseGenerator } from './base-generator'; +export { User2ReportPanelGenerator } from './user2-generator'; +export * from './bi-converter'; diff --git a/packages/runtime/src/controller/control/report-panel/index.ts b/packages/runtime/src/controller/control/report-panel/index.ts index 82a8da19ba2c096ef343bb3a1d4e11e6bebfb349..35ca248d56ea4aa149d8e990e6eb82a8d7eb8a34 100644 --- a/packages/runtime/src/controller/control/report-panel/index.ts +++ b/packages/runtime/src/controller/control/report-panel/index.ts @@ -1,2 +1,3 @@ export * from './report-panel.controller'; export * from './report-panel.service'; +export * from './generator'; diff --git a/packages/runtime/src/controller/control/report-panel/report-panel.controller.ts b/packages/runtime/src/controller/control/report-panel/report-panel.controller.ts index c4577f99065152ff3f6638e0496362f4ea135449..fba33f606ce4580646ae7a0ac89ae1c7ea3107dd 100644 --- a/packages/runtime/src/controller/control/report-panel/report-panel.controller.ts +++ b/packages/runtime/src/controller/control/report-panel/report-panel.controller.ts @@ -1,9 +1,9 @@ import { IDEReportPanel, IAppDataEntity } from '@ibiz/model-core'; import { MDCtrlLoadParams, - IReportPanelController, IReportPanelEvent, IReportPanelState, + IReportPanelController, } from '../../../interface'; import { ControlController } from '../../common'; import { ReportPanelService } from './report-panel.service'; @@ -56,53 +56,36 @@ export class ReportPanelController } /** - * 是否为bi报表 - * - * @author tony001 - * @date 2024-06-19 18:06:02 - * @readonly + * @description 获取数据 + * @returns {*} {IData[]} + * @memberof ReportPanelController */ - get isBIReport(): boolean { - if ( - this.state.reportType === 'DESYSBIREPORTS' || - this.state.reportType === 'SYSBICUBE' || - this.state.reportType === 'DESYSBICUBES' || - this.state.reportType === 'ALLSYSBICUBES' || - this.state.reportType === 'SYSBIREPORT' || - this.state.reportType === 'SYSBICUBEREPORTS' || - this.state.reportType === 'ALLSYSBIREPORTS' - ) { - return true; - } - return false; + getData(): IData[] { + return this.state.data; } /** - * 初始化状态 - * + * @description 初始化状态 * @protected * @memberof ReportPanelController */ protected initState(): void { super.initState(); - this.state.data = {}; + this.state.data = []; this.state.searchParams = {}; - this.state.reportType = - (this.model.appDEReport && this.model.appDEReport.reportType) || ''; + this.state.biReport = undefined; } /** - * 初始化方法 - * + * @description 生命周期-创建完成 * @protected * @returns {*} {Promise} + * @memberof ReportPanelController */ protected async onCreated(): Promise { await super.onCreated(); this.generator = ReportPanelGeneratorFactory.getInstance(this.model, this); - if (this.generator) { - await this.generator.initConfig(); - } + await this.generator?.initConfig(); this.dataEntity = await ibiz.hub.getAppDataEntity( this.model.appDataEntityId!, this.model.appId, @@ -112,10 +95,9 @@ export class ReportPanelController } /** - * 挂载 - * + * @description 生命周期-加载完成 * @protected - * @return {*} {Promise} + * @returns {*} {Promise} * @memberof ReportPanelController */ protected async onMounted(): Promise { @@ -127,35 +109,94 @@ export class ReportPanelController } /** - * 销毁 - * + * @description 获取报表参数 * @protected - * @return {*} {Promise} + * @returns {*} {IParams} * @memberof ReportPanelController */ - protected onDestroyed(): Promise { - return super.onDestroyed(); + protected getReportParams(): IParams { + const appBIReport = this.model.appDEReport?.appBIReport; + if (!appBIReport) return {}; + const { + appBICubeId, + reportUIModel, + appBIReportMeasures, + appBIReportDimensions, + } = appBIReport; + const uiModel = reportUIModel ? JSON.parse(reportUIModel) : {}; + const period = uiModel.period?.[0]; + const colSort: IData[] | undefined = uiModel.grid_col_sort; + const result = { + bicubetag: appBICubeId, + bimeasures: + appBIReportMeasures?.map(measure => { + const item: IParams = { name: measure.measureTag?.toLowerCase() }; + if (measure.measureParams) item.param = measure.measureParams; + if (measure.aggMode) item.aggmode = measure.aggMode; + return item; + }) || [], + bidimensions: + appBIReportDimensions?.map(dimension => { + const item: IParams = { name: dimension.dimensionTag?.toLowerCase() }; + if (dimension.dimensionParams) item.param = dimension.dimensionParams; + return item; + }) || [], + bisort: colSort + ?.map(col => `${col.codename.toLowerCase()},${col.sort}`) + .join(';'), + biperiod: period?.params, + }; + return result; } /** - * 加载数据 - * - * @public - * @param {(MDCtrlLoadParams)} [args] - * @return {*} {Promise} + * @description 获取请求过滤参数 + * @param {IParams} [extraParams] + * @returns {*} {Promise} * @memberof ReportPanelController */ - public async load(args: MDCtrlLoadParams = {}): Promise { - if (this.state.reportType && this.isBIReport) { - return {}; + async getFetchParams(extraParams?: IParams): Promise { + const resultParams: IParams = { + ...this.params, + ...this.getReportParams(), + }; + // *请求参数处理 + await this._evt.emit('onBeforeLoad', undefined); + // 合并搜索条件参数,这些参数在onBeforeLoad监听里由外部填入 + Object.assign(resultParams, { + ...this.state.searchParams, + }); + + // 额外附加参数 + if (extraParams) { + Object.assign(resultParams, extraParams); } + return resultParams; + } + + /** + * @description 加载数据 + * @param {MDCtrlLoadParams} [args={}] + * @returns {*} {Promise} + * @memberof ReportPanelController + */ + public async load(args: MDCtrlLoadParams = {}): Promise { await this.startLoading(); try { // *查询参数处理 + const { codeName, appDEReport, appDataEntityId } = this.model; + const reportTag = appDEReport?.appBIReport?.id || codeName!; + const enyityId = + appDEReport?.appBIReport?.appDataEntityId || appDataEntityId!; const { context } = this.handlerAbilityParams(args); const params = await this.getFetchParams(args?.viewParam); - const res = await this.service.fetch(context, params); + const res = await this.service.fetch( + reportTag, + enyityId, + context, + params, + ); this.state.data = res.data; @@ -177,65 +218,25 @@ export class ReportPanelController } /** - * 部件加载后处理 - * - * @author chitanda - * @date 2023-06-21 15:06:44 - * @param {MDCtrlLoadParams} args 本次请求参数 - * @param {IData[]} items 上游处理的数据(默认是后台数据) - * @return {*} {Promise} 返回给后续处理的数据 + * @description 部件加载后处理 + * @param {MDCtrlLoadParams} args + * @param {IData} data + * @returns {*} {Promise} + * @memberof ReportPanelController */ - async afterLoad(args: MDCtrlLoadParams, data: IData): Promise { + async afterLoad(args: MDCtrlLoadParams, data: IData[]): Promise { + this.state.biReport = this.generator.generate(data); return data; } /** - * 获取请求过滤参数(整合了视图参数,各种过滤条件,排序,分页) - * @param {IParams} [extraParams] 额外视图参数,附加在最后 - * @return {*} {Promise} - */ - async getFetchParams(extraParams?: IParams): Promise { - const resultParams: IParams = { - ...this.params, - }; - // *请求参数处理 - await this._evt.emit('onBeforeLoad', undefined); - // 合并搜索条件参数,这些参数在onBeforeLoad监听里由外部填入 - Object.assign(resultParams, { - ...this.state.searchParams, - }); - - // 额外附加参数 - if (extraParams) { - Object.assign(resultParams, extraParams); - } - return resultParams; - } - - /** - * 报表数据 - * - * @return {*} {IData[]} + * @description 刷新 + * @returns {*} {Promise} * @memberof ReportPanelController */ - getData(): IData[] { - if (this.isBIReport) { - return this.generator.protoRef?.state.items || []; - } - return [this.state.data]; - } - - /** - * 部件刷新,走初始加载 - * @date 2023-05-23 03:42:41 - */ async refresh(): Promise { - if (this.isBIReport) { - this.generator.load(); - } else { - this.doNextActive(() => this.load({ isInitialLoad: false }), { - key: 'refresh', - }); - } + this.doNextActive(() => this.load({ isInitialLoad: false }), { + key: 'refresh', + }); } } diff --git a/packages/runtime/src/controller/control/report-panel/report-panel.service.ts b/packages/runtime/src/controller/control/report-panel/report-panel.service.ts index a5167cd09c107e2961e509d6ae00bb3a65c6e442..5730842c053b2e5fdde9587ef5d1444be604c139 100644 --- a/packages/runtime/src/controller/control/report-panel/report-panel.service.ts +++ b/packages/runtime/src/controller/control/report-panel/report-panel.service.ts @@ -1,4 +1,4 @@ -import { IDEReportPanel, IAppDataEntity } from '@ibiz/model-core'; +import { IDEReportPanel } from '@ibiz/model-core'; import { IHttpResponse } from '@ibiz-template/core'; import { ControlService, ControlVO } from '../../../service'; @@ -6,34 +6,30 @@ export class ReportPanelService< T extends IDEReportPanel = IDEReportPanel, > extends ControlService { /** - * 当前部件对应的应用实体对象 - * - * @protected - * @type {IAppDataEntity} - */ - protected dataEntity!: IAppDataEntity; - - /** - * 执行查询报表数据的方法 - * + * @description 查询报表数据 + * @param {string} reportTag 报表标识 + * @param {string} appDataEntityId 报表实体标识 * @param {IContext} context 上下文 * @param {IParams} [params={}] 视图参数 - * @returns {*} {Promise} + * @returns {*} {Promise>} + * @memberof ReportPanelService */ async fetch( + reportTag: string, + appDataEntityId: string, context: IContext, params: IParams = {}, - ): Promise> { - this.dataEntity = await ibiz.hub.getAppDataEntity( - this.model.appDataEntityId!, + ): Promise> { + const dataEntity = await ibiz.hub.getAppDataEntity( + appDataEntityId!, this.model.appId, ); - const url = `${this.dataEntity.deapicodeName2}/report?srfreporttag=${this.model.codeName}`; + const url = `${dataEntity.deapicodeName2}/report?srfreporttag=${reportTag}`; let res = await ibiz.net.request(url, { method: 'post', data: params, }); res = this.handleResponse(res); - return res as IHttpResponse; + return res as IHttpResponse; } } diff --git a/packages/runtime/src/interface/api/state/control/i-api-report-panel.state.ts b/packages/runtime/src/interface/api/state/control/i-api-report-panel.state.ts index 1e2977073c8bd1bfd4f86ab27422d05ecdffb175..abb65ea414201ae0bc874b5adbcb10f9608589ce 100644 --- a/packages/runtime/src/interface/api/state/control/i-api-report-panel.state.ts +++ b/packages/runtime/src/interface/api/state/control/i-api-report-panel.state.ts @@ -27,11 +27,11 @@ export interface IApiReportPanelState extends IApiControlState { /** * @description 报表数据 - * @type {(IApiData | IApiData[])} + * @type {(IApiData[])} * @default {} * @memberof IApiReportPanelState */ - data: IApiData | IApiData[]; + data: IApiData[]; /** * @description 是否正在处理中 @@ -40,12 +40,4 @@ export interface IApiReportPanelState extends IApiControlState { * @memberof IApiReportPanelState */ processing: boolean; - - /** - * @description 报表类型 - * @type {string} - * @default '' - * @memberof IApiReportPanelState - */ - reportType: string; } diff --git a/packages/runtime/src/interface/controller/state/control/i-report-panel.state.ts b/packages/runtime/src/interface/controller/state/control/i-report-panel.state.ts index 29de985703224c535a7bc5fa0ff6c8ecbe75f4bf..0b972270835aa4547a23761fb8ab21894e4dcec0 100644 --- a/packages/runtime/src/interface/controller/state/control/i-report-panel.state.ts +++ b/packages/runtime/src/interface/controller/state/control/i-report-panel.state.ts @@ -8,6 +8,27 @@ import { IControlState } from './i-control.state'; * @extends {IControlState} * @extends {IApiReportPanelState} */ -export interface IReportPanelState - extends IControlState, - IApiReportPanelState {} +export interface IReportPanelState extends IControlState, IApiReportPanelState { + /** + * @description BI报表 + * @type {{ model: IModel, options: IData, data: IData[] }} + * @memberof IReportPanelState + */ + biReport?: { + /** + * @description 模型 + * @type {IModel} + */ + model: IModel; + /** + * @description 配置 + * @type {IData} + */ + options: IData; + /** + * @description 数据 + * @type {IData[]} + */ + data: IData[]; + }; +}