增加更明显的更新提示
This commit is contained in:
@@ -5,6 +5,7 @@ import Link from 'next/link';
|
|||||||
import { BackButton } from './BackButton';
|
import { BackButton } from './BackButton';
|
||||||
import { useSite } from './SiteProvider';
|
import { useSite } from './SiteProvider';
|
||||||
import { ThemeToggle } from './ThemeToggle';
|
import { ThemeToggle } from './ThemeToggle';
|
||||||
|
import { UpdateNotification } from './UpdateNotification';
|
||||||
import { UserMenu } from './UserMenu';
|
import { UserMenu } from './UserMenu';
|
||||||
|
|
||||||
interface MobileHeaderProps {
|
interface MobileHeaderProps {
|
||||||
@@ -45,6 +46,7 @@ const MobileHeader = ({ showBackButton = false }: MobileHeaderProps) => {
|
|||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
<UserMenu />
|
<UserMenu />
|
||||||
|
<UpdateNotification />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import MobileBottomNav from './MobileBottomNav';
|
|||||||
import MobileHeader from './MobileHeader';
|
import MobileHeader from './MobileHeader';
|
||||||
import Sidebar from './Sidebar';
|
import Sidebar from './Sidebar';
|
||||||
import { ThemeToggle } from './ThemeToggle';
|
import { ThemeToggle } from './ThemeToggle';
|
||||||
|
import { UpdateNotification } from './UpdateNotification';
|
||||||
import { UserMenu } from './UserMenu';
|
import { UserMenu } from './UserMenu';
|
||||||
|
import { VersionCheckProvider } from './VersionCheckProvider';
|
||||||
|
|
||||||
interface PageLayoutProps {
|
interface PageLayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -12,49 +14,52 @@ interface PageLayoutProps {
|
|||||||
|
|
||||||
const PageLayout = ({ children, activePath = '/' }: PageLayoutProps) => {
|
const PageLayout = ({ children, activePath = '/' }: PageLayoutProps) => {
|
||||||
return (
|
return (
|
||||||
<div className='w-full min-h-screen'>
|
<VersionCheckProvider>
|
||||||
{/* 移动端头部 */}
|
<div className='w-full min-h-screen'>
|
||||||
<MobileHeader showBackButton={['/play', '/live'].includes(activePath)} />
|
{/* 移动端头部 */}
|
||||||
|
<MobileHeader showBackButton={['/play', '/live'].includes(activePath)} />
|
||||||
|
|
||||||
{/* 主要布局容器 */}
|
{/* 主要布局容器 */}
|
||||||
<div className='flex md:grid md:grid-cols-[auto_1fr] w-full min-h-screen md:min-h-auto'>
|
<div className='flex md:grid md:grid-cols-[auto_1fr] w-full min-h-screen md:min-h-auto'>
|
||||||
{/* 侧边栏 - 桌面端显示,移动端隐藏 */}
|
{/* 侧边栏 - 桌面端显示,移动端隐藏 */}
|
||||||
<div className='hidden md:block'>
|
<div className='hidden md:block'>
|
||||||
<Sidebar activePath={activePath} />
|
<Sidebar activePath={activePath} />
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 主内容区域 */}
|
|
||||||
<div className='relative min-w-0 flex-1 transition-all duration-300'>
|
|
||||||
{/* 桌面端左上角返回按钮 */}
|
|
||||||
{['/play', '/live'].includes(activePath) && (
|
|
||||||
<div className='absolute top-3 left-1 z-20 hidden md:flex'>
|
|
||||||
<BackButton />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 桌面端顶部按钮 */}
|
|
||||||
<div className='absolute top-2 right-4 z-20 hidden md:flex items-center gap-2'>
|
|
||||||
<ThemeToggle />
|
|
||||||
<UserMenu />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 主内容 */}
|
{/* 主内容区域 */}
|
||||||
<main
|
<div className='relative min-w-0 flex-1 transition-all duration-300'>
|
||||||
className='flex-1 md:min-h-0 mb-14 md:mb-0 md:mt-0 mt-12'
|
{/* 桌面端左上角返回按钮 */}
|
||||||
style={{
|
{['/play', '/live'].includes(activePath) && (
|
||||||
paddingBottom: 'calc(3.5rem + env(safe-area-inset-bottom))',
|
<div className='absolute top-3 left-1 z-20 hidden md:flex'>
|
||||||
}}
|
<BackButton />
|
||||||
>
|
</div>
|
||||||
{children}
|
)}
|
||||||
</main>
|
|
||||||
|
{/* 桌面端顶部按钮 */}
|
||||||
|
<div className='absolute top-2 right-4 z-20 hidden md:flex items-center gap-2'>
|
||||||
|
<ThemeToggle />
|
||||||
|
<UserMenu />
|
||||||
|
<UpdateNotification />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 主内容 */}
|
||||||
|
<main
|
||||||
|
className='flex-1 md:min-h-0 mb-14 md:mb-0 md:mt-0 mt-12'
|
||||||
|
style={{
|
||||||
|
paddingBottom: 'calc(3.5rem + env(safe-area-inset-bottom))',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 移动端底部导航 */}
|
||||||
|
<div className='md:hidden'>
|
||||||
|
<MobileBottomNav activePath={activePath} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</VersionCheckProvider>
|
||||||
{/* 移动端底部导航 */}
|
|
||||||
<div className='md:hidden'>
|
|
||||||
<MobileBottomNav activePath={activePath} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
|
||||||
|
import { UpdateStatus } from '@/lib/version_check';
|
||||||
|
|
||||||
|
import { useVersionCheck } from './VersionCheckProvider';
|
||||||
|
import { VersionPanel } from './VersionPanel';
|
||||||
|
|
||||||
|
export const UpdateNotification: React.FC = () => {
|
||||||
|
const { updateStatus, isChecking } = useVersionCheck();
|
||||||
|
const [isOwner, setIsOwner] = useState(false);
|
||||||
|
const [isVersionPanelOpen, setIsVersionPanelOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 检查认证信息
|
||||||
|
const authInfo = getAuthInfoFromBrowserCookie();
|
||||||
|
setIsOwner(authInfo?.role === 'owner');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 检查中、不是站长或没有更新时不渲染任何内容
|
||||||
|
if (isChecking || !isOwner || updateStatus !== UpdateStatus.HAS_UPDATE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsVersionPanelOpen(true)}
|
||||||
|
className='flex items-center px-3 py-1.5 bg-yellow-100 dark:bg-yellow-900/30 border border-yellow-300 dark:border-yellow-700 rounded-full hover:bg-yellow-200 dark:hover:bg-yellow-900/50 transition-colors cursor-pointer'
|
||||||
|
>
|
||||||
|
<span className='text-xs font-medium text-yellow-800 dark:text-yellow-300'>
|
||||||
|
有更新
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* 版本面板 */}
|
||||||
|
<VersionPanel
|
||||||
|
isOpen={isVersionPanelOpen}
|
||||||
|
onClose={() => setIsVersionPanelOpen(false)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -20,8 +20,9 @@ import { createPortal } from 'react-dom';
|
|||||||
import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
|
import { getAuthInfoFromBrowserCookie } from '@/lib/auth';
|
||||||
import { clearAllDanmakuCache } from '@/lib/danmaku/api';
|
import { clearAllDanmakuCache } from '@/lib/danmaku/api';
|
||||||
import { CURRENT_VERSION } from '@/lib/version';
|
import { CURRENT_VERSION } from '@/lib/version';
|
||||||
import { checkForUpdates, UpdateStatus } from '@/lib/version_check';
|
import { UpdateStatus } from '@/lib/version_check';
|
||||||
|
|
||||||
|
import { useVersionCheck } from './VersionCheckProvider';
|
||||||
import { VersionPanel } from './VersionPanel';
|
import { VersionPanel } from './VersionPanel';
|
||||||
|
|
||||||
interface AuthInfo {
|
interface AuthInfo {
|
||||||
@@ -31,6 +32,7 @@ interface AuthInfo {
|
|||||||
|
|
||||||
export const UserMenu: React.FC = () => {
|
export const UserMenu: React.FC = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { updateStatus, isChecking } = useVersionCheck();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||||
const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false);
|
const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false);
|
||||||
@@ -106,10 +108,6 @@ export const UserMenu: React.FC = () => {
|
|||||||
const [passwordLoading, setPasswordLoading] = useState(false);
|
const [passwordLoading, setPasswordLoading] = useState(false);
|
||||||
const [passwordError, setPasswordError] = useState('');
|
const [passwordError, setPasswordError] = useState('');
|
||||||
|
|
||||||
// 版本检查相关状态
|
|
||||||
const [updateStatus, setUpdateStatus] = useState<UpdateStatus | null>(null);
|
|
||||||
const [isChecking, setIsChecking] = useState(true);
|
|
||||||
|
|
||||||
// 清除弹幕缓存相关状态
|
// 清除弹幕缓存相关状态
|
||||||
const [isClearingCache, setIsClearingCache] = useState(false);
|
const [isClearingCache, setIsClearingCache] = useState(false);
|
||||||
const [clearCacheMessage, setClearCacheMessage] = useState<string | null>(null);
|
const [clearCacheMessage, setClearCacheMessage] = useState<string | null>(null);
|
||||||
@@ -203,22 +201,6 @@ export const UserMenu: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 版本检查
|
|
||||||
useEffect(() => {
|
|
||||||
const checkUpdate = async () => {
|
|
||||||
try {
|
|
||||||
const status = await checkForUpdates();
|
|
||||||
setUpdateStatus(status);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('版本检查失败:', error);
|
|
||||||
} finally {
|
|
||||||
setIsChecking(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
checkUpdate();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 点击外部区域关闭下拉框
|
// 点击外部区域关闭下拉框
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { checkForUpdates, UpdateStatus } from '@/lib/version_check';
|
||||||
|
|
||||||
|
interface VersionCheckContextType {
|
||||||
|
updateStatus: UpdateStatus | null;
|
||||||
|
isChecking: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VersionCheckContext = createContext<VersionCheckContextType>({
|
||||||
|
updateStatus: null,
|
||||||
|
isChecking: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useVersionCheck = () => useContext(VersionCheckContext);
|
||||||
|
|
||||||
|
export const VersionCheckProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const [updateStatus, setUpdateStatus] = useState<UpdateStatus | null>(null);
|
||||||
|
const [isChecking, setIsChecking] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkUpdate = async () => {
|
||||||
|
try {
|
||||||
|
const status = await checkForUpdates();
|
||||||
|
setUpdateStatus(status);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('版本检查失败:', error);
|
||||||
|
} finally {
|
||||||
|
setIsChecking(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkUpdate();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VersionCheckContext.Provider value={{ updateStatus, isChecking }}>
|
||||||
|
{children}
|
||||||
|
</VersionCheckContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user