pay-admin/src/common/components/layout/MyPageContainer.tsx
Your Name 4c844e1a12
All checks were successful
Build and Push Docker Image / build (push) Successful in 4m14s
fix:更新优化
2026-03-31 16:45:08 +08:00

438 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useMyState } from '@/common';
import { CloseOutlined, ReloadOutlined } from '@ant-design/icons';
import { PageContainer, PageContainerProps } from '@ant-design/pro-components';
import { history, useLocation } from '@umijs/max';
import type { MenuProps } from 'antd';
import { Dropdown, message, Space, Tabs } from 'antd';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import './MyPageContainer.scss';
export interface TabItem {
key: string;
label: string;
path: string;
closable?: boolean;
}
export interface MyPageContainerProps extends PageContainerProps {
enableTabs?: boolean;
tabKey?: string;
tabLabel?: string;
onTabChange?: (activeKey: string) => void;
}
// 全局标签页状态管理
class TabsManager {
private tabs: TabItem[] = [];
private activeKey: string = '';
private listeners: Set<() => void> = new Set();
subscribe(listener: () => void) {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
private notify() {
this.listeners.forEach((listener) => listener());
}
getTabs() {
return this.tabs;
}
getActiveKey() {
return this.activeKey;
}
addTab(tab: TabItem) {
const existingIndex = this.tabs.findIndex((t) => t.key === tab.key);
if (existingIndex === -1) {
// 如果是新标签页,插入到当前激活标签页的右边
const currentActiveIndex = this.tabs.findIndex(
(t) => t.key === this.activeKey,
);
if (currentActiveIndex !== -1) {
// 在当前激活标签页的右边插入新标签页
this.tabs.splice(currentActiveIndex + 1, 0, tab);
} else {
// 如果没有当前激活标签页,添加到末尾
this.tabs.push(tab);
}
} else {
// 如果标签页已存在,更新其信息
this.tabs[existingIndex] = { ...this.tabs[existingIndex], ...tab };
}
this.activeKey = tab.key;
this.notify();
}
addTabNext(tab: TabItem) {
const existingIndex = this.tabs.findIndex((t) => t.key === tab.key);
if (existingIndex === -1) {
// 强制在当前激活标签页的右边插入新标签页
const currentActiveIndex = this.tabs.findIndex(
(t) => t.key === this.activeKey,
);
if (currentActiveIndex !== -1) {
this.tabs.splice(currentActiveIndex + 1, 0, tab);
} else {
this.tabs.push(tab);
}
} else {
// 如果标签页已存在,移动到当前激活标签页的右边
const existingTab = this.tabs[existingIndex];
this.tabs.splice(existingIndex, 1); // 先移除原位置的标签页
const currentActiveIndex = this.tabs.findIndex(
(t) => t.key === this.activeKey,
);
if (currentActiveIndex !== -1) {
this.tabs.splice(currentActiveIndex + 1, 0, { ...existingTab, ...tab });
} else {
this.tabs.push({ ...existingTab, ...tab });
}
}
this.activeKey = tab.key;
this.notify();
}
removeTab(targetKey: string) {
const targetIndex = this.tabs.findIndex((tab) => tab.key === targetKey);
if (targetIndex === -1) return;
const newTabs = this.tabs.filter((tab) => tab.key !== targetKey);
if (newTabs.length === 0) {
this.tabs = [];
this.activeKey = '';
history.push('/');
} else {
this.tabs = newTabs;
if (this.activeKey === targetKey) {
// 如果关闭的是当前激活的标签,激活相邻的标签
const newActiveKey =
targetIndex > 0 ? newTabs[targetIndex - 1].key : newTabs[0].key;
this.activeKey = newActiveKey;
const targetTab = newTabs.find((tab) => tab.key === newActiveKey);
if (targetTab) {
history.push(targetTab.path);
}
}
}
this.notify();
}
setActiveKey(key: string) {
this.activeKey = key;
const targetTab = this.tabs.find((tab) => tab.key === key);
if (targetTab) {
history.push(targetTab.path);
}
this.notify();
}
closeOtherTabs(currentKey: string) {
const currentTab = this.tabs.find((tab) => tab.key === currentKey);
if (currentTab) {
this.tabs = [currentTab];
this.activeKey = currentKey;
this.notify();
}
}
closeLeftTabs(currentKey: string) {
const currentIndex = this.tabs.findIndex((tab) => tab.key === currentKey);
if (currentIndex > 0) {
this.tabs = this.tabs.slice(currentIndex);
this.notify();
}
}
closeRightTabs(currentKey: string) {
const currentIndex = this.tabs.findIndex((tab) => tab.key === currentKey);
if (currentIndex !== -1) {
this.tabs = this.tabs.slice(0, currentIndex + 1);
this.notify();
}
}
refreshTab(key: string) {
// 通过路由跳转的方式刷新当前标签页,避免整个页面刷新导致标签页状态丢失
const targetTab = this.tabs.find((tab) => tab.key === key);
if (targetTab) {
const originalPath = targetTab.path;
// 移除可能存在的刷新参数,确保获取干净的原始路径
const cleanPath = originalPath
.replace(/[?&]_refresh=\d+/g, '')
.replace(/\?$/, '');
// 添加时间戳参数强制刷新
const refreshPath = cleanPath.includes('?')
? `${cleanPath}&_refresh=${Date.now()}`
: `${cleanPath}?_refresh=${Date.now()}`;
// 先更新为带刷新参数的路径并跳转
targetTab.path = refreshPath;
this.setActiveKey(key);
// 延迟恢复原始路径,确保路由跳转完成
setTimeout(() => {
// 恢复为干净的原始路径
targetTab.path = cleanPath;
// 再次跳转到干净路径移除URL中的刷新参数
history.push(cleanPath);
this.notify();
}, 300);
}
}
}
// 全局标签页管理器实例
const tabsManager = new TabsManager();
export function MyPageContainer({
title,
children,
enableTabs = true,
tabKey,
tabLabel,
onTabChange,
...rest
}: MyPageContainerProps) {
const location = useLocation();
const { snap } = useMyState();
const [tabs, setTabs] = useState<TabItem[]>([]);
const [activeKey, setActiveKey] = useState<string>('');
const contextMenuRef = useRef<{
x: number;
y: number;
targetKey: string;
} | null>(null);
// 用户下拉菜单配置
const userMenuItems: MenuProps['items'] = [
{
key: 'profile',
label: '个人资料',
icon: <span>👤</span>,
},
{
key: 'password',
label: '修改密码',
icon: <span>🔒</span>,
},
{
type: 'divider',
},
{
key: 'logout',
label: '退出登录',
icon: <span>🚪</span>,
danger: true,
onClick: () => {
localStorage.removeItem('token');
window.location.href = '/login';
},
},
];
// 订阅标签页状态变化
useEffect(() => {
const unsubscribe = tabsManager.subscribe(() => {
setTabs([...tabsManager.getTabs()]);
setActiveKey(tabsManager.getActiveKey());
});
// 初始化状态
setTabs([...tabsManager.getTabs()]);
setActiveKey(tabsManager.getActiveKey());
return () => {
unsubscribe();
};
}, []);
// 当组件挂载时,添加当前页面到标签页
useEffect(() => {
if (enableTabs && tabKey && tabLabel) {
tabsManager.addTab({
key: tabKey,
label: tabLabel,
path: location.pathname + location.search,
closable: true,
});
}
}, [enableTabs, tabKey, tabLabel, location.pathname, location.search]);
const handleTabChange = useCallback(
(key: string) => {
tabsManager.setActiveKey(key);
onTabChange?.(key);
},
[onTabChange],
);
const handleTabEdit = useCallback(
(
targetKey: string | React.MouseEvent | React.KeyboardEvent,
action: 'add' | 'remove',
) => {
if (action === 'remove' && typeof targetKey === 'string') {
tabsManager.removeTab(targetKey);
}
},
[],
);
// 右键菜单配置
const getContextMenuItems = useCallback(
(targetKey: string): MenuProps['items'] => {
const currentIndex = tabs.findIndex((tab) => tab.key === targetKey);
const hasLeftTabs = currentIndex > 0;
const hasRightTabs = currentIndex < tabs.length - 1;
const hasOtherTabs = tabs.length > 1;
return [
{
key: 'refresh',
label: '刷新',
icon: <ReloadOutlined />,
onClick: () => tabsManager.refreshTab(targetKey),
},
{
key: 'close',
label: '关闭',
icon: <CloseOutlined />,
onClick: () => tabsManager.removeTab(targetKey),
},
{
type: 'divider',
},
{
key: 'closeOthers',
label: '关闭其他',
disabled: !hasOtherTabs,
onClick: () => {
tabsManager.closeOtherTabs(targetKey);
message.success('已关闭其他标签页');
},
},
{
key: 'closeLeft',
label: '关闭左侧',
disabled: !hasLeftTabs,
onClick: () => {
tabsManager.closeLeftTabs(targetKey);
message.success('已关闭左侧标签页');
},
},
{
key: 'closeRight',
label: '关闭右侧',
disabled: !hasRightTabs,
onClick: () => {
tabsManager.closeRightTabs(targetKey);
message.success('已关闭右侧标签页');
},
},
];
},
[tabs],
);
// 自定义标签页渲染
const renderTabBar: React.ComponentProps<typeof Tabs>['renderTabBar'] = (
props,
DefaultTabBar,
) => {
return (
<DefaultTabBar {...props}>
{(node) => {
const tabKey = node.key as string;
return (
<Dropdown
menu={{ items: getContextMenuItems(tabKey) }}
trigger={['contextMenu']}
>
<div>{node}</div>
</Dropdown>
);
}}
</DefaultTabBar>
);
};
// if (!enableTabs || tabs.length === 0) {
// return (
// <PageContainer
// fixedHeader
// // header={{
// // title: title,
// // style: { backgroundColor: '#FFF' },
// // }}
// token={{
// paddingBlockPageContainerContent: 0,
// paddingInlinePageContainerContent: 0,
// }}
// {...rest}
// >
// <Space direction="vertical" size="middle" style={{ width: '100%' }}>
// {children}
// </Space>
// </PageContainer>
// );
// }
return (
<PageContainer
fixedHeader
header={{
title: (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
}}
>
{/* 左侧Tabs标签页 */}
<div style={{ flex: 1 }}>
<Tabs
type="editable-card"
activeKey={activeKey}
onChange={handleTabChange}
onEdit={handleTabEdit}
hideAdd
size="middle"
renderTabBar={renderTabBar}
items={tabs.map((tab) => ({
key: tab.key,
label: tab.label,
closable: tab.closable,
}))}
style={{
marginTop: 6,
marginBottom: 0,
}}
/>
</div>
</div>
),
style: { backgroundColor: '#FFF' },
}}
token={{
paddingBlockPageContainerContent: 0,
paddingInlinePageContainerContent: 0,
}}
{...rest}
>
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
{children}
</Space>
</PageContainer>
);
}
// 导出标签页管理器,供其他组件使用
export { tabsManager };