diff --git a/frontend/src/views/chat/component/charts/Bar.ts b/frontend/src/views/chat/component/charts/Bar.ts index e3597dfd4..c14950823 100644 --- a/frontend/src/views/chat/component/charts/Bar.ts +++ b/frontend/src/views/chat/component/charts/Bar.ts @@ -3,6 +3,7 @@ import type { ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts' import type { G2Spec } from '@antv/g2' import { checkIsPercent, + formatNumber, getAxesWithFilter, processMultiQuotaData, } from '@/views/chat/component/charts/utils.ts' @@ -104,6 +105,9 @@ export class Bar extends BaseG2Chart { labelAutoRotate: false, labelAutoWrap: true, labelAutoEllipsis: true, + labelFormatter: (value: any) => { + return String(formatNumber(value)) + }, }, }, scale: { @@ -123,10 +127,13 @@ export class Bar extends BaseG2Chart { if (series.length > 0) { return { name: data[series[0].value], - value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`, + value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`, } } else { - return { name: y[0].name, value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}` } + return { + name: y[0].name, + value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`, + } } }, labels: this.showLabel @@ -137,7 +144,7 @@ export class Bar extends BaseG2Chart { if (value === undefined || value === null) { return '' } - return `${value}${_data.isPercent ? '%' : ''}` + return `${formatNumber(value)}${_data.isPercent ? '%' : ''}` }, position: (data: any) => { if (data[y[0].value] < 0) { diff --git a/frontend/src/views/chat/component/charts/Column.ts b/frontend/src/views/chat/component/charts/Column.ts index 7504cfa2c..8128caa63 100644 --- a/frontend/src/views/chat/component/charts/Column.ts +++ b/frontend/src/views/chat/component/charts/Column.ts @@ -3,6 +3,7 @@ import type { ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts' import type { G2Spec } from '@antv/g2' import { checkIsPercent, + formatNumber, getAxesWithFilter, processMultiQuotaData, } from '@/views/chat/component/charts/utils.ts' @@ -95,6 +96,9 @@ export class Column extends BaseG2Chart { }, y: { title: false, // y[0].name, + labelFormatter: (value: any) => { + return String(formatNumber(value)) + }, }, }, scale: { @@ -114,10 +118,13 @@ export class Column extends BaseG2Chart { if (series.length > 0) { return { name: data[series[0].value], - value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`, + value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`, } } else { - return { name: y[0].name, value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}` } + return { + name: y[0].name, + value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`, + } } }, labels: this.showLabel @@ -128,7 +135,7 @@ export class Column extends BaseG2Chart { if (value === undefined || value === null) { return '' } - return `${value}${_data.isPercent ? '%' : ''}` + return `${formatNumber(value)}${_data.isPercent ? '%' : ''}` }, position: (data: any) => { if (data[y[0].value] < 0) { diff --git a/frontend/src/views/chat/component/charts/Line.ts b/frontend/src/views/chat/component/charts/Line.ts index b110ebfdb..bfa20a1bf 100644 --- a/frontend/src/views/chat/component/charts/Line.ts +++ b/frontend/src/views/chat/component/charts/Line.ts @@ -3,6 +3,7 @@ import type { ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts' import type { G2Spec } from '@antv/g2' import { checkIsPercent, + formatNumber, getAxesWithFilter, processMultiQuotaData, } from '@/views/chat/component/charts/utils.ts' @@ -69,6 +70,9 @@ export class Line extends BaseG2Chart { }, y: { title: false, // y[0].name, + labelFormatter: (value: any) => { + return String(formatNumber(value)) + }, }, }, scale: { @@ -94,7 +98,7 @@ export class Line extends BaseG2Chart { if (value === undefined || value === null) { return '' } - return `${value}${_data.isPercent ? '%' : ''}` + return `${formatNumber(value)}${_data.isPercent ? '%' : ''}` }, style: { dx: -10, @@ -112,10 +116,13 @@ export class Line extends BaseG2Chart { if (series.length > 0) { return { name: data[series[0].value], - value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`, + value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`, } } else { - return { name: y[0].name, value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}` } + return { + name: y[0].name, + value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`, + } } }, }, diff --git a/frontend/src/views/chat/component/charts/Pie.ts b/frontend/src/views/chat/component/charts/Pie.ts index bc729f73b..d0f7227a0 100644 --- a/frontend/src/views/chat/component/charts/Pie.ts +++ b/frontend/src/views/chat/component/charts/Pie.ts @@ -1,7 +1,7 @@ import { BaseG2Chart } from '@/views/chat/component/BaseG2Chart.ts' import type { ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts' import type { G2Spec } from '@antv/g2' -import { checkIsPercent, getAxesWithFilter } from '@/views/chat/component/charts/utils.ts' +import { checkIsPercent, formatNumber, getAxesWithFilter } from '@/views/chat/component/charts/utils.ts' export class Pie extends BaseG2Chart { constructor(id: string) { @@ -48,7 +48,7 @@ export class Pie extends BaseG2Chart { { position: 'spider', text: (data: any) => { - return `${data[series[0].value]}: ${data[y[0].value]}${_data.isPercent ? '%' : ''}` + return `${data[series[0].value]}: ${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}` }, }, ] @@ -59,7 +59,7 @@ export class Pie extends BaseG2Chart { (data: any) => { return { name: y[0].name, - value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`, + value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`, } }, ], diff --git a/frontend/src/views/chat/component/charts/Table.ts b/frontend/src/views/chat/component/charts/Table.ts index e769748c1..1a4358aea 100644 --- a/frontend/src/views/chat/component/charts/Table.ts +++ b/frontend/src/views/chat/component/charts/Table.ts @@ -11,6 +11,7 @@ import { } from '@antv/s2' import { debounce, filter } from 'lodash-es' import { i18n } from '@/i18n' +import { formatNumber } from '@/views/chat/component/charts/utils.ts' import '@antv/s2/dist/s2.min.css' const { t } = i18n.global @@ -120,6 +121,10 @@ export class Table extends BaseChart { return { field: a.value, name: a.name, + formatter: (value: any) => { + const formatted = formatNumber(value) + return String(formatted) + }, } }) ?? [], data: this.data, @@ -196,7 +201,8 @@ export class Table extends BaseChart { container.style.fontSize = '14px' container.style.whiteSpace = 'pre-wrap' - const text = document.createTextNode(meta.fieldValue) + const formattedValue = formatNumber(meta.fieldValue) + const text = document.createTextNode(String(formattedValue)) container.appendChild(text) return container @@ -207,7 +213,7 @@ export class Table extends BaseChart { interaction: { copy: { enable: true, - withFormat: true, + withFormat: false, withHeader: true, }, brushSelection: { diff --git a/frontend/src/views/chat/component/charts/utils.ts b/frontend/src/views/chat/component/charts/utils.ts index 6a4c70ff8..89e8a4a21 100644 --- a/frontend/src/views/chat/component/charts/utils.ts +++ b/frontend/src/views/chat/component/charts/utils.ts @@ -1,6 +1,39 @@ import type { ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts' import { endsWith, filter, replace } from 'lodash-es' +/** + * 为数值添加千分符,保持原有小数位数不变 + * 纯字符串处理,避免精度丢失 + * 支持:正负整数、小数、字符串格式的数值 + */ +export function formatNumber(value: any): string | number { + if (value === null || value === undefined || value === '') { + return value + } + + let str: string + if (typeof value === 'string') { + str = value.trim() + } else if (typeof value === 'number') { + str = String(value) + } else { + return value + } + + const match = str.match(/^([+-])?(\d+)(\.(\d+))?$/) + if (!match) { + return value + } + + const sign = match[1] || '' + const intPart = match[2] + const decPart = match[3] || '' + + const formattedInt = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',') + + return sign + formattedInt + decPart +} + interface CheckedData { isPercent: boolean data: Array diff --git a/g2-ssr/charts/bar.js b/g2-ssr/charts/bar.js index 6545e4aa5..c2970583f 100644 --- a/g2-ssr/charts/bar.js +++ b/g2-ssr/charts/bar.js @@ -1,4 +1,4 @@ -const { checkIsPercent, getAxesWithFilter, processMultiQuotaData } = require('./utils') +const { checkIsPercent, formatNumber, getAxesWithFilter, processMultiQuotaData } = require('./utils') function getBarOptions(baseOptions, axis, data) { @@ -78,7 +78,12 @@ function getBarOptions(baseOptions, axis, data) { labelAutoWrap: true, labelAutoEllipsis: true, }, - y: { title: false }, + y: { + title: false, + labelFormatter: (value) => { + return String(formatNumber(value)) + }, + }, }, scale: { x: { @@ -96,10 +101,10 @@ function getBarOptions(baseOptions, axis, data) { if (series.length > 0) { return { name: data[series[0].value], - value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`, + value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`, } } else { - return { name: y[0].name, value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}` } + return { name: y[0].name, value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}` } } }, labels: [ @@ -109,7 +114,7 @@ function getBarOptions(baseOptions, axis, data) { if (value === undefined || value === null) { return '' } - return `${value}${_data.isPercent ? '%' : ''}` + return `${formatNumber(value)}${_data.isPercent ? '%' : ''}` }, position: (data) => { if (data[y[0].value] < 0) { diff --git a/g2-ssr/charts/column.js b/g2-ssr/charts/column.js index dc7044c56..4d7e798b8 100644 --- a/g2-ssr/charts/column.js +++ b/g2-ssr/charts/column.js @@ -1,4 +1,4 @@ -const { checkIsPercent, getAxesWithFilter, processMultiQuotaData } = require('./utils') +const { checkIsPercent, formatNumber, getAxesWithFilter, processMultiQuotaData } = require('./utils') function getColumnOptions(baseOptions, axis, data) { @@ -77,7 +77,12 @@ function getColumnOptions(baseOptions, axis, data) { labelAutoWrap: true, labelAutoEllipsis: true, }, - y: { title: false }, + y: { + title: false, + labelFormatter: (value) => { + return String(formatNumber(value)) + }, + }, }, scale: { x: { @@ -95,10 +100,10 @@ function getColumnOptions(baseOptions, axis, data) { if (series.length > 0) { return { name: data[series[0].value], - value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`, + value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`, } } else { - return { name: y[0].name, value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}` } + return { name: y[0].name, value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}` } } }, labels: [ @@ -108,7 +113,7 @@ function getColumnOptions(baseOptions, axis, data) { if (value === undefined || value === null) { return '' } - return `${value}${_data.isPercent ? '%' : ''}` + return `${formatNumber(value)}${_data.isPercent ? '%' : ''}` }, position: (data) => { if (data[y[0].value] < 0) { diff --git a/g2-ssr/charts/line.js b/g2-ssr/charts/line.js index 4e6cac293..a3742436b 100644 --- a/g2-ssr/charts/line.js +++ b/g2-ssr/charts/line.js @@ -1,4 +1,4 @@ -const { checkIsPercent, getAxesWithFilter, processMultiQuotaData } = require('./utils') +const { checkIsPercent, formatNumber, getAxesWithFilter, processMultiQuotaData } = require('./utils') function getLineOptions(baseOptions, axis, data) { @@ -51,7 +51,12 @@ function getLineOptions(baseOptions, axis, data) { labelAutoWrap: true, labelAutoEllipsis: true, }, - y: { title: y[0].name }, + y: { + title: y[0].name, + labelFormatter: (value) => { + return String(formatNumber(value)) + }, + }, }, scale: { x: { @@ -75,7 +80,7 @@ function getLineOptions(baseOptions, axis, data) { if (value === undefined || value === null) { return '' } - return `${value}${_data.isPercent ? '%' : ''}` + return `${formatNumber(value)}${_data.isPercent ? '%' : ''}` }, style: { dx: -10, @@ -92,10 +97,10 @@ function getLineOptions(baseOptions, axis, data) { if (series.length > 0) { return { name: data[series[0].value], - value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`, + value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`, } } else { - return { name: y[0].name, value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}` } + return { name: y[0].name, value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}` } } }, }, diff --git a/g2-ssr/charts/pie.js b/g2-ssr/charts/pie.js index 2af250d6e..bdaa62d99 100644 --- a/g2-ssr/charts/pie.js +++ b/g2-ssr/charts/pie.js @@ -1,4 +1,4 @@ -const { checkIsPercent, getAxesWithFilter } = require('./utils') +const { checkIsPercent, formatNumber, getAxesWithFilter } = require('./utils') function getPieOptions(baseOptions, axis, data) { @@ -35,7 +35,7 @@ function getPieOptions(baseOptions, axis, data) { { position: 'spider', text: (data) => - `${data[series[0].value]}: ${data[y[0].value]}${_data.isPercent ? '%' : ''}`, + `${data[series[0].value]}: ${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`, }, ], tooltip: { @@ -44,7 +44,7 @@ function getPieOptions(baseOptions, axis, data) { (data) => { return { name: y[0].name, - value: `${data[y[0].value]}${_data.isPercent ? '%' : ''}`, + value: `${formatNumber(data[y[0].value])}${_data.isPercent ? '%' : ''}`, } }, ], diff --git a/g2-ssr/charts/utils.js b/g2-ssr/charts/utils.js index 6edaa32a2..1af3f33b0 100644 --- a/g2-ssr/charts/utils.js +++ b/g2-ssr/charts/utils.js @@ -1,5 +1,38 @@ const { endsWith, filter, replace } = require('lodash') +/** + * 为数值添加千分符,保持原有小数位数不变 + * 纯字符串处理,避免精度丢失 + * 支持:正负整数、小数、字符串格式的数值 + */ +function formatNumber(value) { + if (value === null || value === undefined || value === '') { + return value + } + + let str + if (typeof value === 'string') { + str = value.trim() + } else if (typeof value === 'number') { + str = String(value) + } else { + return value + } + + const match = str.match(/^([+-])?(\d+)(\.(\d+))?$/) + if (!match) { + return value + } + + const sign = match[1] || '' + const intPart = match[2] + const decPart = match[3] || '' + + const formattedInt = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',') + + return sign + formattedInt + decPart +} + function getAxesWithFilter(axes) { const groups = { x: [], @@ -115,4 +148,4 @@ function checkIsPercent(valueAxes, data) { return result } -module.exports = { checkIsPercent, getAxesWithFilter, processMultiQuotaData } +module.exports = { checkIsPercent, formatNumber, getAxesWithFilter, processMultiQuotaData }