| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060 |
- import { useEffect, useRef, useState } from 'react';
- import { useNavigate, useSearchParams } from 'react-router-dom';
- import { message as antMessage } from 'antd';
- import videojs from 'video.js';
- import { setStorage, STORAGE_KEYS, markRouteVisited, getVisitedRoutes, getUserId, getStorage, setUserId, setUserLevel } from '@/utils/storage';
- import { updateUserUIState, createUser, getUserInfo } from '@/utils/api';
- import 'video.js/dist/video-js.css';
- import { getOssUrl } from '@/utils/oss';
- import { px2vmin } from '@/utils/viewport';
- const lumiImage = getOssUrl('Lumi-image.png');
- // 判断是否为后端生成的默认用户名
- const isBackendGeneratedName = (name: string): boolean => /^User\d+$/.test(name.trim());
- // 扩展 videojs 的类型定义
- type VideoJsPlayer = ReturnType<typeof videojs>;
- type WelcomeViewState = 'welcome' | 'loading' | 'playing';
- export const BeginPage = () => {
- const navigate = useNavigate();
- const [searchParams] = useSearchParams();
- const videoRef = useRef<HTMLVideoElement>(null);
- const playerRef = useRef<VideoJsPlayer | null>(null);
- const [viewState, setViewState] = useState<WelcomeViewState>('welcome');
- const [isLoading, setIsLoading] = useState(false);
- const [agreementChecked, setAgreementChecked] = useState(true);
- const isVideoReadyRef = useRef(false); // 视频是否已加载完成
- const loadingStartTimeRef = useRef<number | null>(null); // 加载开始时间
- const viewStateRef = useRef<WelcomeViewState>('welcome'); // 用于在回调中访问最新状态
- 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); // 是否已经提示过"响应超时"
- // 处理错误参数(如QQ登录时用户未注册)
- useEffect(() => {
- const error = searchParams.get('error');
- const oauth = searchParams.get('oauth');
-
- if (error === 'user_not_registered' && oauth === 'qq') {
- antMessage.error('用户未注册,请先注册');
- // 清除URL参数
- navigate('/welcome', { replace: true });
- }
- }, [searchParams, navigate]);
- useEffect(() => {
- console.log('[BeginPage] init effect start');
- // 记录用户当前所在页面
- const ok = setStorage(STORAGE_KEYS.CURRENT_PAGE, 'login');
- console.log('[BeginPage] set CURRENT_PAGE=login result:', ok);
- if (!getStorage(STORAGE_KEYS.USER_LEVEL)) {
- setStorage(STORAGE_KEYS.USER_LEVEL, '0');
- }
- // 默认标记为未登录、未分享
- setStorage(STORAGE_KEYS.IS_LOGGED_IN, 'false');
- setStorage(STORAGE_KEYS.HAS_SHARED_AVATAR, 'false');
-
- // 记录路由访问
- markRouteVisited('/login');
- console.log('[BeginPage] visited routes after markRouteVisited:', getVisitedRoutes());
- // 将页面与路由状态同步到后端(如果已有 user_id)
- const userId = getUserId();
- if (userId) {
- const visited = getVisitedRoutes();
- updateUserUIState(userId, {
- current_page: 'login',
- visited_routes: visited,
- }).catch((error) => {
- console.warn('[BeginPage] 同步页面状态失败:', error);
- if (!hasShownTimeoutTipRef.current) {
- hasShownTimeoutTipRef.current = true;
- antMessage.warning('响应超时,请重试');
- }
- });
- }
- // 🚀 提前预加载视频资源(使用 link preload)
- const videoUrl = import.meta.env.VITE_WELCOME_VIDEO_URL || getOssUrl('Lyrria-video.mp4');
- const preloadLink = document.createElement('link');
- preloadLink.rel = 'preload';
- preloadLink.as = 'video';
- preloadLink.href = videoUrl;
- preloadLink.crossOrigin = 'anonymous';
- document.head.appendChild(preloadLink);
- console.log('[WelcomePage] 已添加视频预加载链接:', videoUrl);
- // 清理函数
- return () => {
- // 清理预加载链接
- if (preloadLink.parentNode) {
- preloadLink.parentNode.removeChild(preloadLink);
- }
- };
- }, []);
- // 页面打开后,后台预加载视频(在 video 元素挂载后)
- useEffect(() => {
- // 使用 setTimeout 确保 video 元素已经挂载到 DOM
- const initTimer = setTimeout(() => {
- if (!playerRef.current && videoRef.current) {
- console.log('[WelcomePage] 后台预加载视频...');
- const player = videojs(videoRef.current, {
- controls: false,
- autoplay: false,
- preload: 'auto', // 预加载视频
- fluid: true,
- aspectRatio: '16:9',
- muted: false,
- nativeControlsForTouch: false,
- playsinline: true,
- bigPlayButton: false,
- textTrackDisplay: false,
- posterImage: false,
- errorDisplay: false,
- });
- playerRef.current = player;
- // 配置原生 video 元素
- const videoElement = player.el().querySelector('video') as HTMLVideoElement;
- if (videoElement) {
- videoElement.removeAttribute('controls');
- videoElement.controls = false;
- videoElement.setAttribute('playsinline', '');
- videoElement.setAttribute('webkit-playsinline', '');
- videoElement.setAttribute('x5-playsinline', '');
- videoElement.setAttribute('x5-video-player-type', 'h5');
- videoElement.setAttribute('x5-video-player-fullscreen', 'false');
- videoElement.setAttribute('controlsList', 'nodownload nofullscreen noremoteplayback');
- videoElement.setAttribute('disablePictureInPicture', 'true');
-
- // 隐藏视频元素(后台加载)
- videoElement.style.visibility = 'hidden';
- videoElement.style.opacity = '0';
- videoElement.style.position = 'absolute';
- videoElement.style.top = '-9999px';
- // 🚀 强制开始加载视频(立即开始预加载)
- try {
- videoElement.load(); // 强制开始加载
- console.log('[WelcomePage] 已强制开始加载视频');
- } catch (error) {
- console.warn('[WelcomePage] 强制加载失败(可能已开始加载):', error);
- }
- // 监听视频加载状态
- const handleCanPlayThrough = () => {
- console.log('[WelcomePage] 视频已加载完成,可以播放');
- isVideoReadyRef.current = true;
- // 如果正在加载状态,检查是否可以开始播放
- if (viewStateRef.current === 'loading' && loadingStartTimeRef.current) {
- checkAndStartPlayback();
- }
- };
- const handleError = (error: Event) => {
- console.error('[WelcomePage] 视频加载错误:', error);
- const videoEl = error.target as HTMLVideoElement;
- if (videoEl && videoEl.error) {
- console.error('[WelcomePage] 视频错误详情:', {
- code: videoEl.error?.code,
- message: videoEl.error?.message,
- });
- }
- // 即使出错也尝试继续(可能是网络问题,稍后重试)
- isVideoReadyRef.current = false;
- };
- const handleLoadedData = () => {
- console.log('[WelcomePage] 视频数据已加载');
- };
- const handleLoadedMetadata = () => {
- console.log('[WelcomePage] 视频元数据已加载');
- };
- // 绑定事件
- videoElement.addEventListener('canplaythrough', handleCanPlayThrough);
- videoElement.addEventListener('error', handleError);
- videoElement.addEventListener('loadeddata', handleLoadedData);
- videoElement.addEventListener('loadedmetadata', handleLoadedMetadata);
- // 保存事件处理器以便清理
- (player as any)._canPlayThroughHandler = handleCanPlayThrough;
- (player as any)._errorHandler = handleError;
- (player as any)._loadedDataHandler = handleLoadedData;
- (player as any)._loadedMetadataHandler = handleLoadedMetadata;
- }
- // 持续监控并移除 controls 属性
- const controlWatcher = setInterval(() => {
- const playerEl = player.el();
- if (!playerEl) return;
- const videoEl = playerEl.querySelector('video') as HTMLVideoElement;
- if (videoEl && videoEl.hasAttribute('controls')) {
- videoEl.removeAttribute('controls');
- videoEl.controls = false;
- }
- }, 100);
- (player as any)._controlWatcher = controlWatcher;
- // 绑定播放结束事件
- const handleEnded = () => {
- console.log('[WelcomePage] 视频播放完成,跳转到 /naming');
- navigate('/naming');
- };
- player.on('ended', handleEnded);
- (player as any)._endedHandler = handleEnded;
- }
- }, 100);
- // 清理函数
- return () => {
- clearTimeout(initTimer);
- if (playerRef.current) {
- const player = playerRef.current;
- const watcher = (player as any)._controlWatcher;
- if (watcher) {
- clearInterval(watcher);
- }
- const endedHandler = (player as any)._endedHandler;
- if (endedHandler) {
- player.off('ended', endedHandler);
- }
- // 清理视频元素事件监听器
- const videoElement = player.el()?.querySelector('video') as HTMLVideoElement;
- if (videoElement) {
- const canPlayThroughHandler = (player as any)._canPlayThroughHandler;
- const errorHandler = (player as any)._errorHandler;
- const loadedDataHandler = (player as any)._loadedDataHandler;
- const loadedMetadataHandler = (player as any)._loadedMetadataHandler;
- const videoEndedHandler = (player as any)._videoEndedHandler;
- if (canPlayThroughHandler) {
- videoElement.removeEventListener('canplaythrough', canPlayThroughHandler);
- }
- if (errorHandler) {
- videoElement.removeEventListener('error', errorHandler);
- }
- if (loadedDataHandler) {
- videoElement.removeEventListener('loadeddata', loadedDataHandler);
- }
- if (loadedMetadataHandler) {
- videoElement.removeEventListener('loadedmetadata', loadedMetadataHandler);
- }
- if (videoEndedHandler) {
- videoElement.removeEventListener('ended', videoEndedHandler);
- }
- }
- // 注意:不在这里销毁播放器,因为视频需要保持加载状态
- }
- };
- }, [navigate, viewState]);
- // 更新 viewState 时同步更新 ref
- useEffect(() => {
- viewStateRef.current = viewState;
- }, [viewState]);
- // 组件卸载时清理定时器
- useEffect(() => {
- return () => {
- if (checkIntervalRef.current) {
- clearInterval(checkIntervalRef.current);
- checkIntervalRef.current = null;
- }
- if (fallbackTimerRef.current) {
- clearTimeout(fallbackTimerRef.current);
- fallbackTimerRef.current = null;
- }
- };
- }, []);
- // 检查并开始播放(结合加载状态和最小缓冲时间)
- const checkAndStartPlayback = () => {
- if (!playerRef.current || viewStateRef.current !== 'loading') {
- return;
- }
- const now = Date.now();
- const minLoadingTime = 3000; // 最小加载时间3秒
- const elapsed = loadingStartTimeRef.current
- ? now - loadingStartTimeRef.current
- : 0;
- const remainingTime = Math.max(0, minLoadingTime - elapsed);
- // 如果视频已准备好且达到最小加载时间,开始播放
- if (isVideoReadyRef.current && elapsed >= minLoadingTime) {
- console.log('[WelcomePage] 视频已准备好,开始播放');
- // 清理定时器
- if (checkIntervalRef.current) {
- clearInterval(checkIntervalRef.current);
- checkIntervalRef.current = null;
- }
- if (fallbackTimerRef.current) {
- clearTimeout(fallbackTimerRef.current);
- fallbackTimerRef.current = null;
- }
- setIsLoading(false);
- setViewState('playing');
- } else if (isVideoReadyRef.current && remainingTime > 0) {
- // 视频已准备好,但还需要等待最小加载时间
- console.log(`[WelcomePage] 视频已准备好,等待 ${remainingTime}ms 后播放`);
- // 清理检查定时器
- if (checkIntervalRef.current) {
- clearInterval(checkIntervalRef.current);
- checkIntervalRef.current = null;
- }
- setTimeout(() => {
- if (viewStateRef.current === 'loading') {
- setIsLoading(false);
- setViewState('playing');
- }
- }, remainingTime);
- } else if (!isVideoReadyRef.current && elapsed >= minLoadingTime) {
- // 达到最小加载时间但视频未准备好,再等待一段时间
- console.log('[WelcomePage] 达到最小加载时间,但视频未完全准备好,继续等待...');
- // 清理检查定时器
- if (checkIntervalRef.current) {
- clearInterval(checkIntervalRef.current);
- checkIntervalRef.current = null;
- }
- const waitTimer = setTimeout(() => {
- if (viewStateRef.current === 'loading') {
- if (isVideoReadyRef.current) {
- setIsLoading(false);
- setViewState('playing');
- } else {
- // 即使未完全准备好也尝试播放(可能是网络慢)
- console.warn('[WelcomePage] 视频未完全准备好,尝试播放');
- setIsLoading(false);
- setViewState('playing');
- }
- }
- }, 2000); // 额外等待2秒
- (playerRef.current as any)._waitTimer = waitTimer;
- }
- };
- // 当进入播放状态时,显示视频并开始播放
- useEffect(() => {
- if (viewState === 'playing' && playerRef.current) {
- console.log('[WelcomePage] 开始播放视频');
- const player = playerRef.current;
- if (!player) {
- return;
- }
- const videoElement = player.el().querySelector('video') as HTMLVideoElement;
-
- if (videoElement) {
- // 显示视频元素
- videoElement.style.visibility = 'visible';
- videoElement.style.opacity = '1';
- videoElement.style.position = 'absolute';
- videoElement.style.top = '0';
- videoElement.style.left = '0';
- videoElement.style.width = '100%';
- videoElement.style.height = '100%';
- videoElement.style.zIndex = '2';
- // 🚀 在视频播放期间预加载用户数据,避免进入 naming 页面时卡顿
- const preloadUserData = async () => {
- console.log('[WelcomePage] 开始预加载用户数据...');
- try {
- // 确保存在 userId:如果没有,则创建用户
- let userId = getUserId();
- if (!userId) {
- try {
- const response = await createUser();
- if (response.success && response.data?.user_id) {
- userId = response.data.user_id;
- setUserId(userId);
-
- // 保存用户名到 localStorage
- if (response.data.username) {
- if (!isBackendGeneratedName(response.data.username)) {
- setStorage(STORAGE_KEYS.USER_NAME, response.data.username);
- }
- }
-
- if (typeof response.data.user_level === 'number') {
- setUserLevel(response.data.user_level);
- }
-
- console.log('[WelcomePage] ✅ 预加载:创建用户成功', userId);
- }
- } catch (error) {
- console.error('[WelcomePage] 预加载:创建用户失败', error);
- }
- }
- // 如果 localStorage 中没有保存的用户名,从后端获取
- const savedName = getStorage(STORAGE_KEYS.USER_NAME);
- if (!savedName) {
- const user_id = userId || getUserId();
- if (user_id) {
- try {
- const response = await getUserInfo(user_id);
- if (response.success && response.data) {
- const { username, bot_name } = response.data;
-
- if (typeof response.data.user_level === 'number') {
- setUserLevel(response.data.user_level);
- }
-
- // 保存用户名到 localStorage
- if (username) {
- if (!isBackendGeneratedName(username)) {
- setStorage(STORAGE_KEYS.USER_NAME, username);
- }
- }
-
- // 保存 bot_name 到 localStorage
- if (bot_name) {
- setStorage(STORAGE_KEYS.LUMI_NAME, bot_name);
- }
-
- console.log('[WelcomePage] ✅ 预加载:获取用户信息成功');
- }
- } catch (error) {
- console.error('[WelcomePage] 预加载:获取用户信息失败', error);
- }
- }
- }
- // 更新用户 UI 状态(非阻塞)
- if (userId) {
- const visited = getVisitedRoutes();
- updateUserUIState(userId, {
- current_page: 'naming',
- visited_routes: visited,
- }).catch(() => {
- // 同步失败不影响前端流程
- });
- }
- } catch (error) {
- console.error('[WelcomePage] 预加载用户数据异常:', error);
- }
- };
-
- // 立即开始预加载(不阻塞视频播放)
- preloadUserData();
- // 🚀 统一的安全跳转函数,避免多次 navigate
- const safeNavigateToNaming = () => {
- if (hasNavigatedRef.current) {
- return;
- }
- hasNavigatedRef.current = true;
- console.log('[WelcomePage] 触发跳转到 /naming');
- if (checkIntervalRef.current) {
- clearInterval(checkIntervalRef.current);
- checkIntervalRef.current = null;
- }
- if (fallbackTimerRef.current) {
- clearTimeout(fallbackTimerRef.current);
- fallbackTimerRef.current = null;
- }
- navigate('/naming');
- };
- // 绑定视频播放结束事件(原生事件,确保可靠)
- const handleVideoEnded = () => {
- console.log('[WelcomePage] 视频播放完成(video ended),准备跳转');
- safeNavigateToNaming();
- };
- videoElement.addEventListener('ended', handleVideoEnded);
- // 保存处理器以便清理
- (player as any)._videoEndedHandler = handleVideoEnded;
- // 🔁 兼容部分浏览器不触发 ended 的情况:监听播放进度,接近结尾时跳转
- const handleTimeUpdate = () => {
- try {
- const duration = videoElement.duration;
- const currentTime = videoElement.currentTime;
- if (!duration || Number.isNaN(duration) || duration <= 0) {
- return;
- }
- const progress = currentTime / duration;
- if (progress >= 0.995) {
- console.log('[WelcomePage] 视频已播放到 99.5% 以上,触发兜底跳转');
- safeNavigateToNaming();
- }
- } catch (error) {
- console.warn('[WelcomePage] timeupdate 处理异常:', error);
- }
- };
- videoElement.addEventListener('timeupdate', handleTimeUpdate);
- (player as any)._timeUpdateHandler = handleTimeUpdate;
- // 确保视频未静音(用户已交互,应该可以播放声音)
- player.muted(false);
- videoElement.muted = false;
- videoElement.volume = 1.0;
-
- // 由于用户已点击(交互),移动端Chrome应该允许播放声音
- // 尝试播放(有声)
- const playPromise = player.play();
- if (playPromise) {
- playPromise
- .then(() => {
- console.log('[WelcomePage] 视频播放成功(有声)');
- // 确保未静音
- player.muted(false);
- videoElement.muted = false;
- videoElement.volume = 1.0;
- })
- .catch((error: unknown) => {
- console.warn('[WelcomePage] 有声播放失败,尝试静音播放:', error);
- // 如果有声播放失败,尝试静音播放
- player.muted(true);
- videoElement.muted = true;
- const mutedPlayPromise = player.play();
- if (mutedPlayPromise) {
- mutedPlayPromise
- .then(() => {
- console.log('[WelcomePage] 静音播放成功,尝试恢复声音');
- // 静音播放成功后,尝试恢复声音(需要用户再次交互)
- // 但由于用户已点击,应该可以恢复
- setTimeout(() => {
- try {
- player.muted(false);
- videoElement.muted = false;
- videoElement.volume = 1.0;
- console.log('[WelcomePage] 已尝试恢复声音');
- } catch (e) {
- console.warn('[WelcomePage] 恢复声音失败:', e);
- }
- }, 100);
- })
- .catch((err: unknown) => {
- console.error('[WelcomePage] 静音播放也失败:', err);
- });
- }
- });
- }
- }
- // 清理函数:移除事件监听器
- return () => {
- if (playerRef.current) {
- const player = playerRef.current;
- const videoElement = player.el()?.querySelector('video') as HTMLVideoElement;
- if (videoElement) {
- const videoEndedHandler = (player as any)._videoEndedHandler;
- const timeUpdateHandler = (player as any)._timeUpdateHandler;
- if (videoEndedHandler) {
- videoElement.removeEventListener('ended', videoEndedHandler);
- (player as any)._videoEndedHandler = null;
- }
- if (timeUpdateHandler) {
- videoElement.removeEventListener('timeupdate', timeUpdateHandler);
- (player as any)._timeUpdateHandler = null;
- }
- }
- }
- };
- }
- }, [viewState, navigate]);
- // 监听播放结束事件(备用方案,确保事件已绑定)
- useEffect(() => {
- if (!playerRef.current) return;
- const player = playerRef.current;
-
- // 如果还没有绑定 ended 事件,则绑定
- if (!(player as any)._endedHandler) {
- const handleEnded = () => {
- console.log('[WelcomePage] 视频播放完成,跳转到 /naming');
- navigate('/naming');
- };
- player.on('ended', handleEnded);
- (player as any)._endedHandler = handleEnded;
- }
- }, [navigate, viewState]); // 当播放状态改变时,确保事件已绑定
- return (
- <>
- {/* Video.js 全局样式 */}
- <style>{`
- .video-js .vjs-control-bar {
- display: none !important;
- opacity: 0 !important;
- visibility: hidden !important;
- }
- .video-js:hover .vjs-control-bar,
- .video-js.vjs-user-active .vjs-control-bar,
- .video-js.vjs-user-inactive .vjs-control-bar,
- .video-js.vjs-paused .vjs-control-bar {
- display: none !important;
- opacity: 0 !important;
- visibility: hidden !important;
- }
- .video-js video::-webkit-media-controls {
- display: none !important;
- -webkit-appearance: none !important;
- opacity: 0 !important;
- visibility: hidden !important;
- }
- .video-js video::-webkit-media-controls-enclosure {
- display: none !important;
- -webkit-appearance: none !important;
- opacity: 0 !important;
- visibility: hidden !important;
- }
- .video-js video::-webkit-media-controls-panel,
- .video-js video::-webkit-media-controls-play-button,
- .video-js video::-webkit-media-controls-timeline,
- .video-js video::-webkit-media-controls-current-time-display,
- .video-js video::-webkit-media-controls-time-remaining-display,
- .video-js video::-webkit-media-controls-mute-button,
- .video-js video::-webkit-media-controls-volume-slider,
- .video-js video::-webkit-media-controls-fullscreen-button {
- display: none !important;
- }
- video::-webkit-media-controls-start-playback-button,
- video::-webkit-media-controls-overlay-enclosure,
- video::-webkit-media-controls-overlay-play-button {
- display: none !important;
- -webkit-appearance: none !important;
- opacity: 0 !important;
- }
- .player-container video,
- .video-js video {
- -webkit-appearance: none !important;
- appearance: none !important;
- }
- .video-js *:focus {
- outline: none;
- }
- .video-js .vjs-big-play-button {
- display: none !important;
- opacity: 0 !important;
- visibility: hidden !important;
- pointer-events: none !important;
- }
- .video-js .vjs-loading-spinner {
- display: none !important;
- opacity: 0 !important;
- visibility: hidden !important;
- pointer-events: none !important;
- }
- .video-js,
- .video-js .vjs-tech,
- .video-js video {
- pointer-events: auto !important;
- cursor: default;
- }
- .vjs-fluid {
- padding-top: 0 !important;
- }
- .vjs-16-9 {
- padding-top: 0 !important;
- }
- .video-js video {
- width: 100% !important;
- height: 100% !important;
- object-fit: contain;
- z-index: 2;
- display: block !important;
- }
- .player-container,
- .video-js {
- user-select: none;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- -webkit-touch-callout: none;
- }
- @keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
- @keyframes pulse {
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0.6; }
- }
- `}</style>
-
- <div
- style={{
- position: 'relative',
- width: '100%',
- height: 'calc(var(--vh, 1vh) * 100)',
- overflow: 'hidden',
- paddingTop: 'env(safe-area-inset-top)',
- paddingLeft: 'env(safe-area-inset-left)',
- paddingRight: 'env(safe-area-inset-right)',
- paddingBottom: 'env(safe-area-inset-bottom)',
- } as React.CSSProperties & { height: string }}
- >
- {/* 欢迎界面内容 */}
- {viewState === 'welcome' && (
- <div
- style={{
- position: 'relative',
- width: '100%',
- height: 'calc(var(--vh, 1vh) * 100)',
- margin: '0 auto',
- zIndex: 1,
- } as React.CSSProperties & { height: string }}
- >
- {/* Lumi-image 图片 - 右margin 44 */}
- <img
- src={lumiImage}
- alt="Lumi"
- style={{
- position: 'absolute',
- top: px2vmin(185),
- left: px2vmin(-37),
- width: px2vmin(423),
- height: px2vmin(423),
- objectFit: 'contain',
- transform: 'translateZ(0)',
- willChange: 'transform',
- imageRendering: '-webkit-optimize-contrast',
- WebkitUserDrag: 'none',
- } as React.CSSProperties & { WebkitUserDrag: string }}
- />
-
- {/* Hello 文字 */}
- <div
- style={{
- position: 'absolute',
- top: px2vmin(138),
- left: '50%',
- transform: `translateX(calc(-50% - ${px2vmin(54)})) translateZ(0)`,
- 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',
- userSelect: 'none',
- willChange: 'transform',
- }}
- >
- Hello,
- </div>
-
- {/* 已有账号图片按钮 - 跳转到 /login */}
- <img
- src={getOssUrl('have-an-account.png')}
- alt="已有账号"
- style={{
- position: 'absolute',
- top: px2vmin(636),
- left: '50%',
- transform: 'translateX(-50%)',
- width: px2vmin(279),
- height: 'auto',
- cursor: 'pointer',
- userSelect: 'none',
- WebkitTapHighlightColor: 'transparent',
- transition: 'opacity 0.2s ease',
- zIndex: 2,
- objectFit: 'contain',
- }}
- onClick={() => navigate('/login')}
- onMouseEnter={(e) => { e.currentTarget.style.opacity = '0.8'; }}
- onMouseLeave={(e) => { e.currentTarget.style.opacity = '1'; }}
- onMouseDown={(e) => { e.currentTarget.style.opacity = '0.8'; }}
- onMouseUp={(e) => { e.currentTarget.style.opacity = '1'; }}
- role="button"
- tabIndex={0}
- onKeyDown={(event) => {
- if (event.key === 'Enter' || event.key === ' ') {
- event.preventDefault();
- navigate('/login');
- }
- }}
- />
-
- {/* 首次访问图片按钮 - 跳转到 /naming */}
- <img
- src={getOssUrl('fitst-time-here.png')}
- alt="首次访问"
- style={{
- position: 'absolute',
- top: px2vmin(568),
- left: '50%',
- transform: 'translateX(-50%)',
- width: px2vmin(279),
- height: 'auto',
- cursor: 'pointer',
- userSelect: 'none',
- WebkitTapHighlightColor: 'transparent',
- transition: 'opacity 0.2s ease',
- zIndex: 2,
- objectFit: 'contain',
- }}
- onClick={() => navigate('/naming')}
- onMouseEnter={(e) => { e.currentTarget.style.opacity = '0.8'; }}
- onMouseLeave={(e) => { e.currentTarget.style.opacity = '1'; }}
- onMouseDown={(e) => { e.currentTarget.style.opacity = '0.8'; }}
- onMouseUp={(e) => { e.currentTarget.style.opacity = '1'; }}
- role="button"
- tabIndex={0}
- onKeyDown={(event) => {
- if (event.key === 'Enter' || event.key === ' ') {
- event.preventDefault();
- navigate('/naming');
- }
- }}
- />
-
- {/* 协议容器 */}
- <div
- style={{
- position: 'absolute',
- left: '50%',
- transform: 'translateX(-50%)',
- top: px2vmin(708),
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- padding: 0,
- zIndex: 2,
- }}
- >
- <div
- style={{
- display: 'flex',
- alignItems: 'center',
- gap: '8PX',
- }}
- >
- <div
- style={{
- width: '16PX',
- height: '16PX',
- minWidth: '16PX',
- borderRadius: '50%',
- border: '1.5PX solid rgba(245, 243, 240, 1)',
- background: agreementChecked ? 'rgb(145 89 240)' : 'transparent',
- borderColor: agreementChecked ? 'rgb(145 89 240)' : 'rgba(245, 243, 240, 1)',
- cursor: 'pointer',
- transition: 'all 0.2s ease',
- position: 'relative',
- }}
- onClick={() => setAgreementChecked(!agreementChecked)}
- >
- {agreementChecked && (
- <div
- style={{
- position: 'absolute',
- top: '50%',
- left: '50%',
- transform: 'translate(-50%, -50%)',
- width: '6PX',
- height: '6PX',
- background: 'white',
- borderRadius: '50%',
- }}
- />
- )}
- </div>
- <div
- style={{
- fontSize: '12PX',
- lineHeight: 1.5,
- color: 'rgba(245, 243, 240, 1)',
- userSelect: 'none',
- whiteSpace: 'pre',
- }}
- >
- 我已阅读并同意
- <span
- style={{
- color: 'rgba(191, 157, 248, 1)',
- textDecoration: 'none',
- cursor: 'pointer',
- transition: 'opacity 0.2s ease',
- }}
- onClick={() => window.open('/user-agreement.html', '_blank')}
- onMouseEnter={(e) => { e.currentTarget.style.opacity = '0.8'; }}
- onMouseLeave={(e) => { e.currentTarget.style.opacity = '1'; }}
- >
- 《使用协议》
- </span>
- 和
- <span
- style={{
- color: 'rgba(191, 157, 248, 1)',
- textDecoration: 'none',
- cursor: 'pointer',
- transition: 'opacity 0.2s ease',
- }}
- onClick={() => window.open('/privacy-policy.html', '_blank')}
- onMouseEnter={(e) => { e.currentTarget.style.opacity = '0.8'; }}
- onMouseLeave={(e) => { e.currentTarget.style.opacity = '1'; }}
- >
- 《隐私政策》
- </span>
- </div>
- </div>
- </div>
- </div>
- )}
- {/* 加载动画遮罩 */}
- {isLoading && (
- <div
- style={{
- position: 'fixed',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- background: 'rgba(0, 0, 0, 0.7)',
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- zIndex: 10000,
- backdropFilter: 'blur(4px)',
- }}
- >
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- gap: px2vmin(24),
- }}
- >
- {/* 加载动画 */}
- <div
- style={{
- position: 'relative',
- width: px2vmin(80),
- height: px2vmin(80),
- }}
- >
- <div
- style={{
- position: 'absolute',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- border: `${px2vmin(3)} solid transparent`,
- borderTopColor: 'rgba(255, 255, 255, 0.9)',
- borderRadius: '50%',
- animation: 'spin 1s linear infinite',
- }}
- />
- <div
- style={{
- position: 'absolute',
- top: '15%',
- left: '15%',
- width: '70%',
- height: '70%',
- border: `${px2vmin(3)} solid transparent`,
- borderTopColor: 'rgba(255, 255, 255, 0.7)',
- borderRadius: '50%',
- animation: 'spin 1.5s linear infinite reverse',
- }}
- />
- <div
- style={{
- position: 'absolute',
- top: '30%',
- left: '30%',
- width: '40%',
- height: '40%',
- border: `${px2vmin(3)} solid transparent`,
- borderTopColor: 'rgba(255, 255, 255, 0.5)',
- borderRadius: '50%',
- animation: 'spin 2s linear infinite',
- }}
- />
- </div>
- {/* 加载文本 */}
- <p
- style={{
- color: '#fff',
- fontWeight: 500,
- fontSize: px2vmin(16),
- textShadow: `0 ${px2vmin(2)} ${px2vmin(4)} rgba(0, 0, 0, 0.3)`,
- animation: 'pulse 1.5s ease-in-out infinite',
- }}
- >
- 视频加载中...
- </p>
- </div>
- </div>
- )}
-
- {/* 视频播放器容器 */}
- <div
- style={{
- position: 'fixed',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- zIndex: 10,
- transition: 'opacity 0.3s ease-in',
- opacity: viewState === 'playing' ? 1 : 0,
- visibility: viewState === 'playing' ? 'visible' : 'hidden',
- pointerEvents: viewState === 'playing' ? 'auto' : 'none',
- }}
- >
- <video
- ref={videoRef}
- className="video-js"
- playsInline
- webkit-playsinline="true"
- x5-playsinline="true"
- x5-video-player-type="h5"
- x5-video-player-fullscreen="false"
- data-setup="{}"
- controlsList="nodownload nofullscreen noremoteplayback"
- disablePictureInPicture
- preload="auto"
- style={{
- position: 'absolute',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- paddingTop: 0,
- background: 'transparent',
- zIndex: 1,
- }}
- >
- <source
- src={import.meta.env.VITE_WELCOME_VIDEO_URL || getOssUrl('Lyrria-video.mp4')}
- type="video/mp4"
- />
- <p className="vjs-no-js">
- 您的浏览器不支持HTML5视频,请升级浏览器。
- </p>
- </video>
- </div>
- </div>
- </>
- );
- };
|