Compare commits
3 Commits
4fb2a94b5b
...
7ea0406336
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ea0406336 | |||
| 371b478e33 | |||
| 7d6c4ea3c6 |
@@ -22,7 +22,18 @@
|
|||||||
"usedAt": 1775908159660,
|
"usedAt": 1775908159660,
|
||||||
"industryId": "all"
|
"industryId": "all"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"f2494c1730eb411aac709ec2751d60fc": [
|
||||||
|
{
|
||||||
|
"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": 1775921148885,
|
||||||
|
"industryId": "all"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lastUpdated": 1775909408190
|
"lastUpdated": 1775921440281
|
||||||
}
|
}
|
||||||
44
.workbuddy/memory/2026-04-11.md
Normal file
44
.workbuddy/memory/2026-04-11.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# 2026-04-11 工作记录
|
||||||
|
|
||||||
|
## 任务:修复邀请加入应用流程
|
||||||
|
|
||||||
|
### 问题描述
|
||||||
|
`passport/invite/index` 页面在新用户扫码加入应用时存在问题:
|
||||||
|
- 新用户点击"微信手机号快速加入"时,如果未注册,后端返回"用户不存在"
|
||||||
|
- 页面引导用户去登录,但登录完成后**没有自动执行加入应用的操作**
|
||||||
|
|
||||||
|
### 解决方案
|
||||||
|
参考 `UserCard.tsx` 和 `phone-auth/index.tsx` 的实现模式,修改了 `src/passport/invite/index.tsx`:
|
||||||
|
|
||||||
|
#### 1. 新增功能
|
||||||
|
- 页面加载时保存邀请 token 到本地存储
|
||||||
|
- 检测用户登录状态
|
||||||
|
- 未登录用户点击加入时,保存邀请信息并引导到 `phone-auth` 登录页
|
||||||
|
- 登录成功后返回邀请页面,自动执行加入操作
|
||||||
|
- 新增 `clearPendingInviteData` 方法清理临时数据
|
||||||
|
|
||||||
|
#### 2. 关键修改点
|
||||||
|
- 使用 `saveInviteParams` 保存邀请信息
|
||||||
|
- 使用 `pending_invite_*` 系列 storage key 保存待处理的邀请数据
|
||||||
|
- 页面显示时通过 `AppShow` 事件监听登录返回
|
||||||
|
- 登录成功后自动调用 `handleJoinApp` 完成加入
|
||||||
|
|
||||||
|
#### 3. 流程优化
|
||||||
|
```
|
||||||
|
新用户流程:
|
||||||
|
1. 扫码进入邀请页面 → 保存 invite token
|
||||||
|
2. 点击"微信手机号快速加入"
|
||||||
|
3. 检测到未登录 → 保存 pending 数据 → 跳转到 phone-auth
|
||||||
|
4. 完成登录/注册 → 返回邀请页面
|
||||||
|
5. 自动检测 pending 数据 → 自动执行加入
|
||||||
|
6. 加入成功 → 跳转到首页
|
||||||
|
|
||||||
|
已登录用户流程:
|
||||||
|
1. 扫码进入邀请页面
|
||||||
|
2. 点击"微信手机号快速加入"
|
||||||
|
3. 直接执行加入操作
|
||||||
|
4. 加入成功 → 跳转到首页
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件修改
|
||||||
|
- `src/passport/invite/index.tsx` - 完整重构邀请流程
|
||||||
@@ -11,6 +11,13 @@
|
|||||||
"scene": null,
|
"scene": null,
|
||||||
"launchMode": "default"
|
"launchMode": "default"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "passport/invite/index",
|
||||||
|
"pathName": "passport/invite/index",
|
||||||
|
"query": "",
|
||||||
|
"launchMode": "default",
|
||||||
|
"scene": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "passport/invite",
|
"name": "passport/invite",
|
||||||
"pathName": "passport/invite",
|
"pathName": "passport/invite",
|
||||||
|
|||||||
@@ -6,11 +6,20 @@ import Taro, { useRouter } from '@tarojs/taro';
|
|||||||
// 注意:使用 /api/_app 前缀表示小程序专用接口(免登录)
|
// 注意:使用 /api/_app 前缀表示小程序专用接口(免登录)
|
||||||
const INVITE_API_URL = 'https://websopy-api.websoft.top';
|
const INVITE_API_URL = 'https://websopy-api.websoft.top';
|
||||||
import { TenantId } from "@/config/app";
|
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 [agreementChecked, setAgreementChecked] = useState(false);
|
||||||
const [token, setToken] = useState<string>('');
|
const [token, setToken] = useState<string>('');
|
||||||
const [error, setError] = useState<string>('');
|
const [error, setError] = useState<string>('');
|
||||||
|
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// 检查用户是否已登录
|
||||||
|
const accessToken = Taro.getStorageSync('access_token');
|
||||||
|
setIsLoggedIn(!!accessToken);
|
||||||
|
|
||||||
// 从 URL 参数中获取 token
|
// 从 URL 参数中获取 token
|
||||||
const params = router.params;
|
const params = router.params;
|
||||||
let inviteToken = params.scene || params.token || params.qrCodeKey || '';
|
let inviteToken = params.scene || params.token || params.qrCodeKey || '';
|
||||||
@@ -66,6 +80,12 @@ const InvitePage: React.FC = () => {
|
|||||||
|
|
||||||
// 获取邀请信息
|
// 获取邀请信息
|
||||||
if (inviteToken) {
|
if (inviteToken) {
|
||||||
|
// 保存邀请 token 到本地存储,供登录后使用
|
||||||
|
saveInviteParams({
|
||||||
|
inviter: inviteToken,
|
||||||
|
source: 'app_invite',
|
||||||
|
t: Date.now().toString()
|
||||||
|
});
|
||||||
fetchInviteInfo(inviteToken);
|
fetchInviteInfo(inviteToken);
|
||||||
} else {
|
} else {
|
||||||
setError('无效的邀请链接');
|
setError('无效的邀请链接');
|
||||||
@@ -73,6 +93,33 @@ const InvitePage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [router.params]);
|
}, [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 handleGetPhoneNumber = async ({ detail }: GetPhoneNumberEvent) => {
|
||||||
const { code, encryptedData, iv, errMsg } = detail;
|
const { code, encryptedData, iv, errMsg } = detail;
|
||||||
@@ -131,28 +181,57 @@ const InvitePage: React.FC = () => {
|
|||||||
return;
|
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);
|
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 {
|
try {
|
||||||
setAuthLoading(true);
|
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('请求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({
|
const res = await Taro.request({
|
||||||
url: `${INVITE_API_URL}/api/_app/developer/invite/accept`,
|
url: `${INVITE_API_URL}/api/_app/developer/invite/accept`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
token,
|
token: pendingToken,
|
||||||
code: phoneCode,
|
code: pendingPhoneCode,
|
||||||
encryptedData,
|
encryptedData: pendingEncryptedData,
|
||||||
iv
|
iv: pendingIv
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
@@ -164,6 +243,10 @@ const InvitePage: React.FC = () => {
|
|||||||
console.log('响应数据:', res.data);
|
console.log('响应数据:', res.data);
|
||||||
|
|
||||||
if (res.data.code === 200 || res.data.code === 0) {
|
if (res.data.code === 200 || res.data.code === 0) {
|
||||||
|
// 清除 pending 的邀请信息
|
||||||
|
clearPendingInviteData();
|
||||||
|
clearInviteParams();
|
||||||
|
|
||||||
Taro.showToast({ title: '加入成功', icon: 'success', duration: 1500 });
|
Taro.showToast({ title: '加入成功', icon: 'success', duration: 1500 });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// 跳转到应用页面或首页
|
// 跳转到应用页面或首页
|
||||||
@@ -171,7 +254,33 @@ const InvitePage: React.FC = () => {
|
|||||||
}, 1500);
|
}, 1500);
|
||||||
} else {
|
} else {
|
||||||
console.error('接受邀请失败:', res.data.message, 'code:', res.data.code);
|
console.error('接受邀请失败:', res.data.message, 'code:', res.data.code);
|
||||||
Taro.showToast({ title: res.data.message || '加入失败', icon: 'none' });
|
const errorMsg = res.data.message || '';
|
||||||
|
|
||||||
|
// 用户不存在,引导去登录注册
|
||||||
|
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: '您尚未注册,请先完成登录或注册后再加入应用',
|
||||||
|
confirmText: '去登录',
|
||||||
|
cancelText: '取消',
|
||||||
|
success: (modalRes) => {
|
||||||
|
if (modalRes.confirm) {
|
||||||
|
// 跳转到手机号授权登录页面,携带token参数以便登录后返回
|
||||||
|
Taro.navigateTo({
|
||||||
|
url: `/passport/phone-auth/index?redirect=${encodeURIComponent('/passport/invite/index?token=' + pendingToken)}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Taro.showToast({ title: errorMsg || '加入失败', icon: 'none' });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('接受邀请异常:', err);
|
console.error('接受邀请异常:', err);
|
||||||
@@ -181,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