import { useEffect, useMemo, useState } from 'react' import Taro from '@tarojs/taro' import { Button, Radio } from '@nutui/nutui-react-taro' import { TenantId } from '@/config/app' import { getUserInfo, getWxOpenId } from '@/api/layout' import { saveStorageByLoginUser } from '@/utils/server' import { getStoredInviteParams, parseInviteParams, saveInviteParams, trackInviteSource, checkAndHandleInviteRelation, } from '@/utils/invite' interface GetPhoneNumberDetail { code?: string encryptedData?: string iv?: string errMsg?: string } interface GetPhoneNumberEvent { detail: GetPhoneNumberDetail } interface LoginResponse { data: { code?: number message?: string data?: { access_token: string user: any } } } async function getWeappLoginCode(): Promise { try { const res = await new Promise<{ code?: string }>((resolve, reject) => { Taro.login({ success: (r) => resolve(r as any), fail: (e) => reject(e), }) }) return res?.code } catch (_e) { return undefined } } async function ensureWxOpenIdSaved(opts: { user?: any; wxLoginCode?: string }) { // JSAPI 微信支付必须有 openid;注册/登录后立刻补齐,避免后续创建支付单失败。 try { if (Taro.getEnv() !== Taro.ENV_TYPE.WEAPP) return } catch (_e) { if (process.env.TARO_ENV !== 'weapp') return } if (opts.user?.openid) return const code = opts.wxLoginCode || (await getWeappLoginCode()) if (!code) return // 该接口一般会在服务端把 openid 绑定到当前登录用户;返回值并不一定包含 openid。 await getWxOpenId({ code }) // 同步本地 User(让后续页面/逻辑能直接读到 openid) try { const fresh = await getUserInfo() if (fresh) Taro.setStorageSync('User', fresh) } catch (_e) { // ignore: openid 已在服务端绑定,本地不同步也不影响后端创建支付订单 } } function safeDecodeMaybeEncoded(input?: string): string { if (!input) return '' try { // Taro 路由参数通常是 URL 编码过的字符串 return decodeURIComponent(input) } catch (_e) { return input } } function isTabBarUrl(url: string) { const pure = url.split('?')[0] return ( pure === '/pages/index/index' || pure === '/pages/cart/cart' || pure === '/pages/user/user' || pure === '/pages/category/index' ) } const Register = () => { const [isAgree, setIsAgree] = useState(false) const [loading, setLoading] = useState(false) // 短信验证码登录仅在非微信小程序端展示 const isWeapp = useMemo(() => { try { return Taro.getEnv() === Taro.ENV_TYPE.WEAPP } catch (_e) { return process.env.TARO_ENV === 'weapp' } }, []) const router = Taro.getCurrentInstance().router useEffect(() => { // 注册/登录页不需要展示 tabBar Taro.hideTabBar() }, []) const redirectUrl = useMemo(() => { const raw = (router?.params as any)?.redirect as string | undefined const decoded = safeDecodeMaybeEncoded(raw) if (!decoded) return '' return decoded.startsWith('/') ? decoded : `/${decoded}` }, [router?.params]) // 如果从分享/二维码直接进入注册页(携带 inviter/source/t),先暂存邀请信息 useEffect(() => { try { const inviteParams = parseInviteParams({ query: router?.params }) if (inviteParams?.inviter) { saveInviteParams(inviteParams) trackInviteSource(inviteParams.source || 'qrcode', parseInt(inviteParams.inviter, 10)) } } catch (e) { console.error('注册页处理邀请参数失败:', e) } }, [router?.params]) const navigateAfterLogin = async () => { if (!redirectUrl) { await Taro.reLaunch({ url: '/pages/index/index' }) return } if (isTabBarUrl(redirectUrl)) { // switchTab 不支持携带 query,这里按纯路径跳转 await Taro.switchTab({ url: redirectUrl.split('?')[0] }) return } // 替换当前注册页,避免返回栈里再回到注册页 await Taro.redirectTo({ url: redirectUrl }) } const handleGetPhoneNumber = async ({ detail }: GetPhoneNumberEvent) => { if (!isAgree) { Taro.showToast({ title: '请先勾选同意协议', icon: 'none' }) return } if (loading) return const { code: phoneCode, encryptedData, iv, errMsg } = detail || {} if (!phoneCode || (errMsg && errMsg.includes('fail'))) { Taro.showToast({ title: '未授权手机号', icon: 'none' }) return } try { setLoading(true) // 获取存储的邀请参数(推荐人ID) const inviteParams = getStoredInviteParams() const refereeId = inviteParams?.inviter ? parseInt(inviteParams.inviter, 10) : 0 // 获取小程序登录 code(用于后续绑定 openid) const wxLoginCode = await getWeappLoginCode() const res = (await Taro.request({ url: 'https://glt-server.websoft.top/api/wx-login/loginByMpWxPhone', method: 'POST', data: { code: phoneCode, encryptedData, iv, notVerifyPhone: true, refereeId: refereeId, sceneType: 'save_referee', tenantId: TenantId, }, header: { 'content-type': 'application/json', TenantId, }, })) as unknown as LoginResponse if ((res as any)?.data?.code === 1) { Taro.showToast({ title: res.data.message || '登录失败', icon: 'none' }) return } const token = res?.data?.data?.access_token const user = res?.data?.data?.user if (!token || !user?.userId) { Taro.showToast({ title: '登录失败,请重试', icon: 'none' }) return } saveStorageByLoginUser(token, user) // 注册/登录成功后,立即补齐 openid(JSAPI 支付必需) try { await ensureWxOpenIdSaved({ user, wxLoginCode }) } catch (e) { console.error('注册页绑定 openid 失败:', e) } // 登录成功后尝试绑定推荐关系(如果有待处理 inviter,会自动处理并清理参数) try { await checkAndHandleInviteRelation() } catch (e) { console.error('注册页登录后处理邀请关系失败:', e) } Taro.showToast({ title: '登录成功', icon: 'success' }) setTimeout(() => { navigateAfterLogin().catch((e) => console.error('登录后跳转失败:', e)) }, 800) } catch (e: any) { console.error('注册/登录失败:', e) Taro.showToast({ title: e?.message || '登录失败', icon: 'none' }) } finally { setLoading(false) } } const goSmsLogin = () => { const inviteParams = getStoredInviteParams() const inviter = inviteParams?.inviter const source = inviteParams?.source const t = inviteParams?.t const params: Record = {} if (redirectUrl) params.redirect = redirectUrl // 兜底:把 inviter 带过去,避免“先点注册再进入”时丢失 if (inviter) params.inviter = inviter if (source) params.source = source if (t) params.t = t const qs = Object.entries(params) .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`) .join('&') Taro.navigateTo({ url: `/passport/sms-login${qs ? `?${qs}` : ''}` }) } return ( <>
注册/登录
{!isWeapp && ( )}
setIsAgree(!isAgree)} /> setIsAgree(!isAgree)}> 勾选表示您已阅读并同意 Taro.navigateTo({ url: '/passport/agreement' })} className={'text-blue-600'} > 《服务协议及隐私政策》
) } export default Register