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 }}
- + @@ -387,7 +387,7 @@ const btnSelectClick = (val: any) => { :content="$t('ds.form.choose_tables')" placement="top" > - + diff --git a/frontend/src/views/ds/TableRelationship.vue b/frontend/src/views/ds/TableRelationship.vue index 3da3c8f33..9e83ff692 100644 --- a/frontend/src/views/ds/TableRelationship.vue +++ b/frontend/src/views/ds/TableRelationship.vue @@ -419,12 +419,12 @@ const save = () => { { -
+
{{ t('training.add_it_here') }}
-
+
- + {{ t('common.save') }}
diff --git a/frontend/src/views/system/appearance/LoginPreview.vue b/frontend/src/views/system/appearance/LoginPreview.vue index 17ecb7448..72f5ae7d8 100644 --- a/frontend/src/views/system/appearance/LoginPreview.vue +++ b/frontend/src/views/system/appearance/LoginPreview.vue @@ -40,8 +40,8 @@