Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
Binary file added __pycache__/config.cpython-314.pyc
Binary file not shown.
36 changes: 36 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from config import Config

db = SQLAlchemy()
login_manager = LoginManager()

def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)

db.init_app(app)
login_manager.init_app(app)
login_manager.login_view = 'auth.login'
login_manager.login_message = '请先登录后再访问此页面'

from app.routes.main import main as main_blueprint
app.register_blueprint(main_blueprint)

from app.routes.auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint, url_prefix='/auth')

from app.routes.goals import goals as goals_blueprint
app.register_blueprint(goals_blueprint, url_prefix='/goals')

from app.routes.tasks import tasks as tasks_blueprint
app.register_blueprint(tasks_blueprint, url_prefix='/tasks')

from app.routes.reports import reports as reports_blueprint
app.register_blueprint(reports_blueprint, url_prefix='/reports')

with app.app_context():
db.create_all()

return app
Binary file added app/__pycache__/__init__.cpython-314.pyc
Binary file not shown.
Binary file added app/__pycache__/models.cpython-314.pyc
Binary file not shown.
102 changes: 102 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from datetime import datetime
from app import db, login_manager
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True, nullable=False)
email = db.Column(db.String(120), index=True, unique=True, nullable=False)
password_hash = db.Column(db.String(256), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)

goals = db.relationship('LearningGoal', backref='user', lazy='dynamic')

def set_password(self, password):
self.password_hash = generate_password_hash(password)

def check_password(self, password):
return check_password_hash(self.password_hash, password)

def __repr__(self):
return f'<User {self.username}>'

class LearningGoal(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
description = db.Column(db.Text)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
start_date = db.Column(db.DateTime, default=datetime.utcnow)
end_date = db.Column(db.DateTime)
status = db.Column(db.String(20), default='active')
created_at = db.Column(db.DateTime, default=datetime.utcnow)

tasks = db.relationship('LearningTask', backref='goal', lazy='dynamic', cascade='all, delete-orphan')

def __repr__(self):
return f'<LearningGoal {self.title}>'

class LearningTask(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
description = db.Column(db.Text)
goal_id = db.Column(db.Integer, db.ForeignKey('learning_goal.id'), nullable=False)
parent_task_id = db.Column(db.Integer, db.ForeignKey('learning_task.id'))
status = db.Column(db.String(20), default='pending')
progress = db.Column(db.Integer, default=0)
priority = db.Column(db.String(20), default='medium')
estimated_hours = db.Column(db.Float, default=0)
actual_hours = db.Column(db.Float, default=0)
deadline = db.Column(db.DateTime)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
completed_at = db.Column(db.DateTime)

parent_task = db.relationship('LearningTask', remote_side=[id], backref='subtasks')
study_records = db.relationship('StudyRecord', backref='task', lazy='dynamic', cascade='all, delete-orphan')
reminders = db.relationship('TaskReminder', backref='task', lazy='dynamic', cascade='all, delete-orphan')

def __repr__(self):
return f'<LearningTask {self.title}>'

class StudyRecord(db.Model):
id = db.Column(db.Integer, primary_key=True)
task_id = db.Column(db.Integer, db.ForeignKey('learning_task.id'), nullable=False)
start_time = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
end_time = db.Column(db.DateTime)
duration_minutes = db.Column(db.Integer, default=0)
notes = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.utcnow)

def __repr__(self):
return f'<StudyRecord {self.id}>'

class TaskReminder(db.Model):
id = db.Column(db.Integer, primary_key=True)
task_id = db.Column(db.Integer, db.ForeignKey('learning_task.id'), nullable=False)
reminder_time = db.Column(db.DateTime, nullable=False)
message = db.Column(db.String(200))
is_sent = db.Column(db.Boolean, default=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)

def __repr__(self):
return f'<TaskReminder {self.id}>'

class StudyReport(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
report_type = db.Column(db.String(20), nullable=False)
start_date = db.Column(db.DateTime, nullable=False)
end_date = db.Column(db.DateTime, nullable=False)
total_study_hours = db.Column(db.Float, default=0)
completed_tasks = db.Column(db.Integer, default=0)
total_tasks = db.Column(db.Integer, default=0)
goals_progress = db.Column(db.Text)
recommendations = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.utcnow)

def __repr__(self):
return f'<StudyReport {self.id}>'

@login_manager.user_loader
def load_user(id):
return User.query.get(int(id))
7 changes: 7 additions & 0 deletions app/routes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from flask import Blueprint

main = Blueprint('main', __name__)
auth = Blueprint('auth', __name__)
goals = Blueprint('goals', __name__)
tasks = Blueprint('tasks', __name__)
reports = Blueprint('reports', __name__)
Binary file added app/routes/__pycache__/__init__.cpython-314.pyc
Binary file not shown.
Binary file added app/routes/__pycache__/auth.cpython-314.pyc
Binary file not shown.
Binary file added app/routes/__pycache__/goals.cpython-314.pyc
Binary file not shown.
Binary file added app/routes/__pycache__/main.cpython-314.pyc
Binary file not shown.
Binary file added app/routes/__pycache__/reports.cpython-314.pyc
Binary file not shown.
Binary file added app/routes/__pycache__/tasks.cpython-314.pyc
Binary file not shown.
70 changes: 70 additions & 0 deletions app/routes/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from flask import render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, current_user, login_required
from app import db
from app.routes import auth
from app.models import User

@auth.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('main.index'))

