Files
model-platform/frontend/app/features/admin/PermissionEditDialog.tsx
T
2026-09-02 10:10:41 +08:00

129 lines
3.9 KiB
TypeScript

import { useEffect, useState } from "react";
import { X } from "lucide-react";
import type { PlatformPermission, Role } from "../../services/api";
import { Button } from "~/components/ui/button";
import {
modalBackdropClass,
modalCompactClass,
modalEyebrowClass,
modalFooterClass,
modalFormClass,
modalHeaderClass,
modalTitleClass,
} from "../platform/modalUi";
import { PermissionCheckboxList } from "./PermissionCheckboxList";
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 PermissionEditDialog({
open,
target,
permissions,
currentCodes,
saving,
onSubmit,
onClose,
}: {
open: boolean;
target: Role | null;
permissions: PlatformPermission[];
currentCodes: string[];
saving: boolean;
onSubmit: (codes: string[]) => void;
onClose: () => void;
}) {
const [selectedCodes, setSelectedCodes] = useState<string[]>(() => {
if (!open || !target) return [];
const initial = [...currentCodes];
// admin 角色必须保留 system:view(后端守卫);前端锁定勾选兜底。
if (target.role_code === "admin" && !initial.includes("system:view")) {
initial.push("system:view");
}
return initial;
});
useEffect(() => {
const initial = [...currentCodes];
if (target?.role_code === "admin" && !initial.includes("system:view")) {
initial.push("system:view");
}
setSelectedCodes(initial);
}, [open, target, currentCodes]);
const togglePermission = (code: string): void => {
if (!target) return;
const locked = target.role_code === "admin"
? code === "system:view"
: code.startsWith("system:");
if (locked) return;
setSelectedCodes((current) =>
current.includes(code)
? current.filter((item) => item !== code)
: [...current, code],
);
};
if (!open || !target) return null;
return (
<div className={modalBackdropClass} role="presentation">
<section className={modalCompactClass} role="dialog" aria-modal="true">
<div className={modalHeaderClass}>
<div>
<span className={modalEyebrowClass}>PERMISSIONS</span>
<h2 className={modalTitleClass}>权限配置 · {target.role_name}</h2>
</div>
<Button
variant="ghost"
size="icon-sm"
type="button"
aria-label="关闭"
onClick={onClose}
className={CLOSE_BUTTON_CLASS}
>
<X size={16} />
</Button>
</div>
<div className={modalFormClass}>
{target.role_code === "admin" && (
<p className="role-error">admin 角色必须保留 system:view 权限。</p>
)}
<PermissionCheckboxList
permissions={permissions}
selected={selectedCodes}
targetRoleCode={target.role_code}
onToggle={togglePermission}
/>
</div>
<div className={modalFooterClass}>
<Button
variant="outline"
size="sm"
type="button"
onClick={onClose}
className={SECONDARY_BUTTON_CLASS}
>
取消
</Button>
<Button
variant="default"
size="sm"
type="button"
disabled={saving}
onClick={() => onSubmit(selectedCodes)}
className={PRIMARY_BUTTON_CLASS}
>
{saving ? "保存中…" : "保存权限"}
</Button>
</div>
</section>
</div>
);
}