Skip to content

Commit 048375a

Browse files
authored
Merge pull request #34 from HelloGitHub-Team/i33-手机端交互优化用户登录
I33 手机端交互优化用户登录
2 parents 69dc88d + 7d2b06b commit 048375a

22 files changed

+416
-171
lines changed

src/components/PullRefresh.tsx

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { useRouter } from 'next/router';
2+
import { TouchEvent, useEffect, useRef, useState } from 'react';
3+
4+
const ease = (distance: number) => {
5+
return distance / 2;
6+
};
7+
8+
const PullRefresh = ({ children }: { children?: JSX.Element }) => {
9+
const [height, setHeight] = useState(0);
10+
const [isRefresh, setIsRefresh] = useState(false);
11+
const [startY, setStartY] = useState(0);
12+
const [shouldRefresh, setShouldRefresh] = useState(false);
13+
const el = useRef<HTMLDivElement>(null);
14+
const route = useRouter();
15+
16+
const handleTouchStart = (e: TouchEvent) => {
17+
const top = e.currentTarget.getBoundingClientRect().top;
18+
setStartY(e.touches[0].pageY - top);
19+
setHeight(0);
20+
setIsRefresh(false);
21+
setShouldRefresh(false);
22+
};
23+
const handleTouchMove = (e: TouchEvent) => {
24+
const touch = e.touches[0];
25+
const moveY = touch.pageY - startY;
26+
const top = e.currentTarget.getBoundingClientRect().top;
27+
28+
if (top === 0 && moveY > 0) {
29+
document.documentElement.style.overflow = 'hidden';
30+
document.body.style.overflow = 'hidden';
31+
e.preventDefault();
32+
setHeight(ease(moveY));
33+
if (ease(moveY) > 80) {
34+
setShouldRefresh(true);
35+
} else {
36+
setShouldRefresh(false);
37+
}
38+
}
39+
};
40+
const handleTouchEnd = () => {
41+
document.documentElement.style.overflow = 'initial';
42+
document.body.style.overflow = 'initial';
43+
if (shouldRefresh) {
44+
setHeight(50);
45+
setIsRefresh(true);
46+
route.reload();
47+
} else {
48+
setHeight(0);
49+
}
50+
setShouldRefresh(false);
51+
};
52+
53+
useEffect(() => {
54+
const elCurrent = el.current;
55+
elCurrent?.addEventListener('touchstart', handleTouchStart as any, {
56+
passive: false,
57+
});
58+
elCurrent?.addEventListener('touchmove', handleTouchMove as any, {
59+
passive: false,
60+
});
61+
elCurrent?.addEventListener('touchend', handleTouchEnd as any, {
62+
passive: false,
63+
});
64+
elCurrent?.addEventListener('touchcancel', handleTouchEnd as any, {
65+
passive: false,
66+
});
67+
68+
return () => {
69+
elCurrent?.removeEventListener('touchstart', handleTouchStart as any);
70+
elCurrent?.removeEventListener('touchmove', handleTouchMove as any);
71+
elCurrent?.removeEventListener('touchend', handleTouchEnd as any);
72+
elCurrent?.removeEventListener('touchcancel', handleTouchEnd as any);
73+
};
74+
});
75+
76+
return (
77+
<div ref={el}>
78+
<div
79+
style={{ height }}
80+
className='flex items-center justify-center text-sm text-gray-800'
81+
>
82+
{shouldRefresh ? '松开刷新' : isRefresh ? '刷新中...' : '下拉刷新'}
83+
</div>
84+
{children}
85+
</div>
86+
);
87+
};
88+
89+
export default PullRefresh;
+11-16
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
1-
import { getOAtuhURL } from '@/services/login';
1+
import { useLoginContext } from '@/hooks/useLoginContext';
2+
3+
import Button from '@/components/buttons/Button';
24

35
const LoginButton = () => {
4-
const handleOAtuhURL = async () => {
5-
try {
6-
const data = await getOAtuhURL();
7-
if (data?.url != undefined) {
8-
window.location.href = data.url;
9-
}
10-
} catch (error) {
11-
console.log('error:' + error);
12-
}
13-
};
6+
const { login } = useLoginContext();
147

158
return (
16-
<button onClick={handleOAtuhURL}>
17-
<span className='inline-flex cursor-pointer items-center rounded-lg px-3 py-2'>
18-
登录
19-
</span>
20-
</button>
9+
<Button
10+
className='font-normal text-gray-500'
11+
variant='ghost'
12+
onClick={login}
13+
>
14+
登录
15+
</Button>
2116
);
2217
};
2318
export default LoginButton;

