This commit is contained in:
2025-10-27 01:40:38 +08:00
parent 896390e8eb
commit 0f04bbe1f9
57 changed files with 7576 additions and 870 deletions

View File

@@ -0,0 +1,102 @@
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { Layout, MainLayout } from './layout/UserLayout';
import { useUserStore } from './store';
import { message } from '@/modules/message';
export const Info = () => {
return (
<Layout>
<MainLayout className='bg-yellow-50'>
<ProfileForm />
</MainLayout>
</Layout>
);
};
export const ProfileForm: React.FC = () => {
const [nickname, setNickname] = useState('');
const [name, setName] = useState('');
const [avatar, setAvatar] = useState<string | null>(null);
const userStore = useUserStore();
const handleAvatarChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
const file = e.target.files[0];
// 如果文件大于 2MB提示用户
if (file.size > 2 * 1024 * 1024) {
message.error('文件大小不能超过 2MB');
return;
}
const reader = new FileReader();
reader.onload = () => {
setAvatar(reader.result as string);
};
reader.readAsDataURL(file);
}
};
const handleSubmit = () => {
// alert(`昵称: ${nickname}, 姓名: ${name}`)
userStore.updateUser({
nickname,
data: {
personalname: name,
},
avatar,
});
};
useLayoutEffect(() => {
userStore.getUpdateUser();
}, []);
useEffect(() => {
if (userStore.data) {
setNickname(userStore.data.nickname);
setName(userStore.data?.data?.personalname);
setAvatar(userStore.data.avatar);
}
}, [userStore.data]);
return (
<div className='w-full h-full mx-auto p-6 rounded-lg'>
<h2 className='text-center text-[#F39800] text-2xl font-bold mb-2'></h2>
<p className='text-center text-yellow-400 mb-6'></p>
{/* Avatar Section */}
<div className='text-center mb-6'>
<label className='block'>
<div className='w-24 h-24 my-2 mx-auto rounded-full bg-yellow-200 flex items-center justify-center text-4xl text-yellow-500 cursor-pointer'>
{avatar ? <img src={avatar} alt='Avatar' className='rounded-full w-full h-full object-cover' /> : <span>👤</span>}
</div>
<p className='text-sm text-gray-500 mt-2'></p>
<input type='file' accept='image/*' className='hidden' onChange={handleAvatarChange} />
</label>
</div>
{/* Form Fields */}
<div className='mb-4'>
<label className='block text-[#F39800] mb-2'> *</label>
<input
type='text'
placeholder='请设置您的昵称'
value={nickname}
onChange={(e) => setNickname(e.target.value)}
className='w-full border-[#FBBF24] rounded-lg p-2 border focus:outline-none focus:ring-2 focus:ring-[#F39800]'
/>
</div>
<div className='mb-6'>
<label className='block text-[#F39800] mb-2'> *</label>
<input
type='text'
placeholder='请输入您的姓名'
value={name}
onChange={(e) => setName(e.target.value)}
className='w-full border-[#FBBF24] rounded-lg p-2 border focus:outline-none focus:ring-2 focus:ring-[#F39800]'
/>
</div>
{/* Submit Button */}
<button onClick={handleSubmit} className='w-full py-2 bg-[#F39800] text-white rounded hover:bg-orange-600'>
</button>
</div>
);
};

View File

@@ -0,0 +1,13 @@
@media (min-width: 1000px) and (max-width: 1440px) {
.login-main {
/* background-color: red !important; */
scale: 0.8;
}
}
@media (min-width: 1500px) and (max-width: 2000px) {
.login-main {
/* background-color: red !important; */
scale: 1.2;
}
}

View File

@@ -0,0 +1,11 @@
import './index.css';
import { Login } from './login';
import { ToastContainer } from 'react-toastify';
export const App = () => {
return (
<>
<Login />
<ToastContainer position='top-center' autoClose={5000} draggable />
</>
);
};

View File

