forked from gxwebsoft/mp-10550
Merge branch 'demo' into dev
# Conflicts: # src/pages/user/components/UserCard.tsx
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React, {useState, useEffect} from 'react'
|
||||
import {View, Text, Image} from '@tarojs/components'
|
||||
import {Button, Loading} from '@nutui/nutui-react-taro'
|
||||
import {Share, Download, Copy, QrCode} from '@nutui/icons-react-taro'
|
||||
import {Download, QrCode} from '@nutui/icons-react-taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import {useDealerUser} from '@/hooks/useDealerUser'
|
||||
import {generateInviteCode} from '@/api/invite'
|
||||
@@ -115,52 +115,52 @@ const DealerQrcode: React.FC = () => {
|
||||
}
|
||||
|
||||
// 复制邀请信息
|
||||
const copyInviteInfo = () => {
|
||||
if (!dealerUser?.userId) {
|
||||
Taro.showToast({
|
||||
title: '用户信息未加载',
|
||||
icon: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const inviteText = `🎉 邀请您加入我的团队!
|
||||
|
||||
扫描小程序码或搜索"时里院子市集"小程序,即可享受优质商品和服务!
|
||||
|
||||
💰 成为我的团队成员,一起赚取丰厚佣金
|
||||
🎁 新用户专享优惠等你来拿
|
||||
|
||||
邀请码:${dealerUser.userId}
|
||||
快来加入我们吧!`
|
||||
|
||||
Taro.setClipboardData({
|
||||
data: inviteText,
|
||||
success: () => {
|
||||
Taro.showToast({
|
||||
title: '邀请信息已复制',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
// const copyInviteInfo = () => {
|
||||
// if (!dealerUser?.userId) {
|
||||
// Taro.showToast({
|
||||
// title: '用户信息未加载',
|
||||
// icon: 'error'
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// const inviteText = `🎉 邀请您加入我的团队!
|
||||
//
|
||||
// 扫描小程序码或搜索"时里院子市集"小程序,即可享受优质商品和服务!
|
||||
//
|
||||
// 💰 成为我的团队成员,一起赚取丰厚佣金
|
||||
// 🎁 新用户专享优惠等你来拿
|
||||
//
|
||||
// 邀请码:${dealerUser.userId}
|
||||
// 快来加入我们吧!`
|
||||
//
|
||||
// Taro.setClipboardData({
|
||||
// data: inviteText,
|
||||
// success: () => {
|
||||
// Taro.showToast({
|
||||
// title: '邀请信息已复制',
|
||||
// icon: 'success'
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
// 分享小程序码
|
||||
const shareMiniProgramCode = () => {
|
||||
if (!dealerUser?.userId) {
|
||||
Taro.showToast({
|
||||
title: '用户信息未加载',
|
||||
icon: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 小程序分享
|
||||
Taro.showShareMenu({
|
||||
withShareTicket: true,
|
||||
showShareItems: ['shareAppMessage']
|
||||
})
|
||||
}
|
||||
// const shareMiniProgramCode = () => {
|
||||
// if (!dealerUser?.userId) {
|
||||
// Taro.showToast({
|
||||
// title: '用户信息未加载',
|
||||
// icon: 'error'
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // 小程序分享
|
||||
// Taro.showShareMenu({
|
||||
// withShareTicket: true,
|
||||
// showShareItems: ['shareAppMessage']
|
||||
// })
|
||||
// }
|
||||
|
||||
if (!dealerUser) {
|
||||
return (
|
||||
@@ -263,29 +263,29 @@ const DealerQrcode: React.FC = () => {
|
||||
保存小程序码到相册
|
||||
</Button>
|
||||
</View>
|
||||
<View className={'my-2 bg-white'}>
|
||||
<Button
|
||||
size="large"
|
||||
block
|
||||
icon={<Copy/>}
|
||||
onClick={copyInviteInfo}
|
||||
disabled={!dealerUser?.userId || loading}
|
||||
>
|
||||
复制邀请信息
|
||||
</Button>
|
||||
</View>
|
||||
<View className={'my-2 bg-white'}>
|
||||
<Button
|
||||
size="large"
|
||||
block
|
||||
fill="outline"
|
||||
icon={<Share/>}
|
||||
onClick={shareMiniProgramCode}
|
||||
disabled={!dealerUser?.userId || loading}
|
||||
>
|
||||
分享给好友
|
||||
</Button>
|
||||
</View>
|
||||
{/*<View className={'my-2 bg-white'}>*/}
|
||||
{/* <Button*/}
|
||||
{/* size="large"*/}
|
||||
{/* block*/}
|
||||
{/* icon={<Copy/>}*/}
|
||||
{/* onClick={copyInviteInfo}*/}
|
||||
{/* disabled={!dealerUser?.userId || loading}*/}
|
||||
{/* >*/}
|
||||
{/* 复制邀请信息*/}
|
||||
{/* </Button>*/}
|
||||
{/*</View>*/}
|
||||
{/*<View className={'my-2 bg-white'}>*/}
|
||||
{/* <Button*/}
|
||||
{/* size="large"*/}
|
||||
{/* block*/}
|
||||
{/* fill="outline"*/}
|
||||
{/* icon={<Share/>}*/}
|
||||
{/* onClick={shareMiniProgramCode}*/}
|
||||
{/* disabled={!dealerUser?.userId || loading}*/}
|
||||
{/* >*/}
|
||||
{/* 分享给好友*/}
|
||||
{/* </Button>*/}
|
||||
{/*</View>*/}
|
||||
</View>
|
||||
|
||||
{/* 推广说明 */}
|
||||
|
||||
@@ -41,7 +41,7 @@ function Cart() {
|
||||
|
||||
useShareAppMessage(() => {
|
||||
return {
|
||||
title: '购物车 - 网宿小店',
|
||||
title: '购物车 - 时里院子市集',
|
||||
success: function () {
|
||||
console.log('分享成功');
|
||||
},
|
||||
|
||||
@@ -51,30 +51,8 @@ const BestSellers = () => {
|
||||
reload()
|
||||
}, [])
|
||||
|
||||
// 分享给好友
|
||||
useShareAppMessage(() => {
|
||||
return {
|
||||
title: goods?.name || '精选商品',
|
||||
path: `/shop/goodsDetail/index?id=${goods?.goodsId}`,
|
||||
imageUrl: goods?.image, // 分享图片
|
||||
success: function (res: any) {
|
||||
console.log('分享成功', res);
|
||||
Taro.showToast({
|
||||
title: '分享成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
},
|
||||
fail: function (res: any) {
|
||||
console.log('分享失败', res);
|
||||
Taro.showToast({
|
||||
title: '分享失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
// 注意:不在这里配置分享,避免与首页分享冲突
|
||||
// 商品分享应该在商品详情页处理,首页分享应该分享首页本身
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -173,9 +173,10 @@ const Header = (props: any) => {
|
||||
return (
|
||||
<>
|
||||
<View className={'fixed top-0 header-bg'} style={{
|
||||
height: !props.stickyStatus ? '180px' : '148px',
|
||||
height: !props.stickyStatus ? '180px' : 'auto',
|
||||
paddingBottom: '12px'
|
||||
}}>
|
||||
<MySearch/>
|
||||
<MySearch statusBarHeight={statusBarHeight} />
|
||||
{/*{!props.stickyStatus && <MySearch done={reload}/>}*/}
|
||||
</View>
|
||||
<NavBar
|
||||
|
||||
@@ -4,7 +4,7 @@ import {useState} from "react";
|
||||
import Taro from '@tarojs/taro';
|
||||
import { goTo } from '@/utils/navigation';
|
||||
|
||||
function MySearch() {
|
||||
function MySearch(props: any) {
|
||||
const [keywords, setKeywords] = useState<string>('')
|
||||
|
||||
const onKeywords = (keywords: string) => {
|
||||
@@ -39,7 +39,7 @@ function MySearch() {
|
||||
background: '#ffffff',
|
||||
padding: '0 5px',
|
||||
borderRadius: '20px',
|
||||
marginTop: '100px',
|
||||
marginTop: `${props.statusBarHeight + 50}px`,
|
||||
}}
|
||||
>
|
||||
<Search size={18} className={'ml-2 text-gray-400'}/>
|
||||
|
||||
@@ -47,14 +47,27 @@ function Home() {
|
||||
}
|
||||
|
||||
useShareAppMessage(() => {
|
||||
// 获取当前用户ID,用于生成邀请链接
|
||||
const userId = Taro.getStorageSync('UserId');
|
||||
|
||||
return {
|
||||
title: '网宿小店 - 网宿软件',
|
||||
path: `/pages/index/index`,
|
||||
title: '时里院子市集',
|
||||
path: userId ? `/pages/index/index?inviter=${userId}&source=share&t=${Date.now()}` : `/pages/index/index`,
|
||||
success: function () {
|
||||
console.log('分享成功');
|
||||
console.log('首页分享成功');
|
||||
Taro.showToast({
|
||||
title: '分享成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
},
|
||||
fail: function () {
|
||||
console.log('分享失败');
|
||||
console.log('首页分享失败');
|
||||
Taro.showToast({
|
||||
title: '分享失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -185,18 +198,32 @@ function Home() {
|
||||
});
|
||||
});
|
||||
|
||||
// 检查是否有待处理的邀请关系
|
||||
// 检查是否有待处理的邀请关系 - 异步处理,不阻塞页面加载
|
||||
if (hasPendingInvite()) {
|
||||
console.log('检测到待处理的邀请关系')
|
||||
// 延迟处理,确保用户信息已加载
|
||||
// 延迟处理,确保用户信息已加载,并设置超时保护
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const success = await checkAndHandleInviteRelation()
|
||||
// 设置超时保护,避免长时间等待
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('邀请关系处理超时')), 8000)
|
||||
);
|
||||
|
||||
const invitePromise = checkAndHandleInviteRelation();
|
||||
|
||||
const success = await Promise.race([invitePromise, timeoutPromise]);
|
||||
if (success) {
|
||||
console.log('首页邀请关系处理成功')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('首页邀请关系处理失败:', error)
|
||||
// 邀请关系处理失败不应该影响页面正常显示
|
||||
// 可以选择清除邀请参数,避免重复尝试
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
if (errorMessage?.includes('超时')) {
|
||||
console.log('邀请关系处理超时,清除邀请参数')
|
||||
// 可以选择清除邀请参数或稍后重试
|
||||
}
|
||||
}
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ const UserCell = () => {
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Reward className={'text-orange-100 '} size={16}/>
|
||||
<Text style={{fontSize: '16px'}}
|
||||
className={'pl-3 text-orange-100 font-medium'}>管理中心</Text>
|
||||
className={'pl-3 text-orange-100 font-medium'}>分销中心</Text>
|
||||
{/*<Text className={'text-white opacity-80 pl-3'}>门店核销</Text>*/}
|
||||
</View>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {Button} from '@nutui/nutui-react-taro'
|
||||
import {Avatar, Tag} from '@nutui/nutui-react-taro'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import {Scan} from '@nutui/icons-react-taro';
|
||||
import {getUserInfo, getWxOpenId} from '@/api/layout';
|
||||
import Taro from '@tarojs/taro';
|
||||
import {useEffect, useState, forwardRef, useImperativeHandle} from "react";
|
||||
@@ -10,9 +11,6 @@ import {TenantId} from "@/config/app";
|
||||
import {useUser} from "@/hooks/useUser";
|
||||
import {useUserData} from "@/hooks/useUserData";
|
||||
import {getStoredInviteParams} from "@/utils/invite";
|
||||
import {useQRLogin} from "@/hooks/useQRLogin";
|
||||
import {useAdminMode} from "@/hooks/useAdminMode";
|
||||
import AdminPanel from "@/components/AdminPanel";
|
||||
|
||||
const UserCard = forwardRef<any, any>((_, ref) => {
|
||||
const {
|
||||
@@ -23,16 +21,6 @@ const UserCard = forwardRef<any, any>((_, ref) => {
|
||||
const [IsLogin, setIsLogin] = useState<boolean>(false)
|
||||
const [userInfo, setUserInfo] = useState<User>()
|
||||
|
||||
// 扫码登录Hook
|
||||
const { startScan, isLoading: isScanLoading } = useQRLogin();
|
||||
console.log(isScanLoading,'>isScanLoading>>>>')
|
||||
|
||||
// 管理员模式Hook
|
||||
const { isAdminMode, toggleAdminMode } = useAdminMode();
|
||||
|
||||
// 管理员面板显示状态
|
||||
const [showAdminPanel, setShowAdminPanel] = useState(false);
|
||||
|
||||
// 下拉刷新
|
||||
const handleRefresh = async () => {
|
||||
await refresh()
|
||||
@@ -180,27 +168,6 @@ const UserCard = forwardRef<any, any>((_, ref) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 处理扫码登录
|
||||
const handleQRLogin = async () => {
|
||||
try {
|
||||
await startScan();
|
||||
} catch (error) {
|
||||
console.error('扫码登录失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
console.log(handleQRLogin,'handleQRLogin()')
|
||||
|
||||
// 打开管理员面板
|
||||
const handleOpenAdminPanel = () => {
|
||||
setShowAdminPanel(true);
|
||||
};
|
||||
|
||||
// 关闭管理员面板
|
||||
const handleCloseAdminPanel = () => {
|
||||
setShowAdminPanel(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className={'header-bg pt-20'}>
|
||||
<View className={'p-4'}>
|
||||
@@ -238,31 +205,7 @@ const UserCard = forwardRef<any, any>((_, ref) => {
|
||||
) : ''}
|
||||
</View>
|
||||
</View>
|
||||
{isAdmin() && (
|
||||
<View className="flex items-center space-x-2">
|
||||
{/* 管理员模式切换按钮 */}
|
||||
<View
|
||||
className={`px-3 py-1 rounded-full text-xs font-medium transition-all ${
|
||||
isAdminMode
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-gray-100 text-gray-600 border border-gray-300'
|
||||
}`}
|
||||
onClick={toggleAdminMode}
|
||||
>
|
||||
{isAdminMode ? '管理员' : '普通用户'}
|
||||
</View>
|
||||
|
||||
{/* 管理员功能入口 */}
|
||||
{isAdminMode && (
|
||||
<View
|
||||
className="px-3 py-1 bg-blue-50 border border-blue-200 rounded-full text-xs text-blue-600 font-medium"
|
||||
onClick={handleOpenAdminPanel}
|
||||
>
|
||||
管理面板
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
{isAdmin() && <Scan className={'text-gray-900'} size={24} onClick={() => navTo('/user/store/verification', true)} />}
|
||||
<View className={'mr-4 text-sm px-3 py-1 text-black border-gray-400 border-solid border-2 rounded-3xl'}
|
||||
onClick={() => navTo('/user/profile/profile', true)}>
|
||||
{'个人资料'}
|
||||
@@ -291,14 +234,6 @@ const UserCard = forwardRef<any, any>((_, ref) => {
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 管理员面板 */}
|
||||
{isAdmin() && (
|
||||
<AdminPanel
|
||||
visible={showAdminPanel}
|
||||
onClose={handleCloseAdminPanel}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
)
|
||||
|
||||
111
src/pages/user/components/UserGrid.tsx
Normal file
111
src/pages/user/components/UserGrid.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import {Grid, ConfigProvider} from '@nutui/nutui-react-taro'
|
||||
import navTo from "@/utils/common";
|
||||
import Taro from '@tarojs/taro'
|
||||
import {View} from '@tarojs/components'
|
||||
import {ShieldCheck, Location, Tips, Ask, Dongdong, People, AfterSaleService, Logout} from '@nutui/icons-react-taro'
|
||||
import {useUser} from "@/hooks/useUser";
|
||||
|
||||
const UserCell = () => {
|
||||
const {logoutUser} = useUser();
|
||||
|
||||
const onLogout = () => {
|
||||
Taro.showModal({
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
success: function (res) {
|
||||
if (res.confirm) {
|
||||
// 使用 useUser hook 的 logoutUser 方法
|
||||
logoutUser();
|
||||
Taro.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<View className="bg-white mx-4 mt-4 rounded-xl">
|
||||
<View className="font-semibold text-gray-800 pt-4 pl-4">我的服务</View>
|
||||
<ConfigProvider>
|
||||
<Grid
|
||||
columns={4}
|
||||
className="no-border-grid"
|
||||
style={{
|
||||
'--nutui-grid-border-color': 'transparent',
|
||||
'--nutui-grid-item-border-width': '0px',
|
||||
border: 'none'
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
<Grid.Item text="收货地址" onClick={() => navTo('/user/address/index', true)}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-blue-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Location color="#3b82f6" size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
<Grid.Item text={'实名认证'} onClick={() => navTo('/user/userVerify/index', true)}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-green-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<ShieldCheck color="#10b981" size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
<Grid.Item text={'我的邀请'} onClick={() => navTo('/dealer/team/index', true)}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-purple-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<People color="#8b5cf6" size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
<Grid.Item text={'我的邀请码'} onClick={() => navTo('/dealer/qrcode/index', true)}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-orange-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Dongdong color="#f59e0b" size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
<Grid.Item text={'管理中心'} onClick={() => navTo('/admin/index', true)}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-red-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<AfterSaleService className={'text-red-500'} size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
<Grid.Item text={'常见问题'} onClick={() => navTo('/user/help/index')}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-cyan-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Ask className={'text-cyan-500'} size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
<Grid.Item text={'关于我们'} onClick={() => navTo('/user/about/index')}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-amber-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Tips className={'text-amber-500'} size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
<Grid.Item text={'安全退出'} onClick={onLogout}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-pink-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Logout className={'text-pink-500'} size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
</Grid>
|
||||
</ConfigProvider>
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default UserCell
|
||||
@@ -2,17 +2,13 @@ import {useEffect, useRef} from 'react'
|
||||
import {PullToRefresh} from '@nutui/nutui-react-taro'
|
||||
import UserCard from "./components/UserCard";
|
||||
import UserOrder from "./components/UserOrder";
|
||||
import UserCell from "./components/UserCell";
|
||||
import UserFooter from "./components/UserFooter";
|
||||
import {useUser} from "@/hooks/useUser";
|
||||
import {useUserData} from "@/hooks/useUserData";
|
||||
import './user.scss'
|
||||
import IsDealer from "./components/IsDealer";
|
||||
import UserGrid from "@/pages/user/components/UserGrid";
|
||||
|
||||
function User() {
|
||||
const {
|
||||
isAdmin
|
||||
} = useUser();
|
||||
|
||||
const { refresh } = useUserData()
|
||||
const userCardRef = useRef<any>()
|
||||
@@ -29,28 +25,6 @@ function User() {
|
||||
useEffect(() => {
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 门店核销管理
|
||||
*/
|
||||
if (isAdmin()) {
|
||||
return (
|
||||
<PullToRefresh
|
||||
onRefresh={handleRefresh}
|
||||
headHeight={60}
|
||||
>
|
||||
<div className={'w-full'} style={{
|
||||
background: 'linear-gradient(to bottom, #e9fff2, #f9fafb)'
|
||||
}}>
|
||||
<UserCard ref={userCardRef}/>
|
||||
<UserOrder/>
|
||||
<IsDealer/>
|
||||
<UserCell/>
|
||||
<UserFooter/>
|
||||
</div>
|
||||
</PullToRefresh>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<PullToRefresh
|
||||
onRefresh={handleRefresh}
|
||||
@@ -62,7 +36,7 @@ function User() {
|
||||
<UserCard ref={userCardRef}/>
|
||||
<UserOrder/>
|
||||
<IsDealer/>
|
||||
<UserCell/>
|
||||
<UserGrid/>
|
||||
<UserFooter/>
|
||||
</div>
|
||||
</PullToRefresh>
|
||||
|
||||
@@ -24,6 +24,7 @@ const GoodsDetail = () => {
|
||||
const [specAction, setSpecAction] = useState<'cart' | 'buy'>('cart');
|
||||
// const [selectedSku, setSelectedSku] = useState<ShopGoodsSku | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const router = Taro.getCurrentInstance().router;
|
||||
const goodsId = router?.params?.id;
|
||||
|
||||
@@ -117,48 +118,57 @@ const GoodsDetail = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (goodsId) {
|
||||
setLoading(true);
|
||||
// 重新加载数据的函数
|
||||
const reloadData = async () => {
|
||||
if (!goodsId) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// 设置超时时间
|
||||
const timeout = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('请求超时')), 10000)
|
||||
);
|
||||
|
||||
// 加载商品详情
|
||||
getShopGoods(Number(goodsId))
|
||||
.then((res) => {
|
||||
// 处理富文本内容,去掉图片间距
|
||||
if (res.content) {
|
||||
res.content = wxParse(res.content);
|
||||
}
|
||||
setGoods(res);
|
||||
if (res.files) {
|
||||
const arr = JSON.parse(res.files);
|
||||
arr.length > 0 && setFiles(arr);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to fetch goods detail:", error);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
const goodsPromise = getShopGoods(Number(goodsId)).then((res) => {
|
||||
// 处理富文本内容,去掉图片间距
|
||||
if (res.content) {
|
||||
res.content = wxParse(res.content);
|
||||
}
|
||||
setGoods(res);
|
||||
if (res.files) {
|
||||
const arr = JSON.parse(res.files);
|
||||
arr.length > 0 && setFiles(arr);
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
// 加载商品规格
|
||||
listShopGoodsSpec({goodsId: Number(goodsId)} as any)
|
||||
.then((data) => {
|
||||
setSpecs(data || []);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to fetch goods specs:", error);
|
||||
});
|
||||
// 等待商品详情加载完成(带超时)
|
||||
await Promise.race([goodsPromise, timeout]);
|
||||
|
||||
// 加载商品SKU
|
||||
listShopGoodsSku({goodsId: Number(goodsId)} as any)
|
||||
.then((data) => {
|
||||
setSkus(data || []);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to fetch goods skus:", error);
|
||||
});
|
||||
// 并行加载规格和SKU(不阻塞主要内容显示)
|
||||
Promise.all([
|
||||
listShopGoodsSpec({goodsId: Number(goodsId)} as any)
|
||||
.then((data) => setSpecs(data || []))
|
||||
.catch((error) => console.error("Failed to fetch goods specs:", error)),
|
||||
|
||||
listShopGoodsSku({goodsId: Number(goodsId)} as any)
|
||||
.then((data) => setSkus(data || []))
|
||||
.catch((error) => console.error("Failed to fetch goods skus:", error))
|
||||
]);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("Failed to fetch goods detail:", error);
|
||||
setError(error.message || '加载失败,请重试');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
reloadData();
|
||||
}, [goodsId]);
|
||||
|
||||
// 分享给好友
|
||||
@@ -186,8 +196,42 @@ const GoodsDetail = () => {
|
||||
};
|
||||
});
|
||||
|
||||
if (!goods || loading) {
|
||||
return <div>加载中...</div>;
|
||||
// 错误状态
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen p-4">
|
||||
<div className="text-center">
|
||||
<div className="text-6xl mb-4">😵</div>
|
||||
<div className="text-lg font-medium mb-2">页面加载失败</div>
|
||||
<div className="text-gray-500 mb-6">{error}</div>
|
||||
<div className="space-y-3">
|
||||
<button
|
||||
className="bg-blue-500 text-white px-6 py-2 rounded-lg"
|
||||
onClick={reloadData}
|
||||
>
|
||||
重新加载
|
||||
</button>
|
||||
<button
|
||||
className="bg-gray-500 text-white px-6 py-2 rounded-lg ml-3"
|
||||
onClick={() => Taro.navigateBack()}
|
||||
>
|
||||
返回上页
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 加载状态 - 使用更好的加载UI
|
||||
if (loading || !goods) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mb-4"></div>
|
||||
<div className="text-gray-600">正在加载商品信息...</div>
|
||||
<div className="text-sm text-gray-400 mt-2">请稍候</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -65,11 +65,23 @@ export function parseInviteParams(options: any): InviteParams | null {
|
||||
}
|
||||
}
|
||||
|
||||
// 从 query 参数中解析邀请信息(兼容旧版本)
|
||||
if (options.referrer) {
|
||||
return {
|
||||
inviter: options.referrer,
|
||||
source: 'link'
|
||||
// 从 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,13 +175,21 @@ export async function handleInviteRelation(userId: number): Promise<boolean> {
|
||||
return true // 返回true表示关系已存在
|
||||
}
|
||||
|
||||
// 设置API调用超时
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('API调用超时')), 5000)
|
||||
);
|
||||
|
||||
// 使用新的绑定推荐关系接口
|
||||
await bindRefereeRelation({
|
||||
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, {
|
||||
@@ -185,6 +205,16 @@ export async function handleInviteRelation(userId: number): Promise<boolean> {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -329,9 +359,30 @@ export async function checkAndHandleInviteRelation(): Promise<boolean> {
|
||||
}
|
||||
|
||||
console.log('使用用户ID处理邀请关系:', finalUserId)
|
||||
return await handleInviteRelation(parseInt(finalUserId))
|
||||
|
||||
// 设置整体超时保护
|
||||
const timeoutPromise = new Promise<boolean>((_, 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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user