/** * TalkingQ Platform - 创建知识库向导页面 * 包含3步流程:基础信息 -> 数据选择 -> 索引设置 * * API预留: * - POST /api/v1/knowledge-bases - 创建知识库 * - POST /api/v1/knowledge-bases/validate-name - 验证名称唯一性 * - POST /api/v1/files/upload - 文件上传 * - GET /api/v1/files/upload-status/:id - 获取上传状态 */ registerPage('kb-create', function(container) { // ==================== 状态管理 ==================== let currentStep = 1; const totalSteps = 3; let hasUnsavedChanges = false; const DRAFT_KEY = 'kb_create_draft'; // ==================== API预留 - 后端接口定义 ==================== /** * 创建知识库 * POST /api/v1/knowledge-bases * Body: { name, description, type, sourceType, sourceConfig, indexConfig } * Response: { id: string, name: string, status: string } */ const API_CREATE = '/api/v1/knowledge-bases'; /** * 验证名称唯一性 * POST /api/v1/knowledge-bases/validate-name * Body: { name: string } * Response: { valid: boolean, message?: string } */ const API_VALIDATE_NAME = '/api/v1/knowledge-bases/validate-name'; /** * 文件上传 * POST /api/v1/files/upload * FormData: { file, category, knowledgeBaseId? } * Response: { fileId: string, fileName: string, status: string } */ const API_UPLOAD = '/api/v1/files/upload'; /** * 获取上传/解析状态 * GET /api/v1/files/status/:fileId * Response: { status: 'uploading'|'processing'|'ready'|'error', progress?: number, error?: string } */ const API_FILE_STATUS = '/api/v1/files/status/:fileId'; /** * 获取类目列表 * GET /api/v1/categories * Response: { list: [{ id, name, fileCount }] } */ const API_GET_CATEGORIES = '/api/v1/categories'; // ==================== 表单数据 ==================== const formData = { // 步骤1: 基础信息 name: '', description: '', type: '', // 步骤2: 数据选择 sourceType: 'upload', // upload | category | files category: '', uploadedFiles: [], autoSync: false, parseMethod: 'inherit', // inherit | default | custom // 自定义解析配置 docParseMethods: ['electronic'], imageParseMethods: ['smart'], videoParseMethods: ['media'], enableVideoParse: false, // 步骤3: 索引设置 sliceMethod: 'smart', // smart | length | page | title | regex | symbol maxChunkLength: 2000, overlapLength: 200, titleLevel: 3, regexPattern: '', symbolChars: ',', enableMetadataExtract: true, enableExcelHeader: false, enableMultiTurn: false, enableOCR: false, enableAutoKeywords: false, enableAutoTags: false, vectorModel: 'text-embedding-v2', rerankModel: 'bge-reranker-v2.5', rerankMode: 'qa', // qa | similarity | custom vectorTopK: 60, keywordTopK: 60, similarityThreshold: 0.5, maxRecallCount: 10, vectorStorage: 'platform' }; // ==================== 知识库类型定义 ==================== const kbTypes = [ { id: 'doc_search', icon: 'file-text', title: '文档搜索', desc: '构建文档、文件、图片、Excel混合型索引知识库', tooltip: '构建文档、文件、图片、Excel混合型索引知识库,构建常规混合搜索知识库。支持PDF、Word、Excel、图片等多种格式文档。' }, { id: 'data_query', icon: 'table', title: '数据查询', desc: '基于NL2SQL的表格数据查询', tooltip: '构建仅以数据表结构查询的数据索引体系,基于NL2SQL思路,实现表头、列头等结构化数据查询能力。适合结构化数据问答场景。' }, { id: 'image_qa', icon: 'image', title: '图片问答', desc: '基于多模态Embedding的图片检索', tooltip: '构建仅以图片索引为主的知识库,基于多模态Embedding能力,实现图片名称与图片内容搜索。适合图片素材检索场景。' }, { id: 'video_search', icon: 'video', title: '音视频搜索', desc: '音视频内容智能理解', tooltip: '构建音视频搜索知识库,基于高精度解析与多模态Embedding检索技术,对音视频内容进行智能理解与提炼。支持字幕提取、内容摘要等。' } ]; // ==================== 切片方式 ==================== const sliceMethods = [ { id: 'smart', label: '智能切分', desc: '根据语义自动识别段落边界' }, { id: 'length', label: '按长度切分', desc: '固定长度切分,支持重叠' }, { id: 'page', label: '按页切分', desc: '按页分割文档' }, { id: 'title', label: '按标题切分', desc: '按标题层级结构切分' }, { id: 'regex', label: '按正则切分', desc: '自定义正则表达式切分' }, { id: 'symbol', label: '按符号切分', desc: '按标点符号切分' } ]; // ==================== 符号选项 ==================== const symbolOptions = [ { value: ',', label: '中文逗号 ,' }, { value: '。', label: '中文句号 。' }, { value: ',', label: '中文逗号(英文)' }, { value: '.', label: '英文句号 .' }, { value: '、', label: '顿号 、' }, { value: '?', label: '中文问号 ?' }, { value: '!', label: '中文感叹号 !' }, { value: '?', label: '英文问号 ?' }, { value: '!', label: '英文感叹号 !' }, { value: '\n', label: '换行符' }, { value: '\n\n', label: '双换行符' } ]; // ==================== 类目数据 ==================== const categories = [ { id: 'cat-001', name: '默认类目', fileCount: 128 }, { id: 'cat-002', name: '教学资料', fileCount: 56 }, { id: 'cat-003', name: '产品文档', fileCount: 34 }, { id: 'cat-004', name: '用户指南', fileCount: 89 }, ]; // ==================== 工具函数 ==================== function escapeHtml(str) { if (!str) return ''; return str.replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function getFormatsByType() { switch (formData.type) { case 'image_qa': return '.png,.jpg,.jpeg,.bmp,.gif'; case 'video_search': return '.mp4,.avi,.mov,.mkv,.wmv,.mp3,.wav,.m4a'; case 'data_query': return '.xlsx,.xls,.csv'; default: return '.pdf,.doc,.docx,.txt,.md,.ppt,.pptx,.xlsx,.xls,.png,.jpg,.jpeg,.gif'; } } function getFileIcon(filename) { const ext = filename?.split('.').pop()?.toLowerCase() || ''; const icons = { pdf: '📄', doc: '📄', docx: '📄', txt: '📝', md: '📋', xlsx: '📊', xls: '📊', csv: '📊', png: '🖼', jpg: '🖼', jpeg: '🖼', gif: '🖼', bmp: '🖼', mp4: '🎬', avi: '🎬', mov: '🎬', mkv: '🎬', wmv: '🎬', mp3: '🎵', wav: '🎵', m4a: '🎵' }; return icons[ext] || '📁'; } function getFileStatusText(status) { const map = { pending: '待上传', uploading: '上传中', processing: '解析中', ready: '解析完成', error: '解析失败' }; return map[status] || status; } // ==================== 草稿管理 ==================== function loadDraft() { try { const draft = localStorage.getItem(DRAFT_KEY); if (draft) { const saved = JSON.parse(draft); Object.assign(formData, saved.formData || {}); currentStep = saved.currentStep || 1; return true; } } catch (e) { console.error('Failed to load draft:', e); } return false; } function saveDraft() { try { localStorage.setItem(DRAFT_KEY, JSON.stringify({ formData, currentStep, savedAt: new Date().toISOString() })); Toast.success('草稿已保存'); } catch (e) { Toast.warning('草稿保存失败'); } } function clearDraft() { localStorage.removeItem(DRAFT_KEY); } // ==================== 验证函数 ==================== function validateStep1() { const errors = []; if (!formData.name.trim()) { errors.push('请输入知识库名称'); } else if (formData.name.length < 2 || formData.name.length > 64) { errors.push('知识库名称长度需在2-64字符之间'); } if (!formData.type) { errors.push('请选择知识库类型'); } return errors; } function validateStep2() { const errors = []; if (formData.sourceType === 'upload' && formData.uploadedFiles.length === 0) { errors.push('请上传至少一个文件'); } if (formData.sourceType === 'upload') { const failedFiles = formData.uploadedFiles.filter(f => f.status === 'error'); if (failedFiles.length > 0) { errors.push(`有 ${failedFiles.length} 个文件上传失败,请处理后重试`); } } return errors; } function validateStep3() { return []; // 步骤3一般不需要强制验证 } // ==================== 渲染函数 ==================== /** * 渲染向导步骤 */ function renderWizardSteps() { const steps = ['基础信息', '数据选择', '索引设置']; return `
${[1, 2, 3].map((step, idx) => `
${step < currentStep ? '' : step}
${steps[step - 1]}
${idx < 2 ? `
` : ''} `).join('')}
`; } /** * 渲染步骤1:基础信息 */ function renderStep1() { return `

基础知识信息 ?

${formData.name.length}/64
${formData.description.length}/500
${kbTypes.map(type => `
${type.title}
${type.desc}
`).join('')}
`; } /** * 渲染步骤2:数据选择 */ function renderStep2() { const formats = getFormatsByType(); const maxFiles = 50; const maxSize = formData.type === 'image_qa' || formData.type === 'video_search' ? '20MB' : '50MB'; return `

选择数据源

默认文件连接器

从连接器内选择已解析文档构建知识索引,也支持直接上传文件解析并构建索引,上传文件将自动存储至连接器。

文件上传
选择类目
选择文件
${renderSourceContent(formats, maxFiles, maxSize)}
${formData.sourceType !== 'upload' ? `
开启自动同步知识索引 ?
开启后将自动同步类目下所有文件,在类目文档发生变化后,自动重建及更新索引。
` : ''}
`; } /** * 渲染数据源内容 */ function renderSourceContent(formats, maxFiles, maxSize) { switch (formData.sourceType) { case 'upload': return renderUploadContent(formats, maxFiles, maxSize); case 'category': return renderCategoryContent(); case 'files': return renderFilesContent(); default: return renderUploadContent(formats, maxFiles, maxSize); } } /** * 渲染文件上传内容 */ function renderUploadContent(formats, maxFiles, maxSize) { return `
配置类目:
📁
点击或拖拽文件到此处上传
支持格式: ${formats}
单文件最大: ${maxSize} | 最多: ${maxFiles}个文件
${formData.uploadedFiles.length > 0 ? `
文件名 大小 状态 操作
${formData.uploadedFiles.map((file, idx) => `
${getFileIcon(file.name)}
${escapeHtml(file.name)}
${file.size}
${file.status === 'uploading' ? `` : ''} ${file.status === 'processing' ? `` : ''} ${getFileStatusText(file.status)}
${file.status === 'error' ? ` ` : ''}
`).join('')}
已选择 ${formData.uploadedFiles.length}/${maxFiles} 个文件
` : ''}
解析方式 ?
继承目录配置
默认解析
自定义解析
${formData.parseMethod === 'custom' ? `
${renderParseMethodDetail()}
` : ''}
`; } /** * 渲染自定义解析方式 */ function renderParseMethodDetail() { const type = formData.type; let html = '
自定义解析方式:
'; if (['doc_search', 'data_query', ''].includes(type)) { html += `
文档类型
电子文档解析
Markdown解析
HTML解析
PPT文档智能解析
大模型文档解析
`; } if (type === 'image_qa' || !type) { html += `
图片类型
文档智能解析
大模型文档解析
Qwen VL解析
`; } if (type === 'video_search' || !type) { html += `
音视频类型
音视频解析
`; } return html; } /** * 渲染类目选择内容 */ function renderCategoryContent() { return `
选择类目:

选择类目后,该类目下所有文件将被导入并构建索引。

当前类目包含 128 个文件,预计解析时间约 5-10 分钟。

`; } /** * 渲染文件选择内容 */ function renderFilesContent() { const appFiles = [ { name: '产品说明书.pdf', size: '3.2MB', format: 'PDF' }, { name: '用户指南.docx', size: '1.5MB', format: 'DOCX' }, { name: '技术规格.xlsx', size: '856KB', format: 'XLSX' }, { name: '培训视频.mp4', size: '128MB', format: 'MP4' }, { name: '常见问题.md', size: '245KB', format: 'MD' }, ]; return `
应用数据类目:
数据中心文件
${appFiles.map((f, idx) => `
${getFileIcon(f.name)}
${f.name}
${f.format} · ${f.size}
`).join('')}
已选择 0/50

从左侧选择文件

最多支持50个文件

`; } /** * 渲染步骤3:索引设置 */ function renderStep3() { return `

索引设置 ?

${renderSliceConfig()}
高级参数配置
Metadata抽取 ?
Excel表头拼装
多轮对话改写
OCR增强
自动关键词提取
自动标签提取
检索参数配置
${formData.vectorTopK} (10-100)
${formData.keywordTopK} (10-100)
${formData.similarityThreshold.toFixed(2)} (0.01-1)
${formData.maxRecallCount} (1-20)
系统会自动保存草稿,您可以随时中断并继续
`; } /** * 渲染切片配置 */ function renderSliceConfig() { switch (formData.sliceMethod) { case 'smart': return `
最大分段长度 ?
字符 (10-6000)
`; case 'length': return `
分段预估长度 ?
字符 (10-6000)
分段重叠长度 ?
字符 (0-1024)
`; case 'page': return `
最大分段长度
字符 (10-6000)
`; case 'title': return `
最大分段长度
字符 (10-6000)
标题级数
级 (1-5)
`; case 'regex': return `
正则表达式
最大分段长度
字符 (10-6000)
`; case 'symbol': return `
符号选择器
最大分段长度
字符 (10-6000)
`; default: return ''; } } // ==================== 渲染页面 ==================== function renderPage() { container.innerHTML = `
${renderWizardSteps()}
${currentStep === 1 ? renderStep1() : currentStep === 2 ? renderStep2() : renderStep3()}
`; lucide.createIcons({ elements: [container] }); } // ==================== 步骤控制 ==================== function collectStepData() { if (currentStep === 1) { formData.name = document.getElementById('kbName')?.value.trim() || ''; formData.description = document.getElementById('kbDesc')?.value.trim() || ''; } } window.__prevStep = function() { if (currentStep > 1) { collectStepData(); currentStep--; renderPage(); } }; window.__nextStep = function() { collectStepData(); let errors = []; if (currentStep === 1) { errors = validateStep1(); } else if (currentStep === 2) { errors = validateStep2(); } if (errors.length > 0) { Toast.error(errors[0]); return; } if (currentStep < totalSteps) { currentStep++; renderPage(); } }; window.__finishWizard = function() { collectStepData(); // TODO: 调用 API POST /api/v1/knowledge-bases 创建知识库 console.log('创建知识库数据:', formData); const typeMap = { doc_search: '文档搜索', data_query: '数据查询', image_qa: '图片问答', video_search: '音视频搜索', }; const createdAt = new Date(); const pad = value => String(value).padStart(2, '0'); const createdText = `${createdAt.getFullYear()}-${pad(createdAt.getMonth() + 1)}-${pad(createdAt.getDate())} ${pad(createdAt.getHours())}:${pad(createdAt.getMinutes())}:${pad(createdAt.getSeconds())}`; const uploadedCount = Array.isArray(formData.uploadedFiles) ? formData.uploadedFiles.length : 0; const newKb = { id: `kb-${Date.now()}`, name: formData.name || '未命名知识库', description: formData.description || '暂无描述', docs: uploadedCount * 24, size: uploadedCount ? `${Math.max(1, uploadedCount * 8)}MB` : '0MB', type: formData.type || 'doc_search', typeName: typeMap[formData.type] || '文档搜索', status: uploadedCount ? 'processing' : 'ready', createdAt: createdText, updatedAt: createdText, tags: [], }; const catalog = Array.isArray(window.__knowledgeBaseCatalog) ? window.__knowledgeBaseCatalog : []; catalog.unshift(newKb); window.__knowledgeBaseCatalog = catalog; clearDraft(); Toast.success('知识库创建成功!'); setTimeout(() => { navigateTo('knowledge-base'); }, 1000); }; // ==================== 全局函数 ==================== window.__updateCharCount = function(input, max) { const counter = input.closest('.form-group')?.querySelector('.char-counter'); if (counter) { counter.textContent = `${input.value.length}/${max}`; counter.classList.toggle('warning', input.value.length >= max * 0.9); } hasUnsavedChanges = true; }; window.__selectKbType = function(typeId) { formData.type = typeId; hasUnsavedChanges = true; document.querySelectorAll('.kb-type-card').forEach(card => { card.classList.toggle('selected', card.dataset.type === typeId); }); }; window.__showKbTypeHelp = function() { createModal({ title: '知识库类型说明', content: `
${kbTypes.map(type => `
${type.title}
${type.tooltip}
`).join('')}
`, confirmText: '', cancelText: '关闭' }); lucide.createIcons(); }; window.__selectSourceType = function(type) { formData.sourceType = type; hasUnsavedChanges = true; const formats = getFormatsByType(); const maxFiles = 50; const maxSize = formData.type === 'image_qa' || formData.type === 'video_search' ? '20MB' : '50MB'; document.getElementById('sourceContent').innerHTML = renderSourceContent(formats, maxFiles, maxSize); lucide.createIcons(); }; window.__handleDragOver = function(e) { e.preventDefault(); e.currentTarget.classList.add('dragover'); }; window.__handleDragLeave = function(e) { e.currentTarget.classList.remove('dragover'); }; window.__handleDrop = function(e) { e.preventDefault(); e.currentTarget.classList.remove('dragover'); window.__handleFileSelect(e.dataTransfer.files); }; window.__handleFileSelect = function(files) { if (!files || !files.length) return; const maxFiles = 50; const maxSize = formData.type === 'image_qa' || formData.type === 'video_search' ? 20 * 1024 * 1024 : 50 * 1024 * 1024; Array.from(files).forEach(file => { if (formData.uploadedFiles.length >= maxFiles) { Toast.warning(`最多只能上传${maxFiles}个文件`); return; } if (file.size > maxSize) { Toast.warning(`文件 ${file.name} 超过大小限制 (${maxSize / 1024 / 1024}MB)`); return; } const fileObj = { name: file.name, size: (file.size / 1024 / 1024).toFixed(2) + 'MB', status: 'uploading', file: file }; formData.uploadedFiles.push(fileObj); hasUnsavedChanges = true; // TODO: 调用 API POST /api/v1/files/upload 上传文件 // 模拟上传进度 setTimeout(() => { fileObj.status = 'processing'; renderPage(); }, 500); setTimeout(() => { // 模拟90%成功率 fileObj.status = Math.random() > 0.1 ? 'ready' : 'error'; renderPage(); }, 2000); }); renderPage(); }; window.__removeFile = function(index) { formData.uploadedFiles.splice(index, 1); hasUnsavedChanges = true; renderPage(); }; window.__retryUpload = function(index) { if (formData.uploadedFiles[index]) { const fileObj = formData.uploadedFiles[index]; fileObj.status = 'uploading'; renderPage(); setTimeout(() => { fileObj.status = 'processing'; renderPage(); }, 500); setTimeout(() => { fileObj.status = 'ready'; renderPage(); Toast.success(`${fileObj.name} 已重新上传并解析完成`); }, 1500); } }; window.__clearAllFiles = function() { formData.uploadedFiles = []; hasUnsavedChanges = true; renderPage(); }; window.__updateCategory = function(value) { formData.category = value; hasUnsavedChanges = true; }; window.__selectParseMethod = function(method) { formData.parseMethod = method; hasUnsavedChanges = true; document.querySelectorAll('.parse-method-option').forEach(opt => { const isSelected = opt.textContent.includes( method === 'inherit' ? '继承' : method === 'default' ? '默认' : '自定义' ); opt.classList.toggle('selected', isSelected); }); const detail = document.getElementById('parseMethodDetail'); if (detail) { detail.innerHTML = renderParseMethodDetail(); } }; window.__toggleParseMethod = function(type, method) { const key = type === 'doc' ? 'docParseMethods' : type === 'image' ? 'imageParseMethods' : 'videoParseMethods'; const arr = formData[key]; const idx = arr.indexOf(method); if (idx > -1) { arr.splice(idx, 1); } else { arr.push(method); } hasUnsavedChanges = true; const detail = document.getElementById('parseMethodDetail'); if (detail) { detail.innerHTML = renderParseMethodDetail(); lucide.createIcons({ elements: [detail] }); } }; window.__toggleAutoSync = function(checked) { formData.autoSync = checked; hasUnsavedChanges = true; Toast.info(checked ? '已开启自动同步' : '已关闭自动同步'); }; window.__updateSliceConfig = function() { formData.sliceMethod = document.getElementById('sliceMethod').value; hasUnsavedChanges = true; document.getElementById('sliceConfigPanel').innerHTML = renderSliceConfig(); lucide.createIcons(); }; window.__updateRangeValue = function(input, field, multiplier = 1) { const value = parseInt(input.value); const displayEl = document.getElementById(field + 'Value'); if (displayEl) { displayEl.textContent = multiplier > 1 ? (value / multiplier).toFixed(2) : value; } formData[field] = multiplier > 1 ? value / multiplier : value; hasUnsavedChanges = true; }; window.__resetRetrievalParams = function() { formData.vectorTopK = 60; formData.keywordTopK = 60; formData.similarityThreshold = 0.5; formData.maxRecallCount = 10; hasUnsavedChanges = true; renderPage(); }; window.__toggleEnhance = function(key) { formData['enable' + key.charAt(0).toUpperCase() + key.slice(1)] = !formData['enable' + key.charAt(0).toUpperCase() + key.slice(1)]; hasUnsavedChanges = true; const advancedBody = document.querySelector('.advanced-config-body'); if (advancedBody) { const options = advancedBody.querySelectorAll('.parse-method-option'); options.forEach(opt => { if (opt.textContent.includes('Metadata抽取')) { opt.classList.toggle('selected', formData.enableMetadataExtract); } else if (opt.textContent.includes('Excel表头')) { opt.classList.toggle('selected', formData.enableExcelHeader); } else if (opt.textContent.includes('多轮对话')) { opt.classList.toggle('selected', formData.enableMultiTurn); } else if (opt.textContent.includes('OCR')) { opt.classList.toggle('selected', formData.enableOCR); } else if (opt.textContent.includes('关键词')) { opt.classList.toggle('selected', formData.enableAutoKeywords); } else if (opt.textContent.includes('标签')) { opt.classList.toggle('selected', formData.enableAutoTags); } }); } }; window.__showTooltip = function(id) { const tooltip = document.getElementById(id); if (tooltip) { tooltip.style.display = tooltip.style.display === 'none' ? 'block' : 'none'; } }; window.__toggleFileSelect = function(el, filename) { const checkbox = el.querySelector('input[type="checkbox"]'); checkbox.checked = !checkbox.checked; el.classList.toggle('selected', checkbox.checked); Toast.info(checkbox.checked ? `已选择: ${filename}` : `已取消: ${filename}`); }; window.__loadCategoryFiles = function(category) { // TODO: 调用 API GET /api/v1/categories/:id/files createModal({ title: `${category} 文件列表`, size: 'lg', content: `
${['儿童科学百科.pdf', '科学小实验.docx', '物理入门.xlsx'].map((name, index) => `
${name}
mock-file-${index + 1} · ${(index + 1) * 2}.4MB
`).join('')}
`, confirmText: '', cancelText: '关闭' }); }; window.__showCategoryDrawer = function() { const renderCategoryRows = () => { const list = document.getElementById('categoryManageList'); if (!list) return; list.innerHTML = categories.map(category => `
${category.name}
${category.fileCount} 个文件 · ${category.id}
${category.id === 'cat-001' ? '' : ` `}
`).join(''); lucide.createIcons({ elements: list }); }; createModal({ title: '类目管理', size: 'lg', content: `
`, confirmText: '', cancelText: '关闭' }); window.__addCategoryFromModal = function() { const input = document.getElementById('newCategoryName'); const name = input?.value.trim(); if (!name) { Toast.warning('请输入类目名称'); return; } categories.push({ id: `cat-${Date.now()}`, name, fileCount: 0, }); input.value = ''; renderCategoryRows(); Toast.success('类目已添加'); }; window.__removeCategoryFromModal = function(id) { const index = categories.findIndex(category => category.id === id); if (index !== -1) { categories.splice(index, 1); renderCategoryRows(); Toast.success('类目已删除'); } }; window.__chooseCategoryFromModal = function(name) { formData.category = name; hasUnsavedChanges = true; const select = document.getElementById('categorySelect'); if (select && Array.from(select.options).some(option => option.value === name)) { select.value = name; } Toast.success(`已选择类目:${name}`); }; setTimeout(renderCategoryRows, 50); }; window.__saveDraft = saveDraft; window.__confirmLeave = function() { if (hasUnsavedChanges) { createModal({ title: '确认离开', content: `

确定要离开吗?

您有未保存的更改,离开后将丢失。

`, confirmText: '离开', confirmClass: 'btn-danger', onConfirm: () => { navigateTo('knowledge-base'); } }); } else { navigateTo('knowledge-base'); } }; // ==================== 初始化 ==================== loadDraft(); renderPage(); // 离开页面确认 window.addEventListener('beforeunload', function(e) { if (hasUnsavedChanges) { e.preventDefault(); e.returnValue = ''; } }); });