feat(SubTabContainer): 支持 Tab 激活状态和刷新机制

- SubTabContainer: 新增 isActive 和 activationKey props 传递给子组件
- SubTabContainer: 修复 Tab 切换时页面滚动位置跳转问题
- TabPanelContainer: 新增 skeleton prop 支持自定义骨架屏

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-19 18:53:12 +08:00
parent 88da7ad1a5
commit 672e746a26
2 changed files with 33 additions and 1 deletions

View File

@@ -181,6 +181,11 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
() => new Set([controlledIndex ?? defaultIndex])
);
// 记录每个 Tab 的激活次数(用于支持特定 Tab 切换时重新请求)
const [activationCounts, setActivationCounts] = useState<Record<number, number>>(
() => ({ [controlledIndex ?? defaultIndex]: 1 })
);
// 合并主题
const theme: SubTabTheme = {
...THEME_PRESETS[themePreset],
@@ -192,6 +197,9 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
*/
const handleTabChange = useCallback(
(newIndex: number) => {
// 保存当前滚动位置,防止 Tab 切换时页面跳转
const scrollY = window.scrollY;
const tabKey = tabs[newIndex]?.key || '';
onTabChange?.(newIndex, tabKey);
@@ -201,9 +209,20 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
return new Set(prev).add(newIndex);
});
// 更新激活计数(用于触发特定 Tab 的数据刷新)
setActivationCounts(prev => ({
...prev,
[newIndex]: (prev[newIndex] || 0) + 1,
}));
if (controlledIndex === undefined) {
setInternalIndex(newIndex);
}
// 恢复滚动位置,阻止浏览器自动滚动
requestAnimationFrame(() => {
window.scrollTo(0, scrollY);
});
},
[tabs, onTabChange, controlledIndex]
);
@@ -343,6 +362,8 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
const Component = tab.component;
// 懒加载:只渲染已访问过的 Tab
const shouldRender = !isLazy || visitedTabs.has(idx);
// 判断是否为当前激活的 Tab用于控制数据加载
const isActive = idx === currentIndex;
return (
<TabPanel key={tab.key} p={0}>
@@ -361,7 +382,11 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
)
}
>
<Component {...componentProps} />
<Component
{...componentProps}
isActive={isActive}
activationKey={activationCounts[idx] || 0}
/>
</Suspense>
) : null}
</TabPanel>

View File

@@ -28,6 +28,8 @@ export interface TabPanelContainerProps {
loadingMessage?: string;
/** 加载状态高度 */
loadingHeight?: string;
/** 自定义骨架屏组件,优先于默认 Spinner */
skeleton?: React.ReactNode;
/** 子组件间距,默认 6 */
spacing?: number;
/** 内边距,默认 4 */
@@ -74,6 +76,7 @@ const TabPanelContainer: React.FC<TabPanelContainerProps> = memo(
loading = false,
loadingMessage = '加载中...',
loadingHeight = '200px',
skeleton,
spacing = 6,
padding = 4,
showDisclaimer = false,
@@ -81,6 +84,10 @@ const TabPanelContainer: React.FC<TabPanelContainerProps> = memo(
children,
}) => {
if (loading) {
// 如果提供了自定义骨架屏,使用骨架屏;否则使用默认 Spinner
if (skeleton) {
return <>{skeleton}</>;
}
return <LoadingState message={loadingMessage} height={loadingHeight} />;
}