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