commit
dd1cb26063
After Width: | Height: | Size: 2.2 KiB |
@ -0,0 +1,21 @@
|
||||
export default function Footer() {
|
||||
return (
|
||||
<div className="absolute bottom-[30px] left-0 right-0 text-[13px] leading-[18px] text-[rgba(0,0,0,0.4)] text-center">
|
||||
注册登录即代表同意
|
||||
<a
|
||||
className="text-[#000]"
|
||||
href={`${process.env.NEXT_PUBLIC_MOBILE_HOST}/agreement/registrationAgreement.html`}
|
||||
target="_blank"
|
||||
>
|
||||
《注册协议》
|
||||
</a>
|
||||
<a
|
||||
className="text-[#000]"
|
||||
href={`${process.env.NEXT_PUBLIC_MOBILE_HOST}/agreement/privacyPolicy.html`}
|
||||
target="_blank"
|
||||
>
|
||||
《隐私政策》
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useContext } from 'react';
|
||||
|
||||
import { Dialog, DialogContent, DialogTrigger, DialogClose } from '@/components/ui/dialog';
|
||||
|
||||
import Footer from './Footer';
|
||||
import { LoginContext } from './LoginContext';
|
||||
import LoginPhone from './LoginPhone';
|
||||
import LoginQrcode from './LoginQrcode';
|
||||
import Tab from './Tab';
|
||||
|
||||
import { IconClose } from '@/components';
|
||||
|
||||
export default function Login() {
|
||||
const { state, dispatch } = useContext(LoginContext);
|
||||
const [currentTab, setCurrent] = useState<string>('qrCode');
|
||||
|
||||
return (
|
||||
<Dialog open={state.showLogin} onOpenChange={(open) => dispatch({ type: 'SHOW_LOGIN', payload: open })}>
|
||||
<DialogTrigger asChild>
|
||||
<button
|
||||
id="header_login_button_for_gobal"
|
||||
className="w-[74px] h-[36px] ml-[40px] border-[#000] border-[1.5px] rounded-[60px] text-[17px] hover:bg-brand hover:border-brand hover:text-[#fff] "
|
||||
>
|
||||
登录
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className="w-420px h-450px px-40px bg-#f2f3f7" onPointerDownOutside={(e) => e.preventDefault()}>
|
||||
<Tab value={currentTab} changeTab={(value: string) => setCurrent(value)} />
|
||||
|
||||
<DialogClose className="absolute top-[24px] right-[24px] w-24px h-24px cursor-pointer outline-none">
|
||||
<IconClose />
|
||||
</DialogClose>
|
||||
|
||||
<div>
|
||||
{currentTab === 'qrCode' && <LoginQrcode />}
|
||||
{currentTab === 'phone' && <LoginPhone />}
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
|
||||
import { useToast } from '@/components/ui/use-toast';
|
||||
|
||||
import { saveToken, useLoginRedirect } from '@/hooks';
|
||||
import { apiGetUUID, apiCheckQr, apiGetLoginQr } from '@/services';
|
||||
import { useUserStore } from '@/store';
|
||||
|
||||
export default function LoginQrcode() {
|
||||
const redirect = useLoginRedirect();
|
||||
const router = useRouter();
|
||||
const uuidRef = useRef<string>('');
|
||||
const { toast } = useToast();
|
||||
|
||||
const [qrCodeState, setQrCodeState] = useState<0 | 1 | 2>(0); // 0-未扫码 1-已扫码 2-已过期
|
||||
const [qrCodeValue, setQrCodeValue] = useState<string>('');
|
||||
const intervalIdRef = useRef<number | null>(null);
|
||||
|
||||
const { setShowLogin, getUserInfo } = useUserStore(
|
||||
useShallow((state) => ({
|
||||
setShowLogin: state.setShowLogin,
|
||||
getUserInfo: state.getUserInfo,
|
||||
})),
|
||||
);
|
||||
|
||||
const getUUID = async () => {
|
||||
const result = await apiGetUUID();
|
||||
|
||||
if (result.code === 200) {
|
||||
uuidRef.current = result.data;
|
||||
getQrcode(result.data);
|
||||
return result.data;
|
||||
}
|
||||
};
|
||||
|
||||
const getQrcode = async (id: string) => {
|
||||
const res = await apiGetLoginQr(id);
|
||||
if (res.code === 200) {
|
||||
setQrCodeState(0);
|
||||
setQrCodeValue(res.data);
|
||||
handleCountDown();
|
||||
}
|
||||
};
|
||||
|
||||
const handleCountDown = () => {
|
||||
intervalIdRef.current = window.setInterval(() => {
|
||||
checkLoginStatus();
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const handleResetQrcode = () => {
|
||||
setQrCodeValue('');
|
||||
setQrCodeState(0);
|
||||
uuidRef.current = '';
|
||||
|
||||
getUUID();
|
||||
};
|
||||
|
||||
const checkLoginStatus = async () => {
|
||||
const res = await apiCheckQr(uuidRef.current);
|
||||
if (res.code === 10000) {
|
||||
// 过期
|
||||
setQrCodeState(2);
|
||||
intervalIdRef.current !== null && window.clearInterval(intervalIdRef.current);
|
||||
} else if (res.code !== 200) {
|
||||
// 请求出错
|
||||
uuidRef.current = '';
|
||||
setQrCodeValue('');
|
||||
uuidRef.current = '';
|
||||
return;
|
||||
} else if (res.data === '0') {
|
||||
// 未扫码
|
||||
return;
|
||||
} else if (res.data === '1') {
|
||||
setQrCodeState(1);
|
||||
} else {
|
||||
// 扫码成功
|
||||
saveToken(res.data);
|
||||
setShowLogin(false);
|
||||
await getUserInfo();
|
||||
toast({
|
||||
description: '登录成功',
|
||||
duration: 1500,
|
||||
type: 'background',
|
||||
});
|
||||
if (redirect) router.replace(redirect);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getUUID();
|
||||
|
||||
return () => {
|
||||
if (intervalIdRef.current !== null) {
|
||||
window.clearInterval(intervalIdRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center px-40px bg-#f2f3f7">
|
||||
{/* title */}
|
||||
<div className="mt-9px text-13px leading-18px text-#000/70 text-center">通过雀乐APP扫码登录</div>
|
||||
|
||||
<div className="flex items-center justify-center w-200px h-200px rounded-6px mt-15px bg-#fff overflow-hidden shadow-black-[0px_8px_30px_0px] ">
|
||||
{!qrCodeValue && <div>加载中...</div>}
|
||||
|
||||
{!!qrCodeValue && qrCodeState !== 2 && (
|
||||
<QRCodeSVG
|
||||
className="w-90% h-90% rounded-6px overflow-hidden"
|
||||
size={200}
|
||||
value={qrCodeValue}
|
||||
imageSettings={{ src: '/img/logo_qrcode.svg', height: 32, width: 32, excavate: true }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!!qrCodeValue && qrCodeState === 2 && (
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div>二维码已过期</div>
|
||||
<div className="cursor-pointer mt-5px text-blue" onClick={handleResetQrcode}>
|
||||
重新获取
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
export default function Tab({
|
||||
className,
|
||||
value,
|
||||
changeTab,
|
||||
}: {
|
||||
className?: string;
|
||||
value: string;
|
||||
changeTab: (value: string) => void;
|
||||
}) {
|
||||
const tabs = [
|
||||
{ name: 'qrCode', label: '扫码登录' },
|
||||
{ name: 'phone', label: '手机登录' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={`flex justify-center gap-24px w-full ${className}`}>
|
||||
{tabs.map((tab) => {
|
||||
return (
|
||||
<div
|
||||
className={`text-20px leading-28px font-600 cursor-pointer text-#000/40 ${tab.name === value && 'text-#000/95'}`}
|
||||
key={tab.name}
|
||||
onClick={() => changeTab(tab.name)}
|
||||
>
|
||||
{tab.label}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
declare namespace Login {
|
||||
export interface Tabs {
|
||||
name: string;
|
||||
label: string;
|
||||
}
|
||||
}
|
Loading…
Reference in new issue