diff --git a/frontend/src/components/Language-selector/index.vue b/frontend/src/components/Language-selector/index.vue
index 7d9ee8d16..8b21e4904 100644
--- a/frontend/src/components/Language-selector/index.vue
+++ b/frontend/src/components/Language-selector/index.vue
@@ -73,4 +73,4 @@ const changeLanguage = (lang: string) => {
.selected-lang {
color: var(--el-color-primary);
}
-
\ No newline at end of file
+
diff --git a/frontend/src/utils/xss.ts b/frontend/src/utils/xss.ts
index c0307d716..a2504373f 100644
--- a/frontend/src/utils/xss.ts
+++ b/frontend/src/utils/xss.ts
@@ -27,16 +27,13 @@ export function highlightKeyword(
highlightClass: string = 'highlight'
): string {
if (!keyword) return escapeHtml(text)
-
+
const escapedText = escapeHtml(text)
const escapedKeyword = escapeHtml(keyword)
-
+
// Use case-insensitive replace
const regex = new RegExp(escapedKeyword, 'gi')
- return escapedText.replace(
- regex,
- (match) => `${match}`
- )
+ return escapedText.replace(regex, (match) => `${match}`)
}
/**
@@ -48,18 +45,18 @@ export function sanitizeHtml(html: string): string {
// Create a temporary div to parse HTML
const temp = document.createElement('div')
temp.innerHTML = html
-
+
// List of allowed tags
const allowedTags = ['b', 'i', 'u', 'strong', 'em', 'span', 'p', 'br', 'a']
-
+
// List of allowed attributes
const allowedAttrs = ['class', 'href', 'title']
-
+
// Remove disallowed tags and attributes
const sanitize = (node: Node): void => {
if (node.nodeType === Node.ELEMENT_NODE) {
const element = node as Element
-
+
// Check if tag is allowed
if (!allowedTags.includes(element.tagName.toLowerCase())) {
// Replace with text content
@@ -67,14 +64,14 @@ export function sanitizeHtml(html: string): string {
element.parentNode?.replaceChild(textNode, element)
return
}
-
+
// Remove disallowed attributes
Array.from(element.attributes).forEach((attr) => {
if (!allowedAttrs.includes(attr.name.toLowerCase())) {
element.removeAttribute(attr.name)
}
})
-
+
// For links, ensure they don't use javascript: protocol
if (element.tagName.toLowerCase() === 'a') {
const href = element.getAttribute('href') || ''
@@ -83,11 +80,11 @@ export function sanitizeHtml(html: string): string {
}
}
}
-
+
// Recursively sanitize child nodes
Array.from(node.childNodes).forEach(sanitize)
}
-
+
sanitize(temp)
return temp.innerHTML
}
diff --git a/frontend/src/views/chat/ChatTokenTime.vue b/frontend/src/views/chat/ChatTokenTime.vue
index dcdfb3754..958ee6c9c 100644
--- a/frontend/src/views/chat/ChatTokenTime.vue
+++ b/frontend/src/views/chat/ChatTokenTime.vue
@@ -19,7 +19,7 @@ function getLogList() {
{{ $t('parameter.tokens_required') }} {{ totalTokens }}
{{ $t('parameter.time_execution') }} {{ duration }} s
-
+
diff --git a/frontend/src/views/chat/component/charts/Table.ts b/frontend/src/views/chat/component/charts/Table.ts
index b26f8e3bd..4ac55b172 100644
--- a/frontend/src/views/chat/component/charts/Table.ts
+++ b/frontend/src/views/chat/component/charts/Table.ts
@@ -1,7 +1,6 @@
import { BaseChart, type ChartAxis, type ChartData } from '@/views/chat/component/BaseChart.ts'
import {
copyToClipboard,
- type Node,
type S2DataConfig,
S2Event,
type S2MountContainer,
@@ -71,106 +70,51 @@ export class Table extends BaseChart {
data: this.data,
}
+ const sortState: Record
= {}
+
+ const handleSortClick = (params: any) => {
+ const { meta } = params
+ const s2 = meta.spreadsheet
+ if (s2 && meta.isLeaf) {
+ const fieldId = meta.field
+ const currentMethod = sortState[fieldId] || 'none'
+ const sortOrder = ['none', 'desc', 'asc']
+ const nextMethod = sortOrder[(sortOrder.indexOf(currentMethod) + 1) % sortOrder.length]
+ sortState[fieldId] = nextMethod
+ s2.groupSortByMethod(nextMethod === 'none' ? 'none' : (nextMethod as SortMethod), meta)
+ s2.render()
+ }
+ }
+
const s2Options: S2Options = {
width: 600,
height: 360,
- showDefaultHeaderActionIcon: true,
+ showDefaultHeaderActionIcon: false,
+ headerActionIcons: [
+ {
+ icons: ['GlobalDesc'],
+ belongsCell: 'colCell',
+ displayCondition: (node: any) => node.isLeaf && sortState[node.field] === 'desc',
+ onClick: handleSortClick,
+ },
+ {
+ icons: ['GlobalAsc'],
+ belongsCell: 'colCell',
+ displayCondition: (node: any) => node.isLeaf && sortState[node.field] === 'asc',
+ onClick: handleSortClick,
+ },
+ {
+ icons: ['SortDown'],
+ belongsCell: 'colCell',
+ displayCondition: (node: any) =>
+ node.isLeaf && (!sortState[node.field] || sortState[node.field] === 'none'),
+ onClick: handleSortClick,
+ },
+ ],
tooltip: {
operation: {
- // 开启组内排序
sort: true,
},
- dataCell: {
- enable: true,
- content: (cell) => {
- const meta = cell.getMeta()
- const container = document.createElement('div')
- container.style.padding = '8px 0'
- container.style.minWidth = '100px'
- container.style.maxWidth = '400px'
- container.style.display = 'flex'
- container.style.alignItems = 'center'
- container.style.padding = '8px 16px'
- container.style.cursor = 'pointer'
- container.style.color = '#606266'
- container.style.fontSize = '14px'
- container.style.whiteSpace = 'pre-wrap'
-
- const text = document.createTextNode(meta.fieldValue)
- container.appendChild(text)
-
- return container
- },
- },
- colCell: {
- enable: true,
- content: (cell) => {
- const meta = cell.getMeta()
- const { spreadsheet: s2 } = meta
- if (!meta.isLeaf) {
- return null
- }
-
- // 创建类似Element Plus下拉菜单的结构
- const container = document.createElement('div')
- container.className = 'el-dropdown'
- container.style.padding = '8px 0'
- container.style.minWidth = '100px'
-
- const menuItems = [
- {
- label: t('chat.sort_desc'),
- method: 'desc' as SortMethod,
- icon: 'el-icon-sort-down',
- },
- { label: t('chat.sort_asc'), method: 'asc' as SortMethod, icon: 'el-icon-sort-up' },
- { label: t('chat.sort_none'), method: 'none' as SortMethod, icon: 'el-icon-close' },
- ]
-
- menuItems.forEach((item) => {
- const itemEl = document.createElement('div')
- itemEl.className = 'el-dropdown-menu__item'
- itemEl.style.display = 'flex'
- itemEl.style.alignItems = 'center'
- itemEl.style.padding = '8px 16px'
- itemEl.style.cursor = 'pointer'
- itemEl.style.color = '#606266'
- itemEl.style.fontSize = '14px'
-
- // 鼠标悬停效果
- itemEl.addEventListener('mouseenter', () => {
- itemEl.style.backgroundColor = '#f5f7fa'
- itemEl.style.color = '#409eff'
- })
- itemEl.addEventListener('mouseleave', () => {
- itemEl.style.backgroundColor = 'transparent'
- itemEl.style.color = '#606266'
- })
-
- // 添加图标(如果需要)
- if (item.icon) {
- const icon = document.createElement('i')
- icon.className = item.icon
- icon.style.marginRight = '8px'
- icon.style.fontSize = '16px'
- itemEl.appendChild(icon)
- }
-
- const text = document.createTextNode(item.label)
- itemEl.appendChild(text)
-
- itemEl.addEventListener('click', (e) => {
- e.stopPropagation()
- s2.groupSortByMethod(item.method, meta as Node)
- // 可以在这里添加关闭tooltip的逻辑
- })
-
- container.appendChild(itemEl)
- })
-
- return container
- },
- },
},
// 如果有省略号, 复制到的是完整文本
interaction: {
diff --git a/frontend/src/views/ds/DataTable.vue b/frontend/src/views/ds/DataTable.vue
index 312705b2b..9af6d14a7 100644
--- a/frontend/src/views/ds/DataTable.vue
+++ b/frontend/src/views/ds/DataTable.vue
@@ -364,7 +364,7 @@ const btnSelectClick = (val: any) => {
{{ info.name }}