Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
水玉婷
ai-wechat
Commits
403f09ee
Commit
403f09ee
authored
Jan 23, 2026
by
水玉婷
Browse files
feat:图表改为页面,支持分页及排序
parent
59fea6c1
Changes
10
Hide whitespace changes
Inline
Side-by-side
src/views/components/AiChat.vue
View file @
403f09ee
...
...
@@ -106,9 +106,8 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, nextTick
, watch
} from 'vue';
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
import dayjs from 'dayjs';
import { tableTemplate } from './utils/tableTemplate';
import { markdownTemplate, isLastBlockMarkdown, getLastMarkdownBlockIndex, mergeMarkdownContent } from './utils/markdownTemplate';
import { SendOutlined, UserOutlined, UpOutlined, DownOutlined } from '@ant-design/icons-vue';
import defaultAvatar from '@/assets/logo.png';
...
...
@@ -216,7 +215,6 @@ const handleSSEMessage = (data: SSEData) => {
currentBlockIndex.value,
false,
{
tableTemplate,
markdownTemplate,
isLastBlockMarkdown,
getLastMarkdownBlockIndex,
...
...
@@ -493,7 +491,6 @@ onBeforeUnmount(() => {
// 处理历史记录数据
const processHistoryData = (dataArray: any[]) => {
return templateService.processHistoryData(dataArray, {
tableTemplate,
markdownTemplate,
isLastBlockMarkdown,
getLastMarkdownBlockIndex,
...
...
src/views/components/ChartComponent.vue
View file @
403f09ee
...
...
@@ -3,7 +3,6 @@
<!-- 图表类型选择器 -->
<
div class="chart-controls">
<
div class="chart-type-selector">
<!--
<
span class="control-label">图表类型
</span>
-->
<
a-select
v-model:value="selectedChartType"
:options="chartTypeOptions"
...
...
@@ -81,7 +80,10 @@
<div class="chart-render-area">
<!-- 表格渲染 -->
<template v-if="selectedChartType === CHART_TYPES.TABLE">
<div v-html="tableHtml" class="message-chart"></div>
<TableComponent
:table-data="props.chartData"
:config="{ title: title }"
/>
</template>
<!-- 柱状图渲染 -->
...
...
@@ -136,9 +138,7 @@ export type ChartType = typeof CHART_TYPES[keyof typeof CHART_TYPES];
import { ref, computed, watch, onMounted, defineAsyncComponent, type Ref } from 'vue';
import { Select as ASelect, SelectOption as ASelectOption, Tooltip as ATooltip } from 'ant-design-vue';
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
import { generateTableHTML } from './utils/tableTemplate';
import TableComponent from './TableComponent.vue';
// 类型定义
interface ChartData {
...
...
@@ -194,14 +194,6 @@ const availableIndexes = computed(() => {
return props.chartData?.indexFields ?? [];
});
const tableHtml = computed(() => {
return props.chartData ? generateTableHTML({
...props.chartData,
dimFields: props.chartData.dimFields || [],
indexFields: props.chartData.indexFields || []
}) : '';
});
// 修改chartTypeOptions以包含饼图选项
const chartTypeOptions = computed<ChartTypeOption[]>(() => {
return [
...
...
@@ -415,7 +407,6 @@ onMounted(() => {
.chart-render-area {
padding: 0px 8px;
min-height: 300px;
font-size: 0;
width: 100% !important;
display: flex;
...
...
src/views/components/ColumnChart.vue
View file @
403f09ee
...
...
@@ -14,6 +14,7 @@
<
script
setup
lang=
"ts"
>
import
{
ref
,
shallowRef
,
onMounted
,
onUnmounted
,
watch
,
nextTick
}
from
'
vue
'
;
import
*
as
echarts
from
'
echarts
'
;
import
{
formatNumber
,
getColors
}
from
'
./utils/utils
'
;
// 定义组件属性
interface
Props
{
...
...
@@ -59,42 +60,6 @@ const checkDataEmpty = (data: any): boolean => {
return
!
hasValidData
;
};
/**
* 数字格式化工具
*/
const
formatNumber
=
(
value
:
any
):
string
=>
{
if
(
value
===
null
||
value
===
undefined
||
isNaN
(
value
)
||
value
===
''
)
{
return
'
0
'
;
}
const
numValue
=
Number
(
value
);
if
(
isNaN
(
numValue
))
return
'
0
'
;
if
(
numValue
===
0
)
return
'
0
'
;
const
roundedValue
=
Math
.
ceil
(
numValue
*
100
)
/
100
;
if
(
Math
.
abs
(
roundedValue
)
>=
100000000
)
{
return
`
${(
roundedValue
/
100000000
).
toFixed
(
2
)}
亿`
;
}
else
if
(
Math
.
abs
(
roundedValue
)
>=
10000
)
{
return
`
${(
roundedValue
/
10000
).
toFixed
(
2
)}
万`
;
}
else
{
return
roundedValue
.
toFixed
(
2
);
}
};
/**
* 获取颜色方案 - 使用ECharts内置颜色方案
*/
const
getColors
=
()
=>
{
return
[
'
#5B90F9
'
,
'
#73DEB3
'
,
'
#8074FF
'
,
'
#00CDFE
'
,
'
#FDDD60
'
,
'
#FF8B45
'
,
'
#F478D1
'
,
'
#FF6F77
'
,
'
#8D48E4
'
,
'
#1890ff
'
,
'
#52c41a
'
,
'
#faad14
'
,
'
#f5222d
'
,
'
#722ed1
'
,
'
#13c2c2
'
,
'
#fa541c
'
,
'
#eb2f96
'
,
'
#a0d911
'
,
'
#2f54eb
'
,
'
#fa8c16
'
,
'
#52c41a
'
,
'
#13c2c2
'
,
'
#1890ff
'
,
'
#722ed1
'
,
'
#faad14
'
,
'
#f5222d
'
,
'
#eb2f96
'
,
'
#a0d911
'
,
'
#2f54eb
'
,
'
#fa8c16
'
,
'
#52c41a
'
,
'
#13c2c2
'
,
'
#1890ff
'
];
};
/**
* 数据格式化工具
*/
...
...
src/views/components/LineChart.vue
View file @
403f09ee
...
...
@@ -14,6 +14,7 @@
<
script
setup
lang=
"ts"
>
import
{
ref
,
shallowRef
,
watch
,
onMounted
,
onUnmounted
,
nextTick
}
from
'
vue
'
;
import
*
as
echarts
from
'
echarts
'
;
import
{
formatNumber
,
getColors
}
from
'
./utils/utils
'
;
// 定义组件属性 - 与ChartComponent保持一致
interface
Props
{
...
...
@@ -59,42 +60,6 @@ const checkDataEmpty = (data: any): boolean => {
return
!
hasValidData
;
};
/**
* 数字格式化工具
*/
const
formatNumber
=
(
value
:
any
):
string
=>
{
if
(
value
===
null
||
value
===
undefined
||
isNaN
(
value
)
||
value
===
''
)
{
return
'
0
'
;
}
const
numValue
=
Number
(
value
);
if
(
isNaN
(
numValue
))
return
'
0
'
;
if
(
numValue
===
0
)
return
'
0
'
;
const
roundedValue
=
Math
.
ceil
(
numValue
*
100
)
/
100
;
if
(
Math
.
abs
(
roundedValue
)
>=
100000000
)
{
return
`
${(
roundedValue
/
100000000
).
toFixed
(
2
)}
亿`
;
}
else
if
(
Math
.
abs
(
roundedValue
)
>=
10000
)
{
return
`
${(
roundedValue
/
10000
).
toFixed
(
2
)}
万`
;
}
else
{
return
roundedValue
.
toFixed
(
2
);
}
};
/**
* 获取颜色方案 - 使用ECharts内置颜色方案
*/
const
getColors
=
()
=>
{
return
[
'
#5B90F9
'
,
'
#73DEB3
'
,
'
#8074FF
'
,
'
#00CDFE
'
,
'
#FDDD60
'
,
'
#FF8B45
'
,
'
#F478D1
'
,
'
#FF6F77
'
,
'
#8D48E4
'
,
'
#1890ff
'
,
'
#52c41a
'
,
'
#faad14
'
,
'
#f5222d
'
,
'
#722ed1
'
,
'
#13c2c2
'
,
'
#fa541c
'
,
'
#eb2f96
'
,
'
#a0d911
'
,
'
#2f54eb
'
,
'
#fa8c16
'
,
'
#52c41a
'
,
'
#13c2c2
'
,
'
#1890ff
'
,
'
#722ed1
'
,
'
#faad14
'
,
'
#f5222d
'
,
'
#eb2f96
'
,
'
#a0d911
'
,
'
#2f54eb
'
,
'
#fa8c16
'
,
'
#52c41a
'
,
'
#13c2c2
'
,
'
#1890ff
'
];
};
/**
* 数据格式化工具
*/
...
...
src/views/components/PieChart.vue
View file @
403f09ee
...
...
@@ -14,6 +14,7 @@
<
script
setup
lang=
"ts"
>
import
{
ref
,
shallowRef
,
watch
,
onMounted
,
onUnmounted
,
nextTick
}
from
'
vue
'
;
import
*
as
echarts
from
'
echarts
'
;
import
{
formatNumber
,
getColors
}
from
'
./utils/utils
'
;
// 定义组件属性 - 与ChartComponent保持一致
interface
Props
{
...
...
@@ -59,42 +60,6 @@ const checkDataEmpty = (data: any): boolean => {
return
!
hasValidData
;
};
/**
* 数字格式化工具
*/
const
formatNumber
=
(
value
:
any
):
string
=>
{
if
(
value
===
null
||
value
===
undefined
||
isNaN
(
value
)
||
value
===
''
)
{
return
'
0
'
;
}
const
numValue
=
Number
(
value
);
if
(
isNaN
(
numValue
))
return
'
0
'
;
if
(
numValue
===
0
)
return
'
0
'
;
const
roundedValue
=
Math
.
ceil
(
numValue
*
100
)
/
100
;
if
(
Math
.
abs
(
roundedValue
)
>=
100000000
)
{
return
`
${(
roundedValue
/
100000000
).
toFixed
(
2
)}
亿`
;
}
else
if
(
Math
.
abs
(
roundedValue
)
>=
10000
)
{
return
`
${(
roundedValue
/
10000
).
toFixed
(
2
)}
万`
;
}
else
{
return
roundedValue
.
toFixed
(
2
);
}
};
/**
* 获取颜色方案 - 使用ECharts内置颜色方案
*/
const
getColors
=
()
=>
{
return
[
'
#5B90F9
'
,
'
#73DEB3
'
,
'
#8074FF
'
,
'
#00CDFE
'
,
'
#FDDD60
'
,
'
#FF8B45
'
,
'
#F478D1
'
,
'
#FF6F77
'
,
'
#8D48E4
'
,
'
#1890ff
'
,
'
#52c41a
'
,
'
#faad14
'
,
'
#f5222d
'
,
'
#722ed1
'
,
'
#13c2c2
'
,
'
#fa541c
'
,
'
#eb2f96
'
,
'
#a0d911
'
,
'
#2f54eb
'
,
'
#fa8c16
'
,
'
#52c41a
'
,
'
#13c2c2
'
,
'
#1890ff
'
,
'
#722ed1
'
,
'
#faad14
'
,
'
#f5222d
'
,
'
#eb2f96
'
,
'
#a0d911
'
,
'
#2f54eb
'
,
'
#fa8c16
'
,
'
#52c41a
'
,
'
#13c2c2
'
,
'
#1890ff
'
];
};
/**
* 数据格式化工具
*/
...
...
src/views/components/TableComponent.vue
0 → 100644
View file @
403f09ee
<
template
>
<
div class="message-chart">
<!-- 表格标题 -->
<
div class="table-title">
{{
tableTitle
}}
</div>
<!-- Ant Design Table -->
<
div class="table-container">
<
a-table
:columns="tableColumns"
:data-source="processedTableData"
size="middle"
:scroll="
{ x: 'max-content' }"
:pagination="{
pageSize: 15,
total: processedTableData.value?.length || 0
}"
class="data-table"
>
<!-- 自定义单元格渲染 -->
<
template #bodyCell="
{ column, text, record }">
<
div :class="getCellClass(column.dataIndex)">
{{
renderCellContent
(
column
.
dataIndex
,
text
)
}}
</div>
</
template
>
</a-table>
</div>
<!-- 表格页脚 -->
<
div v-if="showFooter" class="table-footer">
共 <span>{{ processedTableData?.length || 0 }}</span> 条记录
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { Table as ATable } from 'ant-design-vue';
import { formatNumber } from './utils/utils';
// 类型定义
interface TableData {
[key: string]: any;
}
interface TableConfig {
showFooter?: boolean;
title?: string;
}
interface Props {
tableData: TableData[] | any;
config?: TableConfig;
}
// 组件属性定义
const props = withDefaults(defineProps<Props>(), {
config: () => ({
showFooter: true,
title: '数据表格',
dimFields: [],
indexFields: []
})
});
// 处理表格数据
const processedTableData = computed(() => {
if (!props.tableData) return [];
// 处理传入完整数据结构的情况
if (Array.isArray(props.tableData.rows)) {
return props.tableData.rows;
} else if (Array.isArray(props.tableData)) {
return props.tableData;
}
return [];
});
// 表格标题
const tableTitle = computed(() => {
if (props.tableData?.title) {
return props.tableData.title;
}
return props.config.title || '数据表格';
});
// 是否显示页脚
const showFooter = computed(() => {
return props.config.showFooter !== false;
});
// 获取表头列配置
const tableColumns = computed(() => {
if (processedTableData.value.length === 0) return [];
const headers = Object.keys(processedTableData.value[0]);
return headers.map(header => ({
title: header,
dataIndex: header,
key: header,
align: getColumnAlign(header),
width: getColumnWidth(header),
ellipsis: true,
sorter: isNumericColumn(header) ? (a, b) => {
const aValue = parseFloat(a[header]);
const bValue = parseFloat(b[header]);
return aValue - bValue;
} : undefined
}));
});
// 判断列是否为数字列
const isNumericColumn = (header: string) => {
// 直接从传入的数据中获取indexFields
const indexFields = props.tableData?.indexFields || [];
// 只通过indexFields来判断数字列
return indexFields.includes(header);
};
// 判断列是否为趋势列
const isTrendColumn = (header: string) => {
return header.toLowerCase().includes('趋势') || header.toLowerCase().includes('trend');
};
// 获取列对齐方式
const getColumnAlign = (header: string) => {
if (isTrendColumn(header)) {
return 'center';
} else if (isNumericColumn(header)) {
return 'right';
} else {
return 'left';
}
};
// 获取列宽度
const getColumnWidth = (header: string) => {
if (isNumericColumn(header)) {
return 120;
} else if (isTrendColumn(header)) {
return 80;
} else {
return 'auto';
}
};
// 获取单元格样式类
const getCellClass = (header: string) => {
if (isTrendColumn(header)) {
return 'trend-cell';
} else if (isNumericColumn(header)) {
return 'numeric-cell';
} else {
return 'text-cell';
}
};
// 渲染单元格内容
const renderCellContent = (header: string, value: any) => {
// 如果是趋势列,根据up/down值显示箭头图标
if (isTrendColumn(header)) {
const trendValue = String(value).toLowerCase().trim();
if (trendValue === 'up' || trendValue === '上升' || trendValue === '上涨') {
return '↑';
} else if (trendValue === 'down' || trendValue === '下降' || trendValue === '下跌') {
return '↓';
}
}
// 如果是数字列,进行格式化
if (isNumericColumn(header)) {
return formatNumber(value);
}
// 其他列保持原样
return value;
};
</script>
<style scoped>
.message-chart {
width: 100%;
max-width: 100%;
margin: 24px 0 16px;
}
.table-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 8px;
color: #323232;
width: 100%;
box-sizing: border-box;
text-align: center;
}
.table-container {
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
border-bottom: 1px solid #f0f0f0;
}
.data-table {
width: auto;
min-width: 100%;
border-collapse: collapse;
background-color: white;
table-layout: auto;
}
.data-table {
th {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 80px;
height: 35px;
vertical-align: middle;
}
td {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 80px;
height: 35px;
vertical-align: middle;
}
}
.table-footer {
margin-top: 12px;
font-size: 14px;
text-align: right;
margin-right: 8px;
span {
color: #5B8AFE;
font-weight: bold;
}
}
/* 单元格样式 */
.trend-cell {
text-align: center;
font-weight: bold;
}
.numeric-cell {
text-align: right;
}
.text-cell {
text-align: left;
}
/* 趋势箭头样式 */
.trend-cell:has(span:contains('↑')) {
color: #f5222d; /* 红色表示上涨 */
}
.trend-cell:has(span:contains('↓')) {
color: #52c41a; /* 绿色表示下跌 */
}
/* 表格样式 */
:deep(.ant-table) {
width: 100%;
.ant-table-thead > tr > th {
background: linear-gradient(135deg, #5B8AFE 0%, #4a7df5 100%);
color: white;
font-weight: 400;
padding: 6px 8px;
font-size: 14px;
border-right: 1px solid rgba(255, 255, 255, 0.2);
height: 35px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 80px;
&:last-child {
border-right: none;
}
.ant-table-column-sorter-up.active, .ant-table-column-sorter-down.active {
color: white;
}
}
.ant-table-tbody > tr > td {
padding: 10px 8px;
font-size: 14px;
border-bottom: 1px solid #f0f0f0;
color: #323232;
height: 35px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
min-width: 80px;
}
/* 奇偶行样式 */
.ant-table-tbody > tr:nth-child(odd) > td {
background-color: #f8faff;
}
.ant-table-tbody > tr:nth-child(even) > td {
background-color: white;
}
.ant-table-tbody > tr:hover > td {
background-color: #f0f5ff;
}
.ant-table-tbody > tr:last-child > td {
border-bottom: none;
}
}
</style>
\ No newline at end of file
src/views/components/style.less
View file @
403f09ee
...
...
@@ -539,163 +539,7 @@ li {
}
}
// =============================================
// 表格消息样式
// =============================================
:deep(.message-table) {
width: 100%;
max-width: 100%;
margin: 24px 0 16px;
// 表格容器
.table-container {
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
border-bottom: 1px solid #f0f0f0;
// 滚动条样式
&::-webkit-scrollbar {
height: 8px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
&:hover {
background: #a8a8a8;
}
}
}
// 确保表头不换行
.data-table {
th {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 80px;
height: 35px;
vertical-align: middle;
}
td {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 80px;
height: 35px;
vertical-align: middle;
}
}
.data-table {
width: auto;
min-width: 100%;
border-collapse: collapse;
background-color: @white;
table-layout: auto;
// 列类型样式
.text-cell {
text-align: left;
padding-left: 12px;
padding-right: 8px;
}
.numeric-cell {
text-align: right;
padding-left: 8px;
padding-right: 12px;
}
.trend-cell {
text-align: center;
padding-left: 8px;
padding-right: 8px;
}
th {
background: linear-gradient(135deg, @primary-color 0%, @primary-hover 100%);
color: @white;
font-weight: 400;
padding: 6px 8px;
font-size: 14px;
border-right: 1px solid rgba(255, 255, 255, 0.2);
height: 35px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 80px;
&:last-child {
border-right: none;
}
}
td {
padding: 10px 8px;
font-size: 14px;
border-bottom: 1px solid #f0f0f0;
color: @gray-7;
height: 35px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
min-width: 80px;
}
// 奇偶行样式
tr:nth-child(odd) td {
background-color: @blue-light-2;
}
tr:nth-child(even) td {
background-color: @white;
}
tr:hover td {
background-color: @blue-light-1;
}
tr:last-child td {
border-bottom: none;
}
}
// 趋势箭头样式
.trend-up {
color: @success-color;
font-weight: bold;
font-size: 16px;
}
.trend-down {
color: @error-color;
font-weight: bold;
font-size: 16px;
}
.table-footer {
margin-top: 12px;
font-size: 14px;
text-align: right;
margin-right: 8px;
span {
color:@primary-color;
font-weight: bold;
}
}
}
// =============================================
// iframe消息样式
...
...
src/views/components/utils/contentTemplateService.ts
View file @
403f09ee
import
dayjs
from
'
dayjs
'
;
import
{
tableTemplate
}
from
'
./tableTemplate
'
;
import
{
markdownTemplate
,
isLastBlockMarkdown
,
getLastMarkdownBlockIndex
,
mergeMarkdownContent
}
from
'
./markdownTemplate
'
;
// 图表类型常量定义
...
...
@@ -15,7 +14,6 @@ const CHART_TYPES = {
text
:
(
content
:
string
)
=>
string
;
thinking
:
(
content
:
string
)
=>
string
;
error
:
(
content
:
string
)
=>
string
;
table
:
(
tableData
:
any
)
=>
string
;
markdown
:
(
content
:
any
,
isStreaming
?:
boolean
)
=>
string
;
option
:
(
optionData
:
any
)
=>
string
;
iframe
:
(
iframeData
:
any
)
=>
string
;
...
...
@@ -147,21 +145,6 @@ export class ContentTemplateService {
return
`<div class="message-success">
${
content
}
</div>`
;
},
// 表格模板 - 使用独立的表格模板工具
table
:
(
tableData
:
any
)
=>
{
// 如果tableData包含dimFields和indexFields,则传递这些配置
if
(
tableData
&&
tableData
.
dimFields
&&
tableData
.
indexFields
)
{
return
tableTemplate
({
rows
:
tableData
.
rows
||
[],
title
:
tableData
.
title
||
''
,
dimFields
:
tableData
.
dimFields
||
[],
indexFields
:
tableData
.
indexFields
||
[]
});
}
// 否则使用默认调用
return
tableTemplate
(
tableData
);
},
// Markdown模板 - 使用独立的markdown模板工具
markdown
:
(
content
:
any
,
isStreaming
:
boolean
=
false
)
=>
{
return
markdownTemplate
(
content
,
isStreaming
);
...
...
@@ -262,7 +245,6 @@ export class ContentTemplateService {
currentBlockIndex
:
number
,
isHistoryData
=
false
,
templateTools
?:
{
tableTemplate
:
(
tableData
:
any
)
=>
string
;
markdownTemplate
:
(
content
:
any
,
isStreaming
?:
boolean
)
=>
string
;
isLastBlockMarkdown
:
(
blocks
:
MessageBlock
[])
=>
boolean
;
getLastMarkdownBlockIndex
:
(
blocks
:
MessageBlock
[])
=>
number
;
...
...
@@ -302,24 +284,7 @@ export class ContentTemplateService {
case
3
:
// 图表数据
if
(
updatedResponse
)
{
switch
(
contentType
)
{
case
2
:
// 表格数据
const
{
rows
}
=
messageContent
;
// // 表格数据处理
// updatedResponse.contentBlocks.push({
// content: templateTools?.tableTemplate ? templateTools.tableTemplate(rows) : this.templates.table(rows),
// hasThinkBox: false,
// thinkContent: '',
// thinkBoxExpanded: false,
// });
// // 图表数据处理
// updatedResponse.contentBlocks.push({
// content: '',
// hasThinkBox: false,
// thinkContent: '',
// thinkBoxExpanded: false,
// chartData: messageContent,
// chartType: CHART_TYPES.COLUMN,
// });
case
2
:
// 图表数据处理
updatedResponse
.
contentBlocks
.
push
({
content
:
''
,
...
...
@@ -571,7 +536,6 @@ export class ContentTemplateService {
public
processHistoryData
(
dataArray
:
any
[],
templateTools
?:
{
tableTemplate
:
(
tableData
:
any
)
=>
string
;
markdownTemplate
:
(
content
:
any
,
isStreaming
?:
boolean
)
=>
string
;
isLastBlockMarkdown
:
(
blocks
:
MessageBlock
[])
=>
boolean
;
getLastMarkdownBlockIndex
:
(
blocks
:
MessageBlock
[])
=>
number
;
...
...
src/views/components/utils/tableTemplate.ts
deleted
100644 → 0
View file @
59fea6c1
/**
* 表格模板工具类
* 用于生成和渲染数据表格
*/
// 表格数据接口
export
interface
TableData
{
[
key
:
string
]:
any
;
}
// 表格配置接口
export
interface
TableConfig
{
showFooter
?:
boolean
;
title
?:
string
;
// 表格标题
dimFields
?:
string
[];
// 维度字段列表,与数据结构中的dimFields保持一致
indexFields
?:
string
[];
// 指标字段列表,这些字段需要特殊数字处理
}
/**
* 生成表格HTML
* @param tableData 表格数据数组或完整数据结构
* @param config 表格配置
* @returns 表格HTML字符串
*/
export
const
generateTableHTML
=
(
tableData
:
TableData
[]
|
any
,
config
:
TableConfig
=
{}):
string
=>
{
// 处理传入完整数据结构的情况
let
rows
:
TableData
[]
=
[];
let
finalConfig
:
TableConfig
=
{
...
config
};
if
(
tableData
&&
typeof
tableData
===
'
object
'
)
{
// 如果传入的是完整数据结构(包含rows、dimFields、indexFields等)
if
(
Array
.
isArray
(
tableData
.
rows
))
{
rows
=
tableData
.
rows
;
// 从数据结构中提取配置
if
(
tableData
.
title
)
{
finalConfig
.
title
=
tableData
.
title
;
}
if
(
tableData
.
dimFields
)
{
finalConfig
.
dimFields
=
tableData
.
dimFields
;
}
if
(
tableData
.
indexFields
)
{
finalConfig
.
indexFields
=
tableData
.
indexFields
;
}
}
else
if
(
Array
.
isArray
(
tableData
))
{
// 如果传入的是纯数组,直接使用
rows
=
tableData
;
}
}
const
{
showFooter
=
true
,
title
=
'
数据表格
'
,
indexFields
=
[],
dimFields
=
[]
}
=
finalConfig
;
// 处理空数据
if
(
!
Array
.
isArray
(
rows
)
||
rows
.
length
===
0
)
{
return
`
<div class="message-table">
<div class="table-title">
${
title
}
</div>
<div class="table-empty">暂无数据</div>
</div>`
;
}
// 动态生成表头 - 使用第一条数据的键名
const
headers
=
Object
.
keys
(
rows
[
0
]);
// 判断列是否为数字列
const
isNumericColumn
=
(
header
:
string
)
=>
{
// 如果指定了indexFields,优先使用indexFields判断
if
(
indexFields
.
length
>
0
)
{
return
indexFields
.
includes
(
header
);
}
// 默认逻辑:根据数据内容判断
return
rows
.
some
(
row
=>
{
const
value
=
row
[
header
];
return
typeof
value
===
'
number
'
||
(
!
isNaN
(
parseFloat
(
value
))
&&
isFinite
(
value
));
});
};
// 数字格式化函数 - 万/亿格式化,保留两位小数,四舍五入
const
formatNumber
=
(
value
:
any
)
=>
{
if
(
value
===
null
||
value
===
undefined
||
value
===
''
)
return
''
;
const
num
=
parseFloat
(
value
);
if
(
isNaN
(
num
))
return
value
;
if
(
num
>=
100000000
)
{
// 亿级别
const
result
=
Math
.
round
((
num
/
100000000
)
*
100
)
/
100
;
return
result
.
toFixed
(
2
)
+
'
亿
'
;
}
else
if
(
num
>=
10000
)
{
// 万级别
const
result
=
Math
.
round
((
num
/
10000
)
*
100
)
/
100
;
return
result
.
toFixed
(
2
)
+
'
万
'
;
}
else
{
// 小于万
return
Math
.
round
(
num
).
toString
();
}
};
// 处理单元格内容,特别处理趋势列和数字列
const
renderCellContent
=
(
header
:
string
,
value
:
any
)
=>
{
// 如果是趋势列,根据up/down值显示箭头图标
if
(
header
.
toLowerCase
().
includes
(
'
趋势
'
)
||
header
.
toLowerCase
().
includes
(
'
trend
'
))
{
const
trendValue
=
String
(
value
).
toLowerCase
().
trim
();
if
(
trendValue
===
'
up
'
||
trendValue
===
'
上升
'
||
trendValue
===
'
上涨
'
)
{
return
`<span class="trend-up">↑</span>`
;
}
else
if
(
trendValue
===
'
down
'
||
trendValue
===
'
下降
'
||
trendValue
===
'
下跌
'
)
{
return
`<span class="trend-down">↓</span>`
;
}
}
// 如果是数字列,进行格式化
if
(
isNumericColumn
(
header
))
{
return
formatNumber
(
value
);
}
// 其他列保持原样
return
value
;
};
// 判断列是否为趋势列
const
isTrendColumn
=
(
header
:
string
)
=>
{
return
header
.
toLowerCase
().
includes
(
'
趋势
'
)
||
header
.
toLowerCase
().
includes
(
'
trend
'
);
};
// 为不同列类型添加对应的样式类
const
getCellClass
=
(
header
:
string
)
=>
{
if
(
isTrendColumn
(
header
))
{
return
'
trend-cell
'
;
// 趋势列居中
}
else
if
(
isNumericColumn
(
header
))
{
return
'
numeric-cell
'
;
// 数字列右对齐
}
else
{
return
'
text-cell
'
;
// 文字列左对齐
}
};
return
`
<div class="message-table">
<div class="table-title">
${
title
}
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
${
headers
.
map
(
header
=>
`<th class="
${
getCellClass
(
header
)}
">
${
header
}
</th>`
).
join
(
''
)}
</tr>
</thead>
<tbody>
${
rows
.
map
(
row
=>
`
<tr>
${
headers
.
map
(
header
=>
`
<td class="
${
getCellClass
(
header
)}
">
${
renderCellContent
(
header
,
row
[
header
])}
</td>
`
).
join
(
''
)}
</tr>
`
).
join
(
''
)}
</tbody>
</table>
</div>
${
showFooter
?
`<div class="table-footer">共 <span>
${
rows
.
length
}
</span> 条记录</div>`
:
''
}
</div>
`
;
};
/**
* 简化的表格生成函数(兼容旧版本)
* @param tableData 表格数据
* @returns 表格HTML字符串
*/
export
const
tableTemplate
=
(
tableData
:
any
):
string
=>
{
return
generateTableHTML
(
tableData
);
};
export
default
{
generateTableHTML
,
tableTemplate
};
\ No newline at end of file
src/views/components/utils/utils.ts
0 → 100644
View file @
403f09ee
/**
* 数字格式化工具函数
* 功能:
* 1. 直接截断而非四舍五入
* 2. 最多保留两位小数,自动移除末尾0
* 3. 智能判断整数和小数显示方式
* 4. 支持亿、万单位自动转换
*/
export
const
formatNumber
=
(
value
:
any
):
string
=>
{
if
(
value
===
null
||
value
===
undefined
||
value
===
''
)
return
''
;
const
num
=
parseFloat
(
value
);
if
(
isNaN
(
num
))
return
value
;
if
(
num
>=
100000000
)
{
// 亿级别
const
result
=
num
/
100000000
;
const
decimalPart
=
result
%
1
;
if
(
decimalPart
===
0
)
{
return
result
.
toString
()
+
'
亿
'
;
}
else
{
// 最多保留两位小数
const
fixedResult
=
result
.
toFixed
(
2
);
// 移除末尾的0
return
fixedResult
.
replace
(
/
\.?
0+$/
,
''
)
+
'
亿
'
;
}
}
else
if
(
num
>=
10000
)
{
// 万级别
const
result
=
num
/
10000
;
const
decimalPart
=
result
%
1
;
if
(
decimalPart
===
0
)
{
return
result
.
toString
()
+
'
万
'
;
}
else
{
// 最多保留两位小数
const
fixedResult
=
result
.
toFixed
(
2
);
// 移除末尾的0
return
fixedResult
.
replace
(
/
\.?
0+$/
,
''
)
+
'
万
'
;
}
}
else
{
// 小于万
const
decimalPart
=
num
%
1
;
if
(
decimalPart
===
0
)
{
return
num
.
toString
();
}
else
{
// 最多保留两位小数
const
fixedResult
=
num
.
toFixed
(
2
);
// 移除末尾的0
return
fixedResult
.
replace
(
/
\.?
0+$/
,
''
);
}
}
};
/**
* 获取颜色方案 - 自定义颜色方案
*/
export
const
getColors
=
()
=>
{
return
[
'
#5B90F9
'
,
'
#73DEB3
'
,
'
#8074FF
'
,
'
#00CDFE
'
,
'
#FDDD60
'
,
'
#FF8B45
'
,
'
#F478D1
'
,
'
#FF6F77
'
,
'
#8D48E4
'
,
'
#1890ff
'
,
'
#52c41a
'
,
'
#faad14
'
,
'
#f5222d
'
,
'
#722ed1
'
,
'
#13c2c2
'
,
'
#fa541c
'
,
'
#eb2f96
'
,
'
#a0d911
'
,
'
#2f54eb
'
,
'
#fa8c16
'
,
'
#52c41a
'
,
'
#13c2c2
'
,
'
#1890ff
'
,
'
#722ed1
'
,
'
#faad14
'
,
'
#f5222d
'
,
'
#eb2f96
'
,
'
#a0d911
'
,
'
#2f54eb
'
,
'
#fa8c16
'
,
'
#52c41a
'
,
'
#13c2c2
'
,
'
#1890ff
'
];
};
\ No newline at end of file
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment