修正观影室管理面板设置

This commit is contained in:
mtvpls
2025-12-08 10:03:25 +08:00
parent 857f7ce73e
commit b99a495a8a
7 changed files with 322 additions and 68 deletions
@@ -0,0 +1,49 @@
/* eslint-disable no-console */
import { NextRequest, NextResponse } from 'next/server';
import { getConfig } from '@/lib/config';
export const runtime = 'nodejs';
export async function GET(request: NextRequest) {
try {
const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
// 调试信息
const debugInfo = {
storageType,
envVars: {
hasRedisUrl: !!process.env.REDIS_URL,
hasUpstashUrl: !!process.env.UPSTASH_REDIS_REST_URL,
hasUpstashToken: !!process.env.UPSTASH_REDIS_REST_TOKEN,
hasKvrocksUrl: !!process.env.KVROCKS_URL,
},
watchRoomConfig: null as any,
configReadError: null as string | null,
};
// 尝试读取配置
try {
const config = await getConfig();
debugInfo.watchRoomConfig = config.WatchRoomConfig || null;
} catch (error) {
debugInfo.configReadError = (error as Error).message;
}
return NextResponse.json(debugInfo, {
headers: {
'Cache-Control': 'no-store',
},
});
} catch (error) {
console.error('Debug API error:', error);
return NextResponse.json(
{
error: 'Failed to get debug info',
details: (error as Error).message,
},
{ status: 500 }
);
}
}
+28 -1
View File
@@ -10,11 +10,38 @@ export const runtime = 'nodejs';
export async function GET(request: NextRequest) {
console.log('server-config called: ', request.url);
const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
// 如果使用 localStorage,返回默认配置
if (storageType === 'localstorage') {
return NextResponse.json({
SiteName: process.env.NEXT_PUBLIC_SITE_NAME || 'MoonTV',
StorageType: 'localstorage',
Version: CURRENT_VERSION,
// localStorage 模式下,返回默认观影室配置
// 可以通过环境变量控制是否启用
WatchRoom: {
enabled: process.env.WATCH_ROOM_ENABLED === 'true',
serverType: 'internal',
externalServerUrl: undefined,
externalServerAuth: undefined,
},
});
}
// 非 localStorage 模式,从数据库读取配置
const config = await getConfig();
const result = {
SiteName: config.SiteConfig.SiteName,
StorageType: process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage',
StorageType: storageType,
Version: CURRENT_VERSION,
// 添加观影室配置(供所有用户访问)
WatchRoom: {
enabled: config.WatchRoomConfig?.enabled ?? false,
serverType: config.WatchRoomConfig?.serverType ?? 'internal',
externalServerUrl: config.WatchRoomConfig?.externalServerUrl,
externalServerAuth: config.WatchRoomConfig?.externalServerAuth,
},
};
return NextResponse.json(result);
}
+53 -15
View File
@@ -7,6 +7,8 @@ import Link from 'next/link';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useWatchRoomContextSafe } from './WatchRoomProvider';
interface MobileBottomNavProps {
/**
* 主动指定当前激活的路径。当未提供时,自动使用 usePathname() 获取的路径。
@@ -17,6 +19,7 @@ interface MobileBottomNavProps {
const MobileBottomNav = ({ activePath }: MobileBottomNavProps) => {
const pathname = usePathname();
const searchParams = useSearchParams();
const watchRoomContext = useWatchRoomContextSafe();
// 直接使用当前路由状态,确保立即响应路由变化
const getCurrentFullPath = () => {
@@ -52,26 +55,61 @@ const MobileBottomNav = ({ activePath }: MobileBottomNavProps) => {
label: '直播',
href: '/live',
},
{
icon: Users,
label: '观影室',
href: '/watch-room',
},
]);
useEffect(() => {
const runtimeConfig = (window as any).RUNTIME_CONFIG;
if (runtimeConfig?.CUSTOM_CATEGORIES?.length > 0) {
setNavItems((prevItems) => [
...prevItems,
{
icon: Star,
label: '自定义',
href: '/douban?type=custom',
},
]);
// 基础导航项(不包括观影室)
let items = [
{ icon: Home, label: '首页', href: '/' },
{
icon: Film,
label: '电影',
href: '/douban?type=movie',
},
{
icon: Tv,
label: '剧集',
href: '/douban?type=tv',
},
{
icon: Cat,
label: '动漫',
href: '/douban?type=anime',
},
{
icon: Clover,
label: '综艺',
href: '/douban?type=show',
},
{
icon: Radio,
label: '直播',
href: '/live',
},
];
// 如果启用观影室,添加观影室入口
if (watchRoomContext?.isEnabled) {
items.push({
icon: Users,
label: '观影室',
href: '/watch-room',
});
}
}, []);
// 添加自定义分类(如果有)
if (runtimeConfig?.CUSTOM_CATEGORIES?.length > 0) {
items.push({
icon: Star,
label: '自定义',
href: '/douban?type=custom',
});
}
setNavItems(items);
}, [watchRoomContext?.isEnabled]);
const isActive = (href: string) => {
const typeMatch = href.match(/type=([^&]+)/)?.[1];
+51 -15
View File
@@ -15,6 +15,7 @@ import {
} from 'react';
import { useSite } from './SiteProvider';
import { useWatchRoomContextSafe } from './WatchRoomProvider';
interface SidebarContextType {
isCollapsed: boolean;
@@ -59,6 +60,7 @@ declare global {
const Sidebar = ({ onToggle, activePath = '/' }: SidebarProps) => {
const pathname = usePathname();
const searchParams = useSearchParams();
const watchRoomContext = useWatchRoomContextSafe();
// 若同一次 SPA 会话中已经读取过折叠状态,则直接复用,避免闪烁
const [isCollapsed, setIsCollapsed] = useState<boolean>(() => {
if (
@@ -143,26 +145,60 @@ const Sidebar = ({ onToggle, activePath = '/' }: SidebarProps) => {
label: '直播',
href: '/live',
},
{
icon: Users,
label: '观影室',
href: '/watch-room',
},
]);
useEffect(() => {
const runtimeConfig = (window as any).RUNTIME_CONFIG;
if (runtimeConfig?.CUSTOM_CATEGORIES?.length > 0) {
setMenuItems((prevItems) => [
...prevItems,
{
icon: Star,
label: '自定义',
href: '/douban?type=custom',
},
]);
// 基础菜单项(不包括观影室)
let items = [
{
icon: Film,
label: '电影',
href: '/douban?type=movie',
},
{
icon: Tv,
label: '剧集',
href: '/douban?type=tv',
},
{
icon: Cat,
label: '动漫',
href: '/douban?type=anime',
},
{
icon: Clover,
label: '综艺',
href: '/douban?type=show',
},
{
icon: Radio,
label: '直播',
href: '/live',
},
];
// 如果启用观影室,添加观影室入口
if (watchRoomContext?.isEnabled) {
items.push({
icon: Users,
label: '观影室',
href: '/watch-room',
});
}
}, []);
// 添加自定义分类(如果有)
if (runtimeConfig?.CUSTOM_CATEGORIES?.length > 0) {
items.push({
icon: Star,
label: '自定义',
href: '/douban?type=custom',
});
}
setMenuItems(items);
}, [watchRoomContext?.isEnabled]);
return (
<SidebarContext.Provider value={contextValue}>
+25 -21
View File
@@ -109,43 +109,47 @@ export function WatchRoomProvider({ children }: WatchRoomProviderProps) {
// 加载配置
useEffect(() => {
const loadConfig = async () => {
// 默认配置:启用内部服务器
const defaultConfig: WatchRoomConfig = {
enabled: true,
serverType: 'internal',
};
try {
const response = await fetch('/api/admin/config');
// 使用公共 API 获取观影室配置(不需要管理员权限)
const response = await fetch('/api/server-config');
if (response.ok) {
const data = await response.json();
// API 返回格式: { SiteName, StorageType, Version, WatchRoom }
const watchRoomConfig: WatchRoomConfig = {
enabled: data.watchRoom?.enabled ?? true,
serverType: data.watchRoom?.serverType ?? 'internal',
externalServerUrl: data.watchRoom?.externalServerUrl,
externalServerAuth: data.watchRoom?.externalServerAuth,
enabled: data.WatchRoom?.enabled ?? false, // 默认不启用
serverType: data.WatchRoom?.serverType ?? 'internal',
externalServerUrl: data.WatchRoom?.externalServerUrl,
externalServerAuth: data.WatchRoom?.externalServerAuth,
};
setConfig(watchRoomConfig);
setIsEnabled(watchRoomConfig.enabled);
// 如果启用了观影室,自动连接
// 只在启用了观影室时才连接
if (watchRoomConfig.enabled) {
console.log('[WatchRoom] Connecting with config:', watchRoomConfig);
await watchRoom.connect(watchRoomConfig);
} else {
console.log('[WatchRoom] Watch room is disabled, skipping connection');
}
} else {
throw new Error('Failed to load config');
console.error('[WatchRoom] Failed to load config:', response.status);
// 加载配置失败时,不连接,保持禁用状态
const defaultConfig: WatchRoomConfig = {
enabled: false,
serverType: 'internal',
};
setConfig(defaultConfig);
setIsEnabled(false);
}
} catch (error) {
console.log('[WatchRoom] Using default config (internal server enabled)');
console.error('[WatchRoom] Error loading config:', error);
// 加载配置失败时,不连接,保持禁用状态
const defaultConfig: WatchRoomConfig = {
enabled: false,
serverType: 'internal',
};
setConfig(defaultConfig);
setIsEnabled(true);
try {
await watchRoom.connect(defaultConfig);
} catch (connectError) {
console.error('[WatchRoom] Failed to connect:', connectError);
}
setIsEnabled(false);
}
};