init: TEALab 2025 实验室网站项目

从百度网盘 /apps/bypy/dofile/ 下载的完整项目结构,包含:
- dofile/: Flask 后端(端口 5000)+ 静态前端(端口 6002)
- officefile/: 项目相关文档
- .gitignore: Python venv / 日志 / 缓存

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 01:07:19 +08:00
commit a3eb5c7b2d
39 changed files with 7110 additions and 0 deletions
+573
View File
@@ -0,0 +1,573 @@
// 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();
});
}
});