- Goal: 目标表,支持存储学习目标 - GoalRelation: 目标自关联多对多表,支持 DAG 结构 - StudentGoal: 学员目标记录表,关联学员和目标
16 KiB
目标管理模块实现计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement this plan.
Goal: 实现目标管理模块,支持目标的CRUD、目标间的DAG关联、与学员的关联记录
Architecture: 使用Flask-SQLAlchemy定义数据模型,通过RESTful API暴露接口,前端复用现有EasyMDE和星级组件
Tech Stack: Flask, SQLite, SQLAlchemy, EasyMDE
文件结构
app/
├── models.py # Goal, GoalRelation, StudentGoal 模型
├── routes/
│ ├── goals.py # 目标 CRUD + 关联管理 API
│ └── student_goals.py # 学员目标 API
└── templates/
├── goals.html # 目标管理页面
└── student.html # 扩展学员详情页的目标区块
docs/
└── superpowers/plans/
└── 2026-04-23-goal-management.md # 设计文档
Task 1: 数据模型
Files:
-
Modify:
app/models.py:193-240(在 PracticePlan 之前添加新模型) -
Step 1: 在 models.py 添加 Goal 模型
class Goal(db.Model):
"""目标表"""
__tablename__ = "goals"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.now)
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"content": self.content,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}
- Step 2: 在 models.py 添加 GoalRelation 模型
class GoalRelation(db.Model):
"""目标关联表 - 自关联多对多"""
__tablename__ = "goal_relations"
parent_goal_id = db.Column(db.Integer, db.ForeignKey("goals.id"), primary_key=True)
child_goal_id = db.Column(db.Integer, db.ForeignKey("goals.id"), primary_key=True)
parent = db.relationship("Goal", foreign_keys=[parent_goal_id], backref="child_relations")
child = db.relationship("Goal", foreign_keys=[child_goal_id], backref="parent_relations")
- Step 3: 在 models.py 添加 StudentGoal 模型
class StudentGoal(db.Model):
"""学员目标记录表"""
__tablename__ = "student_goals"
id = db.Column(db.Integer, primary_key=True)
student_id = db.Column(db.Integer, db.ForeignKey("students.id"), nullable=False)
goal_id = db.Column(db.Integer, db.ForeignKey("goals.id"), nullable=False)
status = db.Column(db.String(20), default="未开始") # 未开始/进行中/已完成
mastery_level = db.Column(db.Integer, default=1) # 1-5
deadline = db.Column(db.DateTime)
completed_at = db.Column(db.DateTime)
created_at = db.Column(db.DateTime, default=datetime.now)
student = db.relationship("Student", backref="goal_records")
goal = db.relationship("Goal")
def to_dict(self):
return {
"id": self.id,
"student_id": self.student_id,
"goal_id": self.goal_id,
"goal_name": self.goal.name if self.goal else None,
"status": self.status,
"mastery_level": self.mastery_level,
"deadline": self.deadline.isoformat() if self.deadline else None,
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
}
- Step 4: 运行 lint 验证
Run: lsp_diagnostics('app/models.py')
- Step 5: 提交
git add app/models.py
git commit -m "feat: 添加 Goal, GoalRelation, StudentGoal 模型"
Task 2: 目标 CRUD API
Files:
-
Create:
app/routes/goals.py -
Step 1: 创建 routes/goals.py
from flask import Blueprint, request, jsonify
from app.models import db, Goal
from app.routes.auth import login_required_json
goals_bp = Blueprint("goals", __name__)
@goals_bp.route("/api/goals", methods=["GET"])
@login_required_json
def get_goals():
goals = Goal.query.order_by(Goal.created_at.desc()).all()
return jsonify([g.to_dict() for g in goals])
@goals_bp.route("/api/goals", methods=["POST"])
@login_required_json
def create_goal():
data = request.get_json()
goal = Goal(name=data["name"], content=data.get("content", ""))
db.session.add(goal)
db.session.commit()
return jsonify(goal.to_dict()), 201
@goals_bp.route("/api/goals/<int:goal_id>", methods=["GET"])
@login_required_json
def get_goal(goal_id):
goal = Goal.query.get_or_404(goal_id)
return jsonify(goal.to_dict())
@goals_bp.route("/api/goals/<int:goal_id>", methods=["PUT"])
@login_required_json
def update_goal(goal_id):
goal = Goal.query.get_or_404(goal_id)
data = request.get_json()
if "name" in data:
goal.name = data["name"]
if "content" in data:
goal.content = data["content"]
db.session.commit()
return jsonify(goal.to_dict())
@goals_bp.route("/api/goals/<int:goal_id>", methods=["DELETE"])
@login_required_json
def delete_goal(goal_id):
goal = Goal.query.get_or_404(goal_id)
db.session.delete(goal)
db.session.commit()
return jsonify({"message": "删除成功"})
- Step 2: 在 app/routes/__init__.py 注册蓝图
from app.routes import goals
app.register_blueprint(goals.goals_bp)
- Step 3: 运行 lint 验证
Run: lsp_diagnostics('app/routes/goals.py')
- Step 4: 提交
git add app/routes/goals.py app/routes/__init__.py
git commit -m "feat: 目标 CRUD API"
Task 3: 目标关联 API + 循环检测
Files:
-
Modify:
app/routes/goals.py(添加关联接口) -
Step 1: 添加循环检测函数
def _has_cycle(parent_id, child_id):
"""检测添加 child_id 作为 parent_id 的子目标是否会形成循环"""
visited = set()
stack = [child_id]
while stack:
current = stack.pop()
if current == parent_id:
return True
if current in visited:
continue
visited.add(current)
for rel in GoalRelation.query.filter_by(parent_goal_id=current).all():
stack.append(rel.child_goal_id)
return False
- Step 2: 添加关联接口
@goals_bp.route("/api/goals/<int:goal_id>/children", methods=["GET"])
@login_required_json
def get_goal_children(goal_id):
relations = GoalRelation.query.filter_by(parent_goal_id=goal_id).all()
child_ids = [r.child_goal_id for r in relations]
children = Goal.query.filter(Goal.id.in_(child_ids)).all() if child_ids else []
return jsonify([c.to_dict() for c in children])
@goals_bp.route("/api/goals/<int:goal_id>/parents", methods=["GET"])
@login_required_json
def get_goal_parents(goal_id):
relations = GoalRelation.query.filter_by(child_goal_id=goal_id).all()
parent_ids = [r.parent_goal_id for r in relations]
parents = Goal.query.filter(Goal.id.in_(parent_ids)).all() if parent_ids else []
return jsonify([p.to_dict() for p in parents])
@goals_bp.route("/api/goals/<int:goal_id>/children", methods=["POST"])
@login_required_json
def add_goal_child(goal_id):
data = request.get_json()
child_id = data["child_goal_id"]
if goal_id == child_id:
return jsonify({"error": "不能将目标关联到自身"}), 400
if _has_cycle(goal_id, child_id):
return jsonify({"error": "添加此关联会形成循环引用"}), 400
# 检查是否已存在
existing = GoalRelation.query.filter_by(parent_goal_id=goal_id, child_goal_id=child_id).first()
if existing:
return jsonify({"error": "关联已存在"}), 400
relation = GoalRelation(parent_goal_id=goal_id, child_goal_id=child_id)
db.session.add(relation)
db.session.commit()
return jsonify({"message": "添加成功"})
@goals_bp.route("/api/goals/<int:goal_id>/children/<int:child_id>", methods=["DELETE"])
@login_required_json
def remove_goal_child(goal_id, child_id):
relation = GoalRelation.query.filter_by(parent_goal_id=goal_id, child_goal_id=child_id).first()
if relation:
db.session.delete(relation)
db.session.commit()
return jsonify({"message": "移除成功"})
- Step 3: 运行 lint 验证
Run: lsp_diagnostics('app/routes/goals.py')
- Step 4: 提交
git add app/routes/goals.py
git commit -m "feat: 目标关联 API + 循环检测"
Task 4: 学员目标 API
Files:
-
Create:
app/routes/student_goals.py -
Step 1: 创建 student_goals.py
from flask import Blueprint, request, jsonify
from app.models import db, Student, Goal, StudentGoal
from app.routes.auth import login_required_json
from datetime import datetime
student_goals_bp = Blueprint("student_goals", __name__)
@student_goals_bp.route("/api/students/<int:student_id>/goals", methods=["GET"])
@login_required_json
def get_student_goals(student_id):
Student.query.get_or_404(student_id)
records = StudentGoal.query.filter_by(student_id=student_id).all()
return jsonify([r.to_dict() for r in records])
@student_goals_bp.route("/api/students/<int:student_id>/goals", methods=["POST"])
@login_required_json
def add_student_goal(student_id):
Student.query.get_or_404(student_id)
data = request.get_json()
goal_id = data["goal_id"]
# 检查目标是否存在
Goal.query.get_or_404(goal_id)
# 检查是否已添加
existing = StudentGoal.query.filter_by(student_id=student_id, goal_id=goal_id).first()
if existing:
return jsonify({"error": "该目标已添加"}), 400
record = StudentGoal(
student_id=student_id,
goal_id=goal_id,
status="未开始",
mastery_level=1
)
db.session.add(record)
db.session.commit()
return jsonify(record.to_dict()), 201
@student_goals_bp.route("/api/students/<int:student_id>/goals/<int:goal_id>", methods=["PUT"])
@login_required_json
def update_student_goal(student_id, goal_id):
record = StudentGoal.query.filter_by(student_id=student_id, goal_id=goal_id).first_or_404()
data = request.get_json()
if "status" in data:
record.status = data["status"]
if data["status"] == "已完成":
record.completed_at = datetime.now()
if "mastery_level" in data:
record.mastery_level = data["mastery_level"]
if "deadline" in data:
record.deadline = datetime.fromisoformat(data["deadline"]) if data["deadline"] else None
db.session.commit()
return jsonify(record.to_dict())
@student_goals_bp.route("/api/students/<int:student_id>/goals/<int:goal_id>", methods=["DELETE"])
@login_required_json
def remove_student_goal(student_id, goal_id):
record = StudentGoal.query.filter_by(student_id=student_id, goal_id=goal_id).first_or_404()
db.session.delete(record)
db.session.commit()
return jsonify({"message": "移除成功"})
- Step 2: 在 app/routes/__init__.py 注册蓝图
from app.routes import student_goals
app.register_blueprint(student_goals.student_goals_bp)
- Step 3: 运行 lint 验证
Run: lsp_diagnostics('app/routes/student_goals.py')
- Step 4: 提交
git add app/routes/student_goals.py app/routes/__init__.py
git commit -m "feat: 学员目标 API"
Task 5: 目标管理页面
Files:
-
Create:
app/templates/goals.html -
Step 1: 创建 goals.html
参考现有模板结构,包含:
-
目标列表(树状或平铺)
-
创建/编辑目标 Modal(使用 EasyMDE)
-
关联管理(添加/移除子目标)
-
Step 2: 添加路由
@goals_bp.route("/goals")
@login_required_json
def goals_page():
return render_template("goals.html")
- Step 3: 运行 lint 验证
Run: lsp_diagnostics('app/templates/goals.html')
- Step 4: 提交
git add app/templates/goals.html app/routes/goals.py
git commit -m "feat: 目标管理页面"
Task 6: 学员详情页目标区块
Files:
-
Modify:
app/templates/student.html -
Step 1: 在 student.html 添加目标区块
在现有问题记录和练习方案之间添加目标区块:
-
显示学员目标列表(名称、状态、★完成度、截止日期)
-
添加/移除目标
-
编辑目标状态
-
Step 2: 添加 JS 函数
-
loadGoals()- 加载目标列表 -
renderGoalList()- 渲染目标 -
showAddGoalModal()- 显示添加目标弹窗 -
saveAddGoal()- 保存添加 -
updateGoalStatus()- 更新状态 -
Step 3: 运行 lint 验证
Run: lsp_diagnostics('app/templates/student.html')
- Step 4: 提交
git add app/templates/student.html
git commit -m "feat: 学员详情页目标区块"
Task 7: 数据库迁移
Files:
-
Modify:
app/__init__.py(在 db.create_all() 之前添加迁移逻辑) -
Step 1: 添加表存在性检测和创建
在 create_app() 中 db.create_all() 之后添加:
# 检查 goals 表是否存在,不存在则创建
result = db.session.execute(
text("SELECT name FROM sqlite_master WHERE type='table' AND name='goals'")
)
if not result.fetchone():
db.session.execute(text("""
CREATE TABLE goals (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(100) NOT NULL,
content TEXT,
created_at DATETIME,
updated_at DATETIME
)
"""))
db.session.commit()
# 检查 goal_relations 表
result = db.session.execute(
text("SELECT name FROM sqlite_master WHERE type='table' AND name='goal_relations'")
)
if not result.fetchone():
db.session.execute(text("""
CREATE TABLE goal_relations (
parent_goal_id INTEGER NOT NULL,
child_goal_id INTEGER NOT NULL,
PRIMARY KEY (parent_goal_id, child_goal_id),
FOREIGN KEY (parent_goal_id) REFERENCES goals(id),
FOREIGN KEY (child_goal_id) REFERENCES goals(id)
)
"""))
db.session.commit()
# 检查 student_goals 表
result = db.session.execute(
text("SELECT name FROM sqlite_master WHERE type='table' AND name='student_goals'")
)
if not result.fetchone():
db.session.execute(text("""
CREATE TABLE student_goals (
id INTEGER PRIMARY KEY AUTOINCREMENT,
student_id INTEGER NOT NULL,
goal_id INTEGER NOT NULL,
status VARCHAR(20) DEFAULT '未开始',
mastery_level INTEGER DEFAULT 1,
deadline DATETIME,
completed_at DATETIME,
created_at DATETIME,
FOREIGN KEY (student_id) REFERENCES students(id),
FOREIGN KEY (goal_id) REFERENCES goals(id)
)
"""))
db.session.commit()
- Step 2: 提交
git add app/__init__.py
git commit -m "feat: 数据库迁移脚本 - goals, goal_relations, student_goals 表"
Task 8: 更新文档
Files:
-
Modify:
docs/MODELS.md(添加新表说明) -
Modify:
docs/API.md(添加新 API 说明) -
Modify:
docs/STRUCTURE.md(添加新文件) -
Step 1: 更新 MODELS.md
添加 Goal, GoalRelation, StudentGoal 表说明
- Step 2: 更新 API.md
添加目标 API 和学员目标 API 说明
- Step 3: 提交
git add docs/MODELS.md docs/API.md docs/STRUCTURE.md
git commit -m "docs: 更新文档 - 目标管理模块"
验证清单
- goals 表创建成功
- goal_relations 表创建成功
- student_goals 表创建成功
- 目标 CRUD API 测试通过
- 目标关联 API 测试通过(包含循环检测)
- 学员目标 API 测试通过
- 目标管理页面可访问
- 学员详情页目标区块正常显示
- 星级显示正确(1-5 → ★-★★★★★)