refactor(invite): 重构邀请参数解析逻辑

- 优先从 query.scene 中解析邀请信息
- 增加对 uid_xxxxx 格式参数的处理
- 优化 key=value&key=value格式参数的解析
-兼容旧版本 scene 参数解析- 更新邀请关系建立 API调用
This commit is contained in:
2025-09-05 12:04:07 +08:00
parent b233407020
commit 0494fd01d0
8 changed files with 176 additions and 109 deletions

View File

@@ -2,7 +2,7 @@
export const ENV_CONFIG = { export const ENV_CONFIG = {
// 开发环境 // 开发环境
development: { development: {
API_BASE_URL: 'http://127.0.0.1:9200/api', API_BASE_URL: 'https://cms-api.websoft.top/api',
APP_NAME: '开发环境', APP_NAME: '开发环境',
DEBUG: 'true', DEBUG: 'true',
}, },

View File

@@ -1,6 +1,7 @@
import request from '@/utils/request'; import request from '@/utils/request';
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import dayjs from 'dayjs'; import dayjs from 'dayjs';
// @ts-ignore
import crypto from 'crypto-js'; import crypto from 'crypto-js';
import {Base64} from 'js-base64'; import {Base64} from 'js-base64';
import {FileRecord} from "@/api/system/file/model"; import {FileRecord} from "@/api/system/file/model";
@@ -49,7 +50,7 @@ export async function uploadOssByPath(filePath: string) {
}) })
} }
const computeSignature = (accessKeySecret, canonicalString) => { const computeSignature = (accessKeySecret: string, canonicalString: string) => {
return crypto.enc.Base64.stringify(crypto.HmacSHA1(canonicalString, accessKeySecret)); return crypto.enc.Base64.stringify(crypto.HmacSHA1(canonicalString, accessKeySecret));
} }

View File

