All checks were successful
continuous-integration/drone/push Build is passing
后端: - 添加 position_x, position_y 字段保存节点位置 前端: - 支持从节点右侧圆点拖拽出箭头连接到其他课程 - 自动根据节点Y坐标识别所属阶段 - 保存并恢复节点位置,不再重置 - 阶段区域高亮显示 - 循环依赖检测 Co-authored-by: Cursor <cursoragent@cursor.com>
229 lines
7.2 KiB
Python
229 lines
7.2 KiB
Python
"""
|
||
成长路径相关 Schema
|
||
"""
|
||
from typing import List, Optional
|
||
from datetime import datetime
|
||
from decimal import Decimal
|
||
from pydantic import BaseModel, Field
|
||
|
||
|
||
# =====================================================
|
||
# 基础数据结构
|
||
# =====================================================
|
||
|
||
class StageConfig(BaseModel):
|
||
"""阶段配置"""
|
||
name: str = Field(..., description="阶段名称")
|
||
description: Optional[str] = Field(None, description="阶段描述")
|
||
order: int = Field(0, description="排序")
|
||
|
||
|
||
class NodeBase(BaseModel):
|
||
"""节点基础信息"""
|
||
course_id: int = Field(..., description="课程ID")
|
||
stage_name: Optional[str] = Field(None, description="所属阶段名称")
|
||
title: str = Field(..., description="节点标题")
|
||
description: Optional[str] = Field(None, description="节点描述")
|
||
order_num: int = Field(0, description="排序顺序")
|
||
is_required: bool = Field(True, description="是否必修")
|
||
prerequisites: Optional[List[int]] = Field(None, description="前置节点IDs")
|
||
estimated_days: int = Field(7, description="预计学习天数")
|
||
position_x: Optional[int] = Field(0, description="画布X坐标")
|
||
position_y: Optional[int] = Field(0, description="画布Y坐标")
|
||
|
||
|
||
# =====================================================
|
||
# 管理端 - 创建/更新
|
||
# =====================================================
|
||
|
||
class GrowthPathNodeCreate(NodeBase):
|
||
"""创建节点"""
|
||
pass
|
||
|
||
|
||
class GrowthPathNodeUpdate(BaseModel):
|
||
"""更新节点"""
|
||
course_id: Optional[int] = None
|
||
stage_name: Optional[str] = None
|
||
title: Optional[str] = None
|
||
description: Optional[str] = None
|
||
order_num: Optional[int] = None
|
||
is_required: Optional[bool] = None
|
||
prerequisites: Optional[List[int]] = None
|
||
estimated_days: Optional[int] = None
|
||
|
||
|
||
class GrowthPathCreate(BaseModel):
|
||
"""创建成长路径"""
|
||
name: str = Field(..., description="路径名称")
|
||
description: Optional[str] = Field(None, description="路径描述")
|
||
target_role: Optional[str] = Field(None, description="目标角色")
|
||
position_id: Optional[int] = Field(None, description="关联岗位ID(兼容旧版)")
|
||
position_ids: Optional[List[int]] = Field(None, description="关联岗位ID列表(支持多选)")
|
||
stages: Optional[List[StageConfig]] = Field(None, description="阶段配置")
|
||
estimated_duration_days: Optional[int] = Field(None, description="预计完成天数")
|
||
is_active: bool = Field(True, description="是否启用")
|
||
sort_order: int = Field(0, description="排序")
|
||
nodes: Optional[List[GrowthPathNodeCreate]] = Field(None, description="节点列表")
|
||
|
||
|
||
class GrowthPathUpdate(BaseModel):
|
||
"""更新成长路径"""
|
||
name: Optional[str] = None
|
||
description: Optional[str] = None
|
||
target_role: Optional[str] = None
|
||
position_id: Optional[int] = None
|
||
position_ids: Optional[List[int]] = None
|
||
stages: Optional[List[StageConfig]] = None
|
||
estimated_duration_days: Optional[int] = None
|
||
is_active: Optional[bool] = None
|
||
sort_order: Optional[int] = None
|
||
nodes: Optional[List[GrowthPathNodeCreate]] = None # 整体替换节点
|
||
|
||
|
||
# =====================================================
|
||
# 管理端 - 响应
|
||
# =====================================================
|
||
|
||
class GrowthPathNodeResponse(NodeBase):
|
||
"""节点响应"""
|
||
id: int
|
||
growth_path_id: int
|
||
course_name: Optional[str] = None # 课程名称(关联查询)
|
||
created_at: datetime
|
||
updated_at: datetime
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
|
||
class GrowthPathResponse(BaseModel):
|
||
"""成长路径响应(管理端)"""
|
||
id: int
|
||
name: str
|
||
description: Optional[str] = None
|
||
target_role: Optional[str] = None
|
||
position_id: Optional[int] = None
|
||
position_name: Optional[str] = None # 岗位名称(关联查询)
|
||
stages: Optional[List[StageConfig]] = None
|
||
estimated_duration_days: Optional[int] = None
|
||
is_active: bool
|
||
sort_order: int
|
||
nodes: List[GrowthPathNodeResponse] = []
|
||
node_count: int = 0 # 节点数量
|
||
created_at: datetime
|
||
updated_at: datetime
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
|
||
class GrowthPathListResponse(BaseModel):
|
||
"""成长路径列表响应"""
|
||
id: int
|
||
name: str
|
||
description: Optional[str] = None
|
||
position_id: Optional[int] = None
|
||
position_name: Optional[str] = None
|
||
is_active: bool
|
||
node_count: int = 0
|
||
estimated_duration_days: Optional[int] = None
|
||
created_at: datetime
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
|
||
# =====================================================
|
||
# 学员端 - 响应
|
||
# =====================================================
|
||
|
||
class TraineeNodeResponse(BaseModel):
|
||
"""学员端节点响应(含进度状态)"""
|
||
id: int
|
||
course_id: int
|
||
title: str
|
||
description: Optional[str] = None
|
||
stage_name: Optional[str] = None
|
||
is_required: bool
|
||
estimated_days: int
|
||
order_num: int
|
||
|
||
# 学员特有
|
||
status: str = Field(..., description="状态: locked/unlocked/in_progress/completed")
|
||
progress: float = Field(0, description="课程学习进度 0-100")
|
||
|
||
# 课程信息
|
||
course_name: Optional[str] = None
|
||
course_cover: Optional[str] = None
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
|
||
class TraineeStageResponse(BaseModel):
|
||
"""学员端阶段响应"""
|
||
name: str
|
||
description: Optional[str] = None
|
||
completed: int = Field(0, description="已完成节点数")
|
||
total: int = Field(0, description="总节点数")
|
||
nodes: List[TraineeNodeResponse] = []
|
||
|
||
|
||
class TraineeGrowthPathResponse(BaseModel):
|
||
"""学员端成长路径响应"""
|
||
id: int
|
||
name: str
|
||
description: Optional[str] = None
|
||
position_id: Optional[int] = None
|
||
position_name: Optional[str] = None
|
||
|
||
# 进度信息
|
||
total_progress: float = Field(0, description="总进度百分比")
|
||
completed_nodes: int = Field(0, description="已完成节点数")
|
||
total_nodes: int = Field(0, description="总节点数")
|
||
status: str = Field("not_started", description="状态: not_started/in_progress/completed")
|
||
|
||
# 时间信息
|
||
started_at: Optional[datetime] = None
|
||
estimated_completion_days: Optional[int] = None
|
||
|
||
# 阶段和节点
|
||
stages: List[TraineeStageResponse] = []
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
|
||
# =====================================================
|
||
# 用户进度
|
||
# =====================================================
|
||
|
||
class UserGrowthPathProgressResponse(BaseModel):
|
||
"""用户成长路径进度响应"""
|
||
id: int
|
||
user_id: int
|
||
growth_path_id: int
|
||
growth_path_name: str
|
||
current_node_id: Optional[int] = None
|
||
current_node_title: Optional[str] = None
|
||
completed_node_ids: List[int] = []
|
||
total_progress: float
|
||
status: str
|
||
started_at: Optional[datetime] = None
|
||
completed_at: Optional[datetime] = None
|
||
last_activity_at: Optional[datetime] = None
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
|
||
class StartGrowthPathRequest(BaseModel):
|
||
"""开始学习成长路径请求"""
|
||
growth_path_id: int = Field(..., description="成长路径ID")
|
||
|
||
|
||
class CompleteNodeRequest(BaseModel):
|
||
"""完成节点请求"""
|
||
node_id: int = Field(..., description="节点ID")
|