feat(frontend): 新增设备型号版本分布统计与展示模块

- 后端新增接口,按型号统计全量设备版本分布,空版本计入“未知版本”
- 前端API添加获取设备版本分布的请求方法
- 首页新增各型号版本占比的饼图展示,支持展开查看完整版本列表
- 饼图支持高亮显示前10大版本,其他版本合并显示
- 设备版本数据加载失败时提供重试功能
- 优化客服反馈状态下拉选项类型定义及默认值
- 组件重构饼图模块,增强易用性和可访问性
This commit is contained in:
eafonyang
2026-09-21 15:55:09 +08:00
parent e6da74cf01
commit d82632414a
6 changed files with 230 additions and 96 deletions
+42
View File
@@ -268,4 +268,46 @@ router.get('/api/dashboard/active-daily', async (req: Request, res: Response) =>
}
});
/**
* GET /api/dashboard/device-versions
* 按型号统计全量设备的版本分布,空版本计入“未知版本”
*/
router.get('/api/dashboard/device-versions', async (_req: Request, res: Response) => {
try {
const version = sql<string>`COALESCE(NULLIF(TRIM(${userDevices.ver}), ''), '未知版本')`;
const rows = await db
.select({
model: userDevices.model,
ver: version.as('version'),
count: sql<number>`COUNT(*)`.mapWith(Number).as('count'),
})
.from(userDevices)
.groupBy(userDevices.model, version)
.orderBy(userDevices.model, desc(sql`COUNT(*)`), version);
const modelMap = new Map<string, {
model: string;
total: number;
versions: Array<{ ver: string; count: number }>;
}>();
for (const row of rows) {
let stats = modelMap.get(row.model);
if (!stats) {
stats = { model: row.model, total: 0, versions: [] };
modelMap.set(row.model, stats);
}
const count = Number(row.count);
stats.total += count;
stats.versions.push({ ver: row.ver, count });
}
res.json(ApiResponse.success({ items: [...modelMap.values()] }));
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : String(e);
logger.error(`获取设备版本分布失败:${msg}`);
res.json(ApiResponse.error(`获取设备版本分布失败:${msg}`));
}
});
export default router;