if request.method == 'POST':
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
password2 = request.form.get('password2')

if not username or not email or not password:
flash('请填写所有必填字段')
return redirect(url_for('auth.register'))

if password != password2:
flash('两次密码输入不一致')
return redirect(url_for('auth.register'))

if User.query.filter_by(username=username).first():
flash('用户名已存在')
return redirect(url_for('auth.register'))

if User.query.filter_by(email=email).first():
flash('邮箱已被注册')
return redirect(url_for('auth.register'))

user = User(username=username, email=email)
user.set_password(password)
db.session.add(user)
db.session.commit()

flash('注册成功!请登录')
return redirect(url_for('auth.login'))

return render_template('register.html')

@auth.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.index'))

if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
remember = request.form.get('remember')

user = User.query.filter_by(username=username).first()

if user is None or not user.check_password(password):
flash('用户名或密码错误')
return redirect(url_for('auth.login'))

login_user(user, remember=remember)
next_page = request.args.get('next')
return redirect(next_page or url_for('main.index'))

return render_template('login.html')

@auth.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('auth.login'))
115 changes: 115 additions & 0 deletions app/routes/goals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from flask import render_template, redirect, url_for, flash, request, jsonify
from flask_login import login_required, current_user
from app import db
from app.routes import goals
from app.models import LearningGoal, LearningTask
from datetime import datetime

@goals.route('/')
@login_required
def list_goals():
status = request.args.get('status', 'active')
if status == 'all':
goals_list = LearningGoal.query.filter_by(user_id=current_user.id).order_by(LearningGoal.created_at.desc()).all()
else:
goals_list = LearningGoal.query.filter_by(user_id=current_user.id, status=status).order_by(LearningGoal.created_at.desc()).all()

return render_template('goals/list.html', goals=goals_list, current_status=status)

@goals.route('/create', methods=['GET', 'POST'])
@login_required
def create_goal():
if request.method == 'POST':
title = request.form.get('title')
description = request.form.get('description')
end_date_str = request.form.get('end_date')

if not title:
flash('请输入目标标题')
return redirect(url_for('goals.create_goal'))

end_date = None
if end_date_str:
try:
end_date = datetime.strptime(end_date_str, '%Y-%m-%d')
except ValueError:
flash('日期格式错误,请使用YYYY-MM-DD格式')
return redirect(url_for('goals.create_goal'))

goal = LearningGoal(
title=title,
description=description,
user_id=current_user.id,
end_date=end_date
)
db.session.add(goal)
db.session.commit()

