195 lines
5.9 KiB
TypeScript
195 lines
5.9 KiB
TypeScript
import { Button, Card, Col, message, Row, Space, Table, Tag, Tree, Typography } from "antd";
|
|
import type { DataNode } from "antd/es/tree";
|
|
import { useEffect, useMemo, useState } from "react";
|
|
import { listPermissions, listRolePermissions, listRoles, saveRolePermissions } from "../api";
|
|
import type { SysPermission, SysRole } from "../types";
|
|
|
|
const { Title, Text } = Typography;
|
|
|
|
type PermissionNode = SysPermission & { key: number; children?: PermissionNode[] };
|
|
|
|
function buildPermissionTree(list: SysPermission[]): PermissionNode[] {
|
|
const map = new Map<number, PermissionNode>();
|
|
const roots: PermissionNode[] = [];
|
|
|
|
list.forEach((item) => {
|
|
map.set(item.permId, { ...item, key: item.permId, children: [] });
|
|
});
|
|
|
|
map.forEach((node) => {
|
|
if (node.parentId && map.has(node.parentId)) {
|
|
map.get(node.parentId)!.children!.push(node);
|
|
} else {
|
|
roots.push(node);
|
|
}
|
|
});
|
|
|
|
const sortNodes = (nodes: PermissionNode[]) => {
|
|
nodes.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
|
|
nodes.forEach((n) => n.children && sortNodes(n.children));
|
|
};
|
|
sortNodes(roots);
|
|
return roots;
|
|
}
|
|
|
|
function toTreeData(nodes: PermissionNode[]): DataNode[] {
|
|
return nodes.map((node) => ({
|
|
key: node.permId,
|
|
title: (
|
|
<Space>
|
|
<span>{node.name}</span>
|
|
{node.permType === "button" && <Tag color="blue">按钮</Tag>}
|
|
</Space>
|
|
),
|
|
children: node.children ? toTreeData(node.children) : undefined
|
|
}));
|
|
}
|
|
|
|
export default function RolePermissionBinding() {
|
|
const [roles, setRoles] = useState<SysRole[]>([]);
|
|
const [permissions, setPermissions] = useState<SysPermission[]>([]);
|
|
const [loadingRoles, setLoadingRoles] = useState(false);
|
|
const [loadingPerms, setLoadingPerms] = useState(false);
|
|
const [saving, setSaving] = useState(false);
|
|
const [selectedRoleId, setSelectedRoleId] = useState<number | null>(null);
|
|
const [checkedPermIds, setCheckedPermIds] = useState<number[]>([]);
|
|
|
|
const selectedRole = useMemo(
|
|
() => roles.find((r) => r.roleId === selectedRoleId) || null,
|
|
[roles, selectedRoleId]
|
|
);
|
|
|
|
const loadRoles = async () => {
|
|
setLoadingRoles(true);
|
|
try {
|
|
const list = await listRoles();
|
|
setRoles(list || []);
|
|
} finally {
|
|
setLoadingRoles(false);
|
|
}
|
|
};
|
|
|
|
const loadPermissions = async () => {
|
|
setLoadingPerms(true);
|
|
try {
|
|
const list = await listPermissions();
|
|
setPermissions(list || []);
|
|
} catch (e) {
|
|
message.error("加载权限失败,请确认接口已实现");
|
|
} finally {
|
|
setLoadingPerms(false);
|
|
}
|
|
};
|
|
|
|
const loadRolePermissions = async (roleId: number) => {
|
|
try {
|
|
const list = await listRolePermissions(roleId);
|
|
setCheckedPermIds(list || []);
|
|
} catch (e) {
|
|
setCheckedPermIds([]);
|
|
message.error("加载角色权限失败,请确认接口已实现");
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
loadRoles();
|
|
loadPermissions();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (selectedRoleId) {
|
|
loadRolePermissions(selectedRoleId);
|
|
} else {
|
|
setCheckedPermIds([]);
|
|
}
|
|
}, [selectedRoleId]);
|
|
|
|
const treeData = useMemo(() => toTreeData(buildPermissionTree(permissions)), [permissions]);
|
|
|
|
const handleSave = async () => {
|
|
if (!selectedRoleId) {
|
|
message.warning("请先选择角色");
|
|
return;
|
|
}
|
|
setSaving(true);
|
|
try {
|
|
await saveRolePermissions(selectedRoleId, checkedPermIds);
|
|
message.success("角色权限绑定已保存");
|
|
} catch (e) {
|
|
message.error("保存失败,请确认接口已实现");
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="page-shell">
|
|
<div className="page-header">
|
|
<div>
|
|
<Title level={4} className="page-title">角色权限绑定</Title>
|
|
<Text type="secondary" className="page-subtitle">为角色配置菜单与按钮权限</Text>
|
|
</div>
|
|
<Button type="primary" onClick={handleSave} loading={saving} disabled={!selectedRoleId}>
|
|
保存绑定
|
|
</Button>
|
|
</div>
|
|
|
|
<Row gutter={[24, 24]}>
|
|
<Col xs={24} lg={10}>
|
|
<Card title="选择角色" bordered={false} className="surface-card">
|
|
<Table
|
|
rowKey="roleId"
|
|
size="middle"
|
|
loading={loadingRoles}
|
|
dataSource={roles}
|
|
rowSelection={{
|
|
type: "radio",
|
|
selectedRowKeys: selectedRoleId ? [selectedRoleId] : [],
|
|
onChange: (keys) => setSelectedRoleId(keys[0] as number)
|
|
}}
|
|
pagination={{ pageSize: 8 }}
|
|
columns={[
|
|
{ title: "ID", dataIndex: "roleId", width: 80 },
|
|
{ title: "角色编码", dataIndex: "roleCode" },
|
|
{ title: "角色名称", dataIndex: "roleName" },
|
|
{
|
|
title: "状态",
|
|
dataIndex: "status",
|
|
width: 90,
|
|
render: (v) => (v === 1 ? <Tag color="green">启用</Tag> : <Tag color="red">禁用</Tag>)
|
|
}
|
|
]}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
<Col xs={24} lg={14}>
|
|
<Card
|
|
title="配置权限"
|
|
bordered={false}
|
|
className="surface-card"
|
|
extra={
|
|
<Text type="secondary">
|
|
{selectedRole ? `当前角色:${selectedRole.roleName}` : "未选择角色"}
|
|
</Text>
|
|
}
|
|
>
|
|
<Tree
|
|
checkable
|
|
selectable={false}
|
|
treeData={treeData}
|
|
checkedKeys={checkedPermIds}
|
|
onCheck={(keys) => setCheckedPermIds(keys as number[])}
|
|
defaultExpandAll
|
|
/>
|
|
{!permissions.length && !loadingPerms && (
|
|
<div style={{ marginTop: 12 }}>
|
|
<Text type="secondary">暂无权限数据</Text>
|
|
</div>
|
|
)}
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
);
|
|
}
|