diff --git a/package.json b/package.json
index b6fbda4..7c7f48b 100644
--- a/package.json
+++ b/package.json
@@ -73,6 +73,7 @@
"omit.js": "^2.0.2",
"qrcode.react": "^1.0.1",
"react": "^17.0.0",
+ "react-custom-scrollbars": "^4.2.1",
"react-dev-inspector": "^1.1.1",
"react-dom": "^17.0.0",
"react-helmet-async": "^1.0.4",
diff --git a/src/app.jsx b/src/app.jsx
index ec7736d..e623305 100644
--- a/src/app.jsx
+++ b/src/app.jsx
@@ -4,6 +4,7 @@ import { notification } from 'antd';
import { history, Link, RequestConfig } from 'umi';
import RightContent from '@/components/RightContent';
import Footer from '@/components/Footer';
+import TagView from '@/components/TagView';
import * as Icon from '@ant-design/icons';
import api from '@/services/api';
const isDev = process.env.NODE_ENV === 'development';
@@ -978,6 +979,15 @@ export const layout = ({ initialState }) => {
}
},
menuDataRender: () => loopMenuItem(initialState?.menuData),
+ childrenRender: (children) => {
+ // children.pathname = history.location.pathname;
+ console.log("11111",children);
+ return (
+
+ {/* {children} */}
+
+ );
+ },
// 自定义 403 页面
// unAccessible:
unAccessible
,
...initialState?.settings,
diff --git a/src/components/TagView/Tags/index.jsx b/src/components/TagView/Tags/index.jsx
new file mode 100644
index 0000000..0d8e504
--- /dev/null
+++ b/src/components/TagView/Tags/index.jsx
@@ -0,0 +1,128 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { Scrollbars } from 'react-custom-scrollbars';
+import { CloseOutlined } from '@ant-design/icons';
+import { history } from 'umi';
+// import type { TagsItemType } from '../index';
+import styles from './index.less';
+
+
+
+const Tags = ({ tagList, closeTag, closeAllTag, closeOtherTag, refreshTag }) => {
+ const [left, setLeft] = useState(0);
+ const [top, setTop] = useState(0);
+ const [menuVisible, setMenuVisible] = useState(false);
+ const [currentTag, setCurrentTag] = useState();
+
+ const tagListRef = useRef();
+ const contextMenuRef = useRef();
+ const [currentPath, setCurrentPath] = useState();
+ useEffect(() => {
+ return () => {
+ document.body.removeEventListener('click', handleClickOutside);
+ };
+ }, []);
+
+ // 由于react的state不能及时穿透到 document.body.addEventListener去,需要在每次值发送改变时进行解绑和再次监听
+ useEffect(() => {
+ document.body.removeEventListener('click', handleClickOutside);
+ document.body.addEventListener('click', handleClickOutside);
+ }, [menuVisible]);
+
+ const handleClickOutside = (event) => {
+ const isOutside = !(contextMenuRef.current && contextMenuRef.current.contains(event.target));
+ if (isOutside && menuVisible) {
+ setMenuVisible(false);
+ }
+ };
+
+ const openContextMenu = (
+ event,
+ tag,
+ ) => {
+ event.preventDefault();
+ const menuMinWidth = 105;
+ const clickX = event.clientX;
+ const clickY = event.clientY; //事件发生时鼠标的Y坐标
+ const clientWidth = tagListRef.current?.clientWidth || 0; // container width
+ const maxLeft = clientWidth - menuMinWidth; // left boundary
+ setCurrentTag(tag);
+ setMenuVisible(true);
+ setTop(clickY);
+
+ // 当鼠标点击位置大于左侧边界时,说明鼠标点击的位置偏右,将菜单放在左边
+ // 反之,当鼠标点击的位置偏左,将菜单放在右边
+ const Left = clickX > maxLeft ? clickX - menuMinWidth + 15 : clickX;
+ setLeft(Left);
+ };
+
+ return (
+
+
+ {tagList.map((item, i) => {
+ if (item.path == history.location.pathname) {
+ item.active = true;
+ } else {
+ item.active = false;
+ }
+ return (
+ {
+ e.stopPropagation();
+ setCurrentPath(item.path);
+ history.push({ pathname: item.path, query: item.query });
+ }}
+ onContextMenu={(e) => openContextMenu(e, item)}
+ >
+ {item.title}
+ {i !== 0 && (
+ {
+ e.stopPropagation();
+ closeTag && closeTag(item);
+ }}
+ />
+ )}
+
+ );
+ })}
+
+ {menuVisible ? (
+
+ - {
+ setMenuVisible(false);
+ currentTag && refreshTag && refreshTag(currentTag);
+ }}
+ >
+ 刷新
+
+ - {
+ setMenuVisible(false);
+ currentTag && closeOtherTag && closeOtherTag(currentTag);
+ }}
+ >
+ 关闭其他
+
+ - {
+ setMenuVisible(false);
+ closeAllTag && closeAllTag();
+ }}
+ >
+ 关闭所有
+
+
+ ) : null}
+
+ );
+};
+
+export default Tags;
diff --git a/src/components/TagView/Tags/index.less b/src/components/TagView/Tags/index.less
new file mode 100644
index 0000000..ff81c16
--- /dev/null
+++ b/src/components/TagView/Tags/index.less
@@ -0,0 +1,88 @@
+@primary: #FA541C;
+span{
+ -moz-user-select: none;
+ user-select: none
+}
+.tags_wrapper {
+ position: relative;
+ width: 100%;
+ height: 34px;
+ line-height: 34px;
+ background: #fff;
+ box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.12), 0 0 1px 0 rgba(0, 0, 0, 0.01);
+ .item {
+ position: relative;
+ display: inline-block;
+ height: 28px;
+ margin-top: 2px;
+ margin-left: 5px;
+ padding: 0 8px;
+ color: #495060;
+ font-size: 13px;
+ line-height: 26px;
+ background: #fff;
+ border: 1px solid #d8dce5;
+ cursor: pointer;
+
+ &:first-of-type {
+ margin-left: 15px;
+ }
+
+ &:last-of-type {
+ margin-right: 15px;
+ }
+
+ &.active {
+ color: #fff;
+ background-color: @primary;
+ border-color: @primary;
+
+ &::before {
+ position: relative;
+ display: inline-block;
+ width: 8px;
+ height: 8px;
+ margin-right: 2px;
+ background: #fff;
+ border-radius: 50%;
+ content: '';
+ }
+ }
+ }
+
+ .icon_close {
+ position: relative;
+ top: -1px;
+ margin-left: 6px;
+ font-size: 10px;
+
+ &:hover {
+ color: red;
+ }
+ }
+
+ .contextmenu {
+ position: fixed;
+ z-index: 3000;
+ margin: 0;
+ padding: 5px 0;
+ color: #333;
+ font-weight: 400;
+ font-size: 12px;
+ list-style-type: none;
+ background: #fff;
+ border-radius: 4px;
+ box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
+
+ li {
+ margin: 0;
+ padding: 2px 16px;
+ line-height: 24px;
+ cursor: pointer;
+
+ &:hover {
+ background: #eee;
+ }
+ }
+ }
+}
diff --git a/src/components/TagView/index.jsx b/src/components/TagView/index.jsx
new file mode 100644
index 0000000..e0821c8
--- /dev/null
+++ b/src/components/TagView/index.jsx
@@ -0,0 +1,169 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { RouteContext } from '@ant-design/pro-layout';
+
+import { history } from 'umi';
+import Tags from './Tags';
+import styles from './index.less';
+
+// export type TagsItemType = {
+// title?: string;
+// path?: string;
+// active: boolean;
+// query?: any;
+// children: any;
+// refresh: number;
+// };
+
+// interface IProps {
+// home: string;
+// current: string;
+// }
+
+/**
+ * @component TagView 标签页组件
+ */
+const TagView = ({ children, home, current }) => {
+ const [tagList, setTagList] = useState([]);
+ const routeContextRef = useRef();
+
+ useEffect(() => {
+ if (routeContextRef?.current) {
+ handleOnChange(routeContextRef.current);
+ }
+ }, [current]);
+
+ // 初始化 visitedViews,设置project为首页
+ const initTags = (routeContext) => {
+ if (tagList.length === 0 && routeContext.menuData) {
+ console.log('routeContext.menuData',routeContext.menuData);
+ const firstTag = routeContext.menuData.filter((el) => el.path === home)[0];
+ const title = firstTag.name;
+ const path = firstTag.path;
+ history.push({ pathname: firstTag.path, query: firstTag.query });
+ console.log(children);
+ setTagList([
+ {
+ title,
+ path,
+ children,
+ refresh: 0,
+ active: true,
+ },
+ ]);
+ }
+ };
+
+ // 监听路由改变
+ const handleOnChange = (routeContext) => {
+ const { currentMenu } = routeContext;
+
+ // tags初始化
+ if (tagList.length === 0) {
+ return initTags(routeContext);
+ }
+ // 判断是否已打开过该页面
+ let hasOpen = false;
+ const tagsCopy = tagList.map((item) => {
+ if (currentMenu?.path === item.path) {
+ hasOpen = true;
+ // 刷新浏览器时,重新覆盖当前 path 的 children
+ return { ...item, active: true, children };
+ } else {
+ return { ...item, active: false };
+ }
+ });
+
+ // 没有该tag时追加一个,并打开这个tag页面
+ if (!hasOpen) {
+ const title = routeContext.title || '';
+ const path = currentMenu?.path;
+ tagsCopy.push({
+ title,
+ path,
+ children,
+ refresh: 0,
+ active: true,
+ });
+ }
+ return setTagList(tagsCopy);
+ };
+
+ // 关闭标签
+ const handleCloseTag = (tag) => {
+ const tagsCopy = tagList.map((el, i) => ({ ...el }));
+
+ // 判断关闭标签是否处于打开状态
+ tagList.forEach((el, i) => {
+ if (el.path === tag.path && tag.active) {
+ const next = tagList[i - 1];
+ next.active = true;
+ history.push({ pathname: next?.path, query: next?.query });
+ }
+ });
+
+ setTagList(tagsCopy.filter((el) => el.path !== tag?.path));
+ };
+
+ // 关闭所有标签
+ const handleCloseAll = () => {
+ const tagsCopy = tagList.filter((el) => el.path === home);
+ history.push(home);
+ setTagList(tagsCopy);
+ };
+
+ // 关闭其他标签
+ const handleCloseOther = (tag) => {
+ const tagsCopy = tagList.filter(
+ (el) => el.path === home || el.path === tag.path,
+ );
+ history.push({ pathname: tag?.path, query: tag?.query });
+ setTagList(tagsCopy);
+ };
+
+ // 刷新选择的标签
+ const handleRefreshTag = (tag) => {
+ const tagsCopy = tagList.map((item) => {
+ if (item.path === tag.path) {
+ history.push({ pathname: tag?.path, query: tag?.query });
+ return { ...item, refresh: item.refresh + 1, active: true };
+ }
+ return { ...item, active: false };
+ });
+ setTagList(tagsCopy);
+ };
+
+ return (
+ <>
+
+ {(value) => {
+ // console.log(value);
+ routeContextRef.current = value;
+ return null;
+ }}
+
+
+
+ {tagList.map((item) => {
+ return (
+
+ );
+ })}
+ >
+ );
+};
+
+export default TagView;
diff --git a/src/components/TagView/index.less b/src/components/TagView/index.less
new file mode 100644
index 0000000..bf1ec75
--- /dev/null
+++ b/src/components/TagView/index.less
@@ -0,0 +1,11 @@
+.tag_view {
+ .tags_container {
+ position: relative;
+ top: -22px;
+ right: 24px;
+ z-index: 199;
+ width: calc(100% + 48px);
+ height: 36px;
+ border: 0px dashed coral;
+ }
+}