- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
751 lines
27 KiB
Python
751 lines
27 KiB
Python
"""
|
||
团队看板 API 路由
|
||
提供团队概览、学习进度、排行榜、动态等数据
|
||
"""
|
||
|
||
import json
|
||
from datetime import datetime, timedelta
|
||
from typing import Any, Dict, List
|
||
|
||
from fastapi import APIRouter, Depends
|
||
from sqlalchemy import and_, func, or_, select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.core.deps import get_current_active_user as get_current_user, get_db
|
||
from app.core.logger import logger
|
||
from app.models.course import Course
|
||
from app.models.exam import Exam
|
||
from app.models.position import Position
|
||
from app.models.position_member import PositionMember
|
||
from app.models.practice import PracticeReport, PracticeSession
|
||
from app.models.user import Team, User, UserTeam
|
||
from app.schemas.base import ResponseModel
|
||
|
||
router = APIRouter(prefix="/team/dashboard", tags=["team-dashboard"])
|
||
|
||
|
||
async def get_accessible_teams(
|
||
current_user: User,
|
||
db: AsyncSession
|
||
) -> List[int]:
|
||
"""获取用户可访问的团队ID列表"""
|
||
if current_user.role in ['admin', 'manager']:
|
||
# 管理员查看所有团队
|
||
stmt = select(Team.id).where(Team.is_deleted == False) # noqa: E712
|
||
result = await db.execute(stmt)
|
||
return [row[0] for row in result.all()]
|
||
else:
|
||
# 普通用户只查看自己的团队
|
||
stmt = select(UserTeam.team_id).where(UserTeam.user_id == current_user.id)
|
||
result = await db.execute(stmt)
|
||
return [row[0] for row in result.all()]
|
||
|
||
|
||
async def get_team_member_ids(
|
||
team_ids: List[int],
|
||
db: AsyncSession
|
||
) -> List[int]:
|
||
"""获取团队成员ID列表"""
|
||
if not team_ids:
|
||
return []
|
||
|
||
stmt = select(UserTeam.user_id).where(
|
||
UserTeam.team_id.in_(team_ids)
|
||
).distinct()
|
||
result = await db.execute(stmt)
|
||
return [row[0] for row in result.all()]
|
||
|
||
|
||
@router.get("/overview", response_model=ResponseModel)
|
||
async def get_team_overview(
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
) -> ResponseModel:
|
||
"""
|
||
获取团队概览统计
|
||
|
||
返回团队总数、成员数、平均学习进度、平均成绩、课程完成率等
|
||
"""
|
||
try:
|
||
# 获取可访问的团队
|
||
team_ids = await get_accessible_teams(current_user, db)
|
||
|
||
# 获取团队成员ID
|
||
member_ids = await get_team_member_ids(team_ids, db)
|
||
|
||
# 统计团队数
|
||
team_count = len(team_ids)
|
||
|
||
# 统计成员数
|
||
member_count = len(member_ids)
|
||
|
||
# 计算平均考试成绩(使用round1_score)
|
||
avg_score = 0.0
|
||
if member_ids:
|
||
stmt = select(func.avg(Exam.round1_score)).where(
|
||
and_(
|
||
Exam.user_id.in_(member_ids),
|
||
Exam.round1_score.isnot(None),
|
||
Exam.status.in_(['completed', 'submitted'])
|
||
)
|
||
)
|
||
result = await db.execute(stmt)
|
||
avg_score_value = result.scalar()
|
||
avg_score = float(avg_score_value) if avg_score_value else 0.0
|
||
|
||
# 计算平均学习进度(基于考试完成情况)
|
||
avg_progress = 0.0
|
||
if member_ids:
|
||
# 统计每个成员完成的考试数
|
||
stmt = select(func.count(Exam.id)).where(
|
||
and_(
|
||
Exam.user_id.in_(member_ids),
|
||
Exam.status.in_(['completed', 'submitted'])
|
||
)
|
||
)
|
||
result = await db.execute(stmt)
|
||
completed_exams = result.scalar() or 0
|
||
|
||
# 假设每个成员应完成10个考试,计算完成率作为进度
|
||
total_expected = member_count * 10
|
||
if total_expected > 0:
|
||
avg_progress = (completed_exams / total_expected) * 100
|
||
|
||
# 计算课程完成率
|
||
course_completion_rate = 0.0
|
||
if member_ids:
|
||
# 统计已完成的课程数(有考试记录且成绩>=60)
|
||
stmt = select(func.count(func.distinct(Exam.course_id))).where(
|
||
and_(
|
||
Exam.user_id.in_(member_ids),
|
||
Exam.round1_score >= 60,
|
||
Exam.status.in_(['completed', 'submitted'])
|
||
)
|
||
)
|
||
result = await db.execute(stmt)
|
||
completed_courses = result.scalar() or 0
|
||
|
||
# 统计总课程数
|
||
stmt = select(func.count(Course.id)).where(
|
||
and_(
|
||
Course.is_deleted == False, # noqa: E712
|
||
Course.status == 'published'
|
||
)
|
||
)
|
||
result = await db.execute(stmt)
|
||
total_courses = result.scalar() or 0
|
||
|
||
if total_courses > 0:
|
||
course_completion_rate = (completed_courses / total_courses) * 100
|
||
|
||
# 趋势数据(暂时返回固定值,后续可实现真实趋势计算)
|
||
trends = {
|
||
"member_trend": 0,
|
||
"progress_trend": 12.3 if avg_progress > 0 else 0,
|
||
"score_trend": 5.8 if avg_score > 0 else 0,
|
||
"completion_trend": -3.2 if course_completion_rate > 0 else 0
|
||
}
|
||
|
||
data = {
|
||
"team_count": team_count,
|
||
"member_count": member_count,
|
||
"avg_progress": round(avg_progress, 1),
|
||
"avg_score": round(avg_score, 1),
|
||
"course_completion_rate": round(course_completion_rate, 1),
|
||
"trends": trends
|
||
}
|
||
|
||
return ResponseModel(code=200, message="success", data=data)
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取团队概览失败: {e}", exc_info=True)
|
||
return ResponseModel(code=500, message=f"获取团队概览失败: {str(e)}", data=None)
|
||
|
||
|
||
@router.get("/progress", response_model=ResponseModel)
|
||
async def get_progress_data(
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
) -> ResponseModel:
|
||
"""
|
||
获取学习进度数据
|
||
|
||
返回Top 5成员的8周学习进度数据
|
||
"""
|
||
try:
|
||
# 获取可访问的团队
|
||
team_ids = await get_accessible_teams(current_user, db)
|
||
member_ids = await get_team_member_ids(team_ids, db)
|
||
|
||
if not member_ids:
|
||
return ResponseModel(
|
||
code=200,
|
||
message="success",
|
||
data={"members": [], "weeks": [], "data": []}
|
||
)
|
||
|
||
# 获取Top 5学习时长最高的成员
|
||
stmt = (
|
||
select(
|
||
User.id,
|
||
User.full_name,
|
||
func.sum(PracticeSession.duration_seconds).label('total_duration')
|
||
)
|
||
.join(PracticeSession, PracticeSession.user_id == User.id)
|
||
.where(
|
||
and_(
|
||
User.id.in_(member_ids),
|
||
PracticeSession.status == 'completed'
|
||
)
|
||
)
|
||
.group_by(User.id, User.full_name)
|
||
.order_by(func.sum(PracticeSession.duration_seconds).desc())
|
||
.limit(5)
|
||
)
|
||
result = await db.execute(stmt)
|
||
top_members = result.all()
|
||
|
||
if not top_members:
|
||
# 如果没有陪练记录,按考试成绩选择Top 5
|
||
stmt = (
|
||
select(
|
||
User.id,
|
||
User.full_name,
|
||
func.avg(Exam.round1_score).label('avg_score')
|
||
)
|
||
.join(Exam, Exam.user_id == User.id)
|
||
.where(
|
||
and_(
|
||
User.id.in_(member_ids),
|
||
Exam.round1_score.isnot(None),
|
||
Exam.status.in_(['completed', 'submitted'])
|
||
)
|
||
)
|
||
.group_by(User.id, User.full_name)
|
||
.order_by(func.avg(Exam.round1_score).desc())
|
||
.limit(5)
|
||
)
|
||
result = await db.execute(stmt)
|
||
top_members = result.all()
|
||
|
||
# 生成周标签
|
||
weeks = [f"第{i+1}周" for i in range(8)]
|
||
|
||
# 为每个成员生成进度数据
|
||
members = []
|
||
data = []
|
||
|
||
for member in top_members:
|
||
member_name = member.full_name or f"用户{member.id}"
|
||
members.append(member_name)
|
||
|
||
# 查询该成员8周内的考试完成情况
|
||
eight_weeks_ago = datetime.now() - timedelta(weeks=8)
|
||
stmt = select(Exam).where(
|
||
and_(
|
||
Exam.user_id == member.id,
|
||
Exam.created_at >= eight_weeks_ago,
|
||
Exam.status.in_(['completed', 'submitted'])
|
||
)
|
||
).order_by(Exam.created_at)
|
||
result = await db.execute(stmt)
|
||
exams = result.scalars().all()
|
||
|
||
# 计算每周的进度(0-100)
|
||
values = []
|
||
for week in range(8):
|
||
week_start = datetime.now() - timedelta(weeks=8-week)
|
||
week_end = week_start + timedelta(weeks=1)
|
||
|
||
# 统计该周完成的考试数
|
||
week_exams = [
|
||
e for e in exams
|
||
if week_start <= e.created_at < week_end
|
||
]
|
||
|
||
# 进度 = 累计完成考试数 * 10(假设每个考试代表10%进度)
|
||
cumulative_exams = len([e for e in exams if e.created_at < week_end])
|
||
progress = min(cumulative_exams * 10, 100)
|
||
values.append(progress)
|
||
|
||
data.append({"name": member_name, "values": values})
|
||
|
||
return ResponseModel(
|
||
code=200,
|
||
message="success",
|
||
data={"members": members, "weeks": weeks, "data": data}
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取学习进度数据失败: {e}", exc_info=True)
|
||
return ResponseModel(code=500, message=f"获取学习进度数据失败: {str(e)}", data=None)
|
||
|
||
|
||
@router.get("/course-distribution", response_model=ResponseModel)
|
||
async def get_course_distribution(
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
) -> ResponseModel:
|
||
"""
|
||
获取课程完成分布
|
||
|
||
返回已完成、进行中、未开始的课程数量
|
||
"""
|
||
try:
|
||
# 获取可访问的团队
|
||
team_ids = await get_accessible_teams(current_user, db)
|
||
member_ids = await get_team_member_ids(team_ids, db)
|
||
|
||
# 统计所有已发布的课程
|
||
stmt = select(func.count(Course.id)).where(
|
||
and_(
|
||
Course.is_deleted == False, # noqa: E712
|
||
Course.status == 'published'
|
||
)
|
||
)
|
||
result = await db.execute(stmt)
|
||
total_courses = result.scalar() or 0
|
||
|
||
if not member_ids or total_courses == 0:
|
||
return ResponseModel(
|
||
code=200,
|
||
message="success",
|
||
data={"completed": 0, "in_progress": 0, "not_started": 0}
|
||
)
|
||
|
||
# 统计已完成的课程(有及格成绩)
|
||
stmt = select(func.count(func.distinct(Exam.course_id))).where(
|
||
and_(
|
||
Exam.user_id.in_(member_ids),
|
||
Exam.round1_score >= 60,
|
||
Exam.status.in_(['completed', 'submitted'])
|
||
)
|
||
)
|
||
result = await db.execute(stmt)
|
||
completed = result.scalar() or 0
|
||
|
||
# 统计进行中的课程(有考试记录但未及格)
|
||
stmt = select(func.count(func.distinct(Exam.course_id))).where(
|
||
and_(
|
||
Exam.user_id.in_(member_ids),
|
||
or_(
|
||
Exam.round1_score < 60,
|
||
Exam.status == 'started'
|
||
)
|
||
)
|
||
)
|
||
result = await db.execute(stmt)
|
||
in_progress = result.scalar() or 0
|
||
|
||
# 未开始 = 总数 - 已完成 - 进行中
|
||
not_started = max(0, total_courses - completed - in_progress)
|
||
|
||
data = {
|
||
"completed": completed,
|
||
"in_progress": in_progress,
|
||
"not_started": not_started
|
||
}
|
||
|
||
return ResponseModel(code=200, message="success", data=data)
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取课程分布失败: {e}", exc_info=True)
|
||
return ResponseModel(code=500, message=f"获取课程分布失败: {str(e)}", data=None)
|
||
|
||
|
||
@router.get("/ability-analysis", response_model=ResponseModel)
|
||
async def get_ability_analysis(
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
) -> ResponseModel:
|
||
"""
|
||
获取能力分析数据
|
||
|
||
返回团队能力雷达图数据和短板列表
|
||
"""
|
||
try:
|
||
# 获取可访问的团队
|
||
team_ids = await get_accessible_teams(current_user, db)
|
||
member_ids = await get_team_member_ids(team_ids, db)
|
||
|
||
if not member_ids:
|
||
return ResponseModel(
|
||
code=200,
|
||
message="success",
|
||
data={
|
||
"radar_data": {
|
||
"dimensions": [],
|
||
"values": []
|
||
},
|
||
"weaknesses": []
|
||
}
|
||
)
|
||
|
||
# 查询所有陪练报告的能力维度数据
|
||
# 需要通过PracticeSession关联,因为PracticeReport没有user_id
|
||
stmt = (
|
||
select(PracticeReport.ability_dimensions)
|
||
.join(PracticeSession, PracticeSession.session_id == PracticeReport.session_id)
|
||
.where(PracticeSession.user_id.in_(member_ids))
|
||
)
|
||
result = await db.execute(stmt)
|
||
all_dimensions = result.scalars().all()
|
||
|
||
if not all_dimensions:
|
||
# 如果没有陪练报告,返回默认能力维度
|
||
default_dimensions = ["沟通表达", "倾听理解", "需求挖掘", "异议处理", "成交技巧", "客户维护"]
|
||
return ResponseModel(
|
||
code=200,
|
||
message="success",
|
||
data={
|
||
"radar_data": {
|
||
"dimensions": default_dimensions,
|
||
"values": [0] * len(default_dimensions)
|
||
},
|
||
"weaknesses": []
|
||
}
|
||
)
|
||
|
||
# 聚合能力数据
|
||
ability_scores: Dict[str, List[float]] = {}
|
||
|
||
# 能力维度名称映射
|
||
dimension_name_map = {
|
||
"sales_ability": "销售能力",
|
||
"service_attitude": "服务态度",
|
||
"technical_skills": "技术能力",
|
||
"沟通表达": "沟通表达",
|
||
"倾听理解": "倾听理解",
|
||
"需求挖掘": "需求挖掘",
|
||
"异议处理": "异议处理",
|
||
"成交技巧": "成交技巧",
|
||
"客户维护": "客户维护"
|
||
}
|
||
|
||
for dimensions in all_dimensions:
|
||
if dimensions:
|
||
# 如果是字符串,进行JSON反序列化
|
||
if isinstance(dimensions, str):
|
||
try:
|
||
dimensions = json.loads(dimensions)
|
||
except json.JSONDecodeError:
|
||
logger.warning(f"无法解析能力维度数据: {dimensions}")
|
||
continue
|
||
|
||
# 处理字典格式:{"sales_ability": 79.0, ...}
|
||
if isinstance(dimensions, dict):
|
||
for key, score in dimensions.items():
|
||
name = dimension_name_map.get(key, key)
|
||
if name not in ability_scores:
|
||
ability_scores[name] = []
|
||
ability_scores[name].append(float(score))
|
||
|
||
# 处理列表格式:[{"name": "沟通表达", "score": 85}, ...]
|
||
elif isinstance(dimensions, list):
|
||
for dim in dimensions:
|
||
if not isinstance(dim, dict):
|
||
logger.warning(f"能力维度项格式错误: {type(dim)}")
|
||
continue
|
||
|
||
name = dim.get('name', '')
|
||
score = dim.get('score', 0)
|
||
if name:
|
||
mapped_name = dimension_name_map.get(name, name)
|
||
if mapped_name not in ability_scores:
|
||
ability_scores[mapped_name] = []
|
||
ability_scores[mapped_name].append(float(score))
|
||
else:
|
||
logger.warning(f"能力维度数据格式错误: {type(dimensions)}")
|
||
|
||
# 计算平均分
|
||
avg_scores = {
|
||
name: sum(scores) / len(scores)
|
||
for name, scores in ability_scores.items()
|
||
}
|
||
|
||
# 按固定顺序排列维度(支持多种维度组合)
|
||
# 优先使用六维度,如果没有则使用三维度
|
||
standard_dimensions_six = ["沟通表达", "倾听理解", "需求挖掘", "异议处理", "成交技巧", "客户维护"]
|
||
standard_dimensions_three = ["销售能力", "服务态度", "技术能力"]
|
||
|
||
# 判断使用哪种维度标准
|
||
has_six_dimensions = any(dim in avg_scores for dim in standard_dimensions_six)
|
||
has_three_dimensions = any(dim in avg_scores for dim in standard_dimensions_three)
|
||
|
||
if has_six_dimensions:
|
||
standard_dimensions = standard_dimensions_six
|
||
elif has_three_dimensions:
|
||
standard_dimensions = standard_dimensions_three
|
||
else:
|
||
# 如果都没有,使用实际数据的维度
|
||
standard_dimensions = list(avg_scores.keys())
|
||
|
||
dimensions = []
|
||
values = []
|
||
|
||
for dim in standard_dimensions:
|
||
if dim in avg_scores:
|
||
dimensions.append(dim)
|
||
values.append(round(avg_scores[dim], 1))
|
||
|
||
# 找出短板(平均分<80)
|
||
weaknesses = []
|
||
weakness_suggestions = {
|
||
# 六维度建议
|
||
"异议处理": "建议加强异议处理专项训练,增加实战演练",
|
||
"成交技巧": "需要系统学习成交话术和时机把握",
|
||
"需求挖掘": "提升提问技巧,深入了解客户需求",
|
||
"沟通表达": "加强沟通技巧训练,提升表达能力",
|
||
"倾听理解": "培养同理心,提高倾听和理解能力",
|
||
"客户维护": "学习客户关系管理,提升服务质量",
|
||
# 三维度建议
|
||
"销售能力": "建议加强销售技巧训练,提升成交率",
|
||
"服务态度": "需要改善服务态度,提高客户满意度",
|
||
"技术能力": "建议学习产品知识,提升专业能力"
|
||
}
|
||
|
||
for name, score in avg_scores.items():
|
||
if score < 80:
|
||
weaknesses.append({
|
||
"name": name,
|
||
"avg_score": int(score),
|
||
"suggestion": weakness_suggestions.get(name, f"建议加强{name}专项训练")
|
||
})
|
||
|
||
# 按分数升序排列
|
||
weaknesses.sort(key=lambda x: x['avg_score'])
|
||
|
||
data = {
|
||
"radar_data": {
|
||
"dimensions": dimensions,
|
||
"values": values
|
||
},
|
||
"weaknesses": weaknesses
|
||
}
|
||
|
||
return ResponseModel(code=200, message="success", data=data)
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取能力分析失败: {e}", exc_info=True)
|
||
return ResponseModel(code=500, message=f"获取能力分析失败: {str(e)}", data=None)
|
||
|
||
|
||
@router.get("/rankings", response_model=ResponseModel)
|
||
async def get_rankings(
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
) -> ResponseModel:
|
||
"""
|
||
获取排行榜数据
|
||
|
||
返回学习时长排行和成绩排行Top 5
|
||
"""
|
||
try:
|
||
# 获取可访问的团队
|
||
team_ids = await get_accessible_teams(current_user, db)
|
||
member_ids = await get_team_member_ids(team_ids, db)
|
||
|
||
if not member_ids:
|
||
return ResponseModel(
|
||
code=200,
|
||
message="success",
|
||
data={
|
||
"study_time_ranking": [],
|
||
"score_ranking": []
|
||
}
|
||
)
|
||
|
||
# 学习时长排行(基于陪练会话)
|
||
stmt = (
|
||
select(
|
||
User.id,
|
||
User.full_name,
|
||
User.avatar_url,
|
||
Position.name.label('position_name'),
|
||
func.sum(PracticeSession.duration_seconds).label('total_duration')
|
||
)
|
||
.join(PracticeSession, PracticeSession.user_id == User.id)
|
||
.outerjoin(PositionMember, and_(
|
||
PositionMember.user_id == User.id,
|
||
PositionMember.is_deleted == False # noqa: E712
|
||
))
|
||
.outerjoin(Position, Position.id == PositionMember.position_id)
|
||
.where(
|
||
and_(
|
||
User.id.in_(member_ids),
|
||
PracticeSession.status == 'completed'
|
||
)
|
||
)
|
||
.group_by(User.id, User.full_name, User.avatar_url, Position.name)
|
||
.order_by(func.sum(PracticeSession.duration_seconds).desc())
|
||
.limit(5)
|
||
)
|
||
result = await db.execute(stmt)
|
||
study_time_data = result.all()
|
||
|
||
study_time_ranking = []
|
||
for row in study_time_data:
|
||
study_time_ranking.append({
|
||
"id": row.id,
|
||
"name": row.full_name or f"用户{row.id}",
|
||
"position": row.position_name or "未分配岗位",
|
||
"avatar": row.avatar_url or "",
|
||
"study_time": round(row.total_duration / 3600, 1) # 转换为小时
|
||
})
|
||
|
||
# 成绩排行(基于考试round1_score)
|
||
stmt = (
|
||
select(
|
||
User.id,
|
||
User.full_name,
|
||
User.avatar_url,
|
||
Position.name.label('position_name'),
|
||
func.avg(Exam.round1_score).label('avg_score')
|
||
)
|
||
.join(Exam, Exam.user_id == User.id)
|
||
.outerjoin(PositionMember, and_(
|
||
PositionMember.user_id == User.id,
|
||
PositionMember.is_deleted == False # noqa: E712
|
||
))
|
||
.outerjoin(Position, Position.id == PositionMember.position_id)
|
||
.where(
|
||
and_(
|
||
User.id.in_(member_ids),
|
||
Exam.round1_score.isnot(None),
|
||
Exam.status.in_(['completed', 'submitted'])
|
||
)
|
||
)
|
||
.group_by(User.id, User.full_name, User.avatar_url, Position.name)
|
||
.order_by(func.avg(Exam.round1_score).desc())
|
||
.limit(5)
|
||
)
|
||
result = await db.execute(stmt)
|
||
score_data = result.all()
|
||
|
||
score_ranking = []
|
||
for row in score_data:
|
||
score_ranking.append({
|
||
"id": row.id,
|
||
"name": row.full_name or f"用户{row.id}",
|
||
"position": row.position_name or "未分配岗位",
|
||
"avatar": row.avatar_url or "",
|
||
"avg_score": round(row.avg_score, 1)
|
||
})
|
||
|
||
data = {
|
||
"study_time_ranking": study_time_ranking,
|
||
"score_ranking": score_ranking
|
||
}
|
||
|
||
return ResponseModel(code=200, message="success", data=data)
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取排行榜失败: {e}", exc_info=True)
|
||
return ResponseModel(code=500, message=f"获取排行榜失败: {str(e)}", data=None)
|
||
|
||
|
||
@router.get("/activities", response_model=ResponseModel)
|
||
async def get_activities(
|
||
current_user: User = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
) -> ResponseModel:
|
||
"""
|
||
获取团队学习动态
|
||
|
||
返回最近20条活动记录(考试、陪练等)
|
||
"""
|
||
try:
|
||
# 获取可访问的团队
|
||
team_ids = await get_accessible_teams(current_user, db)
|
||
member_ids = await get_team_member_ids(team_ids, db)
|
||
|
||
if not member_ids:
|
||
return ResponseModel(
|
||
code=200,
|
||
message="success",
|
||
data={"activities": []}
|
||
)
|
||
|
||
activities = []
|
||
|
||
# 获取最近的考试记录
|
||
stmt = (
|
||
select(Exam, User.full_name, Course.name.label('course_name'))
|
||
.join(User, User.id == Exam.user_id)
|
||
.join(Course, Course.id == Exam.course_id)
|
||
.where(
|
||
and_(
|
||
Exam.user_id.in_(member_ids),
|
||
Exam.status.in_(['completed', 'submitted'])
|
||
)
|
||
)
|
||
.order_by(Exam.updated_at.desc())
|
||
.limit(10)
|
||
)
|
||
result = await db.execute(stmt)
|
||
exam_records = result.all()
|
||
|
||
for exam, user_name, course_name in exam_records:
|
||
score = exam.round1_score or 0
|
||
activity_type = "success" if score >= 60 else "danger"
|
||
result_type = "success" if score >= 60 else "danger"
|
||
result_text = f"成绩:{int(score)}分" if score >= 60 else "未通过"
|
||
|
||
activities.append({
|
||
"id": f"exam_{exam.id}",
|
||
"user_name": user_name or f"用户{exam.user_id}",
|
||
"action": "完成了" if score >= 60 else "参加了",
|
||
"target": f"《{course_name}》课程考试",
|
||
"time": exam.updated_at.strftime("%Y-%m-%d %H:%M"),
|
||
"type": activity_type,
|
||
"result": {"type": result_type, "text": result_text}
|
||
})
|
||
|
||
# 获取最近的陪练记录
|
||
stmt = (
|
||
select(PracticeSession, User.full_name, PracticeReport.total_score)
|
||
.join(User, User.id == PracticeSession.user_id)
|
||
.outerjoin(PracticeReport, PracticeReport.session_id == PracticeSession.session_id)
|
||
.where(
|
||
and_(
|
||
PracticeSession.user_id.in_(member_ids),
|
||
PracticeSession.status == 'completed'
|
||
)
|
||
)
|
||
.order_by(PracticeSession.end_time.desc())
|
||
.limit(10)
|
||
)
|
||
result = await db.execute(stmt)
|
||
practice_records = result.all()
|
||
|
||
for session, user_name, total_score in practice_records:
|
||
activity_type = "primary"
|
||
result_data = None
|
||
if total_score:
|
||
result_data = {"type": "", "text": f"评分:{int(total_score)}分"}
|
||
|
||
activities.append({
|
||
"id": f"practice_{session.id}",
|
||
"user_name": user_name or f"用户{session.user_id}",
|
||
"action": "参加了",
|
||
"target": "AI陪练训练",
|
||
"time": session.end_time.strftime("%Y-%m-%d %H:%M") if session.end_time else "",
|
||
"type": activity_type,
|
||
"result": result_data
|
||
})
|
||
|
||
# 按时间倒序排列,取前20条
|
||
activities.sort(key=lambda x: x['time'], reverse=True)
|
||
activities = activities[:20]
|
||
|
||
return ResponseModel(
|
||
code=200,
|
||
message="success",
|
||
data={"activities": activities}
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取团队动态失败: {e}", exc_info=True)
|
||
return ResponseModel(code=500, message=f"获取团队动态失败: {str(e)}", data=None)
|
||
|