返回 AiToEarn
qrcodeLogin.tsx
根目录 / project / aitoearn-electron / src / views / login / components / qrcodeLogin.tsx
1 /*
2 * @Author: nevin
3 * @Date: 2025-02-17 19:28:13
4 * @LastEditTime: 2025-02-26 09:09:32
5 * @LastEditors: nevin
6 * @Description: 微信二维码登录
7 */
8 import { forwardRef, useImperativeHandle, useState, useEffect } from 'react';
9 import { GzhLoginTyp, userApi } from '@/api/user';
10 import { Button, Modal, Form, Input, message } from 'antd';
11 import { ReloadOutlined } from '@ant-design/icons';
12 import styles from '../login.module.scss';
13 import { useUserStore } from '@/store/user';
14 import { useNavigate } from 'react-router-dom';
15 import GetCode from '@/components/GetCode';
16
17 export interface PubItemRef {
18 init: (pubRecord: any) => Promise<void>;
19 }
20
21 export default forwardRef<PubItemRef>((props, ref) => {
22 const [ticketInfo, setTicketInfo] = useState<{ ticket: string; key: string }>(
23 {
24 ticket: '',
25 key: '',
26 },
27 );
28 const [showMask, setShowMask] = useState(false);
29 const [showPhoneModal, setShowPhoneModal] = useState(false);
30 const [phoneForm] = Form.useForm();
31 const [openId, setOpenId] = useState('');
32 const [isFirstLogin, setIsFirstLogin] = useState(false);
33 const [inviteCode, setInviteCode] = useState('');
34 const userStore = useUserStore();
35 const navigate = useNavigate();
36
37 useEffect(() => {
38 getQrcode();
39 const qrcodeTimer = setTimeout(() => {
40 setShowMask(true);
41 }, 30000); // 30秒后显示遮罩
42
43 return () => {
44 clearTimeout(qrcodeTimer);
45 };
46 }, []);
47
48 // 添加轮询登录检查
49 useEffect(() => {
50 let loginTimer: NodeJS.Timeout;
51
52 if (ticketInfo.ticket) {
53 loginTimer = setInterval(async () => {
54 const res = await userApi.wxGzhQrcodelogin(ticketInfo);
55 console.log('GzhQrcode ======== ', res);
56
57 const { openId, token, phone, status } = res;
58
59 if (status === -1) {
60 message.error('登录失败,请重新扫码登录');
61 setShowMask(true);
62 clearInterval(loginTimer);
63 return;
64 }
65 setOpenId(openId);
66
67 // 登录完成
68 if (status === 1 && !!token && !!phone && res.userInfo) {
69 completeLogin(res);
70 clearInterval(loginTimer);
71 return;
72 }
73
74 loginByPhone(res);
75 }, 2000); // 每2秒检查一次
76 }
77
78 return () => {
79 if (loginTimer) {
80 clearInterval(loginTimer);
81 }
82 };
83 }, [ticketInfo]);
84
85 async function getQrcode() {
86 const res = await userApi.getWxLoginQrcode({});
87 if (res) {
88 setTicketInfo({
89 ticket: res.ticket,
90 key: res.key,
91 });
92 setShowMask(false);
93 }
94 }
95
96 const loginByPhone = (res: GzhLoginTyp) => {
97 if (!res || !res.openId) return;
98 setIsFirstLogin(true);
99 setShowPhoneModal(true);
100 };
101
102 const completeLogin = (res: GzhLoginTyp) => {
103 window.ipcRenderer.invoke('ICP_USER_ADD', res.userInfo);
104 userStore.setToken(res);
105 userStore.getUserInfo(res.userInfo);
106 message.success('登录成功!');
107 window.ipcRenderer.invoke('start-kwai-listen');
108 navigate('/');
109 };
110
111 // 提交手机号绑定
112 const handlePhoneSubmit = async () => {
113 try {
114 const values = await phoneForm.validateFields();
115 // 调手机号登录
116 const res = await userApi.phoneGzhLogin({
117 phone: values.phone,
118 code: values.code,
119 openId: openId,
120 inviteCode: values.inviteCode
121 });
122
123 if (res) {
124 message.success('手机号绑定成功');
125 setShowPhoneModal(false);
126 // 完成登录流程
127 completeLogin({ ...res, phone: values.phone, openId, status: 1 });
128 }
129 } catch (error) {
130 message.error('手机号绑定失败');
131 }
132 };
133
134 useImperativeHandle(ref, () => ({
135 init: async (info: any) => {},
136 }));
137
138 return (
139 <div className={styles.qrcodeLogin}>
140 <div className={styles.qrcodeHeader}>
141 <h2>微信扫码登录</h2>
142 <p>请使用微信扫描二维码关注哎哟赚公众号</p>
143 </div>
144
145 <div className={styles.qrcodeWrapper}>
146 {ticketInfo.ticket && (
147 <img
148 src={`https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=${ticketInfo.ticket}`}
149 alt="微信登录二维码"
150 />
151 )}
152
153 {showMask && (
154 <div className={styles.qrcodeMask}>
155 <p>二维码已过期</p>
156 <Button
157 type="link"
158 icon={<ReloadOutlined />}
159 onClick={getQrcode}
160 className={styles.refreshBtn}
161 >
162 刷新二维码
163 </Button>
164 </div>
165 )}
166 </div>
167
168 <div className={styles.agreement}>
169 登录即表示已接受哎哟赚
170 <a
171 href="#"
172 onClick={(e) => {
173 e.preventDefault(); /* 打开注册协议 */
174 }}
175 >
176 《注册协议》
177 </a>
178 <a
179 href="#"
180 onClick={(e) => {
181 e.preventDefault(); /* 打开隐私政策 */
182 }}
183 >
184 《隐私权政策》
185 </a>
186 </div>
187
188 {/* 手机号绑定弹窗 */}
189 <Modal
190 title="绑定手机号"
191 open={showPhoneModal}
192 onCancel={() => setShowPhoneModal(false)}
193 footer={null}
194 closable={false}
195 maskClosable={false}
196 >
197 <Form form={phoneForm} layout="vertical" onFinish={handlePhoneSubmit}>
198 <Form.Item
199 name="phone"
200 label="手机号"
201 rules={[
202 { required: true, message: '请输入手机号' },
203 { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' }
204 ]}
205 >
206 <Input placeholder="请输入手机号" />
207 </Form.Item>
208 <Form.Item
209 name="code"
210 label="验证码"
211 rules={[
212 { required: true, message: '请输入验证码' },
213 { len: 6, message: '验证码长度为6位' }
214 ]}
215 >
216 <div style={{ display: 'flex', alignItems: 'center', position: 'relative' }}>
217 <Input placeholder="请输入验证码" style={{ flex: 1 }} />
218 <GetCode
219 onGetCode={async (unlock: () => void) => {
220 const validateRes = await phoneForm
221 .validateFields(['phone'])
222 .catch(() => unlock());
223 if (!validateRes) return;
224 const res = await userApi.getUserCode({
225 phone: phoneForm.getFieldValue('phone'),
226 });
227 if (!res) return;
228 message.success('验证码已发送');
229 if (typeof res === 'string') {
230 phoneForm.setFieldsValue({
231 code: res,
232 });
233 }
234 }}
235 />
236 </div>
237 </Form.Item>
238 <Form.Item
239 name="inviteCode"
240 label="邀请码"
241 rules={[{ required: false, message: '请输入邀请码' }]}
242 >
243 <Input
244 placeholder="请输入邀请码"
245 onChange={(e) => setInviteCode(e.target.value)}
246 />
247 </Form.Item>
248 <Form.Item>
249 <Button type="primary" htmlType="submit" block>
250 绑定手机号
251 </Button>
252 </Form.Item>
253 </Form>
254 </Modal>
255 </div>
256 );
257 });
258
258 lines Plain Text