682 lines
19 KiB
JavaScript
682 lines
19 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import { Card, App, Table, Button, Modal, Form, Select, message, Input, Divider, Popconfirm } from 'antd';
|
|
import { PlusOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
|
import { supabase } from '@/config/supabase';
|
|
|
|
const RoleHeader = ({ onAdd, onSearch }) => (
|
|
<div className="flex justify-end mb-4">
|
|
|
|
<Button
|
|
type="primary"
|
|
icon={<PlusOutlined />}
|
|
onClick={onAdd}
|
|
>
|
|
添加权限
|
|
</Button>
|
|
</div>
|
|
);
|
|
|
|
export default function PermissionManagement() {
|
|
const [loading, setLoading] = useState(false);
|
|
const [permissions, setPermissions] = useState([]);
|
|
const [roles, setRoles] = useState([]);
|
|
const [resources, setResources] = useState([]);
|
|
const [modalVisible, setModalVisible] = useState(false);
|
|
const [modalType, setModalType] = useState('add');
|
|
const [form] = Form.useForm();
|
|
const [pagination, setPagination] = useState({
|
|
current: 1,
|
|
pageSize: 10,
|
|
total: 0,
|
|
});
|
|
|
|
const [sorter, setSorter] = useState({
|
|
field: 'created_at',
|
|
order: 'descend',
|
|
});
|
|
|
|
const [roleModalVisible, setRoleModalVisible] = useState(false);
|
|
const [resourceModalVisible, setResourceModalVisible] = useState(false);
|
|
const [roleForm] = Form.useForm();
|
|
const [resourceForm] = Form.useForm();
|
|
const [editingResource, setEditingResource] = useState(null);
|
|
|
|
const fetchPermissions = async (params = {}) => {
|
|
try {
|
|
setLoading(true);
|
|
let query = supabase
|
|
.from('permissions')
|
|
.select(`
|
|
*,
|
|
roles:role_id(*),
|
|
resources:resource_id(*)
|
|
`, { count: 'exact' });
|
|
|
|
if (params.field && params.order) {
|
|
const ascending = params.order === 'ascend';
|
|
query = query.order(params.field, { ascending });
|
|
} else {
|
|
// 默认排序
|
|
query = query.order('created_at', { ascending: false });
|
|
}
|
|
|
|
// 添加分页
|
|
const from = (params.current - 1) * params.pageSize || 0;
|
|
const to = from + (params.pageSize || 10) - 1;
|
|
query = query.range(from, to);
|
|
|
|
const { data, count, error } = await query;
|
|
|
|
if (error) throw error;
|
|
|
|
setPermissions(data || []);
|
|
setPagination(prev => ({
|
|
...prev,
|
|
total: count || 0,
|
|
current: params.current || prev.current,
|
|
pageSize: params.pageSize || prev.pageSize,
|
|
}));
|
|
} catch (error) {
|
|
message.error('获取权限数据失败');
|
|
console.error(error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// 获取所有角色
|
|
const fetchRoles = async () => {
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from('roles')
|
|
.select('*')
|
|
.order('name');
|
|
|
|
if (error) throw error;
|
|
setRoles(data || []);
|
|
} catch (error) {
|
|
message.error('获取角色数据失败');
|
|
console.error(error);
|
|
}
|
|
};
|
|
|
|
// 获取所有资源
|
|
const fetchResources = async () => {
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from('permission_resources')
|
|
.select('*')
|
|
.order('resource_name');
|
|
|
|
if (error) throw error;
|
|
setResources(data || []);
|
|
} catch (error) {
|
|
message.error('获取资源数据失败');
|
|
console.error(error);
|
|
}
|
|
};
|
|
|
|
// 添加权限
|
|
const handleAdd = async (values) => {
|
|
try {
|
|
const { error } = await supabase
|
|
.from('permissions')
|
|
.insert([{
|
|
role_id: values.role_id,
|
|
resource_id: values.resource_id,
|
|
action: values.action
|
|
}]);
|
|
|
|
if (error) throw error;
|
|
message.success('添加成功');
|
|
fetchPermissions(pagination);
|
|
} catch (error) {
|
|
message.error('添加失败');
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// 更新权限
|
|
const handleUpdate = async (values) => {
|
|
try {
|
|
const { error } = await supabase
|
|
.from('permissions')
|
|
.update({
|
|
role_id: values.role_id,
|
|
resource_id: values.resource_id,
|
|
action: values.action
|
|
})
|
|
.eq('id', values.id);
|
|
|
|
if (error) throw error;
|
|
message.success('更新成功');
|
|
fetchPermissions(pagination);
|
|
} catch (error) {
|
|
message.error('更新失败');
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// 删除权限
|
|
const handleDelete = async (id) => {
|
|
try {
|
|
const { error } = await supabase
|
|
.from('permissions')
|
|
.delete()
|
|
.eq('id', id);
|
|
|
|
if (error) throw error;
|
|
message.success('删除成功');
|
|
fetchPermissions(pagination);
|
|
} catch (error) {
|
|
message.error('删除失败');
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// 添加新角色
|
|
const handleAddRole = async (values) => {
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from('roles')
|
|
.insert([{
|
|
name: values.name,
|
|
description: values.description
|
|
}])
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
|
|
message.success('添加角色成功');
|
|
setRoles([...roles, data]);
|
|
setRoleModalVisible(false);
|
|
roleForm.resetFields();
|
|
return data;
|
|
} catch (error) {
|
|
message.error('添加角色失败');
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// 添加新资源
|
|
const handleAddResource = async (values) => {
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from('permission_resources')
|
|
.insert([{
|
|
resource_name: values.resource_name,
|
|
resource_type: values.resource_type,
|
|
attributes_type: values.attributes_type,
|
|
description: values.description
|
|
}])
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
|
|
message.success('添加资源成功');
|
|
setResources([...resources, data]);
|
|
setResourceModalVisible(false);
|
|
resourceForm.resetFields();
|
|
return data;
|
|
} catch (error) {
|
|
message.error('添加资源失败');
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// 添加删除资源的函数
|
|
const handleDeleteResource = async (resourceId) => {
|
|
try {
|
|
const { error } = await supabase
|
|
.from('permission_resources')
|
|
.delete()
|
|
.eq('id', resourceId);
|
|
|
|
if (error) throw error;
|
|
message.success('删除资源成功');
|
|
fetchResources();
|
|
} catch (error) {
|
|
message.error('删除资源失败');
|
|
console.error(error);
|
|
}
|
|
};
|
|
|
|
// 添加更新资源的函数
|
|
const handleUpdateResource = async (values) => {
|
|
try {
|
|
const { error } = await supabase
|
|
.from('permission_resources')
|
|
.update({
|
|
resource_name: values.resource_name,
|
|
resource_type: values.resource_type,
|
|
attributes_type: values.attributes_type,
|
|
description: values.description
|
|
})
|
|
.eq('id', editingResource.id);
|
|
|
|
if (error) throw error;
|
|
message.success('更新资源成功');
|
|
setResourceModalVisible(false);
|
|
resourceForm.resetFields();
|
|
fetchResources();
|
|
} catch (error) {
|
|
message.error('更新资源失败');
|
|
console.error(error);
|
|
}
|
|
};
|
|
|
|
// 初始化加载
|
|
useEffect(() => {
|
|
fetchPermissions();
|
|
fetchRoles();
|
|
fetchResources();
|
|
}, []);
|
|
|
|
const handleTableChange = (newPagination, filters, newSorter) => {
|
|
const params = {
|
|
current: newPagination?.current,
|
|
pageSize: newPagination?.pageSize,
|
|
field: newSorter?.field,
|
|
order: newSorter?.order,
|
|
};
|
|
|
|
setPagination(prev => ({
|
|
...prev,
|
|
current: params.current,
|
|
pageSize: params.pageSize,
|
|
}));
|
|
|
|
setSorter({
|
|
field: params.field || sorter.field,
|
|
order: params.order || sorter.order,
|
|
});
|
|
|
|
fetchPermissions(params);
|
|
};
|
|
|
|
const columns = [
|
|
{
|
|
title: '角色',
|
|
dataIndex: ['roles', 'name'],
|
|
key: 'role_name',
|
|
render: (name) => (
|
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
|
|
{name}
|
|
</span>
|
|
)
|
|
},
|
|
{
|
|
title: '资源',
|
|
dataIndex: ['resources', 'resource_name'],
|
|
key: 'resource_name',
|
|
},
|
|
{
|
|
title: '控制颗粒度',
|
|
dataIndex: ['resources', 'resource_type'],
|
|
key: 'resource_type',
|
|
render: (type) => (
|
|
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-sm font-medium
|
|
${type === 'table'
|
|
? 'bg-green-100 text-green-800'
|
|
: 'bg-purple-100 text-purple-800'}`}>
|
|
{type === 'table' ? '数据表' : '字段值'}
|
|
</span>
|
|
)
|
|
},
|
|
{
|
|
title: '属性类型',
|
|
dataIndex: ['resources', 'attributes_type'],
|
|
key: 'attributes_type',
|
|
render: (attributes_type) => (
|
|
<span>
|
|
{!attributes_type ? '-' : attributes_type}
|
|
</span>
|
|
)
|
|
},
|
|
{
|
|
title: '操作类型',
|
|
dataIndex: 'action',
|
|
key: 'action',
|
|
render: (action) => {
|
|
const actionMap = {
|
|
create: { text: '创建', color: 'bg-green-100 text-green-800' },
|
|
read: { text: '读取', color: 'bg-blue-100 text-blue-800' },
|
|
update: { text: '更新', color: 'bg-yellow-100 text-yellow-800' },
|
|
delete: { text: '删除', color: 'bg-red-100 text-red-800' }
|
|
};
|
|
|
|
const { text, color } = actionMap[action] || { text: action, color: 'bg-gray-100 text-gray-800' };
|
|
|
|
return (
|
|
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-sm font-medium ${color}`}>
|
|
{text}
|
|
</span>
|
|
);
|
|
}
|
|
},
|
|
// {
|
|
// title: '描述',
|
|
// dataIndex: ['resources', 'description'],
|
|
// key: 'description',
|
|
// ellipsis: true,
|
|
// },
|
|
{
|
|
title: '操作',
|
|
key: 'operation',
|
|
fixed: 'right',
|
|
render: (_, record) => (
|
|
<div className="space-x-2">
|
|
<Button
|
|
type="link"
|
|
icon={<EditOutlined />}
|
|
onClick={() => {
|
|
setModalType('edit');
|
|
form.setFieldsValue(record);
|
|
setModalVisible(true);
|
|
}}
|
|
>编辑</Button>
|
|
<Popconfirm
|
|
title="确认删除"
|
|
description="确定要删除这条权限记录吗?"
|
|
okText="确认"
|
|
cancelText="取消"
|
|
onConfirm={() => handleDelete(record.id)}
|
|
>
|
|
<Button
|
|
type="link"
|
|
danger
|
|
icon={<DeleteOutlined />}
|
|
>删除</Button>
|
|
</Popconfirm>
|
|
</div>
|
|
),
|
|
},
|
|
];
|
|
|
|
// Modal 表单内容
|
|
const renderFormItems = () => {
|
|
const [roleSelectOpen, setRoleSelectOpen] = useState(false);
|
|
const [resourceSelectOpen, setResourceSelectOpen] = useState(false);
|
|
|
|
return (
|
|
<>
|
|
<Form.Item
|
|
name="role_id"
|
|
label="角色"
|
|
rules={[{ required: true, message: '请选择角色' }]}
|
|
>
|
|
<Select
|
|
allowClear
|
|
placeholder="请选择角色"
|
|
open={roleSelectOpen}
|
|
onDropdownVisibleChange={setRoleSelectOpen}
|
|
dropdownRender={(menu) => (
|
|
<>
|
|
{menu}
|
|
<Divider style={{ margin: '8px 0' }} />
|
|
<Button
|
|
type="text"
|
|
icon={<PlusOutlined />}
|
|
onClick={() => {
|
|
setRoleSelectOpen(false); // 关闭下拉框
|
|
setRoleModalVisible(true);
|
|
}}
|
|
style={{ paddingLeft: 8 }}
|
|
>
|
|
添加角色
|
|
</Button>
|
|
</>
|
|
)}
|
|
>
|
|
{roles.map(role => (
|
|
<Select.Option key={role.id} value={role.id}>
|
|
{role.name}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="resource_id"
|
|
label="资源"
|
|
rules={[{ required: true, message: '请选择资源' }]}
|
|
>
|
|
<Select
|
|
placeholder="请选择资源"
|
|
open={resourceSelectOpen}
|
|
onDropdownVisibleChange={setResourceSelectOpen}
|
|
dropdownRender={(menu) => (
|
|
<>
|
|
{menu}
|
|
<Divider style={{ margin: '8px 0' }} />
|
|
<div style={{ padding: '8px', display: 'flex', justifyContent: 'space-between' }}>
|
|
<Button
|
|
type="text"
|
|
icon={<PlusOutlined />}
|
|
onClick={() => {
|
|
setResourceSelectOpen(false);
|
|
setEditingResource(null); // 清空编辑状态
|
|
resourceForm.resetFields(); // 清空表单
|
|
setResourceModalVisible(true);
|
|
}}
|
|
>
|
|
添加资源
|
|
</Button>
|
|
</div>
|
|
</>
|
|
)}
|
|
>
|
|
{resources.map(resource => (
|
|
<Select.Option key={resource.id} value={resource.id}>
|
|
<div className="flex justify-between items-center">
|
|
<span>{`${resource.resource_name} ${resource.attributes_type?`(${resource.attributes_type})`:''}`}</span>
|
|
<div className="flex space-x-2" onClick={e => e.stopPropagation()}>
|
|
<Button
|
|
type="text"
|
|
size="small"
|
|
icon={<EditOutlined />}
|
|
onClick={() => {
|
|
setResourceSelectOpen(false);
|
|
setEditingResource(resource);
|
|
resourceForm.setFieldsValue(resource);
|
|
setResourceModalVisible(true);
|
|
}}
|
|
/>
|
|
<Button
|
|
type="text"
|
|
size="small"
|
|
danger
|
|
icon={<DeleteOutlined />}
|
|
onClick={() => {
|
|
Modal.confirm({
|
|
title: '确认删除',
|
|
content: '确定要删除这个资源吗?',
|
|
onOk: () => handleDeleteResource(resource.id),
|
|
});
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="action"
|
|
label="操作类型"
|
|
rules={[{ required: true, message: '请选择操作类型' }]}
|
|
>
|
|
<Select placeholder="请选择操作类型" allowClear>
|
|
<Select.Option value="create">创建</Select.Option>
|
|
<Select.Option value="read">读取</Select.Option>
|
|
<Select.Option value="update">更新</Select.Option>
|
|
<Select.Option value="delete">删除</Select.Option>
|
|
</Select>
|
|
</Form.Item>
|
|
</>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<App>
|
|
<Card title="权限管理" >
|
|
<RoleHeader
|
|
onAdd={() => {
|
|
setModalType('add');
|
|
setModalVisible(true);
|
|
}}
|
|
onSearch={(value) => {
|
|
fetchPermissions({
|
|
...pagination,
|
|
current: 1,
|
|
search: value
|
|
});
|
|
}}
|
|
/>
|
|
|
|
<Table
|
|
scroll={{ x: true}}
|
|
columns={columns}
|
|
dataSource={permissions}
|
|
rowKey="id"
|
|
loading={loading}
|
|
pagination={{
|
|
...pagination,
|
|
showSizeChanger: true,
|
|
showQuickJumper: true,
|
|
showTotal: (total) => `共 ${total} 条记录`,
|
|
pageSizeOptions: ['10', '20', '50', '100'],
|
|
onChange: (page, pageSize) => {
|
|
setPagination(prev => ({ ...prev,page, pageSize }));
|
|
}
|
|
}}
|
|
onChange={handleTableChange}
|
|
className='w-full'
|
|
/>
|
|
|
|
<Modal
|
|
title={`${modalType === 'add' ? '添加' : '编辑'}权限`}
|
|
open={modalVisible}
|
|
onCancel={() => {
|
|
setModalVisible(false);
|
|
form.resetFields();
|
|
}}
|
|
onOk={() => form.submit()}
|
|
confirmLoading={loading}
|
|
>
|
|
<Form
|
|
form={form}
|
|
layout="vertical"
|
|
onFinish={async (values) => {
|
|
try {
|
|
setLoading(true);
|
|
if (modalType === 'add') {
|
|
await handleAdd(values);
|
|
} else {
|
|
await handleUpdate(values);
|
|
}
|
|
setModalVisible(false);
|
|
form.resetFields();
|
|
} catch (error) {
|
|
console.error(error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}}
|
|
>
|
|
{renderFormItems()}
|
|
</Form>
|
|
</Modal>
|
|
|
|
{/* 添加角色 Modal */}
|
|
<Modal
|
|
title="添加角色"
|
|
open={roleModalVisible}
|
|
onCancel={() => {
|
|
setRoleModalVisible(false);
|
|
roleForm.resetFields();
|
|
}}
|
|
onOk={() => roleForm.submit()}
|
|
>
|
|
<Form
|
|
form={roleForm}
|
|
layout="vertical"
|
|
onFinish={handleAddRole}
|
|
>
|
|
<Form.Item
|
|
name="name"
|
|
label="角色名称"
|
|
rules={[{ required: true, message: '请输入角色名称' }]}
|
|
>
|
|
<Input placeholder="请输入角色名称" />
|
|
</Form.Item>
|
|
<Form.Item
|
|
name="description"
|
|
label="角色描述"
|
|
>
|
|
<Input.TextArea placeholder="请输入角色描述" />
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
|
|
{/* 添加资源 Modal */}
|
|
<Modal
|
|
title={editingResource ? "编辑资源" : "添加资源"}
|
|
open={resourceModalVisible}
|
|
onCancel={() => {
|
|
setResourceModalVisible(false);
|
|
setEditingResource(null);
|
|
resourceForm.resetFields();
|
|
}}
|
|
onOk={() => resourceForm.submit()}
|
|
zIndex={1100}
|
|
>
|
|
<Form
|
|
form={resourceForm}
|
|
layout="vertical"
|
|
onFinish={(values) => {
|
|
if (editingResource) {
|
|
handleUpdateResource(values);
|
|
} else {
|
|
handleAddResource(values);
|
|
}
|
|
}}
|
|
>
|
|
<Form.Item
|
|
name="resource_name"
|
|
label="资源名称"
|
|
rules={[{ required: true, message: '请输入资源名称' }]}
|
|
>
|
|
<Input placeholder="请输入资源名称" />
|
|
</Form.Item>
|
|
<Form.Item
|
|
name="resource_type"
|
|
label="资源类型"
|
|
rules={[{ required: true, message: '请选择资源类型' }]}
|
|
>
|
|
<Select placeholder="请选择资源类型">
|
|
<Select.Option value="table">数据表</Select.Option>
|
|
<Select.Option value="field_value">字段值</Select.Option>
|
|
</Select>
|
|
</Form.Item>
|
|
<Form.Item
|
|
name="attributes_type"
|
|
label="属性类型"
|
|
>
|
|
<Input placeholder="请输入属性类型" />
|
|
</Form.Item>
|
|
<Form.Item
|
|
name="description"
|
|
label="资源描述"
|
|
>
|
|
<Input.TextArea placeholder="请输入资源描述" />
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
</Card>
|
|
</App>
|
|
);
|
|
} |