780 lines
28 KiB
TypeScript
780 lines
28 KiB
TypeScript
import React, { useState, useEffect, useCallback } from 'react';
|
|
import {
|
|
App, Table, Form, Input, Select, Button, Modal, Space, DatePicker, Switch, Dropdown, InputNumber, Tree, Tooltip, Checkbox, Popconfirm, Row, Col
|
|
} from 'antd';
|
|
import type { TableColumnsType } from 'antd';
|
|
import {
|
|
SearchOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined, DownloadOutlined,
|
|
CheckCircleOutlined, DownOutlined, ExclamationCircleOutlined, QuestionCircleOutlined, UserOutlined
|
|
} from '@ant-design/icons';
|
|
import {
|
|
listRole, getRole, addRole, updateRole, dataScope, changeRoleStatus, delRole, deptTreeSelect
|
|
} from '../../api/system/role';
|
|
import { treeselect as menuTreeselect, roleMenuTreeselect } from '../../api/system/menu';
|
|
import { saveAs } from 'file-saver';
|
|
import dayjs from 'dayjs';
|
|
import { parseTime } from '../../utils/ruoyi'; // Custom utility
|
|
import Permission from '@/components/Permission';
|
|
import ReadonlyAction from '@/components/Permission/ReadonlyAction';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import './system-admin.css';
|
|
|
|
const { RangePicker } = DatePicker;
|
|
|
|
// Mock Dictionaries
|
|
const sysNormalDisableDict = [
|
|
{ value: '0', label: '正常' },
|
|
{ value: '1', label: '停用' },
|
|
];
|
|
|
|
const dataScopeOptions = [
|
|
{ value: '1', label: '全部数据权限' },
|
|
{ value: '2', label: '自定数据权限' },
|
|
{ value: '3', label: '本部门数据权限' },
|
|
{ value: '4', label: '本部门及以下数据权限' },
|
|
{ value: '5', label: '仅本人数据权限' },
|
|
];
|
|
|
|
type RoleQueryParams = {
|
|
pageNum?: number;
|
|
pageSize?: number;
|
|
roleName?: string;
|
|
roleKey?: string;
|
|
status?: string;
|
|
beginTime?: string;
|
|
endTime?: string;
|
|
};
|
|
|
|
type RoleRecord = {
|
|
roleId?: string | number;
|
|
roleName?: string;
|
|
roleKey?: string;
|
|
roleSort?: string | number;
|
|
status?: string;
|
|
createTime?: string | number | Date;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
const RolePage: React.FC = () => {
|
|
const { message, modal } = App.useApp();
|
|
const navigate = useNavigate();
|
|
const [queryForm] = Form.useForm();
|
|
const [roleForm] = Form.useForm();
|
|
const [dataScopeForm] = Form.useForm();
|
|
const [loading, setLoading] = useState(false);
|
|
const [roleList, setRoleList] = useState<RoleRecord[]>([]);
|
|
const [total, setTotal] = useState(0);
|
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
|
const [dateRange, setDateRange] = useState<[dayjs.Dayjs | null, dayjs.Dayjs | null] | null>(null);
|
|
|
|
const [addEditModalVisible, setAddEditModalVisible] = useState(false);
|
|
const [addEditModalTitle, setAddEditModalTitle] = useState('');
|
|
const [formType, setFormType] = useState<'add' | 'edit'>('add');
|
|
|
|
const [dataScopeModalVisible, setDataScopeModalVisible] = useState(false);
|
|
const [dataScopeModalTitle, setDataScopeModalTitle] = useState('');
|
|
|
|
const [menuOptions, setMenuOptions] = useState<any[]>([]);
|
|
const [deptOptions, setDeptOptions] = useState<any[]>([]);
|
|
|
|
const [menuExpand, setMenuExpand] = useState(false);
|
|
const [menuNodeAll, setMenuNodeAll] = useState(false);
|
|
const [deptExpand, setDeptExpand] = useState(false);
|
|
const [deptNodeAll, setDeptNodeAll] = useState(false);
|
|
const [menuExpandedKeys, setMenuExpandedKeys] = useState<React.Key[]>([]);
|
|
const [deptExpandedKeys, setDeptExpandedKeys] = useState<React.Key[]>([]);
|
|
const [menuCheckedKeys, setMenuCheckedKeys] = useState<React.Key[]>([]);
|
|
const [menuHalfCheckedKeys, setMenuHalfCheckedKeys] = useState<React.Key[]>([]);
|
|
const [deptCheckedKeys, setDeptCheckedKeys] = useState<React.Key[]>([]);
|
|
const [deptHalfCheckedKeys, setDeptHalfCheckedKeys] = useState<React.Key[]>([]);
|
|
const [menuCheckStrictly, setMenuCheckStrictly] = useState(true); // For menu tree linkage
|
|
const [deptCheckStrictly, setDeptCheckStrictly] = useState(true); // For dept tree linkage
|
|
|
|
const [currentRole, setCurrentRole] = useState<RoleRecord>({}); // For storing current role in modals
|
|
|
|
const [queryParams, setQueryParams] = useState<RoleQueryParams>({
|
|
pageNum: 1,
|
|
pageSize: 10,
|
|
roleName: undefined,
|
|
roleKey: undefined,
|
|
status: undefined,
|
|
});
|
|
|
|
const extractResponseData = (response: any) => {
|
|
if (response && typeof response === 'object' && 'data' in response) {
|
|
return response.data ?? {};
|
|
}
|
|
return response ?? {};
|
|
};
|
|
|
|
const extractTreeNodes = (response: any, key: 'menus' | 'depts') => {
|
|
if (!response || typeof response !== 'object') {
|
|
return [];
|
|
}
|
|
if (Array.isArray(response[key])) {
|
|
return response[key];
|
|
}
|
|
if (Array.isArray(response.data)) {
|
|
return response.data;
|
|
}
|
|
if (response.data && typeof response.data === 'object' && Array.isArray(response.data[key])) {
|
|
return response.data[key];
|
|
}
|
|
return [];
|
|
};
|
|
|
|
const extractCheckedKeys = (response: any): React.Key[] => {
|
|
if (!response || typeof response !== 'object') {
|
|
return [];
|
|
}
|
|
if (Array.isArray(response.checkedKeys)) {
|
|
return response.checkedKeys;
|
|
}
|
|
if (response.data && typeof response.data === 'object' && Array.isArray(response.data.checkedKeys)) {
|
|
return response.data.checkedKeys;
|
|
}
|
|
return [];
|
|
};
|
|
|
|
const collectTreeKeys = (nodes: any[]): React.Key[] => {
|
|
const keys: React.Key[] = [];
|
|
const visit = (list: any[]) => {
|
|
list.forEach((node) => {
|
|
const key = node?.id ?? node?.key;
|
|
if (key !== undefined) {
|
|
keys.push(key);
|
|
}
|
|
if (Array.isArray(node?.children) && node.children.length > 0) {
|
|
visit(node.children);
|
|
}
|
|
});
|
|
};
|
|
visit(nodes ?? []);
|
|
return keys;
|
|
};
|
|
|
|
const getList = useCallback(async () => {
|
|
setLoading(true);
|
|
try {
|
|
const formattedQueryParams: RoleQueryParams = { ...queryParams };
|
|
if (dateRange && dateRange[0] && dateRange[1]) {
|
|
formattedQueryParams.beginTime = dateRange[0].format('YYYY-MM-DD');
|
|
formattedQueryParams.endTime = dateRange[1].format('YYYY-MM-DD');
|
|
} else {
|
|
formattedQueryParams.beginTime = undefined;
|
|
formattedQueryParams.endTime = undefined;
|
|
}
|
|
const response = await listRole(formattedQueryParams) as { rows?: RoleRecord[]; total?: number };
|
|
setRoleList(response.rows ?? []);
|
|
setTotal(response.total ?? 0);
|
|
} catch (error) {
|
|
console.error('Failed to fetch role list:', error);
|
|
message.error('获取角色列表失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [queryParams, dateRange]);
|
|
|
|
const getMenuTreeselect = useCallback(async (roleId?: string | number) => {
|
|
try {
|
|
const response = roleId !== undefined ? await roleMenuTreeselect(roleId) : await menuTreeselect();
|
|
setMenuOptions(extractTreeNodes(response, 'menus'));
|
|
return response;
|
|
} catch (error) {
|
|
console.error('Failed to fetch menu tree:', error);
|
|
message.error('获取菜单树失败');
|
|
return null;
|
|
}
|
|
}, []);
|
|
|
|
const getDeptTreeselect = useCallback(async (roleId?: string | number) => {
|
|
try {
|
|
const response = await deptTreeSelect(roleId ?? '');
|
|
setDeptOptions(extractTreeNodes(response, 'depts'));
|
|
return response;
|
|
} catch (error) {
|
|
console.error('Failed to fetch department tree:', error);
|
|
message.error('获取部门树失败');
|
|
return null;
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
getList();
|
|
}, [getList]);
|
|
|
|
|
|
const handleQuery = (values?: Partial<RoleQueryParams>) => {
|
|
const formValues = values ?? queryForm.getFieldsValue();
|
|
setQueryParams((prev) => ({
|
|
...prev,
|
|
pageNum: 1,
|
|
roleName: formValues.roleName || undefined,
|
|
roleKey: formValues.roleKey || undefined,
|
|
status: formValues.status || undefined,
|
|
}));
|
|
};
|
|
|
|
const resetQuery = () => {
|
|
queryForm.resetFields();
|
|
setDateRange(null);
|
|
setQueryParams({
|
|
pageNum: 1,
|
|
pageSize: 10,
|
|
roleName: undefined,
|
|
roleKey: undefined,
|
|
status: undefined,
|
|
});
|
|
};
|
|
|
|
const handleSelectionChange = (selectedKeys: React.Key[]) => {
|
|
setSelectedRowKeys(selectedKeys);
|
|
};
|
|
|
|
const handleStatusChange = async (record: any) => {
|
|
const newStatus = record.status === '0' ? '1' : '0';
|
|
const text = record.status === '0' ? '停用' : '启用';
|
|
modal.confirm({
|
|
title: '确认操作',
|
|
icon: <ExclamationCircleOutlined />,
|
|
content: `确认要"${text}"角色"${record.roleName}"吗?`,
|
|
onOk: async () => {
|
|
try {
|
|
await changeRoleStatus(record.roleId, newStatus);
|
|
message.success(`${text}成功`);
|
|
getList();
|
|
} catch (error) {
|
|
message.error(`${text}失败`);
|
|
setRoleList(prev => prev.map(role => role.roleId === record.roleId ? { ...role, status: record.status } : role));
|
|
}
|
|
},
|
|
onCancel() {
|
|
setRoleList(prev => prev.map(role => role.roleId === record.roleId ? { ...role, status: record.status } : role));
|
|
},
|
|
});
|
|
};
|
|
|
|
const resetRoleForm = () => {
|
|
roleForm.resetFields();
|
|
dataScopeForm.resetFields();
|
|
setMenuOptions([]); // Clear menu options
|
|
setDeptOptions([]); // Clear dept options
|
|
setCurrentRole({}); // Clear current role
|
|
setMenuExpand(false);
|
|
setMenuNodeAll(false);
|
|
setDeptExpand(false);
|
|
setDeptNodeAll(false);
|
|
setMenuExpandedKeys([]);
|
|
setDeptExpandedKeys([]);
|
|
setMenuCheckedKeys([]);
|
|
setMenuHalfCheckedKeys([]);
|
|
setDeptCheckedKeys([]);
|
|
setDeptHalfCheckedKeys([]);
|
|
setMenuCheckStrictly(true);
|
|
setDeptCheckStrictly(true);
|
|
};
|
|
|
|
const handleAdd = async () => {
|
|
resetRoleForm();
|
|
await getMenuTreeselect(); // Get full menu tree
|
|
setAddEditModalTitle('添加角色');
|
|
setFormType('add');
|
|
setAddEditModalVisible(true);
|
|
roleForm.setFieldsValue({
|
|
roleSort: 0,
|
|
status: '0',
|
|
menuCheckStrictly: true,
|
|
deptCheckStrictly: true,
|
|
});
|
|
};
|
|
|
|
const handleUpdate = async (record?: any) => {
|
|
resetRoleForm();
|
|
const roleId = record?.roleId || selectedRowKeys[0];
|
|
if (roleId === undefined) {
|
|
message.warning('请选择要修改的角色');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const roleMenuResponse = await getMenuTreeselect(roleId); // Get menu tree with selected keys
|
|
const roleResponse = await getRole(roleId);
|
|
|
|
const roleData = extractResponseData(roleResponse);
|
|
setCurrentRole(roleData);
|
|
setMenuCheckStrictly(roleData.menuCheckStrictly === undefined ? true : !!roleData.menuCheckStrictly);
|
|
setDeptCheckStrictly(roleData.deptCheckStrictly === undefined ? true : !!roleData.deptCheckStrictly);
|
|
setMenuCheckedKeys(extractCheckedKeys(roleMenuResponse));
|
|
setMenuHalfCheckedKeys([]);
|
|
setAddEditModalTitle('修改角色');
|
|
setFormType('edit');
|
|
|
|
roleForm.setFieldsValue({
|
|
...roleData,
|
|
status: roleData.status?.toString() ?? '0',
|
|
});
|
|
|
|
setAddEditModalVisible(true);
|
|
} catch (error) {
|
|
message.error('获取角色详情失败');
|
|
}
|
|
};
|
|
|
|
const handleDelete = (record?: any) => {
|
|
const roleIds = record?.roleId ? [record.roleId] : selectedRowKeys;
|
|
if (roleIds.length === 0) {
|
|
message.warning('请选择要删除的角色');
|
|
return;
|
|
}
|
|
modal.confirm({
|
|
title: '确认删除',
|
|
icon: <ExclamationCircleOutlined />,
|
|
content: `是否确认删除角色编号为"${roleIds.join(',')}"的数据项?`,
|
|
onOk: async () => {
|
|
try {
|
|
await delRole(roleIds.join(','));
|
|
message.success('删除成功');
|
|
setSelectedRowKeys([]);
|
|
getList();
|
|
} catch (error) {
|
|
message.error('删除失败');
|
|
}
|
|
},
|
|
});
|
|
};
|
|
|
|
const handleExport = async () => {
|
|
const hide = message.loading('正在导出数据...', 0);
|
|
try {
|
|
const response = await listRole({ ...queryParams, pageNum: undefined, pageSize: undefined }) as { rows?: RoleRecord[] };
|
|
const header = ['角色编号', '角色名称', '权限字符', '显示顺序', '状态', '创建时间'];
|
|
const data = (response.rows ?? []).map((role) => [
|
|
role.roleId, role.roleName, role.roleKey, role.roleSort,
|
|
sysNormalDisableDict.find((d) => d.value === String(role.status ?? ''))?.label ?? '',
|
|
parseTime(role.createTime),
|
|
]);
|
|
|
|
const csvContent = [header, ...data]
|
|
.map((row) => row.map((cell: unknown) => `"${String(cell).replace(/"/g, '""')}"`).join(','))
|
|
.join('\n');
|
|
|
|
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
|
saveAs(blob, `role_${dayjs().format('YYYYMMDDHHmmss')}.csv`);
|
|
hide();
|
|
message.success('导出成功');
|
|
} catch {
|
|
hide();
|
|
message.error('导出失败');
|
|
}
|
|
};
|
|
|
|
const handleCommand = (key: string, record: any) => {
|
|
switch (key) {
|
|
case 'handleDataScope':
|
|
handleDataScope(record);
|
|
break;
|
|
case 'handleAuthUser':
|
|
handleAuthUser(record);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
|
|
const handleDataScope = async (record: any) => {
|
|
resetRoleForm();
|
|
const roleId = record.roleId;
|
|
try {
|
|
const deptTreeResponse = await getDeptTreeselect(roleId); // Get dept tree with selected keys
|
|
const roleResponse = await getRole(roleId);
|
|
const roleData = extractResponseData(roleResponse);
|
|
setCurrentRole(roleData);
|
|
setDataScopeModalTitle('分配数据权限');
|
|
setDataScopeModalVisible(true);
|
|
dataScopeForm.setFieldsValue({
|
|
...roleData,
|
|
dataScope: roleData.dataScope?.toString(),
|
|
});
|
|
setDeptCheckedKeys(extractCheckedKeys(deptTreeResponse));
|
|
setDeptHalfCheckedKeys([]);
|
|
} catch (error) {
|
|
message.error('获取数据权限信息失败');
|
|
}
|
|
};
|
|
|
|
const handleAuthUser = (record: any) => {
|
|
navigate(`/system/role-auth/user/${record.roleId}`);
|
|
};
|
|
|
|
const submitRoleForm = async (values: any) => {
|
|
try {
|
|
const menuIds = [...menuCheckedKeys, ...menuHalfCheckedKeys];
|
|
const roleData = { ...currentRole, ...values, menuIds, menuCheckStrictly };
|
|
|
|
if (formType === 'edit') {
|
|
await updateRole(roleData);
|
|
message.success('修改成功');
|
|
} else {
|
|
await addRole(roleData);
|
|
message.success('新增成功');
|
|
}
|
|
setAddEditModalVisible(false);
|
|
getList();
|
|
} catch (error) {
|
|
console.error('Submit role form failed:', error);
|
|
message.error('操作失败');
|
|
}
|
|
};
|
|
|
|
const submitDataScopeForm = async (values: any) => {
|
|
try {
|
|
let deptIds: React.Key[] = [];
|
|
if (values.dataScope === '2') {
|
|
deptIds = [...deptCheckedKeys, ...deptHalfCheckedKeys];
|
|
}
|
|
const roleData = { ...currentRole, ...values, deptIds, deptCheckStrictly };
|
|
|
|
await dataScope(roleData);
|
|
message.success('分配数据权限成功');
|
|
setDataScopeModalVisible(false);
|
|
getList();
|
|
} catch (error) {
|
|
message.error('分配数据权限失败');
|
|
}
|
|
};
|
|
|
|
const dataScopeSelectChange = (value: string) => {
|
|
dataScopeForm.setFieldValue('dataScope', value);
|
|
if (value !== '2') {
|
|
setDeptCheckedKeys([]);
|
|
setDeptHalfCheckedKeys([]);
|
|
}
|
|
};
|
|
|
|
const handleCheckedTreeExpand = (value: boolean, type: 'menu' | 'dept') => {
|
|
if (type === 'menu') {
|
|
setMenuExpand(value);
|
|
setMenuExpandedKeys(value ? collectTreeKeys(menuOptions) : []);
|
|
return;
|
|
}
|
|
setDeptExpand(value);
|
|
setDeptExpandedKeys(value ? collectTreeKeys(deptOptions) : []);
|
|
};
|
|
|
|
const handleCheckedTreeNodeAll = (value: boolean, type: 'menu' | 'dept') => {
|
|
if (type === 'menu') {
|
|
const keys = value ? collectTreeKeys(menuOptions) : [];
|
|
setMenuCheckedKeys(keys);
|
|
setMenuHalfCheckedKeys([]);
|
|
setMenuNodeAll(value);
|
|
return;
|
|
}
|
|
const keys = value ? collectTreeKeys(deptOptions) : [];
|
|
setDeptCheckedKeys(keys);
|
|
setDeptHalfCheckedKeys([]);
|
|
setDeptNodeAll(value);
|
|
};
|
|
|
|
const handleMenuCheck = (checked: any, info: any) => {
|
|
if (Array.isArray(checked)) {
|
|
setMenuCheckedKeys(checked);
|
|
setMenuHalfCheckedKeys(info?.halfCheckedKeys ?? []);
|
|
return;
|
|
}
|
|
setMenuCheckedKeys(checked?.checked ?? []);
|
|
setMenuHalfCheckedKeys(checked?.halfChecked ?? []);
|
|
};
|
|
|
|
const handleDeptCheck = (checked: any, info: any) => {
|
|
if (Array.isArray(checked)) {
|
|
setDeptCheckedKeys(checked);
|
|
setDeptHalfCheckedKeys(info?.halfCheckedKeys ?? []);
|
|
return;
|
|
}
|
|
setDeptCheckedKeys(checked?.checked ?? []);
|
|
setDeptHalfCheckedKeys(checked?.halfChecked ?? []);
|
|
};
|
|
|
|
const handleCheckedTreeConnect = (value: boolean, type: 'menu' | 'dept') => {
|
|
if (type === 'menu') setMenuCheckStrictly(value);
|
|
if (type === 'dept') setDeptCheckStrictly(value);
|
|
};
|
|
|
|
const columns: TableColumnsType<any> = [
|
|
Table.SELECTION_COLUMN,
|
|
{ title: '角色编号', dataIndex: 'roleId', align: 'center', width: 100 },
|
|
{ title: '角色名称', dataIndex: 'roleName', align: 'center', ellipsis: true },
|
|
{ title: '权限字符', dataIndex: 'roleKey', align: 'center', ellipsis: true },
|
|
{ title: '显示顺序', dataIndex: 'roleSort', align: 'center', width: 80 },
|
|
{
|
|
title: '状态', dataIndex: 'status', align: 'center', width: 100,
|
|
render: (text: string, record: RoleRecord) => (
|
|
<Permission
|
|
permissions="system:role:edit"
|
|
fallback={
|
|
<Switch
|
|
checked={text === '0'}
|
|
checkedChildren="正常"
|
|
unCheckedChildren="停用"
|
|
disabled
|
|
/>
|
|
}
|
|
>
|
|
<Switch
|
|
checked={text === '0'}
|
|
checkedChildren="正常"
|
|
unCheckedChildren="停用"
|
|
onChange={() => handleStatusChange(record)}
|
|
disabled={record.roleId === 1}
|
|
/>
|
|
</Permission>
|
|
),
|
|
},
|
|
{ title: '创建时间', dataIndex: 'createTime', align: 'center', width: 180, render: (text: string) => parseTime(text) },
|
|
{
|
|
title: '操作', key: 'operation', align: 'center', width: 220,
|
|
render: (_, record) => (
|
|
<Space size="small">
|
|
<Permission permissions="system:role:edit" fallback={<ReadonlyAction icon={<EditOutlined />}>修改</ReadonlyAction>}>
|
|
<Button type="link" icon={<EditOutlined />} onClick={() => handleUpdate(record)} disabled={record.roleId === 1}>修改</Button>
|
|
</Permission>
|
|
<Permission permissions="system:role:remove" fallback={<ReadonlyAction icon={<DeleteOutlined />} danger>删除</ReadonlyAction>}>
|
|
<Popconfirm
|
|
title="是否确认删除该角色?"
|
|
onConfirm={() => handleDelete(record)}
|
|
okText="是"
|
|
cancelText="否"
|
|
disabled={record.roleId === 1}
|
|
>
|
|
<Button type="link" danger icon={<DeleteOutlined />} disabled={record.roleId === 1}>删除</Button>
|
|
</Popconfirm>
|
|
</Permission>
|
|
<Permission
|
|
permissions="system:role:edit"
|
|
mode="or"
|
|
fallback={<ReadonlyAction icon={<DownOutlined />}>更多</ReadonlyAction>}
|
|
>
|
|
<Dropdown
|
|
menu={{
|
|
items: [
|
|
{ key: 'handleDataScope', icon: <CheckCircleOutlined />, label: '数据权限' },
|
|
{ key: 'handleAuthUser', icon: <UserOutlined />, label: '分配用户' },
|
|
],
|
|
onClick: ({ key }) => handleCommand(String(key), record),
|
|
}}
|
|
trigger={['click']}
|
|
disabled={record.roleId === 1}
|
|
>
|
|
<Button type="link" icon={<DownOutlined />} disabled={record.roleId === 1}>更多</Button>
|
|
</Dropdown>
|
|
</Permission>
|
|
</Space>
|
|
),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="app-container role-page-container">
|
|
<Form form={queryForm} layout="inline" className="search-form" onFinish={handleQuery} initialValues={queryParams}>
|
|
<Form.Item label="角色名称" name="roleName">
|
|
<Input placeholder="请输入角色名称" allowClear onPressEnter={() => queryForm.submit()} style={{ width: 240 }} />
|
|
</Form.Item>
|
|
<Form.Item label="权限字符" name="roleKey">
|
|
<Input placeholder="请输入权限字符" allowClear onPressEnter={() => queryForm.submit()} style={{ width: 240 }} />
|
|
</Form.Item>
|
|
<Form.Item label="状态" name="status">
|
|
<Select placeholder="角色状态" allowClear style={{ width: 240 }}>
|
|
{sysNormalDisableDict.map(dict => (
|
|
<Select.Option key={dict.value} value={dict.value}>{dict.label}</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
<Form.Item label="创建时间">
|
|
<RangePicker
|
|
value={dateRange}
|
|
onChange={(dates) => setDateRange(dates)}
|
|
format="YYYY-MM-DD"
|
|
style={{ width: 240 }}
|
|
/>
|
|
</Form.Item>
|
|
<Form.Item>
|
|
<Button type="primary" icon={<SearchOutlined />} htmlType="submit">搜索</Button>
|
|
<Button icon={<ReloadOutlined />} onClick={resetQuery} style={{ marginLeft: 8 }}>重置</Button>
|
|
</Form.Item>
|
|
</Form>
|
|
|
|
<Space className="mb8">
|
|
<Permission permissions="system:role:add">
|
|
<Button type="primary" ghost icon={<PlusOutlined />} onClick={handleAdd}>新增</Button>
|
|
</Permission>
|
|
<Permission permissions="system:role:edit">
|
|
<Button type="primary" ghost icon={<EditOutlined />} disabled={selectedRowKeys.length !== 1} onClick={() => handleUpdate()}>修改</Button>
|
|
</Permission>
|
|
<Permission permissions="system:role:remove">
|
|
<Button danger ghost icon={<DeleteOutlined />} disabled={selectedRowKeys.length === 0} onClick={() => handleDelete()}>删除</Button>
|
|
</Permission>
|
|
<Permission permissions="system:role:export">
|
|
<Button ghost icon={<DownloadOutlined />} onClick={handleExport}>导出</Button>
|
|
</Permission>
|
|
</Space>
|
|
|
|
<Table
|
|
columns={columns}
|
|
dataSource={roleList}
|
|
rowKey="roleId"
|
|
loading={loading}
|
|
rowSelection={{
|
|
selectedRowKeys,
|
|
onChange: handleSelectionChange,
|
|
}}
|
|
pagination={{
|
|
current: queryParams.pageNum,
|
|
pageSize: queryParams.pageSize,
|
|
total: total,
|
|
showSizeChanger: true,
|
|
showQuickJumper: true,
|
|
showTotal: (total) => `共 ${total} 条`,
|
|
onChange: (page, pageSize) => {
|
|
setQueryParams(prev => ({ ...prev, pageNum: page, pageSize: pageSize }));
|
|
},
|
|
}}
|
|
/>
|
|
|
|
{/* Add/Edit Role Modal */}
|
|
<Modal
|
|
className="system-admin-modal"
|
|
title={addEditModalTitle}
|
|
open={addEditModalVisible}
|
|
onOk={roleForm.submit}
|
|
onCancel={() => setAddEditModalVisible(false)}
|
|
width={500}
|
|
forceRender
|
|
>
|
|
<Form form={roleForm} labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} onFinish={submitRoleForm} initialValues={currentRole}>
|
|
<Form.Item label="角色名称" name="roleName" rules={[{ required: true, message: '请输入角色名称' }]}>
|
|
<Input placeholder="请输入角色名称" />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={
|
|
<Space>
|
|
<Tooltip title="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasRole('admin')`)">
|
|
<QuestionCircleOutlined />
|
|
</Tooltip>
|
|
权限字符
|
|
</Space>
|
|
}
|
|
name="roleKey"
|
|
rules={[{ required: true, message: '请输入权限字符' }]} // Corrected escaping for "
|
|
>
|
|
<Input placeholder="请输入权限字符" />
|
|
</Form.Item>
|
|
<Form.Item label="角色顺序" name="roleSort" rules={[{ required: true, message: '请输入角色顺序' }]}>
|
|
<InputNumber min={0} controls={false} style={{ width: '100%' }} />
|
|
</Form.Item>
|
|
<Form.Item label="状态" name="status">
|
|
<Select>
|
|
{sysNormalDisableDict.map(dict => (
|
|
<Select.Option key={dict.value} value={dict.value}>{dict.label}</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
<Form.Item label="菜单权限">
|
|
<Row gutter={[8, 8]}>
|
|
<Col>
|
|
<Checkbox checked={menuExpand} onChange={(e) => handleCheckedTreeExpand(e.target.checked, 'menu')}>展开/折叠</Checkbox>
|
|
</Col>
|
|
<Col>
|
|
<Checkbox checked={menuNodeAll} onChange={(e) => handleCheckedTreeNodeAll(e.target.checked, 'menu')}>全选/全不选</Checkbox>
|
|
</Col>
|
|
<Col>
|
|
<Checkbox checked={menuCheckStrictly} onChange={(e) => handleCheckedTreeConnect(e.target.checked, 'menu')}>父子联动</Checkbox>
|
|
</Col>
|
|
</Row>
|
|
<Tree
|
|
className="system-tree-panel"
|
|
checkable
|
|
treeData={menuOptions}
|
|
fieldNames={{ title: 'label', key: 'id' }}
|
|
checkStrictly={!menuCheckStrictly}
|
|
checkedKeys={
|
|
!menuCheckStrictly
|
|
? { checked: menuCheckedKeys, halfChecked: menuHalfCheckedKeys }
|
|
: menuCheckedKeys
|
|
}
|
|
onCheck={handleMenuCheck}
|
|
expandedKeys={menuExpandedKeys}
|
|
onExpand={(expandedKeys) => setMenuExpandedKeys(expandedKeys)}
|
|
/>
|
|
</Form.Item>
|
|
<Form.Item label="备注" name="remark">
|
|
<Input.TextArea placeholder="请输入内容" />
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
|
|
{/* Assign Data Scope Modal */}
|
|
<Modal
|
|
className="system-admin-modal"
|
|
title={dataScopeModalTitle}
|
|
open={dataScopeModalVisible}
|
|
onOk={dataScopeForm.submit}
|
|
onCancel={() => setDataScopeModalVisible(false)}
|
|
width={500}
|
|
forceRender
|
|
>
|
|
<Form form={dataScopeForm} labelCol={{ span: 5 }} wrapperCol={{ span: 16 }} onFinish={submitDataScopeForm} initialValues={currentRole}>
|
|
<Form.Item label="角色名称">
|
|
<Input value={currentRole?.roleName ?? ''} disabled />
|
|
</Form.Item>
|
|
<Form.Item label="权限字符">
|
|
<Input value={currentRole?.roleKey ?? ''} disabled />
|
|
</Form.Item>
|
|
<Form.Item label="权限范围" name="dataScope">
|
|
<Select onChange={dataScopeSelectChange}>
|
|
{dataScopeOptions.map(option => (
|
|
<Select.Option key={option.value} value={option.value}>{option.label}</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
<Form.Item noStyle shouldUpdate={(prevValues, currentValues) => prevValues.dataScope !== currentValues.dataScope}>
|
|
{({ getFieldValue }) =>
|
|
getFieldValue('dataScope') === '2' ? (
|
|
<Form.Item label="数据权限">
|
|
<Row gutter={[8, 8]}>
|
|
<Col>
|
|
<Checkbox checked={deptExpand} onChange={(e) => handleCheckedTreeExpand(e.target.checked, 'dept')}>展开/折叠</Checkbox>
|
|
</Col>
|
|
<Col>
|
|
<Checkbox checked={deptNodeAll} onChange={(e) => handleCheckedTreeNodeAll(e.target.checked, 'dept')}>全选/全不选</Checkbox>
|
|
</Col>
|
|
<Col>
|
|
<Checkbox checked={deptCheckStrictly} onChange={(e) => handleCheckedTreeConnect(e.target.checked, 'dept')}>父子联动</Checkbox>
|
|
</Col>
|
|
</Row>
|
|
<Tree
|
|
className="system-tree-panel"
|
|
checkable
|
|
treeData={deptOptions}
|
|
fieldNames={{ title: 'label', key: 'id' }}
|
|
checkStrictly={!deptCheckStrictly}
|
|
checkedKeys={
|
|
!deptCheckStrictly
|
|
? { checked: deptCheckedKeys, halfChecked: deptHalfCheckedKeys }
|
|
: deptCheckedKeys
|
|
}
|
|
onCheck={handleDeptCheck}
|
|
expandedKeys={deptExpandedKeys}
|
|
onExpand={(expandedKeys) => setDeptExpandedKeys(expandedKeys)}
|
|
/>
|
|
</Form.Item>
|
|
) : null
|
|
}
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default RolePage;
|