flash('学习目标创建成功!')
return redirect(url_for('goals.view_goal', goal_id=goal.id))

return render_template('goals/create.html')

@goals.route('/<int:goal_id>')
@login_required
def view_goal(goal_id):
goal = LearningGoal.query.get_or_404(goal_id)
if goal.user_id != current_user.id:
flash('无权访问此目标')
return redirect(url_for('goals.list_goals'))

tasks = LearningTask.query.filter_by(goal_id=goal_id, parent_task_id=None).order_by(LearningTask.created_at.desc()).all()

total_tasks = LearningTask.query.filter_by(goal_id=goal_id).count()
completed_tasks = LearningTask.query.filter_by(goal_id=goal_id, status='completed').count()
in_progress_tasks = LearningTask.query.filter_by(goal_id=goal_id, status='in_progress').count()

stats = {
'total': total_tasks,
'completed': completed_tasks,
'in_progress': in_progress_tasks,
'pending': total_tasks - completed_tasks - in_progress_tasks,
'completion_rate': round((completed_tasks / total_tasks * 100), 1) if total_tasks > 0 else 0
}

return render_template('goals/view.html', goal=goal, tasks=tasks, stats=stats)

@goals.route('/<int:goal_id>/edit', methods=['GET', 'POST'])
@login_required
def edit_goal(goal_id):
goal = LearningGoal.query.get_or_404(goal_id)
if goal.user_id != current_user.id:
flash('无权编辑此目标')
return redirect(url_for('goals.list_goals'))

if request.method == 'POST':
goal.title = request.form.get('title')
goal.description = request.form.get('description')
goal.status = request.form.get('status', 'active')

end_date_str = request.form.get('end_date')
if end_date_str:
try:
goal.end_date = datetime.strptime(end_date_str, '%Y-%m-%d')
except ValueError:
flash('日期格式错误,请使用YYYY-MM-DD格式')
return redirect(url_for('goals.edit_goal', goal_id=goal.id))

db.session.commit()
flash('学习目标更新成功!')
return redirect(url_for('goals.view_goal', goal_id=goal.id))

return render_template('goals/edit.html', goal=goal)

@goals.route('/<int:goal_id>/delete', methods=['POST'])
@login_required
def delete_goal(goal_id):
goal = LearningGoal.query.get_or_404(goal_id)
if goal.user_id != current_user.id:
flash('无权删除此目标')
return redirect(url_for('goals.list_goals'))

db.session.delete(goal)
db.session.commit()
flash('学习目标已删除')
return redirect(url_for('goals.list_goals'))
41 changes: 41 additions & 0 deletions app/routes/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from flask import render_template, redirect, url_for, flash, request
from flask_login import login_required, current_user
from app import db
from app.routes import main
from app.models import LearningGoal, LearningTask, StudyRecord
from datetime import datetime, timedelta

@main.route('/')
@main.route('/index')
@login_required
def index():
today = datetime.utcnow().date()
start_of_day = datetime.combine(today, datetime.min.time())

today_records = StudyRecord.query.join(LearningTask).join(LearningGoal).filter(
LearningGoal.user_id == current_user.id,
StudyRecord.start_time >= start_of_day
).all()

today_hours = sum((r.duration_minutes or 0) for r in today_records) / 60

active_goals = LearningGoal.query.filter_by(
user_id=current_user.id,
status='active'
).all()

pending_tasks = LearningTask.query.join(LearningGoal).filter(
LearningGoal.user_id == current_user.id,
LearningTask.status == 'pending'
).order_by(LearningTask.deadline).limit(5).all()

in_progress_tasks = LearningTask.query.join(LearningGoal).filter(
LearningGoal.user_id == current_user.id,
LearningTask.status == 'in_progress'
).order_by(LearningTask.deadline).limit(5).all()

return render_template('index.html',
today_hours=round(today_hours, 2),
active_goals_count=len(active_goals),
pending_tasks=pending_tasks,
in_progress_tasks=in_progress_tasks)
Loading