/** * 知识库页面组件 */ export default class Knowledge { constructor(container, route) { this.container = container; this.route = route; this.currentPage = 1; // 初始化当前页码 this.init(); } async init() { this.render(); this.bindEvents(); await Promise.all([ this.loadKnowledgeList(), this.loadStats() ]); } async loadStats() { try { const response = await fetch('/api/knowledge/stats'); if (response.ok) { const stats = await response.json(); // 更新统计数据显示 const totalEl = this.container.querySelector('#stat-total'); const activeEl = this.container.querySelector('#stat-active'); const catsEl = this.container.querySelector('#stat-categories'); const confEl = this.container.querySelector('#stat-confidence'); if (totalEl) totalEl.textContent = stats.total_entries || 0; if (activeEl) activeEl.textContent = stats.active_entries || 0; // 后端现在返回的是已验证数量 if (catsEl) catsEl.textContent = Object.keys(stats.category_distribution || {}).length; if (confEl) confEl.textContent = ((stats.average_confidence || 0) * 100).toFixed(0) + '%'; } } catch (error) { console.error('加载统计信息失败:', error); } } render() { this.container.innerHTML = `
0
总条目
0
已验证
0
分类数量
0%
平均置信度

知识条目列表

# 问题/主题 内容预览 分类 置信度 操作
正在加载数据...
`; } bindEvents() { // 导入文件按钮 const fileInput = this.container.querySelector('#file-input'); const importBtn = this.container.querySelector('#btn-import-file'); importBtn.addEventListener('click', () => { fileInput.click(); }); fileInput.addEventListener('change', async (e) => { if (e.target.files.length > 0) { await this.uploadFile(e.target.files[0]); // 清空选择,允许再次选择同名文件 fileInput.value = ''; } }); // 搜索功能 const searchInput = this.container.querySelector('#search-input'); const searchBtn = this.container.querySelector('#btn-search'); const performSearch = () => { const query = searchInput.value.trim(); this.loadKnowledgeList(1, query); }; searchBtn.addEventListener('click', performSearch); searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { performSearch(); } }); // 批量操作按钮 const batchVerifyBtn = this.container.querySelector('#btn-batch-verify'); if (batchVerifyBtn) { batchVerifyBtn.addEventListener('click', () => this.batchAction('verify')); } const batchUnverifyBtn = this.container.querySelector('#btn-batch-unverify'); if (batchUnverifyBtn) { batchUnverifyBtn.addEventListener('click', () => this.batchAction('unverify')); } const batchDeleteBtn = this.container.querySelector('#btn-batch-delete'); if (batchDeleteBtn) { batchDeleteBtn.addEventListener('click', () => this.batchAction('delete')); } // 全选复选框 const checkAll = this.container.querySelector('#check-all'); if (checkAll) { checkAll.addEventListener('change', (e) => { const checks = this.container.querySelectorAll('.item-check'); checks.forEach(check => check.checked = e.target.checked); this.updateBatchButtons(); }); } } bindCheckboxEvents() { const checks = this.container.querySelectorAll('.item-check'); checks.forEach(check => { check.addEventListener('change', () => { this.updateBatchButtons(); // 如果有一个未选中,取消全选选中状态 if (!check.checked) { const checkAll = this.container.querySelector('#check-all'); if (checkAll) checkAll.checked = false; } }); }); } updateBatchButtons() { const checkedCount = this.container.querySelectorAll('.item-check:checked').length; const actionsGroup = this.container.querySelector('#batch-actions'); if (actionsGroup) { if (checkedCount > 0) { actionsGroup.classList.remove('d-none'); // 更新删除按钮文本 const deleteBtn = this.container.querySelector('#btn-batch-delete'); if (deleteBtn) deleteBtn.innerHTML = `删除 (${checkedCount})`; } else { actionsGroup.classList.add('d-none'); } } } async batchDeleteKnowledge() { const checks = this.container.querySelectorAll('.item-check:checked'); const ids = Array.from(checks).map(check => parseInt(check.dataset.id)); console.log('Deleting IDs:', ids); if (ids.length === 0) { alert('请先选择要删除的知识条目'); return; } if (!confirm(`确定要删除选中的 ${ids.length} 条知识吗?`)) { return; } try { const response = await fetch('/api/knowledge/batch_delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids: ids }) }); const result = await response.json(); if (response.ok && result.success) { alert(result.message || '删除成功'); // 重置全选状态 const checkAll = this.container.querySelector('#check-all'); if (checkAll) checkAll.checked = false; this.updateBatchDeleteButton(); // 刷新列表和统计(保持当前页) await Promise.all([ this.loadKnowledgeList(this.currentPage), this.loadStats() ]); } else { alert(`删除失败: ${result.message || '未知错误'}`); } } catch (error) { console.error('批量删除出错:', error); alert('批量删除出错,请查看控制台'); } } async loadKnowledgeList(page = null, query = '') { // 如果未指定页码,使用当前页码,默认为 1 const targetPage = page || this.currentPage || 1; this.currentPage = targetPage; const tbody = this.container.querySelector('#knowledge-list-body'); // 柔性加载:不立即清空,而是降低透明度并显示加载态 // 这可以防止表格高度塌陷导致的视觉跳动 tbody.style.opacity = '0.5'; tbody.style.transition = 'opacity 0.2s'; // 如果表格是空的(第一次加载),则显示加载占位符 if (!tbody.hasChildNodes() || tbody.children.length === 0 || tbody.querySelector('.text-center')) { tbody.innerHTML = '
加载中...
'; tbody.style.opacity = '1'; } try { let url = `/api/knowledge?page=${targetPage}&per_page=10`; if (query) { url = `/api/knowledge/search?q=${encodeURIComponent(query)}`; } const response = await fetch(url); const result = await response.json(); tbody.innerHTML = ''; tbody.style.opacity = '1'; // 恢复不透明 // 处理搜索结果(通常是数组)和分页结果(包含 items)的差异 let items = []; if (Array.isArray(result)) { items = result; } else if (result.items) { items = result.items; } if (items.length === 0) { tbody.innerHTML = '暂无知识条目'; return; } items.forEach((item, index) => { // ... (渲染逻辑保持不变) const tr = document.createElement('tr'); // 验证状态图标 let statusBadge = ''; if (item.is_verified) { statusBadge = ''; } // 验证操作按钮 let verifyBtn = ''; if (item.is_verified) { verifyBtn = ` `; } else { verifyBtn = ` `; } tr.innerHTML = ` ${(targetPage - 1) * 10 + index + 1}
${item.question} ${statusBadge}
${item.answer}
${item.category || '未分类'} ${(item.confidence_score * 100).toFixed(0)}% ${verifyBtn} `; // 绑定验证/取消验证事件 const verifyActionBtn = tr.querySelector('.btn-verify, .btn-unverify'); if (verifyActionBtn) { verifyActionBtn.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); const isVerify = verifyActionBtn.classList.contains('btn-verify'); await this.toggleVerify(item.id, isVerify, tr); }); } // 绑定删除事件 const deleteBtn = tr.querySelector('.btn-delete'); deleteBtn.addEventListener('click', (e) => { e.preventDefault(); this.deleteKnowledge(item.id); }); tbody.appendChild(tr); }); // 重新绑定复选框事件 this.bindCheckboxEvents(); // 渲染分页 if (result.pages && result.pages > 1) { this.renderPagination(result); } else { this.container.querySelector('#pagination-container').innerHTML = ''; } } catch (error) { console.error('加载知识列表失败:', error); tbody.innerHTML = `加载失败: ${error.message}`; tbody.style.opacity = '1'; } } async uploadFile(file) { const formData = new FormData(); formData.append('file', file); // 显示上传中提示 const importBtn = this.container.querySelector('#btn-import-file'); const originalText = importBtn.innerHTML; importBtn.disabled = true; importBtn.innerHTML = ' 上传中...'; try { const response = await fetch('/api/knowledge/upload', { method: 'POST', body: formData }); const result = await response.json(); if (response.ok && result.success) { alert(`文件上传成功!共提取 ${result.knowledge_count} 条知识。`); // 刷新列表和统计 await Promise.all([ this.loadKnowledgeList(), this.loadStats() ]); } else { alert(`上传失败: ${result.error || result.message || '未知错误'}`); } } catch (error) { console.error('上传文件出错:', error); alert('上传文件出错,请查看控制台'); } finally { importBtn.disabled = false; importBtn.innerHTML = originalText; } } async toggleVerify(id, isVerify, trElement = null) { const action = isVerify ? 'verify' : 'unverify'; const url = `/api/knowledge/${action}/${id}`; try { // 如果有 trElement,先显示加载状态 let originalBtnHtml = ''; let actionBtn = null; if (trElement) { actionBtn = trElement.querySelector('.btn-verify, .btn-unverify'); if (actionBtn) { originalBtnHtml = actionBtn.innerHTML; actionBtn.disabled = true; actionBtn.innerHTML = ''; } } const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const result = await response.json(); if (response.ok && result.success) { // 如果提供了 DOM 元素,直接更新 DOM,避免刷新整个列表导致跳动 if (trElement) { this.updateRowStatus(trElement, id, isVerify); // 后台静默刷新统计数据 this.loadStats(); } else { // 仅刷新列表和统计,不跳转页面 await Promise.all([ this.loadKnowledgeList(this.currentPage), this.loadStats() ]); } } else { alert(`${isVerify ? '验证' : '取消验证'}失败: ${result.message}`); // 恢复按钮状态 if (actionBtn) { actionBtn.disabled = false; actionBtn.innerHTML = originalBtnHtml; } } } catch (error) { console.error('操作出错:', error); // 恢复按钮状态 if (trElement) { const actionBtn = trElement.querySelector('.btn-verify, .btn-unverify'); if (actionBtn) { actionBtn.disabled = false; // 简单恢复,无法精确还原之前的图标 actionBtn.innerHTML = isVerify ? '' : ''; } } } } updateRowStatus(tr, id, isVerified) { // 1. 更新问题列的状态图标 const questionCell = tr.cells[2]; // 第3列是问题 const questionDiv = questionCell.querySelector('div'); // 移除旧的徽章 const oldBadge = questionDiv.querySelector('.text-success'); if (oldBadge) oldBadge.remove(); // 如果是验证通过,添加徽章 if (isVerified) { const statusBadge = document.createElement('span'); statusBadge.className = 'text-success ms-2'; statusBadge.title = '已验证'; statusBadge.innerHTML = ''; questionDiv.appendChild(statusBadge); } // 2. 更新操作按钮 const actionCell = tr.cells[6]; // 第7列是操作 const actionBtn = actionCell.querySelector('.btn-verify, .btn-unverify'); if (actionBtn) { // 创建新按钮 const newBtn = document.createElement('button'); newBtn.type = 'button'; newBtn.className = `btn btn-sm btn-icon btn-outline-${isVerified ? 'warning' : 'success'} ${isVerified ? 'btn-unverify' : 'btn-verify'}`; newBtn.dataset.id = id; newBtn.title = isVerified ? '取消验证' : '验证通过'; newBtn.innerHTML = ``; // 重新绑定事件 newBtn.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); await this.toggleVerify(id, !isVerified, tr); }); // 替换旧按钮 actionBtn.replaceWith(newBtn); } } async deleteKnowledge(id) { if (!confirm('确定要删除这条知识吗?')) { return; } try { const response = await fetch(`/api/knowledge/delete/${id}`, { method: 'DELETE' }); const result = await response.json(); if (response.ok && result.success) { // 刷新列表和统计(保持当前页) await Promise.all([ this.loadKnowledgeList(this.currentPage), this.loadStats() ]); } else { alert(`删除失败: ${result.message || '未知错误'}`); } } catch (error) { console.error('删除出错:', error); alert('删除出错,请查看控制台'); } } renderPagination(pagination) { const { page, pages } = pagination; const container = this.container.querySelector('#pagination-container'); let html = ''; container.innerHTML = html; // 绑定点击事件 container.querySelectorAll('.page-link').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const newPage = parseInt(e.target.dataset.page); if (newPage && newPage !== page && newPage >= 1 && newPage <= pages) { this.loadKnowledgeList(newPage); } }); }); } }