@@ -0,0 +1,80 @@
import clsx from 'clsx';
import { Beian } from '@/modules/beian/beian';
import { useUserStore } from '../store';
import { useShallow } from 'zustand/shallow';
import { useEffect } from 'react';
type Props = {
children?: React.ReactNode;
className?: string;
};
export const Layout = (props: Props) => {
return (
<div
className={clsx(
'w-full h-full sm:bg-amber-100 flex sm:items-center sm:justify-center justify-normal items-start relative overflow-hidden',
props?.className,
)}>
<div className='w-full h-full overflow-scroll sm:overflow-hidden sm:scrollbar flex sm:items-center sm:justify-center'>{props.children}</div>
</div>
);
};
export const MainLayout = (props: Props) => {
const config = useUserStore((state) => state.config);
return (
<div
className={clsx(
'login-main w-[450px] min-h-[660px] bg-white mt-10 sm:mt-0 sm:shadow-lg rounded-md flex items-center flex-col relative ',
props?.className,
)}>
{props.children}
<p className='mt-5 text-xs text-center text-[#e69c36]'>
<span className='font-medium text-[#e69c36]'></span> <span className='font-medium text-[#e69c36]'></span>
</p>
<Beian className=' mb-4' text={config?.beian} />
</div>
);
};
export const LoginWrapper = (props: Props) => {
const config = useUserStore((state) => state.config);
const loginWay = config?.loginWay || ['account'];
const store = useUserStore(
useShallow((state) => {
return {
loginByWechat: state.loginByWechat,
};
}),
);
const checkWechat = () => {
const url = new URL(window.location.href);
const code = url.searchParams.get('code');
const state = url.searchParams.get('state');
if (code && state) {
store.loginByWechat(code);
}
};
useEffect(() => {
checkWechat();
}, []);
return (
<Layout>
<MainLayout className=''>
<div
className='mt-4'
style={{
width: '90px',
height: '90px',
overflow: 'hidden',
...config?.logoStyle,
}}>
<img src={config?.logo} />
</div>
<div className='mt-4 text-[#F39800] font-bold text-xl'></div>
{loginWay.length > 1 && <div className='text-sm text-yellow-400 mt-2'></div>}
<div>{props.children}</div>
</MainLayout>
</Layout>
);
};

View File

