feat(login): 登录页改为微信一键登录并移除账密表单

This commit is contained in:
ozh 2026-09-15 12:50:55 +08:00
parent 44918644ef
commit 90e6262c89
4 changed files with 35 additions and 41 deletions

View File

@ -42,7 +42,7 @@
color: rgba(255, 255, 255, 0.85); color: rgba(255, 255, 255, 0.85);
} }
// 表单卡上浮叠在渐变区上 // 登录卡上浮叠在渐变区上
.form { .form {
margin: -120px 24px 0; margin: -120px 24px 0;
padding: 48px 32px; padding: 48px 32px;
@ -53,15 +53,6 @@
flex-direction: column; flex-direction: column;
} }
.input {
height: 100px;
background: $color-bg-gray;
border-radius: $radius-md;
padding: 0 32px;
margin-bottom: 24px;
font-size: 30px;
}
.submit { .submit {
height: 96px; height: 96px;
background: $color-primary-grad; background: $color-primary-grad;

View File

@ -1,8 +1,9 @@
import { useState } from 'react' import { useState } from 'react'
import { View, Text, Input } from '@tarojs/components' import { View, Text } from '@tarojs/components'
import { useAppDispatch } from '@/store/hooks' import { useDidShow } from '@tarojs/taro'
import { useAppDispatch, useAppSelector } from '@/store/hooks'
import { setAuth } from '@/store/authSlice' import { setAuth } from '@/store/authSlice'
import { login } from '@/api/auth' import { performWxLogin } from './wxLoginFlow'
import { useToast } from '@/hooks/useToast' import { useToast } from '@/hooks/useToast'
import { ROUTES } from '@/constants/routes' import { ROUTES } from '@/constants/routes'
import { reLaunch } from '@/utils/navigation' import { reLaunch } from '@/utils/navigation'
@ -11,26 +12,28 @@ import styles from './index.module.scss'
export default function Login() { export default function Login() {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const toast = useToast() const toast = useToast()
const [username, setUsername] = useState('') const token = useAppSelector((s) => s.auth.token)
const [password, setPassword] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const handleLogin = async () => { // 已有 token 直接进首页(本期不做 checkLogin 校验,spec D5)
if (!username || !password) { useDidShow(() => {
toast.error('请填写完整') if (token && !loading) reLaunch(ROUTES.HOME)
return })
}
const handleWxLogin = async () => {
if (loading) return
setLoading(true) setLoading(true)
try { try {
const res = await login({ username, password }) const { code, userInfo } = await performWxLogin()
dispatch(setAuth(res)) dispatch(setAuth({ token: code, userInfo }))
reLaunch(ROUTES.HOME) reLaunch(ROUTES.HOME)
} catch { } catch (e) {
toast.error('登录失败') // code 失效/已用等场景:toast 后端 Message,用户重按按钮重新 Taro.login(code 一次性,重试安全)
} finally { toast.error(e instanceof Error && e.message ? e.message : '登录失败')
setLoading(false) setLoading(false)
} }
} }
return ( return (
<View className={styles.page}> <View className={styles.page}>
<View className={styles.hero}> <View className={styles.hero}>
@ -41,21 +44,8 @@ export default function Login() {
<Text className={styles.slogan}>一起守护家人</Text> <Text className={styles.slogan}>一起守护家人</Text>
</View> </View>
<View className={styles.form}> <View className={styles.form}>
<Input <View className={styles.submit} onClick={handleWxLogin}>
className={styles.input} <Text className={styles.submitText}>{loading ? '登录中...' : '微信一键登录'}</Text>
placeholder="账号"
value={username}
onInput={(e) => setUsername(e.detail.value)}
/>
<Input
className={styles.input}
placeholder="密码"
password
value={password}
onInput={(e) => setPassword(e.detail.value)}
/>
<View className={styles.submit} onClick={handleLogin}>
<Text className={styles.submitText}>{loading ? '登录中...' : '登录'}</Text>
</View> </View>
</View> </View>
</View> </View>

View File

@ -15,7 +15,14 @@ function resolveAppId(): string {
// 微信一键登录主流程(spec D3): // 微信一键登录主流程(spec D3):
// Taro.login 换一次性 code → 组装 appId/openId → 调 miniapp/login → 缓存响应中的 openId // Taro.login 换一次性 code → 组装 appId/openId → 调 miniapp/login → 缓存响应中的 openId
export async function performWxLogin(): Promise<WxLoginResult> { export async function performWxLogin(): Promise<WxLoginResult> {
const { code } = await Taro.login() // Taro.login reject 时返回 { errMsg } 形状对象而非 Error,此处归一化为带 message 的 Error
let code: string
try {
const r = await Taro.login()
code = r.code
} catch (e) {
throw new Error(e instanceof Error ? e.message : '微信登录失败')
}
if (!code) throw new Error('获取微信登录凭证失败') if (!code) throw new Error('获取微信登录凭证失败')
const openId = storage.get<string>(WX_OPENID_STORAGE_KEY) ?? undefined const openId = storage.get<string>(WX_OPENID_STORAGE_KEY) ?? undefined
const result = await wxMiniappLogin({ appId: resolveAppId(), code, openId }) const result = await wxMiniappLogin({ appId: resolveAppId(), code, openId })

View File

@ -77,6 +77,12 @@ describe('performWxLogin', () => {
expect(mockWxMiniappLogin).not.toHaveBeenCalled() expect(mockWxMiniappLogin).not.toHaveBeenCalled()
}) })
it('Taro.login reject(errMsg 形状)时抛带 message 的 Error', async () => {
mockLogin.mockRejectedValue({ errMsg: 'login:fail:cancel' })
await expect(performWxLogin()).rejects.toThrow('微信登录失败')
expect(mockWxMiniappLogin).not.toHaveBeenCalled()
})
it('getAccountInfoSync 抛错时回退常量 appId', async () => { it('getAccountInfoSync 抛错时回退常量 appId', async () => {
mockGetAccountInfo.mockImplementation(() => { throw new Error('not supported') }) mockGetAccountInfo.mockImplementation(() => { throw new Error('not supported') })
mockWxMiniappLogin.mockResolvedValue(result()) mockWxMiniappLogin.mockResolvedValue(result())