112 lines
4.5 KiB
TypeScript
112 lines
4.5 KiB
TypeScript
import { useState } from 'react'
|
||
import { View, Text } from '@tarojs/components'
|
||
import { useRouter, useDidShow } from '@tarojs/taro'
|
||
import { useAppDispatch, useAppSelector } from '@/store/hooks'
|
||
import { setAuth } from '@/store/authSlice'
|
||
import { performWxLogin, completeUserInfoLogin, getLoginSubState } from './wxLoginFlow'
|
||
import { useToast } from '@/hooks/useToast'
|
||
import { ROUTES } from '@/constants/routes'
|
||
import { reLaunch } from '@/utils/navigation'
|
||
import { storage } from '@/utils/storage'
|
||
import { WX_OPENID_STORAGE_KEY } from '@/constants/auth'
|
||
import styles from './index.module.scss'
|
||
|
||
export default function Login() {
|
||
const dispatch = useAppDispatch()
|
||
const toast = useToast()
|
||
const router = useRouter()
|
||
const token = useAppSelector((s) => s.auth.token)
|
||
const [loading, setLoading] = useState(false)
|
||
// 注册中间态:needUserInfo(待用户资料授权)→ null(初始/完成)
|
||
const [needAuth, setNeedAuth] = useState(false)
|
||
const [openId, setOpenId] = useState('')
|
||
|
||
// 登录后落地页:携带 redirect 参数时优先回跳(守卫踢登录/分享卡直达场景),否则进首页
|
||
const afterLoginUrl = () => {
|
||
const raw = router.params.redirect
|
||
const target = typeof raw === 'string' && raw.startsWith('/') ? decodeURIComponent(raw) : ''
|
||
return target || ROUTES.HOME
|
||
}
|
||
|
||
// 已有 token 直接进首页(本期不做 checkLogin 校验,spec D5)
|
||
useDidShow(() => {
|
||
if (token && !loading) reLaunch(afterLoginUrl())
|
||
})
|
||
|
||
// 完整登录态校验与落地;未完成时推进授权态
|
||
const applyResult = async (result: Awaited<ReturnType<typeof performWxLogin>>) => {
|
||
const state = getLoginSubState(result)
|
||
if (state !== 'complete') {
|
||
console.log('[login-page] 注册未完成,推进到', state)
|
||
if (state === 'noMobile') {
|
||
// 手机号授权暂缓(后端字段未定):提示并保持当前态,等后端确认后接续
|
||
throw new Error('后端要求绑定手机号,该流程暂未开通')
|
||
}
|
||
// openId 兜底链:响应 openId → 本地缓存(needUserInfo 首次已写入)→ 当前 state
|
||
const openIdNext =
|
||
result.openId ?? storage.get<string>(WX_OPENID_STORAGE_KEY) ?? openId
|
||
if (!openIdNext) throw new Error('响应缺少 openId,无法继续授权')
|
||
setOpenId(openIdNext)
|
||
setNeedAuth(true)
|
||
setLoading(false)
|
||
return
|
||
}
|
||
// token 优先取响应头落地(http 层附到 token 字段);Content.code 后端恒返 null 仅作兼容
|
||
const token = result.token ?? result.code
|
||
if (!token || !result.userInfo) throw new Error('登录响应缺少凭证')
|
||
console.log('[login-page] 登录完成,token 已落地')
|
||
setNeedAuth(false)
|
||
dispatch(setAuth({ token, userInfo: result.userInfo }))
|
||
await reLaunch(afterLoginUrl())
|
||
}
|
||
|
||
const handleWxLogin = async () => {
|
||
if (loading) return
|
||
setLoading(true)
|
||
try {
|
||
applyResult(await performWxLogin())
|
||
} catch (e) {
|
||
// code 失效/已用等场景:toast 后端 Message,用户重按按钮重新 Taro.login(code 一次性,重试安全)
|
||
console.error('[login-page] performWxLogin 失败', e)
|
||
toast.error(e instanceof Error && e.message ? e.message : '登录失败')
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
// needUserInfo:getUserInfo 授权用户资料(后端文档约定的授权接口)
|
||
const handleAuthUserInfo = async () => {
|
||
if (loading || !openId) return
|
||
setLoading(true)
|
||
try {
|
||
applyResult(await completeUserInfoLogin(openId))
|
||
} catch (e) {
|
||
// 授权失败/取消:保留待授权态,用户可重试
|
||
console.error('[login-page] completeUserInfoLogin 失败', e)
|
||
toast.error(e instanceof Error && e.message ? e.message : '授权失败')
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<View className={styles.page}>
|
||
<View className={styles.hero}>
|
||
<View className={styles.logo}>
|
||
<Text className={styles.logoText}>智</Text>
|
||
</View>
|
||
<Text className={styles.productName}>智慧养老</Text>
|
||
<Text className={styles.slogan}>一起守护家人</Text>
|
||
</View>
|
||
<View className={styles.form}>
|
||
<View
|
||
className={styles.submit}
|
||
onClick={needAuth ? handleAuthUserInfo : handleWxLogin}
|
||
>
|
||
<Text className={styles.submitText}>
|
||
{loading ? '登录中...' : needAuth ? '授权用户信息,继续' : '微信一键登录'}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
)
|
||
}
|