- 修复 get_realtime_activities() 中字段名错误 (exp_amount -> exp_change) - 添加 get_enterprise_overview() 的异常处理,防止单个查询失败导致整体失败 - 满分人数查询添加 NULL 值检查
This commit is contained in:
@@ -42,101 +42,164 @@ class DashboardService:
|
||||
Returns:
|
||||
企业级数据概览
|
||||
"""
|
||||
today = date.today()
|
||||
week_ago = today - timedelta(days=7)
|
||||
month_ago = today - timedelta(days=30)
|
||||
try:
|
||||
today = date.today()
|
||||
week_ago = today - timedelta(days=7)
|
||||
month_ago = today - timedelta(days=30)
|
||||
|
||||
# 基础统计
|
||||
# 1. 总学员数
|
||||
result = await self.db.execute(
|
||||
select(func.count(User.id))
|
||||
.where(User.is_deleted == False, User.role == 'trainee')
|
||||
)
|
||||
total_users = result.scalar() or 0
|
||||
|
||||
# 2. 今日活跃用户(有经验值记录)
|
||||
result = await self.db.execute(
|
||||
select(func.count(func.distinct(ExpHistory.user_id)))
|
||||
.where(func.date(ExpHistory.created_at) == today)
|
||||
)
|
||||
today_active = result.scalar() or 0
|
||||
|
||||
# 3. 本周活跃用户
|
||||
result = await self.db.execute(
|
||||
select(func.count(func.distinct(ExpHistory.user_id)))
|
||||
.where(ExpHistory.created_at >= datetime.combine(week_ago, datetime.min.time()))
|
||||
)
|
||||
week_active = result.scalar() or 0
|
||||
|
||||
# 4. 本月活跃用户
|
||||
result = await self.db.execute(
|
||||
select(func.count(func.distinct(ExpHistory.user_id)))
|
||||
.where(ExpHistory.created_at >= datetime.combine(month_ago, datetime.min.time()))
|
||||
)
|
||||
month_active = result.scalar() or 0
|
||||
|
||||
# 5. 总学习时长(小时)
|
||||
result = await self.db.execute(
|
||||
select(func.coalesce(func.sum(PracticeSession.duration_seconds), 0))
|
||||
.where(PracticeSession.status == 'completed')
|
||||
)
|
||||
practice_hours = (result.scalar() or 0) / 3600
|
||||
|
||||
result = await self.db.execute(
|
||||
select(func.coalesce(func.sum(TrainingSession.duration_seconds), 0))
|
||||
.where(TrainingSession.status == 'COMPLETED')
|
||||
)
|
||||
training_hours = (result.scalar() or 0) / 3600
|
||||
|
||||
total_hours = round(practice_hours + training_hours, 1)
|
||||
|
||||
# 6. 考试统计
|
||||
result = await self.db.execute(
|
||||
select(
|
||||
func.count(Exam.id),
|
||||
func.count(case((Exam.is_passed == True, 1))),
|
||||
func.avg(Exam.score)
|
||||
# 基础统计
|
||||
# 1. 总学员数
|
||||
result = await self.db.execute(
|
||||
select(func.count(User.id))
|
||||
.where(User.is_deleted == False, User.role == 'trainee')
|
||||
)
|
||||
.where(Exam.status == 'submitted')
|
||||
)
|
||||
exam_row = result.first()
|
||||
exam_count = exam_row[0] or 0
|
||||
exam_passed = exam_row[1] or 0
|
||||
exam_avg_score = round(exam_row[2] or 0, 1)
|
||||
exam_pass_rate = round(exam_passed / exam_count * 100, 1) if exam_count > 0 else 0
|
||||
total_users = result.scalar() or 0
|
||||
|
||||
# 7. 满分人数
|
||||
result = await self.db.execute(
|
||||
select(func.count(func.distinct(Exam.user_id)))
|
||||
.where(Exam.status == 'submitted', Exam.score >= Exam.total_score)
|
||||
)
|
||||
perfect_users = result.scalar() or 0
|
||||
# 2. 今日活跃用户(有经验值记录)
|
||||
try:
|
||||
result = await self.db.execute(
|
||||
select(func.count(func.distinct(ExpHistory.user_id)))
|
||||
.where(func.date(ExpHistory.created_at) == today)
|
||||
)
|
||||
today_active = result.scalar() or 0
|
||||
except Exception as e:
|
||||
logger.warning(f"获取今日活跃用户失败: {e}")
|
||||
today_active = 0
|
||||
|
||||
# 8. 签到率(今日签到人数/总用户数)
|
||||
result = await self.db.execute(
|
||||
select(func.count(UserLevel.id))
|
||||
.where(func.date(UserLevel.last_login_date) == today)
|
||||
)
|
||||
today_checkin = result.scalar() or 0
|
||||
checkin_rate = round(today_checkin / total_users * 100, 1) if total_users > 0 else 0
|
||||
# 3. 本周活跃用户
|
||||
try:
|
||||
result = await self.db.execute(
|
||||
select(func.count(func.distinct(ExpHistory.user_id)))
|
||||
.where(ExpHistory.created_at >= datetime.combine(week_ago, datetime.min.time()))
|
||||
)
|
||||
week_active = result.scalar() or 0
|
||||
except Exception as e:
|
||||
logger.warning(f"获取本周活跃用户失败: {e}")
|
||||
week_active = 0
|
||||
|
||||
return {
|
||||
"overview": {
|
||||
"total_users": total_users,
|
||||
"today_active": today_active,
|
||||
"week_active": week_active,
|
||||
"month_active": month_active,
|
||||
"total_hours": total_hours,
|
||||
"checkin_rate": checkin_rate,
|
||||
},
|
||||
"exam": {
|
||||
"total_count": exam_count,
|
||||
"pass_rate": exam_pass_rate,
|
||||
"avg_score": exam_avg_score,
|
||||
"perfect_users": perfect_users,
|
||||
},
|
||||
"updated_at": datetime.now().isoformat()
|
||||
}
|
||||
# 4. 本月活跃用户
|
||||
try:
|
||||
result = await self.db.execute(
|
||||
select(func.count(func.distinct(ExpHistory.user_id)))
|
||||
.where(ExpHistory.created_at >= datetime.combine(month_ago, datetime.min.time()))
|
||||
)
|
||||
month_active = result.scalar() or 0
|
||||
except Exception as e:
|
||||
logger.warning(f"获取本月活跃用户失败: {e}")
|
||||
month_active = 0
|
||||
|
||||
# 5. 总学习时长(小时)
|
||||
practice_hours = 0
|
||||
training_hours = 0
|
||||
try:
|
||||
result = await self.db.execute(
|
||||
select(func.coalesce(func.sum(PracticeSession.duration_seconds), 0))
|
||||
.where(PracticeSession.status == 'completed')
|
||||
)
|
||||
practice_hours = (result.scalar() or 0) / 3600
|
||||
except Exception as e:
|
||||
logger.warning(f"获取陪练时长失败: {e}")
|
||||
|
||||
try:
|
||||
result = await self.db.execute(
|
||||
select(func.coalesce(func.sum(TrainingSession.duration_seconds), 0))
|
||||
.where(TrainingSession.status == 'COMPLETED')
|
||||
)
|
||||
training_hours = (result.scalar() or 0) / 3600
|
||||
except Exception as e:
|
||||
logger.warning(f"获取培训时长失败: {e}")
|
||||
|
||||
total_hours = round(practice_hours + training_hours, 1)
|
||||
|
||||
# 6. 考试统计
|
||||
exam_count = 0
|
||||
exam_passed = 0
|
||||
exam_avg_score = 0
|
||||
try:
|
||||
result = await self.db.execute(
|
||||
select(
|
||||
func.count(Exam.id),
|
||||
func.count(case((Exam.is_passed == True, 1))),
|
||||
func.avg(Exam.score)
|
||||
)
|
||||
.where(Exam.status == 'submitted')
|
||||
)
|
||||
exam_row = result.first()
|
||||
if exam_row:
|
||||
exam_count = exam_row[0] or 0
|
||||
exam_passed = exam_row[1] or 0
|
||||
exam_avg_score = round(exam_row[2] or 0, 1)
|
||||
except Exception as e:
|
||||
logger.warning(f"获取考试统计失败: {e}")
|
||||
|
||||
exam_pass_rate = round(exam_passed / exam_count * 100, 1) if exam_count > 0 else 0
|
||||
|
||||
# 7. 满分人数
|
||||
perfect_users = 0
|
||||
try:
|
||||
result = await self.db.execute(
|
||||
select(func.count(func.distinct(Exam.user_id)))
|
||||
.where(
|
||||
Exam.status == 'submitted',
|
||||
Exam.score.isnot(None),
|
||||
Exam.total_score.isnot(None),
|
||||
Exam.score >= Exam.total_score
|
||||
)
|
||||
)
|
||||
perfect_users = result.scalar() or 0
|
||||
except Exception as e:
|
||||
logger.warning(f"获取满分人数失败: {e}")
|
||||
|
||||
# 8. 签到率(今日签到人数/总用户数)
|
||||
today_checkin = 0
|
||||
try:
|
||||
result = await self.db.execute(
|
||||
select(func.count(UserLevel.id))
|
||||
.where(UserLevel.last_login_date == today)
|
||||
)
|
||||
today_checkin = result.scalar() or 0
|
||||
except Exception as e:
|
||||
logger.warning(f"获取签到率失败: {e}")
|
||||
|
||||
checkin_rate = round(today_checkin / total_users * 100, 1) if total_users > 0 else 0
|
||||
|
||||
return {
|
||||
"overview": {
|
||||
"total_users": total_users,
|
||||
"today_active": today_active,
|
||||
"week_active": week_active,
|
||||
"month_active": month_active,
|
||||
"total_hours": total_hours,
|
||||
"checkin_rate": checkin_rate,
|
||||
},
|
||||
"exam": {
|
||||
"total_count": exam_count,
|
||||
"pass_rate": exam_pass_rate,
|
||||
"avg_score": exam_avg_score,
|
||||
"perfect_users": perfect_users,
|
||||
},
|
||||
"updated_at": datetime.now().isoformat()
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"获取企业概览失败: {e}")
|
||||
# 返回默认数据而不是抛出异常
|
||||
return {
|
||||
"overview": {
|
||||
"total_users": 0,
|
||||
"today_active": 0,
|
||||
"week_active": 0,
|
||||
"month_active": 0,
|
||||
"total_hours": 0,
|
||||
"checkin_rate": 0,
|
||||
},
|
||||
"exam": {
|
||||
"total_count": 0,
|
||||
"pass_rate": 0,
|
||||
"avg_score": 0,
|
||||
"perfect_users": 0,
|
||||
},
|
||||
"updated_at": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
async def get_department_comparison(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
@@ -313,35 +376,40 @@ class DashboardService:
|
||||
"""
|
||||
activities = []
|
||||
|
||||
# 获取最近的经验值记录
|
||||
result = await self.db.execute(
|
||||
select(ExpHistory, User)
|
||||
.join(User, ExpHistory.user_id == User.id)
|
||||
.order_by(ExpHistory.created_at.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
rows = result.all()
|
||||
try:
|
||||
# 获取最近的经验值记录
|
||||
result = await self.db.execute(
|
||||
select(ExpHistory, User)
|
||||
.join(User, ExpHistory.user_id == User.id)
|
||||
.order_by(ExpHistory.created_at.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
rows = result.all()
|
||||
|
||||
for exp, user in rows:
|
||||
activity_type = "学习"
|
||||
if "考试" in (exp.description or ""):
|
||||
activity_type = "考试"
|
||||
elif "签到" in (exp.description or ""):
|
||||
activity_type = "签到"
|
||||
elif "陪练" in (exp.description or ""):
|
||||
activity_type = "陪练"
|
||||
elif "奖章" in (exp.description or ""):
|
||||
activity_type = "奖章"
|
||||
for exp, user in rows:
|
||||
activity_type = "学习"
|
||||
description = exp.description or ""
|
||||
if "考试" in description:
|
||||
activity_type = "考试"
|
||||
elif "签到" in description:
|
||||
activity_type = "签到"
|
||||
elif "陪练" in description:
|
||||
activity_type = "陪练"
|
||||
elif "奖章" in description:
|
||||
activity_type = "奖章"
|
||||
|
||||
activities.append({
|
||||
"id": exp.id,
|
||||
"user_id": user.id,
|
||||
"user_name": user.full_name or user.username,
|
||||
"type": activity_type,
|
||||
"description": exp.description,
|
||||
"exp_amount": exp.exp_amount,
|
||||
"created_at": exp.created_at.isoformat() if exp.created_at else None,
|
||||
})
|
||||
activities.append({
|
||||
"id": exp.id,
|
||||
"user_id": user.id,
|
||||
"user_name": user.full_name or user.username,
|
||||
"type": activity_type,
|
||||
"description": description,
|
||||
"exp_amount": exp.exp_change, # 修复: exp_change 而非 exp_amount
|
||||
"created_at": exp.created_at.isoformat() if exp.created_at else None,
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"获取实时动态失败: {e}")
|
||||
# 返回空列表而不是抛出异常
|
||||
|
||||
return activities
|
||||
|
||||
|
||||
Reference in New Issue
Block a user