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 './useApp';
|
||||||
|
export * from './useAppSpin';
|
||||||
export * from './usePlugin';
|
export * from './usePlugin';
|
||||||
export * from './useRouter';
|
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 qs from 'qs';
|
||||||
import { ChangeEvent, useCallback, useContext, useEffect, useMemo } from 'react';
|
import { ChangeEvent, useCallback, useContext, useEffect, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
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 { useReactToPrint } from 'react-to-print';
|
||||||
import {
|
import {
|
||||||
AssociationFilter,
|
AssociationFilter,
|
||||||
@ -28,6 +28,7 @@ import {
|
|||||||
useCollectionRecord,
|
useCollectionRecord,
|
||||||
useDataSourceHeaders,
|
useDataSourceHeaders,
|
||||||
useFormActiveFields,
|
useFormActiveFields,
|
||||||
|
useRouterBasename,
|
||||||
useTableBlockContext,
|
useTableBlockContext,
|
||||||
} from '../..';
|
} from '../..';
|
||||||
import { useAPIClient, useRequest } from '../../api-client';
|
import { useAPIClient, useRequest } from '../../api-client';
|
||||||
@ -1594,9 +1595,7 @@ export function useLinkActionProps(componentProps?: any) {
|
|||||||
const searchParams = componentPropsValue?.['params'] || [];
|
const searchParams = componentPropsValue?.['params'] || [];
|
||||||
const openInNewWindow = fieldSchema?.['x-component-props']?.['openInNewWindow'];
|
const openInNewWindow = fieldSchema?.['x-component-props']?.['openInNewWindow'];
|
||||||
const { parseURLAndParams } = useParseURLAndParams();
|
const { parseURLAndParams } = useParseURLAndParams();
|
||||||
|
const basenameOfCurrentRouter = useRouterBasename();
|
||||||
// see: https://stackoverflow.com/questions/50449423/accessing-basename-of-browserouter
|
|
||||||
const basenameOfCurrentRouter = useHref('/');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'default',
|
type: 'default',
|
||||||
|
@ -23,6 +23,7 @@ import { useStyles as useAClStyles } from '../../../acl/style';
|
|||||||
import { useRequest } from '../../../api-client';
|
import { useRequest } from '../../../api-client';
|
||||||
import { useNavigateNoUpdate } from '../../../application/CustomRouterContextProvider';
|
import { useNavigateNoUpdate } from '../../../application/CustomRouterContextProvider';
|
||||||
import { useAppSpin } from '../../../application/hooks/useAppSpin';
|
import { useAppSpin } from '../../../application/hooks/useAppSpin';
|
||||||
|
import { useRouterBasename } from '../../../application/hooks/useRouterBasename';
|
||||||
import { useDocumentTitle } from '../../../document-title';
|
import { useDocumentTitle } from '../../../document-title';
|
||||||
import { useGlobalTheme } from '../../../global-theme';
|
import { useGlobalTheme } from '../../../global-theme';
|
||||||
import { Icon } from '../../../icon';
|
import { Icon } from '../../../icon';
|
||||||
@ -47,6 +48,7 @@ export const Page = (props) => {
|
|||||||
const { theme } = useGlobalTheme();
|
const { theme } = useGlobalTheme();
|
||||||
const { getAriaLabel } = useGetAriaLabelOfSchemaInitializer();
|
const { getAriaLabel } = useGetAriaLabelOfSchemaInitializer();
|
||||||
const { tabUid, name: pageUid } = useParams();
|
const { tabUid, name: pageUid } = useParams();
|
||||||
|
const basenameOfCurrentRouter = useRouterBasename();
|
||||||
|
|
||||||
// react18 tab 动画会卡顿,所以第一个 tab 时,动画禁用,后面的 tab 才启用
|
// react18 tab 动画会卡顿,所以第一个 tab 时,动画禁用,后面的 tab 才启用
|
||||||
const [hasMounted, setHasMounted] = useState(false);
|
const [hasMounted, setHasMounted] = useState(false);
|
||||||
@ -112,7 +114,7 @@ export const Page = (props) => {
|
|||||||
}}
|
}}
|
||||||
onChange={(activeKey) => {
|
onChange={(activeKey) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
navigateToTab(activeKey, navigate);
|
navigateToTab({ activeKey, navigate, basename: basenameOfCurrentRouter });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}, 50);
|
}, 50);
|
||||||
@ -320,11 +322,28 @@ const PageContent = memo(
|
|||||||
);
|
);
|
||||||
PageContent.displayName = 'PageContent';
|
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('/')) {
|
if (pathname.endsWith('/')) {
|
||||||
pathname = pathname.slice(0, -1);
|
pathname = pathname.slice(0, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!pathname.startsWith('/')) {
|
||||||
|
pathname = `/${pathname}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (isTabPage(pathname)) {
|
if (isTabPage(pathname)) {
|
||||||
navigate(`${pathname.replace(/\/tabs\/[^/]+$/, `/tabs/${activeKey}`)}`, { replace: true });
|
navigate(`${pathname.replace(/\/tabs\/[^/]+$/, `/tabs/${activeKey}`)}`, { replace: true });
|
||||||
} else {
|
} else {
|
||||||
|
@ -84,7 +84,8 @@ const PopupParamsProvider: FC<Omit<PopupProps, 'hidden'>> = (props) => {
|
|||||||
return <PopupParamsProviderContext.Provider value={value}>{props.children}</PopupParamsProviderContext.Provider>;
|
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 { changeTab } = usePagePopup();
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(key: string) => {
|
(key: string) => {
|
||||||
@ -99,7 +100,7 @@ const PopupTabsPropsProvider: FC<{ params: PopupParams }> = ({ children, params
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabsContextProvider activeKey={params.tab} onChange={onChange}>
|
<TabsContextProvider activeKey={params?.tab} onChange={onChange}>
|
||||||
{children}
|
{children}
|
||||||
</TabsContextProvider>
|
</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 */}
|
{/* 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}>
|
<BlockRequestContext.Provider value={storedContext.service}>
|
||||||
<PopupTabsPropsProvider params={params}>
|
<PopupTabsPropsProvider>
|
||||||
<div style={{ display: 'none' }}>{children}</div>
|
<div style={{ display: 'none' }}>{children}</div>
|
||||||
</PopupTabsPropsProvider>
|
</PopupTabsPropsProvider>
|
||||||
</BlockRequestContext.Provider>
|
</BlockRequestContext.Provider>
|
||||||
|
@ -167,7 +167,7 @@ describe('utils', () => {
|
|||||||
expect(isTabPage('/admin/test/tabs/tabId/')).toBe(true);
|
expect(isTabPage('/admin/test/tabs/tabId/')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('navigateToTab', () => {
|
it('navigateToTab with basename "/"', () => {
|
||||||
const navigate1 = vi.fn();
|
const navigate1 = vi.fn();
|
||||||
const navigate2 = vi.fn();
|
const navigate2 = vi.fn();
|
||||||
const navigate3 = vi.fn();
|
const navigate3 = vi.fn();
|
||||||
@ -177,28 +177,178 @@ describe('utils', () => {
|
|||||||
const navigate7 = vi.fn();
|
const navigate7 = vi.fn();
|
||||||
const navigate8 = 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 });
|
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 });
|
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 });
|
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 });
|
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 });
|
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 });
|
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 });
|
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 });
|
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