lty/qy-lty-admin/components/food/food-media-upload.tsx
2026-03-17 13:17:02 +08:00

264 lines
9.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import React, { useState } from 'react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { FileUpload } from '@/components/ui/file-upload'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Image, Play, Music, Trash2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import type { UploadResponse } from '@/lib/api/upload'
interface FoodMediaUploadProps {
/** 当前图片URL */
imageUrl?: string
/** 当前动画URL */
animationUrl?: string
/** 当前音频URL */
audioUrl?: string
/** 图片上传成功回调 */
onImageUpload?: (url: string) => void
/** 动画上传成功回调 */
onAnimationUpload?: (url: string) => void
/** 音频上传成功回调 */
onAudioUpload?: (url: string) => void
/** 文件移除回调 */
onRemove?: (type: 'image' | 'animation' | 'audio') => void
/** 是否禁用 */
disabled?: boolean
}
export function FoodMediaUpload({
imageUrl,
animationUrl,
audioUrl,
onImageUpload,
onAnimationUpload,
onAudioUpload,
onRemove,
disabled = false,
}: FoodMediaUploadProps) {
const [activeTab, setActiveTab] = useState('image')
// 准备默认文件列表
const getDefaultFiles = (url?: string, type: string = 'image'): UploadResponse[] => {
if (!url) return []
return [{
url,
filename: `当前${type === 'image' ? '图片' : type === 'animation' ? '动画' : '音频'}`,
size: 0,
mimeType: type === 'image' ? 'image/jpeg' : type === 'animation' ? 'video/mp4' : 'audio/mp3'
}]
}
return (
<div className="space-y-4">
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="image" className="flex items-center gap-2">
<Image className="h-4 w-4" />
</TabsTrigger>
<TabsTrigger value="animation" className="flex items-center gap-2">
<Play className="h-4 w-4" />
</TabsTrigger>
<TabsTrigger value="audio" className="flex items-center gap-2">
<Music className="h-4 w-4" />
</TabsTrigger>
</TabsList>
{/* 图片上传 */}
<TabsContent value="image" className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="text-lg flex items-center gap-2">
<Image className="h-5 w-5" />
</CardTitle>
<CardDescription>
JPEGPNGGIF 300x300px
</CardDescription>
</CardHeader>
<CardContent>
<FileUpload
imageOnly={true}
multiple={false}
maxFiles={1}
maxSize={5 * 1024 * 1024} // 5MB
accept={{ 'image/*': ['.jpeg', '.jpg', '.png', '.gif', '.webp'] }}
placeholder="选择或拖拽食物图片"
defaultFiles={getDefaultFiles(imageUrl, 'image')}
disabled={disabled}
onUploadSuccess={(files) => {
if (files.length > 0) {
onImageUpload?.(files[0].url)
}
}}
onRemove={() => onRemove?.('image')}
/>
{imageUrl && (
<div className="mt-4 flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div className="flex items-center space-x-3">
<img
src={imageUrl}
alt="食物图片预览"
className="h-12 w-12 object-cover rounded border"
/>
<span className="text-sm text-gray-700"></span>
</div>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => onRemove?.('image')}
className="text-red-500 hover:text-red-700 hover:bg-red-50"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
)}
</CardContent>
</Card>
</TabsContent>
{/* 动画上传 */}
<TabsContent value="animation" className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="text-lg flex items-center gap-2">
<Play className="h-5 w-5" />
</CardTitle>
<CardDescription>
MP4GIFLottie 50MB
</CardDescription>
</CardHeader>
<CardContent>
<FileUpload
imageOnly={false}
multiple={false}
maxFiles={1}
maxSize={50 * 1024 * 1024} // 50MB
accept={{
'video/*': ['.mp4', '.avi', '.mov', '.wmv', '.flv'],
'image/gif': ['.gif'],
'application/json': ['.json'],
'application/x-lottie': ['.lottie']
}}
placeholder="选择或拖拽动画文件"
defaultFiles={getDefaultFiles(animationUrl, 'animation')}
disabled={disabled}
onUploadSuccess={(files) => {
if (files.length > 0) {
onAnimationUpload?.(files[0].url)
}
}}
onRemove={() => onRemove?.('animation')}
/>
{animationUrl && (
<div className="mt-4 flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div className="flex items-center space-x-3">
{animationUrl.endsWith('.gif') ? (
<img
src={animationUrl}
alt="动画预览"
className="h-12 w-12 object-cover rounded border"
/>
) : animationUrl.match(/\.(mp4|avi|mov|wmv|flv)$/i) ? (
<video
src={animationUrl}
className="h-12 w-12 object-cover rounded border"
muted
loop
autoPlay
/>
) : (
<div className="h-12 w-12 bg-gray-200 rounded border flex items-center justify-center">
<Play className="h-4 w-4 text-gray-500" />
</div>
)}
<span className="text-sm text-gray-700"></span>
</div>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => onRemove?.('animation')}
className="text-red-500 hover:text-red-700 hover:bg-red-50"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
)}
</CardContent>
</Card>
</TabsContent>
{/* 音频上传 */}
<TabsContent value="audio" className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="text-lg flex items-center gap-2">
<Music className="h-5 w-5" />
</CardTitle>
<CardDescription>
MP3WAVOGG 20MB
</CardDescription>
</CardHeader>
<CardContent>
<FileUpload
imageOnly={false}
multiple={false}
maxFiles={1}
maxSize={20 * 1024 * 1024} // 20MB
accept={{
'audio/*': ['.mp3', '.wav', '.ogg', '.aac', '.flac', '.wma', '.m4a']
}}
placeholder="选择或拖拽音频文件"
defaultFiles={getDefaultFiles(audioUrl, 'audio')}
disabled={disabled}
onUploadSuccess={(files) => {
if (files.length > 0) {
onAudioUpload?.(files[0].url)
}
}}
onRemove={() => onRemove?.('audio')}
/>
{audioUrl && (
<div className="mt-4 flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div className="flex items-center space-x-3">
<div className="h-12 w-12 bg-blue-100 rounded border flex items-center justify-center">
<Music className="h-4 w-4 text-blue-500" />
</div>
<div className="flex-1">
<span className="text-sm text-gray-700 block"></span>
<audio controls className="mt-1 w-full max-w-xs">
<source src={audioUrl} />
</audio>
</div>
</div>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => onRemove?.('audio')}
className="text-red-500 hover:text-red-700 hover:bg-red-50"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
)}
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
)
}