fix: close permission review gaps

fix: harden permission-gated UI — minimum-org endpoint, modal/Popconfirm fail-closed on revocation
This commit is contained in:
2026-07-23 12:23:33 +08:00
parent c98d37307e
commit a7a7af1667
17 changed files with 391 additions and 227 deletions

View File

@@ -85,12 +85,15 @@ function parseRoomNumber(input: string) {
}
const RoomsPage: React.FC = () => {
const { hasPermission } = usePermission();
const canEditRooms = hasPermission('room:edit');
const { hasPermission, permissionsReady } = usePermission();
const canEditRooms = permissionsReady && hasPermission('room:edit');
const canCreateRooms = permissionsReady && hasPermission('room:create');
const canDeleteRooms = permissionsReady && hasPermission('room:delete');
const [data, setData] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const [editing, setEditing] = useState<any>(null);
const canSaveRoom = editing ? canEditRooms : canCreateRooms;
const [showArchived, setShowArchived] = useState(false);
const [archivedCount, setArchivedCount] = useState(0);
const [searchText, setSearchText] = useState('');
@@ -114,6 +117,9 @@ const RoomsPage: React.FC = () => {
const [savingLocker, setSavingLocker] = useState(false);
const [batchLoading, setBatchLoading] = useState(false);
// Close modals when the required permission is lost
useEffect(() => { if (!canSaveRoom && modalOpen) { setModalOpen(false); setEditing(null); form.resetFields(); } }, [canSaveRoom, modalOpen, form]);
const handleBatchDelete = async () => {
setBatchLoading(true);
try {
@@ -522,16 +528,17 @@ const RoomsPage: React.FC = () => {
return (
<Space>
{r.status === 'archived' ? (
<Popconfirm title="确定恢复此宿舍?" onConfirm={() => handleRestore(r.id)}>
<PermissionButton
permission="room:edit"
size="small"
icon={<UndoOutlined />}
type="link"
>
</PermissionButton>
</Popconfirm>
canEditRooms ? (
<Popconfirm title="确定恢复此宿舍?" onConfirm={() => handleRestore(r.id)}>
<Button
size="small"
icon={<UndoOutlined />}
type="link"
>
</Button>
</Popconfirm>
) : null
) : (
<>
<PermissionButton
@@ -559,15 +566,16 @@ const RoomsPage: React.FC = () => {
>
</PermissionButton>
<Popconfirm title="确定归档?" onConfirm={() => handleArchive(r.id)}>
<PermissionButton
permission="room:delete"
size="small"
icon={<InboxOutlined />}
>
</PermissionButton>
</Popconfirm>
{canDeleteRooms ? (
<Popconfirm title="确定归档?" onConfirm={() => handleArchive(r.id)}>
<Button
size="small"
icon={<InboxOutlined />}
>
</Button>
</Popconfirm>
) : null}
</>
)}
</Space>
@@ -638,23 +646,24 @@ const RoomsPage: React.FC = () => {
</Button>
</Space>
<Space wrap className="responsive-toolbar__group">
<Popconfirm
title={`确定批量归档选中的 ${selectedRowKeys.length} 间宿舍?(有在住人员的会跳过)`}
onConfirm={handleBatchDelete}
okText="归档"
cancelText="取消"
disabled={selectedRowKeys.length === 0}
>
<PermissionButton
permission="room:delete"
danger
icon={<InboxOutlined />}
{canDeleteRooms ? (
<Popconfirm
title={`确定批量归档选中的 ${selectedRowKeys.length} 间宿舍?(有在住人员的会跳过)`}
onConfirm={handleBatchDelete}
okText="归档"
cancelText="取消"
disabled={selectedRowKeys.length === 0}
loading={batchLoading}
>
</PermissionButton>
</Popconfirm>
<Button
danger
icon={<InboxOutlined />}
disabled={selectedRowKeys.length === 0}
loading={batchLoading}
>
</Button>
</Popconfirm>
) : null}
<PermissionButton
permission="room:create"
type="primary"
@@ -732,8 +741,8 @@ const RoomsPage: React.FC = () => {
<Modal
title={editing ? '编辑宿舍' : '添加宿舍'}
open={modalOpen}
onOk={handleSave}
open={modalOpen && canSaveRoom}
onOk={canSaveRoom ? handleSave : undefined}
onCancel={() => {
setModalOpen(false);
setEditing(null);
@@ -994,20 +1003,19 @@ const RoomsPage: React.FC = () => {
>
</PermissionButton>
{r.status !== 'occupied' && (
{r.status !== 'occupied' && canEditRooms && (
<Popconfirm
title="确定归档?"
onConfirm={() => handleDeleteBed(r.id)}
>
<PermissionButton
permission="room:edit"
<Button
size="small"
type="link"
danger
disabled={drawerRoom?.status === 'archived'}
>
</PermissionButton>
</Button>
</Popconfirm>
)}
</Space>
@@ -1147,20 +1155,19 @@ const RoomsPage: React.FC = () => {
>
</PermissionButton>
{r.status !== 'occupied' && (
{r.status !== 'occupied' && canEditRooms && (
<Popconfirm
title="确定归档?"
onConfirm={() => handleDeleteLocker(r.id)}
>
<PermissionButton
permission="room:edit"
<Button
size="small"
type="link"
danger
disabled={drawerRoom?.status === 'archived'}
>
</PermissionButton>
</Button>
</Popconfirm>
)}
</Space>