mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-02 03:02:19 +08:00
fix: resolve tab switching issue (#5081)
* refactor: convert parameters to destructured object * fix: resolve tab switching issue in multi-app pages * fix: fix ineffective tab switching within nested popups
This commit is contained in:
parent
e61e6d0842
commit
70d96c3e33
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
export * from './useApp';
|
||||
export * from './useAppSpin';
|
||||
export * from './usePlugin';
|
||||
export * from './useRouter';
|
||||
export * from './useAppSpin';
|
||||
export * from './useRouterBasename';
|
||||
|
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { useHref } from 'react-router-dom';
|
||||
|
||||
/**
|
||||
* see: https://stackoverflow.com/questions/50449423/accessing-basename-of-browserouter
|
||||
* @returns {string} basename
|
||||
*/
|
||||
export const useRouterBasename = () => {
|
||||
const basenameOfCurrentRouter = useHref('/');
|
||||
return basenameOfCurrentRouter;
|
||||
};
|
@ -20,7 +20,7 @@ import omit from 'lodash/omit';
|
||||
import qs from 'qs';
|
||||
import { ChangeEvent, useCallback, useContext, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NavigateFunction, useHref } from 'react-router-dom';
|
||||
import { NavigateFunction } from 'react-router-dom';
|
||||
import { useReactToPrint } from 'react-to-print';
|
||||
import {
|
||||
AssociationFilter,
|
||||
@ -28,6 +28,7 @@ import {
|
||||
useCollectionRecord,
|
||||
useDataSourceHeaders,
|
||||
useFormActiveFields,
|
||||
useRouterBasename,
|
||||
useTableBlockContext,
|
||||
} from '../..';
|
||||
import { useAPIClient, useRequest } from '../../api-client';
|
||||
@ -1594,9 +1595,7 @@ export function useLinkActionProps(componentProps?: any) {
|
||||
const searchParams = componentPropsValue?.['params'] || [];
|
||||
const openInNewWindow = fieldSchema?.['x-component-props']?.['openInNewWindow'];
|
||||
const { parseURLAndParams } = useParseURLAndParams();
|
||||
|
||||
// see: https://stackoverflow.com/questions/50449423/accessing-basename-of-browserouter
|
||||
const basenameOfCurrentRouter = useHref('/');
|
||||
const basenameOfCurrentRouter = useRouterBasename();
|
||||
|
||||
return {
|
||||
type: 'default',
|
||||
|
@ -23,6 +23,7 @@ import { useStyles as useAClStyles } from '../../../acl/style';
|
||||
import { useRequest } from '../../../api-client';
|
||||
import { useNavigateNoUpdate } from '../../../application/CustomRouterContextProvider';
|
||||
import { useAppSpin } from '../../../application/hooks/useAppSpin';
|
||||
import { useRouterBasename } from '../../../application/hooks/useRouterBasename';
|
||||
import { useDocumentTitle } from '../../../document-title';
|
||||
import { useGlobalTheme } from '../../../global-theme';
|
||||
import { Icon } from '../../../icon';
|
||||
@ -47,6 +48,7 @@ export const Page = (props) => {
|
||||
const { theme } = useGlobalTheme();
|
||||
const { getAriaLabel } = useGetAriaLabelOfSchemaInitializer();
|
||||
const { tabUid, name: pageUid } = useParams();
|
||||
const basenameOfCurrentRouter = useRouterBasename();
|
||||
|
||||
// react18 tab 动画会卡顿,所以第一个 tab 时,动画禁用,后面的 tab 才启用
|
||||
const [hasMounted, setHasMounted] = useState(false);
|
||||
@ -112,7 +114,7 @@ export const Page = (props) => {
|
||||
}}
|
||||
onChange={(activeKey) => {
|
||||
setLoading(true);
|
||||
navigateToTab(activeKey, navigate);
|
||||
navigateToTab({ activeKey, navigate, basename: basenameOfCurrentRouter });
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 50);
|
||||
@ -320,11 +322,28 @@ const PageContent = memo(
|
||||
);
|
||||
PageContent.displayName = 'PageContent';
|
||||
|
||||
export function navigateToTab(activeKey: string, navigate: NavigateFunction, pathname = window.location.pathname) {
|
||||
export function navigateToTab({
|
||||
activeKey,
|
||||
navigate,
|
||||
basename,
|
||||
pathname = window.location.pathname,
|
||||
}: {
|
||||
activeKey: string;
|
||||
navigate: NavigateFunction;
|
||||
/** the router basename */
|
||||
basename: string;
|
||||
pathname?: string;
|
||||
}) {
|
||||
pathname = pathname.replace(basename, '');
|
||||
|
||||
if (pathname.endsWith('/')) {
|
||||
pathname = pathname.slice(0, -1);
|
||||
}
|
||||
|
||||
if (!pathname.startsWith('/')) {
|
||||
pathname = `/${pathname}`;
|
||||
}
|
||||
|
||||
if (isTabPage(pathname)) {
|
||||
navigate(`${pathname.replace(/\/tabs\/[^/]+$/, `/tabs/${activeKey}`)}`, { replace: true });
|
||||
} else {
|
||||
|
@ -84,7 +84,8 @@ const PopupParamsProvider: FC<Omit<PopupProps, 'hidden'>> = (props) => {
|
||||
return <PopupParamsProviderContext.Provider value={value}>{props.children}</PopupParamsProviderContext.Provider>;
|
||||
};
|
||||
|
||||
const PopupTabsPropsProvider: FC<{ params: PopupParams }> = ({ children, params }) => {
|
||||
const PopupTabsPropsProvider: FC = ({ children }) => {
|
||||
const { params } = useCurrentPopupContext();
|
||||
const { changeTab } = usePagePopup();
|
||||
const onChange = useCallback(
|
||||
(key: string) => {
|
||||
@ -99,7 +100,7 @@ const PopupTabsPropsProvider: FC<{ params: PopupParams }> = ({ children, params
|
||||
}
|
||||
|
||||
return (
|
||||
<TabsContextProvider activeKey={params.tab} onChange={onChange}>
|
||||
<TabsContextProvider activeKey={params?.tab} onChange={onChange}>
|
||||
{children}
|
||||
</TabsContextProvider>
|
||||
);
|
||||
@ -166,7 +167,7 @@ const PagePopupsItemProvider: FC<{
|
||||
>
|
||||
{/* Pass the service of the block where the button is located down, to refresh the block's data when the popup is closed */}
|
||||
<BlockRequestContext.Provider value={storedContext.service}>
|
||||
<PopupTabsPropsProvider params={params}>
|
||||
<PopupTabsPropsProvider>
|
||||
<div style={{ display: 'none' }}>{children}</div>
|
||||
</PopupTabsPropsProvider>
|
||||
</BlockRequestContext.Provider>
|
||||
|
@ -167,7 +167,7 @@ describe('utils', () => {
|
||||
expect(isTabPage('/admin/test/tabs/tabId/')).toBe(true);
|
||||
});
|
||||
|
||||
it('navigateToTab', () => {
|
||||
it('navigateToTab with basename "/"', () => {
|
||||
const navigate1 = vi.fn();
|
||||
const navigate2 = vi.fn();
|
||||
const navigate3 = vi.fn();
|
||||
@ -177,28 +177,178 @@ describe('utils', () => {
|
||||
const navigate7 = vi.fn();
|
||||
const navigate8 = vi.fn();
|
||||
|
||||
navigateToTab('tabId', navigate1, '/admin/test');
|
||||
navigateToTab({ activeKey: 'tabId', navigate: navigate1, pathname: '/admin/test', basename: '/' });
|
||||
expect(navigate1).toBeCalledWith('/admin/test/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab('tabId', navigate2, '/admin/test/');
|
||||
navigateToTab({ activeKey: 'tabId', navigate: navigate2, pathname: '/admin/test/', basename: '/' });
|
||||
expect(navigate2).toBeCalledWith('/admin/test/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab('tabId', navigate3, '/admin/test/tabs/oldTabId');
|
||||
navigateToTab({ activeKey: 'tabId', navigate: navigate3, pathname: '/admin/test/tabs/oldTabId', basename: '/' });
|
||||
expect(navigate3).toBeCalledWith('/admin/test/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab('tabId', navigate4, '/admin/test/tabs/oldTabId/');
|
||||
navigateToTab({ activeKey: 'tabId', navigate: navigate4, pathname: '/admin/test/tabs/oldTabId/', basename: '/' });
|
||||
expect(navigate4).toBeCalledWith('/admin/test/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab('tabId', navigate5, '/admin/test/tabs/tab1/pages/pageId/tabs/tab2');
|
||||
navigateToTab({
|
||||
activeKey: 'tabId',
|
||||
navigate: navigate5,
|
||||
pathname: '/admin/test/tabs/tab1/pages/pageId/tabs/tab2',
|
||||
basename: '/',
|
||||
});
|
||||
expect(navigate5).toBeCalledWith('/admin/test/tabs/tab1/pages/pageId/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab('tabId', navigate6, '/admin/test/tabs/tab1/pages/pageId/tabs/tab2/');
|
||||
navigateToTab({
|
||||
activeKey: 'tabId',
|
||||
navigate: navigate6,
|
||||
pathname: '/admin/test/tabs/tab1/pages/pageId/tabs/tab2/',
|
||||
basename: '/',
|
||||
});
|
||||
expect(navigate6).toBeCalledWith('/admin/test/tabs/tab1/pages/pageId/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab('tabId', navigate7, '/admin/test/tabs/tab1/pages/pageId');
|
||||
navigateToTab({
|
||||
activeKey: 'tabId',
|
||||
navigate: navigate7,
|
||||
pathname: '/admin/test/tabs/tab1/pages/pageId',
|
||||
basename: '/',
|
||||
});
|
||||
expect(navigate7).toBeCalledWith('/admin/test/tabs/tab1/pages/pageId/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab('tabId', navigate8, '/admin/test/tabs/tab1/pages/pageId/');
|
||||
navigateToTab({
|
||||
activeKey: 'tabId',
|
||||
navigate: navigate8,
|
||||
pathname: '/admin/test/tabs/tab1/pages/pageId/',
|
||||
basename: '/',
|
||||
});
|
||||
expect(navigate8).toBeCalledWith('/admin/test/tabs/tab1/pages/pageId/tabs/tabId', { replace: true });
|
||||
});
|
||||
|
||||
it('navigateToTab with basename "/apps/appId"', () => {
|
||||
const navigate1 = vi.fn();
|
||||
const navigate2 = vi.fn();
|
||||
const navigate3 = vi.fn();
|
||||
const navigate4 = vi.fn();
|
||||
const navigate5 = vi.fn();
|
||||
const navigate6 = vi.fn();
|
||||
const navigate7 = vi.fn();
|
||||
const navigate8 = vi.fn();
|
||||
|
||||
navigateToTab({ activeKey: 'tabId', navigate: navigate1, pathname: '/apps/appId/test', basename: '/apps/appId' });
|
||||
expect(navigate1).toBeCalledWith('/test/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab({ activeKey: 'tabId', navigate: navigate2, pathname: '/apps/appId/test/', basename: '/apps/appId' });
|
||||
expect(navigate2).toBeCalledWith('/test/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab({
|
||||
activeKey: 'tabId',
|
||||
navigate: navigate3,
|
||||
pathname: '/apps/appId/test/tabs/oldTabId',
|
||||
basename: '/apps/appId',
|
||||
});
|
||||
expect(navigate3).toBeCalledWith('/test/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab({
|
||||
activeKey: 'tabId',
|
||||
navigate: navigate4,
|
||||
pathname: '/apps/appId/test/tabs/oldTabId/',
|
||||
basename: '/apps/appId',
|
||||
});
|
||||
expect(navigate4).toBeCalledWith('/test/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab({
|
||||
activeKey: 'tabId',
|
||||
navigate: navigate5,
|
||||
pathname: '/apps/appId/test/tabs/tab1/pages/pageId/tabs/tab2',
|
||||
basename: '/apps/appId',
|
||||
});
|
||||
expect(navigate5).toBeCalledWith('/test/tabs/tab1/pages/pageId/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab({
|
||||
activeKey: 'tabId',
|
||||
navigate: navigate6,
|
||||
pathname: '/apps/appId/test/tabs/tab1/pages/pageId/tabs/tab2/',
|
||||
basename: '/apps/appId',
|
||||
});
|
||||
expect(navigate6).toBeCalledWith('/test/tabs/tab1/pages/pageId/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab({
|
||||
activeKey: 'tabId',
|
||||
navigate: navigate7,
|
||||
pathname: '/apps/appId/test/tabs/tab1/pages/pageId',
|
||||
basename: '/apps/appId',
|
||||
});
|
||||
expect(navigate7).toBeCalledWith('/test/tabs/tab1/pages/pageId/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab({
|
||||
activeKey: 'tabId',
|
||||
navigate: navigate8,
|
||||
pathname: '/apps/appId/test/tabs/tab1/pages/pageId/',
|
||||
basename: '/apps/appId',
|
||||
});
|
||||
expect(navigate8).toBeCalledWith('/test/tabs/tab1/pages/pageId/tabs/tabId', { replace: true });
|
||||
});
|
||||
|
||||
it('navigateToTab with basename "/apps/appId/"', () => {
|
||||
const navigate1 = vi.fn();
|
||||
const navigate2 = vi.fn();
|
||||
const navigate3 = vi.fn();
|
||||
const navigate4 = vi.fn();
|
||||
const navigate5 = vi.fn();
|
||||
const navigate6 = vi.fn();
|
||||
const navigate7 = vi.fn();
|
||||
const navigate8 = vi.fn();
|
||||
|
||||
navigateToTab({ activeKey: 'tabId', navigate: navigate1, pathname: '/apps/appId/test', basename: '/apps/appId/' });
|
||||
expect(navigate1).toBeCalledWith('/test/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab({ activeKey: 'tabId', navigate: navigate2, pathname: '/apps/appId/test/', basename: '/apps/appId/' });
|
||||
expect(navigate2).toBeCalledWith('/test/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab({
|
||||
activeKey: 'tabId',
|
||||
navigate: navigate3,
|
||||
pathname: '/apps/appId/test/tabs/oldTabId',
|
||||
basename: '/apps/appId/',
|
||||
});
|
||||
expect(navigate3).toBeCalledWith('/test/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab({
|
||||
activeKey: 'tabId',
|
||||
navigate: navigate4,
|
||||
pathname: '/apps/appId/test/tabs/oldTabId/',
|
||||
basename: '/apps/appId/',
|
||||
});
|
||||
expect(navigate4).toBeCalledWith('/test/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab({
|
||||
activeKey: 'tabId',
|
||||
navigate: navigate5,
|
||||
pathname: '/apps/appId/test/tabs/tab1/pages/pageId/tabs/tab2',
|
||||
basename: '/apps/appId/',
|
||||
});
|
||||
expect(navigate5).toBeCalledWith('/test/tabs/tab1/pages/pageId/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab({
|
||||
activeKey: 'tabId',
|
||||
navigate: navigate6,
|
||||
pathname: '/apps/appId/test/tabs/tab1/pages/pageId/tabs/tab2/',
|
||||
basename: '/apps/appId/',
|
||||
});
|
||||
expect(navigate6).toBeCalledWith('/test/tabs/tab1/pages/pageId/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab({
|
||||
activeKey: 'tabId',
|
||||
navigate: navigate7,
|
||||
pathname: '/apps/appId/test/tabs/tab1/pages/pageId',
|
||||
basename: '/apps/appId/',
|
||||
});
|
||||
expect(navigate7).toBeCalledWith('/test/tabs/tab1/pages/pageId/tabs/tabId', { replace: true });
|
||||
|
||||
navigateToTab({
|
||||
activeKey: 'tabId',
|
||||
navigate: navigate8,
|
||||
pathname: '/apps/appId/test/tabs/tab1/pages/pageId/',
|
||||
basename: '/apps/appId/',
|
||||
});
|
||||
expect(navigate8).toBeCalledWith('/test/tabs/tab1/pages/pageId/tabs/tabId', { replace: true });
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user