From ad6fb13ec1565c75ed2646f251fd2d782998e4f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Sat, 11 Apr 2026 08:35:31 +0800 Subject: [PATCH] =?UTF-8?q?refactor(qr-confirm):=20=E7=AE=80=E5=8C=96?= =?UTF-8?q?=E4=BA=8C=E7=BB=B4=E7=A0=81=E7=A1=AE=E8=AE=A4=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除未使用的组件和图标导入,减少包大小 - 注释掉登录卡片相关样式和元素,隐藏界面内容 - 删除手动确认登录函数及相关按钮渲染逻辑 - 去除状态图标、标题、描述和用户信息的渲染函数 - 删除所有操作按钮和安全提示组件渲染 - 取消整体页面布局,仅直接渲染授权页面部分 - 修改品牌文本首字母小写,统一风格 --- .workbuddy/expert-history.json | 4 +- src/passport/qr-confirm/index.tsx | 751 ++++++++---------------------- 2 files changed, 187 insertions(+), 568 deletions(-) diff --git a/.workbuddy/expert-history.json b/.workbuddy/expert-history.json index 37e7892..9248da0 100644 --- a/.workbuddy/expert-history.json +++ b/.workbuddy/expert-history.json @@ -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 } \ No newline at end of file diff --git a/src/passport/qr-confirm/index.tsx b/src/passport/qr-confirm/index.tsx index 3564add..a8417a7 100644 --- a/src/passport/qr-confirm/index.tsx +++ b/src/passport/qr-confirm/index.tsx @@ -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(''); - const [token, setToken] = useState(''); - const [loginMethod, setLoginMethod] = useState<'scan' | 'url'>('url'); - const [userInfo, setUserInfo] = useState(null); - const [needAuth, setNeedAuth] = useState(false); // 是否需要手机号授权 const [authLoading, setAuthLoading] = useState(false); // 授权中状态 const [agreementChecked, setAgreementChecked] = useState(false); // 协议勾选状态 + const [token, setToken] = useState(''); // 登录 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 ( - + // 科技风格授权页面 + return ( + - {/* 背景科技元素 */} + {/* 背景科技元素 */} + {/* 网格背景 */} + - {/* 1. 网格背景 */} - + + {/* 渐变光晕 - 右下 */} + + + {/* 动态粒子光点 */} + {[ + { 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) => ( + + ))} - {/* 2. 渐变光晕 - 左上 */} - + {/* 扫描线效果 */} + - {/* 3. 渐变光晕 - 右下 */} - + {/* 主内容区域 */} + - {/* 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) => ( - - ))} - - {/* 5. 扫描线效果 */} - - - {/* 主内容区域 */} - - - {/* Logo 区域 */} - - {/* 科技感 Logo 容器 */} - - {/* Logo 内光效 */} - - {/* Logo 图标 */} - 🔐 - - - {/* 品牌名 - 渐变文字 */} - websopy - - {/* 标语 */} - 智能连接 · 安全登录 - - {/* 分割线 */} - - - - {/* 登录卡片 */} - {/**/} - {/* /!* 提示图标 *!/*/} - {/* */} - {/* */} - {/* 📱*/} - {/* */} - {/* */} - - {/* /!* 标题 *!/*/} - {/* 授权登录*/} - - {/* /!* 描述 *!/*/} - {/* 点击下方按钮,使用微信手机号快速登录*/} - {/**/} - - {/* 主按钮 - 渐变发光按钮 */} - - - - - {/* 取消按钮 */} - - 取消 - - - {/* 协议勾选 */} - - setAgreementChecked(!agreementChecked)} - > - - {agreementChecked && ( - - )} - - - - 我已阅读并同意 - - { - e.stopPropagation(); - openAgreement('service'); - }} - > - 《服务协议》 - - - { - e.stopPropagation(); - openAgreement('privacy'); - }} - > - 《隐私政策》 - - - - {/* 底部装饰 */} + {/* Logo 区域 */} + + {/* Logo 内光效 */} - - 安全加密连接 - - + 🔐 + + websopy + + + 智能连接 · 安全登录 + + + + + + {/* 主按钮 - 渐变发光按钮 */} + + + + + {/* 取消按钮 */} + + 取消 + + + {/* 协议勾选 */} + + setAgreementChecked(!agreementChecked)}> + + {agreementChecked && } + + + 我已阅读并同意 + openAgreement('service')}>《服务协议》 + + openAgreement('privacy')}>《隐私政策》 + + + {/* 底部装饰 */} + + + 安全加密连接 + - ); - }; - - - // 如果是授权页面,直接返回授权页面 - return renderAuthPage(); - + + ); }; export default QRConfirmPage;