import { useEffect, useState } from 'react' import Taro from '@tarojs/taro' import { View, Image, Text, Button } from '@tarojs/components' import { TenantId } from '@/config/app' import { getWxOpenId } from '@/api/layout' import { getUserInfo } from '@/api/layout' import { saveStorageByLoginUser } from '@/utils/server' import { isUserDisabled } from '@/utils/auth' import { checkAndHandleInviteRelation, hasPendingInvite, parseInviteParams, saveInviteParams, trackInviteSource, } from '@/utils/invite' import './login.scss' interface GetPhoneNumberDetail { code?: string encryptedData?: string iv?: string errMsg?: string } interface GetPhoneNumberEvent { detail: GetPhoneNumberDetail } /** 同步判断当前是否是微信小程序环境 */ function detectIsWeapp(): boolean { try { return Taro.getEnv() === Taro.ENV_TYPE.WEAPP } catch { return process.env.TARO_ENV === 'weapp' } } const IS_WEAPP = detectIsWeapp() /** 小程序登录获取 code */ async function getWeappLoginCode(): Promise { try { const res = await new Promise<{ code?: string }>((resolve, reject) => { Taro.login({ success: (r) => resolve(r), fail: reject }) }) return res?.code } catch { return undefined } } /** 确保微信 openid 已保存到服务端 */ async function ensureWxOpenIdSaved() { try { if (Taro.getEnv() !== Taro.ENV_TYPE.WEAPP) return } catch { if (process.env.TARO_ENV !== 'weapp') return } const code = await getWeappLoginCode() if (!code) return try { await getWxOpenId({ code }) const freshUser = await getUserInfo() if (freshUser) { const token = Taro.getStorageSync('access_token') saveStorageByLoginUser(token, freshUser) } } catch (e) { console.error('登录后绑定 openid 失败:', e) } } /** 获取用户信息 */ async function fetchUserInfo(token: string) { try { const serverUrl = 'https://server.websoft.top' const res: any = await Taro.request({ url: `${serverUrl}/api/auth/user`, method: 'GET', header: { 'Authorization': `Bearer ${token}`, 'content-type': 'application/json', TenantId: String(TenantId), }, }) if (res.data?.code === 0 && res.data?.data) { return res.data.data } return null } catch (e) { console.error('获取用户信息失败:', e) return null } } const Login = () => { const [isAgree, setIsAgree] = useState(false) const [loading, setLoading] = useState(false) const [showContent, setShowContent] = useState(false) const router = Taro.getCurrentInstance().router const isWeapp = IS_WEAPP /** 页面加载动画 */ useEffect(() => { setTimeout(() => setShowContent(true), 100) }, []) /** 解析 redirect 参数 */ const redirectUrl = (() => { const raw = (router?.params as Record | undefined)?.redirect if (!raw) return '' try { const decoded = decodeURIComponent(raw) return decoded.startsWith('/') ? decoded : `/${decoded}` } catch { return raw.startsWith('/') ? raw : `/${raw}` } })() /** 处理邀请参数 */ useEffect(() => { try { const inviteParams = parseInviteParams({ query: router?.params }) if (inviteParams?.inviter) { saveInviteParams(inviteParams) trackInviteSource(inviteParams.source || 'share', parseInt(inviteParams.inviter, 10)) } } catch (e) { console.error('登录页处理邀请参数失败:', e) } }, [router?.params]) /** 登录成功后跳转 */ const navigateAfterLogin = async () => { if (!redirectUrl) { await Taro.reLaunch({ url: '/pages/index/index' }) return } const tabBarUrls = [ '/pages/index/index', '/pages/shop/index', '/pages/user/user', '/pages/shop/cart', ] const pure = redirectUrl.split('?')[0] if (tabBarUrls.includes(pure)) { await Taro.switchTab({ url: pure }) return } await Taro.redirectTo({ url: redirectUrl }) } /** 跳转到短信验证码登录(降级方案) */ const goSmsLogin = () => { const params: Record = {} if (redirectUrl) params.redirect = redirectUrl // 透传邀请参数,避免从登录页切到短信登录时丢失 inviter const inviteParams = parseInviteParams({ query: router?.params }) if (inviteParams?.inviter) params.inviter = inviteParams.inviter if (inviteParams?.source) params.source = inviteParams.source if (inviteParams?.t) params.t = inviteParams.t const qs = Object.entries(params) .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`) .join('&') Taro.navigateTo({ url: `/passport/sms-login${qs ? `?${qs}` : ''}` }) } /** 手机号授权失败时弹窗引导走短信登录(区分原因) */ const showPhoneAuthFailedModal = (errMsg?: string) => { let content = '请尝试使用短信验证码登录' if (errMsg?.includes('user deny')) { content = '您已取消微信手机号授权,可改用短信验证码登录' } else if (errMsg?.includes('privacy')) { content = '微信未授权获取手机号,可改用短信验证码登录' } else if (errMsg?.includes('no permission')) { content = '小程序暂未获得手机号授权,可改用短信验证码登录' } Taro.showModal({ title: '未获取到手机号', content, confirmText: '短信登录', cancelText: '我知道了', confirmColor: '#07c160', success: (res) => { if (res.confirm) goSmsLogin() }, }) } /** 手机号快捷登录 */ const handleGetPhoneNumber = async ({ detail }: GetPhoneNumberEvent) => { if (!isAgree) { Taro.showToast({ title: '请先勾选同意协议', icon: 'none' }) return } if (loading) return const { code: phoneCode, errMsg } = detail || {} if (!phoneCode || (errMsg && errMsg.includes('fail'))) { showPhoneAuthFailedModal(errMsg) return } try { setLoading(true) const inviteParams = parseInviteParams({ query: router?.params }) const refereeId = inviteParams?.inviter ? parseInt(inviteParams.inviter, 10) : 0 const serverUrl = 'https://server.websoft.top' const res: any = await Taro.request({ url: `${serverUrl}/api/wx-login/loginByMpWxPhone`, method: 'POST', data: { code: phoneCode, notVerifyPhone: true, refereeId, sceneType: 'save_referee', tenantId: Number(TenantId), }, header: { 'content-type': 'application/json', TenantId: String(TenantId) }, }) if (res.data?.code === 0 && res.data?.data?.access_token) { const token = res.data.data.access_token let user = res.data.data.user // 获取最新的用户信息 const freshUserInfo = await fetchUserInfo(token) if (freshUserInfo) { user = freshUserInfo } // 检查用户是否被禁用 if (isUserDisabled(user)) { Taro.showModal({ title: '账号已被禁用', content: '您的账号已被管理员禁用,暂时无法使用小程序。如有疑问请联系门店客服。', showCancel: false, confirmText: '我知道了', confirmColor: '#ee0a24', }) return } saveStorageByLoginUser(token, user) // 绑定 openid + 处理邀请关系 await ensureWxOpenIdSaved() if (hasPendingInvite()) { try { await checkAndHandleInviteRelation() } catch (e) { console.error(e) } } Taro.showToast({ title: '登录成功', icon: 'success' }) setTimeout(() => navigateAfterLogin(), 800) } else { Taro.showToast({ title: res.data?.message || '登录失败', icon: 'none' }) } } catch (e: any) { console.error('微信登录失败:', e) Taro.showToast({ title: e?.message || '登录失败', icon: 'none' }) } finally { setLoading(false) } } return ( {/* 渐变背景 */} {/* 主要内容区域 */} {/* Logo 和标题 */} 欢迎来到鑫龙家电 登录后享受更多精彩功能 {/* 登录按钮区域 */} {isWeapp && ( {/**/} {/* 点击上方按钮,快速登录*/} {/**/} goSmsLogin()} > 使用短信验证码登录 安全快捷 🔒 安全可靠 一键登录 🎁 专属福利 )} {/* 非微信小程序环境提示 */} {!isWeapp && ( 💻 请在微信小程序中打开 当前环境不支持手机号快捷登录,请在微信小程序中使用完整功能 )} {/* 协议勾选 - 自定义实现 */} setIsAgree(!isAgree)}> {isAgree && } {'我已阅读并同意'} { e.stopPropagation(); Taro.navigateTo({ url: '/passport/agreement?type=terms' }) }}> {'《用户协议》'} {'和'} { e.stopPropagation(); Taro.navigateTo({ url: '/passport/agreement?type=privacy' }) }}> {'《隐私政策》'} ) } export default Login