fix(acl): role menu list only displays one page (#3775)

* fix(acl): role menu list only displays one page

* chore: remove log
This commit is contained in:
YANG QIA 2024-03-21 14:30:49 +08:00 committed by GitHub
parent 9b27fa955a
commit 49d3401379
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 120 additions and 30 deletions

View File

@ -79,7 +79,6 @@ export const RolesManagement: React.FC = () => {
resource: 'roles',
action: 'list',
params: {
pagination: false,
filter: {
'name.$ne': 'root',
},

View File

@ -5,55 +5,98 @@ import {
useAPIClient,
useResourceActionContext,
} from '@nocobase/client';
import { Menu, Empty, Dropdown, App, Tag, Row, Col } from 'antd';
import { Menu, Empty, Dropdown, App, Tag, Row, Col, Spin } from 'antd';
import { TagOutlined, MoreOutlined } from '@ant-design/icons';
import React, { useContext, useEffect } from 'react';
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { useACLTranslation } from './locale';
import { Schema } from '@formily/react';
import { RolesManagerContext } from './RolesManagerProvider';
import { roleEditSchema } from './schemas/roles';
import { useLoadMoreObserver } from './hooks/load-more-observer';
export const RolesMenu: React.FC & {
Item: React.FC<{ item: any; onEdit: () => void }>;
} = () => {
const { t } = useACLTranslation();
const { data } = useResourceActionContext();
const [visible, setVisible] = React.useState(false);
const [record, setRecord] = React.useState(null);
const { data, run, loading } = useResourceActionContext();
const [visible, setVisible] = useState(false);
const [record, setRecord] = useState(null);
const { role, setRole } = useContext(RolesManagerContext);
const items = (data?.data || []).map((item: any) => ({
key: item.name,
label: (
<RolesMenu.Item
item={item}
onEdit={() => {
setVisible(true);
setRecord(item);
}}
/>
),
}));
const [roles, setRoles] = useState([]);
const loadMore = useCallback(() => {
const meta = data?.meta;
if (!meta || meta.page >= meta.totalPage) {
return;
}
run({
page: meta.page + 1,
});
}, [data]);
const { lastItem, setLastItem } = useLoadMoreObserver({ loadMore });
const handleSelect = ({ key }) => {
setRole((data?.data || []).find((item: any) => item.name === key));
setRole(roles.find((item: any) => item.name === key));
};
useEffect(() => {
if (!data?.data.length) {
if (!roles[0]) {
return;
}
setRole(data?.data[0]);
}, [data, setRole]);
setRole(roles[0]);
}, [roles, setRole]);
useEffect(() => {
if (!data?.data?.length) {
return;
}
const ref = React.createRef<any>();
setLastItem(ref);
setRoles((prev) => prev.concat(data.data));
}, [data, setLastItem]);
const items = useMemo(
() =>
roles.map((item: any, index: number) => ({
key: item.name,
label:
index === roles.length - 1 ? (
<div ref={lastItem}>
<RolesMenu.Item
item={item}
onEdit={() => {
setVisible(true);
setRecord(item);
}}
/>
</div>
) : (
<RolesMenu.Item
item={item}
onEdit={() => {
setVisible(true);
setRecord(item);
}}
/>
),
})),
[roles, lastItem],
);
return (
<>
{items.length ? (
<Menu
style={{ border: 'none', maxHeight: '65vh', overflowY: 'auto' }}
items={items}
selectedKeys={[role?.name]}
onSelect={handleSelect}
/>
{roles.length ? (
<>
<Menu
style={{ border: 'none', maxHeight: '65vh', overflowY: 'auto' }}
items={items}
selectedKeys={[role?.name]}
onSelect={handleSelect}
/>
{loading && <Spin />}
</>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
@ -92,12 +135,15 @@ RolesMenu.Item = function DepartmentTreeItem({ item, onEdit }) {
deleteDepartment();
}
};
const title = Schema.compile(item.title, { t });
return (
<Row>
<Col flex={3} style={{ display: 'inline-flex', alignItems: 'center' }}>
<span style={{ whiteSpace: 'nowrap', width: '120px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
<TagOutlined />
<span style={{ marginLeft: '10px' }}>{Schema.compile(item.title, { t })}</span>
<span style={{ marginLeft: '10px' }} title={title}>
{title}
</span>
</span>
</Col>
<Col>

View File

@ -0,0 +1,45 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
export const useLoadMoreObserver = ({
loadMore,
}: {
loadMore: () => void; // callback
}) => {
const [lastItem, setLastItem] = useState<React.RefObject<any>>(null);
const observer = useRef<IntersectionObserver | null>(null);
const observeExposure = useCallback(
(lastItem: React.RefObject<any>) => {
if (!lastItem) {
return;
}
observer.current && observer.current.disconnect();
observer.current = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.target !== lastItem.current) {
return;
}
if (entry.isIntersecting) {
observer.current && observer.current.unobserve(lastItem.current);
loadMore();
}
});
});
lastItem.current && observer.current && observer.current.observe(lastItem.current);
},
[loadMore],
);
useEffect(() => {
observeExposure(lastItem);
return () => {
observer.current && observer.current.disconnect();
};
}, [lastItem, observeExposure]);
return {
lastItem,
setLastItem,
};
};