{tags?.map((tag: any) => (
-
+
-
)}
-
+
{files.length > 0 && !isUploading && (
-
)
-}
\ No newline at end of file
+}
diff --git a/admin/src/pages/Photos.tsx b/admin/src/pages/Photos.tsx
index 4af3cae..a94324a 100644
--- a/admin/src/pages/Photos.tsx
+++ b/admin/src/pages/Photos.tsx
@@ -1,35 +1,52 @@
-import { useState } from 'react'
-import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
-import { Card, CardContent } from '@/components/ui/card'
-import { Button } from '@/components/ui/button'
-import { Input } from '@/components/ui/input'
import { Badge } from '@/components/ui/badge'
-import { Skeleton } from '@/components/ui/skeleton'
+import { Button } from '@/components/ui/button'
+import { Card, CardContent } from '@/components/ui/card'
import { Checkbox } from '@/components/ui/checkbox'
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
-import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
-import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog'
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu'
+import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select'
+import { Skeleton } from '@/components/ui/skeleton'
import { Textarea } from '@/components/ui/textarea'
-import {
- Camera,
- Plus,
- Search,
- MoreVertical,
+import { categoryService } from '@/services/categoryService'
+import { Photo, photoService } from '@/services/photoService'
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
+import {
+ Camera,
Edit,
- Trash,
Eye,
+ Folder,
Grid,
- List,
- RefreshCw,
ImageIcon,
+ List,
+ MoreVertical,
+ Plus,
+ RefreshCw,
+ Search,
Tag,
- Folder
+ Trash,
} from 'lucide-react'
+import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { toast } from 'sonner'
-import { photoService, Photo } from '@/services/photoService'
-import { categoryService } from '@/services/categoryService'
type ViewMode = 'grid' | 'list'
@@ -44,7 +61,7 @@ interface PhotoFilters {
export default function Photos() {
const navigate = useNavigate()
const queryClient = useQueryClient()
-
+
// 状态管理
const [viewMode, setViewMode] = useState
('grid')
const [selectedPhotos, setSelectedPhotos] = useState([])
@@ -54,43 +71,43 @@ export default function Photos() {
status: '',
categoryId: '',
tagId: '',
- dateRange: ''
+ dateRange: '',
})
-
+
// 编辑对话框状态
const [editingPhoto, setEditingPhoto] = useState(null)
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
const [editForm, setEditForm] = useState({
title: '',
description: '',
- status: 'draft' as 'draft' | 'published' | 'archived' | 'processing'
+ status: 'draft' as 'draft' | 'published' | 'archived' | 'processing',
})
-
+
// 详情对话框状态
const [viewingPhoto, setViewingPhoto] = useState(null)
const [isViewDialogOpen, setIsViewDialogOpen] = useState(false)
-
+
// 获取照片列表
const { data: photosData, isLoading: photosLoading } = useQuery({
queryKey: ['photos', { page, ...filters }],
- queryFn: () => photoService.getPhotos({
- page,
- limit: 20,
- search: filters.search || undefined,
- status: filters.status || undefined,
- category_id: filters.categoryId ? parseInt(filters.categoryId) : undefined,
- sort_by: 'created_at',
- sort_order: 'desc'
- })
+ queryFn: () =>
+ photoService.getPhotos({
+ page,
+ limit: 20,
+ search: filters.search || undefined,
+ status: filters.status || undefined,
+ category_id: filters.categoryId ? parseInt(filters.categoryId) : undefined,
+ sort_by: 'created_at',
+ sort_order: 'desc',
+ }),
})
-
+
// 获取分类列表
const { data: categories } = useQuery({
queryKey: ['categories-all'],
- queryFn: () => categoryService.getCategories()
+ queryFn: () => categoryService.getCategories(),
})
-
-
+
// 删除照片
const deletePhotoMutation = useMutation({
mutationFn: photoService.deletePhoto,
@@ -100,9 +117,9 @@ export default function Photos() {
},
onError: (error: any) => {
toast.error(error?.message || '删除失败')
- }
+ },
})
-
+
// 批量删除照片
const batchDeleteMutation = useMutation({
mutationFn: photoService.batchDelete,
@@ -113,12 +130,12 @@ export default function Photos() {
},
onError: (error: any) => {
toast.error(error?.message || '批量删除失败')
- }
+ },
})
-
+
// 批量更新状态
const batchUpdateMutation = useMutation({
- mutationFn: ({ ids, status }: { ids: number[], status: string }) =>
+ mutationFn: ({ ids, status }: { ids: number[]; status: string }) =>
photoService.batchUpdate(ids, { status }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['photos'] })
@@ -127,13 +144,12 @@ export default function Photos() {
},
onError: (error: any) => {
toast.error(error?.message || '状态更新失败')
- }
+ },
})
-
+
// 编辑照片
const editPhotoMutation = useMutation({
- mutationFn: ({ id, data }: { id: number, data: any }) =>
- photoService.updatePhoto(id, data),
+ mutationFn: ({ id, data }: { id: number; data: any }) => photoService.updatePhoto(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['photos'] })
setIsEditDialogOpen(false)
@@ -142,9 +158,9 @@ export default function Photos() {
},
onError: (error: any) => {
toast.error(error?.message || '更新失败')
- }
+ },
})
-
+
const handleSelectAll = (checked: boolean) => {
if (checked) {
setSelectedPhotos(photosData?.photos.map(photo => photo.id) || [])
@@ -152,7 +168,7 @@ export default function Photos() {
setSelectedPhotos([])
}
}
-
+
const handleSelectPhoto = (photoId: number, checked: boolean) => {
if (checked) {
setSelectedPhotos([...selectedPhotos, photoId])
@@ -160,13 +176,13 @@ export default function Photos() {
setSelectedPhotos(selectedPhotos.filter(id => id !== photoId))
}
}
-
+
const handleBatchAction = (action: string, value?: string) => {
if (selectedPhotos.length === 0) {
toast.error('请先选择照片')
return
}
-
+
if (action === 'delete') {
if (confirm('确定要删除选中的照片吗?')) {
batchDeleteMutation.mutate(selectedPhotos)
@@ -175,33 +191,43 @@ export default function Photos() {
batchUpdateMutation.mutate({ ids: selectedPhotos, status: value })
}
}
-
+
const handleDeletePhoto = (photoId: number) => {
if (confirm('确定要删除这张照片吗?')) {
deletePhotoMutation.mutate(photoId)
}
}
-
+
const getStatusColor = (status: string) => {
switch (status) {
- case 'published': return 'bg-green-100 text-green-800'
- case 'draft': return 'bg-yellow-100 text-yellow-800'
- case 'archived': return 'bg-gray-100 text-gray-800'
- case 'processing': return 'bg-blue-100 text-blue-800'
- default: return 'bg-gray-100 text-gray-800'
+ case 'published':
+ return 'bg-green-100 text-green-800'
+ case 'draft':
+ return 'bg-yellow-100 text-yellow-800'
+ case 'archived':
+ return 'bg-gray-100 text-gray-800'
+ case 'processing':
+ return 'bg-blue-100 text-blue-800'
+ default:
+ return 'bg-gray-100 text-gray-800'
}
}
-
+
const getStatusText = (status: string) => {
switch (status) {
- case 'published': return '已发布'
- case 'draft': return '草稿'
- case 'archived': return '已归档'
- case 'processing': return '处理中'
- default: return status
+ case 'published':
+ return '已发布'
+ case 'draft':
+ return '草稿'
+ case 'archived':
+ return '已归档'
+ case 'processing':
+ return '处理中'
+ default:
+ return status
}
}
-
+
const formatFileSize = (bytes: number) => {
if (bytes === 0) return '0 Bytes'
const k = 1024
@@ -209,57 +235,54 @@ export default function Photos() {
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
-
+
// 处理编辑照片
const handleEditPhoto = (photo: Photo) => {
setEditingPhoto(photo)
setEditForm({
title: photo.title,
description: photo.description,
- status: photo.status
+ status: photo.status,
})
setIsEditDialogOpen(true)
}
-
+
// 处理查看照片详情
const handleViewPhoto = (photo: Photo) => {
setViewingPhoto(photo)
setIsViewDialogOpen(true)
}
-
+
// 提交编辑
const handleSubmitEdit = () => {
if (!editingPhoto) return
-
+
editPhotoMutation.mutate({
id: editingPhoto.id,
- data: editForm
+ data: editForm,
})
}
-
-
+
// 刷新列表
const handleRefresh = () => {
queryClient.invalidateQueries({ queryKey: ['photos'] })
toast.success('列表已刷新')
}
-
+
return (
{/* 页面头部 */}
照片管理
-
- 共 {photosData?.total || 0} 张照片
-
+
共 {photosData?.total || 0} 张照片
-
+
{/* 工具栏 */}
@@ -270,14 +293,17 @@ export default function Photos() {
setFilters({ ...filters, search: e.target.value })}
+ onChange={e => setFilters({ ...filters, search: e.target.value })}
className="pl-10"
/>
-
+
{/* 过滤器 */}
-
-
+
{/* 视图模式 */}