@@ -70,14 +70,14 @@ function App(props: { children: any; }) {
// 统计邀请来源 // 统计邀请来源
trackInviteSource(inviteParams.source || 'unknown', parseInt(inviteParams.inviter || '0')) trackInviteSource(inviteParams.source || 'unknown', parseInt(inviteParams.inviter || '0'))
// 显示邀请提示 // 检测到邀请信息
setTimeout(() => { // setTimeout(() => {
Taro.showToast({ // Taro.showToast({
title: '检测到邀请信息', // title: '检测到邀请信息',
icon: 'success', // icon: 'success',
duration: 2000 // duration: 2000
}) // })
}, 1000) // }, 1000)
} }
} catch (error) { } catch (error) {
console.error('处理启动参数失败:', error) console.error('处理启动参数失败:', error)

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { View, Text, Image } from '@tarojs/components'; import { View, Text } from '@tarojs/components';
import { Button, Avatar } from '@nutui/nutui-react-taro'; import { Button, Avatar } from '@nutui/nutui-react-taro';
import { useUser } from '@/hooks/useUser'; import { useUser } from '@/hooks/useUser';
import navTo from '@/utils/common'; import navTo from '@/utils/common';

View File

@@ -7,11 +7,13 @@ import {getUserInfo, getWxOpenId} from "@/api/layout";
import {TenantId} from "@/config/app"; import {TenantId} from "@/config/app";
import {getOrganization} from "@/api/system/organization"; import {getOrganization} from "@/api/system/organization";
import {myUserVerify} from "@/api/system/userVerify"; import {myUserVerify} from "@/api/system/userVerify";
import { useShopInfo } from '@/hooks/useShopInfo'; import {useShopInfo} from '@/hooks/useShopInfo';
import {handleInviteRelation} from "@/utils/invite"; import {handleInviteRelation} from "@/utils/invite";
import {View,Text} from '@tarojs/components' import {View, Text} from '@tarojs/components'
import MySearch from "./MySearch"; import MySearch from "./MySearch";
import './Header.scss'; import './Header.scss';
import navTo from "@/utils/common";
import {User} from "@/api/system/user/model";
const Header = (props: any) => { const Header = (props: any) => {
// 使用新的useShopInfo Hook // 使用新的useShopInfo Hook
@@ -22,6 +24,10 @@ const Header = (props: any) => {
const [IsLogin, setIsLogin] = useState<boolean>(true) const [IsLogin, setIsLogin] = useState<boolean>(true)
const [statusBarHeight, setStatusBarHeight] = useState<number>() const [statusBarHeight, setStatusBarHeight] = useState<number>()
const [userInfo, setUserInfo] = useState<User>({
avatar: '',
mobile: '未登录'
})
const reload = async () => { const reload = async () => {
Taro.getSystemInfo({ Taro.getSystemInfo({
@@ -31,62 +37,59 @@ const Header = (props: any) => {
}) })
// 注意商店信息现在通过useShopInfo自动管理不需要手动获取 // 注意商店信息现在通过useShopInfo自动管理不需要手动获取
// 获取用户信息 // 获取用户信息
getUserInfo().then((data) => { const data = await getUserInfo();
if (data) { if (data) {
setIsLogin(true); setIsLogin(true);
console.log('用户信息>>>', data.phone) console.log('用户信息>>>', data.phone)
// 保存userId // 保存userId
Taro.setStorageSync('UserId', data.userId) Taro.setStorageSync('UserId', data.userId)
// 获取openId // 获取openId
if (!data.openid) { if (!data.openid) {
Taro.login({ Taro.login({
success: (res) => { success: (res) => {
getWxOpenId({code: res.code}).then(() => { getWxOpenId({code: res.code}).then(() => {
}) })
} }
}) })
}
// 是否已认证
if (data.certification) {
Taro.setStorageSync('Certification', '1')
}
// 机构ID
Taro.setStorageSync('OrganizationId', data.organizationId)
// 父级机构ID
if (Number(data.organizationId) > 0) {
getOrganization(Number(data.organizationId)).then(res => {
Taro.setStorageSync('OrganizationParentId', res.parentId)
})
}
// 管理员
const isKdy = data.roles?.findIndex(item => item.roleCode == 'admin')
if (isKdy != -1) {
Taro.setStorageSync('RoleName', '管理')
Taro.setStorageSync('RoleCode', 'admin')
return false;
}
// 注册用户
const isUser = data.roles?.findIndex(item => item.roleCode == 'user')
if (isUser != -1) {
Taro.setStorageSync('RoleName', '注册用户')
Taro.setStorageSync('RoleCode', 'user')
return false;
}
} }
}).catch(() => { // 是否已认证
setIsLogin(false); if (data.certification) {
console.log('未登录') Taro.setStorageSync('Certification', '1')
});
// 认证信息
myUserVerify({status: 1}).then(data => {
if (data?.realName) {
Taro.setStorageSync('RealName', data.realName)
} }
}) // 机构ID
Taro.setStorageSync('OrganizationId', data.organizationId)
// 父级机构ID
if (Number(data.organizationId) > 0) {
getOrganization(Number(data.organizationId)).then(res => {
Taro.setStorageSync('OrganizationParentId', res.parentId)
})
}
// 管理员
const isKdy = data.roles?.findIndex(item => item.roleCode == 'admin')
if (isKdy != -1) {
Taro.setStorageSync('RoleName', '管理')
Taro.setStorageSync('RoleCode', 'admin')
return false;
}
// 注册用户
const isUser = data.roles?.findIndex(item => item.roleCode == 'user')
if (isUser != -1) {
Taro.setStorageSync('RoleName', '注册用户')
Taro.setStorageSync('RoleCode', 'user')
return false;
}
// 认证信息
myUserVerify({status: 1}).then(data => {
if (data?.realName) {
Taro.setStorageSync('RealName', data.realName)
}
})
setUserInfo(data)
}
} }
/* 获取用户手机号 */ /* 获取用户手机号 */
const handleGetPhoneNumber = ({detail}: {detail: {code?: string, encryptedData?: string, iv?: string}}) => { const handleGetPhoneNumber = ({detail}: { detail: { code?: string, encryptedData?: string, iv?: string } }) => {
const {code, encryptedData, iv} = detail const {code, encryptedData, iv} = detail
Taro.login({ Taro.login({
success: function () { success: function () {
@@ -167,7 +170,16 @@ const Header = (props: any) => {
onBackClick={() => { onBackClick={() => {
}} }}
left={ left={
!IsLogin ? ( IsLogin ? (
<View style={{display: 'flex', alignItems: 'center', gap: '8px'}} onClick={() => navTo(`/user/profile/profile`,true)}>
<Avatar
size="22"
src={userInfo?.avatar}
/>
<Text className={'text-white'}>{userInfo?.mobile}</Text>
<TriangleDown className={'text-white'} size={9}/>
</View>
) : (
<View style={{display: 'flex', alignItems: 'center'}}> <View style={{display: 'flex', alignItems: 'center'}}>
<Button style={{color: '#ffffff'}} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}> <Button style={{color: '#ffffff'}} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
<Space> <Space>
@@ -180,15 +192,6 @@ const Header = (props: any) => {
</Space> </Space>
</Button> </Button>
</View> </View>
) : (
<View style={{display: 'flex', alignItems: 'center', gap: '8px'}}>
<Avatar
size="22"
src={getWebsiteLogo()}
/>
<Text className={'text-white'}>{getWebsiteName()}</Text>
<TriangleDown className={'text-white'} size={9}/>
</View>
)}> )}>
</NavBar> </NavBar>
</> </>

View File

@@ -3,11 +3,10 @@ import Taro from '@tarojs/taro';
import {Button, Space} from '@nutui/nutui-react-taro' import {Button, Space} from '@nutui/nutui-react-taro'
import {TriangleDown} from '@nutui/icons-react-taro' import {TriangleDown} from '@nutui/icons-react-taro'
import {Popup, Avatar, NavBar} from '@nutui/nutui-react-taro' import {Popup, Avatar, NavBar} from '@nutui/nutui-react-taro'
import {getUserInfo, getWxOpenId} from "@/api/layout"; import {getWxOpenId} from "@/api/layout";
import {TenantId} from "@/config/app"; import {TenantId} from "@/config/app";
import {getOrganization} from "@/api/system/organization"; import {getOrganization} from "@/api/system/organization";
import {myUserVerify} from "@/api/system/userVerify"; import {myUserVerify} from "@/api/system/userVerify";
import {User} from "@/api/system/user/model";
import { useShopInfo } from '@/hooks/useShopInfo'; import { useShopInfo } from '@/hooks/useShopInfo';
import { useUser } from '@/hooks/useUser'; import { useUser } from '@/hooks/useUser';
import {handleInviteRelation} from "@/utils/invite"; import {handleInviteRelation} from "@/utils/invite";
@@ -17,7 +16,6 @@ import './Header.scss';
const Header = (props: any) => { const Header = (props: any) => {
// 使用新的hooks // 使用新的hooks
const { const {
shopInfo,
loading: shopLoading, loading: shopLoading,
getWebsiteName, getWebsiteName,
getWebsiteLogo getWebsiteLogo
@@ -56,14 +54,14 @@ const Header = (props: any) => {
// 检查用户认证状态 // 检查用户认证状态
if (user?.userId) { if (user?.userId) {
// 获取组织信息 // 获取组织信息
getOrganization({userId: user.userId}).then((data) => { getOrganization(user.userId).then((data) => {
console.log('组织信息>>>', data) console.log('组织信息>>>', data)
}).catch(() => { }).catch(() => {
console.log('获取组织信息失败') console.log('获取组织信息失败')
}); });
// 检查用户认证 // 检查用户认证
myUserVerify({userId: user.userId}).then((data) => { myUserVerify({id: user.userId}).then((data) => {
console.log('认证信息>>>', data) console.log('认证信息>>>', data)
}).catch(() => { }).catch(() => {
console.log('获取认证信息失败') console.log('获取认证信息失败')

View File

@@ -1,5 +1,5 @@
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import { createInviteRelation } from '@/api/invite' import {bindRefereeRelation} from "@/api/invite";
/** /**
* 邀请参数接口 * 邀请参数接口
@@ -15,32 +15,98 @@ export interface InviteParams {
*/ */
export function parseInviteParams(options: any): InviteParams | null { export function parseInviteParams(options: any): InviteParams | null {
try { try {
// 从 scene 参数中解析邀请信息 console.log('解析邀请参数:', options)
if (options.scene) {
// 确保 scene 是字符串类型
const sceneStr = typeof options.scene === 'string' ? options.scene : String(options.scene)
const params: InviteParams = {} // 优先从 query.scene 中解析邀请信息
const pairs = sceneStr.split('&') if (options.query && options.query.scene) {
const sceneStr = typeof options.query.scene === 'string' ? options.query.scene : String(options.query.scene)
console.log('从 query.scene 解析:', sceneStr)
pairs.forEach((pair: string) => { // 处理 uid_xxxxx 格式的参数
const [key, value] = pair.split('=') if (sceneStr.startsWith('uid_')) {
if (key && value) { const uid = sceneStr.replace('uid_', '')
switch (key) { if (uid) {
case 'inviter': return {
params.inviter = decodeURIComponent(value) inviter: uid,
break source: 'qrcode',
case 'source': t: String(Date.now())
params.source = decodeURIComponent(value)
break
case 't':
params.t = decodeURIComponent(value)
break
} }
} }
}) }
return params.inviter ? params : null // 处理 key=value&key=value 格式的参数
if (sceneStr.includes('=')) {
const params: InviteParams = {}
const pairs = sceneStr.split('&')
pairs.forEach((pair: string) => {
const [key, value] = pair.split('=')
if (key && value) {
switch (key) {
case 'inviter':
case 'uid':
params.inviter = decodeURIComponent(value)
break
case 'source':
params.source = decodeURIComponent(value)
break
case 't':
params.t = decodeURIComponent(value)
break
}
}
})
if (params.inviter) {
return params
}
}
}
// 从 scene 参数中解析邀请信息(兼容旧版本)
if (options.scene) {
const sceneStr = typeof options.scene === 'string' ? options.scene : String(options.scene)
console.log('从 scene 解析:', sceneStr)
// 处理 uid_xxxxx 格式的参数
if (sceneStr.startsWith('uid_')) {
const uid = sceneStr.replace('uid_', '')
if (uid) {
return {
inviter: uid,
source: 'qrcode',
t: String(Date.now())
}
}
}
// 处理 key=value&key=value 格式的参数
if (sceneStr.includes('=')) {
const params: InviteParams = {}
const pairs = sceneStr.split('&')
pairs.forEach((pair: string) => {
const [key, value] = pair.split('=')
if (key && value) {
switch (key) {
case 'inviter':
case 'uid':
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 参数中解析邀请信息(兼容旧版本) // 从 query 参数中解析邀请信息(兼容旧版本)
@@ -132,12 +198,11 @@ export async function handleInviteRelation(userId: number): Promise<boolean> {
} }
// 建立邀请关系 // 建立邀请关系
await createInviteRelation({ await bindRefereeRelation({
inviterId: inviterId, dealerId: inviterId,
inviteeId: userId, userId: userId,
source: inviteParams.source || 'unknown', source: inviteParams.source || 'unknown',
scene: `inviter=${inviterId}&source=${inviteParams.source}&t=${inviteParams.t}`, scene: `inviter=${inviterId}&source=${inviteParams.source}&t=${inviteParams.t}`
inviteTime: new Date().toISOString()
}) })
// 清除本地存储的邀请参数 // 清除本地存储的邀请参数

View File

@@ -172,9 +172,9 @@ const handleAuthError = () => {
duration: 2000 duration: 2000
}); });
setTimeout(() => { // setTimeout(() => {
Taro.reLaunch({ url: '/passport/login' }); // Taro.reLaunch({ url: '/passport/login' });
}, 2000); // }, 2000);
}; };
// 错误处理 // 错误处理