From 70e51002cb4ca55cc8f0e8e980c1ce35aa9dc161 Mon Sep 17 00:00:00 2001 From: wisonic-s <51065359+wisonic-s@users.noreply.github.com> Date: Tue, 24 Jun 2025 14:52:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=9B=BE=E8=A1=A8):=20=E6=B1=87=E6=80=BB?= =?UTF-8?q?=E8=A1=A8=E6=94=AF=E6=8C=81=E8=A1=A8=E5=A4=B4=E5=88=86=E7=BB=84?= =?UTF-8?q?=20#16302?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chart/server/ChartDataServer.java | 8 +- .../table/TableHeaderGroupConfig.vue | 19 +++-- .../components/table/TableHeaderSelector.vue | 11 ++- .../js/panel/charts/table/table-info.ts | 54 ++------------ .../js/panel/charts/table/table-normal.ts | 73 +++++++++++++------ .../js/panel/common/common_table.ts | 48 ++++++++++++ 6 files changed, 127 insertions(+), 86 deletions(-) diff --git a/core/core-backend/src/main/java/io/dataease/chart/server/ChartDataServer.java b/core/core-backend/src/main/java/io/dataease/chart/server/ChartDataServer.java index 45bfa6a31a..cb6bfbc97e 100644 --- a/core/core-backend/src/main/java/io/dataease/chart/server/ChartDataServer.java +++ b/core/core-backend/src/main/java/io/dataease/chart/server/ChartDataServer.java @@ -347,7 +347,7 @@ public class ChartDataServer implements ChartDataApi { xAxis.addAll(viewInfo.getDrillFields()); TableHeader tableHeader = null; Integer totalDepth = 0; - if (viewInfo.getType().equalsIgnoreCase("table-normal") || viewInfo.getType().equalsIgnoreCase("table-info")) { + if (StringUtils.equalsAnyIgnoreCase(viewInfo.getType(), "table-normal", "table-info")) { for (ChartViewFieldDTO xAxi : xAxis) { if (xAxi.getDeType().equals(DeTypeConstants.DE_INT) || xAxi.getDeType().equals(DeTypeConstants.DE_FLOAT)) { CellStyle formatterCellStyle = createCellStyle(wb, xAxi.getFormatterCfg(), null); @@ -362,7 +362,11 @@ public class ChartDataServer implements ChartDataApi { if (tableHeaderMap.get("headerGroup") != null && Boolean.parseBoolean(tableHeaderMap.get("headerGroup").toString())) { var tmpHeader = JsonUtil.parseObject((String) JsonUtil.toJSONString(customAttr.get("tableHeader")), TableHeader.class); // 校验字段数量和顺序 - if (validateHeaderGroup(tmpHeader, viewInfo.getXAxis())) { + var allAxis = new ArrayList<>(viewInfo.getXAxis().stream().filter(x -> !x.isHide()).toList()); + if (StringUtils.equalsIgnoreCase(viewInfo.getType(), "table-normal")) { + allAxis.addAll(viewInfo.getYAxis().stream().filter(x -> !x.isHide()).toList()); + } + if (validateHeaderGroup(tmpHeader, allAxis)) { tableHeader = tmpHeader; for (TableHeader.ColumnInfo column : tableHeader.getHeaderGroupConfig().getColumns()) { totalDepth = Math.max(totalDepth, getDepth(column, 1)); diff --git a/core/core-frontend/src/views/chart/components/editor/editor-style/components/table/TableHeaderGroupConfig.vue b/core/core-frontend/src/views/chart/components/editor/editor-style/components/table/TableHeaderGroupConfig.vue index 465dc40dfc..3974f36a63 100644 --- a/core/core-frontend/src/views/chart/components/editor/editor-style/components/table/TableHeaderGroupConfig.vue +++ b/core/core-frontend/src/views/chart/components/editor/editor-style/components/table/TableHeaderGroupConfig.vue @@ -52,22 +52,27 @@ const emits = defineEmits(['onConfigChange', 'onCancelConfig']) const onCancelConfig = () => { emits('onCancelConfig') } - +const allAxis = computed(() => { + const axis = [...props.chart.xAxis] + if (props.chart.type === 'table-normal') { + axis.push(...props.chart.yAxis) + } + return axis +}) const onConfigChange = () => { - const allAxis = props.chart.xAxis + const showAxis = allAxis.value ?.map(axis => axis.hide !== true && axis.dataeaseName) .filter(i => i) const { fields, meta } = s2.dataCfg - const groupMeta = meta.filter(item => !allAxis.includes(item.field)) + const groupMeta = meta.filter(item => !showAxis.includes(item.field)) emits('onConfigChange', { columns: fields.columns, meta: groupMeta }) } const init = () => { const chart = cloneDeep(props.chart) - const xAxis = chart.xAxis const { headerGroupConfig } = chart.customAttr.tableHeader const showColumns = [] - xAxis?.forEach(axis => { + allAxis.value?.forEach(axis => { axis.hide !== true && showColumns.push({ key: axis.dataeaseName }) }) if (!showColumns.length) { @@ -109,7 +114,7 @@ const renderTable = (chart: ChartObj) => { const { headerGroupConfig } = chart.customAttr.tableHeader const meta = [...headerGroupConfig.meta] const columns = headerGroupConfig.columns - const axisMap = chart.xAxis.reduce((pre, cur) => { + const axisMap = allAxis.value.reduce((pre, cur) => { pre[cur.dataeaseName] = cur return pre }, {}) @@ -141,7 +146,7 @@ const renderTable = (chart: ChartObj) => { }) }) } else { - chart.xAxis?.forEach(axis => { + allAxis.value?.forEach(axis => { if (axis.hide !== true) { meta.push({ field: axis.dataeaseName, diff --git a/core/core-frontend/src/views/chart/components/editor/editor-style/components/table/TableHeaderSelector.vue b/core/core-frontend/src/views/chart/components/editor/editor-style/components/table/TableHeaderSelector.vue index 93ba36ecf6..dd6fa34953 100644 --- a/core/core-frontend/src/views/chart/components/editor/editor-style/components/table/TableHeaderSelector.vue +++ b/core/core-frontend/src/views/chart/components/editor/editor-style/components/table/TableHeaderSelector.vue @@ -96,18 +96,21 @@ const groupConfigValid = computed(() => { if (noGroup) { return false } - const xAxis = props.chart.xAxis + const allAxis = [...props.chart?.xAxis] + if (props.chart.type === 'table-normal') { + allAxis.push(...props.chart?.yAxis) + } const showColumns = [] - xAxis?.forEach(axis => { + allAxis?.forEach(axis => { axis.hide !== true && showColumns.push({ key: axis.dataeaseName }) }) if (!showColumns.length) { return false } - const allAxis = showColumns.map(item => item.key) + const showColumnFields = showColumns.map(item => item.key) const leafNodes = getLeafNodes(columns as Array) const leafKeys = leafNodes.map(item => item.key) - return isEqual(allAxis, leafKeys) + return isEqual(showColumnFields, leafKeys) }) const init = () => { const tableHeader = props.chart?.customAttr?.tableHeader diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/table/table-info.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/table/table-info.ts index 391b4ca3a4..c41133ad55 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/table/table-info.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/table/table-info.ts @@ -27,7 +27,10 @@ import { getColumns, drawImage, getSummaryRow, - SummaryCell + SummaryCell, + summaryRowStyle, + calcTreeWidth, + getStartPosition } from '@/views/chart/components/js/panel/common/common_table' const { t } = useI18n() @@ -229,7 +232,7 @@ export class TableInfo extends S2ChartView { // 开始渲染 const newChart = new TableSheet(containerDom, s2DataConfig, s2Options) // 总计紧贴在单元格后面 - this.summaryRowStyle(newChart, newData, tableCell, tableHeader, basicStyle.showSummary) + summaryRowStyle(newChart, newData, tableCell, tableHeader, basicStyle.showSummary) // 开启自动换行 if (basicStyle.autoWrap && !tableCell.mergeCells) { // 调整表头宽度时,计算表头高度 @@ -512,54 +515,7 @@ export class TableInfo extends S2ChartView { } } - protected summaryRowStyle(newChart: TableSheet, newData, tableCell, tableHeader, showSummary) { - if (!showSummary || !newData.length) return - const columns = newChart.dataCfg.fields.columns - const showHeader = tableHeader.showTableHeader === true - // 不显示表头时,减少一个表头的高度 - const headerAndSummaryHeight = showHeader ? getMaxTreeDepth(columns) + 1 : 1 - newChart.on(S2Event.LAYOUT_BEFORE_RENDER, () => { - const totalHeight = - tableHeader.tableTitleHeight * headerAndSummaryHeight + - tableCell.tableItemHeight * (newData.length - 1) - if (totalHeight < newChart.container.cfg.height) { - newChart.options.height = - totalHeight < newChart.container.cfg.height - 8 ? totalHeight + 8 : totalHeight - } - }) - } - constructor() { super('table-info', []) } } - -function calcTreeWidth(node) { - if (!node.children?.length) { - return node.width - } - return node.children.reduce((pre, cur) => { - return pre + calcTreeWidth(cur) - }, 0) -} - -function getStartPosition(node) { - if (!node.children?.length) { - return node.x - } - return getStartPosition(node.children[0]) -} - -function getMaxTreeDepth(nodes) { - if (!nodes?.length) { - return 0 - } - return Math.max( - ...nodes.map(node => { - if (!node.children?.length) { - return 1 - } - return getMaxTreeDepth(node.children) + 1 - }) - ) -} diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/table/table-normal.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/table/table-normal.ts index 2cf89ee019..300f24b4d2 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/table/table-normal.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/table/table-normal.ts @@ -6,7 +6,12 @@ import { CustomDataCell, getSummaryRow, SortTooltip, - SummaryCell + SummaryCell, + getLeafNodes, + getColumns, + summaryRowStyle, + calcTreeWidth, + getStartPosition } from '@/views/chart/components/js/panel/common/common_table' import { S2ChartView, S2DrawOptions } from '@/views/chart/components/js/panel/types/impl/s2' import { parseJson } from '@/views/chart/components/js/util' @@ -20,7 +25,7 @@ import { TableSheet, ViewMeta } from '@antv/s2' -import { isNumber } from 'lodash-es' +import { isNumber, isEqual } from 'lodash-es' import { TABLE_EDITOR_PROPERTY, TABLE_EDITOR_PROPERTY_INNER } from './common' const { t } = useI18n() @@ -34,7 +39,8 @@ export class TableNormal extends S2ChartView { 'table-header-selector': [ ...TABLE_EDITOR_PROPERTY_INNER['table-header-selector'], 'tableHeaderSort', - 'showTableHeader' + 'showTableHeader', + 'headerGroup' ], 'basic-style-selector': [ ...TABLE_EDITOR_PROPERTY_INNER['basic-style-selector'], @@ -76,6 +82,7 @@ export class TableNormal extends S2ChartView { const columns = [] const meta = [] + const drillFieldMap = {} if (chart.drill) { // 下钻过滤字段 const filterFields = chart.drillFilters.map(i => i.fieldId) @@ -84,13 +91,14 @@ export class TableNormal extends S2ChartView { const drillFieldIndex = chart.xAxis.findIndex(ele => ele.id === drillFieldId) // 当前下钻字段 const curDrillFieldId = chart.drillFields[filterFields.length].id - const curDrillField = fields.filter(ele => ele.id === curDrillFieldId) + const curDrillField = fields.find(ele => ele.id === curDrillFieldId) filterFields.push(curDrillFieldId) // 移除下钻字段,把当前下钻字段插入到下钻入口位置 fields = fields.filter(ele => { return !filterFields.includes(ele.id) }) - fields.splice(drillFieldIndex, 0, ...curDrillField) + drillFieldMap[curDrillField.dataeaseName] = chart.drillFields[0].dataeaseName + fields.splice(drillFieldIndex, 0, curDrillField) } const axisMap = [...chart.xAxis, ...chart.yAxis].reduce((pre, cur) => { pre[cur.dataeaseName] = cur @@ -124,7 +132,27 @@ export class TableNormal extends S2ChartView { } }) }) - + const { basicStyle, tableCell, tableHeader, tooltip } = parseJson(chart.customAttr) + // 表头分组 + const { headerGroup, showTableHeader } = tableHeader + if (headerGroup && showTableHeader !== false) { + const { headerGroupConfig } = tableHeader + if (headerGroupConfig?.columns?.length) { + const allKeys = columns.map(c => drillFieldMap[c] || c) + const leafNodes = getLeafNodes(headerGroupConfig.columns as ColumnNode[]) + const leafKeys = leafNodes.map(c => c.key) + if (isEqual(leafKeys, allKeys)) { + if (Object.keys(drillFieldMap).length) { + const originField = Object.values(drillFieldMap)[0] + const drillField = Object.keys(drillFieldMap)[0] + const [drillCol] = getColumns([originField], headerGroupConfig.columns as ColumnNode[]) + drillCol.key = drillField + } + columns.splice(0, columns.length, ...headerGroupConfig.columns) + meta.push(...headerGroupConfig.meta) + } + } + } // 空值处理 const newData = this.configEmptyDataStrategy(chart) // data config @@ -136,7 +164,6 @@ export class TableNormal extends S2ChartView { data: newData } - const { basicStyle, tableCell, tableHeader, tooltip } = parseJson(chart.customAttr) // options const s2Options: S2Options = { width: containerDom.getBoundingClientRect().width, @@ -186,7 +213,7 @@ export class TableNormal extends S2ChartView { // 开始渲染 const newChart = new TableSheet(containerDom, s2DataConfig, s2Options) // 总计紧贴在单元格后面 - this.summaryRowStyle(newChart, newData, tableCell, tableHeader, basicStyle.showSummary) + summaryRowStyle(newChart, newData, tableCell, tableHeader, basicStyle.showSummary) // 自适应铺满 if (basicStyle.tableColumnMode === 'adapt') { newChart.on(S2Event.LAYOUT_RESIZE_COL_WIDTH, () => { @@ -207,6 +234,13 @@ export class TableNormal extends S2ChartView { n.x = p return p + n.width }, 0) + // 处理分组的单元格,宽度为所有叶子节点之和 + ev.colNodes.forEach(n => { + if (n.colIndex === -1) { + n.width = calcTreeWidth(n) + n.x = getStartPosition(n) + } + }) ev.colsHierarchy.width = totalWidth newChart.store.set('lastLayoutResult', undefined) return @@ -227,6 +261,13 @@ export class TableNormal extends S2ChartView { n.x = p return p + n.width }, 0) + // 处理分组的单元格,宽度为所有叶子节点之和 + ev.colNodes.forEach(n => { + if (n.colIndex === -1) { + n.width = calcTreeWidth(n) + n.x = getStartPosition(n) + } + }) if (totalWidth > containerWidth) { // 从最后一列减掉 ev.colLeafNodes[ev.colLeafNodes.length - 1].width -= totalWidth - containerWidth @@ -332,22 +373,6 @@ export class TableNormal extends S2ChartView { } } - protected summaryRowStyle(newChart, newData, tableCell, tableHeader, showSummary) { - if (!showSummary || !newData.length) return - newChart.on(S2Event.LAYOUT_BEFORE_RENDER, () => { - const showHeader = tableHeader.showTableHeader === true - // 不显示表头时,减少一个表头的高度 - const headerAndSummaryHeight = showHeader ? 2 : 1 - const totalHeight = - tableHeader.tableTitleHeight * headerAndSummaryHeight + - tableCell.tableItemHeight * (newData.length - 1) - if (totalHeight < newChart.container.cfg.height) { - newChart.options.height = - totalHeight < newChart.container.cfg.height - 8 ? totalHeight + 8 : totalHeight - } - }) - } - constructor() { super('table-normal', []) } diff --git a/core/core-frontend/src/views/chart/components/js/panel/common/common_table.ts b/core/core-frontend/src/views/chart/components/js/panel/common/common_table.ts index 2fddd3f9c9..6d38f12fce 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/common/common_table.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/common/common_table.ts @@ -2393,3 +2393,51 @@ export function drawImage() { }) } } + + +export function calcTreeWidth(node) { + if (!node.children?.length) { + return node.width + } + return node.children.reduce((pre, cur) => { + return pre + calcTreeWidth(cur) + }, 0) +} + +export function getStartPosition(node) { + if (!node.children?.length) { + return node.x + } + return getStartPosition(node.children[0]) +} + +function getMaxTreeDepth(nodes) { + if (!nodes?.length) { + return 0 + } + return Math.max( + ...nodes.map(node => { + if (!node.children?.length) { + return 1 + } + return getMaxTreeDepth(node.children) + 1 + }) + ) +} + +export function summaryRowStyle(newChart, newData, tableCell, tableHeader, showSummary) { + if (!showSummary || !newData.length) return + const columns = newChart.dataCfg.fields.columns + const showHeader = tableHeader.showTableHeader === true + // 不显示表头时,减少一个表头的高度 + const headerAndSummaryHeight = showHeader ? getMaxTreeDepth(columns) + 1 : 1 + newChart.on(S2Event.LAYOUT_BEFORE_RENDER, () => { + const totalHeight = + tableHeader.tableTitleHeight * headerAndSummaryHeight + + tableCell.tableItemHeight * (newData.length - 1) + if (totalHeight < newChart.container.cfg.height) { + newChart.options.height = + totalHeight < newChart.container.cfg.height - 8 ? totalHeight + 8 : totalHeight + } + }) +} \ No newline at end of file