| 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 |