2026-03-17 13:17:02 +08:00

425 lines
15 KiB
TypeScript
Raw Permalink 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 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 { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { Checkbox } from "@/components/ui/checkbox"
import { Loader2, Shield } from "lucide-react"
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
// 定义角色类型
export type Role = {
id: string
name: string
description: string
userCount: number
createdAt: string
status: "系统角色" | "自定义角色"
permissions: Record<string, boolean>
}
// 定义权限组
const permissionGroups = [
{
name: "仪表盘",
permissions: [
{ id: "dashboard.view", label: "仪表盘查看" },
{ id: "dashboard.edit", label: "仪表盘编辑" },
],
},
{
name: "用户管理",
permissions: [
{ id: "users.view", label: "查看用户" },
{ id: "users.create", label: "创建用户" },
{ id: "users.edit", label: "编辑用户" },
{ id: "users.delete", label: "删除用户" },
],
},
{
name: "角色权限",
permissions: [
{ id: "roles.view", label: "查看角色" },
{ id: "roles.create", label: "创建角色" },
{ id: "roles.edit", label: "编辑角色" },
{ id: "roles.delete", label: "删除角色" },
],
},
{
name: "AI模型",
permissions: [
{ id: "ai.view", label: "查看AI模型" },
{ id: "ai.create", label: "创建AI模型" },
{ id: "ai.edit", label: "编辑AI模型" },
{ id: "ai.delete", label: "删除AI模型" },
],
},
{
name: "内容管理",
permissions: [
{ id: "outfits.view", label: "查看服装" },
{ id: "outfits.create", label: "创建服装" },
{ id: "outfits.edit", label: "编辑服装" },
{ id: "outfits.delete", label: "删除服装" },
{ id: "props.view", label: "查看道具" },
{ id: "props.create", label: "创建道具" },
{ id: "props.edit", label: "编辑道具" },
{ id: "props.delete", label: "删除道具" },
{ id: "homeDecor.view", label: "查看家居装饰" },
{ id: "homeDecor.create", label: "创建家居装饰" },
{ id: "homeDecor.edit", label: "编辑家居装饰" },
{ id: "homeDecor.delete", label: "删除家居装饰" },
{ id: "food.view", label: "查看食物" },
{ id: "food.create", label: "创建食物" },
{ id: "food.edit", label: "编辑食物" },
{ id: "food.delete", label: "删除食物" },
{ id: "songs.view", label: "查看歌曲" },
{ id: "songs.create", label: "创建歌曲" },
{ id: "songs.edit", label: "编辑歌曲" },
{ id: "songs.delete", label: "删除歌曲" },
],
},
{
name: "系统设置",
permissions: [
{ id: "settings.view", label: "查看设置" },
{ id: "settings.edit", label: "编辑设置" },
],
},
{
name: "好感度系统",
permissions: [
{ id: "affinity.view", label: "查看好感度系统" },
{ id: "affinity.create", label: "创建好感度规则" },
{ id: "affinity.edit", label: "编辑好感度规则" },
{ id: "affinity.delete", label: "删除好感度规则" },
],
},
]
// 表单验证模式
const formSchema = z.object({
name: z
.string()
.min(2, {
message: "角色名称至少需要2个字符",
})
.max(30, {
message: "角色名称不能超过30个字符",
}),
description: z
.string()
.min(5, {
message: "描述至少需要5个字符",
})
.max(200, {
message: "描述不能超过200个字符",
}),
status: z.enum(["系统角色", "自定义角色"]),
permissions: z.record(z.boolean()).refine(
(data) => {
// 至少选择一个权限
return Object.values(data).some((value) => value === true)
},
{
message: "至少需要选择一个权限",
},
),
})
type RoleDialogProps = {
open?: boolean
onOpenChange?: (open: boolean) => void
onSubmit: (data: z.infer<typeof formSchema>) => Promise<void>
defaultValues?: Partial<z.infer<typeof formSchema>>
mode: "add" | "edit"
trigger?: React.ReactNode
}
export function RoleDialog({ open, onOpenChange, onSubmit, defaultValues, mode, trigger }: RoleDialogProps) {
const [isSubmitting, setIsSubmitting] = useState(false)
const [dialogOpen, setDialogOpen] = useState(open || false)
// 初始化所有权限为false
const initialPermissions: Record<string, boolean> = {}
permissionGroups.forEach((group) => {
group.permissions.forEach((permission) => {
initialPermissions[permission.id] = false
})
})
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
description: "",
status: "自定义角色",
permissions: initialPermissions,
...defaultValues,
},
})
// 当defaultValues改变时重置表单
useEffect(() => {
if (defaultValues) {
form.reset({
name: defaultValues.name || "",
description: defaultValues.description || "",
status: defaultValues.status || "自定义角色",
permissions: {
...initialPermissions,
...defaultValues.permissions,
},
})
}
}, [defaultValues, form])
// 同步内部状态和外部状态
useEffect(() => {
if (open !== undefined) {
setDialogOpen(open)
}
}, [open])
const handleOpenChange = (newOpen: boolean) => {
setDialogOpen(newOpen)
if (onOpenChange) {
onOpenChange(newOpen)
}
// 如果对话框关闭,重置表单
if (!newOpen) {
form.reset()
}
}
const handleSubmit = async (data: z.infer<typeof formSchema>) => {
setIsSubmitting(true)
try {
await onSubmit(data)
handleOpenChange(false)
} catch (error) {
console.error("提交失败:", error)
} finally {
setIsSubmitting(false)
}
}
// 全选/取消全选某个组的所有权限
const toggleGroupPermissions = (groupName: string, value: boolean) => {
const group = permissionGroups.find((g) => g.name === groupName)
if (!group) return
const updatedPermissions = { ...form.getValues().permissions }
group.permissions.forEach((permission) => {
updatedPermissions[permission.id] = value
})
form.setValue("permissions", updatedPermissions, { shouldValidate: true })
}
// 检查一个组是否所有权限都被选中
const isGroupAllChecked = (groupName: string) => {
const group = permissionGroups.find((g) => g.name === groupName)
if (!group) return false
const permissions = form.getValues().permissions
return group.permissions.every((permission) => permissions[permission.id])
}
// 检查一个组是否有部分权限被选中
const isGroupPartiallyChecked = (groupName: string) => {
const group = permissionGroups.find((g) => g.name === groupName)
if (!group) return false
const permissions = form.getValues().permissions
const checkedCount = group.permissions.filter((permission) => permissions[permission.id]).length
return checkedCount > 0 && checkedCount < group.permissions.length
}
return (
<Dialog open={dialogOpen} onOpenChange={handleOpenChange}>
<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">
<Shield className="mr-2 h-4 w-4" />
</Button>
)}
</DialogTrigger>
<DialogContent className="sm:max-w-[700px] max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="text-xl font-bold flex items-center">
<Shield className="mr-2 h-5 w-5 text-purple-600" />
<span className="bg-clip-text text-transparent bg-gradient-to-r from-purple-600 to-pink-600">
{mode === "add" ? "添加角色" : "编辑角色"}
</span>
</DialogTitle>
<DialogDescription>
{mode === "add" ? "创建新的系统角色并分配权限" : "修改角色信息和权限设置"}
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6">
<Tabs defaultValue="basic" className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="basic"></TabsTrigger>
<TabsTrigger value="permissions"></TabsTrigger>
</TabsList>
<TabsContent value="basic" className="space-y-4 pt-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input placeholder="输入角色名称" {...field} />
</FormControl>
<FormDescription>使</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Textarea placeholder="输入角色描述" className="resize-none" {...field} />
</FormControl>
<FormDescription></FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="status"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<div className="flex items-center space-x-4">
<FormControl>
<div className="flex items-center space-x-2">
<input
type="radio"
id="system-role"
value="系统角色"
checked={field.value === "系统角色"}
onChange={() => field.onChange("系统角色")}
className="h-4 w-4 text-purple-600 focus:ring-purple-500"
/>
<label htmlFor="system-role" className="text-sm font-medium">
</label>
</div>
</FormControl>
<FormControl>
<div className="flex items-center space-x-2">
<input
type="radio"
id="custom-role"
value="自定义角色"
checked={field.value === "自定义角色"}
onChange={() => field.onChange("自定义角色")}
className="h-4 w-4 text-purple-600 focus:ring-purple-500"
/>
<label htmlFor="custom-role" className="text-sm font-medium">
</label>
</div>
</FormControl>
</div>
<FormDescription></FormDescription>
<FormMessage />
</FormItem>
)}
/>
</TabsContent>
<TabsContent value="permissions" className="pt-4">
<div className="space-y-6">
{permissionGroups.map((group) => (
<div key={group.name} className="border rounded-lg p-4">
<div className="flex items-center mb-3">
<Checkbox
id={`group-${group.name}`}
checked={isGroupAllChecked(group.name)}
onCheckedChange={(checked) => {
toggleGroupPermissions(group.name, checked === true)
}}
className={isGroupPartiallyChecked(group.name) ? "bg-purple-300" : ""}
/>
<label htmlFor={`group-${group.name}`} className="ml-2 text-sm font-medium text-purple-800">
{group.name}
</label>
</div>
<div className="grid grid-cols-2 gap-3 ml-6">
{group.permissions.map((permission) => (
<FormField
key={permission.id}
control={form.control}
name={`permissions.${permission.id}`}
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
<FormControl>
<Checkbox checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel className="text-sm font-normal">{permission.label}</FormLabel>
</div>
</FormItem>
)}
/>
))}
</div>
</div>
))}
</div>
</TabsContent>
</Tabs>
<DialogFooter>
<Button type="button" variant="outline" onClick={() => handleOpenChange(false)} disabled={isSubmitting}>
</Button>
<Button
type="submit"
disabled={isSubmitting}
className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700"
>
{isSubmitting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
...
</>
) : (
<>{mode === "add" ? "创建角色" : "保存修改"}</>
)}
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
)
}