From cba374f3aa4b1aa354d0a54d0517a52db1de9a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Wed, 8 Apr 2026 00:48:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(passport):=20=E6=B7=BB=E5=8A=A0=E6=89=8B?= =?UTF-8?q?=E6=9C=BA=E5=8F=B7=E6=8E=88=E6=9D=83=E7=99=BB=E5=BD=95=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=94=AF=E6=8C=81=E6=89=AB=E7=A0=81=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E5=9C=BA=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在app配置中新增手机号授权登录页面路由 - 修改扫码确认登录逻辑,未注册用户跳转手机号授权登录而非用户页 - 优化扫码登录成功后的跳转逻辑,支持返回扫码确认页面或跳转指定页面 - 新增手机号授权登录组件,实现微信手机号快速授权登录流程 - 手机号授权登录页面包括服务协议和隐私政策勾选及弹窗展示 - 登录成功后处理邀请绑定逻辑,支持扫码场景自动返回确认页 - 配置postcss禁用autoprefixer自动添加浏览器前缀避免冲突 --- .workbuddy/expert-history.json | 17 ++ .workbuddy/memory/MEMORY.md | 0 postcss.config.js | 4 +- src/app.config.ts | 1 + src/passport/phone-auth/index.tsx | 350 ++++++++++++++++++++++++++++++ src/passport/qr-confirm/index.tsx | 12 +- src/passport/sms-login.tsx | 32 ++- 7 files changed, 404 insertions(+), 12 deletions(-) create mode 100644 .workbuddy/expert-history.json create mode 100644 .workbuddy/memory/MEMORY.md create mode 100644 src/passport/phone-auth/index.tsx diff --git a/.workbuddy/expert-history.json b/.workbuddy/expert-history.json new file mode 100644 index 0000000..e378275 --- /dev/null +++ b/.workbuddy/expert-history.json @@ -0,0 +1,17 @@ +{ + "version": 2, + "sessions": { + "8485265d66ab43638f13f71cec8f191d": [ + { + "expertId": "SeniorDeveloper", + "name": "Will", + "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": 1775579277895, + "industryId": "all" + } + ] + }, + "lastUpdated": 1775579765675 +} \ No newline at end of file diff --git a/.workbuddy/memory/MEMORY.md b/.workbuddy/memory/MEMORY.md new file mode 100644 index 0000000..e69de29 diff --git a/postcss.config.js b/postcss.config.js index 33ad091..807876d 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,6 +1,8 @@ module.exports = { plugins: { tailwindcss: {}, - autoprefixer: {}, + autoprefixer: { + remove: true // 禁用 autoprefixer,避免添加浏览器前缀 + }, }, } diff --git a/src/app.config.ts b/src/app.config.ts index 67a1f2b..dfc14d2 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -15,6 +15,7 @@ export default { "setting", "agreement", "sms-login", + "phone-auth/index", 'qr-login/index', 'qr-confirm/index', 'unified-qr/index' diff --git a/src/passport/phone-auth/index.tsx b/src/passport/phone-auth/index.tsx new file mode 100644 index 0000000..5daaea8 --- /dev/null +++ b/src/passport/phone-auth/index.tsx @@ -0,0 +1,350 @@ +import { useEffect, useState } from "react"; +import Taro, { useRouter } from '@tarojs/taro' +import { Button, Popup } from '@nutui/nutui-react-taro' +import { checkAndHandleInviteRelation, hasPendingInvite } from "@/utils/invite"; +import { TenantId } from "@/config/app"; +import { saveStorageByLoginUser, SERVER_API_URL } from "@/utils/server"; + +/** + * 手机号授权登录页面(扫码登录场景专用) + * 使用微信手机号快速验证组件 + * + * 流程: + * 1. 用户勾选同意服务协议和隐私政策 + * 2. 点击微信手机号授权按钮 + * 3. 微信弹出授权确认框 + * 4. 获取 code 后调用后端接口完成登录 + * 5. 登录成功后返回扫码确认页面 + */ + +// 微信获取手机号回调参数类型 +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; +} + +const AgreementPopup = ({ visible, onClose }: { visible: boolean; onClose: () => void }) => ( + +
+

服务协议

+
+

+ 【首部及导言】
+ 欢迎您使用 WebSoft 服务!
+ 为了更好地为您提供服务,请您仔细阅读以下协议条款。 +

+

+ 一、服务内容
+ WebSoft 提供基于互联网的相关服务,包括但不限于应用开发平台、资源管理、工单系统等。具体服务内容由我们根据实际情况提供。 +

+

+ 二、用户注册
+ 您在使用本服务前需要注册账号,注册时应提供真实、准确的个人信息。如信息变更,请及时更新。 +

+

+ 三、用户使用规范
+ 您不得利用本服务从事任何违法违规活动,不得干扰本服务的正常运行。 +

+

+ 四、隐私保护
+ 我们承诺保护您的个人隐私,未经您同意,不会向第三方透露您的个人信息。 +

+

+ 五、免责声明
+ 因不可抗力或第三方原因导致的损失,我们不承担相应责任。 +

+
+
+ +
+
+
+); + +const PrivacyPopup = ({ visible, onClose }: { visible: boolean; onClose: () => void }) => ( + +
+

隐私政策

+
+

+ 【概述】
+ 我们非常重视您的个人信息和隐私保护。本政策旨在帮助您了解我们如何收集、使用、存储和保护您的信息。 +

+

+ 一、信息收集
+ 我们收集的信息包括:您主动提供的信息(如手机号)、使用服务时自动收集的信息(如设备信息、访问日志)。 +

+

+ 二、信息使用
+ 我们使用收集的信息用于:提供和改进服务、处理您的请求、账户管理和安全保障。 +

+

+ 三、信息共享
+ 未经您的同意,我们不会与任何第三方共享您的个人信息,法律另有规定除外。 +

+

+ 四、信息存储
+ 您的信息将存储在安全的服务器上,我们采取合理的安全措施保护您的信息。 +

+

+ 五、您的权利
+ 您有权访问、更正、删除您的个人信息。如有问题,可联系我们。 +

+

+ 六、联系我们
+ 如有隐私相关问题,请通过官方渠道联系我们。 +

+
+
+ +
+
+
+); + +const PhoneAuthLogin = () => { + const router = useRouter(); + const [loading, setLoading] = useState(false) + const [agreed, setAgreed] = useState(false) + const [showAgreement, setShowAgreement] = useState(false) + const [showPrivacy, setShowPrivacy] = useState(false) + + // 获取 redirect 参数(扫码登录场景) + const redirectUrl = router.params.redirect || ''; + + useEffect(() => { + Taro.hideTabBar() + }, []) + + // 处理微信手机号授权 + const handleGetPhoneNumber = ({ detail }: GetPhoneNumberEvent) => { + const { code, encryptedData, iv, errMsg } = detail + + // 用户拒绝授权 + if (errMsg && errMsg.includes('fail')) { + Taro.showToast({ + title: '需要授权手机号才能登录', + icon: 'none' + }) + return + } + + // 未同意协议 + if (!agreed) { + Taro.showToast({ + title: '请先阅读并同意服务协议和隐私政策', + icon: 'none' + }) + return + } + + if (!code) { + Taro.showToast({ + title: '获取授权信息失败,请重试', + icon: 'none' + }) + return + } + + // 执行登录 + handleLogin(code, encryptedData, iv) + } + + // 执行登录 + const handleLogin = async (phoneCode: string, encryptedData?: string, iv?: string) => { + try { + setLoading(true) + + const res = await Taro.request({ + url: `${SERVER_API_URL}/wx-login/loginByMpWxPhone`, + method: 'POST', + data: { + code: phoneCode, + encryptedData, + iv, + tenantId: TenantId + }, + 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) + } + + // 登录成功后,检查是否存在待处理的邀请关系并尝试绑定 + if (hasPendingInvite()) { + try { + await checkAndHandleInviteRelation() + } catch (e) { + console.error('授权登录后处理邀请关系失败:', e) + } + } + + Taro.showToast({ + title: '授权成功', + icon: 'success' + }) + + // 延迟跳转 + setTimeout(() => { + if (redirectUrl) { + // 扫码登录场景:返回扫码确认页面 + Taro.navigateBack(); + } else { + // 普通场景:跳转到首页 + Taro.reLaunch({ + url: '/pages/index/index' + }) + } + }, 1500) + + } catch (error: any) { + Taro.showToast({ + title: error.message || '授权失败', + icon: 'error' + }) + } finally { + setLoading(false) + } + } + + return ( + <> +
+ {/* Logo/Icon 区域 */} +
+
+ + + + +
+
+ + {/* 标题 */} +
+

手机号授权登录

+

一键授权,快速安全登录

+
+ + {/* 微信授权按钮 */} +
+ +
+ + {/* 协议勾选 */} +
+
setAgreed(!agreed)} + > + {agreed && ( + + + + )} +
+
+ 我已阅读并同意 + { + e.stopPropagation(); + setShowAgreement(true); + }} + > + 《服务协议》 + + 和 + { + e.stopPropagation(); + setShowPrivacy(true); + }} + > + 《隐私政策》 + +
+
+ + {/* 底部提示 */} +
+

