fix: 修复 Prettier 格式检查和依赖问题
## 修复内容 ### 依赖修复 - 安装缺失的 `prettier-plugin-organize-imports` 插件 - 修复 CI/CD 中的 "Cannot find package" 错误 - 更新 package.json 和 bun.lockb ### 代码格式化 - 对所有源文件运行 Prettier 自动格式化 - 统一 import 语句排序和组织 - 修复 49 个文件的代码风格问题 - 确保所有文件符合项目代码规范 ### 格式化改进 - Import 语句自动排序和分组 - 统一缩进和空格规范 - 标准化引号和分号使用 - 优化对象和数组格式 ## 验证结果 ✅ `bun run format` 通过 - 所有文件格式正确 ✅ `prettier-plugin-organize-imports` 正常工作 ✅ CI/CD 格式检查将通过 ## 技术细节 - 添加 prettier-plugin-organize-imports@^4.1.0 - 保持现有 .prettierrc 配置不变 - 格式化涉及 TS/TSX/JS/JSX/JSON/CSS/MD 文件 - 代码功能完全不受影响,仅调整格式
This commit is contained in:
@ -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<ViewMode>('grid')
|
||||
const [selectedPhotos, setSelectedPhotos] = useState<number[]>([])
|
||||
@ -54,43 +71,43 @@ export default function Photos() {
|
||||
status: '',
|
||||
categoryId: '',
|
||||
tagId: '',
|
||||
dateRange: ''
|
||||
dateRange: '',
|
||||
})
|
||||
|
||||
|
||||
// 编辑对话框状态
|
||||
const [editingPhoto, setEditingPhoto] = useState<Photo | null>(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<Photo | null>(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 (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
{/* 页面头部 */}
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">照片管理</h1>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
共 {photosData?.total || 0} 张照片
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-1">共 {photosData?.total || 0} 张照片</p>
|
||||
</div>
|
||||
<Button onClick={() => navigate('/photos/upload')} className="flex items-center gap-2">
|
||||
<Plus className="h-4 w-4" />
|
||||
上传照片
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
{/* 工具栏 */}
|
||||
<Card className="mb-6">
|
||||
<CardContent className="pt-6">
|
||||
@ -270,14 +293,17 @@ export default function Photos() {
|
||||
<Input
|
||||
placeholder="搜索照片..."
|
||||
value={filters.search}
|
||||
onChange={(e) => setFilters({ ...filters, search: e.target.value })}
|
||||
onChange={e => setFilters({ ...filters, search: e.target.value })}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
{/* 过滤器 */}
|
||||
<div className="flex gap-2">
|
||||
<Select value={filters.status} onValueChange={(value) => setFilters({ ...filters, status: value })}>
|
||||
<Select
|
||||
value={filters.status}
|
||||
onValueChange={value => setFilters({ ...filters, status: value })}
|
||||
>
|
||||
<SelectTrigger className="w-32">
|
||||
<SelectValue placeholder="状态" />
|
||||
</SelectTrigger>
|
||||
@ -289,14 +315,17 @@ export default function Photos() {
|
||||
<SelectItem value="processing">处理中</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={filters.categoryId} onValueChange={(value) => setFilters({ ...filters, categoryId: value })}>
|
||||
|
||||
<Select
|
||||
value={filters.categoryId}
|
||||
onValueChange={value => setFilters({ ...filters, categoryId: value })}
|
||||
>
|
||||
<SelectTrigger className="w-32">
|
||||
<SelectValue placeholder="分类" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="">全部分类</SelectItem>
|
||||
{categories?.data?.categories?.map((category) => (
|
||||
{categories?.data?.categories?.map(category => (
|
||||
<SelectItem key={category.id} value={category.id.toString()}>
|
||||
{category.name}
|
||||
</SelectItem>
|
||||
@ -304,7 +333,7 @@ export default function Photos() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
|
||||
{/* 视图模式 */}
|
||||
<div className="flex gap-1 border rounded-md">
|
||||
<Button
|
||||
@ -325,16 +354,14 @@ export default function Photos() {
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
||||
{/* 批量操作栏 */}
|
||||
{selectedPhotos.length > 0 && (
|
||||
<Card className="mb-6 border-primary">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm">
|
||||
已选择 {selectedPhotos.length} 张照片
|
||||
</span>
|
||||
<span className="text-sm">已选择 {selectedPhotos.length} 张照片</span>
|
||||
<Button variant="outline" size="sm" onClick={() => setSelectedPhotos([])}>
|
||||
取消选择
|
||||
</Button>
|
||||
@ -358,8 +385,8 @@ export default function Photos() {
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button
|
||||
variant="destructive"
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => handleBatchAction('delete')}
|
||||
disabled={batchDeleteMutation.isPending}
|
||||
@ -371,14 +398,22 @@ export default function Photos() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
|
||||
{/* 照片列表 */}
|
||||
{photosLoading ? (
|
||||
<div className={viewMode === 'grid' ? 'grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-6' : 'space-y-4'}>
|
||||
<div
|
||||
className={
|
||||
viewMode === 'grid'
|
||||
? 'grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-6'
|
||||
: 'space-y-4'
|
||||
}
|
||||
>
|
||||
{[...Array(8)].map((_, i) => (
|
||||
<Card key={i}>
|
||||
<CardContent className="p-4">
|
||||
<Skeleton className={viewMode === 'grid' ? 'aspect-square mb-4' : 'h-20 w-20 mb-4'} />
|
||||
<Skeleton
|
||||
className={viewMode === 'grid' ? 'aspect-square mb-4' : 'h-20 w-20 mb-4'}
|
||||
/>
|
||||
<Skeleton className="h-4 w-3/4 mb-2" />
|
||||
<Skeleton className="h-3 w-1/2" />
|
||||
</CardContent>
|
||||
@ -390,31 +425,35 @@ export default function Photos() {
|
||||
{/* 全选复选框 */}
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Checkbox
|
||||
checked={selectedPhotos.length === photosData.photos.length && photosData.photos.length > 0}
|
||||
checked={
|
||||
selectedPhotos.length === photosData.photos.length && photosData.photos.length > 0
|
||||
}
|
||||
onCheckedChange={handleSelectAll}
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground">全选</span>
|
||||
</div>
|
||||
|
||||
|
||||
{viewMode === 'grid' ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
||||
{photosData.photos.map((photo) => (
|
||||
{photosData.photos.map(photo => (
|
||||
<Card key={photo.id} className="group">
|
||||
<CardContent className="p-4">
|
||||
<div className="relative mb-4">
|
||||
<div className="aspect-square bg-gray-100 rounded-lg flex items-center justify-center">
|
||||
<Camera className="h-12 w-12 text-gray-400" />
|
||||
</div>
|
||||
|
||||
|
||||
{/* 复选框 */}
|
||||
<div className="absolute top-2 left-2">
|
||||
<Checkbox
|
||||
checked={selectedPhotos.includes(photo.id)}
|
||||
onCheckedChange={(checked) => handleSelectPhoto(photo.id, checked as boolean)}
|
||||
onCheckedChange={checked =>
|
||||
handleSelectPhoto(photo.id, checked as boolean)
|
||||
}
|
||||
className="bg-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<DropdownMenu>
|
||||
@ -440,7 +479,7 @@ export default function Photos() {
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<h3 className="font-medium truncate mb-2">{photo.title}</h3>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
@ -461,19 +500,19 @@ export default function Photos() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{photosData.photos.map((photo) => (
|
||||
{photosData.photos.map(photo => (
|
||||
<Card key={photo.id}>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Checkbox
|
||||
checked={selectedPhotos.includes(photo.id)}
|
||||
onCheckedChange={(checked) => handleSelectPhoto(photo.id, checked as boolean)}
|
||||
onCheckedChange={checked => handleSelectPhoto(photo.id, checked as boolean)}
|
||||
/>
|
||||
|
||||
|
||||
<div className="h-16 w-16 bg-gray-100 rounded flex items-center justify-center flex-shrink-0">
|
||||
<Camera className="h-8 w-8 text-gray-400" />
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-medium truncate">{photo.title}</h3>
|
||||
<p className="text-sm text-muted-foreground truncate">
|
||||
@ -491,7 +530,7 @@ export default function Photos() {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
@ -519,16 +558,12 @@ export default function Photos() {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* 分页 */}
|
||||
{photosData.pages > 1 && (
|
||||
<div className="flex justify-center mt-8">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={page === 1}
|
||||
onClick={() => setPage(page - 1)}
|
||||
>
|
||||
<Button variant="outline" disabled={page === 1} onClick={() => setPage(page - 1)}>
|
||||
上一页
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
@ -565,9 +600,7 @@ export default function Photos() {
|
||||
<ImageIcon className="h-16 w-16 text-gray-300" />
|
||||
</div>
|
||||
<h3 className="text-xl font-medium mb-2">暂无照片</h3>
|
||||
<p className="text-muted-foreground mb-6">
|
||||
开始上传您的第一张照片,构建精美的作品集
|
||||
</p>
|
||||
<p className="text-muted-foreground mb-6">开始上传您的第一张照片,构建精美的作品集</p>
|
||||
<div className="flex justify-center gap-4">
|
||||
<Button onClick={() => navigate('/photos/upload')}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
@ -582,42 +615,43 @@ export default function Photos() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
|
||||
{/* 编辑对话框 */}
|
||||
<Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>编辑照片</DialogTitle>
|
||||
<DialogDescription>
|
||||
修改照片的基本信息
|
||||
</DialogDescription>
|
||||
<DialogDescription>修改照片的基本信息</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div>
|
||||
<Label htmlFor="edit-title">标题</Label>
|
||||
<Input
|
||||
id="edit-title"
|
||||
value={editForm.title}
|
||||
onChange={(e) => setEditForm({ ...editForm, title: e.target.value })}
|
||||
onChange={e => setEditForm({ ...editForm, title: e.target.value })}
|
||||
placeholder="照片标题"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<Label htmlFor="edit-description">描述</Label>
|
||||
<Textarea
|
||||
id="edit-description"
|
||||
value={editForm.description}
|
||||
onChange={(e) => setEditForm({ ...editForm, description: e.target.value })}
|
||||
onChange={e => setEditForm({ ...editForm, description: e.target.value })}
|
||||
placeholder="照片描述"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<Label htmlFor="edit-status">状态</Label>
|
||||
<Select value={editForm.status} onValueChange={(value) => setEditForm({ ...editForm, status: value as any })}>
|
||||
<Select
|
||||
value={editForm.status}
|
||||
onValueChange={value => setEditForm({ ...editForm, status: value as any })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
@ -629,7 +663,7 @@ export default function Photos() {
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" onClick={() => setIsEditDialogOpen(false)}>
|
||||
取消
|
||||
@ -640,23 +674,21 @@ export default function Photos() {
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
|
||||
{/* 详情查看对话框 */}
|
||||
<Dialog open={isViewDialogOpen} onOpenChange={setIsViewDialogOpen}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{viewingPhoto?.title}</DialogTitle>
|
||||
<DialogDescription>
|
||||
照片详细信息
|
||||
</DialogDescription>
|
||||
<DialogDescription>照片详细信息</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
|
||||
{viewingPhoto && (
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="aspect-video bg-gray-100 rounded-lg flex items-center justify-center">
|
||||
<ImageIcon className="h-16 w-16 text-gray-400" />
|
||||
</div>
|
||||
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="text-sm font-medium">状态</Label>
|
||||
@ -664,19 +696,21 @@ export default function Photos() {
|
||||
{getStatusText(viewingPhoto.status)}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<Label className="text-sm font-medium">文件大小</Label>
|
||||
<p className="text-sm text-muted-foreground">{formatFileSize(viewingPhoto.fileSize)}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{formatFileSize(viewingPhoto.fileSize)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<Label className="text-sm font-medium">创建时间</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{new Date(viewingPhoto.createdAt).toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<Label className="text-sm font-medium">更新时间</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
@ -684,19 +718,19 @@ export default function Photos() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{viewingPhoto.description && (
|
||||
<div>
|
||||
<Label className="text-sm font-medium">描述</Label>
|
||||
<p className="text-sm text-muted-foreground mt-1">{viewingPhoto.description}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{viewingPhoto.categories && viewingPhoto.categories.length > 0 && (
|
||||
<div>
|
||||
<Label className="text-sm font-medium">分类</Label>
|
||||
<div className="flex flex-wrap gap-2 mt-1">
|
||||
{viewingPhoto.categories.map((category) => (
|
||||
{viewingPhoto.categories.map(category => (
|
||||
<Badge key={category.id} variant="secondary">
|
||||
<Folder className="h-3 w-3 mr-1" />
|
||||
{category.name}
|
||||
@ -705,12 +739,12 @@ export default function Photos() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{viewingPhoto.tags && viewingPhoto.tags.length > 0 && (
|
||||
<div>
|
||||
<Label className="text-sm font-medium">标签</Label>
|
||||
<div className="flex flex-wrap gap-2 mt-1">
|
||||
{viewingPhoto.tags.map((tag) => (
|
||||
{viewingPhoto.tags.map(tag => (
|
||||
<Badge key={tag.id} variant="outline">
|
||||
<Tag className="h-3 w-3 mr-1" />
|
||||
{tag.name}
|
||||
@ -721,7 +755,7 @@ export default function Photos() {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" onClick={() => setIsViewDialogOpen(false)}>
|
||||
关闭
|
||||
@ -735,4 +769,4 @@ export default function Photos() {
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user