321 lines
11 KiB
TypeScript
321 lines
11 KiB
TypeScript
"use client"
|
|
|
|
import type React from "react"
|
|
|
|
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, Loader2, Award } from "lucide-react"
|
|
import { Switch } from "@/components/ui/switch"
|
|
|
|
// 定义好感度等级类型
|
|
export type AffinityLevel = {
|
|
id: string
|
|
level: number
|
|
name: string
|
|
minAffinity: number
|
|
maxAffinity: number
|
|
unlockContent: string
|
|
rewardType: string
|
|
rewardItems?: string
|
|
rewardCurrency?: number
|
|
isEnabled: boolean
|
|
}
|
|
|
|
type AffinityLevelDialogProps = {
|
|
mode?: "create" | "edit"
|
|
level?: AffinityLevel
|
|
onSave?: (level: AffinityLevel) => void
|
|
trigger?: React.ReactNode
|
|
}
|
|
|
|
export function AffinityLevelDialog({ mode = "create", level, onSave, trigger }: AffinityLevelDialogProps) {
|
|
const [open, setOpen] = useState(false)
|
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
|
|
// 表单状态
|
|
const [levelNumber, setLevelNumber] = useState("6")
|
|
const [levelName, setLevelName] = useState("")
|
|
const [minAffinity, setMinAffinity] = useState("101")
|
|
const [maxAffinity, setMaxAffinity] = useState("120")
|
|
const [unlockContent, setUnlockContent] = useState("")
|
|
const [rewardType, setRewardType] = useState("unlock")
|
|
const [rewardItems, setRewardItems] = useState("")
|
|
const [rewardCurrency, setRewardCurrency] = useState("100")
|
|
const [isEnabled, setIsEnabled] = useState(true)
|
|
|
|
// 当编辑模式且有等级数据时,初始化表单
|
|
useEffect(() => {
|
|
if (mode === "edit" && level) {
|
|
setLevelNumber(String(level.level))
|
|
setLevelName(level.name)
|
|
setMinAffinity(String(level.minAffinity))
|
|
setMaxAffinity(String(level.maxAffinity))
|
|
setUnlockContent(level.unlockContent)
|
|
setRewardType(level.rewardType)
|
|
setRewardItems(level.rewardItems || "")
|
|
setRewardCurrency(String(level.rewardCurrency || 100))
|
|
setIsEnabled(level.isEnabled)
|
|
}
|
|
}, [mode, level, open])
|
|
|
|
// 重置表单
|
|
const resetForm = () => {
|
|
if (mode === "create") {
|
|
setLevelNumber("6")
|
|
setLevelName("")
|
|
setMinAffinity("101")
|
|
setMaxAffinity("120")
|
|
setUnlockContent("")
|
|
setRewardType("unlock")
|
|
setRewardItems("")
|
|
setRewardCurrency("100")
|
|
setIsEnabled(true)
|
|
}
|
|
}
|
|
|
|
const handleSubmit = async () => {
|
|
// 表单验证
|
|
if (!levelName || !unlockContent) {
|
|
alert("请填写所有必填字段!")
|
|
return
|
|
}
|
|
|
|
setIsSubmitting(true)
|
|
try {
|
|
// 构建等级对象
|
|
const updatedLevel: AffinityLevel = {
|
|
id: level?.id || `level-${Date.now()}`,
|
|
level: Number(levelNumber),
|
|
name: levelName,
|
|
minAffinity: Number(minAffinity),
|
|
maxAffinity: Number(maxAffinity),
|
|
unlockContent,
|
|
rewardType,
|
|
rewardItems: rewardItems || undefined,
|
|
rewardCurrency: rewardType === "currency" ? Number(rewardCurrency) : undefined,
|
|
isEnabled,
|
|
}
|
|
|
|
// 模拟API请求
|
|
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
|
|
// 调用保存回调
|
|
if (onSave) {
|
|
onSave(updatedLevel)
|
|
}
|
|
|
|
// 成功提示
|
|
alert(mode === "create" ? "好感度等级创建成功!" : "好感度等级更新成功!")
|
|
setOpen(false)
|
|
resetForm()
|
|
} catch (error) {
|
|
// 错误处理
|
|
console.error(mode === "create" ? "创建好感度等级失败:" : "更新好感度等级失败:", error)
|
|
alert(mode === "create" ? "创建好感度等级失败,请重试!" : "更新好感度等级失败,请重试!")
|
|
} finally {
|
|
setIsSubmitting(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Dialog
|
|
open={open}
|
|
onOpenChange={(newOpen) => {
|
|
setOpen(newOpen)
|
|
if (!newOpen) resetForm()
|
|
}}
|
|
>
|
|
<DialogTrigger asChild>
|
|
{trigger || (
|
|
<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" ? "设置新的好感度等级和对应奖励" : "修改好感度等级和对应奖励"}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="grid gap-4 py-4">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="level" className="text-right">
|
|
等级编号 <span className="text-red-500">*</span>
|
|
</Label>
|
|
<Input
|
|
id="level"
|
|
type="number"
|
|
min="1"
|
|
className="border-gray-300 focus-visible:ring-pink-500"
|
|
value={levelNumber}
|
|
onChange={(e) => setLevelNumber(e.target.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="level-name" className="text-right">
|
|
等级名称 <span className="text-red-500">*</span>
|
|
</Label>
|
|
<Input
|
|
id="level-name"
|
|
placeholder="输入等级名称"
|
|
className="border-gray-300 focus-visible:ring-pink-500"
|
|
value={levelName}
|
|
onChange={(e) => setLevelName(e.target.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="min-affinity" className="text-right">
|
|
最小好感度 <span className="text-red-500">*</span>
|
|
</Label>
|
|
<div className="flex items-center space-x-2">
|
|
<Input
|
|
id="min-affinity"
|
|
type="number"
|
|
min="0"
|
|
className="border-gray-300 focus-visible:ring-pink-500"
|
|
value={minAffinity}
|
|
onChange={(e) => setMinAffinity(e.target.value)}
|
|
required
|
|
/>
|
|
<span className="text-sm text-gray-500">点</span>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="max-affinity" className="text-right">
|
|
最大好感度 <span className="text-red-500">*</span>
|
|
</Label>
|
|
<div className="flex items-center space-x-2">
|
|
<Input
|
|
id="max-affinity"
|
|
type="number"
|
|
min="0"
|
|
className="border-gray-300 focus-visible:ring-pink-500"
|
|
value={maxAffinity}
|
|
onChange={(e) => setMaxAffinity(e.target.value)}
|
|
required
|
|
/>
|
|
<span className="text-sm text-gray-500">点</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="unlock-content" className="text-right">
|
|
解锁内容 <span className="text-red-500">*</span>
|
|
</Label>
|
|
<Textarea
|
|
id="unlock-content"
|
|
placeholder="输入该等级解锁的内容"
|
|
className="min-h-[80px] border-gray-300 focus-visible:ring-pink-500"
|
|
value={unlockContent}
|
|
onChange={(e) => setUnlockContent(e.target.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="reward-type" className="text-right">
|
|
奖励类型
|
|
</Label>
|
|
<Select value={rewardType} onValueChange={setRewardType}>
|
|
<SelectTrigger className="border-gray-300 focus:ring-pink-500">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="unlock">内容解锁</SelectItem>
|
|
<SelectItem value="item">道具奖励</SelectItem>
|
|
<SelectItem value="currency">虚拟货币</SelectItem>
|
|
<SelectItem value="mixed">混合奖励</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{rewardType === "item" && (
|
|
<div className="space-y-2">
|
|
<Label htmlFor="reward-items" className="text-right">
|
|
奖励道具
|
|
</Label>
|
|
<Textarea
|
|
id="reward-items"
|
|
placeholder="输入奖励的道具列表"
|
|
className="min-h-[80px] border-gray-300 focus-visible:ring-pink-500"
|
|
value={rewardItems}
|
|
onChange={(e) => setRewardItems(e.target.value)}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{rewardType === "currency" && (
|
|
<div className="space-y-2">
|
|
<Label htmlFor="reward-currency" className="text-right">
|
|
虚拟货币数量
|
|
</Label>
|
|
<Input
|
|
id="reward-currency"
|
|
type="number"
|
|
min="0"
|
|
className="border-gray-300 focus-visible:ring-pink-500"
|
|
value={rewardCurrency}
|
|
onChange={(e) => setRewardCurrency(e.target.value)}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex items-center space-x-2 pt-2">
|
|
<Switch id="enable-level" checked={isEnabled} onCheckedChange={setIsEnabled} />
|
|
<Label htmlFor="enable-level" className="cursor-pointer">
|
|
{mode === "create" ? "立即启用此等级" : "启用此等级"}
|
|
</Label>
|
|
</div>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => setOpen(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" ? "创建中..." : "更新中..."}
|
|
</>
|
|
) : (
|
|
<>
|
|
<Award className="mr-2 h-4 w-4" />
|
|
{mode === "create" ? "创建等级" : "更新等级"}
|
|
</>
|
|
)}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|