txb 1 год назад
Родитель
Сommit
2acca6e125
6 измененных файлов: 407 добавлений и 0 удалений
  1. +1
    -0
      package.json
  2. +10
    -0
      src/app.jsx
  3. +128
    -0
      src/components/TagView/Tags/index.jsx
  4. +88
    -0
      src/components/TagView/Tags/index.less
  5. +169
    -0
      src/components/TagView/index.jsx
  6. +11
    -0
      src/components/TagView/index.less

+ 1
- 0
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",


+ 10
- 0
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 (
<TagView children={children} home="/quickStart" current={history.location.pathname}>
{/* <PageContainer>{children}</PageContainer> */}
</TagView>
);
},
// 自定义 403 页面
// unAccessible: <div>unAccessible</div>,
...initialState?.settings,


+ 128
- 0
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 (
<div className={styles.tags_wrapper} ref={tagListRef}>
<Scrollbars autoHide autoHideTimeout={1000} autoHideDuration={200}>
{tagList.map((item, i) => {
if (item.path == history.location.pathname) {
item.active = true;
} else {
item.active = false;
}
return (
<div
key={item.path}
className={item.active ? `${styles.item} ${styles.active}` : styles.item}
onClick={(e) => {
e.stopPropagation();
setCurrentPath(item.path);
history.push({ pathname: item.path, query: item.query });
}}
onContextMenu={(e) => openContextMenu(e, item)}
>
<span>{item.title}</span>
{i !== 0 && (
<CloseOutlined
className={styles.icon_close}
onClick={(e) => {
e.stopPropagation();
closeTag && closeTag(item);
}}
/>
)}
</div>
);
})}
</Scrollbars>
{menuVisible ? (
<ul
className={styles.contextmenu}
style={{ left: `${left}px`, top: `${top}px` }}
ref={contextMenuRef}
>
<li
onClick={() => {
setMenuVisible(false);
currentTag && refreshTag && refreshTag(currentTag);
}}
>
刷新
</li>
<li
onClick={() => {
setMenuVisible(false);
currentTag && closeOtherTag && closeOtherTag(currentTag);
}}
>
关闭其他
</li>
<li
onClick={() => {
setMenuVisible(false);
closeAllTag && closeAllTag();
}}
>
关闭所有
</li>
</ul>
) : null}
</div>
);
};

export default Tags;

+ 88
- 0
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;
}
}
}
}

+ 169
- 0
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 (
<>
<RouteContext.Consumer>
{(value) => {
// console.log(value);
routeContextRef.current = value;
return null;
}}
</RouteContext.Consumer>
<div className={styles.tag_view}>
<div className={styles.tags_container}>
<Tags
tagList={tagList}
closeTag={handleCloseTag}
closeAllTag={handleCloseAll}
closeOtherTag={handleCloseOther}
refreshTag={handleRefreshTag}
/>
</div>
</div>

{tagList.map((item) => {
return (
<div key={item.path} style={{ display: item.active ? 'block' : 'none' }}>
<div key={item.refresh}>
{item.children}
</div>
</div>
);
})}
</>
);
};

export default TagView;

+ 11
- 0
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;
}
}

Загрузка…
Отмена
Сохранить