322 lines
11 KiB
TypeScript
322 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, Heart } from "lucide-react"
|
||
import { Switch } from "@/components/ui/switch"
|
||
|
||
// 定义互动规则类型
|
||
export type AffinityRule = {
|
||
id: string
|
||
name: string
|
||
type: string
|
||
description: string
|
||
minChange: number
|
||
maxChange: number
|
||
singleCap: number
|
||
dailyCap: number
|
||
isNegative: boolean
|
||
isEnabled: boolean
|
||
}
|
||
|
||
type AffinityRuleDialogProps = {
|
||
mode?: "create" | "edit"
|
||
rule?: AffinityRule
|
||
onSave?: (rule: AffinityRule) => void
|
||
trigger?: React.ReactNode
|
||
}
|
||
|
||
export function AffinityRuleDialog({ mode = "create", rule, onSave, trigger }: AffinityRuleDialogProps) {
|
||
const [open, setOpen] = useState(false)
|
||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||
|
||
// 表单状态
|
||
const [name, setName] = useState("")
|
||
const [ruleType, setRuleType] = useState("")
|
||
const [description, setDescription] = useState("")
|
||
const [minChange, setMinChange] = useState("1")
|
||
const [maxChange, setMaxChange] = useState("5")
|
||
const [singleCap, setSingleCap] = useState("5")
|
||
const [dailyCap, setDailyCap] = useState("15")
|
||
const [isNegative, setIsNegative] = useState(false)
|
||
const [isEnabled, setIsEnabled] = useState(true)
|
||
|
||
// 当编辑模式且有规则数据时,初始化表单
|
||
useEffect(() => {
|
||
if (mode === "edit" && rule) {
|
||
setName(rule.name)
|
||
setRuleType(rule.type)
|
||
setDescription(rule.description)
|
||
setMinChange(String(rule.minChange))
|
||
setMaxChange(String(rule.maxChange))
|
||
setSingleCap(String(rule.singleCap))
|
||
setDailyCap(String(rule.dailyCap))
|
||
setIsNegative(rule.isNegative)
|
||
setIsEnabled(rule.isEnabled)
|
||
}
|
||
}, [mode, rule, open])
|
||
|
||
// 重置表单
|
||
const resetForm = () => {
|
||
if (mode === "create") {
|
||
setName("")
|
||
setRuleType("")
|
||
setDescription("")
|
||
setMinChange("1")
|
||
setMaxChange("5")
|
||
setSingleCap("5")
|
||
setDailyCap("15")
|
||
setIsNegative(false)
|
||
setIsEnabled(true)
|
||
}
|
||
}
|
||
|
||
const handleSubmit = async () => {
|
||
// 表单验证
|
||
if (!name || !ruleType || !description) {
|
||
alert("请填写所有必填字段!")
|
||
return
|
||
}
|
||
|
||
setIsSubmitting(true)
|
||
try {
|
||
// 构建规则对象
|
||
const updatedRule: AffinityRule = {
|
||
id: rule?.id || `rule-${Date.now()}`,
|
||
name,
|
||
type: ruleType,
|
||
description,
|
||
minChange: Number(minChange),
|
||
maxChange: Number(maxChange),
|
||
singleCap: Number(singleCap),
|
||
dailyCap: Number(dailyCap),
|
||
isNegative,
|
||
isEnabled,
|
||
}
|
||
|
||
// 模拟API请求
|
||
await new Promise((resolve) => setTimeout(resolve, 1500))
|
||
|
||
// 调用保存回调
|
||
if (onSave) {
|
||
onSave(updatedRule)
|
||
}
|
||
|
||
// 成功提示
|
||
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="rule-name" className="text-right">
|
||
规则名称 <span className="text-red-500">*</span>
|
||
</Label>
|
||
<Input
|
||
id="rule-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="rule-type" className="text-right">
|
||
互动类型 <span className="text-red-500">*</span>
|
||
</Label>
|
||
<Select value={ruleType} onValueChange={setRuleType} required>
|
||
<SelectTrigger className="border-gray-300 focus:ring-pink-500">
|
||
<SelectValue placeholder="选择互动类型" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="card">使用<EFBFBD><EFBFBD>片</SelectItem>
|
||
<SelectItem value="chat">对话</SelectItem>
|
||
<SelectItem value="feed">喂食</SelectItem>
|
||
<SelectItem value="touch">抚摸</SelectItem>
|
||
<SelectItem value="dress">换装</SelectItem>
|
||
<SelectItem value="prop">使用道具</SelectItem>
|
||
<SelectItem value="gift">送礼物</SelectItem>
|
||
<SelectItem value="decay">无互动衰减</SelectItem>
|
||
<SelectItem value="custom">自定义</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label htmlFor="description" className="text-right">
|
||
规则描述 <span className="text-red-500">*</span>
|
||
</Label>
|
||
<Textarea
|
||
id="description"
|
||
placeholder="输入规则描述"
|
||
className="min-h-[80px] border-gray-300 focus-visible:ring-pink-500"
|
||
value={description}
|
||
onChange={(e) => setDescription(e.target.value)}
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="min-change" className="text-right">
|
||
最小变化值 <span className="text-red-500">*</span>
|
||
</Label>
|
||
<div className="flex items-center space-x-2">
|
||
<Input
|
||
id="min-change"
|
||
type="number"
|
||
className="border-gray-300 focus-visible:ring-pink-500"
|
||
value={minChange}
|
||
onChange={(e) => setMinChange(e.target.value)}
|
||
required
|
||
/>
|
||
<span className="text-sm text-gray-500">点</span>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="max-change" className="text-right">
|
||
最大变化值 <span className="text-red-500">*</span>
|
||
</Label>
|
||
<div className="flex items-center space-x-2">
|
||
<Input
|
||
id="max-change"
|
||
type="number"
|
||
className="border-gray-300 focus-visible:ring-pink-500"
|
||
value={maxChange}
|
||
onChange={(e) => setMaxChange(e.target.value)}
|
||
required
|
||
/>
|
||
<span className="text-sm text-gray-500">点</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="single-cap" className="text-right">
|
||
单次上限 <span className="text-red-500">*</span>
|
||
</Label>
|
||
<div className="flex items-center space-x-2">
|
||
<Input
|
||
id="single-cap"
|
||
type="number"
|
||
min="0"
|
||
className="border-gray-300 focus-visible:ring-pink-500"
|
||
value={singleCap}
|
||
onChange={(e) => setSingleCap(e.target.value)}
|
||
required
|
||
/>
|
||
<span className="text-sm text-gray-500">点</span>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label htmlFor="daily-cap" className="text-right">
|
||
每日上限 <span className="text-red-500">*</span>
|
||
</Label>
|
||
<div className="flex items-center space-x-2">
|
||
<Input
|
||
id="daily-cap"
|
||
type="number"
|
||
min="0"
|
||
className="border-gray-300 focus-visible:ring-pink-500"
|
||
value={dailyCap}
|
||
onChange={(e) => setDailyCap(e.target.value)}
|
||
required
|
||
/>
|
||
<span className="text-sm text-gray-500">点</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center space-x-2 pt-2">
|
||
<Switch id="negative" checked={isNegative} onCheckedChange={setIsNegative} />
|
||
<Label htmlFor="negative" className="cursor-pointer">
|
||
这是负面规则(减少好感度)
|
||
</Label>
|
||
</div>
|
||
|
||
<div className="flex items-center space-x-2 pt-2">
|
||
<Switch id="enable-rule" checked={isEnabled} onCheckedChange={setIsEnabled} />
|
||
<Label htmlFor="enable-rule" 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" ? "创建中..." : "更新中..."}
|
||
</>
|
||
) : (
|
||
<>
|
||
<Heart className="mr-2 h-4 w-4" />
|
||
{mode === "create" ? "创建规则" : "更新规则"}
|
||
</>
|
||
)}
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
)
|
||
}
|