@@ -0,0 +1,342 @@
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
import { useUserStore } from '../store';
import { setWxerwma, wxId } from '@/wx/ws-login.ts';
import { checkCaptcha } from '@/wx/tencent-captcha.ts';
import { dynimicLoadTcapTcha } from '@/wx/load-js.ts';
import { message } from '@/modules/message';
import { useShallow } from 'zustand/react/shallow';
import { WeChatMpLogin } from './modules/WeChatMpLogin';
const WeChatLogin: React.FC = () => {
const userStore = useUserStore(
useShallow((state) => {
return { config: state.config! };
}),
);
useEffect(() => {
setWxerwma({
...userStore.config.wxLogin,
});
}, []);
return <div id={wxId} className='max-w-sm mx-auto bg-white rounded-lg text-center'></div>;
};
type VerificationCodeInputProps = {
onGetCode: () => void;
verificationCode: string;
setVerificationCode: (value: string) => void;
};
const VerificationCodeInput = forwardRef(({ onGetCode, verificationCode, setVerificationCode }: VerificationCodeInputProps, ref) => {
// const [verificationCode, setVerificationCode] = useState('')
const [isCounting, setIsCounting] = useState(false);
const [countdown, setCountdown] = useState(60);
useImperativeHandle(ref, () => ({
isCounting,
setIsCounting,
setCountdown,
}));
const handleGetCode = () => {
if (!isCounting) {
// setIsCounting(true)
// setCountdown(60)
onGetCode(); // 调用父组件传入的获取验证码逻辑
}
};
useEffect(() => {
let timer;
if (isCounting) {
timer = setInterval(() => {
setCountdown((prev) => {
if (prev <= 1) {
setIsCounting(false);
clearInterval(timer);
return 60;
}
return prev - 1;
});
}, 1000);
}
return () => clearInterval(timer);
}, [isCounting]);
return (
<div className='mb-4 items-center'>
<label className='block text-[#F39800] py-1 mb-1'></label>
<div className='flex'>
<input
type='text'
className='border-[#FBBF24] rounded-lg p-2 border focus:outline-none focus:ring-2 focus:ring-[#F39800]'
placeholder='请输入验证码'
value={verificationCode}
onChange={(e) => setVerificationCode(e.target.value)}
/>
<button
className={`ml-2 px-4 py-2 w-[120px] rounded-md text-white ${isCounting ? 'bg-gray-400 cursor-not-allowed' : 'bg-[#F39800] hover:bg-yellow-400'}`}
onClick={handleGetCode}
disabled={isCounting}>
{isCounting ? `${countdown}s 后重试` : '获取验证码'}
</button>
</div>
</div>
);
});
function PhoneNumberValidation({ phoneNumber, setPhoneNumber }) {
// const [phoneNumber, setPhoneNumber] = useState('')
const [errorMessage, setErrorMessage] = useState('');
const validatePhoneNumber = (number) => {
// 假设手机号的格式为中国的11位数字
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(number)) {
setErrorMessage('请输入有效的手机号');
} else {
setErrorMessage('');
}
};
const handleChange = (e) => {
const value = e.target.value;
setPhoneNumber(value);
validatePhoneNumber(value);
};
return (
<div className=''>
<label className='block text-[#F39800] py-1 mb-1'></label>
<input
type='text'
className={`w-full border rounded-lg p-2 focus:outline-none focus:ring-2 ${
errorMessage ? 'border-red-500 focus:ring-red-500' : 'border-[#FBBF24] focus:ring-[#F39800]'
}`}
placeholder='请输入手机号'
value={phoneNumber}
onChange={handleChange}
/>
{errorMessage && <p className='text-red-500 text-xs mt-1'>{errorMessage}</p>}
{!errorMessage && <p className='text-gray-500 text-xs mt-1 invisible'>11</p>}
</div>
);
}
function AccountLogin({ accountName, setAccountName, password, setPassword }) {
const [errorMessage, setErrorMessage] = useState('');
const validateAccountName = (name) => {
if (name.length < 3) {
setErrorMessage('账户名至少需要3个字符');
} else {
setErrorMessage('');
}
};
const handleAccountChange = (e) => {
const value = e.target.value;
setAccountName(value);
validateAccountName(value);
};
const handlePasswordChange = (e) => {
setPassword(e.target.value);
};
const onTestAccountLogin = () => {
setAccountName('demo');
setPassword('123456');
};
return (
<div className='flex flex-col gap-1'>
<label className='block text-[#F39800] py-1 mb-1'></label>
<input
type='text'
className={`w-full border rounded-lg p-2 focus:outline-none focus:ring-2 ${
errorMessage ? 'border-red-500 focus:ring-red-500' : 'border-[#FBBF24] focus:ring-[#F39800]'
}`}
placeholder='请输入账户名'
value={accountName}
onChange={handleAccountChange}
/>
{errorMessage && <p className='text-red-500 text-xs mt-1'>{errorMessage}</p>}
{!errorMessage && <p className='text-gray-500 text-xs mt-1 invisible'>3</p>}
<label className='block text-[#F39800] py-1 mb-1 mt-2'></label>
<input
type='password'
className='w-full border-[#FBBF24] rounded-lg p-2 border focus:outline-none focus:ring-2 focus:ring-[#F39800]'
placeholder='请输入密码'
value={password}
onChange={handlePasswordChange}
/>
<div
className='text-xs text-gray-400/60 mt-2 hover:text-gray-500 cursor-pointer'
onClick={() => {
onTestAccountLogin();
}}>
</div>
</div>
);
}
const LoginForm: React.FC = () => {
const [phoneNumber, setPhoneNumber] = useState('');
const [verificationCode, setVerificationCode] = useState('');
const [accountName, setAccountName] = useState('');
const [password, setPassword] = useState('');
const [activeTab, setActiveTab] = useState<'phone' | 'wechat' | 'wechat-mp' | 'account'>('phone');
const userStore = useUserStore(
useShallow((state) => {
return {
config: state.config! || {},
getCode: state.getCode,
login: state.login,
loginByAccount: state.loginByAccount,
};
}),
);
const ref = useRef<any>(null);
const handleGetCode = async () => {
const loaded = await dynimicLoadTcapTcha();
if (!loaded) {
message.error('验证码加载失败');
return;
}
const captcha = await checkCaptcha(userStore.config.captchaAppId);
if (captcha.ret !== 0) {
message.error('验证码发送失败');
return;
}
ref.current.setIsCounting(true);
ref.current.setCountdown(60);
userStore.getCode(phoneNumber, captcha);
};
useEffect(() => {
dynimicLoadTcapTcha();
if (userStore.config.loginWay?.length > 0) {
setActiveTab(userStore.config.loginWay[0]);
}
}, [userStore.config.loginWay]);
const handleLogin = () => {
// alert(`登录中:手机号: ${phoneNumber}, 验证码: ${verificationCode}`)
userStore.login(phoneNumber, verificationCode);
};
const inLoginWay = (way: string) => {
const loginWay = userStore.config?.loginWay || [];
return loginWay.includes(way);
};
const handleAccountLogin = () => {
if (!accountName || !password) {
message.error('请输入账户名和密码');
return;
}
userStore.loginByAccount(accountName, password);
};
useListenEnter({ active: activeTab === 'phone', handleLogin });
useListenEnter({ active: activeTab === 'account', handleLogin: handleAccountLogin });
const tab = useMemo(() => {
const phoneCom = (
<button
key='phone'
className={`flex-1 py-2 font-medium ${activeTab === 'phone' ? 'border-[#F39800] text-[#F39800] border-b-2' : ''}`}
onClick={() => setActiveTab('phone')}>
</button>
);
const wechatCom = (
<button
key='wechat'
className={`flex-1 py-2 font-medium ${activeTab === 'wechat' ? 'border-[#F39800] text-[#F39800] border-b-2' : ''}`}
onClick={() => setActiveTab('wechat')}>
</button>
);
const wechatMpCom = (
<button
key='wechat-mp'
className={`flex-1 py-2 font-medium ${activeTab === 'wechat-mp' ? 'border-[#F39800] text-[#F39800] border-b-2' : ''}`}
onClick={() => setActiveTab('wechat-mp')}>
</button>
);
const accountCom = (
<button
key='account'
className={`flex-1 py-2 font-medium ${activeTab === 'account' ? 'border-[#F39800] text-[#F39800] border-b-2' : ''}`}
onClick={() => setActiveTab('account')}>
</button>
);
const coms: React.ReactNode[] = [];
for (const way of userStore.config.loginWay) {
if (way === 'phone') {
coms.push(phoneCom);
} else if (way === 'wechat') {
coms.push(wechatCom);
} else if (way === 'account') {
coms.push(accountCom);
} else if (way === 'wechat-mp') {
coms.push(wechatMpCom);
}
}
return coms;
}, [userStore.config.loginWay, activeTab]);
return (
<div className='max-w-sm mx-auto p-6 bg-white rounded-lg flex flex-col items-center justify-center'>
{/* Tabs */}
<div className='flex text-gray-400 min-w-[360px]'>{tab}</div>
<div className='mt-4 min-h-[300px] w-full relative'>
{/* Phone Login Form */}
{activeTab === 'phone' && inLoginWay('phone') && (
<div className='mt-4 pt-4 '>
<PhoneNumberValidation phoneNumber={phoneNumber} setPhoneNumber={setPhoneNumber} />
<VerificationCodeInput ref={ref} onGetCode={handleGetCode} verificationCode={verificationCode} setVerificationCode={setVerificationCode} />
<button className='w-full mt-3 py-2 bg-[#F39800] text-white rounded-lg hover:bg-yellow-400' onClick={handleLogin}>
</button>
</div>
)}
{/* WeChat Login Placeholder */}
{activeTab === 'wechat' && inLoginWay('wechat') && (
<div className='-mt-2 w-[310px] ml-[12px] flex flex-col justify-center text-center text-gray-500 absolute top-0 left-0 z-index-10'>
<WeChatLogin />
</div>
)}
{activeTab === 'wechat-mp' && inLoginWay('wechat-mp') && (
<div className='mt-2 w-[310px] ml-[12px] flex flex-col justify-center text-center '>
<WeChatMpLogin />
</div>
)}
{activeTab === 'account' && inLoginWay('account') && (
<div className='mt-4 pt-4 w-full '>
<AccountLogin accountName={accountName} setAccountName={setAccountName} password={password} setPassword={setPassword} />
<button className='w-full mt-3 py-2 bg-[#F39800] text-white rounded-lg hover:bg-yellow-400' onClick={handleAccountLogin}>
</button>
</div>
)}
</div>
</div>
);
};
export default LoginForm;
export const useListenEnter = (opts?: { active: boolean; handleLogin: () => void }) => {
useEffect(() => {
if (!opts?.active) {
return;
}
const handleEnter = (e: KeyboardEvent) => {
if (e.key === 'Enter') {
opts?.handleLogin?.();
}
};
window.addEventListener('keydown', handleEnter);
return () => {
window.removeEventListener('keydown', handleEnter);
};
}, [opts?.active, opts?.handleLogin]);
};