登录即表示您同意以上协议条款

+

您的手机号将用于账号安全和身份验证

+
+
+ + {/* 服务协议弹窗 */} + setShowAgreement(false)} + /> + + {/* 隐私政策弹窗 */} + setShowPrivacy(false)} + /> + + ); +} + +export default PhoneAuthLogin; diff --git a/src/passport/qr-confirm/index.tsx b/src/passport/qr-confirm/index.tsx index d9741b4..2ecb6d2 100644 --- a/src/passport/qr-confirm/index.tsx +++ b/src/passport/qr-confirm/index.tsx @@ -126,19 +126,19 @@ const QRConfirmPage: React.FC = () => { // 调用确认登录 await handleConfirmLogin(loginToken, wxLoginResult.data.user); } else { - // 用户未注册,跳转到用户页引导注册 - console.log('[QRConfirm] 用户未注册,跳转到用户页引导注册'); + // 用户未注册,跳转到手机号授权登录页面 + console.log('[QRConfirm] 用户未注册,跳转到手机号授权登录页面'); Taro.showToast({ - title: '请先注册/登录小程序', + title: '请先授权登录小程序', icon: 'none', duration: 2000 }); setTimeout(() => { - // 跳转到用户中心,在那里可以完成注册和登录 - Taro.switchTab({ - url: '/pages/user/user' + // 跳转到手机号授权登录页面,登录/注册成功后返回扫码确认页面 + Taro.navigateTo({ + url: '/passport/phone-auth/index?redirect=/passport/qr-confirm' }); }, 2000); } diff --git a/src/passport/sms-login.tsx b/src/passport/sms-login.tsx index c30933c..62fceb4 100644 --- a/src/passport/sms-login.tsx +++ b/src/passport/sms-login.tsx @@ -1,11 +1,12 @@ import {useEffect, useState} from "react"; -import Taro from '@tarojs/taro' +import Taro, { useRouter } from '@tarojs/taro' import {Input, Button} from '@nutui/nutui-react-taro' import {loginBySms, sendSmsCaptcha} from "@/api/passport/login"; import {LoginParam} from "@/api/passport/login/model"; import {checkAndHandleInviteRelation, hasPendingInvite} from "@/utils/invite"; const SmsLogin = () => { + const router = useRouter(); const [loading, setLoading] = useState(false) const [sendingCode, setSendingCode] = useState(false) const [countdown, setCountdown] = useState(0) @@ -14,6 +15,9 @@ const SmsLogin = () => { code: '' }) + // 获取 redirect 参数(扫码登录场景) + const redirectUrl = router.params.redirect || ''; + const reload = () => { Taro.hideTabBar() } @@ -146,11 +150,29 @@ const SmsLogin = () => { icon: 'success' }) - // 延迟跳转到首页 + // 延迟跳转 setTimeout(() => { - Taro.reLaunch({ - url: '/pages/index/index' - }) + if (redirectUrl) { + // 扫码登录场景:返回扫码确认页面 + // 构建带原参数的完整 URL + const currentPages = Taro.getCurrentPages(); + const prevPage = currentPages[currentPages.length - 2]; + + if (prevPage && prevPage.route && prevPage.route.includes('qr-confirm')) { + // 如果上一页是扫码确认页面,直接返回 + Taro.navigateBack(); + } else { + // 否则跳转到指定页面 + Taro.redirectTo({ + url: redirectUrl + }); + } + } else { + // 普通场景:跳转到首页 + Taro.reLaunch({ + url: '/pages/index/index' + }) + } }, 1500) } catch (error: any) {