- Update food, outfits, props, home-decor pages and components - Add permissions page and sidebar updates - Update API client and all API modules (auth, food, dances, etc.) - Add card model migrations for optional fields - Update Django views, serializers, and authentication - Add affinity level migrations and user app updates - Add project documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
425 lines
15 KiB
TypeScript
425 lines
15 KiB
TypeScript
"use client"
|
||
|
||
import { useState } from "react"
|
||
import { DashboardShell } from "@/components/dashboard-shell"
|
||
import { DashboardHeader } from "@/components/dashboard-header"
|
||
import { Button } from "@/components/ui/button"
|
||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
||
import { Input } from "@/components/ui/input"
|
||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||
import { Badge } from "@/components/ui/badge"
|
||
import { Search, Edit } from "lucide-react"
|
||
import { Checkbox } from "@/components/ui/checkbox"
|
||
import { RoleDialog, type Role } from "@/components/permissions/role-dialog"
|
||
import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog"
|
||
import { useToast } from "@/components/ui/use-toast"
|
||
|
||
// 初始角色数据
|
||
const initialRoles: Role[] = [
|
||
{
|
||
id: "1",
|
||
name: "超级管理员",
|
||
description: "拥有系统所有权限,可以管理所有功能",
|
||
userCount: 1,
|
||
createdAt: "2023-01-01",
|
||
status: "系统角色",
|
||
permissions: {
|
||
"dashboard.view": true,
|
||
"dashboard.edit": true,
|
||
"users.view": true,
|
||
"users.create": true,
|
||
"users.edit": true,
|
||
"users.delete": true,
|
||
"roles.view": true,
|
||
"roles.create": true,
|
||
"roles.edit": true,
|
||
"roles.delete": true,
|
||
"ai.view": true,
|
||
"ai.create": true,
|
||
"ai.edit": true,
|
||
"ai.delete": true,
|
||
"outfits.view": true,
|
||
"outfits.create": true,
|
||
"outfits.edit": true,
|
||
"outfits.delete": true,
|
||
"props.view": true,
|
||
"props.create": true,
|
||
"props.edit": true,
|
||
"props.delete": true,
|
||
"homeDecor.view": true,
|
||
"homeDecor.create": true,
|
||
"homeDecor.edit": true,
|
||
"homeDecor.delete": true,
|
||
"food.view": true,
|
||
"food.create": true,
|
||
"food.edit": true,
|
||
"food.delete": true,
|
||
"songs.view": true,
|
||
"songs.create": true,
|
||
"songs.edit": true,
|
||
"songs.delete": true,
|
||
"settings.view": true,
|
||
"settings.edit": true,
|
||
"affinity.view": true,
|
||
"affinity.create": true,
|
||
"affinity.edit": true,
|
||
"affinity.delete": true,
|
||
},
|
||
},
|
||
{
|
||
id: "2",
|
||
name: "内容管理员",
|
||
description: "管理系统内容,包括服装、道具、歌曲等",
|
||
userCount: 3,
|
||
createdAt: "2023-03-15",
|
||
status: "自定义角色",
|
||
permissions: {
|
||
"dashboard.view": true,
|
||
"outfits.view": true,
|
||
"outfits.create": true,
|
||
"outfits.edit": true,
|
||
"outfits.delete": true,
|
||
"props.view": true,
|
||
"props.create": true,
|
||
"props.edit": true,
|
||
"props.delete": true,
|
||
"homeDecor.view": true,
|
||
"homeDecor.create": true,
|
||
"homeDecor.edit": true,
|
||
"homeDecor.delete": true,
|
||
"food.view": true,
|
||
"food.create": true,
|
||
"food.edit": true,
|
||
"food.delete": true,
|
||
"songs.view": true,
|
||
"songs.create": true,
|
||
"songs.edit": true,
|
||
"songs.delete": true,
|
||
},
|
||
},
|
||
{
|
||
id: "3",
|
||
name: "AI模型管理员",
|
||
description: "管理AI模型、语音合成和知识库",
|
||
userCount: 2,
|
||
createdAt: "2023-05-20",
|
||
status: "自定义角色",
|
||
permissions: {
|
||
"dashboard.view": true,
|
||
"ai.view": true,
|
||
"ai.create": true,
|
||
"ai.edit": true,
|
||
"ai.delete": true,
|
||
},
|
||
},
|
||
{
|
||
id: "4",
|
||
name: "卡牌管理员",
|
||
description: "管理卡牌系统,包括服装、道具、家居装饰等",
|
||
userCount: 4,
|
||
createdAt: "2023-07-10",
|
||
status: "自定义角色",
|
||
permissions: {
|
||
"dashboard.view": true,
|
||
"outfits.view": true,
|
||
"outfits.create": true,
|
||
"outfits.edit": true,
|
||
"props.view": true,
|
||
"props.create": true,
|
||
"props.edit": true,
|
||
"homeDecor.view": true,
|
||
"homeDecor.create": true,
|
||
"homeDecor.edit": true,
|
||
"food.view": true,
|
||
"food.create": true,
|
||
"food.edit": true,
|
||
},
|
||
},
|
||
{
|
||
id: "5",
|
||
name: "查看者",
|
||
description: "只有查看权限,无法修改任何内容",
|
||
userCount: 2,
|
||
createdAt: "2023-09-05",
|
||
status: "自定义角色",
|
||
permissions: {
|
||
"dashboard.view": true,
|
||
},
|
||
},
|
||
]
|
||
|
||
export default function PermissionsPage() {
|
||
const [roles, setRoles] = useState<Role[]>(initialRoles)
|
||
const [searchQuery, setSearchQuery] = useState("")
|
||
const [editingRole, setEditingRole] = useState<Role | null>(null)
|
||
const { toast } = useToast()
|
||
|
||
// 过滤角色
|
||
const filteredRoles = roles.filter(
|
||
(role) =>
|
||
role.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||
role.description.toLowerCase().includes(searchQuery.toLowerCase()),
|
||
)
|
||
|
||
// 添加角色
|
||
const handleAddRole = async (data: any) => {
|
||
const newRole: Role = {
|
||
id: `${roles.length + 1}`,
|
||
name: data.name,
|
||
description: data.description,
|
||
userCount: 0,
|
||
createdAt: new Date().toISOString().split("T")[0],
|
||
status: data.status,
|
||
permissions: data.permissions,
|
||
}
|
||
|
||
setRoles([...roles, newRole])
|
||
toast({
|
||
title: "角色创建成功",
|
||
description: `角色 "${data.name}" 已成功创建`,
|
||
variant: "default",
|
||
})
|
||
}
|
||
|
||
// 编辑角色
|
||
const handleEditRole = async (data: any) => {
|
||
if (!editingRole) return
|
||
|
||
const updatedRoles = roles.map((role) =>
|
||
role.id === editingRole.id
|
||
? {
|
||
...role,
|
||
name: data.name,
|
||
description: data.description,
|
||
status: data.status,
|
||
permissions: data.permissions,
|
||
}
|
||
: role,
|
||
)
|
||
|
||
setRoles(updatedRoles)
|
||
setEditingRole(null)
|
||
toast({
|
||
title: "角色更新成功",
|
||
description: `角色 "${data.name}" 已成功更新`,
|
||
variant: "default",
|
||
})
|
||
}
|
||
|
||
// 删除角色
|
||
const handleDeleteRole = async (roleId: string) => {
|
||
const updatedRoles = roles.filter((role) => role.id !== roleId)
|
||
setRoles(updatedRoles)
|
||
toast({
|
||
title: "角色删除成功",
|
||
description: "角色已成功删除",
|
||
variant: "default",
|
||
})
|
||
}
|
||
|
||
// 开始<E5BC80><E5A78B><EFBFBD>辑角色
|
||
const startEditRole = (role: Role) => {
|
||
setEditingRole(role)
|
||
}
|
||
|
||
return (
|
||
<DashboardShell>
|
||
<DashboardHeader heading="权限管理" text="管理系统角色和权限">
|
||
<RoleDialog mode="add" onSubmit={handleAddRole} />
|
||
</DashboardHeader>
|
||
|
||
<div className="flex items-center justify-between space-y-2 mb-6">
|
||
<div className="flex items-center space-x-2">
|
||
<div className="relative">
|
||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||
<Input
|
||
type="search"
|
||
placeholder="搜索角色..."
|
||
className="w-[300px] pl-8 border-none bg-white shadow-md focus-visible:ring-pink-500"
|
||
value={searchQuery}
|
||
onChange={(e) => setSearchQuery(e.target.value)}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<Card className="border-none shadow-lg bg-gradient-to-br from-white to-purple-50">
|
||
<CardHeader>
|
||
<CardTitle className="text-xl font-bold flex items-center">
|
||
<span className="bg-clip-text text-transparent bg-gradient-to-r from-purple-600 to-pink-600">角色列表</span>
|
||
<div className="ml-2 h-1 w-10 bg-gradient-to-r from-purple-600 to-pink-600 rounded-full"></div>
|
||
</CardTitle>
|
||
<CardDescription>管理系统角色和权限</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<Table>
|
||
<TableHeader className="bg-gray-50">
|
||
<TableRow>
|
||
<TableHead className="w-[150px]">角色名称</TableHead>
|
||
<TableHead>描述</TableHead>
|
||
<TableHead>用户数量</TableHead>
|
||
<TableHead>创建日期</TableHead>
|
||
<TableHead>状态</TableHead>
|
||
<TableHead className="text-right">操作</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{filteredRoles.map((role) => (
|
||
<TableRow key={role.id} className="hover:bg-gray-50 transition-colors">
|
||
<TableCell className="font-medium">{role.name}</TableCell>
|
||
<TableCell>{role.description}</TableCell>
|
||
<TableCell>{role.userCount}</TableCell>
|
||
<TableCell>{role.createdAt}</TableCell>
|
||
<TableCell>
|
||
<Badge className={role.status === "系统角色" ? "bg-purple-500" : "bg-blue-500"}>
|
||
{role.status}
|
||
</Badge>
|
||
</TableCell>
|
||
<TableCell className="text-right">
|
||
<Button
|
||
variant="ghost"
|
||
size="icon"
|
||
className="hover:bg-pink-50 hover:text-pink-600"
|
||
onClick={() => startEditRole(role)}
|
||
>
|
||
<Edit className="h-4 w-4" />
|
||
</Button>
|
||
{role.status !== "系统角色" && (
|
||
<DeleteConfirmationDialog
|
||
title="删除角色"
|
||
description="此操作将永久删除该角色,且无法恢复。"
|
||
itemName={role.name}
|
||
onDelete={() => handleDeleteRole(role.id)}
|
||
/>
|
||
)}
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
{filteredRoles.length === 0 && (
|
||
<TableRow>
|
||
<TableCell colSpan={6} className="h-24 text-center text-muted-foreground">
|
||
没有找到匹配的角色
|
||
</TableCell>
|
||
</TableRow>
|
||
)}
|
||
</TableBody>
|
||
</Table>
|
||
</CardContent>
|
||
<CardFooter className="flex justify-between">
|
||
<div className="text-sm text-muted-foreground">
|
||
显示 {filteredRoles.length} 共 {roles.length} 个角色
|
||
</div>
|
||
</CardFooter>
|
||
</Card>
|
||
|
||
<Card className="border-none shadow-lg bg-gradient-to-br from-white to-blue-50 mt-6">
|
||
<CardHeader>
|
||
<CardTitle className="text-lg font-bold flex items-center">
|
||
<span className="bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-teal-600">权限矩阵</span>
|
||
<div className="ml-2 h-1 w-10 bg-gradient-to-r from-blue-600 to-teal-600 rounded-full"></div>
|
||
</CardTitle>
|
||
<CardDescription>角色权限分配表</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="overflow-x-auto">
|
||
<Table>
|
||
<TableHeader className="bg-gray-50">
|
||
<TableRow>
|
||
<TableHead className="w-[200px]">权限/角色</TableHead>
|
||
{roles.map((role) => (
|
||
<TableHead key={role.id}>{role.name}</TableHead>
|
||
))}
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
<TableRow className="hover:bg-gray-50 transition-colors">
|
||
<TableCell className="font-medium">仪表盘查看</TableCell>
|
||
{roles.map((role) => (
|
||
<TableCell key={role.id}>
|
||
<Checkbox checked={role.permissions["dashboard.view"]} disabled />
|
||
</TableCell>
|
||
))}
|
||
</TableRow>
|
||
<TableRow className="hover:bg-gray-50 transition-colors">
|
||
<TableCell className="font-medium">用户管理</TableCell>
|
||
{roles.map((role) => (
|
||
<TableCell key={role.id}>
|
||
<Checkbox checked={role.permissions["users.view"]} disabled />
|
||
</TableCell>
|
||
))}
|
||
</TableRow>
|
||
<TableRow className="hover:bg-gray-50 transition-colors">
|
||
<TableCell className="font-medium">角色权限管理</TableCell>
|
||
{roles.map((role) => (
|
||
<TableCell key={role.id}>
|
||
<Checkbox checked={role.permissions["roles.view"]} disabled />
|
||
</TableCell>
|
||
))}
|
||
</TableRow>
|
||
<TableRow className="hover:bg-gray-50 transition-colors">
|
||
<TableCell className="font-medium">AI模型管理</TableCell>
|
||
{roles.map((role) => (
|
||
<TableCell key={role.id}>
|
||
<Checkbox checked={role.permissions["ai.view"]} disabled />
|
||
</TableCell>
|
||
))}
|
||
</TableRow>
|
||
<TableRow className="hover:bg-gray-50 transition-colors">
|
||
<TableCell className="font-medium">服装管理</TableCell>
|
||
{roles.map((role) => (
|
||
<TableCell key={role.id}>
|
||
<Checkbox checked={role.permissions["outfits.view"]} disabled />
|
||
</TableCell>
|
||
))}
|
||
</TableRow>
|
||
<TableRow className="hover:bg-gray-50 transition-colors">
|
||
<TableCell className="font-medium">道具管理</TableCell>
|
||
{roles.map((role) => (
|
||
<TableCell key={role.id}>
|
||
<Checkbox checked={role.permissions["props.view"]} disabled />
|
||
</TableCell>
|
||
))}
|
||
</TableRow>
|
||
<TableRow className="hover:bg-gray-50 transition-colors">
|
||
<TableCell className="font-medium">歌曲管理</TableCell>
|
||
{roles.map((role) => (
|
||
<TableCell key={role.id}>
|
||
<Checkbox checked={role.permissions["songs.view"]} disabled />
|
||
</TableCell>
|
||
))}
|
||
</TableRow>
|
||
<TableRow className="hover:bg-gray-50 transition-colors">
|
||
<TableCell className="font-medium">系统设置</TableCell>
|
||
{roles.map((role) => (
|
||
<TableCell key={role.id}>
|
||
<Checkbox checked={role.permissions["settings.view"]} disabled />
|
||
</TableCell>
|
||
))}
|
||
</TableRow>
|
||
</TableBody>
|
||
</Table>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 编辑角色对话框 */}
|
||
{editingRole && (
|
||
<RoleDialog
|
||
mode="edit"
|
||
open={!!editingRole}
|
||
onOpenChange={(open) => {
|
||
if (!open) setEditingRole(null)
|
||
}}
|
||
onSubmit={handleEditRole}
|
||
defaultValues={{
|
||
name: editingRole.name,
|
||
description: editingRole.description,
|
||
status: editingRole.status,
|
||
permissions: editingRole.permissions,
|
||
}}
|
||
/>
|
||
)}
|
||
</DashboardShell>
|
||
)
|
||
}
|