View File

@@ -0,0 +1,49 @@
import { useEffect, useLayoutEffect, useState } from 'react';
import { Layout, LoginWrapper, MainLayout } from '../layout/UserLayout';
import LoginForm from './Login';
import { useUserStore } from '../store';
import { useShallow } from 'zustand/react/shallow';
import { Suspense } from 'react';
export const Login = () => {
const userStore = useUserStore(
useShallow((state) => {
return {
config: state.config,
setConfig: state.setConfig,
loadedConfig: state.loadedConfig,
setLoadedConfig: state.setLoadedConfig,
queryCheck: state.queryCheck,
};
}),
);
useLayoutEffect(() => {
fetchConfig();
userStore.queryCheck();
}, []);
const fetchConfig = async () => {
try {
const res = await fetch('./config.js');
const configScript = await res.text();
const blob = new Blob([configScript], { type: 'application/javascript' });
const moduleUrl = URL.createObjectURL(blob);
const module = await import(/* @vite-ignore */ moduleUrl);
URL.revokeObjectURL(moduleUrl); // Clean up the object URL
userStore.setConfig(module.config);
} catch (error) {
console.error('Failed to load config:', error);
}
};
return (
<Suspense fallback={<div></div>}>
{userStore.loadedConfig ? (
<LoginWrapper>
<LoginForm />
</LoginWrapper>
) : (
<div>Loading...</div>
)}
</Suspense>
);
};

