Checkpoint: v2.0完整版本:新增社区排行榜、每日打卡、24种成就徽章、实时摄像头姿势分析、在线录制(稳定压缩流/断线重连/自动剪辑)、移动端全面适配。47个测试通过。包含完整开发文档。

这个提交包含在:
Manus
2026-03-14 08:04:00 -04:00
父节点 36907d1110
当前提交 2c418b482e
修改 22 个文件,包含 4370 行新增41 行删除

查看文件

@@ -22,19 +22,23 @@ import {
import { useIsMobile } from "@/hooks/useMobile";
import {
LayoutDashboard, LogOut, PanelLeft, Target, Video,
Award, Activity, FileVideo
Award, Activity, FileVideo, Trophy, Flame, Camera, CircleDot
} from "lucide-react";
import { CSSProperties, useEffect, useRef, useState } from "react";
import { useLocation, Redirect } from "wouter";
import { DashboardLayoutSkeleton } from './DashboardLayoutSkeleton';
const menuItems = [
{ icon: LayoutDashboard, label: "仪表盘", path: "/dashboard" },
{ icon: Target, label: "训练计划", path: "/training" },
{ icon: Video, label: "视频分析", path: "/analysis" },
{ icon: FileVideo, label: "视频库", path: "/videos" },
{ icon: Activity, label: "训练进度", path: "/progress" },
{ icon: Award, label: "NTRP评分", path: "/rating" },
{ icon: LayoutDashboard, label: "仪表盘", path: "/dashboard", group: "main" },
{ icon: Target, label: "训练计划", path: "/training", group: "main" },
{ icon: Flame, label: "每日打卡", path: "/checkin", group: "main" },
{ icon: Camera, label: "实时分析", path: "/live-camera", group: "analysis" },
{ icon: CircleDot, label: "在线录制", path: "/recorder", group: "analysis" },
{ icon: Video, label: "视频分析", path: "/analysis", group: "analysis" },
{ icon: FileVideo, label: "视频库", path: "/videos", group: "analysis" },
{ icon: Activity, label: "训练进度", path: "/progress", group: "stats" },
{ icon: Award, label: "NTRP评分", path: "/rating", group: "stats" },
{ icon: Trophy, label: "排行榜", path: "/leaderboard", group: "stats" },
];
const SIDEBAR_WIDTH_KEY = "sidebar-width";
@@ -163,7 +167,8 @@ function DashboardLayoutContent({
<SidebarContent className="gap-0">
<SidebarMenu className="px-2 py-1">
{menuItems.map(item => {
{/* Main group */}
{menuItems.filter(i => i.group === "main").map(item => {
const isActive = location === item.path;
return (
<SidebarMenuItem key={item.path}>
@@ -173,9 +178,49 @@ function DashboardLayoutContent({
tooltip={item.label}
className={`h-10 transition-all font-normal`}
>
<item.icon
className={`h-4 w-4 ${isActive ? "text-primary" : ""}`}
/>
<item.icon className={`h-4 w-4 ${isActive ? "text-primary" : ""}`} />
<span>{item.label}</span>
</SidebarMenuButton>
</SidebarMenuItem>
);
})}
{/* Divider */}
{!isCollapsed && <div className="my-2 mx-2 border-t border-border/50" />}
{!isCollapsed && <p className="px-3 text-[10px] font-medium text-muted-foreground uppercase tracking-wider mb-1"></p>}
{menuItems.filter(i => i.group === "analysis").map(item => {
const isActive = location === item.path;
return (
<SidebarMenuItem key={item.path}>
<SidebarMenuButton
isActive={isActive}
onClick={() => setLocation(item.path)}
tooltip={item.label}
className={`h-10 transition-all font-normal`}
>
<item.icon className={`h-4 w-4 ${isActive ? "text-primary" : ""}`} />
<span>{item.label}</span>
</SidebarMenuButton>
</SidebarMenuItem>
);
})}
{/* Divider */}
{!isCollapsed && <div className="my-2 mx-2 border-t border-border/50" />}
{!isCollapsed && <p className="px-3 text-[10px] font-medium text-muted-foreground uppercase tracking-wider mb-1"></p>}
{menuItems.filter(i => i.group === "stats").map(item => {
const isActive = location === item.path;
return (
<SidebarMenuItem key={item.path}>
<SidebarMenuButton
isActive={isActive}
onClick={() => setLocation(item.path)}
tooltip={item.label}
className={`h-10 transition-all font-normal`}
>
<item.icon className={`h-4 w-4 ${isActive ? "text-primary" : ""}`} />
<span>{item.label}</span>
</SidebarMenuButton>
</SidebarMenuItem>