Files
MemeStudio/components/levels/level-dialog.tsx
2026-04-19 14:28:36 +08:00

249 lines
7.1 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 { useState, useEffect } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { Spinner } from '@/components/ui/spinner'
import { Level, LevelFormData } from '@/types'
import { ImageUploader } from './image-uploader'
interface LevelDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
level?: Level | null
onSubmit: (data: LevelFormData) => Promise<void>
}
const defaultFormData: LevelFormData = {
image1Url: '',
image1Description: '',
image2Url: '',
image2Description: '',
answer: '',
punchline: '',
hint1: '',
hint2: '',
hint3: '',
}
export function LevelDialog({ open, onOpenChange, level, onSubmit }: LevelDialogProps) {
const [formData, setFormData] = useState<LevelFormData>(defaultFormData)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState('')
// Reset form when dialog opens/closes or level changes
useEffect(() => {
if (open) {
if (level) {
setFormData({
image1Url: level.image1Url,
image1Description: level.image1Description || '',
image2Url: level.image2Url,
image2Description: level.image2Description || '',
answer: level.answer,
punchline: level.punchline || '',
hint1: level.hint1 || '',
hint2: level.hint2 || '',
hint3: level.hint3 || '',
})
} else {
setFormData(defaultFormData)
}
setError('')
}
}, [open, level])
const handleImage1Upload = (url: string) => {
setFormData((prev) => ({ ...prev, image1Url: url }))
}
const handleImage2Upload = (url: string) => {
setFormData((prev) => ({ ...prev, image2Url: url }))
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError('')
if (!formData.image1Url) {
setError('请上传图片1')
return
}
if (!formData.image2Url) {
setError('请上传图片2')
return
}
if (!formData.answer.trim()) {
setError('请输入答案')
return
}
if (formData.answer.trim().length > 4) {
setError('答案最多4个字')
return
}
setIsLoading(true)
try {
await onSubmit(formData)
onOpenChange(false)
} catch (err) {
setError(err instanceof Error ? err.message : '操作失败,请稍后重试')
} finally {
setIsLoading(false)
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-2xl max-h-[85vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{level ? '编辑关卡' : '添加关卡'}</DialogTitle>
<DialogDescription>
{level ? '修改关卡信息' : '创建新的关卡'}
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="p-3 text-sm text-red-600 bg-red-50 border border-red-200 rounded-md">
{error}
</div>
)}
{/* 双图上传区域 */}
<div className="grid grid-cols-2 gap-4">
{/* 图片1 */}
<div className="space-y-2">
<Label>1 *</Label>
<ImageUploader
value={formData.image1Url}
onChange={handleImage1Upload}
/>
<Input
value={formData.image1Description}
onChange={(e) =>
setFormData((prev) => ({ ...prev, image1Description: e.target.value }))
}
placeholder="图片1描述 (可选)"
/>
</div>
{/* 图片2 */}
<div className="space-y-2">
<Label>2 *</Label>
<ImageUploader
value={formData.image2Url}
onChange={handleImage2Upload}
/>
<Input
value={formData.image2Description}
onChange={(e) =>
setFormData((prev) => ({ ...prev, image2Description: e.target.value }))
}
placeholder="图片2描述 (可选)"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="answer"> *</Label>
<Input
id="answer"
value={formData.answer}
onChange={(e) =>
setFormData((prev) => ({ ...prev, answer: e.target.value }))
}
placeholder="请输入答案最多4个字"
maxLength={4}
required
/>
<p className="text-xs text-muted-foreground text-right">
{formData.answer.length}/4
</p>
</div>
<div className="space-y-2">
<Label htmlFor="punchline"> ()</Label>
<Input
id="punchline"
value={formData.punchline}
onChange={(e) =>
setFormData((prev) => ({ ...prev, punchline: e.target.value }))
}
placeholder="请输入谐音梗说明"
/>
</div>
<div className="space-y-2">
<Label htmlFor="hint1">1 ()</Label>
<Input
id="hint1"
value={formData.hint1}
onChange={(e) =>
setFormData((prev) => ({ ...prev, hint1: e.target.value }))
}
placeholder="请输入提示1"
/>
</div>
<div className="space-y-2">
<Label htmlFor="hint2">2 ()</Label>
<Input
id="hint2"
value={formData.hint2}
onChange={(e) =>
setFormData((prev) => ({ ...prev, hint2: e.target.value }))
}
placeholder="请输入提示2"
/>
</div>
<div className="space-y-2">
<Label htmlFor="hint3">3 ()</Label>
<Input
id="hint3"
value={formData.hint3}
onChange={(e) =>
setFormData((prev) => ({ ...prev, hint3: e.target.value }))
}
placeholder="请输入提示3"
/>
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
disabled={isLoading}
>
</Button>
<Button type="submit" disabled={isLoading}>
{isLoading ? (
<>
<Spinner size="sm" className="mr-2" />
...
</>
) : (
'保存'
)}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
)
}