import Taro from '@tarojs/taro' import { bindRefereeRelation } from '@/api/invite' /** * 邀请参数接口 */ export interface InviteParams { inviter?: string; source?: string; t?: string; } /** * 解析小程序启动参数中的邀请信息 */ export function parseInviteParams(options: any): InviteParams | null { try { // 优先从 query.scene 参数中解析邀请信息 let sceneStr = null if (options.query && options.query.scene) { sceneStr = typeof options.query.scene === 'string' ? options.query.scene : String(options.query.scene) } else if (options.scene) { // 兼容直接从 scene 参数解析 sceneStr = typeof options.scene === 'string' ? options.scene : String(options.scene) } // 从 scene 参数中解析邀请信息 if (sceneStr) { // 处理 uid_xxx 格式的邀请码 if (sceneStr.startsWith('uid_')) { const inviterId = sceneStr.replace('uid_', '') if (inviterId && !isNaN(parseInt(inviterId))) { return { inviter: inviterId, source: 'qrcode', t: Date.now().toString() } } } // 处理传统的 key=value&key=value 格式 const params: InviteParams = {} const pairs = sceneStr.split('&') pairs.forEach((pair: string) => { const [key, value] = pair.split('=') if (key && value) { switch (key) { case 'inviter': params.inviter = decodeURIComponent(value) break case 'source': params.source = decodeURIComponent(value) break case 't': params.t = decodeURIComponent(value) break } } }) if (params.inviter) { return params } } // 从 query 参数中解析邀请信息(处理首页分享链接) if (options.query) { const query = options.query if (query.inviter) { return { inviter: query.inviter, source: query.source || 'share', t: query.t } } // 兼容旧版本 if (query.referrer) { return { inviter: query.referrer, source: 'link' } } } return null } catch (error) { console.error('解析邀请参数失败:', error) return null } } /** * 保存邀请信息到本地存储 */ export function saveInviteParams(params: InviteParams) { try { const saveData = { ...params, timestamp: Date.now() } Taro.setStorageSync('invite_params', saveData) } catch (error) { console.error('保存邀请参数失败:', error) } } /** * 获取本地存储的邀请信息 */ export function getStoredInviteParams(): InviteParams | null { try { const stored = Taro.getStorageSync('invite_params') if (stored && stored.inviter) { // 检查是否过期(24小时) const now = Date.now() const expireTime = 24 * 60 * 60 * 1000 // 24小时 if (now - stored.timestamp < expireTime) { return { inviter: stored.inviter, source: stored.source || 'unknown', t: stored.t } } else { // 过期则清除 clearInviteParams() } } return null } catch (error) { console.error('获取邀请参数失败:', error) return null } } /** * 清除本地存储的邀请信息 */ export function clearInviteParams() { try { Taro.removeStorageSync('invite_params') } catch (error) { console.error('清除邀请参数失败:', error) } } /** * 处理邀请关系建立 */ export async function handleInviteRelation(userId: number): Promise { try { const inviteParams = getStoredInviteParams() if (!inviteParams || !inviteParams.inviter) { return false } const inviterId = parseInt(inviteParams.inviter) if (isNaN(inviterId) || inviterId === userId) { // 邀请人ID无效或自己邀请自己 clearInviteParams() return false } // 防重复检查:检查是否已经处理过这个邀请关系 const relationKey = `invite_relation_${inviterId}_${userId}` const existingRelation = Taro.getStorageSync(relationKey) if (existingRelation) { clearInviteParams() // 清除邀请参数 return true // 返回true表示关系已存在 } // 设置API调用超时 const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('API调用超时')), 5000) ); // 使用新的绑定推荐关系接口 const apiPromise = bindRefereeRelation({ dealerId: inviterId, userId: userId, source: inviteParams.source || 'qrcode', scene: inviteParams.source === 'qrcode' ? `uid_${inviterId}` : `inviter=${inviterId}&source=${inviteParams.source}&t=${inviteParams.t}` }); // 等待API调用完成或超时 await Promise.race([apiPromise, timeoutPromise]); // 标记邀请关系已处理(设置过期时间为7天) Taro.setStorageSync(relationKey, { inviterId, userId, timestamp: Date.now(), source: inviteParams.source || 'qrcode' }) // 清除本地存储的邀请参数 clearInviteParams() return true } catch (error) { console.error('建立邀请关系失败:', error) // 如果是网络错误或超时,不清除邀请参数,允许稍后重试 const errorMessage = error instanceof Error ? error.message : String(error) if (errorMessage.includes('超时') || errorMessage.includes('网络')) { console.log('网络问题,保留邀请参数供稍后重试') return false } // 其他错误(如业务逻辑错误),清除邀请参数 clearInviteParams() return false } } /** * 检查是否有待处理的邀请 */ export function hasPendingInvite(): boolean { const params = getStoredInviteParams() return !!(params && params.inviter) } /** * 获取邀请来源的显示名称 */ export function getSourceDisplayName(source: string): string { const sourceMap: Record = { 'qrcode': '小程序码', 'link': '分享链接', 'share': '好友分享', 'goods_share': '商品分享', 'poster': '海报分享', 'unknown': '未知来源' } return sourceMap[source] || source } /** * 验证邀请码格式 */ export function validateInviteCode(scene: string): boolean { try { if (!scene) return false // 检查是否包含必要的参数 const hasInviter = scene.includes('inviter=') const hasSource = scene.includes('source=') return hasInviter && hasSource } catch (error) { return false } } /** * 生成邀请场景值 */ export function generateInviteScene(inviterId: number, source: string): string { const timestamp = Date.now() return `inviter=${inviterId}&source=${source}&t=${timestamp}` } /** * 统计邀请来源 */ export function trackInviteSource(source: string, inviterId?: number) { try { // 记录邀请来源统计 const trackData = { source, inviterId, timestamp: Date.now(), userAgent: Taro.getSystemInfoSync() } // 可以发送到统计服务 console.log('邀请来源统计:', trackData) // 暂存到本地,后续可批量上报 const existingTracks = Taro.getStorageSync('invite_tracks') || [] existingTracks.push(trackData) // 只保留最近100条记录 if (existingTracks.length > 100) { existingTracks.splice(0, existingTracks.length - 100) } Taro.setStorageSync('invite_tracks', existingTracks) } catch (error) { console.error('统计邀请来源失败:', error) } } /** * 调试工具:打印所有邀请相关的存储信息 */ export function debugInviteInfo() { try { console.log('=== 邀请参数调试信息 ===') // 获取启动参数 const launchOptions = Taro.getLaunchOptionsSync() console.log('启动参数:', JSON.stringify(launchOptions, null, 2)) // 获取存储的邀请参数 const storedParams = Taro.getStorageSync('invite_params') console.log('存储的邀请参数:', JSON.stringify(storedParams, null, 2)) // 获取用户信息 const userId = Taro.getStorageSync('UserId') const userInfo = Taro.getStorageSync('userInfo') console.log('用户ID:', userId) console.log('用户信息:', JSON.stringify(userInfo, null, 2)) // 获取邀请统计 const inviteTracks = Taro.getStorageSync('invite_tracks') console.log('邀请统计:', JSON.stringify(inviteTracks, null, 2)) console.log('=== 调试信息结束 ===') return { launchOptions, storedParams, userId, userInfo, inviteTracks } } catch (error) { console.error('获取调试信息失败:', error) return null } } /** * 检查并处理当前用户的邀请关系 * 用于在用户登录后立即检查是否需要建立邀请关系 */ export async function checkAndHandleInviteRelation(): Promise { try { // 清理过期的防重记录 cleanExpiredInviteRelations() // 获取当前用户信息 const userInfo = Taro.getStorageSync('userInfo') const userId = Taro.getStorageSync('UserId') const finalUserId = userId || userInfo?.userId if (!finalUserId) { console.log('用户未登录,无法处理邀请关系') return false } console.log('使用用户ID处理邀请关系:', finalUserId) // 设置整体超时保护 const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('邀请关系处理整体超时')), 6000) ); const handlePromise = handleInviteRelation(parseInt(finalUserId)); return await Promise.race([handlePromise, timeoutPromise]); } catch (error) { console.error('检查邀请关系失败:', error) // 记录失败次数,避免无限重试 const failKey = 'invite_handle_fail_count' const failCount = Taro.getStorageSync(failKey) || 0 if (failCount >= 3) { console.log('邀请关系处理失败次数过多,清除邀请参数') clearInviteParams() Taro.removeStorageSync(failKey) } else { Taro.setStorageSync(failKey, failCount + 1) } return false } } /** * 手动触发邀请关系建立 * 用于在特定页面或时机手动建立邀请关系 */ export async function manualHandleInviteRelation(userId: number): Promise { try { console.log('手动触发邀请关系建立,用户ID:', userId) const inviteParams = getStoredInviteParams() if (!inviteParams || !inviteParams.inviter) { console.log('没有待处理的邀请参数') return false } const result = await handleInviteRelation(userId) if (result) { // 显示成功提示 Taro.showModal({ title: '邀请成功', content: '您已成功加入邀请人的团队!', showCancel: false, confirmText: '知道了' }) } return result } catch (error) { console.error('手动处理邀请关系失败:', error) return false } } /** * 清理过期的邀请关系防重记录 */ export function cleanExpiredInviteRelations() { try { const keys = Taro.getStorageInfoSync().keys const expireTime = 7 * 24 * 60 * 60 * 1000 // 7天 const now = Date.now() keys.forEach(key => { if (key.startsWith('invite_relation_')) { try { const data = Taro.getStorageSync(key) if (data && data.timestamp && (now - data.timestamp > expireTime)) { Taro.removeStorageSync(key) } } catch (error) { // 如果读取失败,直接删除 Taro.removeStorageSync(key) } } }) } catch (error) { console.error('清理过期邀请关系记录失败:', error) } } /** * 直接绑定推荐关系 * 用于直接调用绑定推荐关系接口 */ export async function bindReferee(refereeId: number, userId?: number, source: string = 'qrcode'): Promise { try { // 如果没有传入userId,尝试从本地存储获取 let targetUserId = userId if (!targetUserId) { const userInfo = Taro.getStorageSync('userInfo') if (userInfo && userInfo.userId) { targetUserId = userInfo.userId } else { throw new Error('无法获取用户ID') } } // 防止自己推荐自己 if (refereeId === targetUserId) { throw new Error('不能推荐自己') } await bindRefereeRelation({ dealerId: refereeId, userId: targetUserId, source: source, scene: source === 'qrcode' ? `uid_${refereeId}` : undefined }) return true } catch (error: any) { console.error('绑定推荐关系失败:', error) return false } }