feat: 仪表盘(租户卡片墙/平台总览/时间切换)+ 顺手:409契约锁与死代码

- 仪表盘视图:超管=平台总览四指标卡(/api/platform/stats 合并视图)+
  租户卡片墙(逐租户 ?tenant= 下钻,含停用租户);租户管理员=自己
  指标卡(/tenants/{slug}/stats,slug 取登录态);口径文案照
  stats.py docstring(线索=留资三选一/成功率分母0显—等)
- 时间范围快捷切换(今天/近7天/近30天)→ from/to 本地日期串;
  utils/statsRange.ts 纯函数 + epoch 防快速切换过期响应竞写
- 空数据态:无租户 ElEmpty / 单租户零数据自然显 0 与 —
- api/stats.ts 补 fetchPlatformStats/fetchTenantStats;tenants.ts 补
  fetchTenantList(卡片墙枚举,其余仍留 Task 6)
- vitest +11(statsRange 6 + stats API 5,总 36)
- 顺手1:test_auth must_change 409 测试补 detail 断言,锁死前后端
  契约文案(client.ts MUST_CHANGE_DETAIL)
- 顺手2:删除 beforeEach.ts 的 isUnauthorizedError 死分支与 ApiStatus
  import(模板 isHttpError 判自建 ApiError 恒 false,分支不可达)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-13 16:59:12 +08:00
parent 3973abea8b
commit f640889f22
9 changed files with 664 additions and 32 deletions
+34 -3
View File
@@ -1,10 +1,15 @@
/**
* 统计分析 API 骨架(Phase 3 Task 3)
* 统计分析 API(Phase 3 Task 4 实装调用函数;类型与端点 Task 3 建立)
*
* 端点事实源:app/platform/routes/stats_routes.py + app/platform/stats.py
* (平台合并统计 / 单租户统计,?from/&to= 闭区间)。本文件只建类型与
* 端点常量——调用函数在 Task 4(仪表盘)与 Task 8(统计分析)实装。
* (平台合并统计 / 单租户统计,?from/&to= 闭区间)。
*
* 指标口径事实源:app/platform/stats.py 模块 docstring 的口径表——
* 会话数不要求有消息 / 线索=留资三选一(phone/whatsapp/email)任一非空 /
* 高意向=评估 interested / 推送成功率 success/(success+failed)(分母 0 →
* null,前端展示"—")。Task 8(统计分析页)复用本组函数。
*/
import { http } from './client'
/** 指标卡(None 项为 null——如无推送时成功率无定义) */
export interface StatsCards {
@@ -66,3 +71,29 @@ export const STATS_ENDPOINTS = {
platform: '/api/platform/stats',
tenant: (slug: string) => `/api/platform/tenants/${slug}/stats`
} as const
/** 平台合并统计查询参数(?tenant= 单租户下钻,未知 slug 404) */
export interface PlatformStatsParams extends StatsParams {
tenant?: string
}
/**
* 平台统计(仅超管):缺省全部租户合并(逐库查询应用层合并);
* tenant=slug 单租户下钻——仪表盘租户卡片墙逐租户调用即此形态。
* options.showErrorMessage: false 关全局错误提示(卡片墙 N 连发的失败
* toast 改由调用方逐卡内联展示)
*/
export function fetchPlatformStats(
params?: PlatformStatsParams,
options?: { showErrorMessage?: boolean }
): Promise<StatsResponse> {
return http.get<StatsResponse>(STATS_ENDPOINTS.platform, { params, ...options })
}
/**
* 单租户统计(本租户管理员或超管):租户管理员的"自己指标卡"走这里,
* slug 取登录态 tenant_slug(auth store)
*/
export function fetchTenantStats(slug: string, params?: StatsParams): Promise<StatsResponse> {
return http.get<StatsResponse>(STATS_ENDPOINTS.tenant(slug), { params })
}