- 移除根据扫码参数自动确认登录逻辑,改为统一显示授权登录界面 - 简化用户登录判断,统一走手机号授权登录流程,提升用户体验 - 替换按钮组件由 TaroButton 改为 NutUI Button,统一样式风格 - 更新授权登录页面 UI,调整背景、品牌名和标语内容 - 优化手机号授权登录按钮样式,使用渐变色背景和中心对齐
827 lines
26 KiB
TypeScript
827 lines
26 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
||
import { View, Text } from '@tarojs/components';
|
||
import { Loading, Card, Button } from '@nutui/nutui-react-taro';
|
||
import { Success, Failure, Tips, User, Checklist } from '@nutui/icons-react-taro';
|
||
import Taro, { useRouter } from '@tarojs/taro';
|
||
import { confirmQRLogin } from '@/api/passport/qr-login';
|
||
import { loginByOpenId } from '@/api/passport/wx-login';
|
||
import { TenantId } from "@/config/app";
|
||
import { saveStorageByLoginUser, SERVER_API_URL } from "@/utils/server";
|
||
import { checkAndHandleInviteRelation, hasPendingInvite, getStoredInviteParams } from "@/utils/invite";
|
||
|
||
/**
|
||
* 扫码登录确认页面
|
||
*
|
||
* 支持两种场景:
|
||
* 1. 主动扫码:用户点击按钮调用微信扫码,扫描 PC 端二维码
|
||
* 2. URL 扫码(小程序码):用户扫描小程序码后,微信自动打开此页面
|
||
*
|
||
* URL 扫码场景:
|
||
* - 微信「扫普通链接二维码打开小程序」配置的二维码规则:`https://websopy.websoft.top/wx-scan/`
|
||
* - 扫码后 URL:`https://websopy.websoft.top/wx-scan?token=xxx`
|
||
* - 小程序接收到参数后自动确认登录
|
||
*
|
||
* 登录流程(2026-04-08 更新):
|
||
* 1. 用户扫码 → 进入 qr-confirm 页面
|
||
* 2. 页面立即调用 wx.login() 获取 code
|
||
* 3. 用 code 调用 /api/wx-login/loginByOpenId 获取/验证用户身份
|
||
* 4. 如果用户不存在 → 显示微信手机号授权按钮(一键注册登录)
|
||
* 5. 如果用户存在 → 自动调用 confirmQRLogin 确认登录
|
||
*/
|
||
|
||
// 微信获取手机号回调参数类型
|
||
interface GetPhoneNumberDetail {
|
||
code?: string;
|
||
encryptedData?: string;
|
||
iv?: string;
|
||
errMsg: string;
|
||
}
|
||
|
||
interface GetPhoneNumberEvent {
|
||
detail: GetPhoneNumberDetail;
|
||
}
|
||
|
||
// 登录接口返回数据类型
|
||
interface LoginResponse {
|
||
data: {
|
||
access_token: string;
|
||
user: any;
|
||
};
|
||
code: number;
|
||
message: string;
|
||
}
|
||
|
||
// 协议弹窗类型
|
||
type AgreementType = 'service' | 'privacy' | null;
|
||
|
||
const QRConfirmPage: React.FC = () => {
|
||
const router = useRouter();
|
||
const [loading, setLoading] = useState(false);
|
||
const [confirmed, setConfirmed] = useState(false);
|
||
const [error, setError] = useState<string>('');
|
||
const [token, setToken] = useState<string>('');
|
||
const [loginMethod, setLoginMethod] = useState<'scan' | 'url'>('url');
|
||
const [userInfo, setUserInfo] = useState<any>(null);
|
||
const [needAuth, setNeedAuth] = useState(false); // 是否需要手机号授权
|
||
const [authLoading, setAuthLoading] = useState(false); // 授权中状态
|
||
const [agreementChecked, setAgreementChecked] = useState(false); // 协议勾选状态
|
||
const [showAgreementPopup, setShowAgreementPopup] = useState<AgreementType>(null); // 协议弹窗
|
||
|
||
useEffect(() => {
|
||
// 从 URL 参数中获取 token
|
||
const params = router.params;
|
||
|
||
// 兼容多种参数名
|
||
let loginToken = params.scene || params.token || params.qrCodeKey || '';
|
||
|
||
// 如果是 q 参数(URL 编码的完整 URL),需要解析
|
||
if (params.q && !loginToken) {
|
||
try {
|
||
const decodedUrl = decodeURIComponent(params.q);
|
||
console.log('[QRConfirm] 解码后的 URL:', decodedUrl);
|
||
|
||
const url = new URL(decodedUrl);
|
||
loginToken = url.searchParams.get('token') ||
|
||
url.searchParams.get('qrCodeKey') ||
|
||
'';
|
||
|
||
setLoginMethod('url');
|
||
} catch (e) {
|
||
console.error('[QRConfirm] 解析 q 参数失败:', e);
|
||
loginToken = decodeURIComponent(params.q);
|
||
setLoginMethod('url');
|
||
}
|
||
} else if (loginToken) {
|
||
setLoginMethod('url');
|
||
}
|
||
|
||
// 扫码场景:直接显示授权登录界面
|
||
console.log('[QRConfirm] 显示授权登录界面');
|
||
setToken(loginToken);
|
||
setNeedAuth(true);
|
||
}, [router.params]);
|
||
|
||
/**
|
||
* 自动确认登录(URL 扫码场景)
|
||
*/
|
||
const handleAutoConfirm = async (loginToken: string) => {
|
||
try {
|
||
setLoading(true);
|
||
|
||
// 1. 调用微信登录获取 code
|
||
console.log('[QRConfirm] 调用 wx.login() 获取 code...');
|
||
const loginResult = await Taro.login();
|
||
|
||
if (!loginResult.code) {
|
||
throw new Error('获取微信登录凭证失败');
|
||
}
|
||
console.log('[QRConfirm] 获取到 code:', loginResult.code);
|
||
|
||
// 2. 用 code 调用后端接口验证用户身份
|
||
console.log('[QRConfirm] 调用后端 loginByOpenId...');
|
||
const wxLoginResult = await loginByOpenId({
|
||
code: loginResult.code,
|
||
tenantId: 5
|
||
});
|
||
|
||
console.log('[QRConfirm] loginByOpenId 结果:', wxLoginResult);
|
||
|
||
// 3. 统一显示授权登录界面,让用户一键授权完成登录
|
||
// 无论用户是否注册、是否绑定手机号,都走授权登录流程
|
||
console.log('[QRConfirm] 显示手机号授权登录界面');
|
||
setNeedAuth(true);
|
||
setLoading(false);
|
||
} catch (err: any) {
|
||
console.error('[QRConfirm] 自动确认登录失败:', err);
|
||
setError(err.message || '自动确认登录失败');
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 处理微信手机号授权
|
||
*/
|
||
const handleGetPhoneNumber = async ({ detail }: GetPhoneNumberEvent) => {
|
||
const { code, encryptedData, iv, errMsg } = detail;
|
||
|
||
// 检查协议是否勾选
|
||
if (!agreementChecked) {
|
||
Taro.showToast({
|
||
title: '请先同意服务协议和隐私政策',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 用户拒绝授权
|
||
if (errMsg && errMsg.includes('fail')) {
|
||
Taro.showToast({
|
||
title: '需要授权手机号才能完成登录',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (!code) {
|
||
Taro.showToast({
|
||
title: '获取授权信息失败,请重试',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 执行授权登录
|
||
await handleAuthLogin(code, encryptedData, iv);
|
||
};
|
||
|
||
/**
|
||
* 授权登录(未注册用户)
|
||
*/
|
||
const handleAuthLogin = async (phoneCode: string, encryptedData?: string, iv?: string) => {
|
||
try {
|
||
setAuthLoading(true);
|
||
|
||
// 获取存储的邀请参数
|
||
const inviteParams = getStoredInviteParams();
|
||
const refereeId = inviteParams?.inviter ? parseInt(inviteParams.inviter) : 0;
|
||
|
||
const res = await Taro.request<LoginResponse>({
|
||
url: `${SERVER_API_URL}/wx-login/loginByMpWxPhone`,
|
||
method: 'POST',
|
||
data: {
|
||
code: phoneCode,
|
||
encryptedData,
|
||
iv,
|
||
tenantId: TenantId,
|
||
notVerifyPhone: true,
|
||
refereeId: refereeId,
|
||
sceneType: 'save_referee'
|
||
},
|
||
header: {
|
||
'content-type': 'application/json',
|
||
'TenantId': TenantId
|
||
}
|
||
});
|
||
|
||
if (res.data.code !== 0) {
|
||
throw new Error(res.data.message || '登录失败');
|
||
}
|
||
|
||
// 保存登录信息
|
||
if (res.data.data?.user) {
|
||
saveStorageByLoginUser(res.data.data.access_token, res.data.data.user);
|
||
setUserInfo(res.data.data.user);
|
||
|
||
// 处理邀请关系
|
||
if (hasPendingInvite()) {
|
||
try {
|
||
await checkAndHandleInviteRelation();
|
||
} catch (e) {
|
||
console.error('授权登录后处理邀请关系失败:', e);
|
||
}
|
||
}
|
||
|
||
Taro.showToast({
|
||
title: '授权成功,正在确认登录...',
|
||
icon: 'success'
|
||
});
|
||
|
||
// 延迟后自动确认扫码登录
|
||
setTimeout(() => {
|
||
handleConfirmLogin(token, res.data.data.user);
|
||
}, 1500);
|
||
}
|
||
|
||
} catch (error: any) {
|
||
Taro.showToast({
|
||
title: error.message || '授权失败',
|
||
icon: 'error'
|
||
});
|
||
} finally {
|
||
setAuthLoading(false);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 确认登录
|
||
*/
|
||
const handleConfirmLogin = async (loginToken?: string, wxUserInfo?: any) => {
|
||
const confirmToken = loginToken || token;
|
||
|
||
if (!confirmToken) {
|
||
setError('缺少登录token');
|
||
return;
|
||
}
|
||
|
||
const currentUser = wxUserInfo || userInfo;
|
||
|
||
if (!currentUser?.userId) {
|
||
const userId = Taro.getStorageSync('UserId');
|
||
if (!userId) {
|
||
setError('请先登录小程序');
|
||
setNeedAuth(true);
|
||
return;
|
||
}
|
||
currentUser && (currentUser.userId = Number(userId));
|
||
}
|
||
|
||
try {
|
||
setLoading(true);
|
||
setError('');
|
||
|
||
const result = await confirmQRLogin({
|
||
token: confirmToken,
|
||
userId: currentUser.userId,
|
||
platform: 'wechat',
|
||
wechatInfo: {
|
||
nickname: currentUser.nickname || currentUser.username,
|
||
avatar: currentUser.avatar
|
||
}
|
||
});
|
||
|
||
const isConfirmed = result.status === 'confirmed' || result.success === true;
|
||
|
||
if (isConfirmed) {
|
||
setConfirmed(true);
|
||
setNeedAuth(false);
|
||
Taro.showToast({
|
||
title: result.successMessage || result.message || '登录确认成功',
|
||
icon: 'success',
|
||
duration: 2000
|
||
});
|
||
|
||
setTimeout(() => {
|
||
const pages = Taro.getCurrentPages();
|
||
if (pages.length > 1) {
|
||
Taro.navigateBack();
|
||
} else {
|
||
Taro.showModal({
|
||
title: '登录成功',
|
||
content: '请回到电脑端刷新页面',
|
||
showCancel: false,
|
||
confirmText: '我知道了'
|
||
});
|
||
}
|
||
}, 3000);
|
||
} else if (result.status === 'bind_phone' || result.needBindPhone) {
|
||
Taro.showToast({
|
||
title: '请先绑定手机号',
|
||
icon: 'none'
|
||
});
|
||
setTimeout(() => {
|
||
Taro.redirectTo({ url: '/passport/sms-login' });
|
||
}, 1500);
|
||
} else {
|
||
setError(result.message || '登录确认失败');
|
||
}
|
||
} catch (err: any) {
|
||
console.error('[QRConfirm] 确认登录失败:', err);
|
||
setError(err.message || '登录确认失败');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 手动确认登录(主动扫码场景)
|
||
*/
|
||
const handleManualConfirm = () => {
|
||
handleConfirmLogin();
|
||
};
|
||
|
||
/**
|
||
* 取消登录
|
||
*/
|
||
const handleCancel = () => {
|
||
const pages = Taro.getCurrentPages();
|
||
if (pages.length > 1) {
|
||
Taro.navigateBack();
|
||
} else {
|
||
Taro.switchTab({
|
||
url: '/pages/user/user'
|
||
});
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 重试
|
||
*/
|
||
const handleRetry = () => {
|
||
setError('');
|
||
setConfirmed(false);
|
||
setNeedAuth(false);
|
||
if (token) {
|
||
handleAutoConfirm(token);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 打开微信扫码
|
||
*/
|
||
const handleScan = () => {
|
||
Taro.scanCode({
|
||
success: async (res) => {
|
||
console.log('[QRConfirm] 扫码成功:', res);
|
||
|
||
let scanToken = '';
|
||
const qrContent = res.result;
|
||
|
||
try {
|
||
if (qrContent.includes('http')) {
|
||
const url = new URL(qrContent);
|
||
scanToken = url.searchParams.get('token') ||
|
||
url.searchParams.get('qrCodeKey') ||
|
||
'';
|
||
}
|
||
|
||
if (!scanToken && qrContent.startsWith('{')) {
|
||
const parsed = JSON.parse(qrContent);
|
||
scanToken = parsed.token || parsed.qrCodeKey || '';
|
||
}
|
||
|
||
if (!scanToken && qrContent.length >= 32) {
|
||
scanToken = qrContent;
|
||
}
|
||
|
||
if (scanToken) {
|
||
setToken(scanToken);
|
||
setLoginMethod('scan');
|
||
setNeedAuth(false);
|
||
setError('');
|
||
handleConfirmLogin(scanToken);
|
||
} else {
|
||
setError('无效的二维码内容');
|
||
}
|
||
} catch (e) {
|
||
console.error('[QRConfirm] 解析二维码失败:', e);
|
||
setError('二维码解析失败');
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
console.error('[QRConfirm] 扫码失败:', err);
|
||
if (err.errMsg !== 'scanCode:fail cancel') {
|
||
setError('扫码失败,请重试');
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
// 打开协议弹窗
|
||
const openAgreement = (type: AgreementType) => {
|
||
setShowAgreementPopup(type);
|
||
};
|
||
|
||
// 关闭协议弹窗
|
||
const closeAgreement = () => {
|
||
setShowAgreementPopup(null);
|
||
};
|
||
|
||
// 渲染协议弹窗内容
|
||
const renderAgreementContent = () => {
|
||
if (showAgreementPopup === 'service') {
|
||
return (
|
||
<View className="p-4">
|
||
<View className="text-center mb-4">
|
||
<Text className="text-lg font-bold">服务协议</Text>
|
||
</View>
|
||
<View className="text-sm text-gray-600 leading-relaxed max-h-64 overflow-y-auto">
|
||
<Text className="block mb-2">欢迎使用我们的服务!</Text>
|
||
<Text className="block mb-2">1. 服务条款</Text>
|
||
<Text className="block mb-2">本服务协议是您与我们之间关于使用我们提供的各项服务的协议。</Text>
|
||
<Text className="block mb-2">2. 账号注册</Text>
|
||
<Text className="block mb-2">您需要注册账号才能使用我们的部分服务。注册时您需要提供真实、准确的信息。</Text>
|
||
<Text className="block mb-2">3. 服务使用</Text>
|
||
<Text className="block mb-2">您同意遵守相关法律法规,不得利用我们的服务从事违法违规活动。</Text>
|
||
<Text className="block mb-2">4. 隐私保护</Text>
|
||
<Text className="block mb-2">我们重视您的隐私保护,具体请参见《隐私政策》。</Text>
|
||
<Text className="block mb-2">5. 协议修改</Text>
|
||
<Text className="block mb-2">我们有权在必要时修改本协议,修改后的协议将在公布时立即生效。</Text>
|
||
</View>
|
||
<Button
|
||
className="mt-4 bg-orange-500 text-white rounded-full"
|
||
onClick={closeAgreement}
|
||
>
|
||
我知道了
|
||
</Button>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
if (showAgreementPopup === 'privacy') {
|
||
return (
|
||
<View className="p-4">
|
||
<View className="text-center mb-4">
|
||
<Text className="text-lg font-bold">隐私政策</Text>
|
||
</View>
|
||
<View className="text-sm text-gray-600 leading-relaxed max-h-64 overflow-y-auto">
|
||
<Text className="block mb-2">我们非常重视您的隐私保护。</Text>
|
||
<Text className="block mb-2">1. 信息收集</Text>
|
||
<Text className="block mb-2">我们可能会收集您的手机号、微信信息等,用于提供服务和身份验证。</Text>
|
||
<Text className="block mb-2">2. 信息使用</Text>
|
||
<Text className="block mb-2">我们仅将您的信息用于提供服务、改进用户体验和保障账号安全。</Text>
|
||
<Text className="block mb-2">3. 信息保护</Text>
|
||
<Text className="block mb-2">我们采用行业标准的加密技术保护您的信息安全。</Text>
|
||
<Text className="block mb-2">4. 信息共享</Text>
|
||
<Text className="block mb-2">我们不会将您的个人信息出售或分享给第三方,除非获得您的同意或法律法规要求。</Text>
|
||
<Text className="block mb-2">5. 联系我们</Text>
|
||
<Text className="block mb-2">如有任何隐私相关问题,请联系我们。</Text>
|
||
</View>
|
||
<Button
|
||
className="mt-4 bg-orange-500 text-white rounded-full"
|
||
onClick={closeAgreement}
|
||
>
|
||
我知道了
|
||
</Button>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
return null;
|
||
};
|
||
|
||
// 授权登录页面(参考权大师风格)
|
||
const renderAuthPage = () => {
|
||
return (
|
||
<View className="min-h-screen bg-gray-50 flex flex-col">
|
||
|
||
{/* Logo 区域 */}
|
||
<View className="flex-1 flex flex-col items-center justify-center px-8 -mt-20">
|
||
{/* Logo */}
|
||
<View className="w-20 h-20 mb-4">
|
||
|
||
</View>
|
||
|
||
{/* 品牌名 */}
|
||
<Text className="text-2xl font-bold text-gray-900 mb-2">websopy</Text>
|
||
|
||
{/* 标语 */}
|
||
<Text className="text-sm text-gray-500 tracking-widest">构建下一代 AI 应用</Text>
|
||
</View>
|
||
|
||
{/* 底部操作区域 */}
|
||
<View className="px-6 pb-12">
|
||
{/* 主按钮 - 手机号授权登录 */}
|
||
<div className={'flex flex-col w-full text-white rounded-full justify-between items-center my-2'} style={{ background: 'linear-gradient(to right, #7e22ce, #9333ea)'}}>
|
||
<Button open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
|
||
授权手机号登录
|
||
</Button>
|
||
</div>
|
||
{/*<button*/}
|
||
{/* className="w-full h-12 bg-orange-500 text-white text-base font-medium rounded-lg flex items-center justify-center mb-4 border-0"*/}
|
||
{/* open-type="getPhoneNumber"*/}
|
||
{/* onGetPhoneNumber={handleGetPhoneNumber}*/}
|
||
{/* disabled={authLoading}*/}
|
||
{/*>*/}
|
||
{/* {authLoading ? '授权中...' : '手机号授权登录'}*/}
|
||
{/*</button>*/}
|
||
|
||
{/* 取消按钮 */}
|
||
<View
|
||
className="w-full h-12 flex items-center justify-center text-gray-500 text-base cursor-pointer"
|
||
onClick={handleCancel}
|
||
>
|
||
取消
|
||
</View>
|
||
|
||
{/* 协议勾选 */}
|
||
<View className="flex items-center justify-center mt-6">
|
||
<View
|
||
className={`w-4 h-4 rounded-full border-2 flex items-center justify-center mr-2 ${
|
||
agreementChecked ? 'bg-orange-500 border-orange-500' : 'border-gray-300'
|
||
}`}
|
||
onClick={() => setAgreementChecked(!agreementChecked)}
|
||
>
|
||
{agreementChecked && (
|
||
<Checklist size="12" color="#fff" />
|
||
)}
|
||
</View>
|
||
<Text className="text-xs text-gray-500">
|
||
我已阅读并同意权大师的
|
||
</Text>
|
||
<Text
|
||
className="text-xs text-orange-500 ml-1"
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
openAgreement('service');
|
||
}}
|
||
>
|
||
《服务协议》
|
||
</Text>
|
||
<Text className="text-xs text-gray-500">和</Text>
|
||
<Text
|
||
className="text-xs text-orange-500 ml-1"
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
openAgreement('privacy');
|
||
}}
|
||
>
|
||
《隐私政策》
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
|
||
{/* 协议弹窗 */}
|
||
{showAgreementPopup && (
|
||
<View className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 px-6">
|
||
<View className="bg-white rounded-xl w-full max-w-sm">
|
||
{renderAgreementContent()}
|
||
</View>
|
||
</View>
|
||
)}
|
||
</View>
|
||
);
|
||
};
|
||
|
||
// 渲染状态图标
|
||
const renderStatusIcon = () => {
|
||
if (loading || authLoading) return (
|
||
<View className="mb-6">
|
||
<View className="w-16 h-16 mx-auto flex items-center justify-center">
|
||
<Loading className="text-blue-500" />
|
||
</View>
|
||
</View>
|
||
);
|
||
|
||
if (confirmed) return (
|
||
<View className="mb-6">
|
||
<View className="w-16 h-16 mx-auto bg-green-100 rounded-full flex items-center justify-center">
|
||
<Success className="text-green-500" size="32" />
|
||
</View>
|
||
</View>
|
||
);
|
||
|
||
if (error && !needAuth) return (
|
||
<View className="mb-6">
|
||
<View className="w-16 h-16 mx-auto bg-red-100 rounded-full flex items-center justify-center">
|
||
<Failure className="text-red-500" size="32" />
|
||
</View>
|
||
</View>
|
||
);
|
||
|
||
if (needAuth) {
|
||
// 需要授权时显示授权页面
|
||
return renderAuthPage();
|
||
}
|
||
|
||
return (
|
||
<View className="mb-6">
|
||
<View className="w-16 h-16 mx-auto bg-blue-100 rounded-full flex items-center justify-center">
|
||
<User size="32" className="text-blue-500" />
|
||
</View>
|
||
</View>
|
||
);
|
||
};
|
||
|
||
// 获取标题
|
||
const getTitle = () => {
|
||
if (loading || authLoading) return '正在处理...';
|
||
if (confirmed) return '登录确认成功';
|
||
if (needAuth) return ''; // 授权页面有自己的标题
|
||
if (error) return '登录确认失败';
|
||
return loginMethod === 'url' ? '扫码登录确认' : '确认登录';
|
||
};
|
||
|
||
// 获取描述
|
||
const getDescription = () => {
|
||
if (loading) return '请稍候,正在为您确认登录';
|
||
if (authLoading) return '正在授权登录...';
|
||
if (confirmed) return '您已成功确认登录,网页端将自动登录';
|
||
if (needAuth) return ''; // 授权页面有自己的描述
|
||
if (error) return error;
|
||
if (loginMethod === 'url') {
|
||
return '检测到登录请求,是否确认登录?';
|
||
}
|
||
const displayName = userInfo?.nickname || userInfo?.username || '当前用户';
|
||
return `确认使用 ${displayName} 登录网页端?`;
|
||
};
|
||
|
||
// 渲染操作按钮
|
||
const renderActions = () => {
|
||
// 需要授权登录时,按钮在授权页面内部渲染
|
||
if (needAuth) {
|
||
return null;
|
||
}
|
||
|
||
if (loading) {
|
||
return (
|
||
<View
|
||
className="w-full h-12 bg-gray-300 text-white text-base font-medium rounded-lg flex items-center justify-center"
|
||
>
|
||
确认中...
|
||
</View>
|
||
);
|
||
}
|
||
|
||
if (confirmed) {
|
||
return (
|
||
<View
|
||
className="w-full h-12 bg-green-500 text-white text-base font-medium rounded-lg flex items-center justify-center"
|
||
onClick={handleCancel}
|
||
>
|
||
完成
|
||
</View>
|
||
);
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<View className="space-y-3">
|
||
<View
|
||
className="w-full h-12 bg-blue-500 text-white text-base font-medium rounded-lg flex items-center justify-center"
|
||
onClick={handleRetry}
|
||
>
|
||
重试
|
||
</View>
|
||
<View
|
||
className="w-full h-12 bg-gray-100 text-gray-700 text-base font-medium rounded-lg flex items-center justify-center"
|
||
onClick={handleScan}
|
||
>
|
||
扫码其他二维码
|
||
</View>
|
||
<View
|
||
className="w-full h-10 text-gray-500 text-sm flex items-center justify-center"
|
||
onClick={handleCancel}
|
||
>
|
||
取消
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
if (loginMethod === 'scan') {
|
||
return (
|
||
<View className="space-y-3">
|
||
<View
|
||
className="w-full h-12 bg-blue-500 text-white text-base font-medium rounded-lg flex items-center justify-center"
|
||
onClick={handleManualConfirm}
|
||
>
|
||
确认登录
|
||
</View>
|
||
<View
|
||
className="w-full h-12 text-gray-500 text-base flex items-center justify-center"
|
||
onClick={handleCancel}
|
||
>
|
||
取消
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<View
|
||
className="w-full h-12 bg-blue-500 text-white text-base font-medium rounded-lg flex items-center justify-center"
|
||
onClick={handleManualConfirm}
|
||
>
|
||
确认登录
|
||
</View>
|
||
);
|
||
};
|
||
|
||
// 如果是授权页面,直接返回授权页面
|
||
if (needAuth) {
|
||
return renderAuthPage();
|
||
}
|
||
|
||
return (
|
||
<View className="min-h-screen bg-gradient-to-b from-blue-50 to-white">
|
||
<View className="p-4">
|
||
{/* Logo/品牌区域 */}
|
||
<View className="text-center pt-8 pb-6">
|
||
<View className="w-20 h-20 mx-auto bg-white rounded-2xl shadow-lg flex items-center justify-center mb-4">
|
||
<Text className="text-3xl">🔐</Text>
|
||
</View>
|
||
<Text className="text-gray-400 text-sm">WebSoft Platform</Text>
|
||
</View>
|
||
|
||
{/* 主要内容卡片 */}
|
||
<Card className="bg-white rounded-2xl shadow-xl -mt-4">
|
||
<View className="p-6 text-center">
|
||
{/* 状态图标 */}
|
||
{renderStatusIcon()}
|
||
|
||
{/* 标题 */}
|
||
{getTitle() && (
|
||
<Text className="text-xl font-bold text-gray-800 mb-2 block">
|
||
{getTitle()}
|
||
</Text>
|
||
)}
|
||
|
||
{/* 描述 */}
|
||
{getDescription() && (
|
||
<Text className="text-gray-600 mb-6 block text-sm">
|
||
{getDescription()}
|
||
</Text>
|
||
)}
|
||
|
||
{/* 用户信息 */}
|
||
{!loading && !confirmed && !error && !needAuth && userInfo && (
|
||
<View className="bg-gray-50 rounded-xl p-4 mb-6">
|
||
<View className="flex items-center justify-center">
|
||
{userInfo.avatar ? (
|
||
<View
|
||
className="w-12 h-12 rounded-full bg-blue-100 mr-3 overflow-hidden"
|
||
style={{ backgroundImage: `url(${userInfo.avatar})`, backgroundSize: 'cover' }}
|
||
/>
|
||
) : (
|
||
<View className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center mr-3">
|
||
<User className="text-blue-500" size="20" />
|
||
</View>
|
||
)}
|
||
<View className="text-left">
|
||
<Text className="text-sm font-medium text-gray-800 block">
|
||
{userInfo.nickname || userInfo.username || '用户'}
|
||
</Text>
|
||
<Text className="text-xs text-gray-500 block">
|
||
ID: {userInfo.userId}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
)}
|
||
|
||
{/* Token 信息 */}
|
||
{token && !loading && !confirmed && !needAuth && (
|
||
<View className="bg-blue-50 rounded-lg p-3 mb-4">
|
||
<Text className="text-xs text-blue-600">
|
||
登录令牌:{token.substring(0, 20)}...{token.substring(token.length - 10)}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
|
||
{/* 操作按钮 */}
|
||
{renderActions()}
|
||
</View>
|
||
</Card>
|
||
|
||
{/* 安全提示 */}
|
||
{!needAuth && (
|
||
<Card className="bg-yellow-50 border border-yellow-200 rounded-xl mt-4">
|
||
<View className="p-4">
|
||
<View className="flex items-start">
|
||
<Tips className="text-yellow-600 mr-2 mt-1" size="16" />
|
||
<View>
|
||
<Text className="text-sm font-medium text-yellow-800 mb-1 block">
|
||
安全提示
|
||
</Text>
|
||
<Text className="text-xs text-yellow-700 block">
|
||
请确认这是您本人的登录操作。如果不是,请点击取消并检查账户安全。
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
</Card>
|
||
)}
|
||
|
||
{/* 底部说明 */}
|
||
<View className="text-center mt-6 pb-8">
|
||
<Text className="text-xs text-gray-400">
|
||
如有问题,请联系管理员
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
);
|
||
};
|
||
|
||
export default QRConfirmPage;
|