feat(公告): 添加网站公告功能及弹窗提示

- 从环境变量读取公告内容,提供默认值
- 扩展SiteProvider组件以支持公告功能
- 在首页添加公告弹窗组件,支持本地存储记录用户已读状态
- 弹窗包含关闭功能和完善的样式交互
This commit is contained in:
SongPro
2025-07-01 16:02:33 +08:00
parent b045967f9c
commit 6c81f52953
3 changed files with 64 additions and 4 deletions

View File

@@ -29,6 +29,9 @@ export default function RootLayout({
children: React.ReactNode;
}) {
const siteName = process.env.SITE_NAME || 'MoonTV';
const announcement =
process.env.ANNOUNCEMENT ||
'本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。';
return (
<html lang='zh-CN' suppressHydrationWarning>
@@ -41,7 +44,7 @@ export default function RootLayout({
enableSystem
disableTransitionOnChange
>
<SiteProvider siteName={siteName}>
<SiteProvider siteName={siteName} announcement={announcement}>
<AuthProvider>{children}</AuthProvider>
</SiteProvider>
</ThemeProvider>

View File

@@ -13,6 +13,7 @@ import ContinueWatching from '@/components/ContinueWatching';
import DemoCard from '@/components/DemoCard';
import PageLayout from '@/components/PageLayout';
import ScrollableRow from '@/components/ScrollableRow';
import { useSite } from '@/components/SiteProvider';
import VideoCard from '@/components/VideoCard';
function HomeClient() {
@@ -20,6 +21,16 @@ function HomeClient() {
const [hotMovies, setHotMovies] = useState<DoubanItem[]>([]);
const [hotTvShows, setHotTvShows] = useState<DoubanItem[]>([]);
const [loading, setLoading] = useState(true);
const { announcement } = useSite();
const [showAnnouncement, setShowAnnouncement] = useState(() => {
// 检查本地存储中是否已记录弹窗显示状态
const hasSeenAnnouncement = localStorage.getItem('hasSeenAnnouncement');
if (hasSeenAnnouncement !== announcement) {
return true;
}
return !hasSeenAnnouncement && announcement; // 未记录且有公告时显示弹窗
});
// 收藏夹数据
type FavoriteItem = {
@@ -88,6 +99,11 @@ function HomeClient() {
})();
}, [activeTab]);
const handleCloseAnnouncement = (announcement: string) => {
setShowAnnouncement(false);
localStorage.setItem('hasSeenAnnouncement', announcement); // 记录已查看弹窗
};
return (
<PageLayout>
<div className='px-2 sm:px-10 py-4 sm:py-8 overflow-visible'>
@@ -233,6 +249,40 @@ function HomeClient() {
)}
</div>
</div>
{announcement && showAnnouncement && (
<div
className={`fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm dark:bg-black/70 p-4 transition-opacity duration-300 ${
showAnnouncement ? '' : 'opacity-0 pointer-events-none'
}`}
>
<div className='w-full max-w-md rounded-xl bg-white p-6 shadow-xl dark:bg-gray-900 transform transition-all duration-300 hover:shadow-2xl'>
<div className='flex justify-between items-start mb-4'>
<h3 className='text-2xl font-bold tracking-tight text-gray-800 dark:text-white border-b border-green-500 pb-1'>
</h3>
<button
onClick={() => handleCloseAnnouncement(announcement)}
className='text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-white transition-colors'
aria-label='关闭'
></button>
</div>
<div className='mb-6'>
<div className='relative overflow-hidden rounded-lg mb-4 bg-green-50 dark:bg-green-900/20'>
<div className='absolute inset-y-0 left-0 w-1.5 bg-green-500 dark:bg-green-400'></div>
<p className='ml-4 text-gray-600 dark:text-gray-300 leading-relaxed'>
{announcement}
</p>
</div>
</div>
<button
onClick={() => handleCloseAnnouncement(announcement)}
className='w-full rounded-lg bg-gradient-to-r from-green-600 to-green-700 px-4 py-3 text-white font-medium shadow-md hover:shadow-lg hover:from-green-700 hover:to-green-800 dark:from-green-600 dark:to-green-700 dark:hover:from-green-700 dark:hover:to-green-800 transition-all duration-300 transform hover:-translate-y-0.5'
>
</button>
</div>
</div>
)}
</PageLayout>
);
}

View File

@@ -2,8 +2,11 @@
import { createContext, ReactNode, useContext } from 'react';
const SiteContext = createContext<{ siteName: string }>({
siteName: 'MoonTV', // Default value
const SiteContext = createContext<{ siteName: string; announcement?: string }>({
// 默认值
siteName: 'MoonTV',
announcement:
'本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。',
});
export const useSite = () => useContext(SiteContext);
@@ -11,11 +14,15 @@ export const useSite = () => useContext(SiteContext);
export function SiteProvider({
children,
siteName,
announcement,
}: {
children: ReactNode;
siteName: string;
announcement?: string;
}) {
return (
<SiteContext.Provider value={{ siteName }}>{children}</SiteContext.Provider>
<SiteContext.Provider value={{ siteName, announcement }}>
{children}
</SiteContext.Provider>
);
}