feat(图表): 汇总表支持自定义汇总 #16766 #16890

This commit is contained in:
wisonic
2025-11-04 18:39:49 +08:00
committed by wisonic-s
parent d7925dd2f9
commit 6a336fc296
8 changed files with 152 additions and 21 deletions

View File

@@ -4,11 +4,14 @@ import com.fasterxml.jackson.core.type.TypeReference;
import io.dataease.api.chart.dto.PageInfo;
import io.dataease.api.dataset.union.DatasetGroupInfoDTO;
import io.dataease.chart.charts.impl.DefaultChartHandler;
import io.dataease.constant.DeTypeConstants;
import io.dataease.engine.constant.ExtFieldConstant;
import io.dataease.engine.sql.SQLProvider;
import io.dataease.engine.trans.Dimension2SQLObj;
import io.dataease.engine.trans.ExtWhere2Str;
import io.dataease.engine.trans.Quota2SQLObj;
import io.dataease.engine.utils.Utils;
import io.dataease.extensions.datasource.dto.DatasetTableFieldDTO;
import io.dataease.extensions.datasource.dto.DatasourceRequest;
import io.dataease.extensions.datasource.dto.DatasourceSchemaDTO;
import io.dataease.extensions.datasource.model.SQLMeta;
@@ -16,9 +19,11 @@ import io.dataease.extensions.datasource.provider.Provider;
import io.dataease.extensions.view.dto.*;
import io.dataease.extensions.view.util.ChartDataUtil;
import io.dataease.extensions.view.util.FieldUtil;
import io.dataease.utils.IDUtils;
import io.dataease.utils.JsonUtil;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
@@ -110,7 +115,7 @@ public class TableNormalHandler extends DefaultChartHandler {
var tablePageMode = (String) filterResult.getContext().get("tablePageMode");
var totalPageSql = "SELECT COUNT(*) FROM (" + SQLProvider.createQuerySQLNoSort(sqlMeta, true, view) + ") COUNT_TEMP";
if (StringUtils.isNotEmpty(totalPageSql) && StringUtils.equalsIgnoreCase(tablePageMode, "page")) {
if (StringUtils.equalsIgnoreCase(tablePageMode, "page")) {
totalPageSql = provider.rebuildSQL(totalPageSql, sqlMeta, crossDs, dsMap);
datasourceRequest.setQuery(totalPageSql);
datasourceRequest.setTotalPageFlag(true);
@@ -131,7 +136,6 @@ public class TableNormalHandler extends DefaultChartHandler {
List<String[]> data = (List<String[]>) provider.fetchResultField(datasourceRequest).get("data");
//自定义排序
data = ChartDataUtil.resultCustomSort(xAxis, yAxis, view.getSortPriority(), data);
var yoyFiltered = filterResult.getContext().get("yoyFiltered") != null;
if (yoyFiltered) {
// 这里没加分页,因为加了分页参数可能会把原始数据挤出去
@@ -208,6 +212,56 @@ public class TableNormalHandler extends DefaultChartHandler {
} catch (Exception e) {
e.printStackTrace();
}
// 自定义汇总
var basicStyle = (Map<String, Object>) view.getCustomAttr().get("basicStyle");
var showSummary = BooleanUtils.isTrue((Boolean) basicStyle.get("showSummary"));
if (showSummary) {
var fieldList = (List) basicStyle.get("seriesSummary");
if (CollectionUtils.isNotEmpty(fieldList)) {
var customCalcFields = new ArrayList<ChartViewFieldDTO>();
var seriesList = JsonUtil.parseList(JsonUtil.toJSONString(fieldList).toString(), new TypeReference<List<ChartViewFieldDTO>>(){});
seriesList.forEach(field -> {
if (!BooleanUtils.isTrue(field.getShow()) || !"custom".equalsIgnoreCase(field.getSummary())) {
return;
}
if (StringUtils.isBlank(field.getOriginName())) {
return;
}
field.setSummary("");
field.setDeType(DeTypeConstants.DE_FLOAT);
field.setId(IDUtils.snowID());
field.setExtField(ExtFieldConstant.EXT_CALC);
customCalcFields.add(field);
});
if (!customCalcFields.isEmpty()) {
var xFields = sqlMeta.getXFields();
// 清空维度值,获取完结果再设置回去
sqlMeta.setXFields(Collections.emptyList());
List<DatasetTableFieldDTO> tmpList = FieldUtil.transFields(allFields);
tmpList.addAll(customCalcFields);
Quota2SQLObj.quota2sqlObj(sqlMeta, customCalcFields, tmpList, crossDs, dsMap, Utils.getParams(FieldUtil.transFields(allFields)), view.getCalParams(), pluginManage);
String customSumSql = SQLProvider.createQuerySQL(sqlMeta, false, !StringUtils.equalsIgnoreCase(dsMap.values().iterator().next().getType(), "es"), view);
customSumSql = provider.rebuildSQL(customSumSql, sqlMeta, crossDs, dsMap);
var customSumReq = new DatasourceRequest();
customSumReq.setIsCross(crossDs);
customSumReq.setDsList(dsMap);
customSumReq.setQuery(customSumSql);
var customSumData = (List<String[]>) provider.fetchResultField(customSumReq).get("data");
if (CollectionUtils.isNotEmpty(customSumData)) {
var customSumResult = new HashMap<String, Double>();
// 只取第一行结果
var customSumArr = customSumData.get(0);
for (int i = 0; i < customSumArr.length; i++) {
if (customCalcFields.get(i) != null) {
customSumResult.put(customCalcFields.get(i).getField(), Double.valueOf(customSumArr[i]));
}
}
result.put("customSumResult", customSumResult);
}
sqlMeta.setXFields(xFields);
}
}
}
return calcResult;
}
}

View File

@@ -306,6 +306,7 @@ declare interface ChartBasicStyle {
show: boolean
field: string
summary: string
originName?: string
}>
/**
* 符号地图符号大小最小值

View File

@@ -33,6 +33,7 @@ declare interface Chart {
tableRow: []
}
customCalc: any
customSumResult?: Record<string, any>
}
xAxis?: Axis[]
xAxisExt?: Axis[]

View File

@@ -9,7 +9,6 @@ import YAxisSelector from '@/views/chart/components/editor/editor-style/componen
import DualYAxisSelector from '@/views/chart/components/editor/editor-style/components/DualYAxisSelector.vue'
import TitleSelector from '@/views/chart/components/editor/editor-style/components/TitleSelector.vue'
import LegendSelector from '@/views/chart/components/editor/editor-style/components/LegendSelector.vue'
import SummarySelector from '@/views/chart/components/editor/editor-style/components/SummarySelector.vue'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import CollapseSwitchItem from '@/components/collapse-switch-item/src/CollapseSwitchItem.vue'
@@ -22,6 +21,7 @@ import BackgroundOverallCommon from '@/components/visualization/component-backgr
import TableHeaderSelector from '@/views/chart/components/editor/editor-style/components/table/TableHeaderSelector.vue'
import TableCellSelector from '@/views/chart/components/editor/editor-style/components/table/TableCellSelector.vue'
import TableTotalSelector from '@/views/chart/components/editor/editor-style/components/table/TableTotalSelector.vue'
import SummarySelector from '@/views/chart/components/editor/editor-style/components/table/SummarySelector.vue'
import MiscStyleSelector from '@/views/chart/components/editor/editor-style/components/MiscStyleSelector.vue'
import IndicatorValueSelector from '@/views/chart/components/editor/editor-style/components/IndicatorValueSelector.vue'
import IndicatorNameSelector from '@/views/chart/components/editor/editor-style/components/IndicatorNameSelector.vue'

View File

@@ -1,10 +1,12 @@
<script setup lang="ts">
import { useI18n } from '@/hooks/web/useI18n'
import { computed, onMounted, PropType, reactive, watch } from 'vue'
import { computed, onMounted, PropType, reactive, watch, ref, nextTick, inject } from 'vue'
import { DEFAULT_BASIC_STYLE } from '@/views/chart/components/editor/util/chart'
import { cloneDeep, defaultsDeep, filter, find } from 'lodash-es'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import CustomAggrEdit from './CustomAggrEdit.vue'
const dvMainStore = dvMainStoreWithOut()
const { batchOptStatus } = storeToRefs(dvMainStore)
const { t } = useI18n()
@@ -30,6 +32,7 @@ const state = reactive({
show: boolean
field: string
summary: string
originName?: string
}
})
@@ -37,7 +40,12 @@ const emit = defineEmits(['onBasicStyleChange'])
const changeBasicStyle = (prop?: string, requestData = false) => {
emit('onBasicStyleChange', { data: state.basicStyleForm, requestData }, prop)
}
const changeSummaryType = () => {
if (state.currentAxisSummary.summary === 'custom' && !state.currentAxisSummary.originName) {
return
}
changeBasicStyle('seriesSummary')
}
watch(
[
() => props.chart.customAttr.basicStyle.showSummary,
@@ -65,15 +73,38 @@ const summaryTypes = [
{ key: 'sum', name: t('chart.sum') },
{ key: 'avg', name: t('chart.avg') },
{ key: 'max', name: t('chart.max') },
{ key: 'min', name: t('chart.min') }
// { key: 'stddev_pop', name: t('chart.stddev_pop') },
// { key: 'var_pop', name: t('chart.var_pop') }
{ key: 'min', name: t('chart.min') },
{ key: 'custom', name: t('commons.custom') }
]
function onSelectAxis(value) {
state.currentAxisSummary = find(state.basicStyleForm.seriesSummary, s => s.field === value)
}
const calcEdit = ref()
const editCalcField = ref(false)
const dimension = inject('dimension', () => [])
const quota = inject('quota', () => [])
const editField = () => {
editCalcField.value = true
nextTick(() => {
calcEdit.value.initEdit(
state.currentAxisSummary,
quota().filter(ele => ele.id !== '-1')
)
})
}
const closeEditCalc = () => {
editCalcField.value = false
}
const confirmEditCalc = () => {
calcEdit.value.setFieldForm()
const obj = cloneDeep(calcEdit.value.fieldForm)
state.currentAxisSummary.originName = obj.originName
setFieldDefaultValue(state.currentAxisSummary)
closeEditCalc()
changeSummaryType()
}
const init = () => {
const basicStyle = cloneDeep(props.chart.customAttr.basicStyle)
@@ -112,7 +143,14 @@ const init = () => {
state.currentAxisSummary = undefined
}
}
const setFieldDefaultValue = field => {
field.extField = 2
field.chartId = props.chart.id
field.datasetGroupId = props.chart.tableId
field.lastSyncTime = null
field.columnIndex = dimension().length + quota().length
field.deExtractType = field.deType
}
onMounted(() => {
init()
})
@@ -173,21 +211,41 @@ onMounted(() => {
<div class="indented-container">
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-select
v-model="state.currentAxisSummary.summary"
:class="'form-item-' + themes"
class="form-item"
:effect="themes"
:disabled="!state.currentAxisSummary.show"
@change="changeBasicStyle('seriesSummary')"
>
<el-option v-for="c in summaryTypes" :key="c.key" :value="c.key" :label="c.name" />
</el-select>
<el-col :span="state.currentAxisSummary.summary === 'custom' ? 19 : 22" :offset="2">
<el-select
v-model="state.currentAxisSummary.summary"
:class="'form-item-' + themes"
class="form-item"
:effect="themes"
:disabled="!state.currentAxisSummary.show"
@change="changeSummaryType"
>
<el-option v-for="c in summaryTypes" :key="c.key" :value="c.key" :label="c.name" />
</el-select>
</el-col>
<el-col v-if="state.currentAxisSummary.summary === 'custom'" :span="2" :offset="1">
<el-icon style="cursor: pointer">
<Setting @click="editField()" />
</el-icon>
</el-col>
</el-form-item>
</div>
</template>
</el-form>
</div>
<!--图表计算字段-->
<el-dialog
v-model="editCalcField"
width="1000px"
title="自定义总计"
:close-on-click-modal="false"
>
<custom-aggr-edit ref="calcEdit" />
<template #footer>
<el-button secondary @click="closeEditCalc()">{{ t('dataset.cancel') }} </el-button>
<el-button type="primary" @click="confirmEditCalc()">{{ t('dataset.confirm') }} </el-button>
</template>
</el-dialog>
</template>
<style scoped lang="less">

View File

@@ -354,7 +354,12 @@ export class TableNormal extends S2ChartView<TableSheet> {
s2Options.style.rowCfg = { heightByField }
// 计算汇总加入到数据里,冻结最后一行
s2Options.frozenTrailingRowCount = 1
const summaryObj = getSummaryRow(data, yAxis, basicStyle.seriesSummary) as any
const summaryObj = getSummaryRow(
data,
yAxis,
basicStyle.seriesSummary,
chart.data.customSumResult
) as any
data.push(summaryObj)
}
s2Options.dataCell = viewMeta => {

View File

@@ -2389,7 +2389,7 @@ const getWrapTextHeight = (wrapText, textStyle, spreadsheet, maxLines) => {
}
// 导出获取汇总行的函数
export function getSummaryRow(data, axis, sumCon = []) {
export function getSummaryRow(data, axis, sumCon = [], customSumResult = {}) {
const summaryObj = { SUMMARY: true }
for (let i = 0; i < axis.length; i++) {
const a = axis[i].dataeaseName
@@ -2473,6 +2473,9 @@ export function getSummaryRow(data, axis, sumCon = []) {
.toNumber() // 计算总体标准差
}
break
case 'custom':
summaryObj[a] = customSumResult[a]
break
}
}

View File

@@ -1,6 +1,8 @@
package io.dataease.extensions.view.dto;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.Serializable;
@@ -34,4 +36,11 @@ public class ChartViewFieldDTO extends ChartViewFieldBaseDTO implements Serializ
*/
@JsonIgnore
private FieldSource source;
/**
* 显隐
*/
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Boolean show;
private String field;
}