Skip to content

Commit a4f6a11

Browse files
committed
feat: cra-react18项目增加悬浮工具组件(一键到顶等)
fix #113
1 parent 4f5b651 commit a4f6a11

File tree

9 files changed

+116
-22
lines changed

9 files changed

+116
-22
lines changed

.changeset/orange-wombats-act.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"cra-react18": minor
3+
---
4+
5+
feat: cra-react18项目增加悬浮工具组件(一键到顶等)

app/cra-react18/src/components/BaseLayout/HotColumn.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useEffect, useState } from "react";
1+
import { useEffect, useState } from "react";
22
import { Card, Empty } from "antd";
33
import styled from "styled-components";
44
import { NavLink } from "react-router-dom";
@@ -46,12 +46,12 @@ const StyledCard = styled(Card)`
4646

4747
const HotColumn: React.FC = () => {
4848
const [hotList, setHotList] = useState<ArticleDTO[]>([]);
49-
const handleGetHotList = useCallback(async () => {
49+
const handleGetHotList = async () => {
5050
const res = await articleService.topRead({
5151
count: 6,
5252
});
5353
setHotList(res.data);
54-
}, []);
54+
};
5555

5656
const { trigger: getHotList, loading } = useAsyncLoading(handleGetHotList);
5757

app/cra-react18/src/components/BaseLayout/index.tsx

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { NavLink, useLocation } from "react-router-dom";
1+
import { NavLink } from "react-router-dom";
22
import styled, { RuleSet } from "styled-components";
33
import classNames from "classnames";
4-
import { PropsWithChildren, useCallback, useEffect, useState } from "react";
4+
import { PropsWithChildren, useEffect, useState } from "react";
5+
import { throttle } from "lodash-es";
56
import IconSvg from "../IconSvg";
67
import BaseMenu from "./BaseMenu";
78
import BaseFooter from "./BaseFooter";
@@ -10,6 +11,8 @@ import logo from "@/assets/img/logo.png";
1011
import { useIsAuthed } from "@/store/hooks/auth";
1112
import { useAppDispatch, useAppSelector } from "@/store/hooks";
1213
import { setIsMenuVisible } from "@/store/slices/ui";
14+
import { setScrollTop } from "@/utils/dom";
15+
import { flexCenter } from "@/styles/styled-mixins/base";
1316

1417
const Header = styled.header`
1518
padding: 18px 40px;
@@ -66,23 +69,81 @@ const Main = styled.main<{ $mainCss?: RuleSet }>`
6669

6770
const LayoutWrapper = styled.section`
6871
min-height: 100%;
72+
73+
> aside {
74+
position: fixed;
75+
bottom: 160px;
76+
right: 24px;
77+
78+
${IconSvg} {
79+
${flexCenter}
80+
color: #fff;
81+
font-size: 24px;
82+
width: 50px;
83+
height: 50px;
84+
border-radius: 50%;
85+
background-color: rgba(102, 57, 57, 0.4);
86+
cursor: pointer;
87+
+ ${IconSvg} {
88+
margin-top: 10px;
89+
margin-left: 0;
90+
}
91+
}
92+
}
6993
`;
7094

71-
const BaseLayout: React.FC<PropsWithChildren<{ mainCss?: RuleSet }>> = ({ children, mainCss }) => {
95+
const BaseLayout: React.FC<PropsWithChildren<{ mainCss?: RuleSet; asideIcons?: React.ReactNode }>> = ({
96+
children,
97+
asideIcons,
98+
mainCss,
99+
}) => {
72100
const isAuthed = useIsAuthed();
73101
const isMenuVisible = useAppSelector((state) => state.ui.isMenuVisible);
74102
const dispatch = useAppDispatch();
75103
const [isAnimationEnabled, setIsAnimationEnabled] = useState(false);
104+
const [isShowGoTopIcon, setIsShowGoTopIcon] = useState(false);
76105

77-
const location = useLocation();
78-
79-
const hideMenu = useCallback(() => {
106+
const hideMenu = () => {
80107
dispatch(setIsMenuVisible(false));
81-
}, [dispatch]);
108+
};
82109

83110
useEffect(() => {
84-
hideMenu();
85-
}, [location, hideMenu]);
111+
let hideTimer: number | null = null;
112+
const clearHideTimer = () => {
113+
if (hideTimer) {
114+
clearTimeout(hideTimer);
115+
hideTimer = null;
116+
}
117+
};
118+
119+
const setHideTimer = () => {
120+
clearHideTimer();
121+
hideTimer = window.setTimeout(() => {
122+
setIsShowGoTopIcon(false);
123+
}, 5000);
124+
};
125+
126+
const onScroll = () => {
127+
const currScrollTop = document.body.scrollTop || document.documentElement.scrollTop;
128+
if (currScrollTop > 0) {
129+
setIsShowGoTopIcon(true);
130+
setHideTimer();
131+
} else {
132+
setIsShowGoTopIcon(false);
133+
}
134+
};
135+
136+
const onScrollThrottle = throttle(onScroll, 300, { leading: true });
137+
138+
document.addEventListener("scroll", onScrollThrottle);
139+
return () => {
140+
hideMenu();
141+
document.removeEventListener("scroll", onScrollThrottle);
142+
clearHideTimer();
143+
document.body.style.overflow = "";
144+
};
145+
// eslint-disable-next-line react-hooks/exhaustive-deps
146+
}, []);
86147

87148
const onToggleMenu = () => {
88149
if (isAnimationEnabled === false) {
@@ -110,6 +171,12 @@ const BaseLayout: React.FC<PropsWithChildren<{ mainCss?: RuleSet }>> = ({ childr
110171
hideMenu();
111172
};
112173

174+
const goToTop = () => {
175+
setScrollTop({
176+
useAnimation: true,
177+
});
178+
};
179+
113180
const sectionClass = classNames({
114181
slideInLeft: isMenuVisible,
115182
slideOutLeft: !isMenuVisible,
@@ -142,6 +209,11 @@ const BaseLayout: React.FC<PropsWithChildren<{ mainCss?: RuleSet }>> = ({ childr
142209
<BaseMenu open={isMenuVisible} />
143210

144211
<Mask open={isMenuVisible} onClick={onClickMask} />
212+
213+
<aside>
214+
{asideIcons}
215+
<IconSvg icon="arrow-up" style={{ display: isShowGoTopIcon ? "flex" : "none" }} onClick={goToTop} />
216+
</aside>
145217
</LayoutWrapper>
146218
);
147219
};

app/cra-react18/src/components/CardArticle/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ const Article = styled.article`
2626
font-weight: 700;
2727
> ul {
2828
> li {
29-
display: inline-block;
29+
display: inline-flex;
30+
align-items: center;
3031
+ li {
3132
margin-left: 20px;
3233
}

app/cra-react18/src/router/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ const router = createBrowserRouter([
3333
path: "/timeline",
3434
lazy: () => import("../views/Timeline"),
3535
},
36+
{
37+
path: "/article/:id",
38+
lazy: () => import("../views/Article"),
39+
},
3640
]);
3741

3842
export default router;

app/cra-react18/src/styles/styled-mixins/base.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,9 @@ export const ellipsis = `
33
white-space: nowrap;
44
text-overflow: ellipsis;
55
`;
6+
7+
export const flexCenter = `
8+
display: flex;
9+
align-items: center;
10+
justify-content: center;
11+
`;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import BaseLayout from "@/components/BaseLayout";
2+
3+
export const Component: React.FC = () => {
4+
return (
5+
<BaseLayout>
6+
<div>Article</div>
7+
</BaseLayout>
8+
);
9+
};

app/cra-react18/src/views/Home/index.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { useEffect, useMemo, useRef, useState } from "react";
22
import { useSearchParams } from "react-router-dom";
3-
import styled from "styled-components";
43
import { Empty, Pagination, Skeleton } from "antd";
54
import BaseLayout from "@/components/BaseLayout";
65
import { ArticleDTO } from "@/bean/dto";
@@ -9,8 +8,6 @@ import { setScrollTop } from "@/utils/dom";
98
import { useAsyncLoading } from "@/hooks/async";
109
import CardArticle from "@/components/CardArticle";
1110

12-
const ArticleList = styled.section``;
13-
1411
const Home: React.FC = () => {
1512
const [articleList, setArticleList] = useState<ArticleDTO[]>([]);
1613

@@ -65,11 +62,11 @@ const Home: React.FC = () => {
6562
<Skeleton loading={loading} active={true} paragraph={{ rows: 12 }}>
6663
{articleList.length > 0 ? (
6764
<>
68-
<ArticleList>
65+
<section>
6966
{articleList.map((article) => (
7067
<CardArticle article={article} key={article.id} />
7168
))}
72-
</ArticleList>
69+
</section>
7370

7471
<Pagination
7572
current={fetchParams.pageNo}

app/cra-react18/src/views/Timeline/index.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export const Component: React.FC = () => {
106106
return (
107107
<BaseLayout>
108108
<>
109-
{articleList.length > 0 ? (
109+
{articleList.length ? (
110110
<StyledTimeline>
111111
{articleList.map((article) => (
112112
<Timeline.Item key={article.id} color="#2b82a8">
@@ -133,9 +133,9 @@ export const Component: React.FC = () => {
133133
</Timeline.Item>
134134
))}
135135
</StyledTimeline>
136-
) : (
137-
<Empty />
138-
)}
136+
) : null}
137+
138+
{!articleList.length && !loading ? <Empty /> : null}
139139

140140
<Skeleton loading={loading} active paragraph={{ rows: 6 }} />
141141

0 commit comments

Comments
 (0)