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