a3eb5c7b2d
从百度网盘 /apps/bypy/dofile/ 下载的完整项目结构,包含: - dofile/: Flask 后端(端口 5000)+ 静态前端(端口 6002) - officefile/: 项目相关文档 - .gitignore: Python venv / 日志 / 缓存 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
411 lines
13 KiB
Python
411 lines
13 KiB
Python
from flask import Blueprint, request, jsonify, send_from_directory, current_app
|
|
from flask_login import login_required, current_user
|
|
from werkzeug.utils import secure_filename
|
|
import sys
|
|
import os
|
|
from datetime import datetime, date
|
|
|
|
# 添加父目录到路径
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
from models import db, ResearchProject, ResearchNote, ResearchTask, ResearchFile, User
|
|
|
|
bp = Blueprint('research', __name__)
|
|
|
|
def allowed_file(filename, allowed_extensions):
|
|
"""检查文件扩展名是否允许"""
|
|
return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions
|
|
|
|
# 研究项目管理
|
|
@bp.route('/projects', methods=['GET'])
|
|
@login_required
|
|
def get_projects():
|
|
"""获取研究项目列表(实验室成员可查看所有)"""
|
|
status = request.args.get('status')
|
|
owner_id = request.args.get('owner_id') # 可选:筛选特定用户的项目
|
|
|
|
query = ResearchProject.query
|
|
|
|
if status:
|
|
query = query.filter_by(status=status)
|
|
if owner_id:
|
|
query = query.filter_by(owner_id=owner_id)
|
|
|
|
projects = query.order_by(ResearchProject.created_at.desc()).all()
|
|
|
|
return jsonify({
|
|
'projects': [project.to_dict() for project in projects]
|
|
}), 200
|
|
|
|
@bp.route('/projects/<int:project_id>', methods=['GET'])
|
|
@login_required
|
|
def get_project(project_id):
|
|
"""获取单个研究项目详情"""
|
|
project = ResearchProject.query.get_or_404(project_id)
|
|
|
|
# 获取笔记、任务、文件
|
|
notes = ResearchNote.query.filter_by(project_id=project_id).order_by(ResearchNote.created_at.desc()).all()
|
|
tasks = ResearchTask.query.filter_by(project_id=project_id).order_by(ResearchTask.created_at.desc()).all()
|
|
files = ResearchFile.query.filter_by(project_id=project_id).order_by(ResearchFile.uploaded_at.desc()).all()
|
|
|
|
result = project.to_dict()
|
|
result['notes'] = [note.to_dict() for note in notes]
|
|
result['tasks'] = [task.to_dict() for task in tasks]
|
|
result['files'] = [f.to_dict() for f in files]
|
|
|
|
return jsonify({'project': result}), 200
|
|
|
|
@bp.route('/projects', methods=['POST'])
|
|
@login_required
|
|
def create_project():
|
|
"""创建研究项目(需要登录)"""
|
|
data = request.get_json()
|
|
|
|
start_date = datetime.strptime(data['start_date'], '%Y-%m-%d').date() if data.get('start_date') else None
|
|
end_date = datetime.strptime(data['end_date'], '%Y-%m-%d').date() if data.get('end_date') else None
|
|
tags_str = ','.join(data.get('tags', [])) if isinstance(data.get('tags'), list) else data.get('tags', '')
|
|
|
|
project = ResearchProject(
|
|
title=data.get('title'),
|
|
description=data.get('description'),
|
|
start_date=start_date,
|
|
end_date=end_date,
|
|
owner_id=current_user.id,
|
|
status=data.get('status', 'active'),
|
|
progress=data.get('progress', 0),
|
|
tags=tags_str
|
|
)
|
|
|
|
db.session.add(project)
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'message': 'Project created successfully',
|
|
'project': project.to_dict()
|
|
}), 201
|
|
|
|
@bp.route('/projects/<int:project_id>', methods=['PUT'])
|
|
@login_required
|
|
def update_project(project_id):
|
|
"""更新研究项目(只有创建者可以编辑)"""
|
|
project = ResearchProject.query.get_or_404(project_id)
|
|
|
|
# 检查权限
|
|
if project.owner_id != current_user.id:
|
|
return jsonify({'error': 'Only the owner can edit this project'}), 403
|
|
|
|
data = request.get_json()
|
|
|
|
if 'title' in data:
|
|
project.title = data['title']
|
|
if 'description' in data:
|
|
project.description = data['description']
|
|
if 'start_date' in data:
|
|
project.start_date = datetime.strptime(data['start_date'], '%Y-%m-%d').date() if data['start_date'] else None
|
|
if 'end_date' in data:
|
|
project.end_date = datetime.strptime(data['end_date'], '%Y-%m-%d').date() if data['end_date'] else None
|
|
if 'status' in data:
|
|
project.status = data['status']
|
|
if 'progress' in data:
|
|
project.progress = data['progress']
|
|
if 'tags' in data:
|
|
tags_str = ','.join(data['tags']) if isinstance(data['tags'], list) else data['tags']
|
|
project.tags = tags_str
|
|
|
|
project.updated_at = datetime.utcnow()
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'message': 'Project updated successfully',
|
|
'project': project.to_dict()
|
|
}), 200
|
|
|
|
@bp.route('/projects/<int:project_id>', methods=['DELETE'])
|
|
@login_required
|
|
def delete_project(project_id):
|
|
"""删除研究项目(只有创建者可以删除)"""
|
|
project = ResearchProject.query.get_or_404(project_id)
|
|
|
|
# 检查权限
|
|
if project.owner_id != current_user.id:
|
|
return jsonify({'error': 'Only the owner can delete this project'}), 403
|
|
|
|
db.session.delete(project)
|
|
db.session.commit()
|
|
|
|
return jsonify({'message': 'Project deleted successfully'}), 200
|
|
|
|
# 研究笔记管理
|
|
@bp.route('/projects/<int:project_id>/notes', methods=['GET'])
|
|
@login_required
|
|
def get_notes(project_id):
|
|
"""获取项目笔记列表"""
|
|
note_type = request.args.get('type')
|
|
|
|
query = ResearchNote.query.filter_by(project_id=project_id)
|
|
|
|
if note_type:
|
|
query = query.filter_by(note_type=note_type)
|
|
|
|
notes = query.order_by(ResearchNote.created_at.desc()).all()
|
|
|
|
return jsonify({
|
|
'notes': [note.to_dict() for note in notes]
|
|
}), 200
|
|
|
|
@bp.route('/projects/<int:project_id>/notes', methods=['POST'])
|
|
@login_required
|
|
def create_note(project_id):
|
|
"""创建研究笔记(需要登录)"""
|
|
project = ResearchProject.query.get_or_404(project_id)
|
|
|
|
data = request.get_json()
|
|
|
|
note = ResearchNote(
|
|
project_id=project_id,
|
|
title=data.get('title'),
|
|
content=data.get('content'),
|
|
author_id=current_user.id,
|
|
note_type=data.get('note_type', 'note')
|
|
)
|
|
|
|
db.session.add(note)
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'message': 'Note created successfully',
|
|
'note': note.to_dict()
|
|
}), 201
|
|
|
|
@bp.route('/notes/<int:note_id>', methods=['PUT'])
|
|
@login_required
|
|
def update_note(note_id):
|
|
"""更新研究笔记(只有创建者可以编辑)"""
|
|
note = ResearchNote.query.get_or_404(note_id)
|
|
|
|
# 检查权限
|
|
if note.author_id != current_user.id:
|
|
return jsonify({'error': 'Only the author can edit this note'}), 403
|
|
|
|
data = request.get_json()
|
|
|
|
if 'title' in data:
|
|
note.title = data['title']
|
|
if 'content' in data:
|
|
note.content = data['content']
|
|
if 'note_type' in data:
|
|
note.note_type = data['note_type']
|
|
|
|
note.updated_at = datetime.utcnow()
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'message': 'Note updated successfully',
|
|
'note': note.to_dict()
|
|
}), 200
|
|
|
|
@bp.route('/notes/<int:note_id>', methods=['DELETE'])
|
|
@login_required
|
|
def delete_note(note_id):
|
|
"""删除研究笔记(只有创建者可以删除)"""
|
|
note = ResearchNote.query.get_or_404(note_id)
|
|
|
|
# 检查权限
|
|
if note.author_id != current_user.id:
|
|
return jsonify({'error': 'Only the author can delete this note'}), 403
|
|
|
|
db.session.delete(note)
|
|
db.session.commit()
|
|
|
|
return jsonify({'message': 'Note deleted successfully'}), 200
|
|
|
|
# 研究任务管理
|
|
@bp.route('/projects/<int:project_id>/tasks', methods=['GET'])
|
|
@login_required
|
|
def get_tasks(project_id):
|
|
"""获取项目任务列表"""
|
|
status = request.args.get('status')
|
|
assignee_id = request.args.get('assignee_id')
|
|
|
|
query = ResearchTask.query.filter_by(project_id=project_id)
|
|
|
|
if status:
|
|
query = query.filter_by(status=status)
|
|
if assignee_id:
|
|
query = query.filter_by(assignee_id=assignee_id)
|
|
|
|
tasks = query.order_by(ResearchTask.due_date, ResearchTask.priority).all()
|
|
|
|
return jsonify({
|
|
'tasks': [task.to_dict() for task in tasks]
|
|
}), 200
|
|
|
|
@bp.route('/projects/<int:project_id>/tasks', methods=['POST'])
|
|
@login_required
|
|
def create_task(project_id):
|
|
"""创建研究任务(项目创建者可以创建)"""
|
|
project = ResearchProject.query.get_or_404(project_id)
|
|
|
|
# 只有项目创建者可以创建任务
|
|
if project.owner_id != current_user.id:
|
|
return jsonify({'error': 'Only the project owner can create tasks'}), 403
|
|
|
|
data = request.get_json()
|
|
|
|
due_date = datetime.strptime(data['due_date'], '%Y-%m-%d').date() if data.get('due_date') else None
|
|
|
|
task = ResearchTask(
|
|
project_id=project_id,
|
|
title=data.get('title'),
|
|
description=data.get('description'),
|
|
assignee_id=data.get('assignee_id'),
|
|
due_date=due_date,
|
|
status=data.get('status', 'todo'),
|
|
priority=data.get('priority', 'medium')
|
|
)
|
|
|
|
db.session.add(task)
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'message': 'Task created successfully',
|
|
'task': task.to_dict()
|
|
}), 201
|
|
|
|
@bp.route('/tasks/<int:task_id>', methods=['PUT'])
|
|
@login_required
|
|
def update_task(task_id):
|
|
"""更新研究任务(项目创建者或任务分配者可以编辑)"""
|
|
task = ResearchTask.query.get_or_404(task_id)
|
|
project = ResearchProject.query.get(task.project_id)
|
|
|
|
# 检查权限:项目创建者或任务分配者
|
|
if project.owner_id != current_user.id and task.assignee_id != current_user.id:
|
|
return jsonify({'error': 'Only the project owner or assignee can edit this task'}), 403
|
|
|
|
data = request.get_json()
|
|
|
|
if 'title' in data:
|
|
task.title = data['title']
|
|
if 'description' in data:
|
|
task.description = data['description']
|
|
if 'assignee_id' in data:
|
|
task.assignee_id = data['assignee_id']
|
|
if 'due_date' in data:
|
|
task.due_date = datetime.strptime(data['due_date'], '%Y-%m-%d').date() if data['due_date'] else None
|
|
if 'status' in data:
|
|
task.status = data['status']
|
|
if 'priority' in data:
|
|
task.priority = data['priority']
|
|
|
|
task.updated_at = datetime.utcnow()
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'message': 'Task updated successfully',
|
|
'task': task.to_dict()
|
|
}), 200
|
|
|
|
@bp.route('/tasks/<int:task_id>', methods=['DELETE'])
|
|
@login_required
|
|
def delete_task(task_id):
|
|
"""删除研究任务(只有项目创建者可以删除)"""
|
|
task = ResearchTask.query.get_or_404(task_id)
|
|
project = ResearchProject.query.get(task.project_id)
|
|
|
|
# 检查权限
|
|
if project.owner_id != current_user.id:
|
|
return jsonify({'error': 'Only the project owner can delete this task'}), 403
|
|
|
|
db.session.delete(task)
|
|
db.session.commit()
|
|
|
|
return jsonify({'message': 'Task deleted successfully'}), 200
|
|
|
|
# 研究文件管理
|
|
@bp.route('/projects/<int:project_id>/files', methods=['POST'])
|
|
@login_required
|
|
def upload_research_file(project_id):
|
|
"""上传研究文件(需要登录)"""
|
|
project = ResearchProject.query.get_or_404(project_id)
|
|
|
|
if 'file' not in request.files:
|
|
return jsonify({'error': 'No file provided'}), 400
|
|
|
|
file = request.files['file']
|
|
if file.filename == '':
|
|
return jsonify({'error': 'No file selected'}), 400
|
|
|
|
if file and allowed_file(file.filename, {'pdf', 'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'zip', 'rar', 'txt', 'csv', 'json', 'py', 'ipynb'}):
|
|
filename = secure_filename(file.filename)
|
|
uploads_dir = current_app.config['UPLOAD_FOLDER']
|
|
project_dir = os.path.join(uploads_dir, 'research', str(project_id))
|
|
os.makedirs(project_dir, exist_ok=True)
|
|
|
|
filepath = os.path.join(project_dir, filename)
|
|
file.save(filepath)
|
|
|
|
research_file = ResearchFile(
|
|
project_id=project_id,
|
|
name=request.form.get('name', filename),
|
|
file_path=f'/uploads/research/{project_id}/{filename}',
|
|
file_size=os.path.getsize(filepath),
|
|
file_type=filename.rsplit('.', 1)[1].lower(),
|
|
uploader_id=current_user.id
|
|
)
|
|
|
|
db.session.add(research_file)
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'message': 'File uploaded successfully',
|
|
'file': research_file.to_dict()
|
|
}), 201
|
|
|
|
return jsonify({'error': 'Invalid file type'}), 400
|
|
|
|
@bp.route('/projects/<int:project_id>/files/<int:file_id>', methods=['GET'])
|
|
@login_required
|
|
def download_research_file(project_id, file_id):
|
|
"""下载研究文件"""
|
|
research_file = ResearchFile.query.filter_by(
|
|
project_id=project_id,
|
|
id=file_id
|
|
).first_or_404()
|
|
|
|
uploads_dir = current_app.config['UPLOAD_FOLDER']
|
|
file_path = os.path.join(uploads_dir, 'research', str(project_id), os.path.basename(research_file.file_path))
|
|
|
|
return send_from_directory(
|
|
os.path.dirname(file_path),
|
|
os.path.basename(file_path),
|
|
as_attachment=True
|
|
)
|
|
|
|
@bp.route('/projects/<int:project_id>/files/<int:file_id>', methods=['DELETE'])
|
|
@login_required
|
|
def delete_research_file(project_id, file_id):
|
|
"""删除研究文件(只有上传者可以删除)"""
|
|
research_file = ResearchFile.query.filter_by(
|
|
project_id=project_id,
|
|
id=file_id
|
|
).first_or_404()
|
|
|
|
# 检查权限
|
|
if research_file.uploader_id != current_user.id:
|
|
return jsonify({'error': 'Only the uploader can delete this file'}), 403
|
|
|
|
db.session.delete(research_file)
|
|
db.session.commit()
|
|
|
|
return jsonify({'message': 'File deleted successfully'}), 200
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|