feat: 实现成长路径功能
All checks were successful
continuous-integration/drone/push Build is passing

- 新增数据库表: growth_path_nodes, user_growth_path_progress, user_node_completions
- 新增 Model: GrowthPathNode, UserGrowthPathProgress, UserNodeCompletion
- 新增 Service: GrowthPathService(管理端CRUD、学员端进度追踪)
- 新增 API: 学员端获取成长路径、管理端CRUD
- 前端学员端从API动态加载成长路径数据
- 更新管理端API接口定义
This commit is contained in:
yuliang_guo
2026-01-30 15:37:14 +08:00
parent d44111e712
commit b4906c543b
11 changed files with 1816 additions and 154 deletions

View File

@@ -37,6 +37,13 @@ from app.models.user_course_progress import (
UserMaterialProgress,
ProgressStatus,
)
from app.models.growth_path import (
GrowthPathNode,
UserGrowthPathProgress,
UserNodeCompletion,
GrowthPathStatus,
NodeStatus,
)
__all__ = [
"Base",
@@ -80,4 +87,9 @@ __all__ = [
"UserCourseProgress",
"UserMaterialProgress",
"ProgressStatus",
"GrowthPathNode",
"UserGrowthPathProgress",
"UserNodeCompletion",
"GrowthPathStatus",
"NodeStatus",
]

View File

@@ -223,10 +223,20 @@ class GrowthPath(BaseModel, SoftDeleteMixin):
target_role: Mapped[Optional[str]] = mapped_column(
String(100), nullable=True, comment="目标角色"
)
# 岗位关联
position_id: Mapped[Optional[int]] = mapped_column(
Integer, nullable=True, comment="关联岗位ID"
)
# 路径配置
# 路径配置(保留用于兼容,新版使用 nodes 关联表)
courses: Mapped[Optional[List[dict]]] = mapped_column(
JSON, nullable=True, comment="课程列表[{course_id, order, is_required}]"
JSON, nullable=True, comment="课程列表[{course_id, order, is_required}]已废弃使用nodes"
)
# 阶段配置
stages: Mapped[Optional[List[dict]]] = mapped_column(
JSON, nullable=True, comment="阶段配置[{name, description, order}]"
)
# 预计时长
@@ -241,6 +251,11 @@ class GrowthPath(BaseModel, SoftDeleteMixin):
sort_order: Mapped[int] = mapped_column(
Integer, default=0, nullable=False, comment="排序顺序"
)
# 关联关系
nodes: Mapped[List["GrowthPathNode"]] = relationship(
"GrowthPathNode", back_populates="growth_path", cascade="all, delete-orphan"
)
class MaterialKnowledgePoint(BaseModel, SoftDeleteMixin):

View File

@@ -0,0 +1,206 @@
"""
成长路径相关数据库模型
"""
from enum import Enum
from typing import List, Optional
from datetime import datetime
from decimal import Decimal
from sqlalchemy import (
String,
Text,
Integer,
Boolean,
ForeignKey,
Enum as SQLEnum,
JSON,
DateTime,
DECIMAL,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.models.base import BaseModel, SoftDeleteMixin
class GrowthPathStatus(str, Enum):
"""成长路径学习状态"""
NOT_STARTED = "not_started" # 未开始
IN_PROGRESS = "in_progress" # 进行中
COMPLETED = "completed" # 已完成
class NodeStatus(str, Enum):
"""节点状态"""
LOCKED = "locked" # 锁定(前置未完成)
UNLOCKED = "unlocked" # 已解锁(可以开始)
IN_PROGRESS = "in_progress" # 学习中
COMPLETED = "completed" # 已完成
class GrowthPathNode(BaseModel, SoftDeleteMixin):
"""
成长路径节点表
每个节点对应一门课程
"""
__tablename__ = "growth_path_nodes"
# 关联
growth_path_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("growth_paths.id", ondelete="CASCADE"),
nullable=False,
comment="成长路径ID"
)
course_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("courses.id", ondelete="CASCADE"),
nullable=False,
comment="课程ID"
)
# 节点信息
stage_name: Mapped[Optional[str]] = mapped_column(
String(100), nullable=True, comment="所属阶段名称"
)
title: Mapped[str] = mapped_column(
String(200), nullable=False, comment="节点标题"
)
description: Mapped[Optional[str]] = mapped_column(
Text, nullable=True, comment="节点描述"
)
# 配置
order_num: Mapped[int] = mapped_column(
Integer, default=0, nullable=False, comment="排序顺序"
)
is_required: Mapped[bool] = mapped_column(
Boolean, default=True, nullable=False, comment="是否必修"
)
prerequisites: Mapped[Optional[List[int]]] = mapped_column(
JSON, nullable=True, comment="前置节点IDs"
)
estimated_days: Mapped[int] = mapped_column(
Integer, default=7, nullable=False, comment="预计学习天数"
)
# 关联关系
growth_path: Mapped["GrowthPath"] = relationship(
"GrowthPath", back_populates="nodes"
)
course: Mapped["Course"] = relationship("Course")
user_completions: Mapped[List["UserNodeCompletion"]] = relationship(
"UserNodeCompletion", back_populates="node"
)
class UserGrowthPathProgress(BaseModel):
"""
用户成长路径进度表
记录用户在某条成长路径上的整体进度
"""
__tablename__ = "user_growth_path_progress"
# 关联
user_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
comment="用户ID"
)
growth_path_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("growth_paths.id", ondelete="CASCADE"),
nullable=False,
comment="成长路径ID"
)
# 进度信息
current_node_id: Mapped[Optional[int]] = mapped_column(
Integer, nullable=True, comment="当前学习节点ID"
)
completed_node_ids: Mapped[Optional[List[int]]] = mapped_column(
JSON, nullable=True, comment="已完成节点IDs"
)
total_progress: Mapped[Decimal] = mapped_column(
DECIMAL(5, 2), default=0.00, nullable=False, comment="总进度百分比"
)
# 状态
status: Mapped[str] = mapped_column(
String(20),
default=GrowthPathStatus.NOT_STARTED.value,
nullable=False,
comment="状态"
)
# 时间记录
started_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True), nullable=True, comment="开始时间"
)
completed_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True), nullable=True, comment="完成时间"
)
last_activity_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True), nullable=True, comment="最后活动时间"
)
# 关联关系
user: Mapped["User"] = relationship("User")
growth_path: Mapped["GrowthPath"] = relationship("GrowthPath")
class UserNodeCompletion(BaseModel):
"""
用户节点完成记录表
详细记录用户在每个节点上的学习状态
"""
__tablename__ = "user_node_completions"
# 关联
user_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
comment="用户ID"
)
growth_path_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("growth_paths.id", ondelete="CASCADE"),
nullable=False,
comment="成长路径ID"
)
node_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("growth_path_nodes.id", ondelete="CASCADE"),
nullable=False,
comment="节点ID"
)
# 进度信息
course_progress: Mapped[Decimal] = mapped_column(
DECIMAL(5, 2), default=0.00, nullable=False, comment="课程学习进度"
)
status: Mapped[str] = mapped_column(
String(20),
default=NodeStatus.LOCKED.value,
nullable=False,
comment="状态"
)
# 时间记录
unlocked_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True), nullable=True, comment="解锁时间"
)
started_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True), nullable=True, comment="开始学习时间"
)
completed_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True), nullable=True, comment="完成时间"
)
# 关联关系
user: Mapped["User"] = relationship("User")
node: Mapped["GrowthPathNode"] = relationship(
"GrowthPathNode", back_populates="user_completions"
)
growth_path: Mapped["GrowthPath"] = relationship("GrowthPath")