refactor(invite): 重构邀请参数解析逻辑
- 优先从 query.scene 中解析邀请信息 - 增加对 uid_xxxxx 格式参数的处理 - 优化 key=value&key=value格式参数的解析 -兼容旧版本 scene 参数解析- 更新邀请关系建立 API调用
This commit is contained in:
@@ -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',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
src/app.ts
16
src/app.ts
@@ -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)
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -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('获取认证信息失败')
|
||||||
|
|||||||
@@ -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()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 清除本地存储的邀请参数
|
// 清除本地存储的邀请参数
|
||||||
|
|||||||
@@ -172,9 +172,9 @@ const handleAuthError = () => {
|
|||||||
duration: 2000
|
duration: 2000
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
Taro.reLaunch({ url: '/passport/login' });
|
// Taro.reLaunch({ url: '/passport/login' });
|
||||||
}, 2000);
|
// }, 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 错误处理
|
// 错误处理
|
||||||
|
|||||||
Reference in New Issue
Block a user