update: 知识库样式优化、添加多语言配置

This commit is contained in:
zhuoqinglian
2026-06-05 17:37:21 +08:00
parent 8a50eea4ce
commit 0173c7abe1
15 changed files with 263 additions and 194 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 995 B

@@ -0,0 +1,20 @@
<template>
<svg t="1780649285700" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="79487" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48">
<path
d="M917.333333 138.666667C917.333333 85.333333 870.4 42.666667 810.666667 42.666667H219.733333C157.866667 42.666667 106.666667 89.6 106.666667 145.066667V855.466667c0 70.4 57.6 128 128 128h576c59.733333 0 106.666667-46.933333 106.666666-106.666667V138.666667zM234.666667 213.333333h362.666666c23.466667 0 42.666667 19.2 42.666667 42.666667s-19.2 42.666667-42.666667 42.666667H234.666667c-23.466667 0-42.666667-19.2-42.666667-42.666667s19.2-42.666667 42.666667-42.666667z m0 128h277.333333c23.466667 0 42.666667 19.2 42.666667 42.666667s-19.2 42.666667-42.666667 42.666667H234.666667c-23.466667 0-42.666667-19.2-42.666667-42.666667s19.2-42.666667 42.666667-42.666667z m597.333333 533.333334c0 12.8-8.533333 21.333333-21.333333 21.333333H236.8c-21.333333 0-40.533333-14.933333-44.8-36.266667-4.266667-25.6 17.066667-49.066667 42.666667-49.066666h576c12.8 0 21.333333 8.533333 21.333333 21.333333v42.666667z"
:fill="color" p-id="79488"></path>
</svg>
</template>
<script>
export default {
name: 'ManualIcon',
props: {
color: {
type: String,
default: '#b3a0fa',
},
},
}
</script>
+11
View File
@@ -1384,8 +1384,19 @@ export default {
'knowledgeFileUpload.comprehensiveSimilarity': 'Umfassende Ähnlichkeit', 'knowledgeFileUpload.comprehensiveSimilarity': 'Umfassende Ähnlichkeit',
'knowledgeFileUpload.content': 'Inhalt:', 'knowledgeFileUpload.content': 'Inhalt:',
'knowledgeFileUpload.testQuestionRequired': 'Bitte Testfrage eingeben', 'knowledgeFileUpload.testQuestionRequired': 'Bitte Testfrage eingeben',
'knowledgeFileUpload.parsing': 'Analysiere...',
'knowledgeBaseDialog.descriptionRequired': 'Bitte Wissensbasis-Beschreibung eingeben', 'knowledgeBaseDialog.descriptionRequired': 'Bitte Wissensbasis-Beschreibung eingeben',
// Knowledge Base Management page text
'knowledgeBaseManagement.loading': 'Laden...',
'knowledgeBaseManagement.noData': 'Keine Daten',
'knowledgeBaseManagement.switchKnowledgeBase': 'Wissensbasis wechseln',
'knowledgeBaseManagement.currentKnowledgeBaseDocuments': 'Aktuelle Wissensbasis-Dokumentassets',
'knowledgeBaseManagement.enabled': 'Aktiviert',
'knowledgeBaseManagement.disabled': 'Deaktiviert',
'knowledgeBaseManagement.totalDocuments': 'Gesamt {total} Dokumente',
'knowledgeBaseManagement.getListFailed': 'Liste konnte nicht abgerufen werden',
// Feature Management page text // Feature Management page text
'featureManagement.selectAll': 'Alle auswählen', 'featureManagement.selectAll': 'Alle auswählen',
'featureManagement.deselectAll': 'Alle abwählen', 'featureManagement.deselectAll': 'Alle abwählen',
+11
View File
@@ -1384,8 +1384,19 @@ export default {
'knowledgeFileUpload.comprehensiveSimilarity': 'Comprehensive Similarity', 'knowledgeFileUpload.comprehensiveSimilarity': 'Comprehensive Similarity',
'knowledgeFileUpload.content': 'Content:', 'knowledgeFileUpload.content': 'Content:',
'knowledgeFileUpload.testQuestionRequired': 'Please enter test question', 'knowledgeFileUpload.testQuestionRequired': 'Please enter test question',
'knowledgeFileUpload.parsing': 'Parsing...',
'knowledgeBaseDialog.descriptionRequired': 'Please enter knowledge base description', 'knowledgeBaseDialog.descriptionRequired': 'Please enter knowledge base description',
// Knowledge Base Management page text
'knowledgeBaseManagement.loading': 'Loading...',
'knowledgeBaseManagement.noData': 'No data',
'knowledgeBaseManagement.switchKnowledgeBase': 'Switch Knowledge Base',
'knowledgeBaseManagement.currentKnowledgeBaseDocuments': 'Current Knowledge Base Document Assets',
'knowledgeBaseManagement.enabled': 'Enabled',
'knowledgeBaseManagement.disabled': 'Disabled',
'knowledgeBaseManagement.totalDocuments': 'Total {total} documents',
'knowledgeBaseManagement.getListFailed': 'Failed to get list',
// Feature Management page text // Feature Management page text
'featureManagement.selectAll': 'Select All', 'featureManagement.selectAll': 'Select All',
'featureManagement.deselectAll': 'Deselect All', 'featureManagement.deselectAll': 'Deselect All',
+11
View File
@@ -1384,8 +1384,19 @@ export default {
'knowledgeFileUpload.comprehensiveSimilarity': 'Similaridade Geral', 'knowledgeFileUpload.comprehensiveSimilarity': 'Similaridade Geral',
'knowledgeFileUpload.content': 'Conteúdo:', 'knowledgeFileUpload.content': 'Conteúdo:',
'knowledgeFileUpload.testQuestionRequired': 'Por favor, insira a pergunta de teste', 'knowledgeFileUpload.testQuestionRequired': 'Por favor, insira a pergunta de teste',
'knowledgeFileUpload.parsing': 'Analisando...',
'knowledgeBaseDialog.descriptionRequired': 'Por favor, insira a descrição da base de conhecimento', 'knowledgeBaseDialog.descriptionRequired': 'Por favor, insira a descrição da base de conhecimento',
// Página de gerenciamento de base de conhecimento
'knowledgeBaseManagement.loading': 'Carregando...',
'knowledgeBaseManagement.noData': 'Sem dados',
'knowledgeBaseManagement.switchKnowledgeBase': 'Alternar Base de Conhecimento',
'knowledgeBaseManagement.currentKnowledgeBaseDocuments': 'Ativos de Documentos da Base de Conhecimento Atual',
'knowledgeBaseManagement.enabled': 'Ativado',
'knowledgeBaseManagement.disabled': 'Desativado',
'knowledgeBaseManagement.totalDocuments': 'Total de {total} documentos',
'knowledgeBaseManagement.getListFailed': 'Falha ao obter lista',
// Página de gerenciamento de funcionalidades // Página de gerenciamento de funcionalidades
'featureManagement.selectAll': 'Selecionar Tudo', 'featureManagement.selectAll': 'Selecionar Tudo',
'featureManagement.deselectAll': 'Desmarcar Tudo', 'featureManagement.deselectAll': 'Desmarcar Tudo',
+11
View File
@@ -1384,8 +1384,19 @@ export default {
'knowledgeFileUpload.comprehensiveSimilarity': 'Độ tương đồng toàn diện', 'knowledgeFileUpload.comprehensiveSimilarity': 'Độ tương đồng toàn diện',
'knowledgeFileUpload.content': 'Nội dung:', 'knowledgeFileUpload.content': 'Nội dung:',
'knowledgeFileUpload.testQuestionRequired': 'Vui lòng nhập câu hỏi kiểm tra', 'knowledgeFileUpload.testQuestionRequired': 'Vui lòng nhập câu hỏi kiểm tra',
'knowledgeFileUpload.parsing': 'Đang phân tích...',
'knowledgeBaseDialog.descriptionRequired': 'Vui lòng nhập mô tả cơ sở kiến thức', 'knowledgeBaseDialog.descriptionRequired': 'Vui lòng nhập mô tả cơ sở kiến thức',
// Knowledge Base Management page text
'knowledgeBaseManagement.loading': 'Đang tải...',
'knowledgeBaseManagement.noData': 'Không có dữ liệu',
'knowledgeBaseManagement.switchKnowledgeBase': 'Chuyển đổi cơ sở kiến thức',
'knowledgeBaseManagement.currentKnowledgeBaseDocuments': 'Tài sản tài liệu cơ sở kiến thức hiện tại',
'knowledgeBaseManagement.enabled': 'Đã bật',
'knowledgeBaseManagement.disabled': 'Đã tắt',
'knowledgeBaseManagement.totalDocuments': 'Tổng {total} tài liệu',
'knowledgeBaseManagement.getListFailed': 'Lấy danh sách thất bại',
// Feature Management page text // Feature Management page text
'featureManagement.selectAll': 'Chọn tất cả', 'featureManagement.selectAll': 'Chọn tất cả',
'featureManagement.deselectAll': 'Bỏ chọn tất cả', 'featureManagement.deselectAll': 'Bỏ chọn tất cả',
+9
View File
@@ -1385,6 +1385,15 @@ export default {
'knowledgeFileUpload.content': '内容:', 'knowledgeFileUpload.content': '内容:',
'knowledgeFileUpload.testQuestionRequired': '请输入测试问题', 'knowledgeFileUpload.testQuestionRequired': '请输入测试问题',
'knowledgeBaseDialog.descriptionRequired': '请输入知识库描述', 'knowledgeBaseDialog.descriptionRequired': '请输入知识库描述',
'knowledgeFileUpload.parsing': '正在解析中...',
'knowledgeBaseManagement.loading': '加载中...',
'knowledgeBaseManagement.noData': '暂无数据',
'knowledgeBaseManagement.switchKnowledgeBase': '切换知识库',
'knowledgeBaseManagement.currentKnowledgeBaseDocuments': '当前知识库文档资产',
'knowledgeBaseManagement.enabled': '已启用',
'knowledgeBaseManagement.disabled': '已禁用',
'knowledgeBaseManagement.totalDocuments': '共 {total} 个文档',
'knowledgeBaseManagement.getListFailed': '获取列表失败',
// 系统功能配置页面文本 // 系统功能配置页面文本
'featureManagement.selectAll': '全选', 'featureManagement.selectAll': '全选',
+11
View File
@@ -1384,8 +1384,19 @@ export default {
'knowledgeFileUpload.comprehensiveSimilarity': '綜合相似度', 'knowledgeFileUpload.comprehensiveSimilarity': '綜合相似度',
'knowledgeFileUpload.content': '內容:', 'knowledgeFileUpload.content': '內容:',
'knowledgeFileUpload.testQuestionRequired': '請輸入測試問題', 'knowledgeFileUpload.testQuestionRequired': '請輸入測試問題',
'knowledgeFileUpload.parsing': '正在解析中...',
'knowledgeBaseDialog.descriptionRequired': '請輸入知识库描述', 'knowledgeBaseDialog.descriptionRequired': '請輸入知识库描述',
// 知識庫管理頁面文本
'knowledgeBaseManagement.loading': '加載中...',
'knowledgeBaseManagement.noData': '暫無數據',
'knowledgeBaseManagement.switchKnowledgeBase': '切換知識庫',
'knowledgeBaseManagement.currentKnowledgeBaseDocuments': '當前知識庫文檔資產',
'knowledgeBaseManagement.enabled': '已啟用',
'knowledgeBaseManagement.disabled': '已禁用',
'knowledgeBaseManagement.totalDocuments': '共 {total} 個文檔',
'knowledgeBaseManagement.getListFailed': '獲取列表失敗',
// 功能管理頁面文本 // 功能管理頁面文本
'featureManagement.selectAll': '全選', 'featureManagement.selectAll': '全選',
'featureManagement.deselectAll': '取消全選', 'featureManagement.deselectAll': '取消全選',
+91 -106
View File
@@ -2,47 +2,47 @@
<div class="doc-section"> <div class="doc-section">
<div class="doc-section-header"> <div class="doc-section-header">
<div class="doc-section-left"> <div class="doc-section-left">
<div class="doc-section-title">{{`当前知识库文档资产${kb?.name}`}}</div> <div class="doc-section-title">{{`${$t('knowledgeBaseManagement.currentKnowledgeBaseDocuments')}${kb?.name}`}}</div>
<div class="doc-section-count" v-if="kb"> {{ documents.length }} 个文档</div> <div class="doc-section-count" v-if="kb">{{ $t('knowledgeBaseManagement.totalDocuments', { total: documents.length }) }}</div>
</div> </div>
<div class="doc-section-actions"> <div class="doc-section-actions">
<el-input <el-input
placeholder="搜索文档..." :placeholder="$t('knowledgeFileUpload.documentNamePlaceholder')"
v-model="searchDocName" v-model="searchDocName"
class="doc-search-input" class="doc-search-input"
size="small" size="small"
prefix-icon="el-icon-search" prefix-icon="el-icon-search"
clearable clearable
/> />
<el-button size="small" @click="handleSearch">查询</el-button> <el-button size="small" @click="handleSearch">{{ $t('knowledgeBaseManagement.search') }}</el-button>
<el-button size="small" @click="handleUpload">新增</el-button> <el-button size="small" @click="handleUpload">{{ $t('knowledgeBaseManagement.add') }}</el-button>
<el-button size="small" @click="handleRetrievalTest">召回测试</el-button> <el-button size="small" @click="handleRetrievalTest">{{ $t('knowledgeFileUpload.retrievalTest') }}</el-button>
</div> </div>
</div> </div>
<div class="doc-grid" v-loading="docLoading" element-loading-text="加载中..."> <div class="doc-grid" v-loading="docLoading" :element-loading-text="$t('knowledgeBaseManagement.loading')">
<div v-for="doc in documents" :key="doc.id" class="doc-card"> <div v-for="doc in documents" :key="doc.id" class="doc-card">
<div class="doc-card-top"> <div class="doc-card-top">
<div class="doc-file-icon" :class="getFileIconClass(doc.name)"> <img class="doc-file-icon" :src="getFileIconSrc(doc.name)" />
<i :class="getFileIcon(doc.name)"></i>
</div>
<div class="doc-card-info"> <div class="doc-card-info">
<div class="doc-card-name" :title="doc.name">{{ doc.name }}</div> <div class="doc-card-name" :title="doc.name">{{ doc.name }}</div>
<div class="doc-card-meta">上传时间 {{ formatDate(doc.createdAt) }}</div> <div class="doc-card-meta">{{ $t('knowledgeFileUpload.uploadTime') }} {{ formatDate(doc.createdAt) }}</div>
</div> </div>
</div> </div>
<div class="doc-card-bottom"> <div class="doc-card-bottom">
<div class="doc-card-progress"> <div class="doc-card-progress">
<div class="progress-ring"> <div class="progress-ring">
<svg viewBox="0 0 36 36"> <svg viewBox="0 0 44 44">
<circle class="bg" cx="18" cy="18" r="14"/> <circle class="bg" cx="22" cy="22" r="17"/>
<circle class="fg" :class="getProgressClass(doc)" cx="18" cy="18" r="14" <circle class="fg" :class="getProgressClass(doc)" cx="22" cy="22" r="17"
stroke-dasharray="87.96" stroke-dasharray="106.81"
:stroke-dashoffset="getProgressOffset(doc)"/> :stroke-dashoffset="getProgressOffset(doc)"/>
</svg> </svg>
<span class="progress-ring-text" :style="{ color: getProgressTextColor(doc) }">{{ getProgressText(doc) }}</span> <span class="progress-ring-text" :style="{ color: getProgressTextColor(doc) }">{{ getProgressText(doc) }}</span>
</div> </div>
<span class="progress-label" :class="getProgressClass(doc)">{{ getDocStatusText(doc) }}</span> <div class="progress-container">
<span class="doc-slice-count" v-if="doc.sliceCount > 0">切片 {{ doc.sliceCount }}</span> <span class="progress-label" :class="getProgressClass(doc)">{{ getDocStatusText(doc) }}</span>
<span class="doc-slice-count">{{ $t('knowledgeFileUpload.sliceCount') }} <b>{{ doc.sliceCount }}</b></span>
</div>
</div> </div>
<div class="doc-card-actions"> <div class="doc-card-actions">
<el-button <el-button
@@ -50,7 +50,7 @@
type="text" type="text"
@click.stop="$emit('view-slices', doc)" @click.stop="$emit('view-slices', doc)"
> >
查看切片 {{ $t('knowledgeFileUpload.viewSlices') }}
</el-button> </el-button>
<el-button <el-button
v-else v-else
@@ -59,16 +59,16 @@
:loading="doc.parsing" :loading="doc.parsing"
@click.stop="handleParse(doc)" @click.stop="handleParse(doc)"
> >
解析 {{ $t('knowledgeFileUpload.parse') }}
</el-button> </el-button>
<el-button type="text" class="delete-btn" @click.stop="handleDelete(doc)"> <el-button type="text" class="delete-btn" @click.stop="handleDelete(doc)">
删除 {{ $t('knowledgeBaseManagement.delete') }}
</el-button> </el-button>
</div> </div>
</div> </div>
</div> </div>
<div v-if="documents.length === 0 && !docLoading" class="doc-empty"> <div v-if="documents.length === 0 && !docLoading" class="doc-empty">
<el-empty description="暂无文档"></el-empty> <el-empty :description="$t('knowledgeBaseManagement.noData')"></el-empty>
</div> </div>
</div> </div>
</div> </div>
@@ -77,6 +77,18 @@
<script> <script>
import Api from "@/apis/api"; import Api from "@/apis/api";
const iconMap = {
pdf: require('@/assets/knowledge-base/pdf.png'),
doc: require('@/assets/knowledge-base/docX.png'),
docx: require('@/assets/knowledge-base/docX.png'),
xls: require('@/assets/knowledge-base/xlsx.png'),
xlsx: require('@/assets/knowledge-base/xlsx.png'),
csv: require('@/assets/knowledge-base/xlsx.png'),
md: require('@/assets/knowledge-base/MD.png'),
markdown: require('@/assets/knowledge-base/MD.png'),
txt: require('@/assets/knowledge-base/txt.png'),
};
export default { export default {
name: 'LibraryItem', name: 'LibraryItem',
props: { props: {
@@ -143,49 +155,41 @@ export default {
}, },
handleUpload() { handleUpload() {
if (!this.kb) {
this.$message.warning('请先选择知识库');
return;
}
this.$emit('upload'); this.$emit('upload');
}, },
handleRetrievalTest() { handleRetrievalTest() {
if (!this.kb) {
this.$message.warning('请先选择知识库');
return;
}
this.$emit('retrieval-test'); this.$emit('retrieval-test');
}, },
handleDelete(doc) { handleDelete(doc) {
if (!this.kb) return; if (!this.kb) return;
this.$confirm( this.$confirm(
'确定要删除该文档吗?', this.$t('knowledgeFileUpload.confirmDelete'),
'警告', this.$t('message.warning'),
{ {
confirmButtonText: '确定', confirmButtonText: this.$t('knowledgeBaseDialog.confirm'),
cancelButtonText: '取消', cancelButtonText: this.$t('knowledgeBaseDialog.cancel'),
type: 'warning' type: 'warning'
} }
).then(() => { ).then(() => {
Api.knowledgeBase.deleteDocument(this.kb.datasetId, doc.id, (res) => { Api.knowledgeBase.deleteDocument(this.kb.datasetId, doc.id, (res) => {
if (res.data && res.data.code === 0) { if (res.data && res.data.code === 0) {
this.fetchDocuments(); this.fetchDocuments();
this.$message.success('文档删除成功'); this.$message.success(this.$t('knowledgeFileUpload.deleteSuccess'));
} else { } else {
this.$message.error(res.data?.msg || '文档删除失败'); this.$message.error(res.data?.msg || this.$t('knowledgeFileUpload.deleteFailed'));
} }
}, () => { }, () => {
this.$message.error('文档删除失败'); this.$message.error(this.$t('knowledgeFileUpload.deleteFailed'));
}); });
}).catch(() => {}); }).catch(() => {});
}, },
handleParse(doc) { handleParse(doc) {
this.$confirm('确定要解析该文档吗?', '提示', { this.$confirm(this.$t('knowledgeFileUpload.confirmParse'), this.$t('message.info'), {
confirmButtonText: '确定', confirmButtonText: this.$t('knowledgeFileUpload.confirm'),
cancelButtonText: '取消', cancelButtonText: this.$t('knowledgeFileUpload.cancel'),
type: 'info' type: 'info'
}).then(() => { }).then(() => {
this.$set(doc, 'parsing', true); this.$set(doc, 'parsing', true);
@@ -195,19 +199,19 @@ export default {
doc.id, doc.id,
({ data }) => { ({ data }) => {
if (data && data.code === 0) { if (data && data.code === 0) {
this.$message.success('解析请求已提交,正在处理中'); this.$message.success(this.$t('knowledgeFileUpload.parsing'));
this.$set(doc, 'parsing', false); this.$set(doc, 'parsing', false);
this.startParsePolling(doc); this.startParsePolling(doc);
} else { } else {
this.$set(doc, 'parsing', false); this.$set(doc, 'parsing', false);
doc.parseStatusCode = 0; doc.parseStatusCode = 0;
this.$message.error(data?.msg || '解析失败'); this.$message.error(data?.msg || this.$t('knowledgeFileUpload.parseFailed'));
} }
}, },
(err) => { (err) => {
this.$set(doc, 'parsing', false); this.$set(doc, 'parsing', false);
doc.parseStatusCode = 0; doc.parseStatusCode = 0;
this.$message.error(err?.data?.msg || '解析失败'); this.$message.error(err?.data?.msg || this.$t('knowledgeFileUpload.parseFailed'));
} }
); );
}).catch(() => {}); }).catch(() => {});
@@ -228,10 +232,10 @@ export default {
if (updated.parseStatusCode !== 1) { if (updated.parseStatusCode !== 1) {
this.$set(doc, 'parsing', false); this.$set(doc, 'parsing', false);
if (updated.parseStatusCode === 3) { if (updated.parseStatusCode === 3) {
this.$message.success(`文档${doc.name}解析完成`); this.$message.success(`${doc.name}${this.$t('knowledgeFileUpload.parseSuccess')}`);
this.fetchSliceCountForSingleDocument(doc); this.fetchSliceCountForSingleDocument(doc);
} else if (updated.parseStatusCode === 4) { } else if (updated.parseStatusCode === 4) {
this.$message.error(`文档${doc.name}解析失败`); this.$message.error(`${doc.name}${this.$t('knowledgeFileUpload.parseFailed')}`);
} }
return; return;
} }
@@ -269,26 +273,10 @@ export default {
); );
}, },
getFileIconClass(fileName) { getFileIconSrc(fileName) {
if (!fileName) return 'txt'; if (!fileName) return require('@/assets/knowledge-base/default.png');
const ext = fileName.split('.').pop().toLowerCase(); const ext = fileName.split('.').pop().toLowerCase();
if (ext === 'pdf') return 'pdf'; return iconMap[ext] || require('@/assets/knowledge-base/default.png');
if (['doc', 'docx'].includes(ext)) return 'word';
if (['xls', 'xlsx', 'csv'].includes(ext)) return 'excel';
if (['ppt', 'pptx'].includes(ext)) return 'ppt';
if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'].includes(ext)) return 'img';
return 'txt';
},
getFileIcon(fileName) {
if (!fileName) return 'el-icon-document';
const ext = fileName.split('.').pop().toLowerCase();
if (ext === 'pdf') return 'el-icon-document';
if (['doc', 'docx'].includes(ext)) return 'el-icon-document';
if (['xls', 'xlsx', 'csv'].includes(ext)) return 'el-icon-s-grid';
if (['ppt', 'pptx'].includes(ext)) return 'el-icon-data-board';
if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'].includes(ext)) return 'el-icon-picture';
return 'el-icon-document';
}, },
getProgressClass(doc) { getProgressClass(doc) {
@@ -300,7 +288,7 @@ export default {
getProgressOffset(doc) { getProgressOffset(doc) {
const code = doc.parseStatusCode; const code = doc.parseStatusCode;
const circumference = 87.96; const circumference = 106.81;
if (code === 3) return 0; if (code === 3) return 0;
if (code === 1) { if (code === 1) {
const progress = doc.chunkNum && doc.totalChunkNum const progress = doc.chunkNum && doc.totalChunkNum
@@ -332,12 +320,12 @@ export default {
getDocStatusText(doc) { getDocStatusText(doc) {
const code = doc.parseStatusCode; const code = doc.parseStatusCode;
if (code === 3) return '已完成'; if (code === 3) return this.$t('knowledgeFileUpload.statusCompleted');
if (code === 1) return '处理中'; if (code === 1) return this.$t('knowledgeFileUpload.statusProcessing');
if (code === 0) return '未开始'; if (code === 0) return this.$t('knowledgeFileUpload.statusNotStarted');
if (code === 4) return '失败'; if (code === 4) return this.$t('knowledgeFileUpload.statusFailed');
if (code === 2) return '已取消'; if (code === 2) return this.$t('knowledgeFileUpload.statusCancelled');
return '未开始'; return this.$t('knowledgeFileUpload.statusNotStarted');
}, },
formatDate(dateString) { formatDate(dateString) {
@@ -353,7 +341,7 @@ export default {
/* ========== Document Section ========== */ /* ========== Document Section ========== */
.doc-section { .doc-section {
background: #fff; background: #fff;
border-radius: 16px; border-radius: 10px;
padding: 16px 20px; padding: 16px 20px;
box-shadow: 0 4px 16px rgba(31, 42, 68, 0.06); box-shadow: 0 4px 16px rgba(31, 42, 68, 0.06);
border: 1px solid #f0f3f9; border: 1px solid #f0f3f9;
@@ -378,7 +366,7 @@ export default {
.doc-section-title { .doc-section-title {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 500;
color: #1f2a44; color: #1f2a44;
} }
@@ -402,7 +390,7 @@ export default {
.doc-grid { .doc-grid {
padding: 10px; padding: 10px;
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(auto-fill,minmax(300px, 1fr));
gap: 16px 20px; gap: 16px 20px;
flex: 1; flex: 1;
min-height: 0; min-height: 0;
@@ -411,7 +399,7 @@ export default {
} }
.doc-card { .doc-card {
height: 110px; height: 120px;
border-radius: 8px; border-radius: 8px;
border: 1px solid transparent; border: 1px solid transparent;
padding: 18px; padding: 18px;
@@ -437,21 +425,11 @@ export default {
} }
.doc-file-icon { .doc-file-icon {
width: 44px; width: 46px;
height: 44px; height: 46px;
border-radius: 10px; border-radius: 10px;
display: flex; object-fit: contain;
align-items: center;
justify-content: center;
font-size: 20px;
flex-shrink: 0; flex-shrink: 0;
&.pdf { background: #fff0f0; color: #ff5a5f; }
&.word { background: #e8ecff; color: #2f5bff; }
&.excel { background: #e8f9ee; color: #34c759; }
&.ppt { background: #fff3e0; color: #ff9500; }
&.txt { background: #f0f3f9; color: #788ba8; }
&.img { background: #f3e8ff; color: #9b59b6; }
} }
.doc-card-info { .doc-card-info {
@@ -466,7 +444,7 @@ export default {
.doc-card-name { .doc-card-name {
font-size: 15px; font-size: 15px;
font-weight: 600; font-weight: 500;
color: #1f2a44; color: #1f2a44;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@@ -494,13 +472,13 @@ export default {
/* ========== Progress Ring ========== */ /* ========== Progress Ring ========== */
.progress-ring { .progress-ring {
width: 36px; width: 44px;
height: 36px; height: 44px;
position: relative; position: relative;
svg { svg {
width: 36px; width: 44px;
height: 36px; height: 44px;
transform: rotate(-90deg); transform: rotate(-90deg);
} }
@@ -532,14 +510,30 @@ export default {
color: #7888a8; color: #7888a8;
} }
.progress-label { .progress-container {
font-size: 12px; height: 32px;
color: #7888a8; display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
.progress-label {
font-size: 12px;
color: #7888a8;
&.complete { color: #34c759; }
&.processing { color: #2f5bff; }
}
&.complete { color: #34c759; } .doc-slice-count {
&.processing { color: #2f5bff; } font-size: 12px;
> b {
font-weight: bold;
font-size: 14px;
}
}
} }
.doc-card-actions { .doc-card-actions {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -558,15 +552,6 @@ export default {
} }
} }
.doc-slice-count {
font-size: 12px;
color: #6b80eb;
background: #f0f3ff;
padding: 2px 8px;
border-radius: 4px;
margin-left: 4px;
}
.doc-empty { .doc-empty {
grid-column: 1 / -1; grid-column: 1 / -1;
display: flex; display: flex;
@@ -2,10 +2,10 @@
<div class="welcome"> <div class="welcome">
<HeaderBar /> <HeaderBar />
<div class="operation-bar"> <div class="operation-bar">
<h2 class="page-title">知识库中心</h2> <h2 class="page-title">{{ $t('knowledgeBaseManagement.title') }}</h2>
<div class="right-operations"> <div class="right-operations">
<el-input <el-input
placeholder="搜索知识库..." :placeholder="$t('knowledgeBaseManagement.searchPlaceholder')"
v-model="searchKbName" v-model="searchKbName"
class="search-input" class="search-input"
@keyup.enter.native="handleSearchKb" @keyup.enter.native="handleSearchKb"
@@ -14,22 +14,22 @@
/> />
<el-button class="btn-upload" @click="handleSearchKb"> <el-button class="btn-upload" @click="handleSearchKb">
<i class="el-icon-search"></i> <i class="el-icon-search"></i>
查询 {{ $t('knowledgeBaseManagement.search') }}
</el-button> </el-button>
<el-button class="btn-new-kb" @click="showAddDialog"> <el-button class="btn-new-kb" @click="showAddDialog">
<i class="el-icon-plus"></i> <i class="el-icon-plus"></i>
新建知识库 {{ $t('knowledgeBaseManagement.addKnowledgeBase') }}
</el-button> </el-button>
</div> </div>
</div> </div>
<div class="main-wrapper"> <div class="main-wrapper">
<div class="content-panel"> <div class="content-panel">
<div class="content-area" v-loading="loading" element-loading-text="加载中..."> <div class="content-area" v-loading="loading" :element-loading-text="$t('knowledgeBaseManagement.loading')">
<!-- Knowledge Base Cards Section --> <!-- Knowledge Base Cards Section -->
<div class="kb-section"> <div class="kb-section">
<div class="kb-section-header"> <div class="kb-section-header">
<div class="kb-section-title">知识库切换</div> <div class="kb-section-title">{{ $t('knowledgeBaseManagement.switchKnowledgeBase') }}</div>
</div> </div>
<div class="kb-cards-wrapper"> <div class="kb-cards-wrapper">
<div class="kb-arrow left" @click="scrollCards(-1)" v-if="filteredKnowledgeBases.length > 0"> <div class="kb-arrow left" @click="scrollCards(-1)" v-if="filteredKnowledgeBases.length > 0">
@@ -44,16 +44,16 @@
@click="selectKnowledgeBase(kb)" @click="selectKnowledgeBase(kb)"
> >
<div class="kb-card-actions-top"> <div class="kb-card-actions-top">
<button class="kb-action-icon" title="编辑" @click.stop="editKnowledgeBase(kb)"> <button class="kb-action-icon" :title="$t('knowledgeBaseManagement.edit')" @click.stop="editKnowledgeBase(kb)">
<i class="el-icon-edit"></i> <i class="el-icon-edit"></i>
</button> </button>
<button class="kb-action-icon delete" title="删除" @click.stop="deleteKnowledgeBase(kb)"> <button class="kb-action-icon delete" :title="$t('knowledgeBaseManagement.delete')" @click.stop="deleteKnowledgeBase(kb)">
<i class="el-icon-delete"></i> <i class="el-icon-delete"></i>
</button> </button>
</div> </div>
<div class="kb-card-top"> <div class="kb-card-top">
<div class="kb-card-icon" :class="getCardColorClass(index)"> <div class="kb-card-icon" :class="getCardColorClass(index)">
<i class="el-icon-document"></i> <ManualIcon :color="getIconColor(index)" />
</div> </div>
<div class="kb-card-info"> <div class="kb-card-info">
<div class="kb-card-name">{{ kb.name }}</div> <div class="kb-card-name">{{ kb.name }}</div>
@@ -80,7 +80,7 @@
</div> </div>
<div v-if="filteredKnowledgeBases.length === 0 && !loading" class="kb-empty"> <div v-if="filteredKnowledgeBases.length === 0 && !loading" class="kb-empty">
<i class="el-icon-folder-opened"></i> <i class="el-icon-folder-opened"></i>
<p>暂无知识库</p> <p>{{ $t('knowledgeBaseManagement.noData') }}</p>
</div> </div>
</div> </div>
<div class="kb-arrow right" @click="scrollCards(1)" v-if="filteredKnowledgeBases.length > 0"> <div class="kb-arrow right" @click="scrollCards(1)" v-if="filteredKnowledgeBases.length > 0">
@@ -100,7 +100,7 @@
@refresh="refreshDocuments" @refresh="refreshDocuments"
/> />
<div v-else class="doc-empty-placeholder"> <div v-else class="doc-empty-placeholder">
<el-empty description="请先选择一个知识库"></el-empty> <el-empty :description="$t('knowledgeBaseManagement.noData')"></el-empty>
</div> </div>
</div> </div>
</div> </div>
@@ -118,7 +118,7 @@
<!-- Slice Dialog --> <!-- Slice Dialog -->
<el-dialog <el-dialog
:title="`切片管理 - ${currentDocumentName}`" :title="`${$t('knowledgeFileUpload.viewSlices')} - ${currentDocumentName}`"
:visible.sync="sliceDialogVisible" :visible.sync="sliceDialogVisible"
width="900px" width="900px"
:close-on-click-modal="false" :close-on-click-modal="false"
@@ -128,7 +128,7 @@
<div v-if="sliceList.length > 0" class="slice-cards-container"> <div v-if="sliceList.length > 0" class="slice-cards-container">
<div v-for="(slice, index) in sliceList" :key="index" class="slice-card"> <div v-for="(slice, index) in sliceList" :key="index" class="slice-card">
<div class="slice-header-info"> <div class="slice-header-info">
<p><strong>切片 {{ (sliceCurrentPage - 1) * slicePageSize + index + 1 }}</strong></p> <p><strong>{{ $t('knowledgeFileUpload.slice') }} {{ (sliceCurrentPage - 1) * slicePageSize + index + 1 }}</strong></p>
</div> </div>
<div class="slice-card-content"> <div class="slice-card-content">
<div class="content-text">{{ slice.content }}</div> <div class="content-text">{{ slice.content }}</div>
@@ -136,25 +136,25 @@
</div> </div>
</div> </div>
<div v-else class="no-slice-data"> <div v-else class="no-slice-data">
<el-empty description="暂无切片数据"></el-empty> <el-empty :description="$t('knowledgeFileUpload.noSliceData')"></el-empty>
</div> </div>
</div> </div>
<div class="slice-pagination"> <div class="slice-pagination">
<el-select v-model="slicePageSize" @change="handleSliceSizeChange" class="slice-page-size-select"> <el-select v-model="slicePageSize" @change="handleSliceSizeChange" class="slice-page-size-select">
<el-option v-for="item in [10, 20, 50]" :key="item" :label="`${item}条/页`" :value="item"></el-option> <el-option v-for="item in [10, 20, 50]" :key="item" :label="`${item}${$t('knowledgeFileUpload.itemsPerPage')}`" :value="item"></el-option>
</el-select> </el-select>
<button class="slice-page-btn" :disabled="sliceCurrentPage === 1" @click="sliceCurrentPage = 1; fetchSlices()">首页</button> <button class="slice-page-btn" :disabled="sliceCurrentPage === 1" @click="sliceCurrentPage = 1; fetchSlices()">{{ $t('knowledgeBaseManagement.firstPage') }}</button>
<button class="slice-page-btn" :disabled="sliceCurrentPage === 1" @click="sliceCurrentPage--; fetchSlices()">上一页</button> <button class="slice-page-btn" :disabled="sliceCurrentPage === 1" @click="sliceCurrentPage--; fetchSlices()">{{ $t('knowledgeBaseManagement.prevPage') }}</button>
<button v-for="page in sliceVisiblePages" :key="page" class="slice-page-btn" :class="{ active: page === sliceCurrentPage }" @click="sliceCurrentPage = page; fetchSlices()">{{ page }}</button> <button v-for="page in sliceVisiblePages" :key="page" class="slice-page-btn" :class="{ active: page === sliceCurrentPage }" @click="sliceCurrentPage = page; fetchSlices()">{{ page }}</button>
<button class="slice-page-btn" :disabled="sliceCurrentPage >= slicePageCount" @click="sliceCurrentPage++; fetchSlices()">下一页</button> <button class="slice-page-btn" :disabled="sliceCurrentPage >= slicePageCount" @click="sliceCurrentPage++; fetchSlices()">{{ $t('knowledgeBaseManagement.nextPage') }}</button>
<span class="slice-total-text"> {{ sliceTotal }} </span> <span class="slice-total-text"> {{ $t('knowledgeBaseManagement.totalRecords', { total: sliceTotal }) }}</span>
</div> </div>
</div> </div>
</el-dialog> </el-dialog>
<!-- Upload Dialog --> <!-- Upload Dialog -->
<el-dialog <el-dialog
title="上传文档" :title="$t('knowledgeFileUpload.uploadDocument')"
:visible.sync="uploadDialogVisible" :visible.sync="uploadDialogVisible"
width="800px" width="800px"
:close-on-click-modal="false" :close-on-click-modal="false"
@@ -170,12 +170,12 @@
drag drag
> >
<i class="el-icon-upload"></i> <i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处或点击上传</div> <div class="el-upload__text">{{ $t('knowledgeFileUpload.dragOrClick') }}</div>
<div class="el-upload__tip" slot="tip">支持的文档类型PDFDOCDOCXTXTMDCSVXLSXLSXPPTPPTX单次批量上传文件数不超过 32 </div> <div class="el-upload__tip" slot="tip">{{ $t('knowledgeFileUpload.uploadTip') }}</div>
</el-upload> </el-upload>
<!-- 已选择文件列表 --> <!-- 已选择文件列表 -->
<div class="selected-files-section" v-if="selectedFilesList.length > 0"> <div class="selected-files-section" v-if="selectedFilesList.length > 0">
<h4>已选择文件 ({{ selectedFilesList.length }})</h4> <h4>{{ $t('knowledgeFileUpload.selectedFiles') }} ({{ selectedFilesList.length }})</h4>
<div class="selected-files-list"> <div class="selected-files-list">
<div v-for="(file, index) in selectedFilesList" :key="index" class="selected-file-item"> <div v-for="(file, index) in selectedFilesList" :key="index" class="selected-file-item">
<div class="file-info"> <div class="file-info">
@@ -190,41 +190,41 @@
</div> </div>
</div> </div>
<span slot="footer"> <span slot="footer">
<el-button @click="uploadDialogVisible = false">取消</el-button> <el-button @click="uploadDialogVisible = false">{{ $t('knowledgeFileUpload.cancel') }}</el-button>
<el-button type="primary" @click="handleBatchUploadSubmit" :loading="uploading" :disabled="selectedFilesList.length === 0"> <el-button type="primary" @click="handleBatchUploadSubmit" :loading="uploading" :disabled="selectedFilesList.length === 0">
确认上传 ({{ selectedFilesList.length }}) {{ $t('knowledgeFileUpload.confirm') }} ({{ selectedFilesList.length }})
</el-button> </el-button>
</span> </span>
</el-dialog> </el-dialog>
<!-- Retrieval Test Dialog --> <!-- Retrieval Test Dialog -->
<el-dialog <el-dialog
title="召回测试" :title="$t('knowledgeFileUpload.retrievalTest')"
:visible.sync="retrievalTestDialogVisible" :visible.sync="retrievalTestDialogVisible"
width="900px" width="900px"
:close-on-click-modal="false" :close-on-click-modal="false"
> >
<div class="retrieval-test-form"> <div class="retrieval-test-form">
<el-form :model="retrievalTestForm" label-width="100px"> <el-form :model="retrievalTestForm" label-width="100px">
<el-form-item label="测试问题" required> <el-form-item :label="$t('knowledgeFileUpload.testQuestion')" required>
<el-input <el-input
v-model="retrievalTestForm.question" v-model="retrievalTestForm.question"
type="textarea" type="textarea"
:rows="3" :rows="3"
placeholder="请输入测试问题" :placeholder="$t('knowledgeFileUpload.testQuestionPlaceholder')"
/> />
</el-form-item> </el-form-item>
</el-form> </el-form>
<div style="text-align: center; margin-top: 20px;"> <div style="text-align: center; margin-top: 20px;">
<el-button type="primary" @click="runRetrievalTest" :loading="retrievalTestLoading">执行测试</el-button> <el-button type="primary" @click="runRetrievalTest" :loading="retrievalTestLoading">{{ $t('knowledgeFileUpload.executeTest') }}</el-button>
<el-button @click="retrievalTestDialogVisible = false">取消</el-button> <el-button @click="retrievalTestDialogVisible = false">{{ $t('knowledgeFileUpload.cancel') }}</el-button>
</div> </div>
<div v-if="retrievalTestResult" class="retrieval-test-result" style="margin-top: 20px;"> <div v-if="retrievalTestResult" class="retrieval-test-result" style="margin-top: 20px;">
<h4 style="margin-bottom: 12px;">测试结果</h4> <h4 style="margin-bottom: 12px;">{{ $t('knowledgeFileUpload.testResult') }}</h4>
<div class="result-chunks"> <div class="result-chunks">
<div v-for="(chunk, index) in retrievalTestResult.chunks" :key="index" class="result-chunk"> <div v-for="(chunk, index) in retrievalTestResult.chunks" :key="index" class="result-chunk">
<p><strong>切片 {{ index + 1 }}</strong></p> <p><strong>{{ $t('knowledgeFileUpload.slice') }} {{ index + 1 }}</strong></p>
<div style="margin: 8px 0; font-size: 12px; color: #409eff;">相似度: {{ (chunk.similarity || 0).toFixed(4) }}</div> <div style="margin: 8px 0; font-size: 12px; color: #409eff;">{{ $t('knowledgeFileUpload.comprehensiveSimilarity') }}: {{ (chunk.similarity || 0).toFixed(4) }}</div>
<div class="chunk-content">{{ chunk.content }}</div> <div class="chunk-content">{{ chunk.content }}</div>
</div> </div>
</div> </div>
@@ -244,9 +244,10 @@ import HeaderBar from "@/components/HeaderBar.vue";
import VersionFooter from "@/components/VersionFooter.vue"; import VersionFooter from "@/components/VersionFooter.vue";
import KnowledgeBaseDialog from "@/components/KnowledgeBaseDialog.vue"; import KnowledgeBaseDialog from "@/components/KnowledgeBaseDialog.vue";
import KnowledgeBaseItem from "./KnowledgeBaseItem.vue"; import KnowledgeBaseItem from "./KnowledgeBaseItem.vue";
import ManualIcon from "@/components/ManualIcon.vue";
export default { export default {
components: { HeaderBar, VersionFooter, KnowledgeBaseDialog, KnowledgeBaseItem }, components: { HeaderBar, VersionFooter, KnowledgeBaseDialog, KnowledgeBaseItem, ManualIcon },
data() { data() {
return { return {
knowledgeBases: [], knowledgeBases: [],
@@ -326,12 +327,12 @@ export default {
if (updated) this.selectedKb = updated; if (updated) this.selectedKb = updated;
} }
} else { } else {
this.$message.error(res.data?.msg || '获取知识库列表失败'); this.$message.error(res.data?.msg || this.$t('knowledgeBaseManagement.getListFailed'));
} }
}, },
() => { () => {
this.loading = false; this.loading = false;
this.$message.error('获取知识库列表失败'); this.$message.error(this.$t('knowledgeBaseManagement.getListFailed'));
} }
); );
}, },
@@ -356,7 +357,7 @@ export default {
}, },
showAddDialog() { showAddDialog() {
this.dialogTitle = '新增知识库'; this.dialogTitle = this.$t('knowledgeBaseManagement.addKnowledgeBase');
this.knowledgeBaseForm = { this.knowledgeBaseForm = {
id: null, id: null,
datasetId: null, datasetId: null,
@@ -368,7 +369,7 @@ export default {
}, },
editKnowledgeBase(kb) { editKnowledgeBase(kb) {
this.dialogTitle = '编辑知识库'; this.dialogTitle = this.$t('knowledgeBaseManagement.editKnowledgeBase');
this.knowledgeBaseForm = { this.knowledgeBaseForm = {
id: kb.id, id: kb.id,
datasetId: kb.datasetId, datasetId: kb.datasetId,
@@ -391,50 +392,46 @@ export default {
Api.knowledgeBase.updateKnowledgeBase(kb.datasetId, updateForm, (res) => { Api.knowledgeBase.updateKnowledgeBase(kb.datasetId, updateForm, (res) => {
if (res.data && res.data.code !== 0) { if (res.data && res.data.code !== 0) {
this.fetchKnowledgeBases(); this.fetchKnowledgeBases();
this.$message.error(res.data?.msg || '更新失败'); this.$message.error(res.data?.msg || this.$t('knowledgeBaseManagement.updateFailed'));
} else { } else {
this.$message.success(kb.status === 1 ? '已启用' : '已停用'); this.$message.success(kb.status === 1 ? this.$t('knowledgeBaseManagement.enabled') : this.$t('knowledgeBaseManagement.disabled'));
if (this.selectedKb && this.selectedKb.datasetId === kb.datasetId) { if (this.selectedKb && this.selectedKb.datasetId === kb.datasetId) {
this.selectedKb = { ...kb }; this.selectedKb = { ...kb };
} }
} }
}, () => { }, () => {
this.fetchKnowledgeBases(); this.fetchKnowledgeBases();
this.$message.error('更新失败'); this.$message.error(this.$t('knowledgeBaseManagement.updateFailed'));
}); });
}, },
deleteKnowledgeBase(kb) { deleteKnowledgeBase(kb) {
this.$confirm( this.$confirm(
`确定要删除知识库「${kb.name}」吗?删除后不可恢复。`, this.$t("knowledgeBaseManagement.confirmBatchDelete", {count: 1}),
'警告', this.$t('message.warning'),
{ {
confirmButtonText: '确定', confirmButtonText: this.$t('knowledgeBaseDialog.confirm'),
cancelButtonText: '取消', cancelButtonText: this.$t('knowledgeBaseDialog.cancel'),
type: 'warning' type: 'warning'
} }
).then(() => { ).then(() => {
Api.knowledgeBase.deleteKnowledgeBase(kb.datasetId, (res) => { Api.knowledgeBase.deleteKnowledgeBase(kb.datasetId, (res) => {
if (res.data && res.data.code === 0) { if (res.data && res.data.code === 0) {
this.$message.success('删除成功'); this.$message.success(this.$t('knowledgeBaseManagement.batchDeleteSuccess', { count: 1 }));
if (this.selectedKb && this.selectedKb.datasetId === kb.datasetId) { if (this.selectedKb && this.selectedKb.datasetId === kb.datasetId) {
this.selectedKb = null; this.selectedKb = null;
} }
this.fetchKnowledgeBases(); this.fetchKnowledgeBases();
} else { } else {
this.$message.error(res.data?.msg || '删除失败'); this.$message.error(res.data?.msg || this.$t('knowledgeBaseManagement.deleteFailed'));
} }
}, (err) => { }, (err) => {
this.$message.error(err?.data?.msg || '删除失败'); this.$message.error(err?.data?.msg || this.$t('knowledgeBaseManagement.deleteFailed'));
}); });
}).catch(() => {}); }).catch(() => {});
}, },
showUploadDialog() { showUploadDialog() {
if (!this.selectedKb) {
this.$message.warning('请先选择知识库');
return;
}
this.uploadDialogVisible = true; this.uploadDialogVisible = true;
}, },
@@ -444,12 +441,12 @@ export default {
if (res.data && res.data.code === 0) { if (res.data && res.data.code === 0) {
this.dialogVisible = false; this.dialogVisible = false;
this.fetchKnowledgeBases(); this.fetchKnowledgeBases();
this.$message.success('修改成功'); this.$message.success(this.$t('knowledgeBaseManagement.updateSuccess'));
} else { } else {
this.$message.error(res.data?.msg || '更新失败'); this.$message.error(res.data?.msg || this.$t('knowledgeBaseManagement.updateFailed'));
} }
}, (err) => { }, (err) => {
this.$message.error(err?.data?.msg || '更新失败'); this.$message.error(err?.data?.msg || this.$t('knowledgeBaseManagement.updateFailed'));
}); });
} else { } else {
const createData = { const createData = {
@@ -462,12 +459,12 @@ export default {
if (res.data && res.data.code === 0) { if (res.data && res.data.code === 0) {
this.dialogVisible = false; this.dialogVisible = false;
this.fetchKnowledgeBases(); this.fetchKnowledgeBases();
this.$message.success('新增成功'); this.$message.success(this.$t('knowledgeBaseManagement.addSuccess'));
} else { } else {
this.$message.error(res.data?.msg || '新增失败'); this.$message.error(res.data?.msg || this.$t('knowledgeBaseManagement.addFailed'));
} }
}, (err) => { }, (err) => {
this.$message.error(err?.data?.msg || '新增失败'); this.$message.error(err?.data?.msg || this.$t('knowledgeBaseManagement.addFailed'));
}); });
} }
}, },
@@ -479,13 +476,8 @@ export default {
this.selectedFilesList = []; this.selectedFilesList = [];
}, },
handleFileChange(file, fileList) { handleFileChange(file) {
if (!file || !file.raw) return; if (!file || !file.raw) return;
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
this.$message.error('文件大小不能超过10MB!');
return;
}
this.selectedFilesList.push({ this.selectedFilesList.push({
name: file.name, name: file.name,
size: file.size, size: file.size,
@@ -507,7 +499,7 @@ export default {
handleBatchUploadSubmit() { handleBatchUploadSubmit() {
if (this.selectedFilesList.length === 0) { if (this.selectedFilesList.length === 0) {
this.$message.error('请选择要上传的文件'); this.$message.error(this.$t('knowledgeFileUpload.fileRequired'));
return; return;
} }
@@ -523,14 +515,14 @@ export default {
if (data && data.code === 0) { if (data && data.code === 0) {
resolve({ success: true, fileName: file.name }); resolve({ success: true, fileName: file.name });
} else { } else {
reject({ success: false, fileName: file.name, error: data?.msg || '上传失败' }); reject({ success: false, fileName: file.name, error: data?.msg || this.$t('knowledgeFileUpload.uploadFailed') });
} }
}, },
(err) => { (err) => {
if (err && err.data) { if (err && err.data) {
reject({ success: false, fileName: file.name, error: err.data.msg || err.msg || '上传失败' }); reject({ success: false, fileName: file.name, error: err.data.msg || err.msg || this.$t('knowledgeFileUpload.uploadFailed') });
} else { } else {
reject({ success: false, fileName: file.name, error: '上传失败' }); reject({ success: false, fileName: file.name, error: this.$t('knowledgeFileUpload.uploadFailed') });
} }
console.error('上传文档失败:', err); console.error('上传文档失败:', err);
} }
@@ -546,12 +538,11 @@ export default {
const failedCount = results.filter(r => !r.success).length; const failedCount = results.filter(r => !r.success).length;
if (successCount > 0) { if (successCount > 0) {
this.$message.success(`成功上传 ${successCount} 个文件`); this.$message.success(this.$t('knowledgeFileUpload.uploadSuccess'));
} }
if (failedCount > 0) { if (failedCount > 0) {
const failedFiles = results.filter(r => !r.success).map(r => r.fileName); this.$message.error(this.$t('knowledgeFileUpload.uploadFailed'));
this.$message.error(`上传失败 ${failedCount} 个文件: ${failedFiles.join(', ')}`);
} }
if (successCount > 0) { if (successCount > 0) {
@@ -561,16 +552,12 @@ export default {
}) })
.catch(error => { .catch(error => {
this.uploading = false; this.uploading = false;
this.$message.error('批量上传失败'); this.$message.error(this.$t('knowledgeFileUpload.uploadFailed'));
console.error('批量上传失败:', error); console.error(error);
}); });
}, },
showRetrievalTestDialog() { showRetrievalTestDialog() {
if (!this.selectedKb) {
this.$message.warning('请先选择知识库');
return;
}
this.retrievalTestForm.question = ''; this.retrievalTestForm.question = '';
this.retrievalTestResult = null; this.retrievalTestResult = null;
this.retrievalTestDialogVisible = true; this.retrievalTestDialogVisible = true;
@@ -578,7 +565,7 @@ export default {
runRetrievalTest() { runRetrievalTest() {
if (!this.retrievalTestForm.question.trim()) { if (!this.retrievalTestForm.question.trim()) {
this.$message.error('请输入测试问题'); this.$message.error(this.$t('knowledgeFileUpload.testQuestionRequired'));
return; return;
} }
this.retrievalTestLoading = true; this.retrievalTestLoading = true;
@@ -637,7 +624,7 @@ export default {
this.sliceTotal = 0; this.sliceTotal = 0;
} }
} else { } else {
this.$message.error(data?.msg || '获取切片列表失败'); this.$message.error(data?.msg || this.$t('knowledgeBaseManagement.getListFailed'));
this.sliceList = []; this.sliceList = [];
this.sliceTotal = 0; this.sliceTotal = 0;
} }
@@ -645,9 +632,9 @@ export default {
(err) => { (err) => {
this.sliceLoading = false; this.sliceLoading = false;
if (err && err.data) { if (err && err.data) {
this.$message.error(err.data.msg || err.msg || '获取切片列表失败'); this.$message.error(err.data.msg || err.msg || this.$t('knowledgeBaseManagement.getListFailed'));
} else { } else {
this.$message.error('获取切片列表失败'); this.$message.error(this.$t('knowledgeBaseManagement.getListFailed'));
} }
this.sliceList = []; this.sliceList = [];
this.sliceTotal = 0; this.sliceTotal = 0;
@@ -662,7 +649,12 @@ export default {
}, },
getCardColorClass(index) { getCardColorClass(index) {
const colors = ['blue', 'purple', 'green', 'orange']; const colors = ['blue', 'green', 'purple', 'orange', 'pink', 'cyan'];
return colors[index % colors.length];
},
getIconColor(index) {
const colors = ['#2f5bff', '#34c759', '#6a5cff', '#ff9500', '#f43f7a', '#00c9db'];
return colors[index % colors.length]; return colors[index % colors.length];
}, },
@@ -671,7 +663,7 @@ export default {
}, },
getStatusText(kb) { getStatusText(kb) {
return kb.status === 1 ? '已启用' : '未启用'; return kb.status === 1 ? this.$t('knowledgeBaseManagement.enabled') : this.$t('knowledgeBaseManagement.disabled');
}, },
formatDate(dateString) { formatDate(dateString) {
@@ -794,7 +786,7 @@ export default {
/* ========== Knowledge Base Cards Section ========== */ /* ========== Knowledge Base Cards Section ========== */
.kb-section { .kb-section {
background: #fff; background: #fff;
border-radius: 16px; border-radius: 10px;
padding: 14px 20px; padding: 14px 20px;
box-shadow: 0 4px 16px rgba(31, 42, 68, 0.06); box-shadow: 0 4px 16px rgba(31, 42, 68, 0.06);
border: 1px solid #f0f3f9; border: 1px solid #f0f3f9;
@@ -809,7 +801,7 @@ export default {
.kb-section-title { .kb-section-title {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 500;
color: #1f2a44; color: #1f2a44;
} }
@@ -856,7 +848,7 @@ export default {
} }
.kb-card { .kb-card {
width: 340px; width: 300px;
border-radius: 8px; border-radius: 8px;
padding: 20px; padding: 20px;
border: 1px solid transparent; border: 1px solid transparent;
@@ -865,7 +857,7 @@ export default {
transition: all 0.25s; transition: all 0.25s;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 0 0 340px; flex: 0 0 300px;
position: relative; position: relative;
box-shadow: 0 0 10px rgba(47, 91, 255, 0.15); box-shadow: 0 0 10px rgba(47, 91, 255, 0.15);
@@ -944,6 +936,14 @@ export default {
background: linear-gradient(135deg, #fff3e0, #ffe4b8); background: linear-gradient(135deg, #fff3e0, #ffe4b8);
color: #ff9500; color: #ff9500;
} }
&.pink {
background: linear-gradient(135deg, #ffe4ed, #ffc8db);
color: #f43f7a;
}
&.cyan {
background: linear-gradient(135deg, #dff8fa, #c4f0f4);
color: #00c9db;
}
} }
.kb-card-info { .kb-card-info {
@@ -956,7 +956,7 @@ export default {
.kb-card-name { .kb-card-name {
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 500;
color: #1f2a44; color: #1f2a44;
margin-bottom: 6px; margin-bottom: 6px;
white-space: nowrap; white-space: nowrap;