src/components/buttons/LogoutButton.tsx

+11-21
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,18 @@
1-
import useToken from '@/hooks/useToken';
1+
import { useLoginContext } from '@/hooks/useLoginContext';
22

3-
import { Logout } from '@/services/login';
3+
import Button from '@/components/buttons/Button';
44

5-
import { LoginOutProps } from '@/types/user';
6-
7-
const LogoutButton = ({ updateLoginStatus }: LoginOutProps) => {
8-
const { token, setToken } = useToken();
9-
const handleLogout = async () => {
10-
try {
11-
await Logout({ Authorization: `Bearer ${token}` });
12-
setToken(null);
13-
updateLoginStatus(false);
14-
return true;
15-
} catch (error) {
16-
return false;
17-
}
18-
};
5+
const LogoutButton = () => {
6+
const { logout } = useLoginContext();
197

208
return (
21-
<button onClick={handleLogout}>
22-
<span className='inline-flex items-center rounded-lg px-3 py-2'>
23-
退出
24-
</span>
25-
</button>
9+
<Button
10+
className='font-normal text-gray-500'
11+
variant='ghost'
12+
onClick={logout}
13+
>
14+
退出
15+
</Button>
2616
);
2717
};
2818

src/components/buttons/Periodical.tsx

+14-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { useRouter } from 'next/router';
22
import { useEffect, useState } from 'react';
33

4+
import Button from '@/components/buttons/Button';
5+
46
import { getVolumeNum } from '@/services/volume';
57

68
const PeriodicalButton = () => {
@@ -22,21 +24,23 @@ const PeriodicalButton = () => {
2224
return (
2325
<>
2426
{atPeriodical ? (
25-
<button
27+
<Button
28+
className='font-normal text-gray-500'
29+
variant='ghost'
2630
onClick={() => {
2731
router.push('/');
2832
}}
2933
>
30-
<span className='inline-flex cursor-pointer items-center rounded-lg px-3 py-2'>
31-
首页
32-
</span>
33-
</button>
34+
首页
35+
</Button>
3436
) : (
35-
<button onClick={handlePeriodicalURL}>
36-
<span className='inline-flex cursor-pointer items-center rounded-lg px-3 py-2'>
37-
月刊
38-
</span>
39-
</button>
37+
<Button
38+
className='font-normal text-gray-500'
39+
variant='ghost'
40+
onClick={handlePeriodicalURL}
41+
>
42+
月刊
43+
</Button>
4044
)}
4145
</>
4246
);

src/components/home/Items.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useEffect, useState } from 'react';
55
import useInfiniteScroll from 'react-infinite-scroll-hook';
66
import useSWRInfinite from 'swr/infinite';
77

8+
import Button from '@/components/buttons/Button';
89
import Loading from '@/components/loading/Loading';
910
import { RepoModal } from '@/components/respository/Submit';
1011

@@ -116,13 +117,13 @@ const Items = () => {
116117
<a className={linkClassName('last')}>最近</a>
117118
</Link>
118119

119-
<button
120-
type='button'
120+
<Button
121+
variant='ghost'
121122
onClick={handleTags}
122123
className={labelClassName()}
123124
>
124125
标签
125-
</button>
126+
</Button>
126127

127128
<div className='absolute top-0 right-0 p-2.5 md:hidden'>
128129
<RepoModal>

src/components/layout/Header.tsx

+63-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,68 @@
11
import Image from 'next/image';
2+
import Link from 'next/link';
23
import { useRouter } from 'next/router';
4+
import { useEffect, useState } from 'react';
5+
6+
import { useLoginContext } from '@/hooks/useLoginContext';
7+
8+
import LogoutButton from '@/components/buttons/LogoutButton';
39

410
import LoginButton from '../buttons/LoginButton';
5-
import LogoutButton from '../buttons/LogoutButton';
611
import PeriodicalButton from '../buttons/Periodical';
712
import SearchInput from '../search/SearchInput';
813

9-
import { LoginStatusProps } from '@/types/user';
14+
import { DEFAULT_AVATAR } from '~/constants';
15+
16+
const AvatarWithDropdown = (props: { className?: string }) => {
17+
const [isOpen, setIsOpen] = useState(false);
18+
const { logout } = useLoginContext();
19+
20+
useEffect(() => {
21+
const handleDocumentClick = () => {
22+
setIsOpen(false);
23+
};
24+
25+
document.addEventListener('click', handleDocumentClick);
26+
27+
return () => {
28+
document.removeEventListener('click', handleDocumentClick);
29+
};
30+
}, []);
1031

11-
const Header = ({ loginStatus, updateLoginStatus }: LoginStatusProps) => {
32+
return (
33+
<div className={`${props.className} h-7 w-7`}>
34+
<Image
35+
className='relative overflow-hidden rounded-full'
36+
src={DEFAULT_AVATAR}
37+
alt='头像'
38+
width={28}
39+
height={28}
40+
onClick={(e) => {
41+
e.stopPropagation();
42+
setIsOpen(!isOpen);
43+
}}
44+
/>
45+
<div
46+
className='absolute right-1 mt-2 w-32 rounded border bg-white py-2 shadow-md'
47+
hidden={!isOpen}
48+
>
49+
<div className='absolute -top-1.5 right-3 h-3 w-3 rotate-45 border-l border-t bg-white'></div>
50+
<Link href='/' className='block'>
51+
<div className='block px-4 leading-8 active:bg-gray-100'>
52+
我的首页
53+
</div>
54+
</Link>
55+
<div className='px-4 leading-8 active:bg-gray-100' onClick={logout}>
56+
退出
57+
</div>
58+
</div>
59+
</div>
60+
);
61+
};
62+
63+
const Header = () => {
1264
const router = useRouter();
65+
const { isLogin } = useLoginContext();
1366

1467
const showMessage = () => {
1568
router.push('/');
@@ -34,14 +87,17 @@ const Header = ({ loginStatus, updateLoginStatus }: LoginStatusProps) => {
3487
<PeriodicalButton></PeriodicalButton>
3588
</li>
3689
<>
37-
{!loginStatus ? (
90+
{!isLogin ? (
3891
<li className='block md:hidden'>
3992
<LoginButton></LoginButton>
4093
</li>
4194
) : (
42-
<li className='hidden md:block '>
43-
<LogoutButton updateLoginStatus={updateLoginStatus} />
44-
</li>
95+
<>
96+
<li className='hidden md:block '>
97+
<LogoutButton />
98+
</li>
99+
<AvatarWithDropdown className='md:hidden' />
100+
</>
45101
)}
46102
</>
47103
</ul>

src/components/layout/Layout.tsx

+2-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { useRouter } from 'next/router';
22
import * as React from 'react';
3-
import { useState } from 'react';
43

54
import Header from '@/components/layout/Header';
65
import ToTop from '@/components/toTop/ToTop';
@@ -9,23 +8,15 @@ import IndexSide from '../side/IndexSide';
98

109
export default function Layout({ children }: { children: React.ReactNode }) {
1110
// Put Header or Footer Here
12-
const [loginStatus, setLoginStatus] = useState<boolean>(false);
1311
const router = useRouter();
1412
const showIndexSide = React.useMemo<boolean>(() => {
1513
const { pathname } = router;
1614
return pathname !== '/periodical/volume/[id]';
1715
}, [router]);
1816

19-
const updateLoginStatus = (value: boolean) => {
20-
setLoginStatus(value);
21-
};
22-
2317
return (
2418
<>
25-
<Header
26-
loginStatus={loginStatus}
27-
updateLoginStatus={updateLoginStatus}
28-
></Header>
19+
<Header></Header>
2920
<main className='container mx-auto px-0 pt-14 xl:px-40'>
3021
{showIndexSide ? (
3122
<div className='flex shrink grow flex-row sm:border-l sm:dark:border-slate-600 md:border-none'>
@@ -34,10 +25,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
3425
</div>
3526

3627
<div className='relative hidden w-3/12 shrink-0 md:block md:grow-0'>
37-
<IndexSide
38-
loginStatus={loginStatus}
39-
updateLoginStatus={updateLoginStatus}
40-
></IndexSide>
28+
<IndexSide></IndexSide>
4129
<ToTop />
4230
</div>
4331
</div>

0 commit comments

Comments
 (0)