refactor(qr-confirm): 简化二维码确认登录页面逻辑
- 移除未使用的组件和图标导入,减少包大小 - 注释掉登录卡片相关样式和元素,隐藏界面内容 - 删除手动确认登录函数及相关按钮渲染逻辑 - 去除状态图标、标题、描述和用户信息的渲染函数 - 删除所有操作按钮和安全提示组件渲染 - 取消整体页面布局,仅直接渲染授权页面部分 - 修改品牌文本首字母小写,统一风格
This commit is contained in:
@@ -30,10 +30,10 @@
|
||||
"profession": "高级开发工程师",
|
||||
"avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/SeniorDeveloper/SeniorDeveloper.png",
|
||||
"promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/SeniorDeveloper/SeniorDeveloper_zh.md",
|
||||
"usedAt": 1775819465726,
|
||||
"usedAt": 1775866025894,
|
||||
"industryId": "all"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lastUpdated": 1775819525128
|
||||
"lastUpdated": 1775867452878
|
||||
}
|
||||
@@ -1,31 +1,13 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import { Button } from '@nutui/nutui-react-taro';
|
||||
import { View, Text, Button } from '@tarojs/components';
|
||||
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 确认登录
|
||||
* 用户扫描 PC 端二维码后,打开此小程序页面进行微信手机号授权登录
|
||||
*/
|
||||
|
||||
// 微信获取手机号回调参数类型
|
||||
@@ -51,91 +33,33 @@ interface LoginResponse {
|
||||
}
|
||||
|
||||
// 协议类型
|
||||
type AgreementType = 'service' | 'privacy' | null;
|
||||
type AgreementType = 'service' | 'privacy';
|
||||
|
||||
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 [token, setToken] = useState<string>(''); // 登录 token
|
||||
|
||||
useEffect(() => {
|
||||
// 从 URL 参数中获取 token
|
||||
const params = router.params;
|
||||
|
||||
// 兼容多种参数名
|
||||
let loginToken = params.scene || params.token || params.qrCodeKey || '';
|
||||
|
||||
// 如果是 q 参数(URL 编码的完整 URL),需要解析
|
||||
// 兼容 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');
|
||||
loginToken = url.searchParams.get('token') || url.searchParams.get('qrCodeKey') || '';
|
||||
} 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 (_: 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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理微信手机号授权
|
||||
*/
|
||||
@@ -144,42 +68,32 @@ const QRConfirmPage: React.FC = () => {
|
||||
|
||||
// 检查协议是否勾选
|
||||
if (!agreementChecked) {
|
||||
Taro.showToast({
|
||||
title: '请先同意服务协议和隐私政策',
|
||||
icon: 'none'
|
||||
});
|
||||
Taro.showToast({ title: '请先同意服务协议和隐私政策', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 用户拒绝授权
|
||||
if (errMsg && errMsg.includes('fail')) {
|
||||
Taro.showToast({
|
||||
title: '需要授权手机号才能完成登录',
|
||||
icon: 'none'
|
||||
});
|
||||
Taro.showToast({ title: '需要授权手机号才能完成登录', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!code) {
|
||||
Taro.showToast({
|
||||
title: '获取授权信息失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
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;
|
||||
|
||||
@@ -190,133 +104,82 @@ const QRConfirmPage: React.FC = () => {
|
||||
code: phoneCode,
|
||||
encryptedData,
|
||||
iv,
|
||||
tenantId: TenantId,
|
||||
tenantId: 5,
|
||||
notVerifyPhone: true,
|
||||
refereeId: refereeId,
|
||||
refereeId,
|
||||
sceneType: 'save_referee'
|
||||
},
|
||||
header: {
|
||||
'content-type': 'application/json',
|
||||
'TenantId': TenantId
|
||||
}
|
||||
header: { 'content-type': 'application/json', 'TenantId': 5 }
|
||||
});
|
||||
|
||||
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);
|
||||
console.error('处理邀请关系失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
Taro.showToast({
|
||||
title: '授权成功,正在确认登录...',
|
||||
icon: 'success'
|
||||
});
|
||||
Taro.showToast({ title: '授权成功,正在确认登录...', icon: 'success' });
|
||||
|
||||
// 延迟后自动确认扫码登录
|
||||
setTimeout(() => {
|
||||
handleConfirmLogin(token, res.data.data.user);
|
||||
}, 1500);
|
||||
// 延迟确认扫码登录
|
||||
setTimeout(() => handleConfirmQRLogin(res.data.data.user), 1500);
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
Taro.showToast({
|
||||
title: error.message || '授权失败',
|
||||
icon: 'error'
|
||||
});
|
||||
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');
|
||||
const handleConfirmQRLogin = async (userInfo: any) => {
|
||||
if (!token) {
|
||||
Taro.showToast({ title: '缺少登录token', icon: 'none' });
|
||||
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 res = await Taro.request({
|
||||
url: `${SERVER_API_URL}/qr-login/confirm`,
|
||||
method: 'POST',
|
||||
data: {
|
||||
token,
|
||||
userId: userInfo.userId,
|
||||
platform: 'wechat',
|
||||
wechatInfo: {
|
||||
nickname: userInfo.nickname || userInfo.username,
|
||||
avatar: userInfo.avatar
|
||||
}
|
||||
},
|
||||
header: { 'content-type': 'application/json', 'TenantId': 5 }
|
||||
});
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
if (res.data.success || res.data.status === 'confirmed') {
|
||||
Taro.showToast({ title: '登录确认成功', 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);
|
||||
Taro.showModal({
|
||||
title: '登录成功',
|
||||
content: '请回到电脑端刷新页面',
|
||||
showCancel: false,
|
||||
confirmText: '我知道了'
|
||||
});
|
||||
}, 2000);
|
||||
} else {
|
||||
setError(result.message || '登录确认失败');
|
||||
Taro.showToast({ title: res.data.message || '登录确认失败', icon: 'none' });
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('[QRConfirm] 确认登录失败:', err);
|
||||
setError(err.message || '登录确认失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
Taro.showToast({ title: err.message || '确认登录失败', icon: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -328,411 +191,167 @@ const QRConfirmPage: React.FC = () => {
|
||||
if (pages.length > 1) {
|
||||
Taro.navigateBack();
|
||||
} else {
|
||||
Taro.switchTab({
|
||||
url: '/pages/user/user'
|
||||
});
|
||||
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) => {
|
||||
const urlMap = {
|
||||
service: 'https://websopy.websoft.top/agreement',
|
||||
privacy: 'https://websopy.websoft.top/privacy',
|
||||
};
|
||||
if (!type) return;
|
||||
const targetUrl = encodeURIComponent(urlMap[type]);
|
||||
Taro.navigateTo({
|
||||
url: `/passport/webview/index?url=${targetUrl}`
|
||||
});
|
||||
Taro.navigateTo({ url: `/passport/webview/index?url=${targetUrl}` });
|
||||
};
|
||||
|
||||
// 授权登录页面 - 科技风格
|
||||
const renderAuthPage = () => {
|
||||
return (
|
||||
<View className="min-h-screen relative overflow-hidden" style={{ background: 'linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%)' }}>
|
||||
// 科技风格授权页面
|
||||
return (
|
||||
<View className="min-h-screen relative overflow-hidden" style={{ background: 'linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%)' }}>
|
||||
|
||||
{/* 背景科技元素 */}
|
||||
{/* 背景科技元素 */}
|
||||
{/* 网格背景 */}
|
||||
<View style={{
|
||||
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
|
||||
backgroundImage: `linear-gradient(rgba(138, 43, 226, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(138, 43, 226, 0.1) 1px, transparent 1px)`,
|
||||
backgroundSize: '50px 50px', opacity: 0.5,
|
||||
}} />
|
||||
|
||||
{/* 1. 网格背景 */}
|
||||
<View style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundImage: `
|
||||
linear-gradient(rgba(138, 43, 226, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(138, 43, 226, 0.1) 1px, transparent 1px)
|
||||
`,
|
||||
backgroundSize: '50px 50px',
|
||||
opacity: 0.5,
|
||||
{/* 渐变光晕 - 左上 */}
|
||||
<View style={{
|
||||
position: 'absolute', top: '-30%', left: '-20%', width: '60%', height: '60%',
|
||||
background: 'radial-gradient(circle, rgba(147, 51, 234, 0.3) 0%, transparent 70%)',
|
||||
filter: 'blur(40px)',
|
||||
}} />
|
||||
|
||||
{/* 渐变光晕 - 右下 */}
|
||||
<View style={{
|
||||
position: 'absolute', bottom: '-20%', right: '-20%', width: '50%', height: '50%',
|
||||
background: 'radial-gradient(circle, rgba(59, 130, 246, 0.25) 0%, transparent 70%)',
|
||||
filter: 'blur(50px)',
|
||||
}} />
|
||||
|
||||
{/* 动态粒子光点 */}
|
||||
{[
|
||||
{ top: '15%', left: '20%', size: 4, delay: '0s' },
|
||||
{ top: '25%', left: '80%', size: 3, delay: '0.5s' },
|
||||
{ top: '40%', left: '15%', size: 5, delay: '1s' },
|
||||
{ top: '35%', left: '85%', size: 3, delay: '1.5s' },
|
||||
{ top: '55%', left: '10%', size: 4, delay: '2s' },
|
||||
{ top: '60%', left: '90%', size: 3, delay: '0.3s' },
|
||||
{ top: '75%', left: '25%', size: 4, delay: '1.2s' },
|
||||
{ top: '80%', left: '75%', size: 5, delay: '0.8s' },
|
||||
].map((particle, index) => (
|
||||
<View key={index} className="particle" style={{
|
||||
position: 'absolute', top: particle.top, left: particle.left,
|
||||
width: particle.size, height: particle.size, borderRadius: '50%',
|
||||
background: index % 2 === 0 ? '#a855f7' : '#3b82f6',
|
||||
boxShadow: index % 2 === 0
|
||||
? '0 0 10px rgba(168, 85, 247, 0.8), 0 0 20px rgba(168, 85, 247, 0.4)'
|
||||
: '0 0 10px rgba(59, 130, 246, 0.8), 0 0 20px rgba(59, 130, 246, 0.4)',
|
||||
animationDelay: particle.delay,
|
||||
}} />
|
||||
))}
|
||||
|
||||
{/* 2. 渐变光晕 - 左上 */}
|
||||
<View style={{
|
||||
position: 'absolute',
|
||||
top: '-30%',
|
||||
left: '-20%',
|
||||
width: '60%',
|
||||
height: '60%',
|
||||
background: 'radial-gradient(circle, rgba(147, 51, 234, 0.3) 0%, transparent 70%)',
|
||||
filter: 'blur(40px)',
|
||||
}} />
|
||||
{/* 扫描线效果 */}
|
||||
<View style={{
|
||||
position: 'absolute', top: 0, left: 0, right: 0, height: '2px',
|
||||
background: 'linear-gradient(90deg, transparent, rgba(168, 85, 247, 0.6), transparent)',
|
||||
animation: 'scanline 3s ease-in-out infinite',
|
||||
}} />
|
||||
|
||||
{/* 3. 渐变光晕 - 右下 */}
|
||||
<View style={{
|
||||
position: 'absolute',
|
||||
bottom: '-20%',
|
||||
right: '-20%',
|
||||
width: '50%',
|
||||
height: '50%',
|
||||
background: 'radial-gradient(circle, rgba(59, 130, 246, 0.25) 0%, transparent 70%)',
|
||||
filter: 'blur(50px)',
|
||||
}} />
|
||||
{/* 主内容区域 */}
|
||||
<View className="relative z-10 flex flex-col items-center justify-center min-h-screen px-8">
|
||||
|
||||
{/* 4. 动态粒子光点 */}
|
||||
{[
|
||||
{ top: '15%', left: '20%', size: 4, delay: '0s' },
|
||||
{ top: '25%', left: '80%', size: 3, delay: '0.5s' },
|
||||
{ top: '40%', left: '15%', size: 5, delay: '1s' },
|
||||
{ top: '35%', left: '85%', size: 3, delay: '1.5s' },
|
||||
{ top: '55%', left: '10%', size: 4, delay: '2s' },
|
||||
{ top: '60%', left: '90%', size: 3, delay: '0.3s' },
|
||||
{ top: '75%', left: '25%', size: 4, delay: '1.2s' },
|
||||
{ top: '80%', left: '75%', size: 5, delay: '0.8s' },
|
||||
].map((particle, index) => (
|
||||
<View
|
||||
key={index}
|
||||
className="particle"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: particle.top,
|
||||
left: particle.left,
|
||||
width: particle.size,
|
||||
height: particle.size,
|
||||
borderRadius: '50%',
|
||||
background: index % 2 === 0 ? '#a855f7' : '#3b82f6',
|
||||
boxShadow: index % 2 === 0
|
||||
? '0 0 10px rgba(168, 85, 247, 0.8), 0 0 20px rgba(168, 85, 247, 0.4)'
|
||||
: '0 0 10px rgba(59, 130, 246, 0.8), 0 0 20px rgba(59, 130, 246, 0.4)',
|
||||
animationDelay: particle.delay,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* 5. 扫描线效果 */}
|
||||
<View style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: '2px',
|
||||
background: 'linear-gradient(90deg, transparent, rgba(168, 85, 247, 0.6), transparent)',
|
||||
animation: 'scanline 3s ease-in-out infinite',
|
||||
}} />
|
||||
|
||||
{/* 主内容区域 */}
|
||||
<View className="relative z-10 flex flex-col items-center justify-center min-h-screen px-8">
|
||||
|
||||
{/* Logo 区域 */}
|
||||
<View className="flex flex-col items-center mb-12">
|
||||
{/* 科技感 Logo 容器 */}
|
||||
<View style={{
|
||||
width: '100px',
|
||||
height: '100px',
|
||||
borderRadius: '24px',
|
||||
background: 'linear-gradient(135deg, rgba(168, 85, 247, 0.2) 0%, rgba(59, 130, 246, 0.2) 100%)',
|
||||
border: '1px solid rgba(168, 85, 247, 0.3)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: '20px',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
{/* Logo 内光效 */}
|
||||
<View style={{
|
||||
position: 'absolute',
|
||||
top: '-50%',
|
||||
left: '-50%',
|
||||
width: '200%',
|
||||
height: '200%',
|
||||
background: 'conic-gradient(from 0deg, transparent, rgba(168, 85, 247, 0.1), transparent, rgba(59, 130, 246, 0.1), transparent)',
|
||||
animation: 'rotate 4s linear infinite',
|
||||
}} />
|
||||
{/* Logo 图标 */}
|
||||
<Text style={{ fontSize: '48px', position: 'relative', zIndex: 1 }}>🔐</Text>
|
||||
</View>
|
||||
|
||||
{/* 品牌名 - 渐变文字 */}
|
||||
<Text style={{
|
||||
fontSize: '28px',
|
||||
fontWeight: '700',
|
||||
background: 'linear-gradient(90deg, #a855f7, #6366f1, #3b82f6)',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
backgroundClip: 'text',
|
||||
marginBottom: '8px',
|
||||
}}>websopy</Text>
|
||||
|
||||
{/* 标语 */}
|
||||
<Text style={{
|
||||
fontSize: '14px',
|
||||
color: 'rgba(255, 255, 255, 0.6)',
|
||||
letterSpacing: '2px',
|
||||
}}>智能连接 · 安全登录</Text>
|
||||
|
||||
{/* 分割线 */}
|
||||
<View style={{
|
||||
width: '60px',
|
||||
height: '3px',
|
||||
background: 'linear-gradient(90deg, transparent, #a855f7, transparent)',
|
||||
marginTop: '20px',
|
||||
borderRadius: '2px',
|
||||
}} />
|
||||
</View>
|
||||
|
||||
{/* 登录卡片 */}
|
||||
{/*<View style={{*/}
|
||||
{/* width: '100%',*/}
|
||||
{/* maxWidth: '320px',*/}
|
||||
{/* background: 'rgba(255, 255, 255, 0.05)',*/}
|
||||
{/* backdropFilter: 'blur(20px)',*/}
|
||||
{/* WebkitBackdropFilter: 'blur(20px)',*/}
|
||||
{/* borderRadius: '20px',*/}
|
||||
{/* border: '1px solid rgba(255, 255, 255, 0.1)',*/}
|
||||
{/* padding: '24px',*/}
|
||||
{/* marginBottom: '24px',*/}
|
||||
{/*}}>*/}
|
||||
{/* /!* 提示图标 *!/*/}
|
||||
{/* <View style={{*/}
|
||||
{/* display: 'flex',*/}
|
||||
{/* alignItems: 'center',*/}
|
||||
{/* justifyContent: 'center',*/}
|
||||
{/* marginBottom: '16px',*/}
|
||||
{/* }}>*/}
|
||||
{/* <View style={{*/}
|
||||
{/* width: '48px',*/}
|
||||
{/* height: '48px',*/}
|
||||
{/* borderRadius: '50%',*/}
|
||||
{/* background: 'linear-gradient(135deg, rgba(168, 85, 247, 0.3), rgba(59, 130, 246, 0.3))',*/}
|
||||
{/* display: 'flex',*/}
|
||||
{/* alignItems: 'center',*/}
|
||||
{/* justifyContent: 'center',*/}
|
||||
{/* border: '1px solid rgba(168, 85, 247, 0.4)',*/}
|
||||
{/* }}>*/}
|
||||
{/* <Text style={{ fontSize: '24px' }}>📱</Text>*/}
|
||||
{/* </View>*/}
|
||||
{/* </View>*/}
|
||||
|
||||
{/* /!* 标题 *!/*/}
|
||||
{/* <Text style={{*/}
|
||||
{/* textAlign: 'center',*/}
|
||||
{/* color: '#fff',*/}
|
||||
{/* fontSize: '18px',*/}
|
||||
{/* fontWeight: '600',*/}
|
||||
{/* marginBottom: '8px',*/}
|
||||
{/* }}>授权登录</Text>*/}
|
||||
|
||||
{/* /!* 描述 *!/*/}
|
||||
{/* <Text style={{*/}
|
||||
{/* textAlign: 'center',*/}
|
||||
{/* color: 'rgba(255, 255, 255, 0.6)',*/}
|
||||
{/* fontSize: '13px',*/}
|
||||
{/* lineHeight: '1.5',*/}
|
||||
{/* }}>点击下方按钮,使用微信手机号快速登录</Text>*/}
|
||||
{/*</View>*/}
|
||||
|
||||
{/* 主按钮 - 渐变发光按钮 */}
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
maxWidth: '320px',
|
||||
background: 'linear-gradient(135deg, #7c3aed 0%, #a855f7 50%, #6366f1 100%)',
|
||||
borderRadius: '30px',
|
||||
padding: '2px',
|
||||
boxShadow: '0 0 30px rgba(168, 85, 247, 0.4), 0 0 60px rgba(168, 85, 247, 0.2)',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
className="authorize-btn"
|
||||
open-type="getPhoneNumber"
|
||||
onGetPhoneNumber={handleGetPhoneNumber}
|
||||
disabled={authLoading}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '52px',
|
||||
fontSize: '17px',
|
||||
fontWeight: '600',
|
||||
color: '#fff',
|
||||
background: authLoading ? 'rgba(255, 255, 255, 0.1)' : 'transparent',
|
||||
borderRadius: '28px',
|
||||
border: 'none',
|
||||
padding: '0',
|
||||
boxSizing: 'border-box',
|
||||
lineHeight: '52px',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
{authLoading ? '授权中...' : '微信手机号登录'}
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
{/* 取消按钮 */}
|
||||
<View
|
||||
className="w-full max-w-80 flex items-center justify-center mt-4 cursor-pointer"
|
||||
style={{ height: '44px' }}
|
||||
onClick={handleCancel}
|
||||
>
|
||||
<Text style={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: '14px' }}>取消</Text>
|
||||
</View>
|
||||
|
||||
{/* 协议勾选 */}
|
||||
<View className="flex items-center justify-center mt-6">
|
||||
<View
|
||||
style={{ padding: '8px', marginRight: '4px' }}
|
||||
onClick={() => setAgreementChecked(!agreementChecked)}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: '18px',
|
||||
height: '18px',
|
||||
borderRadius: '4px',
|
||||
border: agreementChecked ? 'none' : '1px solid rgba(255, 255, 255, 0.3)',
|
||||
background: agreementChecked ? 'linear-gradient(135deg, #7c3aed, #a855f7)' : 'transparent',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{agreementChecked && (
|
||||
<Text style={{ color: '#fff', fontSize: '12px' }}>✓</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<Text style={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: '12px' }}>
|
||||
我已阅读并同意
|
||||
</Text>
|
||||
<Text
|
||||
style={{ color: '#a855f7', fontSize: '12px', padding: '4px 2px' }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openAgreement('service');
|
||||
}}
|
||||
>
|
||||
《服务协议》
|
||||
</Text>
|
||||
<Text style={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: '12px' }}>和</Text>
|
||||
<Text
|
||||
style={{ color: '#a855f7', fontSize: '12px', padding: '4px 2px' }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openAgreement('privacy');
|
||||
}}
|
||||
>
|
||||
《隐私政策》
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* 底部装饰 */}
|
||||
{/* Logo 区域 */}
|
||||
<View className="flex flex-col items-center mb-12">
|
||||
<View style={{
|
||||
position: 'absolute',
|
||||
bottom: '40px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
width: '100px', height: '100px', borderRadius: '24px',
|
||||
background: 'linear-gradient(135deg, rgba(168, 85, 247, 0.2), rgba(59, 130, 246, 0.2))',
|
||||
border: '1px solid rgba(168, 85, 247, 0.3)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
marginBottom: '20px', position: 'relative', overflow: 'hidden',
|
||||
}}>
|
||||
{/* Logo 内光效 */}
|
||||
<View style={{
|
||||
width: '6px',
|
||||
height: '6px',
|
||||
borderRadius: '50%',
|
||||
background: '#a855f7',
|
||||
boxShadow: '0 0 10px rgba(168, 85, 247, 0.8)',
|
||||
}} />
|
||||
<Text style={{ color: 'rgba(255, 255, 255, 0.3)', fontSize: '11px' }}>
|
||||
安全加密连接
|
||||
</Text>
|
||||
<View style={{
|
||||
width: '6px',
|
||||
height: '6px',
|
||||
borderRadius: '50%',
|
||||
background: '#3b82f6',
|
||||
boxShadow: '0 0 10px rgba(59, 130, 246, 0.8)',
|
||||
position: 'absolute', top: '-50%', left: '-50%', width: '200%', height: '200%',
|
||||
background: 'conic-gradient(from 0deg, transparent, rgba(168, 85, 247, 0.1), transparent, rgba(59, 130, 246, 0.1), transparent)',
|
||||
animation: 'rotate 4s linear infinite',
|
||||
}} />
|
||||
<Text style={{ fontSize: '48px', position: 'relative', zIndex: 1 }}>🔐</Text>
|
||||
</View>
|
||||
|
||||
<Text style={{
|
||||
fontSize: '28px', fontWeight: '700',
|
||||
background: 'linear-gradient(90deg, #a855f7, #6366f1, #3b82f6)',
|
||||
WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
|
||||
backgroundClip: 'text', marginBottom: '8px',
|
||||
}}>websopy</Text>
|
||||
|
||||
<Text style={{ fontSize: '14px', color: 'rgba(255, 255, 255, 0.6)', letterSpacing: '2px' }}>
|
||||
智能连接 · 安全登录
|
||||
</Text>
|
||||
|
||||
<View style={{
|
||||
width: '60px', height: '3px',
|
||||
background: 'linear-gradient(90deg, transparent, #a855f7, transparent)',
|
||||
marginTop: '20px', borderRadius: '2px',
|
||||
}} />
|
||||
</View>
|
||||
|
||||
{/* 主按钮 - 渐变发光按钮 */}
|
||||
<View style={{
|
||||
width: '100%', maxWidth: '320px',
|
||||
background: 'linear-gradient(135deg, #7c3aed 0%, #a855f7 50%, #6366f1 100%)',
|
||||
borderRadius: '30px', padding: '2px',
|
||||
boxShadow: '0 0 30px rgba(168, 85, 247, 0.4), 0 0 60px rgba(168, 85, 247, 0.2)',
|
||||
}}>
|
||||
<Button
|
||||
open-type="getPhoneNumber"
|
||||
onGetPhoneNumber={handleGetPhoneNumber}
|
||||
disabled={authLoading}
|
||||
style={{
|
||||
width: '100%', height: '52px', fontSize: '17px', fontWeight: '600',
|
||||
color: '#fff', background: 'transparent', borderRadius: '28px',
|
||||
border: 'none', padding: '0', boxSizing: 'border-box', lineHeight: '52px',
|
||||
}}
|
||||
>
|
||||
{authLoading ? '授权中...' : '微信手机号登录'}
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
{/* 取消按钮 */}
|
||||
<View style={{ width: '100%', maxWidth: '320px', height: '44px', display: 'flex', alignItems: 'center', justifyContent: 'center', marginTop: '16px' }} onClick={handleCancel}>
|
||||
<Text style={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: '14px' }}>取消</Text>
|
||||
</View>
|
||||
|
||||
{/* 协议勾选 */}
|
||||
<View className="flex items-center justify-center mt-6">
|
||||
<View style={{ padding: '8px', marginRight: '4px' }} onClick={() => setAgreementChecked(!agreementChecked)}>
|
||||
<View style={{
|
||||
width: '18px', height: '18px', borderRadius: '4px',
|
||||
border: agreementChecked ? 'none' : '1px solid rgba(255, 255, 255, 0.3)',
|
||||
background: agreementChecked ? 'linear-gradient(135deg, #7c3aed, #a855f7)' : 'transparent',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
}}>
|
||||
{agreementChecked && <Text style={{ color: '#fff', fontSize: '12px' }}>✓</Text>}
|
||||
</View>
|
||||
</View>
|
||||
<Text style={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: '12px' }}>我已阅读并同意</Text>
|
||||
<Text style={{ color: '#a855f7', fontSize: '12px', padding: '4px 2px' }} onClick={() => openAgreement('service')}>《服务协议》</Text>
|
||||
<Text style={{ color: 'rgba(255, 255, 255, 0.5)', fontSize: '12px' }}>和</Text>
|
||||
<Text style={{ color: '#a855f7', fontSize: '12px', padding: '4px 2px' }} onClick={() => openAgreement('privacy')}>《隐私政策》</Text>
|
||||
</View>
|
||||
|
||||
{/* 底部装饰 */}
|
||||
<View style={{ position: 'absolute', bottom: '40px', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<View style={{ width: '6px', height: '6px', borderRadius: '50%', background: '#a855f7', boxShadow: '0 0 10px rgba(168, 85, 247, 0.8)' }} />
|
||||
<Text style={{ color: 'rgba(255, 255, 255, 0.3)', fontSize: '11px' }}>安全加密连接</Text>
|
||||
<View style={{ width: '6px', height: '6px', borderRadius: '50%', background: '#3b82f6', boxShadow: '0 0 10px rgba(59, 130, 246, 0.8)' }} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// 如果是授权页面,直接返回授权页面
|
||||
return renderAuthPage();
|
||||
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default QRConfirmPage;
|
||||
|
||||
Reference in New Issue
Block a user