fix(api): 替换所有接口请求的域名为 guilixu-api.websoft.top
- 将获取 STS Token 的接口地址由 paopao-api 改为 guilixu-api - 更新图片上传接口地址为新的 guilixu-api 域名 - 修改用户推广页面中邀请码链接和二维码接口的域名 - 更改注册页微信登录接口请求的域名为 guilixu-api
This commit is contained in:
339
src/passport/login.tsx
Normal file
339
src/passport/login.tsx
Normal file
@@ -0,0 +1,339 @@
|
||||
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 {
|
||||
checkAndHandleInviteRelation,
|
||||
hasPendingInvite,
|
||||
parseInviteParams,
|
||||
saveInviteParams,
|
||||
trackInviteSource,
|
||||
} from '@/utils/invite'
|
||||
import logoImg from '@/assets/logo.png'
|
||||
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<string | undefined> {
|
||||
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<string, string> | 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/points/index',
|
||||
'/pages/user/user',
|
||||
]
|
||||
const pure = redirectUrl.split('?')[0]
|
||||
|
||||
if (tabBarUrls.includes(pure)) {
|
||||
await Taro.switchTab({ url: pure })
|
||||
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, errMsg } = detail || {}
|
||||
if (!phoneCode || (errMsg && errMsg.includes('fail'))) {
|
||||
Taro.showToast({ title: '未授权手机号', icon: 'none' })
|
||||
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
|
||||
}
|
||||
|
||||
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 (
|
||||
<View className={`page-login ${showContent ? 'page-login--show' : ''}`}>
|
||||
{/* 渐变背景 */}
|
||||
<View className='login-bg'>
|
||||
<View className='login-bg__gradient' />
|
||||
<View className='login-bg__circle login-bg__circle--1' />
|
||||
<View className='login-bg__circle login-bg__circle--2' />
|
||||
<View className='login-bg__circle login-bg__circle--3' />
|
||||
</View>
|
||||
|
||||
{/* 主要内容区域 */}
|
||||
<View className='login-content'>
|
||||
{/* Logo 和标题 */}
|
||||
<View className='login-header'>
|
||||
<View className='login-logo'>
|
||||
<Image
|
||||
className='login-logo__image'
|
||||
src={logoImg}
|
||||
mode='aspectFit'
|
||||
/>
|
||||
</View>
|
||||
<Text className='login-title'>欢迎来到鑫龙家电</Text>
|
||||
<Text className='login-subtitle'>登录后享受更多精彩功能</Text>
|
||||
</View>
|
||||
|
||||
{/* 登录按钮区域 */}
|
||||
<View className='login-body'>
|
||||
{isWeapp && (
|
||||
<View className='login-methods'>
|
||||
|
||||
<Button
|
||||
className='login-btn'
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #07c160, #06ad56)',
|
||||
border: 'none',
|
||||
borderRadius: '48px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: '#ffffff',
|
||||
fontWeight: '600',
|
||||
textAlign: 'center',
|
||||
opacity: (!isAgree || loading) ? 0.5 : 1,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
}}
|
||||
openType='getPhoneNumber'
|
||||
onGetPhoneNumber={handleGetPhoneNumber}
|
||||
disabled={!isAgree || loading}
|
||||
loading={loading}
|
||||
>
|
||||
手机号快捷登录
|
||||
</Button>
|
||||
|
||||
<View className='login-methods__tip text-gray-200'>
|
||||
<Text>点击上方按钮,快速登录</Text>
|
||||
</View>
|
||||
|
||||
<View className='login-methods__divider'>
|
||||
<View className='divider-line' />
|
||||
<Text className='divider-text'>安全快捷</Text>
|
||||
<View className='divider-line' />
|
||||
</View>
|
||||
|
||||
<View className='login-features'>
|
||||
<View className='feature-item'>
|
||||
<Text className='feature-icon'>🔒</Text>
|
||||
<Text className='feature-text'>安全可靠</Text>
|
||||
</View>
|
||||
<View className='feature-item'>
|
||||
<Text className='feature-icon'>⚡</Text>
|
||||
<Text className='feature-text'>一键登录</Text>
|
||||
</View>
|
||||
<View className='feature-item'>
|
||||
<Text className='feature-icon'>🎁</Text>
|
||||
<Text className='feature-text'>专属福利</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 非微信小程序环境提示 */}
|
||||
{!isWeapp && (
|
||||
<View className='login-non-weapp'>
|
||||
<View className='non-weapp-icon'>💻</View>
|
||||
<Text className='non-weapp-title'>请在微信小程序中打开</Text>
|
||||
<Text className='non-weapp-desc'>当前环境不支持手机号快捷登录,请在微信小程序中使用完整功能</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 协议勾选 - 自定义实现 */}
|
||||
<View className='login-footer'>
|
||||
<View className='login-agreement' onClick={() => setIsAgree(!isAgree)}>
|
||||
<View className={`login-agreement__checkbox ${isAgree ? 'login-agreement__checkbox--checked' : ''}`}>
|
||||
{isAgree && <Text className='login-agreement__check'>✓</Text>}
|
||||
</View>
|
||||
<Text className='login-agreement__text'>
|
||||
{'我已阅读并同意'}
|
||||
<Text className='link' onClick={(e) => { e.stopPropagation(); Taro.navigateTo({ url: '/passport/agreement?type=terms' }) }}>
|
||||
{'《用户协议》'}
|
||||
</Text>
|
||||
{'和'}
|
||||
<Text className='link' onClick={(e) => { e.stopPropagation(); Taro.navigateTo({ url: '/passport/agreement?type=privacy' }) }}>
|
||||
{'《隐私政策》'}
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default Login
|
||||
Reference in New Issue
Block a user