Files
omniai-web/src/components/PageTransition.tsx
T
stringadmin f5a75074a4 feat: 邮箱注册验证 + 9项功能修复与优化
【认证系统】
- 新增邮箱验证码注册/登录流程 (sendEmailCode / verifyEmail / forgotPassword / resetPassword)
- register-email 现在需要验证码
- 服务端新增 email_verification_codes 表 + patch-email-verification.js
- App.tsx 登录后 emailVerified 检查提醒
- keyServerClient token 显式传递修复 401 错误

【电商模块】
- 自动推进: 策划完成后自动生成分镜图/视频
- 模特图选项 (性别/年龄/种族/体型/场景) 注入 AI 提示词
- 任务持久化指纹修复 (图片数量替代 blob URL)
- 新增「视频换装」入口 (happyhorse-1.0-video-edit)

【剧本评分】
- 新增 .docx/.doc Word 文档支持 (ZIP解压+XML提取)
- 历史记录支持点击查看/恢复评测结果

【画布】
- ReactFlow 节点禁止内置拖拽避免冲突
- 连接线拖拽弹窗优化 (预览线不消失, 弹窗跟踪鼠标)

【页面修复】
- 首页轮播图改为 aspect-ratio: 16/9 解决尺寸问题
- 资产库新增悬停删除按钮
- scriptEvalClient 改用服务端 /api/ai/chat 端点
- TokenUsagePage 未登录跳过 API 调用
2026-06-03 20:19:07 +08:00

88 lines
2.3 KiB
TypeScript

import { useEffect, useRef, useState, type ReactNode } from "react";
interface PageTransitionProps {
viewKey: string;
children: ReactNode;
}
const EXIT_DURATION_MS = 180;
const NAV_ORDER: string[] = [
"home",
"workbench",
"ecommerce",
"ecommerceTemplates",
"sizeTemplate",
"canvas",
"scriptTokens",
"tokenUsage",
"community",
"assets",
"more",
"imageWorkbench",
"resolutionUpscale",
"watermarkRemoval",
"subtitleRemoval",
"digitalHuman",
"avatarConsole",
"characterMix",
"agent",
"login",
"profile",
"report",
];
function getNavIndex(key: string): number {
return NAV_ORDER.indexOf(key);
}
export default function PageTransition({ viewKey, children }: PageTransitionProps) {
const [displayedChildren, setDisplayedChildren] = useState(children);
const [phase, setPhase] = useState<"idle" | "exit">("idle");
const [exitDirection, setExitDirection] = useState<"forward" | "backward" | "neutral">("neutral");
const prevKeyRef = useRef(viewKey);
const timerRef = useRef<ReturnType<typeof setTimeout>>();
useEffect(() => {
if (viewKey === prevKeyRef.current) {
setDisplayedChildren(children);
// Cancel any active exit animation — children updated but viewKey stable.
setPhase("idle");
return;
}
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (prefersReducedMotion) {
prevKeyRef.current = viewKey;
setDisplayedChildren(children);
setPhase("idle");
return;
}
const prevIndex = getNavIndex(prevKeyRef.current);
const nextIndex = getNavIndex(viewKey);
if (prevIndex < nextIndex) {
setExitDirection("forward");
} else if (prevIndex > nextIndex) {
setExitDirection("backward");
} else {
setExitDirection("neutral");
}
prevKeyRef.current = viewKey;
setPhase("exit");
timerRef.current = setTimeout(() => {
setDisplayedChildren(children);
setPhase("idle");
}, EXIT_DURATION_MS);
return () => clearTimeout(timerRef.current);
}, [viewKey, children]);
const dirClass = exitDirection === "forward" ? " is-forward" : exitDirection === "backward" ? " is-backward" : "";
return (
<div className={phase === "exit" ? `page-transition-wrap page-motion--exit${dirClass}` : "page-transition-wrap"}>
{displayedChildren}
</div>
);
}