公告增加每次显示模式

This commit is contained in:
mtvpls
2026-07-03 11:41:43 +08:00
parent ba34019bde
commit c705b2fd16
8 changed files with 96 additions and 3 deletions
+1
View File
@@ -457,6 +457,7 @@ dockge/komodo 等 docker compose UI 也有自动更新功能
| SITE_BASE | 站点 url | 形如 https://example.com | 空 |
| NEXT_PUBLIC_SITE_NAME | 站点名称 | 任意字符串 | MoonTV |
| ANNOUNCEMENT | 站点公告 | 任意字符串 | 本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。 |
| ANNOUNCEMENT_DISPLAY_MODE | 公告显示模式 | once、every | once |
| NEXT_PUBLIC_STORAGE_TYPE | 播放记录/收藏的存储方式 | redis、kvrocks、upstash、d1 | 无默认,必填字段 |
| KVROCKS_URL | kvrocks 连接 url | 连接 url | 空 |
| REDIS_URL | redis 连接 url | 连接 url | 空 |
+51
View File
@@ -357,6 +357,7 @@ interface StandaloneSourceScript {
interface SiteConfig {
SiteName: string;
Announcement: string;
AnnouncementDisplayMode?: 'once' | 'every';
SearchDownstreamMaxPage: number;
SiteInterfaceCacheTime: number;
DoubanProxyType: string;
@@ -10150,6 +10151,7 @@ const SiteConfigComponent = ({
const [siteSettings, setSiteSettings] = useState<SiteConfig>({
SiteName: '',
Announcement: '',
AnnouncementDisplayMode: 'once',
SearchDownstreamMaxPage: 1,
SiteInterfaceCacheTime: 7200,
DoubanProxyType: 'cmliussss-cdn-tencent',
@@ -10304,6 +10306,10 @@ const SiteConfigComponent = ({
config.SiteConfig.MagnetAcgripReverseProxy || '',
MagnetNyaaReverseProxy: config.SiteConfig.MagnetNyaaReverseProxy || '',
EnableComments: config.SiteConfig.EnableComments || false,
AnnouncementDisplayMode:
config.SiteConfig.AnnouncementDisplayMode === 'every'
? 'every'
: 'once',
});
}
}, [config]);
@@ -10462,6 +10468,51 @@ const SiteConfigComponent = ({
/>
</div>
{/* 公告显示模式 */}
<div>
<label className='block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2'>
</label>
<div className='flex gap-4'>
<label className='inline-flex items-center gap-2 cursor-pointer'>
<input
type='radio'
name='announcementDisplayMode'
value='once'
checked={siteSettings.AnnouncementDisplayMode !== 'every'}
onChange={() =>
setSiteSettings((prev) => ({
...prev,
AnnouncementDisplayMode: 'once',
}))
}
className='text-green-600 focus:ring-green-500'
/>
<span className='text-sm text-gray-700 dark:text-gray-300'>
</span>
</label>
<label className='inline-flex items-center gap-2 cursor-pointer'>
<input
type='radio'
name='announcementDisplayMode'
value='every'
checked={siteSettings.AnnouncementDisplayMode === 'every'}
onChange={() =>
setSiteSettings((prev) => ({
...prev,
AnnouncementDisplayMode: 'every',
}))
}
className='text-green-600 focus:ring-green-500'
/>
<span className='text-sm text-gray-700 dark:text-gray-300'>
</span>
</label>
</div>
</div>
{/* 豆瓣数据源设置 */}
<div className='space-y-3'>
<div>
+6
View File
@@ -31,6 +31,7 @@ export async function POST(request: NextRequest) {
const {
SiteName,
Announcement,
AnnouncementDisplayMode,
SearchDownstreamMaxPage,
SiteInterfaceCacheTime,
DoubanProxyType,
@@ -85,6 +86,7 @@ export async function POST(request: NextRequest) {
} = body as {
SiteName: string;
Announcement: string;
AnnouncementDisplayMode?: 'once' | 'every';
SearchDownstreamMaxPage: number;
SiteInterfaceCacheTime: number;
DoubanProxyType: string;
@@ -142,6 +144,9 @@ export async function POST(request: NextRequest) {
if (
typeof SiteName !== 'string' ||
typeof Announcement !== 'string' ||
(AnnouncementDisplayMode !== undefined &&
AnnouncementDisplayMode !== 'once' &&
AnnouncementDisplayMode !== 'every') ||
typeof SearchDownstreamMaxPage !== 'number' ||
typeof SiteInterfaceCacheTime !== 'number' ||
typeof DoubanProxyType !== 'string' ||
@@ -238,6 +243,7 @@ export async function POST(request: NextRequest) {
adminConfig.SiteConfig = {
SiteName,
Announcement,
AnnouncementDisplayMode,
SearchDownstreamMaxPage,
SiteInterfaceCacheTime,
DoubanProxyType,
+6
View File
@@ -58,6 +58,9 @@ export default async function RootLayout({
let announcement =
process.env.ANNOUNCEMENT ||
'本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。';
// 公告显示模式:从环境变量读取,数据库模式下由管理面板配置覆盖
let announcementDisplayMode: 'once' | 'every' =
process.env.ANNOUNCEMENT_DISPLAY_MODE === 'every' ? 'every' : 'once';
let doubanProxyType =
process.env.NEXT_PUBLIC_DOUBAN_PROXY_TYPE || 'cmliussss-cdn-tencent';
@@ -136,6 +139,8 @@ export default async function RootLayout({
const config = await getConfig();
siteName = config.SiteConfig.SiteName;
announcement = config.SiteConfig.Announcement;
announcementDisplayMode =
config.SiteConfig.AnnouncementDisplayMode === 'every' ? 'every' : 'once';
doubanProxyType = config.SiteConfig.DoubanProxyType;
doubanProxy = config.SiteConfig.DoubanProxy;
@@ -346,6 +351,7 @@ export default async function RootLayout({
<SiteProvider
siteName={siteName}
announcement={announcement}
announcementDisplayMode={announcementDisplayMode}
tmdbApiKey={tmdbApiKey}
>
<WatchRoomProvider>
+14 -2
View File
@@ -53,7 +53,7 @@ function HomeClient() {
BangumiCalendarData[]
>([]);
const [loading, setLoading] = useState(true);
const { announcement } = useSite();
const { announcement, announcementDisplayMode } = useSite();
// 首页模块配置状态
const [homeModules, setHomeModules] = useState<HomeModule[]>([
{ id: 'hotMovies', name: '热门电影', enabled: true, order: 0 },
@@ -362,6 +362,17 @@ function HomeClient() {
// 检查公告弹窗状态
useEffect(() => {
if (typeof window !== 'undefined' && announcement) {
// 会话级标记:只在首次访问站点时弹出,导航切回首页不重复弹
if (sessionStorage.getItem('announcementShown')) {
return;
}
// 每次显示模式:每次新会话首次访问弹出一次
if (announcementDisplayMode === 'every') {
setShowAnnouncement(true);
sessionStorage.setItem('announcementShown', '1');
return;
}
// 单次显示模式:localStorage 记住已看过的公告文本,换公告则重新弹出
const hasSeenAnnouncement = localStorage.getItem('hasSeenAnnouncement');
if (hasSeenAnnouncement !== announcement) {
setShowAnnouncement(true);
@@ -369,7 +380,8 @@ function HomeClient() {
setShowAnnouncement(Boolean(!hasSeenAnnouncement && announcement));
}
}
}, [announcement]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [announcement, announcementDisplayMode]);
useEffect(() => {
const CACHE_DURATION = 60 * 60 * 1000; // 1小时
+7 -1
View File
@@ -5,12 +5,14 @@ import { createContext, ReactNode, useContext } from 'react';
const SiteContext = createContext<{
siteName: string;
announcement?: string;
announcementDisplayMode?: 'once' | 'every';
tmdbApiKey?: string;
}>({
// 默认值
siteName: 'MoonTVPlus',
announcement:
'本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。',
announcementDisplayMode: 'once',
tmdbApiKey: '',
});
@@ -20,15 +22,19 @@ export function SiteProvider({
children,
siteName,
announcement,
announcementDisplayMode,
tmdbApiKey,
}: {
children: ReactNode;
siteName: string;
announcement?: string;
announcementDisplayMode?: 'once' | 'every';
tmdbApiKey?: string;
}) {
return (
<SiteContext.Provider value={{ siteName, announcement, tmdbApiKey }}>
<SiteContext.Provider
value={{ siteName, announcement, announcementDisplayMode, tmdbApiKey }}
>
{children}
</SiteContext.Provider>
);
+2
View File
@@ -8,6 +8,8 @@ export interface AdminConfig {
SiteConfig: {
SiteName: string;
Announcement: string;
// 公告显示模式:once=单次显示(每个用户每条公告仅显示一次,换公告则重新显示);every=每次显示(每次打开首页都显示)
AnnouncementDisplayMode?: 'once' | 'every';
SearchDownstreamMaxPage: number;
SiteInterfaceCacheTime: number;
DoubanProxyType: string;
+9
View File
@@ -265,6 +265,10 @@ async function getInitConfig(
Announcement:
process.env.ANNOUNCEMENT ||
'本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。',
AnnouncementDisplayMode:
process.env.ANNOUNCEMENT_DISPLAY_MODE === 'every'
? 'every'
: 'once',
SearchDownstreamMaxPage:
Number(process.env.NEXT_PUBLIC_SEARCH_MAX_PAGE) || 5,
SiteInterfaceCacheTime: cfgFile.cache_time || 7200,
@@ -555,6 +559,11 @@ export function configSelfCheck(adminConfig: AdminConfig): AdminConfig {
if (adminConfig.SiteConfig.EnableComments === undefined) {
adminConfig.SiteConfig.EnableComments = false;
}
// 确保公告显示模式存在
if (adminConfig.SiteConfig.AnnouncementDisplayMode === undefined) {
adminConfig.SiteConfig.AnnouncementDisplayMode =
process.env.ANNOUNCEMENT_DISPLAY_MODE === 'every' ? 'every' : 'once';
}
if (adminConfig.SiteConfig.EnableRegistration === undefined) {
adminConfig.SiteConfig.EnableRegistration = false;
}