Files
banban/banban-mini/src/pages/login/index.tsx
T

217 lines
7.2 KiB
TypeScript

import { getPageStyle } from '@/utils/page-style'
import { View, Text, Button, Checkbox, CheckboxGroup, Image } from '@tarojs/components'
import { useEffect, useState } from 'react'
import Taro from '@tarojs/taro'
import { getToken, wechatLogin } from '@/services/auth'
import './index.scss'
const LOGIN_PAGE_URL = '/pages/login/index'
const DEFAULT_POST_LOGIN_URL = '/pages/device/index'
const TAB_BAR_ROUTES = new Set([
'/pages/device/index',
'/pages/chat/index',
'/pages/location/index',
'/pages/sleep/index',
])
function consumePostLoginRedirect(): string {
const storedUrl = String(Taro.getStorageSync('postLoginRedirect') || '').trim()
Taro.removeStorageSync('postLoginRedirect')
if (!storedUrl || storedUrl === LOGIN_PAGE_URL || !storedUrl.startsWith('/pages/')) {
return DEFAULT_POST_LOGIN_URL
}
return storedUrl
}
function navigateAfterLogin(url: string): void {
const route = url.split('?')[0]
if (!TAB_BAR_ROUTES.has(route)) {
Taro.reLaunch({ url })
return
}
// Tab pages must be opened with switchTab so their lifecycle and custom tab bar refresh.
void Promise.resolve(Taro.switchTab({ url: route })).catch(() => {
Taro.reLaunch({ url: route })
})
}
export default function Login() {
const [submitting, setSubmitting] = useState(false)
const [agreementAccepted, setAgreementAccepted] = useState(false)
useEffect(() => {
if (getToken()) {
navigateAfterLogin(consumePostLoginRedirect())
return
}
}, [])
const handleLogin = async () => {
if (submitting) return
if (!agreementAccepted) {
Taro.showToast({
title: '请先阅读并同意协议',
icon: 'none',
})
return
}
setSubmitting(true)
Taro.showLoading({ title: '登录中...' })
try {
const loginRes = await Taro.login()
if (!loginRes.code) {
throw new Error('未获取到微信登录凭证')
}
const session = await wechatLogin({
code: loginRes.code,
})
const nextUserInfo = {
nickname: session.nickname || '',
}
if (nextUserInfo.nickname) {
Taro.setStorageSync('userInfo', {
nickname: nextUserInfo.nickname,
})
} else {
Taro.removeStorageSync('userInfo')
}
Taro.showToast({ title: '登录成功', icon: 'success' })
setTimeout(() => {
navigateAfterLogin(consumePostLoginRedirect())
}, 300)
} catch (error: any) {
console.error('[login] failed:', error)
const message = String(error?.message || '登录失败,请重试')
Taro.showModal({
title: '登录失败',
content: message,
showCancel: false,
})
} finally {
Taro.hideLoading()
setSubmitting(false)
}
}
const handleGuestBrowse = () => {
Taro.removeStorageSync('postLoginRedirect')
Taro.reLaunch({ url: '/pages/device/index' })
}
const handleAgreementChange = (event) => {
const values = event.detail.value || []
setAgreementAccepted(values.includes('accepted'))
}
const openUserAgreement = () => {
Taro.showModal({
title: '用户服务协议',
content: '欢迎使用伴伴。您在使用本小程序前,应阅读并同意本协议。我们将为您提供设备绑定、状态查看、消息互动、定位展示等服务。请您遵守法律法规,不得利用本服务从事违法或侵犯他人权益的行为。',
showCancel: false,
confirmText: '我知道了',
})
}
const openPrivacyPolicy = () => {
const taroWithPrivacy = Taro as typeof Taro & {
openPrivacyContract?: (option?: {
success?: () => void
fail?: (error: unknown) => void
}) => void
}
if (typeof taroWithPrivacy.openPrivacyContract === 'function') {
taroWithPrivacy.openPrivacyContract({
fail: () => {
Taro.showModal({
title: '隐私政策',
content: '暂时无法打开隐私保护指引,请稍后重试。',
showCancel: false,
confirmText: '我知道了',
})
},
})
return
}
Taro.showModal({
title: '隐私政策',
content: '伴伴会在提供设备绑定、消息互动、定位展示等功能时,按需处理您的账号信息、设备信息和位置信息。我们仅在实现相关功能所必需的范围内使用上述信息。',
showCancel: false,
confirmText: '我知道了',
})
}
return (
<View className='login-page' style={getPageStyle(false)}>
<View className='login-header'>
<View className='logo'>
<Image
className='login-hero-art'
src={require('../../assets/ui/login-hero.png')}
mode='aspectFit'
/>
</View>
<Text className='title'>伴伴</Text>
<Text className='subtitle'>让关爱随时随地</Text>
</View>
<View className='login-content'>
<View className='features'>
<View className='feature-item'>
<Image className='feature-icon-image' src={require('../../assets/ui/feature-chat.png')} mode='aspectFit' />
<Text className='feature-text'>AI 智能对话</Text>
</View>
<View className='feature-item'>
<Image className='feature-icon-image' src={require('../../assets/ui/feature-location.png')} mode='aspectFit' />
<Text className='feature-text'>实时定位追踪</Text>
</View>
<View className='feature-item'>
<Image className='feature-icon-image' src={require('../../assets/ui/feature-status.png')} mode='aspectFit' />
<Text className='feature-text'>设备状态监控</Text>
</View>
</View>
<View className='login-info-card'>
<Text className='login-info-title'>登录后启用设备管理</Text>
<Text className='login-info-desc'>仅在绑定设备、查看定位和管理宝贝资料时需要登录。</Text>
</View>
<View className='login-btn-wrapper'>
<Button className='login-btn' loading={submitting} disabled={submitting} onClick={handleLogin}>
<Text className='btn-icon'>🌐</Text>
<Text className='btn-text'>{submitting ? '登录中...' : '登录后继续'}</Text>
</Button>
<View className='guest-browse-btn' onClick={handleGuestBrowse}>
<Text className='guest-browse-text'>暂不登录,先浏览</Text>
</View>
</View>
<View className='agreement-panel'>
<CheckboxGroup value={agreementAccepted ? ['accepted'] : []} onChange={handleAgreementChange}>
<View className='agreement-row'>
<Checkbox className='agreement-checkbox' value='accepted' checked={agreementAccepted} color='#07C160' />
<View className='agreement-text-wrap'>
<Text className='agreement-text'>我已阅读并同意</Text>
<Text className='agreement-link' onClick={openUserAgreement}>《用户服务协议》</Text>
<Text className='agreement-text'>和</Text>
<Text className='agreement-link' onClick={openPrivacyPolicy}>《隐私政策》</Text>
</View>
</View>
</CheckboxGroup>
</View>
</View>
</View>
)
}