Files
SI_TEALab_2025_LabWebsite/dofile/js/research.js
T
pengxiao a3eb5c7b2d init: TEALab 2025 实验室网站项目
从百度网盘 /apps/bypy/dofile/ 下载的完整项目结构,包含:
- dofile/: Flask 后端(端口 5000)+ 静态前端(端口 6002)
- officefile/: 项目相关文档
- .gitignore: Python venv / 日志 / 缓存

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 01:07:19 +08:00

573 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Research Management JavaScript
let currentProjects = [];
let currentFilter = 'all';
let currentProjectDetail = null;
// 检查登录状态
async function checkLogin() {
try {
const response = await window.DAELAPI.auth.checkAuth();
if (!response.authenticated) {
window.location.href = 'login.html';
return false;
}
return true;
} catch (error) {
window.location.href = 'login.html';
return false;
}
}
// 加载项目列表
async function loadProjects() {
try {
const response = await window.DAELAPI.research.getProjects();
currentProjects = response.projects;
displayProjects(currentProjects);
} catch (error) {
console.error('Failed to load projects:', error);
document.getElementById('projects-list').innerHTML = '<p class="text-red-600">加载失败</p>';
}
}
// 显示项目列表
function displayProjects(projects) {
const container = document.getElementById('projects-list');
// 根据筛选器过滤
let filteredProjects = projects;
if (currentFilter !== 'all') {
filteredProjects = projects.filter(p => p.status === currentFilter);
}
if (filteredProjects.length === 0) {
container.innerHTML = '<p class="text-gray-500 text-center py-12">暂无项目</p>';
return;
}
container.innerHTML = filteredProjects.map(project => {
const startDate = project.start_date ? new Date(project.start_date).toLocaleDateString('zh-CN') : '未设置';
const endDate = project.end_date ? new Date(project.end_date).toLocaleDateString('zh-CN') : '未设置';
const isOwner = project.owner_id === (window.currentUser?.id);
return `
<div class="bg-white border border-gray-200 p-6 rounded-lg hover:shadow-md transition-shadow">
<div class="flex justify-between items-start">
<div class="flex-1">
<div class="flex items-center gap-3 mb-2">
<h3 class="text-xl font-bold">${project.title}</h3>
<span class="text-xs px-2 py-1 rounded ${getStatusColor(project.status)}">${getStatusText(project.status)}</span>
</div>
<p class="text-gray-600 text-sm mb-3">${project.description || ''}</p>
<div class="flex gap-6 text-sm text-gray-500 mb-3">
<span>📅 ${startDate} - ${endDate}</span>
<span>👤 ${project.owner_name || ''}</span>
<span>📊 进度: ${project.progress}%</span>
</div>
${project.tags && project.tags.length > 0 ? `
<div class="flex gap-2 flex-wrap">
${project.tags.map(tag => `<span class="text-xs px-2 py-1 bg-gray-100 rounded">${tag}</span>`).join('')}
</div>
` : ''}
</div>
<div class="flex gap-2 ml-4">
<button onclick="viewProject(${project.id})" class="text-sm text-blue-600 hover:text-blue-800 px-3 py-1 border border-blue-600 rounded">查看</button>
${isOwner ? `
<button onclick="editProject(${project.id})" class="text-sm text-gray-600 hover:text-gray-800 px-3 py-1 border border-gray-600 rounded">编辑</button>
<button onclick="deleteProject(${project.id})" class="text-sm text-red-600 hover:text-red-800 px-3 py-1 border border-red-600 rounded">删除</button>
` : ''}
</div>
</div>
<div class="mt-4 pt-4 border-t border-gray-200">
<div class="w-full bg-gray-200 rounded-full h-2">
<div class="bg-black h-2 rounded-full" style="width: ${project.progress}%"></div>
</div>
</div>
</div>
`;
}).join('');
}
// 状态颜色
function getStatusColor(status) {
const colors = {
'active': 'bg-green-100 text-green-800',
'completed': 'bg-gray-100 text-gray-800',
'paused': 'bg-yellow-100 text-yellow-800',
'cancelled': 'bg-red-100 text-red-800'
};
return colors[status] || 'bg-gray-100 text-gray-800';
}
// 状态文本
function getStatusText(status) {
const texts = {
'active': '进行中',
'completed': '已完成',
'paused': '已暂停',
'cancelled': '已取消'
};
return texts[status] || status;
}
// 显示项目表单
function showProjectForm(projectId = null) {
const modal = document.getElementById('project-modal');
const form = document.getElementById('project-form');
const title = document.getElementById('project-modal-title');
if (projectId) {
const project = currentProjects.find(p => p.id === projectId);
if (project) {
title.textContent = '编辑项目';
document.getElementById('project-id').value = project.id;
document.getElementById('project-title').value = project.title;
document.getElementById('project-description').value = project.description || '';
document.getElementById('project-start-date').value = project.start_date || '';
document.getElementById('project-end-date').value = project.end_date || '';
document.getElementById('project-status').value = project.status;
document.getElementById('project-progress').value = project.progress;
document.getElementById('project-tags').value = project.tags ? project.tags.join(', ') : '';
}
} else {
title.textContent = '新建项目';
form.reset();
document.getElementById('project-id').value = '';
document.getElementById('project-progress').value = 0;
}
modal.classList.remove('hidden');
}
// 关闭项目表单
function closeProjectForm() {
document.getElementById('project-modal').classList.add('hidden');
}
// 保存项目
async function saveProject(event) {
event.preventDefault();
const projectId = document.getElementById('project-id').value;
const tagsInput = document.getElementById('project-tags').value;
const tags = tagsInput ? tagsInput.split(',').map(t => t.trim()).filter(t => t) : [];
const projectData = {
title: document.getElementById('project-title').value,
description: document.getElementById('project-description').value,
start_date: document.getElementById('project-start-date').value || null,
end_date: document.getElementById('project-end-date').value || null,
status: document.getElementById('project-status').value,
progress: parseInt(document.getElementById('project-progress').value) || 0,
tags: tags
};
try {
if (projectId) {
await window.DAELAPI.research.updateProject(projectId, projectData);
} else {
await window.DAELAPI.research.createProject(projectData);
}
closeProjectForm();
loadProjects();
} catch (error) {
alert('保存失败: ' + error.message);
}
}
// 查看项目详情
async function viewProject(projectId) {
try {
const response = await window.DAELAPI.research.getProject(projectId);
currentProjectDetail = response.project;
displayProjectDetail(currentProjectDetail);
} catch (error) {
alert('加载失败: ' + error.message);
}
}
// 显示项目详情
function displayProjectDetail(project) {
const modal = document.getElementById('project-detail-modal');
const container = document.getElementById('project-detail-content');
const isOwner = project.owner_id === (window.currentUser?.id);
container.innerHTML = `
<div class="flex justify-between items-start mb-6">
<div>
<h3 class="text-2xl font-bold mb-2">${project.title}</h3>
<div class="flex items-center gap-3">
<span class="text-xs px-2 py-1 rounded ${getStatusColor(project.status)}">${getStatusText(project.status)}</span>
<span class="text-sm text-gray-500">进度: ${project.progress}%</span>
</div>
</div>
<button onclick="closeProjectDetail()" class="text-gray-400 hover:text-gray-600">✕</button>
</div>
<div class="space-y-4 mb-6">
${project.description ? `
<div>
<h4 class="font-bold mb-2">项目描述</h4>
<p class="text-sm text-gray-600">${project.description}</p>
</div>
` : ''}
<div class="grid grid-cols-2 gap-4 text-sm">
<div>
<span class="text-gray-500">开始日期:</span>
<span>${project.start_date ? new Date(project.start_date).toLocaleDateString('zh-CN') : '未设置'}</span>
</div>
<div>
<span class="text-gray-500">结束日期:</span>
<span>${project.end_date ? new Date(project.end_date).toLocaleDateString('zh-CN') : '未设置'}</span>
</div>
<div>
<span class="text-gray-500">负责人:</span>
<span>${project.owner_name || ''}</span>
</div>
</div>
${project.tags && project.tags.length > 0 ? `
<div>
<span class="text-gray-500 text-sm">标签:</span>
<div class="flex gap-2 flex-wrap mt-2">
${project.tags.map(tag => `<span class="text-xs px-2 py-1 bg-gray-100 rounded">${tag}</span>`).join('')}
</div>
</div>
` : ''}
</div>
<!-- Tabs for Notes, Tasks, Files -->
<div class="border-b border-gray-200 mb-4">
<button class="tab-btn active px-4 py-2 text-sm font-medium border-b-2 border-black" data-tab="notes">笔记 (${project.notes?.length || 0})</button>
<button class="tab-btn px-4 py-2 text-sm font-medium text-gray-500" data-tab="tasks">任务 (${project.tasks?.length || 0})</button>
<button class="tab-btn px-4 py-2 text-sm font-medium text-gray-500" data-tab="files">文件 (${project.files?.length || 0})</button>
</div>
<div id="tab-content-notes" class="tab-content">
${isOwner ? `
<button onclick="showNoteForm()" class="mb-4 bg-black text-white px-4 py-2 rounded text-sm hover:bg-gray-800">
+ 新建笔记
</button>
` : ''}
<div class="space-y-3">
${project.notes && project.notes.length > 0 ?
project.notes.map(note => `
<div class="border border-gray-200 p-4 rounded">
<div class="flex justify-between items-start mb-2">
<h5 class="font-bold">${note.title}</h5>
${isOwner && note.author_id === window.currentUser?.id ? `
<div class="flex gap-2">
<button onclick="editNote(${note.id})" class="text-xs text-blue-600">编辑</button>
<button onclick="deleteNote(${note.id})" class="text-xs text-red-600">删除</button>
</div>
` : ''}
</div>
<p class="text-sm text-gray-600 mb-2">${note.content.substring(0, 200)}${note.content.length > 200 ? '...' : ''}</p>
<div class="text-xs text-gray-400">
${note.author_name} · ${new Date(note.created_at).toLocaleDateString('zh-CN')}
</div>
</div>
`).join('') :
'<p class="text-sm text-gray-500">暂无笔记</p>'
}
</div>
</div>
<div id="tab-content-tasks" class="tab-content hidden">
${isOwner ? `
<button onclick="showTaskForm()" class="mb-4 bg-black text-white px-4 py-2 rounded text-sm hover:bg-gray-800">
+ 新建任务
</button>
` : ''}
<div class="space-y-3">
${project.tasks && project.tasks.length > 0 ?
project.tasks.map(task => `
<div class="border border-gray-200 p-4 rounded">
<div class="flex justify-between items-start mb-2">
<div class="flex items-center gap-2">
<input type="checkbox" ${task.status === 'completed' ? 'checked' : ''}
onchange="toggleTask(${task.id}, this.checked)" class="mr-2">
<h5 class="font-bold">${task.title}</h5>
<span class="text-xs px-2 py-1 rounded ${getPriorityColor(task.priority)}">${getPriorityText(task.priority)}</span>
</div>
${isOwner ? `
<div class="flex gap-2">
<button onclick="editTask(${task.id})" class="text-xs text-blue-600">编辑</button>
<button onclick="deleteTask(${task.id})" class="text-xs text-red-600">删除</button>
</div>
` : ''}
</div>
${task.description ? `<p class="text-sm text-gray-600 mb-2">${task.description}</p>` : ''}
<div class="text-xs text-gray-400">
${task.assignee_name || '未分配'} ·
${task.due_date ? new Date(task.due_date).toLocaleDateString('zh-CN') : '无截止日期'} ·
${getStatusText(task.status)}
</div>
</div>
`).join('') :
'<p class="text-sm text-gray-500">暂无任务</p>'
}
</div>
</div>
<div id="tab-content-files" class="tab-content hidden">
${isOwner ? `
<button onclick="showFileUpload()" class="mb-4 bg-black text-white px-4 py-2 rounded text-sm hover:bg-gray-800">
+ 上传文件
</button>
` : ''}
<div class="space-y-3">
${project.files && project.files.length > 0 ?
project.files.map(file => `
<div class="border border-gray-200 p-4 rounded flex justify-between items-center">
<div>
<span class="text-sm font-medium">${file.name}</span>
<span class="text-xs text-gray-400 ml-2">${(file.file_size / 1024 / 1024).toFixed(2)} MB</span>
</div>
<div class="flex gap-2">
<button onclick="window.DAELAPI.research.downloadResearchFile(${project.id}, ${file.id})"
class="text-xs text-blue-600">下载</button>
${isOwner && file.uploader_id === window.currentUser?.id ? `
<button onclick="deleteFile(${project.id}, ${file.id})" class="text-xs text-red-600">删除</button>
` : ''}
</div>
</div>
`).join('') :
'<p class="text-sm text-gray-500">暂无文件</p>'
}
</div>
</div>
${isOwner ? `
<div class="mt-6 flex gap-4">
<button onclick="editProject(${project.id})" class="bg-black text-white px-4 py-2 rounded text-sm hover:bg-gray-800">
编辑项目
</button>
<button onclick="deleteProject(${project.id})" class="bg-red-600 text-white px-4 py-2 rounded text-sm hover:bg-red-700">
删除项目
</button>
</div>
` : ''}
`;
// 初始化标签页切换
initDetailTabs();
modal.classList.remove('hidden');
}
// 初始化详情页标签
function initDetailTabs() {
document.querySelectorAll('#project-detail-modal .tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
const tabName = btn.dataset.tab;
// 更新按钮样式
document.querySelectorAll('#project-detail-modal .tab-btn').forEach(b => {
b.classList.remove('active', 'border-black', 'text-black');
b.classList.add('text-gray-500');
});
btn.classList.add('active', 'border-black', 'text-black');
btn.classList.remove('text-gray-500');
// 更新内容
document.querySelectorAll('#project-detail-modal .tab-content').forEach(content => {
content.classList.add('hidden');
});
document.getElementById(`tab-content-${tabName}`).classList.remove('hidden');
});
});
}
// 关闭项目详情
function closeProjectDetail() {
document.getElementById('project-detail-modal').classList.add('hidden');
currentProjectDetail = null;
}
// 编辑项目
function editProject(projectId) {
closeProjectDetail();
showProjectForm(projectId);
}
// 删除项目
async function deleteProject(projectId) {
if (!confirm('确定要删除这个项目吗?所有相关的笔记、任务和文件也将被删除。')) return;
try {
await window.DAELAPI.research.deleteProject(projectId);
closeProjectDetail();
loadProjects();
} catch (error) {
alert('删除失败: ' + error.message);
}
}
// 笔记管理
function showNoteForm(noteId = null) {
// 简化版:使用prompt创建笔记
const title = prompt('笔记标题:');
if (!title) return;
const content = prompt('笔记内容:');
if (!content) return;
createNote(title, content);
}
async function createNote(title, content) {
try {
await window.DAELAPI.research.createNote(currentProjectDetail.id, {
title: title,
content: content,
note_type: 'note'
});
viewProject(currentProjectDetail.id); // 刷新详情
} catch (error) {
alert('创建失败: ' + error.message);
}
}
async function deleteNote(noteId) {
if (!confirm('确定要删除这条笔记吗?')) return;
try {
await window.DAELAPI.research.deleteNote(noteId);
viewProject(currentProjectDetail.id); // 刷新详情
} catch (error) {
alert('删除失败: ' + error.message);
}
}
// 任务管理
function showTaskForm() {
const title = prompt('任务标题:');
if (!title) return;
const description = prompt('任务描述:');
const dueDate = prompt('截止日期 (YYYY-MM-DD)');
createTask(title, description, dueDate);
}
async function createTask(title, description, dueDate) {
try {
await window.DAELAPI.research.createTask(currentProjectDetail.id, {
title: title,
description: description || '',
due_date: dueDate || null,
status: 'todo',
priority: 'medium'
});
viewProject(currentProjectDetail.id); // 刷新详情
} catch (error) {
alert('创建失败: ' + error.message);
}
}
async function toggleTask(taskId, completed) {
try {
await window.DAELAPI.research.updateTask(taskId, {
status: completed ? 'completed' : 'todo'
});
viewProject(currentProjectDetail.id); // 刷新详情
} catch (error) {
alert('更新失败: ' + error.message);
}
}
async function deleteTask(taskId) {
if (!confirm('确定要删除这个任务吗?')) return;
try {
await window.DAELAPI.research.deleteTask(taskId);
viewProject(currentProjectDetail.id); // 刷新详情
} catch (error) {
alert('删除失败: ' + error.message);
}
}
// 文件管理
function showFileUpload() {
const input = document.createElement('input');
input.type = 'file';
input.multiple = false;
input.onchange = async (e) => {
const file = e.target.files[0];
if (!file) return;
try {
await window.DAELAPI.research.uploadResearchFile(currentProjectDetail.id, file, file.name);
viewProject(currentProjectDetail.id); // 刷新详情
} catch (error) {
alert('上传失败: ' + error.message);
}
};
input.click();
}
async function deleteFile(projectId, fileId) {
if (!confirm('确定要删除这个文件吗?')) return;
try {
await window.DAELAPI.research.deleteResearchFile(projectId, fileId);
viewProject(projectId); // 刷新详情
} catch (error) {
alert('删除失败: ' + error.message);
}
}
// 优先级颜色
function getPriorityColor(priority) {
const colors = {
'high': 'bg-red-100 text-red-800',
'medium': 'bg-yellow-100 text-yellow-800',
'low': 'bg-blue-100 text-blue-800'
};
return colors[priority] || 'bg-gray-100 text-gray-800';
}
// 优先级文本
function getPriorityText(priority) {
const texts = {
'high': '高',
'medium': '中',
'low': '低'
};
return texts[priority] || priority;
}
// 筛选器切换
document.querySelectorAll('.filter-tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.filter-tab').forEach(t => {
t.classList.remove('active', 'border-black', 'text-black');
t.classList.add('text-gray-500');
});
tab.classList.add('active', 'border-black', 'text-black');
tab.classList.remove('text-gray-500');
currentFilter = tab.dataset.filter;
displayProjects(currentProjects);
});
});
// 初始化
document.addEventListener('DOMContentLoaded', async () => {
if (await checkLogin()) {
// 获取当前用户信息
try {
const authResponse = await window.DAELAPI.auth.checkAuth();
window.currentUser = authResponse.user;
} catch (error) {
console.error('Failed to get user info:', error);
}
loadProjects();
// 退出按钮
document.getElementById('logout-btn').addEventListener('click', async () => {
await logout();
});
}
});