zlwu hace 10 meses
padre
commit
adeea94cea

+ 36 - 8
frontend/src/pages/ChatBotPage.tsx

@@ -93,6 +93,22 @@ const getEmotionCategory = (emotion: string): 'positive' | 'negative' | 'neutral
   return 'neutral';
 };
 
+// 获取情绪 GIF 播放时长(毫秒)
+// 注册的正向情绪GIF 2秒,注册的负向情绪 5秒
+// 未注册的正向情绪GIF 2秒,未注册的负向情绪 4秒
+const getEmotionGifDuration = (avatarId: string | null, emotionCategory: 'positive' | 'negative'): number => {
+  const isRegistered = avatarId === AVATAR_ID_REGISTERED;
+  
+  if (emotionCategory === 'positive') {
+    // 正向情绪:注册和未注册都是 2秒
+    return 2000;
+  } else if (emotionCategory === 'negative') {
+    // 负向情绪:注册 5秒,未注册 4秒
+    return isRegistered ? 5000 : 4000;
+  }
+  return 0;
+};
+
 export const ChatBotPage = () => {
   const navigate = useNavigate();
   const [searchParams] = useSearchParams();
@@ -1867,25 +1883,31 @@ export const ChatBotPage = () => {
         if (appState.currentState === 'playing' || appState.currentState === 'sending') {
           const emotionCategory = getEmotionCategory(message.emotion);
           if (emotionCategory === 'positive') {
-            // 正向情绪:先显示正向情绪 GIF,2秒后切换到 talking GIF
+            // 正向情绪:先显示正向情绪 GIF,播放一次后切换到 talking GIF
             showPositiveGif();
             if (emotionGifTimerRef.current) {
               clearTimeout(emotionGifTimerRef.current);
             }
+            // 获取当前形象ID,根据形象类型设置对应的播放时长
+            const currentAvatarId = getStorage(STORAGE_KEYS.CURRENT_AVATAR_ID);
+            const duration = getEmotionGifDuration(currentAvatarId, emotionCategory);
             emotionGifTimerRef.current = window.setTimeout(() => {
               showTalkingGif();
               emotionGifTimerRef.current = null;
-            }, 2000);
+            }, duration);
           } else if (emotionCategory === 'negative') {
-            // 反向情绪:先显示反向情绪 GIF,5秒后切换到 talking GIF
+            // 负向情绪:先显示负向情绪 GIF,播放一次后切换到 talking GIF
             showNegativeGif();
             if (emotionGifTimerRef.current) {
               clearTimeout(emotionGifTimerRef.current);
             }
+            // 获取当前形象ID,根据形象类型设置对应的播放时长
+            const currentAvatarId = getStorage(STORAGE_KEYS.CURRENT_AVATAR_ID);
+            const duration = getEmotionGifDuration(currentAvatarId, emotionCategory);
             emotionGifTimerRef.current = window.setTimeout(() => {
               showTalkingGif();
               emotionGifTimerRef.current = null;
-            }, 5000);
+            }, duration);
           } else {
             // 中性情绪:直接显示 talking GIF
             showTalkingGif();
@@ -1923,25 +1945,31 @@ export const ChatBotPage = () => {
           // 根据情绪类型设置 GIF
           const emotionCategory = getEmotionCategory(latestEmotion);
           if (emotionCategory === 'positive') {
-            // 正向情绪:先显示正向情绪 GIF,2秒后切换到 talking GIF
+            // 正向情绪:先显示正向情绪 GIF,播放一次后切换到 talking GIF
             showPositiveGif();
             if (emotionGifTimerRef.current) {
               clearTimeout(emotionGifTimerRef.current);
             }
+            // 获取当前形象ID,根据形象类型设置对应的播放时长
+            const currentAvatarId = getStorage(STORAGE_KEYS.CURRENT_AVATAR_ID);
+            const duration = getEmotionGifDuration(currentAvatarId, emotionCategory);
             emotionGifTimerRef.current = window.setTimeout(() => {
               showTalkingGif();
               emotionGifTimerRef.current = null;
-            }, 2000);
+            }, duration);
           } else if (emotionCategory === 'negative') {
-            // 反向情绪:先显示反向情绪 GIF,5秒后切换到 talking GIF
+            // 负向情绪:先显示负向情绪 GIF,播放一次后切换到 talking GIF
             showNegativeGif();
             if (emotionGifTimerRef.current) {
               clearTimeout(emotionGifTimerRef.current);
             }
+            // 获取当前形象ID,根据形象类型设置对应的播放时长
+            const currentAvatarId = getStorage(STORAGE_KEYS.CURRENT_AVATAR_ID);
+            const duration = getEmotionGifDuration(currentAvatarId, emotionCategory);
             emotionGifTimerRef.current = window.setTimeout(() => {
               showTalkingGif();
               emotionGifTimerRef.current = null;
-            }, 5000);
+            }, duration);
           } else {
             // 中性情绪或无情绪:直接显示 talking GIF
             showTalkingGif();

+ 128 - 50
frontend/src/pages/WelcomePage.tsx

@@ -6,7 +6,7 @@ import { setStorage, STORAGE_KEYS, markRouteVisited, getVisitedRoutes, getUserId
 import { updateUserUIState, createUser, getUserInfo } from '@/utils/api';
 import 'video.js/dist/video-js.css';
 import { getOssUrl } from '@/utils/oss';
-import { px2vmin } from '@/utils/viewport';
+import { px2vmin, px2vw, px2vh } from '@/utils/viewport';
 
 // 判断是否为后端生成的默认用户名
 const isBackendGeneratedName = (name: string): boolean => /^User\d+$/.test(name.trim());
@@ -14,23 +14,25 @@ const isBackendGeneratedName = (name: string): boolean => /^User\d+$/.test(name.
 // 扩展 videojs 的类型定义
 type VideoJsPlayer = ReturnType<typeof videojs>;
 
-type WelcomeViewState = 'welcome' | 'loading' | 'playing';
+type WelcomeViewState = 'gif1' | 'gif2' | 'button' | 'loading' | 'playing';
 
 export const WelcomePage = () => {
   const navigate = useNavigate();
   const [searchParams] = useSearchParams();
   const videoRef = useRef<HTMLVideoElement>(null);
   const playerRef = useRef<VideoJsPlayer | null>(null);
-  const [viewState, setViewState] = useState<WelcomeViewState>('welcome');
+  const [viewState, setViewState] = useState<WelcomeViewState>('gif1');
   const [isLoading, setIsLoading] = useState(false);
   const isVideoReadyRef = useRef(false); // 视频是否已加载完成
   const loadingStartTimeRef = useRef<number | null>(null); // 加载开始时间
   const userInteractionRef = useRef(false); // 用户是否已交互(用于确保声音播放)
-  const viewStateRef = useRef<WelcomeViewState>('welcome'); // 用于在回调中访问最新状态
+  const viewStateRef = useRef<WelcomeViewState>('gif1'); // 用于在回调中访问最新状态
   const hasNavigatedRef = useRef(false); // 是否已经跳转到 /naming,防止重复执行
   const checkIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null); // 检查定时器
   const fallbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); // 兜底定时器
   const hasShownTimeoutTipRef = useRef(false); // 是否已经提示过"响应超时"
+  const gif1TimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); // GIF1 定时器
+  const gif2TimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); // GIF2 定时器
 
   // 处理错误参数(如QQ登录时用户未注册)
   useEffect(() => {
@@ -259,6 +261,34 @@ export const WelcomePage = () => {
     viewStateRef.current = viewState;
   }, [viewState]);
 
+  // GIF 动画流程控制
+  useEffect(() => {
+    // 页面打开后立即显示 leaf-one.gif
+    setViewState('gif1');
+    
+    // 2秒后切换到 leaf-two.gif
+    gif1TimerRef.current = window.setTimeout(() => {
+      setViewState('gif2');
+      
+      // 3秒后显示按钮
+      gif2TimerRef.current = window.setTimeout(() => {
+        setViewState('button');
+      }, 3000) as unknown as ReturnType<typeof setTimeout>;
+    }, 2000) as unknown as ReturnType<typeof setTimeout>;
+
+    // 清理函数
+    return () => {
+      if (gif1TimerRef.current) {
+        clearTimeout(gif1TimerRef.current);
+        gif1TimerRef.current = null;
+      }
+      if (gif2TimerRef.current) {
+        clearTimeout(gif2TimerRef.current);
+        gif2TimerRef.current = null;
+      }
+    };
+  }, []);
+
   // 组件卸载时清理定时器
   useEffect(() => {
     return () => {
@@ -577,7 +607,7 @@ export const WelcomePage = () => {
     }
   }, [navigate, viewState]); // 当播放状态改变时,确保事件已绑定
 
-  const handleHelloClick = async () => {
+  const handleStartButtonClick = async () => {
     // 标记用户已交互(用于确保声音播放)
     userInteractionRef.current = true;
     
@@ -590,6 +620,14 @@ export const WelcomePage = () => {
       clearTimeout(fallbackTimerRef.current);
       fallbackTimerRef.current = null;
     }
+    if (gif1TimerRef.current) {
+      clearTimeout(gif1TimerRef.current);
+      gif1TimerRef.current = null;
+    }
+    if (gif2TimerRef.current) {
+      clearTimeout(gif2TimerRef.current);
+      gif2TimerRef.current = null;
+    }
     
     // 直接进入播放状态(页面已预加载视频,无需再显示加载层)
     setIsLoading(false);
@@ -750,57 +788,97 @@ export const WelcomePage = () => {
           paddingBottom: 'env(safe-area-inset-bottom)',
         }}
       >
-        {/* 欢迎界面内容 */}
-        {viewState === 'welcome' && (
+        {/* GIF 动画层 */}
+        {(viewState === 'gif1' || viewState === 'gif2' || viewState === 'button') && (
           <div
             style={{
-              position: 'relative',
+              position: 'fixed',
+              top: 0,
+              left: 0,
               width: '100%',
-              minHeight: 'calc(var(--vh, 1vh) * 100)',
-              margin: '0 auto',
+              height: '100%',
               zIndex: 1,
+              display: 'flex',
+              alignItems: 'center',
+              justifyContent: 'center',
             }}
           >
-            {/* Hello 文字 - 可点击 */}
-            <div
-              onClick={handleHelloClick}
-              style={{
-                position: 'absolute',
-                top: px2vmin(138),
-                left: px2vmin(45),
-                width: '100%',
-                height: px2vmin(113),
-                background: 'linear-gradient(97.3deg, rgba(245, 243, 240, 0.8) 2.21%, rgba(245, 243, 240, 0.3) 150.89%)',
-                WebkitBackgroundClip: 'text',
-                WebkitTextFillColor: 'transparent',
-                backgroundClip: 'text',
-                opacity: 1,
-                display: 'flex',
-                alignItems: 'center',
-                justifyContent: 'center',
-                fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif',
-                fontWeight: 500,
-                fontStyle: 'normal',
-                fontSize: px2vmin(96),
-                lineHeight: 1,
-                letterSpacing: 0,
-                textAlign: 'center',
-                textTransform: 'capitalize',
-                cursor: 'pointer',
-                transition: 'opacity 0.2s ease, transform 0.2s ease',
-                userSelect: 'none',
-                WebkitTapHighlightColor: 'transparent',
-                touchAction: 'manipulation',
-                transform: 'translateZ(0)',
-                willChange: 'transform',
-              }}
-              onMouseEnter={(e) => { e.currentTarget.style.opacity = '0.9'; }}
-              onMouseLeave={(e) => { e.currentTarget.style.opacity = '1'; }}
-              onMouseDown={(e) => { e.currentTarget.style.opacity = '0.8'; }}
-              onMouseUp={(e) => { e.currentTarget.style.opacity = '1'; }}
-            >
-              Hello,
-            </div>
+            {/* leaf-one.gif - 全屏显示 */}
+            {viewState === 'gif1' && (
+              <img
+                src={getOssUrl('leaf-one.gif')}
+                alt="Leaf One"
+                style={{
+                  width: '100%',
+                  height: '100%',
+                  objectFit: 'cover',
+                }}
+              />
+            )}
+            
+            {/* leaf-two.gif - 全屏显示 */}
+            {viewState === 'gif2' && (
+              <img
+                src={getOssUrl('leaf-two.gif')}
+                alt="Leaf Two"
+                style={{
+                  width: '100%',
+                  height: '100%',
+                  objectFit: 'cover',
+                }}
+              />
+            )}
+            
+            {/* 按钮 - 在 gif2 播放3秒后显示 */}
+            {viewState === 'button' && (
+              <>
+                {/* 继续显示 leaf-two.gif 作为背景 */}
+                <img
+                  src={getOssUrl('leaf-two.gif')}
+                  alt="Leaf Two"
+                  style={{
+                    position: 'absolute',
+                    top: 0,
+                    left: 0,
+                    width: '100%',
+                    height: '100%',
+                    objectFit: 'cover',
+                    zIndex: 1,
+                  }}
+                />
+                {/* 按钮 */}
+                <button
+                  onClick={handleStartButtonClick}
+                  style={{
+                    position: 'absolute',
+                    top: px2vh(616),
+                    left: '50%',
+                    transform: 'translateX(-50%)',
+                    width: px2vw(163),
+                    height: px2vmin(56),
+                    background: 'transparent',
+                    border: 'none',
+                    padding: 0,
+                    cursor: 'pointer',
+                    zIndex: 2,
+                    touchAction: 'manipulation',
+                    WebkitTapHighlightColor: 'transparent',
+                  }}
+                  aria-label="Start your journey"
+                >
+                  <img
+                    src={getOssUrl('start-your-journey.png')}
+                    alt="Start your journey"
+                    style={{
+                      width: '100%',
+                      height: '100%',
+                      objectFit: 'contain',
+                    }}
+                    draggable={false}
+                  />
+                </button>
+              </>
+            )}
           </div>
         )}
 

+ 1 - 0
项目文档/小进度值异常阴影修复方案.md

@@ -149,3 +149,4 @@ if (progress > 0) {
 2. 方式:简单绘制(单次 `arc` + `stroke`)
 3. 颜色:统一颜色(避免分段计算异常)
 
+