项目总览界面UI美化与入组数据统计修复

- 重构总览页面布局:卡片化设计、顶部标题栏、图标装饰
- 美化进度时间线:hover高亮、呼吸动画、渐变连接线
- 重写入组图表:去除边框、虚线网格、渐变柱子、柔和背景
- 修复入组统计:同时匹配enrollment_date和ENROLLED状态
- 移除adapter中hasEnrollmentStage过滤,显示真实入组数

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Cheng Zhou
2026-05-20 16:53:36 +08:00
parent 5b97ea32ab
commit 6d682103f3
7 changed files with 323 additions and 202 deletions
+27 -20
View File
@@ -3,7 +3,7 @@ import uuid
from datetime import datetime
from typing import Dict, List, Tuple
from sqlalchemy import func, select, and_
from sqlalchemy import Date, cast, func, select, and_, or_
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.site import Site
@@ -86,20 +86,18 @@ async def get_project_overview(db: AsyncSession, study_id: uuid.UUID) -> Dict:
startup_status = "NOT_STARTED"
startup_completed = kickoff.get("kickoff_date") if contract_status == "COMPLETED" else None
# 入组状态 - 依赖启动会完成
# 入组状态
enrollment = enrollment_data.get(site_id_str, {"actual": 0})
enrollment_actual = enrollment["actual"]
enrollment_target = site.enrollment_target or 0
if startup_status == "COMPLETED":
if enrollment_actual >= enrollment_target and enrollment_target > 0:
enrollment_status = "COMPLETED"
elif enrollment_actual > 0:
enrollment_status = "IN_PROGRESS"
else:
enrollment_status = "NOT_STARTED"
if enrollment_actual >= enrollment_target and enrollment_target > 0:
enrollment_status = "COMPLETED"
elif enrollment_actual > 0:
enrollment_status = "IN_PROGRESS"
elif startup_status == "COMPLETED":
enrollment_status = "NOT_STARTED"
else:
# 启动会未完成,入组强制为未开始
enrollment_status = "NOT_STARTED"
# 检查状态 - 依赖入组完成
@@ -265,35 +263,44 @@ async def _get_enrollment_data(db: AsyncSession, study_id: uuid.UUID, site_ids:
and_(
Subject.study_id == study_id,
Subject.site_id.in_(site_ids),
Subject.enrollment_date.isnot(None)
or_(
Subject.enrollment_date.isnot(None),
Subject.status == "ENROLLED"
)
)
).group_by(Subject.site_id)
result = await db.execute(stmt)
rows = result.all()
data = {}
for row in rows:
data[str(row.site_id)] = {"actual": row.count}
return data
async def _get_monthly_enrollment(db: AsyncSession, study_id: uuid.UUID) -> List[Dict]:
"""获取月度入组统计"""
month_col = func.to_char(Subject.enrollment_date, 'YYYY-MM').label("month")
month_col = func.to_char(
func.coalesce(Subject.enrollment_date, func.cast(Subject.created_at, Date)),
'YYYY-MM'
).label("month")
stmt = select(
month_col,
func.count(Subject.id).label("count")
).where(
and_(
Subject.study_id == study_id,
Subject.enrollment_date.isnot(None)
or_(
Subject.enrollment_date.isnot(None),
Subject.status == "ENROLLED"
)
)
).group_by(month_col).order_by(month_col)
result = await db.execute(stmt)
rows = result.all()
return [{"month": row.month, "count": row.count} for row in rows]