feat: add right sidebar collapse

This commit is contained in:
shinya
2025-07-08 13:23:27 +08:00
parent abed52c766
commit e412e2d765
8 changed files with 97 additions and 35 deletions

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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>

View File

@@ -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 =

View File

@@ -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;

View File

@@ -109,6 +109,3 @@ export interface DoubanResult {
message: string;
list: DoubanItem[];
}
// 导出AdminConfig类型
export { AdminConfig };