Checkpoint: v4.0 media service, compose deploy, and verified docs
这个提交包含在:
@@ -54,7 +54,7 @@ export default function Dashboard() {
|
||||
{/* Welcome header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">
|
||||
<h1 className="text-2xl font-bold tracking-tight" data-testid="dashboard-title">
|
||||
欢迎回来,{user?.name || "球友"}
|
||||
</h1>
|
||||
<div className="flex items-center gap-3 mt-2">
|
||||
@@ -65,7 +65,7 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={() => setLocation("/training")} className="gap-2">
|
||||
<Button data-testid="dashboard-training-button" onClick={() => setLocation("/training")} className="gap-2">
|
||||
<Target className="h-4 w-4" />
|
||||
开始训练
|
||||
</Button>
|
||||
|
||||
@@ -341,7 +341,7 @@ export default function LiveCamera() {
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center text-white/60">
|
||||
<CameraOff className="h-12 w-12 mb-3" />
|
||||
<p className="text-sm">摄像头未启动</p>
|
||||
<Button variant="secondary" className="mt-3 gap-2" onClick={() => setShowSetupGuide(true)}>
|
||||
<Button data-testid="live-camera-start-button" variant="secondary" className="mt-3 gap-2" onClick={() => setShowSetupGuide(true)}>
|
||||
<Camera className="h-4 w-4" />启动摄像头
|
||||
</Button>
|
||||
</div>
|
||||
@@ -357,7 +357,7 @@ export default function LiveCamera() {
|
||||
{/* Controls bar */}
|
||||
<div className="flex items-center justify-center gap-3 p-3 bg-muted/30 flex-wrap">
|
||||
{!cameraActive ? (
|
||||
<Button onClick={() => setShowSetupGuide(true)} className="gap-2">
|
||||
<Button data-testid="live-camera-toolbar-start-button" onClick={() => setShowSetupGuide(true)} className="gap-2">
|
||||
<Camera className="h-4 w-4" />启动摄像头
|
||||
</Button>
|
||||
) : (
|
||||
|
||||
@@ -42,13 +42,14 @@ export default function Login() {
|
||||
|
||||
<Card className="border-0 shadow-xl">
|
||||
<CardHeader className="text-center pb-2">
|
||||
<CardTitle className="text-xl">开始训练</CardTitle>
|
||||
<CardTitle className="text-xl" data-testid="login-title">开始训练</CardTitle>
|
||||
<CardDescription>输入用户名即可开始使用</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleLogin} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Input
|
||||
data-testid="login-username-input"
|
||||
type="text"
|
||||
placeholder="请输入您的用户名"
|
||||
value={username}
|
||||
@@ -59,6 +60,7 @@ export default function Login() {
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
data-testid="login-submit-button"
|
||||
type="submit"
|
||||
className="w-full h-12 text-base font-medium"
|
||||
disabled={loginMutation.isPending || !username.trim()}
|
||||
|
||||
文件差异内容过多而无法显示
加载差异
@@ -95,7 +95,7 @@ export default function Training() {
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">训练计划</h1>
|
||||
<h1 className="text-2xl font-bold tracking-tight" data-testid="training-title">训练计划</h1>
|
||||
<p className="text-muted-foreground text-sm mt-1">AI个性化训练方案</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,6 +143,7 @@ export default function Training() {
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
data-testid="training-generate-button"
|
||||
onClick={() => generateMutation.mutate({ skillLevel, durationDays })}
|
||||
disabled={generateMutation.isPending}
|
||||
className="w-full sm:w-auto gap-2"
|
||||
|
||||
@@ -47,12 +47,12 @@ export default function Videos() {
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">训练视频库</h1>
|
||||
<h1 className="text-2xl font-bold tracking-tight" data-testid="videos-title">训练视频库</h1>
|
||||
<p className="text-muted-foreground text-sm mt-1">
|
||||
管理您的所有训练视频及分析结果 · 共 {videos?.length || 0} 个视频
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={() => setLocation("/analysis")} className="gap-2">
|
||||
<Button data-testid="videos-upload-button" onClick={() => setLocation("/analysis")} className="gap-2">
|
||||
<Video className="h-4 w-4" />
|
||||
上传新视频
|
||||
</Button>
|
||||
@@ -77,7 +77,7 @@ export default function Videos() {
|
||||
const status = statusMap[video.analysisStatus] || statusMap.pending;
|
||||
|
||||
return (
|
||||
<Card key={video.id} className="border-0 shadow-sm hover:shadow-md transition-shadow">
|
||||
<Card key={video.id} className="border-0 shadow-sm hover:shadow-md transition-shadow" data-testid="video-card">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start gap-4">
|
||||
{/* Thumbnail / icon */}
|
||||
|
||||
在新工单中引用
屏蔽一个用户