View File

@@ -0,0 +1,80 @@
import { useUserStore, Config } from '@/user/store';
import { useShallow } from 'zustand/shallow';
import QRCode, { QRCodeToDataURLOptions } from 'qrcode';
import { message } from '@/modules/message';
import { useEffect, useRef, useState } from 'react';
import { query, queryLogin } from '@/modules/query';
const useCreateLoginQRCode = (config: Config) => {
const url = new URL(window.location.href);
const redirect = url.searchParams.get('redirect');
const loginSuccessUrl = config.loginSuccess;
const redirectURL = redirect ? decodeURIComponent(redirect) : loginSuccessUrl;
var opts: QRCodeToDataURLOptions = {
errorCorrectionLevel: 'H',
type: 'image/jpeg',
margin: 1,
width: 300,
};
const [state, setState] = useState('');
let timer = useRef<any>(null);
const loginUrl = config?.wxmpLogin?.loginUrl || '';
if (!loginUrl) {
message.error('没有配置微信登陆配置');
return;
}
const createQrcode = async (state: string) => {
const text = `${loginUrl}?state=${state}`;
var img = document.getElementById('qrcode')! as HTMLCanvasElement;
// img.src = url;
const res = await QRCode.toDataURL(img, text, opts);
};
const checkLogin = async (state: string) => {
const res = await fetch(`/api/router?path=wx&key=checkLogin&state=${state}`).then((res) => res.json());
if (res.code === 200) {
console.log(res);
const token = res.data;
if (token) {
localStorage.setItem('token', token.accessToken);
await queryLogin.setLoginToken(token);
}
setTimeout(() => {
window.location.href = redirectURL;
}, 1000);
} else {
timer.current = setTimeout(() => {
checkLogin(state);
}, 2000);
}
};
useEffect(() => {
// 随机生成一个state
const state = Math.random().toString(36).substring(2, 15);
createQrcode(state);
checkLogin(state);
const timer2 = setInterval(() => {
const state = Math.random().toString(36).substring(2, 15);
clearTimeout(timer.current); // 清除定时器
createQrcode(state); // 90秒后更新二维码
checkLogin(state);
}, 90000);
return () => {
clearInterval(timer.current);
clearInterval(timer2);
};
}, []);
return { createQrcode };
};
export const WeChatMpLogin = () => {
const userStore = useUserStore(
useShallow((state) => {
return { config: state.config! };
}),
);
useCreateLoginQRCode(userStore.config);
return (
<div>
<canvas id='qrcode' width='300' height='300'></canvas>
</div>
);
};

