This commit is contained in:
2025-12-23 14:52:06 +08:00
parent 7ed46b0cc0
commit baddf406f0
8 changed files with 99 additions and 136 deletions

View File

@@ -105,24 +105,21 @@ export default function MonthlyChallengeTable({
<table className="w-full">
<thead>
<tr className={`border-b ${darkMode ? 'border-gray-700' : 'border-gray-200'}`}>
<th className={`text-left py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'} font-medium`}>
<th className={`text-left py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'} font-medium whitespace-nowrap`}>
</th>
<th className={`text-left py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'} font-medium`}>
<th className={`text-left py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'} font-medium whitespace-nowrap`}>
</th>
<th className={`text-left py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'} font-medium`}>
<th className={`text-left py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'} font-medium whitespace-nowrap`}>
</th>
<th className={`text-left py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'} font-medium`}>
<th className={`text-left py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'} font-medium whitespace-nowrap`}>
</th>
<th className={`text-left py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'} font-medium`}>
<th className={`text-left py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'} font-medium whitespace-nowrap`}>
</th>
<th className={`text-left py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'} font-medium`}>
</th>
</tr>
</thead>
<tbody>
@@ -131,7 +128,7 @@ export default function MonthlyChallengeTable({
key={challenge.id}
className={`border-b ${darkMode ? 'border-gray-800 hover:bg-gray-700' : 'border-gray-100 hover:bg-gray-50'} transition-colors`}
>
<td className="py-3 px-4">
<td className="py-3 px-4 whitespace-nowrap">
<div>
<div className={`font-medium ${darkMode ? 'text-white' : 'text-gray-900'}`}>
{challenge.songTitleCn}
@@ -143,33 +140,34 @@ export default function MonthlyChallengeTable({
)}
</div>
</td>
<td className={`py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'}`}>
<div className="flex items-center gap-1">
<Music className="w-3 h-3" />
{challenge.difficulty}
<td className={`py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'} whitespace-nowrap`}>
<div className="flex items-center gap-2">
<img
src={`/level_${challenge.difficulty}.png`}
alt={`难度 ${challenge.difficulty}`}
className="w-6 h-6 sm:w-5 sm:h-5"
title={`难度 ${challenge.difficulty}`}
/>
</div>
</td>
<td className={`py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'}`}>
<td className={`py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'} whitespace-nowrap`}>
<div className="flex items-center gap-1">
<Star className="w-3 h-3 text-yellow-500" />
{challenge.stars.toFixed(1)}
<span className="text-sm sm:text-base">{challenge.stars.toFixed(1)}</span>
</div>
</td>
<td className={`py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'}`}>
<td className={`py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'} whitespace-nowrap`}>
<div className="flex items-center gap-1">
<Target className="w-3 h-3 text-red-500" />
{formatScore(challenge.requiredScore)}
<span className="text-sm sm:text-base">{formatScore(challenge.requiredScore)}</span>
</div>
</td>
<td className={`py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'}`}>
<td className={`py-3 px-4 ${darkMode ? 'text-gray-300' : 'text-gray-700'} whitespace-nowrap`}>
<div className="flex items-center gap-1">
<Gift className="w-3 h-3 text-green-500" />
{formatReward(challenge)}
<span className="text-sm sm:text-base">{formatReward(challenge)}</span>
</div>
</td>
<td className={`py-3 px-4 text-sm ${darkMode ? 'text-gray-400' : 'text-gray-600'}`}>
{new Date(challenge.createdAt).toLocaleDateString('zh-CN')}
</td>
</tr>
))}
</tbody>

View File

@@ -72,18 +72,8 @@ export default function HomePage() {
{/* 页面标题和刷新按钮 */}
<div className="flex items-center justify-between mb-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
</h1>
{!isLoading && (
<button
onClick={loadData}
className={`px-4 py-2 rounded-lg flex items-center gap-2 ${darkMode ? 'bg-gray-700 hover:bg-gray-600 text-white' : 'bg-gray-100 hover:bg-gray-200 text-gray-800'}`}
>
<RefreshCw className="w-4 h-4" />
</button>
)}
</div>
{/* 加载状态 */}

View File

@@ -2,12 +2,12 @@
import { useState, useEffect, useCallback, useRef } from 'react';
import Navbar from '@/app/components/Navbar';
import Sidebar from '@/app/components/Sidebar';
import PhotoGallery from '@/app/components/PhotoGallery';
import PhotoDetailModal from '@/app/components/PhotoDetailModal';
import ImageModal from '@/app/components/ImageModal';
import { Photo, Pagination } from '@/app/types';
import { fetchPhotos } from '@/app/lib/api';
import { Search } from 'lucide-react';
export default function PhotosPage() {
const [photos, setPhotos] = useState<Photo[]>([]);
@@ -16,8 +16,6 @@ export default function PhotosPage() {
const [searchQuery, setSearchQuery] = useState('');
const [sortBy, setSortBy] = useState('newest');
const [darkMode, setDarkMode] = useState(false);
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [pagination, setPagination] = useState<Pagination>({
page: 1,
limit: 20,
@@ -101,7 +99,8 @@ export default function PhotosPage() {
// 处理排序变化
const handleSortChange = useCallback((value: string) => {
setSortBy(value);
}, []);
fetchPhotosData(1, false);
}, [fetchPhotosData]);
// 切换暗色模式
const toggleDarkMode = () => {
@@ -116,21 +115,6 @@ export default function PhotosPage() {
});
};
// 切换侧边栏收缩状态
const toggleSidebar = () => {
setSidebarCollapsed(!sidebarCollapsed);
};
// 切换移动端菜单
const toggleMobileMenu = () => {
setMobileMenuOpen(!mobileMenuOpen);
};
// 关闭移动端菜单
const closeMobileMenu = () => {
setMobileMenuOpen(false);
};
// 打开图片模态框(点击卡片时)
const openImageModal = (photo: Photo) => {
setSelectedPhoto(photo);
@@ -165,99 +149,90 @@ export default function PhotosPage() {
return (
<div className={`min-h-screen transition-colors duration-200 ${darkMode ? 'dark' : ''}`}>
{/* 导航栏 */}
<Navbar darkMode={darkMode} onToggleDarkMode={toggleDarkMode} />
<Navbar
darkMode={darkMode}
onToggleDarkMode={toggleDarkMode}
/>
{/* 移动端菜单遮罩 */}
{mobileMenuOpen && (
<div
className="lg:hidden fixed inset-0 z-40 bg-black/50"
onClick={closeMobileMenu}
/>
)}
{/* 主内容区域 */}
<main className="pt-4 px-4">
{/* 顶部控制栏 */}
<div className="mb-6">
<div className="bg-white dark:bg-gray-800 rounded-lg p-4 shadow">
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
{/* 搜索表单 */}
<form onSubmit={handleSearch} className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="搜索标题或日期..."
className="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<button
type="submit"
className="mt-2 lg:hidden w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
</button>
</form>
{/* 移动端侧边栏菜单 */}
<div className={`
lg:hidden fixed top-0 left-0 z-50 h-full w-64
transform transition-transform duration-300 ease-in-out
${mobileMenuOpen ? 'translate-x-0' : '-translate-x-full'}
bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-700
overflow-y-auto
`}>
<div className="p-4">
<div className="flex items-center justify-between mb-8">
<h1 className="text-xl font-bold text-gray-900 dark:text-white">
</h1>
<button
onClick={closeMobileMenu}
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
aria-label="关闭菜单"
>
</button>
{/* 排序选项 */}
<div className="lg:w-48">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 lg:mb-0 lg:sr-only">
</label>
<select
value={sortBy}
onChange={(e) => handleSortChange(e.target.value)}
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="newest"></option>
<option value="oldest"></option>
<option value="title"></option>
</select>
</div>
{/* 桌面端搜索按钮 */}
<button
type="submit"
onClick={handleSearch}
className="hidden lg:block px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors whitespace-nowrap"
>
</button>
</div>
</div>
{/* 移动端侧边栏内容 */}
<Sidebar
darkMode={darkMode}
sidebarCollapsed={false}
searchQuery={searchQuery}
sortBy={sortBy}
pagination={pagination}
loading={loading}
onToggleDarkMode={toggleDarkMode}
onToggleSidebar={toggleSidebar}
onSearchChange={setSearchQuery}
onSortChange={handleSortChange}
onSearchSubmit={handleSearch}
/>
</div>
</div>
{/* 桌面端布局 */}
<div className="flex">
{/* 桌面端侧边栏 */}
<Sidebar
darkMode={darkMode}
sidebarCollapsed={sidebarCollapsed}
searchQuery={searchQuery}
sortBy={sortBy}
pagination={pagination}
<PhotoGallery
photos={photos}
loading={loading}
onToggleDarkMode={toggleDarkMode}
onToggleSidebar={toggleSidebar}
onSearchChange={setSearchQuery}
onSortChange={handleSortChange}
onSearchSubmit={handleSearch}
loadingMore={loadingMore}
pagination={pagination}
selectedPhoto={selectedPhoto}
onPhotoClick={openImageModal}
loadMoreRef={loadMoreRef}
/>
</main>
{/* 主内容区域 */}
<main className={`flex-1 ${sidebarCollapsed ? 'lg:ml-16' : 'lg:ml-64'} transition-all duration-300 ease-in-out`}>
<PhotoGallery
photos={photos}
loading={loading}
loadingMore={loadingMore}
pagination={pagination}
selectedPhoto={selectedPhoto}
onPhotoClick={openImageModal}
loadMoreRef={loadMoreRef}
/>
</main>
{/* 图片模态框 */}
<ImageModal
photo={selectedPhoto}
isOpen={imageModalOpen}
onClose={closeAllModals}
onInfoClick={openDetailDrawer}
/>
{/* 图片模态框 */}
<ImageModal
photo={selectedPhoto}
isOpen={imageModalOpen}
onClose={closeAllModals}
onInfoClick={openDetailDrawer}
/>
{/* 照片详情抽屉 */}
<PhotoDetailModal
photo={selectedPhoto}
isOpen={detailDrawerOpen}
onClose={closePhotoDetail}
/>
</div>
{/* 照片详情抽屉 */}
<PhotoDetailModal
photo={selectedPhoto}
isOpen={detailDrawerOpen}
onClose={closePhotoDetail}
/>
</div>
);
}

BIN
public/level_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/level_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
public/level_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/level_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/level_5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB