update
This commit is contained in:
@@ -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>
|
||||
|
||||
12
app/page.tsx
12
app/page.tsx
@@ -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>
|
||||
|
||||
{/* 加载状态 */}
|
||||
|
||||
@@ -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
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
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
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
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
BIN
public/level_5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
Reference in New Issue
Block a user