feat(web): finish Semi UI track workspace

This commit is contained in:
lingniu
2026-07-19 23:33:53 +08:00
parent 4c64c4c738
commit 1c18941980
8 changed files with 52 additions and 18 deletions

View File

@@ -98,11 +98,14 @@ describe('route recovery boundary', () => {
useEffect(() => { mounts += 1; }, []);
return <><strong> {count}</strong><button onClick={() => setCount((value) => value + 1)}></button><button onClick={() => setSearchParams({ page: '2' })}></button></>;
}
render(<MemoryRouter future={ROUTER_FUTURE} initialEntries={['/alerts']}><RoutePage page={FilterablePage} label="告警中心" /></MemoryRouter>);
const view = render(<main className="v2-content"><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/alerts']}><RoutePage page={FilterablePage} label="告警中心" /></MemoryRouter></main>);
const content = view.container.querySelector<HTMLElement>('.v2-content')!;
content.scrollTop = 240;
fireEvent.click(screen.getByRole('button', { name: '修改本地状态' }));
fireEvent.click(screen.getByRole('button', { name: '修改查询参数' }));
expect(screen.getByText('本地状态 1')).toBeInTheDocument();
expect(mounts).toBe(1);
expect(content.scrollTop).toBe(240);
});
it('retries a failed route when its search scope changes without remounting healthy scopes', () => {

View File

@@ -1,9 +1,10 @@
import { IconAlertTriangle } from '@douyinfe/semi-icons';
import { Card, Tag, Typography } from '@douyinfe/semi-ui';
import { Component, type ElementType, type ErrorInfo, type ReactNode, Suspense, useCallback, useMemo, useState } from 'react';
import { Component, type ElementType, type ErrorInfo, type ReactNode, Suspense, useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { PageLoading } from '../shared/AsyncState';
import { RecoveryActions } from '../shared/RecoveryActions';
import { resetWorkspaceScroll } from './resetWorkspaceScroll';
const { Title, Text } = Typography;
const CHUNK_RELOAD_KEY = 'vehicle-platform:route-chunk-reload';
@@ -141,6 +142,16 @@ export class PlatformErrorBoundary extends Component<{ children: ReactNode }, {
}
}
function RouteScrollReset({ routeKey }: { routeKey: string }) {
useLayoutEffect(() => {
const reset = () => resetWorkspaceScroll();
reset();
const frame = window.requestAnimationFrame(reset);
return () => window.cancelAnimationFrame(frame);
}, [routeKey]);
return null;
}
export function RoutePage({ page, recreatePage, label }: { page: ElementType; recreatePage?: () => ElementType; label: string }) {
const location = useLocation();
const routeKey = `${location.pathname}${location.search}`;
@@ -148,6 +159,9 @@ export function RoutePage({ page, recreatePage, label }: { page: ElementType; re
const Page = useMemo(() => retryKey && recreatePage ? recreatePage() : page, [page, recreatePage, retryKey]);
const retry = useCallback(() => setRetryKey((value) => value + 1), []);
return <RecoverableRouteBoundary key={location.pathname} routeKey={routeKey} retryKey={retryKey} onRetry={retry}>
<Suspense fallback={<PageLoading label={`正在加载${label}`} />}><Page key={retryKey} /></Suspense>
<Suspense fallback={<PageLoading label={`正在加载${label}`} />}>
<RouteScrollReset routeKey={location.pathname} />
<Page key={retryKey} />
</Suspense>
</RecoverableRouteBoundary>;
}

View File

@@ -0,0 +1,11 @@
export function resetWorkspaceScroll(content = document.querySelector<HTMLElement>('.v2-content')) {
if (!content) return;
content.scrollTop = 0;
content.scrollLeft = 0;
content.scrollTo?.({ top: 0, left: 0, behavior: 'auto' });
const routeRoot = content.firstElementChild;
if (!(routeRoot instanceof HTMLElement)) return;
routeRoot.scrollTop = 0;
routeRoot.scrollLeft = 0;
}