lty/qy-lty-admin/components/props/add-prop-dialog.tsx
2026-03-17 13:17:02 +08:00

307 lines
11 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 {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Plus, Upload, AlertTriangle, Loader2 } from "lucide-react"
import { Switch } from "@/components/ui/switch"
import type { Prop } from "./prop-detail-dialog"
type AddPropDialogProps = {
mode?: "create" | "edit"
initialProp?: Prop
open?: boolean
onOpenChange?: (open: boolean) => void
onSave?: (prop: Prop) => void
}
export function AddPropDialog({
mode = "create",
initialProp,
open: controlledOpen,
onOpenChange: setControlledOpen,
onSave,
}: AddPropDialogProps) {
const [open, setOpen] = useState(false)
const [isSubmitting, setIsSubmitting] = useState(false)
// 表单状态
const [name, setName] = useState("")
const [propType, setPropType] = useState("")
const [rarity, setRarity] = useState("")
const [description, setDescription] = useState("")
const [isLimited, setIsLimited] = useState(false)
const [previewId, setPreviewId] = useState(
"PRP" +
Math.floor(Math.random() * 1000)
.toString()
.padStart(3, "0"),
)
// 当编辑模式且有道具数据时,初始化表单
useEffect(() => {
if (mode === "edit" && initialProp) {
setName(initialProp.name)
setPropType(initialProp.type)
setRarity(initialProp.rarity)
setDescription(initialProp.description)
setIsLimited(initialProp.type === "限定道具")
setPreviewId(initialProp.id)
}
}, [mode, initialProp])
// 处理受控和非受控开关状态
useEffect(() => {
if (controlledOpen !== undefined) {
setOpen(controlledOpen)
}
}, [controlledOpen])
// 处理对话框关闭
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen)
if (setControlledOpen) {
setControlledOpen(newOpen)
}
// 如果关闭对话框,重置表单(仅在创建模式下)
if (!newOpen && mode === "create") {
resetForm()
}
}
// 重置表单
const resetForm = () => {
if (mode === "create") {
setName("")
setPropType("")
setRarity("")
setDescription("")
setIsLimited(false)
setPreviewId(
"PRP" +
Math.floor(Math.random() * 1000)
.toString()
.padStart(3, "0"),
)
}
}
const handleSubmit = async () => {
// 表单验证
if (!name || !propType || !rarity || !description) {
alert("请填写所有必填字段!")
return
}
setIsSubmitting(true)
try {
// 构建道具对象
const prop: Prop = {
id: initialProp?.id || previewId,
name,
type: isLimited ? "限定道具" : propType,
rarity,
description,
releaseDate: initialProp?.releaseDate || "",
status: initialProp?.status || "未发布",
activatedCount: initialProp?.activatedCount || 0,
image: initialProp?.image || "/placeholder.svg?height=300&width=300",
}
// 模拟API请求
await new Promise((resolve) => setTimeout(resolve, 1500))
// 调用保存回调
if (onSave) {
onSave(prop)
}
// 关闭对话框
handleOpenChange(false)
} catch (error) {
console.error(mode === "create" ? "创建道具失败:" : "更新道具失败:", error)
alert(mode === "create" ? "创建道具失败,请重试!" : "更新道具失败,请重试!")
} finally {
setIsSubmitting(false)
}
}
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
{mode === "create" && (
<DialogTrigger asChild>
<Button className="bg-gradient-to-r from-pink-500 to-purple-600 hover:from-pink-600 hover:to-purple-700 transition-all duration-300 shadow-md hover:shadow-lg">
<Plus className="mr-2 h-4 w-4" />
</Button>
</DialogTrigger>
)}
<DialogContent className="sm:max-w-[550px]">
<DialogHeader>
<DialogTitle className="text-xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-purple-600 to-pink-600">
{mode === "create" ? "添加新道具" : "编辑道具"}
</DialogTitle>
<DialogDescription>
{mode === "create" ? "填写道具信息以创建新的道具卡牌。创建后将生成唯一的卡牌ID。" : "修改道具信息。"}
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="name" className="text-right">
<span className="text-red-500">*</span>
</Label>
<Input
id="name"
placeholder="输入道具名称"
className="border-gray-300 focus-visible:ring-pink-500"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="type" className="text-right">
<span className="text-red-500">*</span>
</Label>
<Select value={propType} onValueChange={setPropType} required>
<SelectTrigger className="border-gray-300 focus:ring-pink-500">
<SelectValue placeholder="选择道具类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="演出道具"></SelectItem>
<SelectItem value="互动道具"></SelectItem>
<SelectItem value="收藏品"></SelectItem>
<SelectItem value="限定道具"></SelectItem>
<SelectItem value="其他"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="rarity" className="text-right">
<span className="text-red-500">*</span>
</Label>
<Select value={rarity} onValueChange={setRarity} required>
<SelectTrigger className="border-gray-300 focus:ring-pink-500">
<SelectValue placeholder="选择稀有度" />
</SelectTrigger>
<SelectContent>
<SelectItem value="普通"></SelectItem>
<SelectItem value="稀有"></SelectItem>
<SelectItem value="史诗"></SelectItem>
<SelectItem value="传说"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="quantity" className="text-right">
<span className="text-red-500">*</span>
</Label>
<Input
id="quantity"
type="number"
min="1"
defaultValue="1000"
className="border-gray-300 focus-visible:ring-pink-500"
required
disabled={mode === "edit"}
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="description" className="text-right">
<EFBFBD><EFBFBD><EFBFBD> <span className="text-red-500">*</span>
</Label>
<Textarea
id="description"
placeholder="输入道具描述"
className="min-h-[100px] border-gray-300 focus-visible:ring-pink-500"
value={description}
onChange={(e) => setDescription(e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label className="text-right">
{mode === "create" && <span className="text-red-500">*</span>}
</Label>
<div className="border-2 border-dashed border-gray-300 rounded-lg p-6 flex flex-col items-center justify-center hover:border-pink-500 transition-colors cursor-pointer">
<Upload className="h-8 w-8 text-gray-400 mb-2" />
<p className="text-sm text-gray-500"></p>
<p className="text-xs text-gray-400 mt-1"> PNG, JPG, JPEG 5MB</p>
</div>
</div>
<div className="flex items-center space-x-2 pt-2">
<Switch id="limited" checked={isLimited} onCheckedChange={setIsLimited} />
<Label htmlFor="limited" className="cursor-pointer">
</Label>
</div>
{isLimited && (
<div className="p-3 bg-amber-50 border border-amber-200 rounded-lg flex items-start">
<AlertTriangle className="h-5 w-5 text-amber-500 mr-2 flex-shrink-0 mt-0.5" />
<div className="text-sm text-amber-700">
<p className="font-medium"></p>
<p></p>
</div>
</div>
)}
{mode === "create" && (
<div className="p-4 bg-gray-50 rounded-lg">
<h3 className="text-sm font-medium mb-2"></h3>
<p className="text-sm text-gray-600">
ID: <span className="font-medium text-pink-600">{previewId}</span>
</p>
<p className="text-sm text-gray-600 mt-1">
: <span className="font-medium"></span>
</p>
</div>
)}
</div>
<DialogFooter>
<Button variant="outline" onClick={() => handleOpenChange(false)} disabled={isSubmitting}>
</Button>
<Button
className="bg-gradient-to-r from-pink-500 to-purple-600 hover:from-pink-600 hover:to-purple-700"
onClick={handleSubmit}
disabled={isSubmitting}
>
{isSubmitting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
{mode === "create" ? "创建中..." : "更新中..."}
</>
) : mode === "create" ? (
"创建道具"
) : (
"更新道具"
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}