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
+11
View File
@@ -0,0 +1,11 @@
# Routes package
+59
View File
@@ -0,0 +1,59 @@
from flask import Blueprint, request, jsonify, send_from_directory
from flask_login import login_required, current_user
import sys
import os
# 添加父目录到路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from models import db, Resource
bp = Blueprint('api', __name__)
@bp.route('/download/<int:resource_id>', methods=['GET'])
def download_resource(resource_id):
"""下载资源文件"""
resource = Resource.query.get_or_404(resource_id)
# 检查是否需要登录
if resource.is_protected and not current_user.is_authenticated:
return jsonify({'error': 'Authentication required'}), 401
# 增加下载计数
resource.download_count += 1
db.session.commit()
# 返回文件
uploads_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'uploads')
filename = os.path.basename(resource.file_path)
return send_from_directory(uploads_dir, filename, as_attachment=True)
@bp.route('/stats', methods=['GET'])
@login_required
def get_stats():
"""获取统计数据(需要登录)"""
from models import Article, Resource, TeamMember
stats = {
'articles': Article.query.count(),
'resources': Resource.query.count(),
'team_members': TeamMember.query.count(),
'total_downloads': db.session.query(db.func.sum(Resource.download_count)).scalar() or 0
}
return jsonify({'stats': stats}), 200
@bp.route('/protected-content', methods=['GET'])
@login_required
def get_protected_content():
"""获取受保护内容(需要登录)"""
from models import Article, Resource
protected_articles = Article.query.filter_by(is_protected=True, published=True).all()
protected_resources = Resource.query.filter_by(is_protected=True).all()
return jsonify({
'articles': [article.to_dict() for article in protected_articles],
'resources': [resource.to_dict() for resource in protected_resources]
}), 200
+87
View File
@@ -0,0 +1,87 @@
from flask import Blueprint, request, jsonify
from flask_login import login_user, logout_user, login_required, current_user
import sys
import os
# 添加父目录到路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from models import db, User
from werkzeug.security import check_password_hash
bp = Blueprint('auth', __name__)
@bp.route('/register', methods=['POST'])
def register():
"""用户注册"""
data = request.get_json()
if not data or not data.get('username') or not data.get('email') or not data.get('password'):
return jsonify({'error': 'Missing required fields'}), 400
# 检查用户名是否已存在
if User.query.filter_by(username=data['username']).first():
return jsonify({'error': 'Username already exists'}), 400
# 检查邮箱是否已存在
if User.query.filter_by(email=data['email']).first():
return jsonify({'error': 'Email already exists'}), 400
# 创建新用户
user = User(
username=data['username'],
email=data['email'],
is_admin=data.get('is_admin', False)
)
user.set_password(data['password'])
db.session.add(user)
db.session.commit()
return jsonify({
'message': 'User registered successfully',
'user': user.to_dict()
}), 201
@bp.route('/login', methods=['POST'])
def login():
"""用户登录"""
data = request.get_json()
if not data or not data.get('username') or not data.get('password'):
return jsonify({'error': 'Username and password required'}), 400
user = User.query.filter_by(username=data['username']).first()
if user and user.check_password(data['password']):
login_user(user, remember=data.get('remember', False))
return jsonify({
'message': 'Login successful',
'user': user.to_dict()
}), 200
else:
return jsonify({'error': 'Invalid username or password'}), 401
@bp.route('/logout', methods=['POST'])
@login_required
def logout():
"""用户登出"""
logout_user()
return jsonify({'message': 'Logout successful'}), 200
@bp.route('/me', methods=['GET'])
@login_required
def get_current_user():
"""获取当前登录用户信息"""
return jsonify({'user': current_user.to_dict()}), 200
@bp.route('/check', methods=['GET'])
def check_auth():
"""检查认证状态"""
if current_user.is_authenticated:
return jsonify({
'authenticated': True,
'user': current_user.to_dict()
}), 200
else:
return jsonify({'authenticated': False}), 200
+227
View File
@@ -0,0 +1,227 @@
from flask import Blueprint, request, jsonify, send_file, current_app
from flask_login import login_required, current_user
from werkzeug.utils import secure_filename
import sys
import os
# 添加父目录到路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from models import db, Article, Resource, TeamMember
from datetime import datetime
bp = Blueprint('cms', __name__)
def allowed_file(filename, allowed_extensions):
"""检查文件扩展名是否允许"""
return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions
# 文章管理
@bp.route('/articles', methods=['GET'])
def get_articles():
"""获取文章列表"""
category = request.args.get('category', 'news')
published_only = request.args.get('published_only', 'true').lower() == 'true'
query = Article.query.filter_by(category=category)
if published_only:
query = query.filter_by(published=True)
# 如果未登录,过滤受保护内容
if not current_user.is_authenticated:
query = query.filter_by(is_protected=False)
articles = query.order_by(Article.created_at.desc()).all()
return jsonify({
'articles': [article.to_dict() for article in articles]
}), 200
@bp.route('/articles/<int:article_id>', methods=['GET'])
def get_article(article_id):
"""获取单篇文章"""
article = Article.query.get_or_404(article_id)
# 检查是否需要登录
if article.is_protected and not current_user.is_authenticated:
return jsonify({'error': 'Authentication required'}), 401
if not article.published:
if not current_user.is_authenticated or not current_user.is_admin:
return jsonify({'error': 'Article not found'}), 404
return jsonify({'article': article.to_dict()}), 200
@bp.route('/articles', methods=['POST'])
@login_required
def create_article():
"""创建文章(需要管理员权限)"""
if not current_user.is_admin:
return jsonify({'error': 'Admin access required'}), 403
data = request.get_json()
article = Article(
title=data.get('title'),
content=data.get('content'),
summary=data.get('summary'),
category=data.get('category', 'news'),
is_protected=data.get('is_protected', False),
author_id=current_user.id,
published=data.get('published', True)
)
db.session.add(article)
db.session.commit()
return jsonify({
'message': 'Article created successfully',
'article': article.to_dict()
}), 201
@bp.route('/articles/<int:article_id>', methods=['PUT'])
@login_required
def update_article(article_id):
"""更新文章(需要管理员权限)"""
if not current_user.is_admin:
return jsonify({'error': 'Admin access required'}), 403
article = Article.query.get_or_404(article_id)
data = request.get_json()
article.title = data.get('title', article.title)
article.content = data.get('content', article.content)
article.summary = data.get('summary', article.summary)
article.category = data.get('category', article.category)
article.is_protected = data.get('is_protected', article.is_protected)
article.published = data.get('published', article.published)
article.updated_at = datetime.utcnow()
db.session.commit()
return jsonify({
'message': 'Article updated successfully',
'article': article.to_dict()
}), 200
@bp.route('/articles/<int:article_id>', methods=['DELETE'])
@login_required
def delete_article(article_id):
"""删除文章(需要管理员权限)"""
if not current_user.is_admin:
return jsonify({'error': 'Admin access required'}), 403
article = Article.query.get_or_404(article_id)
db.session.delete(article)
db.session.commit()
return jsonify({'message': 'Article deleted successfully'}), 200
# 资源管理
@bp.route('/resources', methods=['GET'])
def get_resources():
"""获取资源列表"""
category = request.args.get('category')
query = Resource.query
if category:
query = query.filter_by(category=category)
# 如果未登录,过滤受保护资源
if not current_user.is_authenticated:
query = query.filter_by(is_protected=False)
resources = query.order_by(Resource.created_at.desc()).all()
return jsonify({
'resources': [resource.to_dict() for resource in resources]
}), 200
@bp.route('/resources', methods=['POST'])
@login_required
def upload_resource():
"""上传资源文件(需要管理员权限)"""
if not current_user.is_admin:
return jsonify({'error': 'Admin access required'}), 403
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', 'zip', 'rar', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'}):
filename = secure_filename(file.filename)
uploads_dir = current_app.config['UPLOAD_FOLDER']
os.makedirs(uploads_dir, exist_ok=True)
filepath = os.path.join(uploads_dir, filename)
file.save(filepath)
resource = Resource(
name=request.form.get('name', filename),
description=request.form.get('description', ''),
file_path=f'/uploads/{filename}',
file_size=os.path.getsize(filepath),
file_type=filename.rsplit('.', 1)[1].lower(),
category=request.form.get('category', 'document'),
is_protected=request.form.get('is_protected', 'false').lower() == 'true'
)
db.session.add(resource)
db.session.commit()
return jsonify({
'message': 'Resource uploaded successfully',
'resource': resource.to_dict()
}), 201
return jsonify({'error': 'Invalid file type'}), 400
# 团队成员管理
@bp.route('/team', methods=['GET'])
def get_team():
"""获取团队成员列表"""
role = request.args.get('role')
query = TeamMember.query
if role:
query = query.filter_by(role=role)
members = query.order_by(TeamMember.order, TeamMember.created_at).all()
return jsonify({
'members': [member.to_dict() for member in members]
}), 200
@bp.route('/team', methods=['POST'])
@login_required
def create_team_member():
"""创建团队成员(需要管理员权限)"""
if not current_user.is_admin:
return jsonify({'error': 'Admin access required'}), 403
data = request.get_json()
member = TeamMember(
name=data.get('name'),
title=data.get('title'),
title_en=data.get('title_en'),
bio=data.get('bio'),
bio_en=data.get('bio_en'),
role=data.get('role', 'student'),
email=data.get('email'),
website=data.get('website'),
order=data.get('order', 0)
)
db.session.add(member)
db.session.commit()
return jsonify({
'message': 'Team member created successfully',
'member': member.to_dict()
}), 201
+256
View File
@@ -0,0 +1,256 @@
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, time
# 添加父目录到路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from models import db, Meeting, MeetingRegistration, MeetingFile, User
bp = Blueprint('meetings', __name__)
def allowed_file(filename, allowed_extensions):
"""检查文件扩展名是否允许"""
return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions
@bp.route('/meetings', methods=['GET'])
@login_required
def get_meetings():
"""获取组会列表(实验室成员可查看所有)"""
status = request.args.get('status')
meeting_type = request.args.get('type')
query = Meeting.query
if status:
query = query.filter_by(status=status)
if meeting_type:
query = query.filter_by(meeting_type=meeting_type)
meetings = query.order_by(Meeting.date.desc(), Meeting.time.desc()).all()
return jsonify({
'meetings': [meeting.to_dict() for meeting in meetings]
}), 200
@bp.route('/meetings/<int:meeting_id>', methods=['GET'])
@login_required
def get_meeting(meeting_id):
"""获取单个组会详情"""
meeting = Meeting.query.get_or_404(meeting_id)
# 获取参会人员列表
registrations = MeetingRegistration.query.filter_by(meeting_id=meeting_id).all()
attendees = [reg.to_dict() for reg in registrations]
# 获取文件列表
files = MeetingFile.query.filter_by(meeting_id=meeting_id).all()
file_list = [f.to_dict() for f in files]
result = meeting.to_dict()
result['attendees'] = attendees
result['files'] = file_list
return jsonify({'meeting': result}), 200
@bp.route('/meetings', methods=['POST'])
@login_required
def create_meeting():
"""创建组会(需要登录)"""
data = request.get_json()
# 解析日期和时间
meeting_date = datetime.strptime(data['date'], '%Y-%m-%d').date() if data.get('date') else None
meeting_time = datetime.strptime(data['time'], '%H:%M').time() if data.get('time') else None
meeting = Meeting(
title=data.get('title'),
description=data.get('description'),
date=meeting_date,
time=meeting_time,
location=data.get('location'),
organizer_id=current_user.id,
status=data.get('status', 'planned'),
meeting_type=data.get('meeting_type', 'weekly')
)
db.session.add(meeting)
db.session.commit()
return jsonify({
'message': 'Meeting created successfully',
'meeting': meeting.to_dict()
}), 201
@bp.route('/meetings/<int:meeting_id>', methods=['PUT'])
@login_required
def update_meeting(meeting_id):
"""更新组会(只有创建者可以编辑)"""
meeting = Meeting.query.get_or_404(meeting_id)
# 检查权限:只有创建者可以编辑
if meeting.organizer_id != current_user.id:
return jsonify({'error': 'Only the organizer can edit this meeting'}), 403
data = request.get_json()
if 'title' in data:
meeting.title = data['title']
if 'description' in data:
meeting.description = data['description']
if 'date' in data:
meeting.date = datetime.strptime(data['date'], '%Y-%m-%d').date()
if 'time' in data:
meeting.time = datetime.strptime(data['time'], '%H:%M').time()
if 'location' in data:
meeting.location = data['location']
if 'status' in data:
meeting.status = data['status']
if 'meeting_type' in data:
meeting.meeting_type = data['meeting_type']
if 'meeting_notes' in data:
meeting.meeting_notes = data['meeting_notes']
meeting.updated_at = datetime.utcnow()
db.session.commit()
return jsonify({
'message': 'Meeting updated successfully',
'meeting': meeting.to_dict()
}), 200
@bp.route('/meetings/<int:meeting_id>', methods=['DELETE'])
@login_required
def delete_meeting(meeting_id):
"""删除组会(只有创建者可以删除)"""
meeting = Meeting.query.get_or_404(meeting_id)
# 检查权限
if meeting.organizer_id != current_user.id:
return jsonify({'error': 'Only the organizer can delete this meeting'}), 403
db.session.delete(meeting)
db.session.commit()
return jsonify({'message': 'Meeting deleted successfully'}), 200
@bp.route('/meetings/<int:meeting_id>/register', methods=['POST'])
@login_required
def register_meeting(meeting_id):
"""报名参加组会"""
meeting = Meeting.query.get_or_404(meeting_id)
# 检查是否已报名
existing = MeetingRegistration.query.filter_by(
meeting_id=meeting_id,
user_id=current_user.id
).first()
if existing:
return jsonify({'error': 'Already registered'}), 400
registration = MeetingRegistration(
meeting_id=meeting_id,
user_id=current_user.id,
status='registered'
)
db.session.add(registration)
db.session.commit()
return jsonify({
'message': 'Registered successfully',
'registration': registration.to_dict()
}), 201
@bp.route('/meetings/<int:meeting_id>/checkin', methods=['POST'])
@login_required
def checkin_meeting(meeting_id):
"""签到组会"""
registration = MeetingRegistration.query.filter_by(
meeting_id=meeting_id,
user_id=current_user.id
).first_or_404()
registration.status = 'checked_in'
db.session.commit()
return jsonify({
'message': 'Checked in successfully',
'registration': registration.to_dict()
}), 200
@bp.route('/meetings/<int:meeting_id>/files', methods=['POST'])
@login_required
def upload_meeting_file(meeting_id):
"""上传组会文件(只有创建者可以上传)"""
meeting = Meeting.query.get_or_404(meeting_id)
# 检查权限
if meeting.organizer_id != current_user.id:
return jsonify({'error': 'Only the organizer can upload files'}), 403
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'}):
filename = secure_filename(file.filename)
uploads_dir = current_app.config['UPLOAD_FOLDER']
meeting_dir = os.path.join(uploads_dir, 'meetings', str(meeting_id))
os.makedirs(meeting_dir, exist_ok=True)
filepath = os.path.join(meeting_dir, filename)
file.save(filepath)
meeting_file = MeetingFile(
meeting_id=meeting_id,
name=request.form.get('name', filename),
file_path=f'/uploads/meetings/{meeting_id}/{filename}',
file_size=os.path.getsize(filepath),
file_type=filename.rsplit('.', 1)[1].lower(),
uploader_id=current_user.id
)
db.session.add(meeting_file)
db.session.commit()
return jsonify({
'message': 'File uploaded successfully',
'file': meeting_file.to_dict()
}), 201
return jsonify({'error': 'Invalid file type'}), 400
@bp.route('/meetings/<int:meeting_id>/files/<int:file_id>', methods=['GET'])
@login_required
def download_meeting_file(meeting_id, file_id):
"""下载组会文件"""
meeting_file = MeetingFile.query.filter_by(
meeting_id=meeting_id,
id=file_id
).first_or_404()
uploads_dir = current_app.config['UPLOAD_FOLDER']
file_path = os.path.join(uploads_dir, 'meetings', str(meeting_id), os.path.basename(meeting_file.file_path))
return send_from_directory(
os.path.dirname(file_path),
os.path.basename(file_path),
as_attachment=True
)
+410
View File
@@ -0,0 +1,410 @@
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