first commit
This commit is contained in:
@@ -19,6 +19,7 @@ registerPage('knowledge-base', function(container) {
|
||||
let sortField = 'updatedAt';
|
||||
let sortOrder = 'desc';
|
||||
let isLoading = true;
|
||||
let hasStartedInitialLoad = false;
|
||||
let kbData = [];
|
||||
|
||||
// ==================== API预留 - 后端接口定义 ====================
|
||||
@@ -71,8 +72,37 @@ registerPage('knowledge-base', function(container) {
|
||||
{ id: 'kb-008', name: '音乐教育资源库', description: '儿童音乐教育资源', docs: 560, size: '65MB', type: 'video_search', typeName: '音视频搜索', status: 'uploading', createdAt: '2026-05-12 10:00:00', updatedAt: '2026-05-12 18:00:00', tags: ['音乐', '教育'] },
|
||||
];
|
||||
|
||||
// 初始化数据
|
||||
kbData = [...mockKbData];
|
||||
// 初始化数据。页面刷新后回到 mock;SPA 内新建/编辑/删除会保留在全局目录中。
|
||||
kbData = Array.isArray(window.__knowledgeBaseCatalog) && window.__knowledgeBaseCatalog.length
|
||||
? window.__knowledgeBaseCatalog.map(item => ({ ...item, tags: [...(item.tags || [])] }))
|
||||
: mockKbData.map(item => ({ ...item, tags: [...(item.tags || [])] }));
|
||||
window.__knowledgeBaseCatalog = kbData;
|
||||
|
||||
function syncKnowledgeBaseCatalog() {
|
||||
window.__knowledgeBaseCatalog = kbData;
|
||||
}
|
||||
|
||||
function getKbById(id) {
|
||||
return kbData.find(kb => kb.id === id);
|
||||
}
|
||||
|
||||
function getNowText() {
|
||||
const date = new Date();
|
||||
const pad = value => String(value).padStart(2, '0');
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
||||
}
|
||||
|
||||
function downloadTextFile(filename, content, mime = 'application/json;charset=utf-8') {
|
||||
const blob = new Blob([content], { type: mime });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
||||
}
|
||||
|
||||
// ==================== 工具函数 ====================
|
||||
|
||||
@@ -197,6 +227,13 @@ registerPage('knowledge-base', function(container) {
|
||||
if (sortField === 'name' || sortField === 'typeName') {
|
||||
return sortOrder === 'asc' ? valA.localeCompare(valB) : valB.localeCompare(valA);
|
||||
}
|
||||
if (sortField === 'createdAt' || sortField === 'updatedAt') {
|
||||
valA = Date.parse(valA || '') || 0;
|
||||
valB = Date.parse(valB || '') || 0;
|
||||
} else {
|
||||
valA = Number(valA) || 0;
|
||||
valB = Number(valB) || 0;
|
||||
}
|
||||
return sortOrder === 'asc' ? valA - valB : valB - valA;
|
||||
});
|
||||
|
||||
@@ -252,7 +289,7 @@ registerPage('knowledge-base', function(container) {
|
||||
</div>
|
||||
<div class="kb-list-actions">
|
||||
<button class="btn btn-primary" id="addKbBtn">
|
||||
<i data-lucide="plus"></i> 添加知识库
|
||||
<i data-lucide="plus"></i> 创建知识库
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -445,7 +482,7 @@ registerPage('knowledge-base', function(container) {
|
||||
</div>
|
||||
<div class="kb-empty-title">暂无知识库</div>
|
||||
<div class="kb-empty-desc">点击上方「添加知识库」开始创建您的第一个知识库</div>
|
||||
<button class="btn btn-primary" onclick="window.__showCreateKbModal()">
|
||||
<button class="btn btn-primary" onclick="navigateTo('kb-create')">
|
||||
<i data-lucide="plus" style="width:14px;height:14px"></i> 创建知识库
|
||||
</button>
|
||||
</div>
|
||||
@@ -477,7 +514,7 @@ registerPage('knowledge-base', function(container) {
|
||||
|
||||
function bindEvents() {
|
||||
// 添加知识库
|
||||
container.querySelector('#addKbBtn')?.addEventListener('click', showCreateKbModal);
|
||||
container.querySelector('#addKbBtn')?.addEventListener('click', () => navigateTo('kb-create'));
|
||||
|
||||
// 搜索
|
||||
container.querySelector('#kbSearchInput')?.addEventListener('input', debounce((e) => {
|
||||
@@ -487,11 +524,14 @@ registerPage('knowledge-base', function(container) {
|
||||
renderPage();
|
||||
}, 300));
|
||||
|
||||
// 模拟加载
|
||||
setTimeout(() => {
|
||||
isLoading = false;
|
||||
renderPage();
|
||||
}, 500);
|
||||
// 模拟加载,只在首次进入页面时触发一次,避免重渲染打断下拉菜单等交互。
|
||||
if (isLoading && !hasStartedInitialLoad) {
|
||||
hasStartedInitialLoad = true;
|
||||
setTimeout(() => {
|
||||
isLoading = false;
|
||||
renderPage();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -639,11 +679,17 @@ registerPage('knowledge-base', function(container) {
|
||||
`;
|
||||
|
||||
document.body.appendChild(modalOverlay);
|
||||
document.body.classList.add('modal-open');
|
||||
lucide.createIcons({ elements: [modalOverlay] });
|
||||
|
||||
const close = () => {
|
||||
modalOverlay.classList.remove('open');
|
||||
setTimeout(() => modalOverlay.remove(), 200);
|
||||
setTimeout(() => {
|
||||
modalOverlay.remove();
|
||||
if (!document.querySelector('.modal-overlay')) {
|
||||
document.body.classList.remove('modal-open');
|
||||
}
|
||||
}, 200);
|
||||
};
|
||||
|
||||
// 关闭事件
|
||||
@@ -818,6 +864,148 @@ registerPage('knowledge-base', function(container) {
|
||||
}
|
||||
};
|
||||
|
||||
function openEditKbModal(id) {
|
||||
const kb = getKbById(id);
|
||||
if (!kb) {
|
||||
Toast.warning('未找到该知识库');
|
||||
return;
|
||||
}
|
||||
|
||||
createModal({
|
||||
title: '编辑知识库',
|
||||
size: 'lg',
|
||||
content: `
|
||||
<div class="form-group">
|
||||
<label class="form-label">知识库名称 <span style="color:var(--danger)">*</span></label>
|
||||
<input class="form-control" id="kbEditName" maxlength="64" value="${escapeHtml(kb.name)}" placeholder="请输入知识库名称">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">知识库描述</label>
|
||||
<textarea class="form-control" id="kbEditDesc" rows="3" maxlength="500" placeholder="请输入知识库描述">${escapeHtml(kb.description || '')}</textarea>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px">
|
||||
<div class="form-group">
|
||||
<label class="form-label">知识库类型</label>
|
||||
<select class="form-control" id="kbEditType">
|
||||
<option value="doc_search" ${kb.type === 'doc_search' ? 'selected' : ''}>文档搜索</option>
|
||||
<option value="data_query" ${kb.type === 'data_query' ? 'selected' : ''}>数据查询</option>
|
||||
<option value="image_qa" ${kb.type === 'image_qa' ? 'selected' : ''}>图片问答</option>
|
||||
<option value="video_search" ${kb.type === 'video_search' ? 'selected' : ''}>音视频搜索</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">状态</label>
|
||||
<select class="form-control" id="kbEditStatus">
|
||||
<option value="ready" ${kb.status === 'ready' ? 'selected' : ''}>已就绪</option>
|
||||
<option value="processing" ${kb.status === 'processing' ? 'selected' : ''}>处理中</option>
|
||||
<option value="uploading" ${kb.status === 'uploading' ? 'selected' : ''}>上传中</option>
|
||||
<option value="error" ${kb.status === 'error' ? 'selected' : ''}>异常</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">标签</label>
|
||||
<input class="form-control" id="kbEditTags" value="${escapeHtml((kb.tags || []).join(', '))}" placeholder="多个标签用逗号分隔">
|
||||
</div>
|
||||
<div class="form-hint">
|
||||
<i data-lucide="info" style="width:12px;height:12px;vertical-align:middle"></i>
|
||||
前端 mock 会立即更新当前列表;后端接入后调用 PUT /api/v1/knowledge-bases/:id。
|
||||
</div>
|
||||
`,
|
||||
confirmText: '保存',
|
||||
onConfirm: () => {
|
||||
const name = document.getElementById('kbEditName')?.value.trim();
|
||||
if (!name) {
|
||||
Toast.warning('请输入知识库名称');
|
||||
return false;
|
||||
}
|
||||
const type = document.getElementById('kbEditType')?.value || kb.type;
|
||||
const tags = (document.getElementById('kbEditTags')?.value || '')
|
||||
.split(/[,,]/)
|
||||
.map(tag => tag.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
kb.name = name;
|
||||
kb.description = document.getElementById('kbEditDesc')?.value.trim() || '';
|
||||
kb.type = type;
|
||||
kb.typeName = getTypeTag(type).label;
|
||||
kb.status = document.getElementById('kbEditStatus')?.value || kb.status;
|
||||
kb.tags = tags;
|
||||
kb.updatedAt = getNowText();
|
||||
syncKnowledgeBaseCatalog();
|
||||
renderPage();
|
||||
Toast.success('知识库信息已更新');
|
||||
}
|
||||
});
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
function exportKnowledgeBases(ids) {
|
||||
const items = ids.map(id => getKbById(id)).filter(Boolean);
|
||||
if (!items.length) {
|
||||
Toast.warning('请先选择要导出的知识库');
|
||||
return;
|
||||
}
|
||||
|
||||
createModal({
|
||||
title: items.length > 1 ? '批量导出知识库' : '导出知识库',
|
||||
content: `
|
||||
<div class="form-group">
|
||||
<label class="form-label">导出范围</label>
|
||||
<div style="padding:10px 12px;background:var(--bg-base);border-radius:var(--radius);font-size:13px;line-height:1.6">
|
||||
${items.map(item => escapeHtml(item.name)).join('、')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">导出格式</label>
|
||||
<select class="form-control" id="kbExportFormat">
|
||||
<option value="json">JSON 配置数据</option>
|
||||
<option value="csv">CSV 列表数据</option>
|
||||
</select>
|
||||
</div>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="kbExportIncludeStats" checked>
|
||||
<span>包含文档数量、数据量、状态和标签</span>
|
||||
</label>
|
||||
<div class="form-hint">
|
||||
浏览器会下载到默认下载目录;后端接入后替换为 GET /api/v1/knowledge-bases/:id/export。
|
||||
</div>
|
||||
`,
|
||||
confirmText: '确认导出',
|
||||
onConfirm: () => {
|
||||
const format = document.getElementById('kbExportFormat')?.value || 'json';
|
||||
const includeStats = document.getElementById('kbExportIncludeStats')?.checked !== false;
|
||||
const exportRows = items.map(item => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
type: item.type,
|
||||
typeName: item.typeName,
|
||||
...(includeStats ? {
|
||||
docs: item.docs,
|
||||
size: item.size,
|
||||
status: item.status,
|
||||
tags: item.tags,
|
||||
} : {}),
|
||||
createdAt: item.createdAt,
|
||||
updatedAt: item.updatedAt,
|
||||
}));
|
||||
const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
||||
if (format === 'csv') {
|
||||
const headers = Object.keys(exportRows[0]);
|
||||
const csv = [
|
||||
headers.join(','),
|
||||
...exportRows.map(row => headers.map(key => `"${String(Array.isArray(row[key]) ? row[key].join('|') : row[key] ?? '').replace(/"/g, '""')}"`).join(',')),
|
||||
].join('\n');
|
||||
downloadTextFile(`knowledge-bases-${stamp}.csv`, csv, 'text/csv;charset=utf-8');
|
||||
} else {
|
||||
downloadTextFile(`knowledge-bases-${stamp}.json`, JSON.stringify(exportRows, null, 2));
|
||||
}
|
||||
Toast.success(`已导出 ${items.length} 个知识库`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 操作
|
||||
window.__kbDetail = function(id) {
|
||||
navigateTo('kb-detail?id=' + id);
|
||||
@@ -828,7 +1016,7 @@ registerPage('knowledge-base', function(container) {
|
||||
};
|
||||
|
||||
window.__kbEdit = function(id) {
|
||||
Toast.info('编辑知识库:' + id);
|
||||
openEditKbModal(id);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -855,6 +1043,8 @@ registerPage('knowledge-base', function(container) {
|
||||
// TODO: 调用 API DELETE /api/v1/knowledge-bases/:id
|
||||
const idx = kbData.findIndex(k => k.id === id);
|
||||
if (idx !== -1) kbData.splice(idx, 1);
|
||||
selectedRows.delete(id);
|
||||
syncKnowledgeBaseCatalog();
|
||||
renderPage();
|
||||
Toast.success('知识库已删除');
|
||||
}
|
||||
@@ -879,6 +1069,12 @@ registerPage('knowledge-base', function(container) {
|
||||
confirmText: '重建',
|
||||
onConfirm: () => {
|
||||
// TODO: 调用 API POST /api/v1/knowledge-bases/:id/rebuild
|
||||
if (kb) {
|
||||
kb.status = 'processing';
|
||||
kb.updatedAt = getNowText();
|
||||
syncKnowledgeBaseCatalog();
|
||||
renderPage();
|
||||
}
|
||||
Toast.success('索引重建任务已启动');
|
||||
}
|
||||
});
|
||||
@@ -889,8 +1085,7 @@ registerPage('knowledge-base', function(container) {
|
||||
* API: GET /api/v1/knowledge-bases/:id/export
|
||||
*/
|
||||
window.__kbExport = function(id) {
|
||||
// TODO: 调用 API GET /api/v1/knowledge-bases/:id/export
|
||||
Toast.info('正在导出知识库...');
|
||||
exportKnowledgeBases([id]);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -898,8 +1093,7 @@ registerPage('knowledge-base', function(container) {
|
||||
* API: GET /api/v1/knowledge-bases/batch/export
|
||||
*/
|
||||
window.__kbBatchExport = function() {
|
||||
// TODO: 调用 API GET /api/v1/knowledge-bases/batch/export
|
||||
Toast.info(`正在导出 ${selectedRows.size} 个知识库...`);
|
||||
exportKnowledgeBases(Array.from(selectedRows));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -907,6 +1101,11 @@ registerPage('knowledge-base', function(container) {
|
||||
* API: DELETE /api/v1/knowledge-bases/batch
|
||||
*/
|
||||
window.__kbBatchDelete = function() {
|
||||
if (selectedRows.size === 0) {
|
||||
Toast.warning('请先选择要删除的知识库');
|
||||
return;
|
||||
}
|
||||
const deleteCount = selectedRows.size;
|
||||
createModal({
|
||||
title: '批量删除',
|
||||
content: `
|
||||
@@ -928,14 +1127,17 @@ registerPage('knowledge-base', function(container) {
|
||||
if (idx !== -1) kbData.splice(idx, 1);
|
||||
});
|
||||
selectedRows.clear();
|
||||
syncKnowledgeBaseCatalog();
|
||||
renderPage();
|
||||
Toast.success(`已删除 ${selectedRows.size + 1} 个知识库`);
|
||||
Toast.success(`已删除 ${deleteCount} 个知识库`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 导出弹窗函数
|
||||
window.__showCreateKbModal = showCreateKbModal;
|
||||
window.__showCreateKbModal = function() {
|
||||
navigateTo('kb-create');
|
||||
};
|
||||
|
||||
// ==================== 初始化 ====================
|
||||
renderPage();
|
||||
|
||||
Reference in New Issue
Block a user