feat(passport-invite): 优化新用户扫码邀请加入流程
- 扫码进入邀请页面时保存邀请 token 到本地存储 - 新增登录状态检测,未登录用户点击快速加入时保存邀请信息并跳转登录页 - 登录成功后返回邀请页自动执行加入应用操作,提升用户体验 - 支持已登录用户直接加入应用,简化流程 - 增加清理 pending 邀请数据的方法,防止数据残留 - 监听页面显示事件,处理登录返回后自动操作 - 调整接口请求逻辑,确保携带最新的邀请和授权数据 - 更新项目配置,注册邀请页面路径信息
This commit is contained in:
@@ -6,11 +6,20 @@ import Taro, { useRouter } from '@tarojs/taro';
|
||||
// 注意:使用 /api/_app 前缀表示小程序专用接口(免登录)
|
||||
const INVITE_API_URL = 'https://websopy-api.websoft.top';
|
||||
import { TenantId } from "@/config/app";
|
||||
import { getStoredInviteParams, saveInviteParams, clearInviteParams } from "@/utils/invite";
|
||||
|
||||
/**
|
||||
* 邀请加入确认页面
|
||||
*
|
||||
* 用户扫描邀请二维码后,打开此小程序页面确认加入应用
|
||||
*
|
||||
* 流程:
|
||||
* 1. 用户扫码进入页面,解析 token 参数
|
||||
* 2. 获取邀请信息展示给用户
|
||||
* 3. 用户点击"微信手机号快速加入"
|
||||
* 4. 如果用户未登录,保存邀请信息并引导到登录页
|
||||
* 5. 登录成功后返回此页面,自动执行加入操作
|
||||
* 6. 已登录用户直接执行加入操作
|
||||
*/
|
||||
|
||||
// 微信获取手机号回调参数类型
|
||||
@@ -45,8 +54,13 @@ const InvitePage: React.FC = () => {
|
||||
const [agreementChecked, setAgreementChecked] = useState(false);
|
||||
const [token, setToken] = useState<string>('');
|
||||
const [error, setError] = useState<string>('');
|
||||
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
// 检查用户是否已登录
|
||||
const accessToken = Taro.getStorageSync('access_token');
|
||||
setIsLoggedIn(!!accessToken);
|
||||
|
||||
// 从 URL 参数中获取 token
|
||||
const params = router.params;
|
||||
let inviteToken = params.scene || params.token || params.qrCodeKey || '';
|
||||
@@ -66,6 +80,12 @@ const InvitePage: React.FC = () => {
|
||||
|
||||
// 获取邀请信息
|
||||
if (inviteToken) {
|
||||
// 保存邀请 token 到本地存储,供登录后使用
|
||||
saveInviteParams({
|
||||
inviter: inviteToken,
|
||||
source: 'app_invite',
|
||||
t: Date.now().toString()
|
||||
});
|
||||
fetchInviteInfo(inviteToken);
|
||||
} else {
|
||||
setError('无效的邀请链接');
|
||||
@@ -73,6 +93,33 @@ const InvitePage: React.FC = () => {
|
||||
}
|
||||
}, [router.params]);
|
||||
|
||||
// 页面显示时检查是否需要自动执行加入操作(从登录页返回)
|
||||
useEffect(() => {
|
||||
const handleShow = () => {
|
||||
// 检查是否有 pending 的邀请数据且用户已登录
|
||||
const pendingToken = Taro.getStorageSync('pending_invite_token');
|
||||
const accessToken = Taro.getStorageSync('access_token');
|
||||
|
||||
if (pendingToken && accessToken) {
|
||||
console.log('检测到登录后返回,自动执行加入应用操作');
|
||||
// 更新登录状态
|
||||
setIsLoggedIn(true);
|
||||
// 自动执行加入操作
|
||||
handleJoinApp();
|
||||
}
|
||||
};
|
||||
|
||||
// 监听页面显示事件
|
||||
Taro.eventCenter.on('AppShow', handleShow);
|
||||
|
||||
// 立即检查一次(处理页面首次加载时已经是登录状态的情况)
|
||||
handleShow();
|
||||
|
||||
return () => {
|
||||
Taro.eventCenter.off('AppShow', handleShow);
|
||||
};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 获取邀请信息
|
||||
*/
|
||||
@@ -110,6 +157,9 @@ const InvitePage: React.FC = () => {
|
||||
|
||||
/**
|
||||
* 处理微信手机号授权
|
||||
*
|
||||
* 如果用户未登录,先引导到登录页面完成注册/登录
|
||||
* 登录成功后返回此页面自动执行加入操作
|
||||
*/
|
||||
const handleGetPhoneNumber = async ({ detail }: GetPhoneNumberEvent) => {
|
||||
const { code, encryptedData, iv, errMsg } = detail;
|
||||
@@ -131,28 +181,57 @@ const InvitePage: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查用户是否已登录
|
||||
const accessToken = Taro.getStorageSync('access_token');
|
||||
|
||||
if (!accessToken) {
|
||||
// 未登录用户:保存当前邀请信息,引导到登录页面
|
||||
// 保存邀请 token 和微信授权信息,供登录后使用
|
||||
Taro.setStorageSync('pending_invite_token', token);
|
||||
Taro.setStorageSync('pending_invite_phone_code', code);
|
||||
if (encryptedData) Taro.setStorageSync('pending_invite_encrypted_data', encryptedData);
|
||||
if (iv) Taro.setStorageSync('pending_invite_iv', iv);
|
||||
|
||||
// 引导到手机号授权登录页面
|
||||
Taro.navigateTo({
|
||||
url: `/passport/phone-auth/index?redirect=${encodeURIComponent('/passport/invite/index?token=' + token)}`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 已登录用户:直接执行加入操作
|
||||
await handleJoinApp(code, encryptedData, iv);
|
||||
};
|
||||
|
||||
/**
|
||||
* 加入应用
|
||||
*
|
||||
* 支持两种调用方式:
|
||||
* 1. 新用户:通过手机号授权码完成注册并加入(phoneCode 必传)
|
||||
* 2. 已登录用户:直接加入应用(使用存储的 token)
|
||||
*/
|
||||
const handleJoinApp = async (phoneCode: string, encryptedData?: string, iv?: string) => {
|
||||
const handleJoinApp = async (phoneCode?: string, encryptedData?: string, iv?: string) => {
|
||||
try {
|
||||
setAuthLoading(true);
|
||||
|
||||
console.log('开始接受邀请, token:', token);
|
||||
// 获取 pending 的邀请信息(如果是从登录页返回)
|
||||
const pendingToken = Taro.getStorageSync('pending_invite_token') || token;
|
||||
const pendingPhoneCode = phoneCode || Taro.getStorageSync('pending_invite_phone_code');
|
||||
const pendingEncryptedData = encryptedData || Taro.getStorageSync('pending_invite_encrypted_data');
|
||||
const pendingIv = iv || Taro.getStorageSync('pending_invite_iv');
|
||||
|
||||
console.log('开始接受邀请, token:', pendingToken);
|
||||
console.log('请求URL:', `${INVITE_API_URL}/api/_app/developer/invite/accept`);
|
||||
console.log('请求参数:', { token, code: phoneCode ? '***' : null });
|
||||
console.log('请求参数:', { token: pendingToken, code: pendingPhoneCode ? '***' : null });
|
||||
|
||||
const res = await Taro.request({
|
||||
url: `${INVITE_API_URL}/api/_app/developer/invite/accept`,
|
||||
method: 'POST',
|
||||
data: {
|
||||
token,
|
||||
code: phoneCode,
|
||||
encryptedData,
|
||||
iv
|
||||
token: pendingToken,
|
||||
code: pendingPhoneCode,
|
||||
encryptedData: pendingEncryptedData,
|
||||
iv: pendingIv
|
||||
},
|
||||
header: {
|
||||
'content-type': 'application/json',
|
||||
@@ -164,6 +243,10 @@ const InvitePage: React.FC = () => {
|
||||
console.log('响应数据:', res.data);
|
||||
|
||||
if (res.data.code === 200 || res.data.code === 0) {
|
||||
// 清除 pending 的邀请信息
|
||||
clearPendingInviteData();
|
||||
clearInviteParams();
|
||||
|
||||
Taro.showToast({ title: '加入成功', icon: 'success', duration: 1500 });
|
||||
setTimeout(() => {
|
||||
// 跳转到应用页面或首页
|
||||
@@ -175,6 +258,12 @@ const InvitePage: React.FC = () => {
|
||||
|
||||
// 用户不存在,引导去登录注册
|
||||
if (errorMsg.includes('用户不存在') || errorMsg.includes('用户创建失败') || errorMsg.includes('请先登录')) {
|
||||
// 保存当前邀请信息
|
||||
Taro.setStorageSync('pending_invite_token', pendingToken);
|
||||
if (pendingPhoneCode) Taro.setStorageSync('pending_invite_phone_code', pendingPhoneCode);
|
||||
if (pendingEncryptedData) Taro.setStorageSync('pending_invite_encrypted_data', pendingEncryptedData);
|
||||
if (pendingIv) Taro.setStorageSync('pending_invite_iv', pendingIv);
|
||||
|
||||
Taro.showModal({
|
||||
title: '需要登录',
|
||||
content: '您尚未注册,请先完成登录或注册后再加入应用',
|
||||
@@ -182,9 +271,9 @@ const InvitePage: React.FC = () => {
|
||||
cancelText: '取消',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
// 跳转到登录页面,携带token参数以便登录后返回
|
||||
// 跳转到手机号授权登录页面,携带token参数以便登录后返回
|
||||
Taro.navigateTo({
|
||||
url: `/passport/login/index?redirect=${encodeURIComponent('/passport/invite/index?token=' + token)}`
|
||||
url: `/passport/phone-auth/index?redirect=${encodeURIComponent('/passport/invite/index?token=' + pendingToken)}`
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -201,6 +290,16 @@ const InvitePage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 清除 pending 的邀请数据
|
||||
*/
|
||||
const clearPendingInviteData = () => {
|
||||
Taro.removeStorageSync('pending_invite_token');
|
||||
Taro.removeStorageSync('pending_invite_phone_code');
|
||||
Taro.removeStorageSync('pending_invite_encrypted_data');
|
||||
Taro.removeStorageSync('pending_invite_iv');
|
||||
};
|
||||
|
||||
/**
|
||||
* 拒绝邀请
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user