fix(api): 替换所有接口请求的域名为 guilixu-api.websoft.top

- 将获取 STS Token 的接口地址由 paopao-api 改为 guilixu-api
- 更新图片上传接口地址为新的 guilixu-api 域名
- 修改用户推广页面中邀请码链接和二维码接口的域名
- 更改注册页微信登录接口请求的域名为 guilixu-api
This commit is contained in:
2026-06-16 17:15:59 +08:00
commit f3886664f7
617 changed files with 77059 additions and 0 deletions

339
src/passport/login.tsx Normal file
View 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