mirror of
https://github.com/MoonTechLab/LunaTV.git
synced 2026-02-22 10:34:42 +08:00
feat: add right sidebar collapse
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { getStorage, IStorage } from '@/lib/db';
|
||||
import { getStorage } from '@/lib/db';
|
||||
import { IStorage } from '@/lib/types';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
import { getConfig } from '@/lib/config';
|
||||
import { getStorage, IStorage } from '@/lib/db';
|
||||
import { getStorage } from '@/lib/db';
|
||||
import { IStorage } from '@/lib/types';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { db, Favorite } from '@/lib/db';
|
||||
import { db } from '@/lib/db';
|
||||
import { Favorite } from '@/lib/types';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { db } from '@/lib/db';
|
||||
import { PlayRecord } from '@/lib/db';
|
||||
import { PlayRecord } from '@/lib/types';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
|
||||
@@ -109,6 +109,10 @@ function PlayPageClient() {
|
||||
null
|
||||
);
|
||||
|
||||
// 折叠状态(仅在 lg 及以上屏幕有效)
|
||||
const [isEpisodeSelectorCollapsed, setIsEpisodeSelectorCollapsed] =
|
||||
useState(false);
|
||||
|
||||
// 播放进度保存相关
|
||||
const saveIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const lastSaveTimeRef = useRef<number>(0);
|
||||
@@ -1207,7 +1211,7 @@ function PlayPageClient() {
|
||||
|
||||
return (
|
||||
<PageLayout activePath='/play'>
|
||||
<div className='flex flex-col gap-6 py-4 px-5 lg:px-10 xl:px-20'>
|
||||
<div className='flex flex-col gap-3 py-4 px-5 lg:px-10 xl:px-20'>
|
||||
{/* 第一行:影片标题 */}
|
||||
<div className='py-1'>
|
||||
<h1 className='text-xl font-semibold text-gray-900 dark:text-gray-100'>
|
||||
@@ -1220,30 +1224,89 @@ function PlayPageClient() {
|
||||
</h1>
|
||||
</div>
|
||||
{/* 第二行:播放器和选集 */}
|
||||
<div className='grid grid-cols-1 md:grid-cols-4 gap-4 lg:h-[500px] xl:h-[650px]'>
|
||||
{/* 播放器 */}
|
||||
<div className='md:col-span-3 h-full'>
|
||||
<div
|
||||
ref={artRef}
|
||||
className='bg-black w-full h-[300px] lg:h-full rounded-xl overflow-hidden border border-white/0 dark:border-white/30'
|
||||
></div>
|
||||
<div className='space-y-2'>
|
||||
{/* 折叠控制 - 仅在 lg 及以上屏幕显示 */}
|
||||
<div className='hidden lg:flex justify-end'>
|
||||
<button
|
||||
onClick={() =>
|
||||
setIsEpisodeSelectorCollapsed(!isEpisodeSelectorCollapsed)
|
||||
}
|
||||
className='group relative flex items-center space-x-1.5 px-3 py-1.5 rounded-full bg-white/80 hover:bg-white dark:bg-gray-800/80 dark:hover:bg-gray-800 backdrop-blur-sm border border-gray-200/50 dark:border-gray-700/50 shadow-sm hover:shadow-md transition-all duration-200'
|
||||
title={
|
||||
isEpisodeSelectorCollapsed ? '显示选集面板' : '隐藏选集面板'
|
||||
}
|
||||
>
|
||||
<svg
|
||||
className={`w-3.5 h-3.5 text-gray-500 dark:text-gray-400 transition-transform duration-200 ${
|
||||
isEpisodeSelectorCollapsed ? 'rotate-180' : 'rotate-0'
|
||||
}`}
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
viewBox='0 0 24 24'
|
||||
>
|
||||
<path
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
strokeWidth='2'
|
||||
d='M9 5l7 7-7 7'
|
||||
/>
|
||||
</svg>
|
||||
<span className='text-xs font-medium text-gray-600 dark:text-gray-300'>
|
||||
{isEpisodeSelectorCollapsed ? '显示' : '隐藏'}
|
||||
</span>
|
||||
|
||||
{/* 精致的状态指示点 */}
|
||||
<div
|
||||
className={`absolute -top-0.5 -right-0.5 w-2 h-2 rounded-full transition-all duration-200 ${
|
||||
isEpisodeSelectorCollapsed
|
||||
? 'bg-orange-400 animate-pulse'
|
||||
: 'bg-green-400'
|
||||
}`}
|
||||
></div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 选集和换源 */}
|
||||
<div className='md:col-span-1 h-[300px] lg:h-full md:overflow-hidden'>
|
||||
<EpisodeSelector
|
||||
totalEpisodes={totalEpisodes}
|
||||
value={currentEpisodeIndex + 1}
|
||||
onChange={handleEpisodeChange}
|
||||
onSourceChange={handleSourceChange}
|
||||
currentSource={currentSource}
|
||||
currentId={currentId}
|
||||
videoTitle={videoTitle}
|
||||
availableSources={availableSources}
|
||||
onSearchSources={handleSearchSources}
|
||||
sourceSearchLoading={sourceSearchLoading}
|
||||
sourceSearchError={sourceSearchError}
|
||||
/>
|
||||
<div
|
||||
className={`grid gap-4 lg:h-[500px] xl:h-[650px] transition-all duration-300 ease-in-out ${
|
||||
isEpisodeSelectorCollapsed
|
||||
? 'grid-cols-1'
|
||||
: 'grid-cols-1 md:grid-cols-4'
|
||||
}`}
|
||||
>
|
||||
{/* 播放器 */}
|
||||
<div
|
||||
className={`h-full transition-all duration-300 ease-in-out ${
|
||||
isEpisodeSelectorCollapsed ? 'col-span-1' : 'md:col-span-3'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
ref={artRef}
|
||||
className='bg-black w-full h-[300px] lg:h-full rounded-xl overflow-hidden border border-white/0 dark:border-white/30 shadow-lg'
|
||||
></div>
|
||||
</div>
|
||||
|
||||
{/* 选集和换源 - 在移动端始终显示,在 lg 及以上可折叠 */}
|
||||
<div
|
||||
className={`h-[300px] lg:h-full md:overflow-hidden transition-all duration-300 ease-in-out ${
|
||||
isEpisodeSelectorCollapsed
|
||||
? 'md:col-span-1 lg:hidden lg:opacity-0 lg:scale-95'
|
||||
: 'md:col-span-1 lg:opacity-100 lg:scale-100'
|
||||
}`}
|
||||
>
|
||||
<EpisodeSelector
|
||||
totalEpisodes={totalEpisodes}
|
||||
value={currentEpisodeIndex + 1}
|
||||
onChange={handleEpisodeChange}
|
||||
onSourceChange={handleSourceChange}
|
||||
currentSource={currentSource}
|
||||
currentId={currentId}
|
||||
videoTitle={videoTitle}
|
||||
availableSources={availableSources}
|
||||
onSearchSources={handleSearchSources}
|
||||
sourceSearchLoading={sourceSearchLoading}
|
||||
sourceSearchError={sourceSearchError}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import { AdminConfig } from './admin.types';
|
||||
import { RedisStorage } from './redis.db';
|
||||
import { AdminConfig, Favorite, IStorage, PlayRecord } from './types';
|
||||
|
||||
// 重新导出类型,保持向后兼容
|
||||
export type { AdminConfig, Favorite, IStorage, PlayRecord };
|
||||
import { Favorite, IStorage, PlayRecord } from './types';
|
||||
|
||||
// storage type 常量: 'localstorage' | 'database',默认 'localstorage'
|
||||
const STORAGE_TYPE =
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import { createClient, RedisClientType } from 'redis';
|
||||
|
||||
import { AdminConfig, Favorite, IStorage, PlayRecord } from './types';
|
||||
import { AdminConfig } from './admin.types';
|
||||
import { Favorite, IStorage, PlayRecord } from './types';
|
||||
|
||||
// 搜索历史最大条数
|
||||
const SEARCH_HISTORY_LIMIT = 20;
|
||||
|
||||
@@ -109,6 +109,3 @@ export interface DoubanResult {
|
||||
message: string;
|
||||
list: DoubanItem[];
|
||||
}
|
||||
|
||||
// 导出AdminConfig类型
|
||||
export { AdminConfig };
|
||||
|
||||
Reference in New Issue
Block a user