feat(scope): add company scope switcher — two views (by-task all companies / by-company all tasks)

- CompanyScopeContext + Provider with localStorage persistence
- Sidebar company selector (desktop + mobile)
- apiFetch auto-injects company_id on GET requests
- Dashboard summary API supports company_id filter
- Milestones/Financial/DigitalTwins pages use scope instead of manual input
- HealthHeatmap/HealthTrends components react to scope changes
This commit is contained in:
selfrelease
2026-07-20 07:47:53 +08:00
parent 8b8926bde3
commit e81aa6828e
10 changed files with 306 additions and 53 deletions
+21 -8
View File
@@ -28,10 +28,11 @@ _DIMENSION_KEYS = [
@router.get("/summary", response_model=ApiResponse[DashboardSummary])
async def get_dashboard_summary(
company_id: str | None = Query(default=None, description="指定企业 ID,不传则汇总全租户"),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取仪表盘汇总数据。"""
"""获取仪表盘汇总数据。支持按企业过滤。"""
tenant_id = user.tenant_id
# 企业总数
@@ -40,40 +41,52 @@ async def get_dashboard_summary(
)
total_companies = companies_result.scalar_one()
# 平均健康度
avg_result = await db.execute(
# 平均健康度(按企业过滤时只算该企业)
avg_query = (
select(func.avg(HealthScore.total_score))
.join(Company, HealthScore.company_id == Company.id)
.where(Company.tenant_id == tenant_id)
)
if company_id:
avg_query = avg_query.where(HealthScore.company_id == company_id)
avg_result = await db.execute(avg_query)
avg_score = avg_result.scalar_one()
avg_health_score = float(avg_score) if avg_score else 0.0
# 高风险事件数
risk_result = await db.execute(
risk_query = (
select(func.count())
.select_from(RiskEvent)
.join(Company, RiskEvent.company_id == Company.id)
.where(Company.tenant_id == tenant_id, RiskEvent.status.in_(["open", "assigned", "in_progress"]))
)
if company_id:
risk_query = risk_query.where(RiskEvent.company_id == company_id)
risk_result = await db.execute(risk_query)
high_risk_count = risk_result.scalar_one()
# 待审阅月报数
pending_result = await db.execute(
pending_query = (
select(func.count())
.select_from(MonthlyReport)
.join(Company, MonthlyReport.company_id == Company.id)
.where(Company.tenant_id == tenant_id, MonthlyReport.status.in_(["submitted", "ai_parsed"]))
)
if company_id:
pending_query = pending_query.where(MonthlyReport.company_id == company_id)
pending_result = await db.execute(pending_query)
pending_reports = pending_result.scalar_one()
# 最近评分(最多 10 条)
recent_result = await db.execute(
recent_query = (
select(HealthScore, Company.name.label("company_name"))
.join(Company, HealthScore.company_id == Company.id)
.where(Company.tenant_id == tenant_id)
.order_by(HealthScore.calculated_at.desc())
.limit(10)
)
if company_id:
recent_query = recent_query.where(HealthScore.company_id == company_id)
recent_result = await db.execute(
recent_query.order_by(HealthScore.calculated_at.desc()).limit(10)
)
recent_rows = recent_result.all()
recent_scores = []