Add leave-state hints for live analysis
这个提交包含在:
@@ -8,6 +8,22 @@ export type ChangeLogEntry = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const CHANGE_LOG_ENTRIES: ChangeLogEntry[] = [
|
export const CHANGE_LOG_ENTRIES: ChangeLogEntry[] = [
|
||||||
|
{
|
||||||
|
version: "2026.03.15-live-analysis-leave-hint",
|
||||||
|
releaseDate: "2026-03-15",
|
||||||
|
repoVersion: "pending-commit",
|
||||||
|
summary: "实时分析结束后增加离开提示,明确何时必须停留、何时可以安全关闭或切页。",
|
||||||
|
features: [
|
||||||
|
"分析进行中显示“不要关闭或切走页面”提示",
|
||||||
|
"结束分析后保存阶段显示“请暂时停留当前页面”提示",
|
||||||
|
"保存成功后明确提示“现在可以关闭浏览器或切换到其他页面”",
|
||||||
|
"分析中和保存中挂接 beforeunload 提醒,减少误关页面导致的数据丢失",
|
||||||
|
],
|
||||||
|
tests: [
|
||||||
|
"pnpm check",
|
||||||
|
"pnpm build",
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
version: "2026.03.15-training-generator-collapse",
|
version: "2026.03.15-training-generator-collapse",
|
||||||
releaseDate: "2026-03-15",
|
releaseDate: "2026-03-15",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useAuth } from "@/_core/hooks/useAuth";
|
import { useAuth } from "@/_core/hooks/useAuth";
|
||||||
import { trpc } from "@/lib/trpc";
|
import { trpc } from "@/lib/trpc";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
@@ -572,6 +573,7 @@ export default function LiveCamera() {
|
|||||||
const [sessionMode, setSessionMode] = useState<SessionMode>("practice");
|
const [sessionMode, setSessionMode] = useState<SessionMode>("practice");
|
||||||
const [analyzing, setAnalyzing] = useState(false);
|
const [analyzing, setAnalyzing] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [leaveStatus, setLeaveStatus] = useState<"idle" | "analyzing" | "saving" | "safe" | "failed">("idle");
|
||||||
const [immersivePreview, setImmersivePreview] = useState(false);
|
const [immersivePreview, setImmersivePreview] = useState(false);
|
||||||
const [liveScore, setLiveScore] = useState<PoseScore | null>(null);
|
const [liveScore, setLiveScore] = useState<PoseScore | null>(null);
|
||||||
const [currentAction, setCurrentAction] = useState<ActionType>("unknown");
|
const [currentAction, setCurrentAction] = useState<ActionType>("unknown");
|
||||||
@@ -979,6 +981,7 @@ export default function LiveCamera() {
|
|||||||
analyzingRef.current = true;
|
analyzingRef.current = true;
|
||||||
setAnalyzing(true);
|
setAnalyzing(true);
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
|
setLeaveStatus("analyzing");
|
||||||
setSegments([]);
|
setSegments([]);
|
||||||
segmentsRef.current = [];
|
segmentsRef.current = [];
|
||||||
currentSegmentRef.current = null;
|
currentSegmentRef.current = null;
|
||||||
@@ -1049,6 +1052,7 @@ export default function LiveCamera() {
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
analyzingRef.current = false;
|
analyzingRef.current = false;
|
||||||
setAnalyzing(false);
|
setAnalyzing(false);
|
||||||
|
setLeaveStatus("idle");
|
||||||
await stopSessionRecorder();
|
await stopSessionRecorder();
|
||||||
toast.error(`实时分析启动失败: ${error?.message || "未知错误"}`);
|
toast.error(`实时分析启动失败: ${error?.message || "未知错误"}`);
|
||||||
}
|
}
|
||||||
@@ -1059,6 +1063,7 @@ export default function LiveCamera() {
|
|||||||
analyzingRef.current = false;
|
analyzingRef.current = false;
|
||||||
setAnalyzing(false);
|
setAnalyzing(false);
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
|
setLeaveStatus("saving");
|
||||||
|
|
||||||
if (animationRef.current) {
|
if (animationRef.current) {
|
||||||
cancelAnimationFrame(animationRef.current);
|
cancelAnimationFrame(animationRef.current);
|
||||||
@@ -1071,15 +1076,32 @@ export default function LiveCamera() {
|
|||||||
poseRef.current = null;
|
poseRef.current = null;
|
||||||
}
|
}
|
||||||
await persistSession();
|
await persistSession();
|
||||||
|
setLeaveStatus("safe");
|
||||||
toast.success("实时分析已保存,并同步写入训练记录");
|
toast.success("实时分析已保存,并同步写入训练记录");
|
||||||
await liveSessionsQuery.refetch();
|
await liveSessionsQuery.refetch();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
setLeaveStatus("failed");
|
||||||
toast.error(`保存实时分析失败: ${error?.message || "未知错误"}`);
|
toast.error(`保存实时分析失败: ${error?.message || "未知错误"}`);
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
}, [liveSessionsQuery, persistSession]);
|
}, [liveSessionsQuery, persistSession]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!analyzing && !saving) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.returnValue = "实时分析数据仍在处理中,请先等待保存完成。";
|
||||||
|
return event.returnValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("beforeunload", handleBeforeUnload);
|
||||||
|
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
|
||||||
|
}, [analyzing, saving]);
|
||||||
|
|
||||||
const handleSetupComplete = useCallback(async () => {
|
const handleSetupComplete = useCallback(async () => {
|
||||||
setShowSetupGuide(false);
|
setShowSetupGuide(false);
|
||||||
await startCamera(facing, zoomTargetRef.current, qualityPreset);
|
await startCamera(facing, zoomTargetRef.current, qualityPreset);
|
||||||
@@ -1217,6 +1239,46 @@ export default function LiveCamera() {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{leaveStatus === "analyzing" ? (
|
||||||
|
<Alert>
|
||||||
|
<Activity className="h-4 w-4" />
|
||||||
|
<AlertTitle>分析进行中</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
当前仍在采集和识别动作数据,请先不要关闭浏览器或切走页面。
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{leaveStatus === "saving" ? (
|
||||||
|
<Alert>
|
||||||
|
<Activity className="h-4 w-4" />
|
||||||
|
<AlertTitle>正在保存分析结果</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
视频、动作区间和训练记录正在提交,请暂时停留当前页面;保存完成后会提示你可以离开。
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{leaveStatus === "safe" ? (
|
||||||
|
<Alert>
|
||||||
|
<CheckCircle2 className="h-4 w-4" />
|
||||||
|
<AlertTitle>分析结果已保存</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
当前分析数据已经提交完成。现在可以关闭浏览器、返回上一页,或切换到其他页面,不会影响已保存的数据。
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{leaveStatus === "failed" ? (
|
||||||
|
<Alert>
|
||||||
|
<Activity className="h-4 w-4" />
|
||||||
|
<AlertTitle>分析保存失败</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
当前会话还没有完整写入,请先留在本页并重新尝试结束分析或检查网络状态。
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<section className="rounded-[28px] border border-border/60 bg-[radial-gradient(circle_at_top_left,_rgba(249,115,22,0.16),_transparent_32%),linear-gradient(135deg,rgba(12,18,24,0.98),rgba(26,31,43,0.96))] p-5 text-white shadow-xl shadow-black/10 md:p-7">
|
<section className="rounded-[28px] border border-border/60 bg-[radial-gradient(circle_at_top_left,_rgba(249,115,22,0.16),_transparent_32%),linear-gradient(135deg,rgba(12,18,24,0.98),rgba(26,31,43,0.96))] p-5 text-white shadow-xl shadow-black/10 md:p-7">
|
||||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
|
<div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
# Tennis Training Hub - 变更日志
|
# Tennis Training Hub - 变更日志
|
||||||
|
|
||||||
|
## 2026.03.15-live-analysis-leave-hint (2026-03-15)
|
||||||
|
|
||||||
|
### 功能更新
|
||||||
|
|
||||||
|
- 实时分析进行中显示“不要关闭浏览器或切走页面”提示
|
||||||
|
- 点击“结束分析”后,保存阶段显示“请暂时停留当前页面”提示
|
||||||
|
- 保存完成后明确提示“现在可以关闭浏览器或切换到其他页面”
|
||||||
|
- 分析中和保存中增加离开页面提醒,减少误关导致的数据丢失
|
||||||
|
|
||||||
|
### 测试
|
||||||
|
|
||||||
|
- `pnpm check`
|
||||||
|
- `pnpm build`
|
||||||
|
|
||||||
|
### 仓库版本
|
||||||
|
|
||||||
|
- `pending-commit`
|
||||||
|
|
||||||
## 2026.03.15-training-generator-collapse (2026-03-15)
|
## 2026.03.15-training-generator-collapse (2026-03-15)
|
||||||
|
|
||||||
### 功能更新
|
### 功能更新
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户