a3eb5c7b2d
从百度网盘 /apps/bypy/dofile/ 下载的完整项目结构,包含: - dofile/: Flask 后端(端口 5000)+ 静态前端(端口 6002) - officefile/: 项目相关文档 - .gitignore: Python venv / 日志 / 缓存 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
573 lines
23 KiB
JavaScript
573 lines
23 KiB
JavaScript
// 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();
|
||
});
|
||
}
|
||
}); |