王为溪 3 months ago
parent b9f2da1a1c
commit 5238ebbf4d

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 KiB

@ -426,53 +426,52 @@
<!-- 主内容区 -->
<main class="flex-1 overflow-y-auto bg-gray-50">
<!-- 笔记管理视图(默认显示) -->
<div id="notes-view" class="view-container flex flex-col h-full"> <!-- 用flex-col确保内部模块垂直排列 -->
<!-- 工具栏 -->
<div class="bg-white p-4 border-b border-gray-200 flex items-center justify-between">
<h1 class="text-xl font-semibold">笔记管理</h1>
<div class="flex items-center gap-3">
<div class="relative mr-3">
<select id="categoryFilter" class="input-field pl-3 pr-8 w-48 border border-gray-300 rounded-md py-2 focus:outline-none focus:ring-2 focus:ring-primary/50 appearance-none bg-white">
<!-- 选项将通过JS动态生成 -->
</select>
<i class="fa fa-chevron-down absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none"></i>
</div>
<!-- 笔记管理视图(默认显示) -->
<div id="notes-view" class="view-container flex flex-col h-full">
<!-- 工具栏 -->
<div class="bg-white p-4 border-b border-gray-200 flex items-center justify-between">
<h1 class="text-xl font-semibold">笔记管理</h1>
<div class="flex items-center gap-3">
<div class="relative mr-3">
<select id="categoryFilter" class="input-field pl-3 pr-8 w-48 border border-gray-300 rounded-md py-2 focus:outline-none focus:ring-2 focus:ring-primary/50 appearance-none bg-white">
<!-- 选项将通过JS动态生成 -->
</select>
<i class="fa fa-chevron-down absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none"></i>
</div>
<div class="relative">
<input
type="text"
id="noteSearch"
placeholder="搜索笔记..."
class="input-field pl-9 w-64 border border-gray-300 rounded-md py-2 px-3 focus:outline-none focus:ring-2 focus:ring-primary/50"
>
</div>
<button class="btn-action btn-primary bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition-colors" id="newNoteBtn">
<i class="fa fa-plus mr-1"></i> 新建笔记
</button>
<button class="btn-action btn-secondary border border-gray-300 px-4 py-2 rounded-md hover:bg-gray-100 transition-colors" id="importNoteBtn">
<i class="fa fa-upload mr-1"></i> 导入
</button>
</div>
</div>
<div class="relative">
<input
type="text"
id="noteSearch"
placeholder="搜索笔记..."
class="input-field pl-9 w-64 border border-gray-300 rounded-md py-2 px-3 focus:outline-none focus:ring-2 focus:ring-primary/50"
>
</div>
<button class="btn-action btn-primary bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition-colors" id="newNoteBtn">
<i class="fa fa-plus mr-1"></i> 新建笔记
</button>
<button class="btn-action btn-secondary border border-gray-300 px-4 py-2 rounded-md hover:bg-gray-100 transition-colors" id="importNoteBtn">
<i class="fa fa-upload mr-1"></i> 导入
</button>
</div>
</div>
<!-- 已上传文件列表 -->
<div class="p-6 border-b border-gray-200 bg-white">
<h2 class="text-lg font-semibold mb-4">已上传文件</h2>
<div id="documents-container" class="border rounded-lg p-4 bg-gray-50">
<!-- 文件列表将通过JS动态生成 -->
</div>
</div>
<!-- 笔记列表区域 -->
<div class="p-6 flex-1"> <!-- flex-1占满剩余空间避免内容过短 -->
<h2 class="text-lg font-semibold mb-4">我的笔记</h2> <!-- 补充标题区分模块 -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-11" id="notes-container">
</div>
</div>
<!-- 笔记列表区域(删除了"我的笔记"标题) -->
<div class="p-6 flex-1">
<!-- 删除了原来的 <h2 class="text-lg font-semibold mb-4">我的笔记</h2> -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" id="notes-container">
<!-- 笔记卡片将通过JS动态生成 -->
</div>
</div>
</div>
<!-- 已上传文件列表(移到笔记列表下方,作为独立模块) -->
<div class="p-6 border-t border-gray-200 bg-white"> <!-- 用白色背景区分 -->
<h2 class="text-lg font-semibold mb-4">已上传文件</h2>
<div id="documents-container" class="border rounded-lg p-4 bg-gray-50">
</div>
</div>
</div>
<!-- 智能答疑视图(默认隐藏) -->
<div id="qa-view" class="view-container hidden flex flex-col h-full"> <!-- 用flex-col垂直排列聊天区和输入区 -->
@ -565,19 +564,21 @@
>
</div>
<div>
<label for="graphNoteIds" class="block text-sm font-medium text-gray-700 mb-1">
文档ID <span class="text-red-500">*</span>
</label>
<input
type="text"
id="graphNoteIds"
class="input-field w-full"
placeholder="多个用逗号分隔file1.txt,file2.docx"
required
>
<p class="text-xs text-gray-500 mt-1">从已上传的文档中选择ID</p>
</div>
<!-- 替换原来的文档ID输入框 -->
<div>
<label for="graphCategory" class="block text-sm font-medium text-gray-700 mb-1">
分类 <span class="text-red-500">*</span>
</label>
<select
id="graphCategory"
class="input-field w-full"
required
>
<option value="">请选择分类</option>
<!-- 动态生成分类选项 -->
</select>
<p class="text-xs text-gray-500 mt-1">选择分类后,将生成该分类下所有文件的知识图谱</p>
</div>
</div>
<div class="flex justify-end pt-1">
@ -871,15 +872,17 @@
</div>
<!-- 修改后(正确) -->
<!-- 修改导入笔记模态框中的分类输入框使用唯一ID -->
<div>
<label for="importModalCategory" class="block text-sm font-medium text-gray-700 mb-1">笔记分类</label>
<input
type="text"
id="noteCategory"
id="importModalCategory"
class="login-input"
placeholder="请输入笔记分类(例如:高等数学、计算机网络)"
required
>
<p id="importCategoryError" class="text-red-500 text-xs mt-1 hidden">请输入笔记分类</p>
</div>
<!-- 按钮 -->
@ -922,7 +925,7 @@
// DOM元素
let conversationHistory = [];
// 基础地址只保留 域名/IP + 端口
const API_BASE_URL = 'http://192.168.54.16:9621';
const API_BASE_URL = 'http://192.168.131.16:9621';
// 调用接口时,再拼接具体路径(如上传接口)
const uploadUrl = `${API_BASE_URL}/documents/upload`;
const splashScreen = document.getElementById('splashScreen');
@ -1769,14 +1772,22 @@ function closeImportModalHandler() {
}, 300);
}
// 重置导入表单
// 重置导入表单(修正版)
function resetImportForm() {
importNoteForm.reset();
if (importNoteForm) {
importNoteForm.reset();
}
importFile.value = '';
selectedFileInfo.classList.add('hidden');
fileName.textContent = '';
importedFileContent = '';
currentParsingFile = null;
// 隐藏分类错误提示
const categoryError = document.getElementById('importCategoryError');
if (categoryError) {
categoryError.classList.add('hidden');
}
}
// 文件上传区域事件
@ -1866,6 +1877,10 @@ function handleFileSelection(file) {
fileName.textContent = file.name;
selectedFileInfo.classList.remove('hidden');
fetchAllDocuments(); // 新增:上传成功后刷新文件列表
// 新增:更新知识图谱分类选择框
setTimeout(() => {
renderGraphCategoryFilter();
}, 500);
} else if (data.status === 'duplicated') {
// 处理文件重复的情况
alert(`文件已存在:${data.message}`);
@ -2118,7 +2133,7 @@ removeFile.addEventListener('click', () => {
currentParsingFile = null;
});
// 导入笔记表单提交(适配后端异步处理
// 导入笔记表单提交(修正版
importNoteForm.addEventListener('submit', (e) => {
e.preventDefault();
@ -2127,24 +2142,85 @@ importNoteForm.addEventListener('submit', (e) => {
return;
}
// 显示进度提示(后端异步处理,这里提示用户等待)
// 获取分类信息 - 使用正确的ID
const categoryInput = document.getElementById('importModalCategory');
const category = categoryInput.value.trim();
// 验证分类是否填写
if (!category) {
const errorElement = document.getElementById('importCategoryError');
if (errorElement) {
errorElement.classList.remove('hidden');
} else {
alert('请填写笔记分类!');
}
return;
}
// 隐藏错误提示
const errorElement = document.getElementById('importCategoryError');
if (errorElement) {
errorElement.classList.add('hidden');
}
// 显示进度提示
importNoteModal.classList.add('hidden');
importProgressModal.classList.remove('hidden');
importProgressBar.style.width = '50%';
importProgressText.textContent = '文件已上传,等待服务器处理中...';
// 实际项目中,这里应该轮询后端接口查询处理状态
// 这里简化为3秒后模拟处理完成
// 保存分类信息
const fileName = currentParsingFile.name;
saveFileCategoryMapping(fileName, category);
// 模拟处理完成实际项目中这里应该是真实的API调用
setTimeout(() => {
importProgressModal.classList.add('hidden');
alert('文件导入成功,已添加到笔记列表!');
alert(`文件"${fileName}"导入成功,分类为"${category}"`);
resetImportForm();
fetchNotesFromServer(); // 刷新笔记数据
renderDynamicCategoryFilter(); // 更新分类
}, 1000);
fetchAllDocuments(); // 刷新文件列表
// 更新知识图谱分类选择框
setTimeout(() => {
renderGraphCategoryFilter();
}, 500);
}, 2000);
});
// 增强的文件分类映射管理函数
function saveFileCategoryMapping(fileName, category) {
try {
let fileCategories = JSON.parse(localStorage.getItem('fileCategories')) || {};
fileCategories[fileName] = category;
localStorage.setItem('fileCategories', JSON.stringify(fileCategories));
console.log(`分类映射已保存: ${fileName} -> ${category}`);
} catch (error) {
console.error('保存分类映射失败:', error);
}
}
function getFileCategory(fileName) {
try {
const fileCategories = JSON.parse(localStorage.getItem('fileCategories')) || {};
return fileCategories[fileName] || '未分类';
} catch (error) {
console.error('获取分类失败:', error);
return '未分类';
}
}
// 获取所有分类列表
function getAllCategories() {
try {
const fileCategories = JSON.parse(localStorage.getItem('fileCategories')) || {};
const categories = new Set(Object.values(fileCategories).filter(cat => cat && cat.trim() !== ''));
return Array.from(categories).sort((a, b) => a.localeCompare(b));
} catch (error) {
console.error('获取分类列表失败:', error);
return [];
}
}
// 新增:从后端拉取最新笔记列表
function fetchNotesFromServer() {
const apiKey = '';
@ -2207,9 +2283,9 @@ function fetchAllDocuments() {
// 渲染文档列表(适配后端文档字段)
function renderDocumentsList() {
const container = document.getElementById('documents-container'); // 前端文档列表容器ID
const container = document.getElementById('documents-container');
if (!container) {
console.error('未找到文档列表容器请检查DOM中是否有 id="documents-container" 的元素');
console.error('未找到文档列表容器');
return;
}
@ -2218,58 +2294,68 @@ function renderDocumentsList() {
container.innerHTML = `
<div class="text-center py-10 text-gray-500">
<i class="fa fa-file-text-o text-3xl mb-3"></i>
<p>暂无上传的文档,请点击"上传文件"按钮开始操作</p>
<p>暂无上传的文档,请点击"导入"按钮开始上传文件</p>
</div>
`;
return;
}
// 渲染文档表格修正操作列的HTML语法
container.innerHTML = `
<div class="overflow-x-auto">
<table class="min-w-full border-collapse border border-gray-200">
<thead>
<tr class="bg-gray-50">
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">文件名</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">状态</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">上传时间</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">处理进度</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">操作</th>
</tr>
</thead>
<tbody>
${window.allDocuments.map(doc => `
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 text-sm text-gray-900 border">${doc.file_path.split('/').pop()}</td>
<td class="px-4 py-3 text-sm border">
<span class="px-2 py-1 rounded text-xs ${
doc.status === 'PROCESSED' ? 'bg-green-100 text-green-800' :
doc.status === 'PENDING' ? 'bg-yellow-100 text-yellow-800' :
doc.status === 'PROCESSING' ? 'bg-blue-100 text-blue-800' :
'bg-red-100 text-red-800'
}">${doc.status}</span>
</td>
<td class="px-4 py-3 text-sm text-gray-600 border">${new Date(doc.created_at).toLocaleString()}</td>
<td class="px-4 py-3 text-sm text-gray-600 border">${
doc.status === 'PROCESSED' ? `已完成(${doc.chunks_count || 0}个片段)` :
doc.status === 'PROCESSING' ? '处理中...' :
doc.status === 'FAILED' ? `失败:${doc.error || '未知错误'}` :
'等待处理'
}</td>
<!-- 修正操作列确保HTML标签闭合和属性正确 -->
<td class="px-4 py-3 text-sm border">
<button class="delete-document-btn text-red-500 hover:text-red-700 flex items-center gap-1 px-2 py-1 transition-colors"
data-doc-id="${doc.id}"
title="删除文件">
<i class="fa fa-trash-o"></i>
<span>删除</span>
</button>
</td>
// 获取文件分类映射
const fileCategories = JSON.parse(localStorage.getItem('fileCategories')) || {};
container.innerHTML = `
<div class="overflow-x-auto">
<table class="min-w-full border-collapse border border-gray-200">
<thead>
<tr class="bg-gray-50">
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">文件名</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">分类</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">状态</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">上传时间</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">处理进度</th>
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">操作</th>
</tr>
`).join('')}
</tbody>
</table>
</div>
`;
</thead>
<tbody>
${window.allDocuments.map(doc => {
const fileName = doc.file_path.split('/').pop();
const category = fileCategories[fileName] || '未分类';
return `
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 text-sm text-gray-900 border">${fileName}</td>
<td class="px-4 py-3 text-sm text-gray-600 border">
<span class="px-2 py-1 bg-blue-100 text-blue-800 rounded text-xs">${category}</span>
</td>
<td class="px-4 py-3 text-sm border">
<span class="px-2 py-1 rounded text-xs ${
doc.status === 'PROCESSED' ? 'bg-green-100 text-green-800' :
doc.status === 'PENDING' ? 'bg-yellow-100 text-yellow-800' :
doc.status === 'PROCESSING' ? 'bg-blue-100 text-blue-800' :
'bg-red-100 text-red-800'
}">${doc.status}</span>
</td>
<td class="px-4 py-3 text-sm text-gray-600 border">${new Date(doc.created_at).toLocaleString()}</td>
<td class="px-4 py-3 text-sm text-gray-600 border">${
doc.status === 'PROCESSED' ? `已完成(${doc.chunks_count || 0}个片段)` :
doc.status === 'PROCESSING' ? '处理中...' :
doc.status === 'FAILED' ? `失败:${doc.error || '未知错误'}` :
'等待处理'
}</td>
<td class="px-4 py-3 text-sm border">
<button class="delete-document-btn text-red-500 hover:text-red-700 flex items-center gap-1 px-2 py-1 transition-colors"
data-doc-id="${doc.id}"
title="删除文件">
<i class="fa fa-trash-o"></i>
<span>删除</span>
</button>
</td>
</tr>
`;
}).join('')}
</tbody>
</table>
</div>
`;
// 文档删除功能(使用事件委托,支持动态生成的按钮)
document.getElementById('documents-container').addEventListener('click', async function(e) {
@ -2295,7 +2381,7 @@ document.getElementById('documents-container').addEventListener('click', async f
try {
// 处理API密钥参数后端要求的query参数
const apiKey = ''; // 替换为实际API密钥若无则留空
const deleteUrl = new URL('http://192.168.54.16:9621/documents/delete_document');
const deleteUrl = new URL('http://192.168.131.16:9621/documents/delete_document');
deleteUrl.searchParams.append('api_key_header_value', apiKey);
// 调用后端删除接口
@ -2688,13 +2774,14 @@ function formatMessageContent(content) {
.replace(/\n/g, '<br>') // 换行转<br>
.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1" target="_blank" class="text-primary underline">$1</a>'); // 链接转超链接
}
// 初始化页面
// 在DOMContentLoaded和文件上传成功后调用
document.addEventListener('DOMContentLoaded', () => {
fetchNotesFromServer();
fetchAllDocuments();
document.getElementById('notes-view').classList.remove('hidden');
document.getElementById('qa-view').classList.add('hidden');
renderDynamicCategoryFilter(); // 初始化分类下拉框
renderDynamicCategoryFilter();
renderGraphCategoryFilter(); // 新增:初始化知识图谱分类选择框
const searchTerm = noteSearchInput.value.trim().toLowerCase();
filterNotesByCategoryAndSearch(searchTerm);
});
@ -2732,214 +2819,256 @@ document.addEventListener('DOMContentLoaded', function() {
// 知识图谱功能实现
document.addEventListener('DOMContentLoaded', function() {
// 视图切换逻辑(已有的视图切换代码无需修改,会自动识别新视图)
// 知识图谱表单提交处理
const graphForm = document.getElementById('knowledgeGraphForm');
if (graphForm) {
graphForm.addEventListener('submit', function(e) {
e.preventDefault();
const nodeInfo = document.getElementById('node-info');
nodeInfo.className = 'loading-message';
nodeInfo.innerHTML = '正在加载知识图谱...';
// 1. 收集表单参数
const params = {
label: document.getElementById('graphLabel').value.trim(),
max_depth: document.getElementById('graphMaxDepth').value.trim() || 3,
max_nodes: document.getElementById('graphMaxNodes').value.trim() || 100,
note_ids: document.getElementById('graphNoteIds').value.trim().split(',').map(id => id.trim()).filter(Boolean)
};
// 表单验证
if (!params.label || params.note_ids.length === 0) {
nodeInfo.className = 'error-message';
nodeInfo.innerHTML = '请填写必填参数标签和文档ID';
return;
}
const importCategoryInput = document.getElementById('importModalCategory');
if (importCategoryInput) {
importCategoryInput.addEventListener('input', function() {
const errorElement = document.getElementById('importCategoryError');
if (errorElement && this.value.trim()) {
errorElement.classList.add('hidden');
}
});
}
// 知识图谱表单提交处理
const graphForm = document.getElementById('knowledgeGraphForm');
if (graphForm) {
graphForm.addEventListener('submit', function(e) {
e.preventDefault();
const nodeInfo = document.getElementById('node-info');
nodeInfo.className = 'loading-message';
nodeInfo.innerHTML = '正在加载知识图谱...';
// 1. 收集表单参数
const selectedCategory = document.getElementById('graphCategory').value;
if (!selectedCategory) {
nodeInfo.className = 'error-message';
nodeInfo.innerHTML = '请选择分类';
return;
}
// 2. 构建请求URL
const baseUrl = `${API_BASE_URL}/graphs/note`;
const urlParams = new URLSearchParams();
// 添加参数(自动处理编码)
urlParams.append('label', params.label);
urlParams.append('max_depth', params.max_depth);
urlParams.append('max_nodes', params.max_nodes);
params.note_ids.forEach(id => urlParams.append('note_ids', id));
// 添加API密钥如果需要
const apiKey = ''; // 替换为实际API密钥
if (apiKey) {
urlParams.append('api_key_header_value', apiKey);
}
const fullUrl = `${baseUrl}?${urlParams.toString()}`;
console.log('知识图谱接口调用:', fullUrl);
// 3. 发起请求
fetch(fullUrl, {
method: 'GET',
mode: 'cors',
headers: { 'Accept': 'application/json' }
})
.then(response => {
if (!response.ok) {
return response.json().then(err => {
throw new Error(err.detail?.[0]?.msg || `请求失败: ${response.status}`);
});
}
return response.json();
})
.then(kgData => {
// 验证返回数据格式
if (!kgData.nodes || !kgData.edges) {
throw new Error('知识图谱数据格式不正确');
}
// 2. 根据分类获取对应的文件列表
const fileCategories = JSON.parse(localStorage.getItem('fileCategories')) || {};
const categoryFiles = [];
Object.entries(fileCategories).forEach(([fileName, category]) => {
if (category === selectedCategory) {
categoryFiles.push(fileName);
}
});
// 4. 处理节点连接数(用于节点大小显示)
const nodeLinkCount = {};
kgData.nodes.forEach(node => nodeLinkCount[node.id] = 0);
kgData.edges.forEach(edge => {
nodeLinkCount[edge.source]++;
nodeLinkCount[edge.target]++;
});
if (categoryFiles.length === 0) {
nodeInfo.className = 'error-message';
nodeInfo.innerHTML = `分类"${selectedCategory}"下没有找到文件`;
return;
}
// 5. 转换为ECharts所需格式
const echartsNodes = kgData.nodes.map(node => ({
id: node.id,
name: node.labels?.[0] || '未知节点',
category: node.properties?.entity_type || '默认类型',
properties: node.properties || {},
symbolSize: 20 + (nodeLinkCount[node.id] * 3) // 根据连接数调整节点大小
}));
const echartsLinks = kgData.edges.map(edge => ({
source: edge.source,
target: edge.target,
properties: edge.properties || {}
}));
// 提取分类(用于图例)
const categories = Array.from(new Set(echartsNodes.map(n => n.category)))
.map(type => ({ name: type }));
// 6. 初始化ECharts并渲染
const chart = echarts.init(document.getElementById('graph-container'));
// 设置图表配置
chart.setOption({
tooltip: {
formatter: function(params) {
return `<strong>${params.name}</strong><br>类型: ${params.data.category}`;
const params = {
label: document.getElementById('graphLabel').value.trim(),
max_depth: document.getElementById('graphMaxDepth').value.trim() || 3,
max_nodes: document.getElementById('graphMaxNodes').value.trim() || 100,
note_ids: categoryFiles // 使用分类对应的文件列表
};
// 表单验证
if (!params.label) {
nodeInfo.className = 'error-message';
nodeInfo.innerHTML = '请填写标签';
return;
}
},
legend: {
data: categories.map(c => c.name),
bottom: 10,
textStyle: { fontSize: 12 }
},
series: [{
type: 'graph',
layout: 'force', // 力导向布局
roam: true, // 支持缩放和平移
draggable: true, // 节点可拖拽
force: {
repulsion: 300, // 节点之间的排斥力
edgeLength: 100, // 边的长度
gravity: 0.1, // gravity力影响节点向中心聚集
iterations: 50 // 迭代次数,影响布局稳定性
},
label: {
show: true,
fontSize: 12,
overflow: 'truncate',
width: 60
},
categories: categories,
data: echartsNodes,
links: echartsLinks,
lineStyle: {
color: 'source', // 边的颜色和源节点一致
curveness: 0.1 // 边的弯曲度
},
emphasis: {
focus: 'adjacency', // 高亮相邻节点和边
lineStyle: {
width: 5 // 高亮时边的宽度
}
// 3. 构建请求URL
const baseUrl = `${API_BASE_URL}/graphs/note`;
const urlParams = new URLSearchParams();
// 添加参数
urlParams.append('label', params.label);
urlParams.append('max_depth', params.max_depth);
urlParams.append('max_nodes', params.max_nodes);
params.note_ids.forEach(id => urlParams.append('note_ids', id));
// 添加API密钥
const apiKey = '';
if (apiKey) {
urlParams.append('api_key_header_value', apiKey);
}
}]
});
const fullUrl = `${baseUrl}?${urlParams.toString()}`;
console.log('知识图谱接口调用:', fullUrl);
// 4. 发起请求(后面的代码保持不变)
fetch(fullUrl, {
method: 'GET',
mode: 'cors',
headers: { 'Accept': 'application/json' }
})
.then(response => {
if (!response.ok) {
return response.json().then(err => {
throw new Error(err.detail?.[0]?.msg || `请求失败: ${response.status}`);
});
}
return response.json();
})
.then(kgData => {
// 验证返回数据格式
if (!kgData.nodes || !kgData.edges) {
throw new Error('知识图谱数据格式不正确');
}
// 7. 节点和边的点击事件
chart.on('click', function(params) {
if (params.dataType === 'node') {
const props = params.data.properties;
nodeInfo.innerHTML = `
<strong>${params.name}</strong> (${params.data.category})<br>
连接数: ${nodeLinkCount[params.data.id]}<br>
${props.description ? `描述: ${props.description.substring(0, 150)}${props.description.length > 150 ? '...' : ''}<br>` : ''}
${props.source ? `来源: ${props.source}` : ''}
`;
} else if (params.dataType === 'edge') {
const sourceNode = echartsNodes.find(n => n.id === params.data.source);
const targetNode = echartsNodes.find(n => n.id === params.data.target);
nodeInfo.innerHTML = `
<strong>关系</strong><br>
${sourceNode?.name || '未知节点'} → ${targetNode?.name || '未知节点'}<br>
${params.data.properties.description ? `描述: ${params.data.properties.description}` : ''}
`;
}
// 处理节点连接数
const nodeLinkCount = {};
kgData.nodes.forEach(node => nodeLinkCount[node.id] = 0);
kgData.edges.forEach(edge => {
nodeLinkCount[edge.source]++;
nodeLinkCount[edge.target]++;
});
// 转换为ECharts所需格式
const echartsNodes = kgData.nodes.map(node => ({
id: node.id,
name: node.labels?.[0] || '未知节点',
category: node.properties?.entity_type || '默认类型',
properties: node.properties || {},
symbolSize: 20 + (nodeLinkCount[node.id] * 3)
}));
const echartsLinks = kgData.edges.map(edge => ({
source: edge.source,
target: edge.target,
properties: edge.properties || {}
}));
// 提取分类
const categories = Array.from(new Set(echartsNodes.map(n => n.category)))
.map(type => ({ name: type }));
// 初始化ECharts并渲染
const chart = echarts.init(document.getElementById('graph-container'));
chart.setOption({
tooltip: {
formatter: function(params) {
return `<strong>${params.name}</strong><br>类型: ${params.data.category}`;
}
},
legend: {
data: categories.map(c => c.name),
bottom: 10,
textStyle: { fontSize: 12 }
},
series: [{
type: 'graph',
layout: 'force',
roam: true,
draggable: true,
force: {
repulsion: 300,
edgeLength: 100,
gravity: 0.1,
iterations: 50
},
label: {
show: true,
fontSize: 12,
overflow: 'truncate',
width: 60
},
categories: categories,
data: echartsNodes,
links: echartsLinks,
lineStyle: {
color: 'source',
curveness: 0.1
},
emphasis: {
focus: 'adjacency',
lineStyle: {
width: 5
}
}
}]
});
// 节点和边的点击事件
chart.on('click', function(params) {
if (params.dataType === 'node') {
const props = params.data.properties;
nodeInfo.innerHTML = `
<strong>${params.name}</strong> (${params.data.category})<br>
连接数: ${nodeLinkCount[params.data.id]}<br>
${props.description ? `描述: ${props.description.substring(0, 150)}${props.description.length > 150 ? '...' : ''}<br>` : ''}
${props.source ? `来源: ${props.source}` : ''}
`;
} else if (params.dataType === 'edge') {
const sourceNode = echartsNodes.find(n => n.id === params.data.source);
const targetNode = echartsNodes.find(n => n.id === params.data.target);
nodeInfo.innerHTML = `
<strong>关系</strong><br>
${sourceNode?.name || '未知节点'} → ${targetNode?.name || '未知节点'}<br>
${params.data.properties.description ? `描述: ${params.data.properties.description}` : ''}
`;
}
});
// 窗口大小变化时重绘
window.addEventListener('resize', () => chart.resize());
// 更新状态信息
nodeInfo.className = '';
nodeInfo.innerHTML = `
知识图谱加载完成 | 分类: ${selectedCategory} | 文件数: ${categoryFiles.length}<br>
节点数: ${echartsNodes.length} | 关系数: ${echartsLinks.length}<br>
提示: 可拖拽节点、缩放视图,点击节点/边查看详情
`;
})
.catch(error => {
console.error('知识图谱加载失败:', error);
nodeInfo.className = 'error-message';
nodeInfo.innerHTML = `加载失败: ${error.message}`;
});
});
}
// 窗口大小变化时重绘
window.addEventListener('resize', () => chart.resize());
// 更新状态信息
nodeInfo.className = '';
nodeInfo.innerHTML = `
知识图谱加载完成 | 节点数: ${echartsNodes.length} | 关系数: ${echartsLinks.length}<br>
提示: 可拖拽节点、缩放视图,点击节点/边查看详情
`;
})
.catch(error => {
console.error('知识图谱加载失败:', error);
nodeInfo.className = 'error-message';
nodeInfo.innerHTML = `加载失败: ${error.message}`;
});
// 初始化时渲染分类选择框
renderGraphCategoryFilter();
});
// 修复知识图谱分类选择框渲染
function renderGraphCategoryFilter() {
const categorySelect = document.getElementById('graphCategory');
if (!categorySelect) {
console.error('未找到知识图谱分类选择框');
return;
}
// 保存当前选中的值
const currentValue = categorySelect.value;
// 清空现有选项
categorySelect.innerHTML = '<option value="">请选择分类</option>';
// 获取所有分类
const categories = getAllCategories();
// 生成选项
categories.forEach(category => {
const option = document.createElement('option');
option.value = category;
option.textContent = category;
categorySelect.appendChild(option);
});
}
// 自动填充已上传的文档ID到知识图谱表单
function populateNoteIds() {
const noteIdsInput = document.getElementById('graphNoteIds');
if (noteIdsInput && Array.isArray(window.allDocuments)) {
// 提取所有已上传文档的文件名作为默认值
const docNames = window.allDocuments.map(doc => {
const pathParts = doc.file_path.split('/');
return pathParts[pathParts.length - 1]; // 获取文件名
});
if (docNames.length > 0) {
noteIdsInput.value = docNames.join(',');
}
// 恢复之前选中的值(如果还存在)
if (currentValue && categories.includes(currentValue)) {
categorySelect.value = currentValue;
} else if (categories.length > 0) {
// 否则选择第一个分类
categorySelect.value = categories[0];
}
}
// 当文档列表加载完成后自动填充
// 在fetchAllDocuments函数的then回调中添加populateNoteIds()调用
// 修改原fetchAllDocuments函数的then部分
const originalFetchAllDocumentsThen = fetchAllDocuments.toString().includes('renderDocumentsList')
? fetchAllDocuments.then
: null;
if (originalFetchAllDocumentsThen) {
// 这里是示意实际需要在原fetchAllDocuments的then中添加
// renderDocumentsList();
// populateNoteIds(); // 添加这一行
}
});
console.log(`知识图谱分类选择框已更新,共${categories.length}个分类`);
}
</script>
</body>
</html>

@ -0,0 +1,625 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>灵简 - Windows 轻量化学习辅助软件</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- Tailwind配置自定义颜色与字体 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF', // 主蓝色
secondary: '#36CFC9', // 辅助蓝绿色
neutral: '#F5F7FA', // 中性背景色
dark: '#1D2129', // 深色文本
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.text-shadow {
text-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.card-hover {
transition: all 0.3s ease;
}
.card-hover:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(22, 93, 255, 0.15);
}
.windows-badge {
background-color: #00A4EF;
}
}
</style>
</head>
<body class="font-sans text-dark bg-white">
<!-- 导航栏 -->
<header id="navbar" class="fixed w-full top-0 z-50 transition-all 0.3s ease bg-white/90 backdrop-blur-sm shadow-sm">
<div class="container mx-auto px-4 md:px-8 py-4 flex justify-between items-center">
<!-- Logo -->
<a href="#" class="flex items-center gap-2">
<img src="https://picsum.photos/200/200" alt="灵简软件图标" class="w-10 h-10 rounded-lg">
<span class="text-xl font-bold text-dark">灵简</span>
<span class="windows-badge text-white text-xs px-2 py-1 rounded-full">Windows</span>
</a>
<!-- 导航菜单 - 桌面端 -->
<nav class="hidden md:flex items-center gap-8">
<a href="#features" class="text-dark/80 hover:text-primary transition-colors">核心功能</a>
<a href="#tech" class="text-dark/80 hover:text-primary transition-colors">技术特色</a>
<a href="#deployment" class="text-dark/80 hover:text-primary transition-colors">部署方式</a>
<a href="#download" class="text-dark/80 hover:text-primary transition-colors">下载使用</a>
</nav>
<!-- 下载按钮 -->
<a href="#download" class="hidden md:block px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors">
立即下载
</a>
<!-- 移动端菜单按钮 -->
<button id="menuBtn" class="md:hidden text-dark text-xl">
<i class="fa fa-bars"></i>
</button>
</div>
<!-- 移动端导航菜单 -->
<div id="mobileMenu" class="md:hidden hidden bg-white border-t border-gray-100">
<div class="container mx-auto px-4 py-3 flex flex-col gap-3">
<a href="#features" class="py-2 text-dark/80 hover:text-primary transition-colors">核心功能</a>
<a href="#tech" class="py-2 text-dark/80 hover:text-primary transition-colors">技术特色</a>
<a href="#deployment" class="py-2 text-dark/80 hover:text-primary transition-colors">部署方式</a>
<a href="#download" class="py-2 text-dark/80 hover:text-primary transition-colors">下载使用</a>
<a href="#download" class="py-2 bg-primary text-white rounded-lg text-center hover:bg-primary/90 transition-colors">
立即下载
</a>
</div>
</div>
</header>
<main class="pt-24">
<!-- 英雄区 -->
<section class="container mx-auto px-4 md:px-8 py-12 md:py-20">
<div class="flex flex-col md:flex-row items-center gap-10">
<div class="md:w-1/2 space-y-6">
<h1 class="text-[clamp(2rem,5vw,3.5rem)] font-bold leading-tight text-shadow">
告别笔记碎片化<br>
<span class="text-primary">AI 助力高效学习</span>
</h1>
<div class="flex items-center gap-3 bg-primary/10 text-primary px-4 py-2 rounded-lg inline-block">
<i class="fa fa-windows"></i>
<span>专为 Windows 系统优化</span>
</div>
<p class="text-lg text-dark/70 max-w-lg">
灵简聚焦学生“笔记管理-知识点检索-即时答疑-学习反馈”全流程需求结合RAG与大模型技术让学习更轻量、更高效。
</p>
<div class="flex flex-wrap gap-4 pt-4">
<a href="#download" class="px-8 py-3 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors flex items-center gap-2">
<i class="fa fa-download"></i> Windows 版
</a>
<a href="#features" class="px-8 py-3 border border-primary text-primary rounded-lg hover:bg-primary/5 transition-colors flex items-center gap-2">
<i class="fa fa-info-circle"></i> 了解更多
</a>
</div>
<!-- 信任标识 -->
<div class="pt-6 space-y-3">
<p class="text-sm text-dark/50">已被全国多所高校学生使用</p>
<div class="flex items-center gap-6 text-dark/40">
<span class="text-lg">中国民航大学</span>
<span class="text-lg">北京大学</span>
<span class="text-lg">清华大学</span>
</div>
</div>
</div>
<!-- 产品截图 -->
<div class="md:w-1/2 relative">
<div class="absolute -top-10 -left-10 w-64 h-64 bg-primary/5 rounded-full filter blur-3xl"></div>
<div class="absolute -bottom-10 -right-10 w-80 h-80 bg-secondary/5 rounded-full filter blur-3xl"></div>
<img src="https://picsum.photos/id/0/600/400" alt="灵简软件Windows界面截图" class="w-full h-auto rounded-xl shadow-xl relative z-10">
<div class="absolute top-4 right-4 bg-white/90 backdrop-blur-sm px-3 py-1 rounded-full text-sm shadow-md flex items-center gap-2">
<i class="fa fa-windows text-blue-500"></i>
<span>Windows 10/11 兼容</span>
</div>
</div>
</div>
</section>
<!-- 核心功能 -->
<section id="features" class="container mx-auto px-4 md:px-8 py-16 bg-neutral rounded-3xl my-16">
<div class="text-center max-w-3xl mx-auto mb-16">
<h2 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold mb-4">四大核心功能,解决学习痛点</h2>
<p class="text-dark/70 text-lg">从笔记管理到学习反馈,覆盖学生全场景学习需求,让每一次学习都有价值</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
<!-- 功能1多格式笔记管理 -->
<div class="bg-white p-6 rounded-xl shadow-md card-hover">
<div class="w-12 h-12 bg-primary/10 text-primary rounded-lg flex items-center justify-center mb-5">
<i class="fa fa-files-o text-xl"></i>
</div>
<h3 class="text-xl font-semibold mb-3">多格式笔记统一管理</h3>
<p class="text-dark/70 mb-4">支持PDF、Word、手写拍照JPG/PNG上传解析无需切换软件统一预览编辑。</p>
<ul class="space-y-2 text-dark/60">
<li class="flex items-start gap-2">
<i class="fa fa-check text-secondary mt-1"></i>
<span>结构化提取文本与公式</span>
</li>
<li class="flex items-start gap-2">
<i class="fa fa-check text-secondary mt-1"></i>
<span>按科目/标签智能分类</span>
</li>
<li class="flex items-start gap-2">
<i class="fa fa-check text-secondary mt-1"></i>
<span>本地/云端双重备份</span>
</li>
</ul>
</div>
<!-- 功能2RAG语义检索 -->
<div class="bg-white p-6 rounded-xl shadow-md card-hover">
<div class="w-12 h-12 bg-primary/10 text-primary rounded-lg flex items-center justify-center mb-5">
<i class="fa fa-search text-xl"></i>
</div>
<h3 class="text-xl font-semibold mb-3">RAG语义检索</h3>
<p class="text-dark/70 mb-4">输入自然语言即可定位相关知识点,告别“文件名关键词”检索的局限性。</p>
<ul class="space-y-2 text-dark/60">
<li class="flex items-start gap-2">
<i class="fa fa-check text-secondary mt-1"></i>
<span>支持“关键词+语义”双模式</span>
</li>
<li class="flex items-start gap-2">
<i class="fa fa-check text-secondary mt-1"></i>
<span>教育领域词库优化</span>
</li>
<li class="flex items-start gap-2">
<i class="fa fa-check text-secondary mt-1"></i>
<span>检索结果智能排序</span>
</li>
</ul>
</div>
<!-- 功能3大模型答疑 -->
<div class="bg-white p-6 rounded-xl shadow-md card-hover">
<div class="w-12 h-12 bg-primary/10 text-primary rounded-lg flex items-center justify-center mb-5">
<i class="fa fa-comments-o text-xl"></i>
</div>
<h3 class="text-xl font-semibold mb-3">结合笔记的大模型答疑</h3>
<p class="text-dark/70 mb-4">优先基于你的笔记生成答案,补充通用知识,确保与课堂重点一致。</p>
<ul class="space-y-2 text-dark/60">
<li class="flex items-start gap-2">
<i class="fa fa-check text-secondary mt-1"></i>
<span>标注答案来源如“2023高数笔记P5”</span>
</li>
<li class="flex items-start gap-2">
<i class="fa fa-check text-secondary mt-1"></i>
<span>支持“基于笔记片段提问”</span>
</li>
<li class="flex items-start gap-2">
<i class="fa fa-check text-secondary mt-1"></i>
<span>开源/云端模型灵活切换</span>
</li>
</ul>
</div>
<!-- 功能4学习数据反馈 -->
<div class="bg-white p-6 rounded-xl shadow-md card-hover">
<div class="w-12 h-12 bg-primary/10 text-primary rounded-lg flex items-center justify-center mb-5">
<i class="fa fa-bar-chart text-xl"></i>
</div>
<h3 class="text-xl font-semibold mb-3">学习数据可视化反馈</h3>
<p class="text-dark/70 mb-4">统计检索频次与答疑主题,生成薄弱知识点报告,辅助制定复习计划。</p>
<ul class="space-y-2 text-dark/60">
<li class="flex items-start gap-2">
<i class="fa fa-check text-secondary mt-1"></i>
<span>多维度图表展示(饼图/折线图)</span>
</li>
<li class="flex items-start gap-2">
<i class="fa fa-check text-secondary mt-1"></i>
<span>自动生成薄弱点分析</span>
</li>
<li class="flex items-start gap-2">
<i class="fa fa-check text-secondary mt-1"></i>
<span>数据支持Excel导出</span>
</li>
</ul>
</div>
</div>
<!-- 功能演示图表 -->
<div class="mt-16 bg-white p-6 md:p-8 rounded-xl shadow-md">
<h3 class="text-xl font-semibold mb-6 text-center">学习数据反馈示例</h3>
<div class="w-full h-80">
<canvas id="studyChart"></canvas>
</div>
</div>
</section>
<!-- 技术特色 -->
<section id="tech" class="container mx-auto px-4 md:px-8 py-16">
<div class="text-center max-w-3xl mx-auto mb-16">
<h2 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold mb-4">三大技术特色,保障体验</h2>
<p class="text-dark/70 text-lg">Windows 平台深度优化,兼顾性能与隐私保护</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<!-- 特色1多格式智能解析 -->
<div class="border border-gray-100 rounded-xl p-6 bg-white shadow-md card-hover">
<div class="mb-6 flex justify-center">
<div class="w-16 h-16 bg-primary/10 text-primary rounded-full flex items-center justify-center">
<i class="fa fa-magic text-2xl"></i>
</div>
</div>
<h3 class="text-xl font-semibold mb-3 text-center">多格式笔记智能解析</h3>
<p class="text-dark/70 text-center mb-4">不仅是文件存储,更是结构化处理,为检索与答疑提供高质量数据基础。</p>
<div class="space-y-3 text-dark/60">
<div class="flex items-center gap-3 p-3 bg-neutral rounded-lg">
<i class="fa fa-file-pdf-o text-red-500"></i>
<span>PDF/Word提取文本、公式Mathpix API、图片文字OCR</span>
</div>
<div class="flex items-center gap-3 p-3 bg-neutral rounded-lg">
<i class="fa fa-camera text-green-500"></i>
<span>手写拍照场景化OCR支持手动修正识别错误</span>
</div>
</div>
</div>
<!-- 特色2检索-答疑-笔记联动 -->
<div class="border border-gray-100 rounded-xl p-6 bg-white shadow-md card-hover">
<div class="mb-6 flex justify-center">
<div class="w-16 h-16 bg-primary/10 text-primary rounded-full flex items-center justify-center">
<i class="fa fa-link text-2xl"></i>
</div>
</div>
<h3 class="text-xl font-semibold mb-3 text-center">“检索-答疑-笔记”联动</h3>
<p class="text-dark/70 text-center mb-4">打破功能壁垒,形成学习闭环,减少操作成本,提升效率。</p>
<div class="space-y-3 text-dark/60">
<div class="flex items-start gap-3 p-3 bg-neutral rounded-lg">
<i class="fa fa-angle-right text-primary mt-1"></i>
<span>检索结果页可直接发起答疑,基于片段生成针对性答案</span>
</div>
<div class="flex items-start gap-3 p-3 bg-neutral rounded-lg">
<i class="fa fa-angle-right text-primary mt-1"></i>
<span>答疑补充的知识点,可一键添加到对应笔记,动态完善</span>
</div>
</div>
</div>
<!-- 特色3Windows平台优化 -->
<div class="border border-gray-100 rounded-xl p-6 bg-white shadow-md card-hover">
<div class="mb-6 flex justify-center">
<div class="w-16 h-16 bg-primary/10 text-primary rounded-full flex items-center justify-center">
<i class="fa fa-windows text-2xl"></i>
</div>
</div>
<h3 class="text-xl font-semibold mb-3 text-center">Windows 平台深度优化</h3>
<p class="text-dark/70 text-center mb-4">针对 Windows 系统特性优化性能,提供最佳使用体验。</p>
<div class="space-y-3 text-dark/60">
<div class="flex items-start gap-3 p-3 bg-neutral rounded-lg">
<i class="fa fa-check text-primary mt-1"></i>
<span>支持 Windows 10/11 系统,兼容触控屏操作</span>
</div>
<div class="flex items-start gap-3 p-3 bg-neutral rounded-lg">
<i class="fa fa-check text-primary mt-1"></i>
<span>低资源占用设计,适配主流笔记本电脑</span>
</div>
<div class="flex items-start gap-3 p-3 bg-neutral rounded-lg">
<i class="fa fa-check text-primary mt-1"></i>
<span>支持 Windows Hello 快速登录,保护隐私</span>
</div>
</div>
</div>
</div>
</section>
<!-- 部署方式 -->
<section id="deployment" class="container mx-auto px-4 md:px-8 py-16 bg-neutral rounded-3xl my-16">
<div class="text-center max-w-3xl mx-auto mb-16">
<h2 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold mb-4">灵活部署方式,适应不同场景</h2>
<p class="text-dark/70 text-lg">针对 Windows 平台特点,提供两种部署选择</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-5xl mx-auto">
<!-- 本地部署 -->
<div class="bg-white rounded-xl shadow-md overflow-hidden card-hover">
<div class="bg-primary/10 p-6">
<h3 class="text-2xl font-semibold text-primary flex items-center gap-2">
<i class="fa fa-desktop"></i> 本地部署
</h3>
<p class="text-dark/60 mt-2">适合个人使用、敏感笔记管理(如考研真题)</p>
</div>
<div class="p-6 space-y-4">
<h4 class="font-semibold text-lg">适用场景</h4>
<ul class="space-y-2 text-dark/70">
<li class="flex items-start gap-2">
<i class="fa fa-check-circle text-secondary mt-1"></i>
<span>个人学习,需保护笔记隐私</span>
</li>
<li class="flex items-start gap-2">
<i class="fa fa-check-circle text-secondary mt-1"></i>
<span>设备配置较高8GB内存+GTX1050及以上显卡</span>
</li>
<li class="flex items-start gap-2">
<i class="fa fa-check-circle text-secondary mt-1"></i>
<span>无稳定网络环境(本地部署无需联网)</span>
</li>
</ul>
<h4 class="font-semibold text-lg mt-6">部署步骤</h4>
<ol class="space-y-2 text-dark/70 list-decimal list-inside">
<li>下载 Windows 客户端安装包(.exe</li>
<li>双击安装,支持自定义安装路径</li>
<li>内置FAISS向量数据库自动初始化</li>
<li>一键式安装开源大模型如Llama 2-7B</li>
<li>直接使用,数据存储在本地</li>
</ol>
</div>
</div>
<!-- 云端部署 -->
<div class="bg-white rounded-xl shadow-md overflow-hidden card-hover">
<div class="bg-secondary/10 p-6">
<h3 class="text-2xl font-semibold text-secondary flex items-center gap-2">
<i class="fa fa-cloud"></i> 云端部署
</h3>
<p class="text-dark/60 mt-2">适合小组共享如班级、低配置PC使用</p>
</div>
<div class="p-6 space-y-4">
<h4 class="font-semibold text-lg">适用场景</h4>
<ul class="space-y-2 text-dark/70">
<li class="flex items-start gap-2">
<i class="fa fa-check-circle text-secondary mt-1"></i>
<span>小组协作,需共享部分笔记资源</span>
</li>
<li class="flex items-start gap-2">
<i class="fa fa-check-circle text-secondary mt-1"></i>
<span>设备配置较低无独立显卡、4GB内存</span>
</li>
<li class="flex items-start gap-2">
<i class="fa fa-check-circle text-secondary mt-1"></i>
<span>需多设备同步笔记如PC+平板)</span>
</li>
</ul>
<h4 class="font-semibold text-lg mt-6">部署步骤</h4>
<ol class="space-y-2 text-dark/70 list-decimal list-inside">
<li>下载 Windows 轻量客户端(.exe</li>
<li>安装后输入团队提供的服务器地址</li>
<li>通过 Windows 账户授权登录</li>
<li>笔记加密存储在云端,本地仅缓存</li>
<li>检索/答疑在服务端处理,结果实时返回</li>
</ol>
</div>
</div>
</div>
</section>
<!-- 下载入口 -->
<section id="download" class="container mx-auto px-4 md:px-8 py-16">
<div class="max-w-4xl mx-auto bg-gradient-to-r from-primary/90 to-primary rounded-2xl overflow-hidden shadow-xl">
<div class="flex flex-col md:flex-row items-center">
<div class="md:w-1/2 p-8 md:p-12 text-white">
<h2 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold mb-4">开始你的高效学习之旅</h2>
<p class="text-white/80 text-lg mb-8">专为 Windows 系统打造,下载后即可免费使用全部核心功能,无广告、无订阅。</p>
<div class="flex flex-wrap gap-4">
<a href="#" class="flex items-center gap-2 px-6 py-3 bg-white text-primary rounded-lg hover:bg-white/90 transition-colors">
<i class="fa fa-windows text-xl"></i>
<span>Windows 版 (.exe)</span>
</a>
</div>
<div class="mt-6 flex items-center gap-4 text-white/80">
<div class="flex items-center gap-1">
<i class="fa fa-check-circle"></i>
<span>支持 Win 10/11</span>
</div>
<div class="flex items-center gap-1">
<i class="fa fa-check-circle"></i>
<span>大小286 MB</span>
</div>
</div>
<p class="text-white/60 text-sm mt-6">当前版本v1.0.0 | 更新时间2025.09</p>
</div>
<div class="md:w-1/2 p-8 md:p-12 bg-white/10 backdrop-blur-sm">
<div class="bg-white rounded-xl p-6 shadow-md">
<h3 class="text-xl font-semibold mb-4 text-dark">需要帮助?联系我们</h3>
<form class="space-y-4">
<div>
<label class="block text-dark/80 mb-2 text-sm">你的邮箱</label>
<input type="email" placeholder="请输入你的邮箱" class="w-full px-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary">
</div>
<div>
<label class="block text-dark/80 mb-2 text-sm">问题描述</label>
<textarea rows="3" placeholder="请描述你的问题或需求" class="w-full px-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"></textarea>
</div>
<button type="submit" class="w-full px-4 py-3 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors">
提交反馈
</button>
</form>
</div>
</div>
</div>
</div>
</section>
</main>
<!-- 页脚 -->
<footer class="bg-dark text-white py-12">
<div class="container mx-auto px-4 md:px-8">
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 mb-8">
<div>
<div class="flex items-center gap-2 mb-4">
<img src="https://picsum.photos/200/200" alt="灵简软件图标" class="w-10 h-10 rounded-lg">
<span class="text-xl font-bold">灵简</span>
<span class="windows-badge text-white text-xs px-2 py-1 rounded-full">Windows</span>
</div>
<p class="text-white/60 mb-4">专为 Windows 系统打造的轻量化学习辅助工具,让笔记管理更简单,知识点应用更高效。</p>
<div class="flex gap-4">
<a href="#" class="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center hover:bg-primary transition-colors">
<i class="fa fa-weixin"></i>
</a>
<a href="#" class="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center hover:bg-primary transition-colors">
<i class="fa fa-github"></i>
</a>
<a href="#" class="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center hover:bg-primary transition-colors">
<i class="fa fa-envelope"></i>
</a>
</div>
</div>
<div>
<h4 class="text-lg font-semibold mb-4">产品</h4>
<ul class="space-y-2">
<li><a href="#features" class="text-white/60 hover:text-white transition-colors">核心功能</a></li>
<li><a href="#tech" class="text-white/60 hover:text-white transition-colors">技术特色</a></li>
<li><a href="#deployment" class="text-white/60 hover:text-white transition-colors">部署方式</a></li>
<li><a href="#" class="text-white/60 hover:text-white transition-colors">更新日志</a></li>
</ul>
</div>
<div>
<h4 class="text-lg font-semibold mb-4">支持</h4>
<ul class="space-y-2">
<li><a href="#" class="text-white/60 hover:text-white transition-colors">Windows 版帮助中心</a></li>
<li><a href="#" class="text-white/60 hover:text-white transition-colors">安装教程</a></li>
<li><a href="#" class="text-white/60 hover:text-white transition-colors">常见问题</a></li>
<li><a href="#" class="text-white/60 hover:text-white transition-colors">联系我们</a></li>
</ul>
</div>
<div>
<h4 class="text-lg font-semibold mb-4">关于</h4>
<ul class="space-y-2">
<li><a href="#" class="text-white/60 hover:text-white transition-colors">团队介绍</a></li>
<li><a href="#" class="text-white/60 hover:text-white transition-colors">隐私政策</a></li>
<li><a href="#" class="text-white/60 hover:text-white transition-colors">用户协议</a></li>
<li><a href="#" class="text-white/60 hover:text-white transition-colors">版权声明</a></li>
</ul>
</div>
</div>
<div class="border-t border-white/10 pt-8 text-center text-white/40 text-sm">
<p>© 2025 灵简学习辅助软件 版权所有 | 专为 Windows 系统优化</p>
</div>
</div>
</footer>
<!-- JavaScript -->
<script>
// 移动端菜单切换
const menuBtn = document.getElementById('menuBtn');
const mobileMenu = document.getElementById('mobileMenu');
menuBtn.addEventListener('click', () => {
mobileMenu.classList.toggle('hidden');
menuBtn.innerHTML = mobileMenu.classList.contains('hidden')
? '<i class="fa fa-bars"></i>'
: '<i class="fa fa-times"></i>';
});
// 导航栏滚动效果
const navbar = document.getElementById('navbar');
window.addEventListener('scroll', () => {
if (window.scrollY > 50) {
navbar.classList.add('py-2', 'shadow');
navbar.classList.remove('py-4');
} else {
navbar.classList.add('py-4');
navbar.classList.remove('py-2', 'shadow');
}
});
// 平滑滚动
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href');
const targetElement = document.querySelector(targetId);
if (targetElement) {
window.scrollTo({
top: targetElement.offsetTop - 80,
behavior: 'smooth'
});
// 关闭移动端菜单
if (!mobileMenu.classList.contains('hidden')) {
mobileMenu.classList.add('hidden');
menuBtn.innerHTML = '<i class="fa fa-bars"></i>';
}
}
});
});
// 学习数据图表
const ctx = document.getElementById('studyChart').getContext('2d');
const studyChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['微积分', '线性代数', '概率论', '大学物理', '程序设计'],
datasets: [
{
label: '检索频次',
data: [28, 15, 12, 8, 22],
backgroundColor: 'rgba(22, 93, 255, 0.7)',
borderColor: 'rgba(22, 93, 255, 1)',
borderWidth: 1,
borderRadius: 4
},
{
label: '答疑次数',
data: [18, 10, 8, 5, 15],
backgroundColor: 'rgba(54, 207, 201, 0.7)',
borderColor: 'rgba(54, 207, 201, 1)',
borderWidth: 1,
borderRadius: 4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
},
tooltip: {
mode: 'index',
intersect: false
},
title: {
display: true,
text: '各科目检索与答疑统计近30天',
font: {
size: 16
}
}
},
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: '次数'
}
},
x: {
title: {
display: true,
text: '科目'
}
}
}
}
});
</script>
</body>
</html>
Loading…
Cancel
Save