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', resource: 'roles',
action: 'list', action: 'list',
params: { params: {
pagination: false,
filter: { filter: {
'name.$ne': 'root', 'name.$ne': 'root',
}, },

View File

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