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:
@@ -181,6 +181,11 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
|
|||||||
() => new Set([controlledIndex ?? defaultIndex])
|
() => new Set([controlledIndex ?? defaultIndex])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 记录每个 Tab 的激活次数(用于支持特定 Tab 切换时重新请求)
|
||||||
|
const [activationCounts, setActivationCounts] = useState<Record<number, number>>(
|
||||||
|
() => ({ [controlledIndex ?? defaultIndex]: 1 })
|
||||||
|
);
|
||||||
|
|
||||||
// 合并主题
|
// 合并主题
|
||||||
const theme: SubTabTheme = {
|
const theme: SubTabTheme = {
|
||||||
...THEME_PRESETS[themePreset],
|
...THEME_PRESETS[themePreset],
|
||||||
@@ -192,6 +197,9 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
|
|||||||
*/
|
*/
|
||||||
const handleTabChange = useCallback(
|
const handleTabChange = useCallback(
|
||||||
(newIndex: number) => {
|
(newIndex: number) => {
|
||||||
|
// 保存当前滚动位置,防止 Tab 切换时页面跳转
|
||||||
|
const scrollY = window.scrollY;
|
||||||
|
|
||||||
const tabKey = tabs[newIndex]?.key || '';
|
const tabKey = tabs[newIndex]?.key || '';
|
||||||
onTabChange?.(newIndex, tabKey);
|
onTabChange?.(newIndex, tabKey);
|
||||||
|
|
||||||
@@ -201,9 +209,20 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
|
|||||||
return new Set(prev).add(newIndex);
|
return new Set(prev).add(newIndex);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 更新激活计数(用于触发特定 Tab 的数据刷新)
|
||||||
|
setActivationCounts(prev => ({
|
||||||
|
...prev,
|
||||||
|
[newIndex]: (prev[newIndex] || 0) + 1,
|
||||||
|
}));
|
||||||
|
|
||||||
if (controlledIndex === undefined) {
|
if (controlledIndex === undefined) {
|
||||||
setInternalIndex(newIndex);
|
setInternalIndex(newIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 恢复滚动位置,阻止浏览器自动滚动
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
window.scrollTo(0, scrollY);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[tabs, onTabChange, controlledIndex]
|
[tabs, onTabChange, controlledIndex]
|
||||||
);
|
);
|
||||||
@@ -343,6 +362,8 @@ const SubTabContainer: React.FC<SubTabContainerProps> = memo(({
|
|||||||
const Component = tab.component;
|
const Component = tab.component;
|
||||||
// 懒加载:只渲染已访问过的 Tab
|
// 懒加载:只渲染已访问过的 Tab
|
||||||
const shouldRender = !isLazy || visitedTabs.has(idx);
|
const shouldRender = !isLazy || visitedTabs.has(idx);
|
||||||
|
// 判断是否为当前激活的 Tab(用于控制数据加载)
|
||||||
|
const isActive = idx === currentIndex;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabPanel key={tab.key} p={0}>
|
<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>
|
</Suspense>
|
||||||
) : null}
|
) : null}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ export interface TabPanelContainerProps {
|
|||||||
loadingMessage?: string;
|
loadingMessage?: string;
|
||||||
/** 加载状态高度 */
|
/** 加载状态高度 */
|
||||||
loadingHeight?: string;
|
loadingHeight?: string;
|
||||||
|
/** 自定义骨架屏组件,优先于默认 Spinner */
|
||||||
|
skeleton?: React.ReactNode;
|
||||||
/** 子组件间距,默认 6 */
|
/** 子组件间距,默认 6 */
|
||||||
spacing?: number;
|
spacing?: number;
|
||||||
/** 内边距,默认 4 */
|
/** 内边距,默认 4 */
|
||||||
@@ -74,6 +76,7 @@ const TabPanelContainer: React.FC<TabPanelContainerProps> = memo(
|
|||||||
loading = false,
|
loading = false,
|
||||||
loadingMessage = '加载中...',
|
loadingMessage = '加载中...',
|
||||||
loadingHeight = '200px',
|
loadingHeight = '200px',
|
||||||
|
skeleton,
|
||||||
spacing = 6,
|
spacing = 6,
|
||||||
padding = 4,
|
padding = 4,
|
||||||
showDisclaimer = false,
|
showDisclaimer = false,
|
||||||
@@ -81,6 +84,10 @@ const TabPanelContainer: React.FC<TabPanelContainerProps> = memo(
|
|||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
// 如果提供了自定义骨架屏,使用骨架屏;否则使用默认 Spinner
|
||||||
|
if (skeleton) {
|
||||||
|
return <>{skeleton}</>;
|
||||||
|
}
|
||||||
return <LoadingState message={loadingMessage} height={loadingHeight} />;
|
return <LoadingState message={loadingMessage} height={loadingHeight} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user