import { useEffect, useState } from "react"; import { X } from "lucide-react"; import type { PlatformPermission, Role, RoleCreatePayload, RoleUpdatePayload, } from "../../services/api"; import { Button } from "~/components/ui/button"; import { formFieldClass, formInputClass, formTextareaClass, modalBackdropClass, modalCompactClass, modalEyebrowClass, modalFooterClass, modalFormClass, modalHeaderClass, modalTitleClass, } from "../platform/modalUi"; import { PermissionCheckboxList } from "./PermissionCheckboxList"; const RESERVED_ROLE_CODES = new Set(["admin", "developer"]); const ROLE_CODE_PATTERN = /^[a-z][a-z0-9_-]{1,63}$/; type RoleFormState = { role_code: string; role_name: string; description: string; permission_codes: string[]; }; const EMPTY_FORM: RoleFormState = { role_code: "", role_name: "", description: "", permission_codes: [], }; /** 实时校验 role_code:空值不报错,提交时由必填拦截兜底。 */ function validateRoleCode(value: string): string { const code = value.trim(); if (!code) return ""; if (RESERVED_ROLE_CODES.has(code)) { return `role_code "${code}" 是预留字,不可使用`; } if (!/^[a-z]/.test(code)) return "role_code 必须以小写字母开头"; if (!/^[a-z][a-z0-9_-]*$/.test(code)) { return "role_code 仅含小写字母/数字/下划线/连字符"; } if (code.length < 2) return "role_code 至少 2 个字符"; if (code.length > 64) return "role_code 最多 64 个字符"; if (!ROLE_CODE_PATTERN.test(code)) { return "role_code 必须以小写字母开头,仅含字母/数字/下划线/连字符"; } return ""; } const CLOSE_BUTTON_CLASS = "size-[34px] rounded-[7px] border border-[#dfe6ed] bg-white hover:bg-[#f7faff] hover:border-[#b9c9da]"; const SECONDARY_BUTTON_CLASS = "h-[37px] gap-[7px] rounded-md border-[#d8e1e9] bg-white px-[15px] text-xs font-[650] text-[#56687c]"; const PRIMARY_BUTTON_CLASS = "h-[37px] gap-[7px] rounded-md border border-[#126ac3] bg-gradient-to-br from-[#1881e7] to-[#1268c9] px-[15px] text-xs font-[650] text-white shadow-[0_6px_15px_rgb(20_111_202/18%)] enabled:hover:from-[#1175d8] enabled:hover:to-[#0e5bad]"; export function RoleEditDialog({ open, editing, permissions, saving, onNotify, onSubmit, onClose, }: { open: boolean; editing: Role | null; permissions: PlatformPermission[]; saving: boolean; onNotify: (notice: { tone: "success" | "error" | "info"; message: string }) => void; onSubmit: (payload: RoleCreatePayload | RoleUpdatePayload) => void; onClose: () => void; }) { const [form, setForm] = useState(() => editing ? { role_code: editing.role_code, role_name: editing.role_name, description: editing.description ?? "", permission_codes: [...editing.permission_codes], } : EMPTY_FORM, ); const [roleCodeError, setRoleCodeError] = useState(""); useEffect(() => { setForm( editing ? { role_code: editing.role_code, role_name: editing.role_name, description: editing.description ?? "", permission_codes: [...editing.permission_codes], } : EMPTY_FORM, ); setRoleCodeError(""); }, [open, editing]); const handleRoleCodeChange = ( event: React.ChangeEvent, ): void => { const value = event.target.value; setForm((current) => ({ ...current, role_code: value })); setRoleCodeError(validateRoleCode(value)); }; const toggleCreatePermission = (code: string): void => { // 新建角色为非 admin,system:* 由后端守卫禁止;此处防御性拦截 if (code.startsWith("system:")) return; setForm((current) => ({ ...current, permission_codes: current.permission_codes.includes(code) ? current.permission_codes.filter((item) => item !== code) : [...current.permission_codes, code], })); }; const handleSubmit = (event: React.FormEvent): void => { event.preventDefault(); if (!form.role_name.trim()) { onNotify({ tone: "error", message: "请输入角色名称" }); return; } if (!editing) { const codeError = validateRoleCode(form.role_code); if (codeError) { setRoleCodeError(codeError); onNotify({ tone: "error", message: codeError }); return; } } const payload: RoleCreatePayload | RoleUpdatePayload = editing ? { // 内置角色 role_name 锁定;description 可改 ...(editing.is_builtin ? {} : { role_name: form.role_name.trim() }), description: form.description.trim() || null, } : { role_code: form.role_code.trim(), role_name: form.role_name.trim(), description: form.description.trim() || undefined, permission_codes: form.permission_codes, }; onSubmit(payload); }; if (!open) return null; return (
ROLE

{editing ? "编辑角色" : "新建角色"}

void handleSubmit(event)}> {editing ? ( ) : ( )}