View File

@@ -0,0 +1,21 @@
// <script src="https://turing.captcha.qcloud.com/TCaptcha.js"></script>
export const dynimicLoadTcapTcha = async (): Promise<boolean> => {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
script.type = 'text/javascript'
script.id = 'tencent-captcha'
if (document.getElementById('tencent-captcha')) {
resolve(true)
return
}
script.src = 'https://turing.captcha.qcloud.com/TCaptcha.js'
script.onload = () => {
resolve(true)
}
script.onerror = (error) => {
reject(error)
}
document.body.appendChild(script)
})
}

View File

@@ -0,0 +1,251 @@
import { query, queryLogin } from '@/modules/query';
import { TencentCaptcha } from '@/wx/tencent-captcha.ts';
import { message } from '@/modules/message';
import { create } from 'zustand';
export type Config = {
loginWay: any[];
wxLogin: {
appid: string;
redirect_uri: string;
};
wxmpLogin: {
loginUrl?: string; // 微信公众号的网页授权登陆
appid?: string; // 微信公众号的appid
redirect_uri?: string; // 微信公众号的网页授权登陆
};
captchaAppId: string;
loginSuccess: string;
loginSuccessIsNew: string;
logo: string;
logoStyle: {
borderRadius: string;
width?: string;
height?: string;
};
beian: string;
};
export const inIframeToDo = async (config?: Config) => {
const isInIframe = window !== window.parent && !window.opener;
if (isInIframe && config) {
try {
// 检查是否同源
const isSameOrigin = (() => {
try {
// 尝试访问父窗口的 location.origin如果能访问则是同源
return window.parent.location.origin === window.location.origin;
} catch (e) {
// 如果出现跨域错误,则不是同源
return false;
}
})();
const isLocalhost = window.location.hostname === 'localhost';
const isKevisual = window.location.hostname.includes('kevisual');
if (isSameOrigin || isLocalhost || isKevisual) {
// 同源情况下,可以直接向父窗口传递配置
window.parent.postMessage(
{
type: 'kevisual-login',
data: config,
},
window.location.origin,
);
console.log('已向父窗口传递登录配置信息');
}
} catch (error) {
console.error('向父窗口传递配置信息失败:', error);
}
}
return isInIframe;
};
export const redirectToSuccess = async (config: Config) => {
const href = location.href;
const url = new URL(href);
const check = await inIframeToDo(config);
if (check) {
return;
}
const redirect = url.searchParams.get('redirect');
if (redirect) {
const href = decodeURIComponent(redirect);
window.open(href, '_self');
}
await new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, 1000);
});
if (config?.loginSuccess) {
location.href = config?.loginSuccess;
} else {
location.href = '/';
}
};
type UserStore = {
isAuthenticated: boolean;
qrCodeUrl: string;
checkAuthStatus: () => void;
getCode: (phone: string, captcha: TencentCaptcha) => void;
/**
* 手机号登录
* @param phone 手机号
* @param code 验证码
*/
login: (phone: string, code: string) => void;
updateUser: (data: any, opts?: { needRedirect?: boolean }) => void;
getUpdateUser: () => void;
data: any;
setData: (data: any) => void;
config: Config | null;
setConfig: (config: any) => void;
loadedConfig: boolean;
setLoadedConfig: (loadedConfig: boolean) => void;
/**
* 账号密码登录
* @param username 账号
* @param password 密码
*/
loginByAccount: (username: string, password: string) => void;
/**
* 检查是否需要跳转, 插件登陆
*/
queryCheck: () => void;
loginByWechat: (code: string) => void;
};
export const useUserStore = create<UserStore>((set, get) => ({
isAuthenticated: false,
qrCodeUrl: '',
checkAuthStatus: () => {
//
},
getCode: async (phone, captcha) => {
const res = await query.post({
path: 'sms',
key: 'send',
data: {
phone,
captcha,
},
});
if (res.code === 200) {
// do something
message.success('验证码发送成功');
} else {
message.error(res.message || '验证码发送失败');
}
},
login: async (phone, code) => {
const config = get().config!;
const res = await query.post({
path: 'sms',
key: 'login',
data: {
phone,
code,
},
});
if (res.code === 200) {
message.success('登录成功');
set({ isAuthenticated: true });
redirectToSuccess(config);
} else {
message.error(res.message || '登录失败');
}
},
updateUser: async (data, opts) => {
const config = get().config!;
const res = await query.post({
path: 'user',
key: 'updateInfo',
data,
});
if (res.code === 200) {
message.success('更新成功');
if (opts?.needRedirect) {
setTimeout(() => {
location.href = config?.loginSuccess;
}, 1000);
}
} else {
message.error(res.message || '更新失败');
}
},
getUpdateUser: async () => {
const res = await query.post({
path: 'user',
key: 'getUpdateInfo',
});
if (res.code === 200) {
set({ data: res.data });
} else {
message.error(res.message || '获取用户信息失败');
}
},
data: {},
setData: (data) => set({ data }),
loadedConfig: false,
setLoadedConfig: (loadedConfig) => set({ loadedConfig }),
config: null,
setConfig: (config) => set({ config, loadedConfig: true }),
loginByAccount: async (username, password) => {
const config = get().config!;
const isEmail = username.includes('@');
const data: any = { password };
if (isEmail) {
data.email = username;
} else {
data.username = username;
}
const res = await queryLogin.login(data);
if (res.code === 200) {
message.success('登录成功');
set({ isAuthenticated: true });
redirectToSuccess(config);
} else {
message.error(res.message || '登录失败');
}
},
queryCheck: async () => {
// const
const userCheck = 'user-check';
const url = new URL(location.href);
const redirect = url.searchParams.get('redirect');
const redirectUrl = redirect ? decodeURIComponent(redirect) : '';
const checkKey = url.searchParams.get(userCheck);
if (redirect && checkKey) {
// 通过refresh_token 刷新token
const me = await queryLogin.getMe();
if (me.code === 200) {
message.success('登录插件中...');
const token = await queryLogin.cacheStore.getAccessToken();
const newRedirectUrl = new URL(redirectUrl);
newRedirectUrl.searchParams.set('token', token + '');
setTimeout(() => {
window.open(newRedirectUrl.toString(), '_blank');
}, 2000);
return;
}
// 刷新token失败登陆页自己跳转
}
console.log('checkKey', checkKey, redirectUrl);
},
loginByWechat: async (code) => {
const config = get().config!;
if (!code) {
message.error('code is required');
return;
}
const res = await queryLogin.loginByWechat({ code });
if (res.code === 200) {
message.success('登录成功');
redirectToSuccess(config);
} else {
message.error(res.message || '登录失败');
}
},
}));