20 changed files with 2499 additions and 4 deletions
@ -0,0 +1,75 @@ |
|||
import { View, Button } from '@tarojs/components' |
|||
import { useState, useEffect } from 'react' |
|||
|
|||
interface EarningsCardProps { |
|||
availableAmount?: number |
|||
pendingAmount?: number |
|||
onWithdraw?: () => void |
|||
} |
|||
|
|||
function EarningsCard({ |
|||
availableAmount = 0.00, |
|||
pendingAmount = 0.00, |
|||
onWithdraw |
|||
}: EarningsCardProps) { |
|||
|
|||
const handleWithdraw = () => { |
|||
if (onWithdraw) { |
|||
onWithdraw() |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<View className="mx-4 mb-4"> |
|||
<View |
|||
className="rounded-2xl p-4 relative overflow-hidden" |
|||
style={{ |
|||
background: 'linear-gradient(135deg, #8B5CF6 0%, #A855F7 50%, #C084FC 100%)' |
|||
}} |
|||
> |
|||
{/* 装饰性背景元素 */} |
|||
<View |
|||
className="absolute -top-4 -right-4 w-20 h-20 rounded-full opacity-20" |
|||
style={{ backgroundColor: 'rgba(255, 255, 255, 0.2)' }} |
|||
/> |
|||
<View |
|||
className="absolute -bottom-6 -left-6 w-16 h-16 rounded-full opacity-10" |
|||
style={{ backgroundColor: 'rgba(255, 255, 255, 0.2)' }} |
|||
/> |
|||
|
|||
<View className="relative z-10"> |
|||
{/* 可提现金额 */} |
|||
<View className="mb-4"> |
|||
<View className="text-white text-base opacity-90 mb-1"> |
|||
可提现 {availableAmount.toFixed(2)} 元 |
|||
</View> |
|||
<View className="text-white text-base opacity-90"> |
|||
待提现 {pendingAmount.toFixed(2)} 元 |
|||
</View> |
|||
</View> |
|||
|
|||
{/* 提现按钮 */} |
|||
<View className="flex justify-end"> |
|||
<Button |
|||
className="bg-white text-purple-600 px-6 py-2 rounded-full text-sm font-medium border-0" |
|||
style={{ |
|||
backgroundColor: 'white', |
|||
color: '#8B5CF6', |
|||
fontSize: '14px', |
|||
fontWeight: '500', |
|||
borderRadius: '20px', |
|||
padding: '8px 24px', |
|||
border: 'none' |
|||
}} |
|||
onClick={handleWithdraw} |
|||
> |
|||
去提现 |
|||
</Button> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
export default EarningsCard |
@ -0,0 +1,102 @@ |
|||
import { View } from '@tarojs/components' |
|||
import Taro from '@tarojs/taro' |
|||
|
|||
interface MenuItem { |
|||
id: string |
|||
title: string |
|||
icon: string |
|||
color: string |
|||
bgColor: string |
|||
onClick?: () => void |
|||
} |
|||
|
|||
function FunctionMenu() { |
|||
const menuItems: MenuItem[] = [ |
|||
{ |
|||
id: 'withdraw-detail', |
|||
title: '提现明细', |
|||
icon: '💰', |
|||
color: '#F59E0B', |
|||
bgColor: '#FEF3C7', |
|||
onClick: () => { |
|||
Taro.navigateTo({ |
|||
url: '/dealer/withdraw/index' |
|||
}) |
|||
} |
|||
}, |
|||
{ |
|||
id: 'distribution-orders', |
|||
title: '分销订单', |
|||
icon: '📋', |
|||
color: '#EF4444', |
|||
bgColor: '#FEE2E2', |
|||
onClick: () => { |
|||
Taro.navigateTo({ |
|||
url: '/dealer/orders/index' |
|||
}) |
|||
} |
|||
}, |
|||
{ |
|||
id: 'my-team', |
|||
title: '我的团队', |
|||
icon: '👥', |
|||
color: '#10B981', |
|||
bgColor: '#D1FAE5', |
|||
onClick: () => { |
|||
Taro.navigateTo({ |
|||
url: '/dealer/team/index' |
|||
}) |
|||
} |
|||
}, |
|||
{ |
|||
id: 'promotion-qr', |
|||
title: '推广二维码', |
|||
icon: '📱', |
|||
color: '#3B82F6', |
|||
bgColor: '#DBEAFE', |
|||
onClick: () => { |
|||
Taro.navigateTo({ |
|||
url: '/dealer/qrcode/index' |
|||
}) |
|||
} |
|||
} |
|||
] |
|||
|
|||
const handleMenuClick = (item: MenuItem) => { |
|||
if (item.onClick) { |
|||
item.onClick() |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<View className="mx-4 mb-4"> |
|||
<View className="bg-white rounded-2xl p-4 shadow-sm"> |
|||
<View className="grid grid-cols-2 gap-4"> |
|||
{menuItems.map((item) => ( |
|||
<View |
|||
key={item.id} |
|||
className="flex flex-col items-center justify-center py-6 rounded-xl cursor-pointer" |
|||
style={{ backgroundColor: item.bgColor }} |
|||
onClick={() => handleMenuClick(item)} |
|||
> |
|||
<View |
|||
className="w-12 h-12 rounded-full flex items-center justify-center mb-2 text-2xl" |
|||
style={{ backgroundColor: 'white' }} |
|||
> |
|||
{item.icon} |
|||
</View> |
|||
<View |
|||
className="text-base font-medium" |
|||
style={{ color: item.color }} |
|||
> |
|||
{item.title} |
|||
</View> |
|||
</View> |
|||
))} |
|||
</View> |
|||
</View> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
export default FunctionMenu |
@ -0,0 +1,78 @@ |
|||
import { View } from '@tarojs/components' |
|||
import Taro from '@tarojs/taro' |
|||
|
|||
interface NavigationBarProps { |
|||
title?: string |
|||
showBack?: boolean |
|||
showMore?: boolean |
|||
onBack?: () => void |
|||
onMore?: () => void |
|||
} |
|||
|
|||
function NavigationBar({ |
|||
title = '分销中心', |
|||
showBack = true, |
|||
showMore = true, |
|||
onBack, |
|||
onMore |
|||
}: NavigationBarProps) { |
|||
|
|||
const handleBack = () => { |
|||
if (onBack) { |
|||
onBack() |
|||
} else { |
|||
Taro.navigateBack() |
|||
} |
|||
} |
|||
|
|||
const handleMore = () => { |
|||
if (onMore) { |
|||
onMore() |
|||
} else { |
|||
Taro.showActionSheet({ |
|||
itemList: ['分享', '设置', '帮助'] |
|||
}) |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<View className="relative"> |
|||
{/* 状态栏占位 */} |
|||
<View style={{ height: Taro.getSystemInfoSync().statusBarHeight + 'px' }} /> |
|||
|
|||
{/* 导航栏 */} |
|||
<View className="flex items-center justify-between px-4 py-3 relative z-10"> |
|||
{/* 左侧返回按钮 */} |
|||
<View className="w-8 h-8 flex items-center justify-center"> |
|||
{showBack && ( |
|||
<View |
|||
className="w-6 h-6 flex items-center justify-center cursor-pointer" |
|||
onClick={handleBack} |
|||
> |
|||
<View className="text-white text-lg">‹</View> |
|||
</View> |
|||
)} |
|||
</View> |
|||
|
|||
{/* 中间标题 */} |
|||
<View className="flex-1 text-center"> |
|||
<View className="text-white text-xl font-medium">{title}</View> |
|||
</View> |
|||
|
|||
{/* 右侧更多按钮 */} |
|||
<View className="w-8 h-8 flex items-center justify-center"> |
|||
{showMore && ( |
|||
<View |
|||
className="w-6 h-6 flex items-center justify-center cursor-pointer" |
|||
onClick={handleMore} |
|||
> |
|||
<View className="text-white text-lg">⋯</View> |
|||
</View> |
|||
)} |
|||
</View> |
|||
</View> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
export default NavigationBar |
@ -0,0 +1,211 @@ |
|||
import {useEffect, useState} from 'react' |
|||
import {navigateTo} from '@tarojs/taro' |
|||
import Taro from '@tarojs/taro' |
|||
import {Button} from '@tarojs/components'; |
|||
import {Image} from '@nutui/nutui-react-taro' |
|||
import {getUserInfo, getWxOpenId} from "@/api/layout"; |
|||
import {TenantId} from "@/config/app"; |
|||
import {User} from "@/api/system/user/model"; |
|||
// import News from "./News";
|
|||
import {myPageBszxBm} from "@/api/bszx/bszxBm"; |
|||
import {listCmsNavigation} from "@/api/cms/cmsNavigation"; |
|||
|
|||
const OrderIcon = () => { |
|||
|
|||
const [loading, setLoading] = useState(true) |
|||
const [isLogin, setIsLogin] = useState<boolean>(false) |
|||
const [userInfo, setUserInfo] = useState<User>() |
|||
const [bmLogs, setBmLogs] = useState<any>() |
|||
const [navItems, setNavItems] = useState<any>([]) |
|||
|
|||
/* 获取用户手机号 */ |
|||
const handleGetPhoneNumber = ({detail}) => { |
|||
const {code, encryptedData, iv} = detail |
|||
Taro.login({ |
|||
success: function () { |
|||
if (code) { |
|||
Taro.request({ |
|||
url: 'https://server.websoft.top/api/wx-login/loginByMpWxPhone', |
|||
method: 'POST', |
|||
data: { |
|||
code, |
|||
encryptedData, |
|||
iv, |
|||
notVerifyPhone: true, |
|||
refereeId: 0, |
|||
sceneType: 'save_referee', |
|||
tenantId: TenantId |
|||
}, |
|||
header: { |
|||
'content-type': 'application/json', |
|||
TenantId |
|||
}, |
|||
success: function (res) { |
|||
Taro.setStorageSync('access_token', res.data.data.access_token) |
|||
Taro.setStorageSync('UserId', res.data.data.user.userId) |
|||
setUserInfo(res.data.data.user) |
|||
Taro.setStorageSync('Phone', res.data.data.user.phone) |
|||
setIsLogin(true) |
|||
Taro.showToast({ |
|||
title: '登录成功', |
|||
icon: 'success' |
|||
}); |
|||
} |
|||
}) |
|||
} else { |
|||
console.log('登录失败!') |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const onLogin = (item: any, index: number) => { |
|||
if(!isLogin){ |
|||
return navigateTo({url: `/pages/category/category?id=${item.navigationId}`}) |
|||
}else { |
|||
// 报名链接
|
|||
if(index == 0){ |
|||
console.log(bmLogs,'bmLogs') |
|||
if(bmLogs && bmLogs.length > 0){ |
|||
return navigateTo({url: `/bszx/bm-cert/bm-cert?id=${bmLogs[0].id}`}) |
|||
}else { |
|||
navigateTo({url: `/user/profile/profile`}) |
|||
} |
|||
} |
|||
// 善款明细
|
|||
if(item.navigationId == 4119){ |
|||
return navigateTo({url: `/bszx/pay-record/pay-record`}) |
|||
} |
|||
return navigateTo({url: `/pages/category/category?id=${item.navigationId}`}) |
|||
} |
|||
} |
|||
|
|||
const reload = () => { |
|||
// 读取栏目
|
|||
listCmsNavigation({parentId: 2828,hide: 0}).then(res => { |
|||
console.log(res,'9999') |
|||
setNavItems(res); |
|||
}) |
|||
Taro.getUserInfo({ |
|||
success: (res) => { |
|||
const avatar = res.userInfo.avatarUrl; |
|||
setUserInfo({ |
|||
avatar, |
|||
nickname: res.userInfo.nickName, |
|||
sexName: res.userInfo.gender == 1 ? '男' : '女' |
|||
}) |
|||
getUserInfo().then((data) => { |
|||
if (data) { |
|||
setUserInfo(data) |
|||
setIsLogin(true); |
|||
console.log(userInfo, 'userInfo...') |
|||
Taro.setStorageSync('UserId', data.userId) |
|||
// 获取openId
|
|||
if (!data.openid) { |
|||
Taro.login({ |
|||
success: (res) => { |
|||
getWxOpenId({code: res.code}).then(() => { |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
}).catch(() => { |
|||
console.log('未登录') |
|||
}); |
|||
} |
|||
}); |
|||
// 报名日志
|
|||
myPageBszxBm({limit: 1}).then(res => { |
|||
if (res.list) { |
|||
setBmLogs(res.list); |
|||
} |
|||
}) |
|||
setLoading(false); |
|||
}; |
|||
|
|||
const showAuthModal = () => { |
|||
Taro.showModal({ |
|||
title: '授权提示', |
|||
content: '需要获取您的用户信息', |
|||
confirmText: '去授权', |
|||
cancelText: '取消', |
|||
success: (res) => { |
|||
if (res.confirm) { |
|||
// 用户点击确认,打开授权设置页面
|
|||
openSetting(); |
|||
} |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
const openSetting = () => { |
|||
// Taro.openSetting:调起客户端小程序设置界面,返回用户设置的操作结果。设置界面只会出现小程序已经向用户请求过的权限。
|
|||
Taro.openSetting({ |
|||
success: (res) => { |
|||
if (res.authSetting['scope.userInfo']) { |
|||
// 用户授权成功,可以获取用户信息
|
|||
reload(); |
|||
} else { |
|||
// 用户拒绝授权,提示授权失败
|
|||
Taro.showToast({ |
|||
title: '授权失败', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
useEffect(() => { |
|||
Taro.getSetting({ |
|||
success: (res) => { |
|||
if (res.authSetting['scope.userInfo']) { |
|||
// 用户已经授权过,可以直接获取用户信息
|
|||
console.log('用户已经授权过,可以直接获取用户信息') |
|||
reload(); |
|||
} else { |
|||
// 用户未授权,需要弹出授权窗口
|
|||
console.log('用户未授权,需要弹出授权窗口') |
|||
showAuthModal(); |
|||
} |
|||
} |
|||
}); |
|||
reload(); |
|||
}, []) |
|||
|
|||
return ( |
|||
<div className={'my-3'}> |
|||
<div className={'pt-4 bg-yellow-50 rounded-2xl'} |
|||
style={{background: 'linear-gradient(to bottom, #ffffff, #ffffcc)'}}> |
|||
<div className={'flex justify-between pb-2 px-1'}> |
|||
{ |
|||
navItems.map((item, index) => ( |
|||
<div key={index} className={'text-center'}> |
|||
{ |
|||
isLogin && !loading ? |
|||
<div className={'flex flex-col justify-center items-center'} onClick={() => { |
|||
onLogin(item, index) |
|||
}}> |
|||
<Image src={item.icon} height={28} width={28} lazyLoad={false}/> |
|||
<div className={'mt-2'} style={{fontSize: '15px'}}>{item?.title}</div> |
|||
</div> |
|||
: |
|||
<Button className={'text-white'} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}> |
|||
<div className={'flex flex-col justify-center items-center'}> |
|||
<Image src={item.icon} height={28} width={28} lazyLoad={false}/> |
|||
<div className={'mt-2 text-gray-700'} style={{fontSize: '15px'}}>{item?.title}</div> |
|||
</div> |
|||
</Button> |
|||
} |
|||
</div> |
|||
)) |
|||
} |
|||
</div> |
|||
</div> |
|||
{/*<image src={'https://oss.wsdns.cn/20250224/18a2f3b807c94aac8a67af34e95534d6.jpeg'} className={'book'}>倡议书</image>*/} |
|||
{/*<News id={categoryId}/>*/} |
|||
</div> |
|||
) |
|||
} |
|||
export default OrderIcon |
@ -0,0 +1,135 @@ |
|||
import {Avatar} from '@nutui/nutui-react-taro' |
|||
import {getUserInfo, getWxOpenId} from '@/api/layout'; |
|||
import Taro from '@tarojs/taro'; |
|||
import {useEffect, useState} from "react"; |
|||
import {User} from "@/api/system/user/model"; |
|||
import {View} from '@tarojs/components' |
|||
function UserCard() { |
|||
const [IsLogin, setIsLogin] = useState<boolean>(false) |
|||
const [userInfo, setUserInfo] = useState<User>() |
|||
const [referrerName, setReferrerName] = useState<string>('平台') |
|||
|
|||
|
|||
useEffect(() => { |
|||
// Taro.getSetting:获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
|
|||
Taro.getSetting({ |
|||
success: (res) => { |
|||
if (res.authSetting['scope.userInfo']) { |
|||
// 用户已经授权过,可以直接获取用户信息
|
|||
console.log('用户已经授权过,可以直接获取用户信息') |
|||
reload(); |
|||
} else { |
|||
// 用户未授权,需要弹出授权窗口
|
|||
console.log('用户未授权,需要弹出授权窗口') |
|||
showAuthModal(); |
|||
} |
|||
} |
|||
}); |
|||
}, []); |
|||
|
|||
|
|||
|
|||
const reload = () => { |
|||
Taro.getUserInfo({ |
|||
success: (res) => { |
|||
const avatar = res.userInfo.avatarUrl; |
|||
setUserInfo({ |
|||
avatar, |
|||
nickname: res.userInfo.nickName, |
|||
sexName: res.userInfo.gender == 1 ? '男' : '女' |
|||
}) |
|||
getUserInfo().then((data) => { |
|||
if (data) { |
|||
setUserInfo(data) |
|||
setIsLogin(true); |
|||
Taro.setStorageSync('UserId', data.userId) |
|||
|
|||
// 获取openId
|
|||
if (!data.openid) { |
|||
Taro.login({ |
|||
success: (res) => { |
|||
getWxOpenId({code: res.code}).then(() => { |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
// 获取推荐人信息
|
|||
const referrer = data.nickname || '平台'; |
|||
setReferrerName(referrer) |
|||
} |
|||
}).catch(() => { |
|||
console.log('未登录') |
|||
}); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
const showAuthModal = () => { |
|||
Taro.showModal({ |
|||
title: '授权提示', |
|||
content: '需要获取您的用户信息', |
|||
confirmText: '去授权', |
|||
cancelText: '取消', |
|||
success: (res) => { |
|||
if (res.confirm) { |
|||
// 用户点击确认,打开授权设置页面
|
|||
openSetting(); |
|||
} |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
|
|||
const openSetting = () => { |
|||
// Taro.openSetting:调起客户端小程序设置界面,返回用户设置的操作结果。设置界面只会出现小程序已经向用户请求过的权限。
|
|||
Taro.openSetting({ |
|||
success: (res) => { |
|||
if (res.authSetting['scope.userInfo']) { |
|||
// 用户授权成功,可以获取用户信息
|
|||
reload(); |
|||
} else { |
|||
// 用户拒绝授权,提示授权失败
|
|||
Taro.showToast({ |
|||
title: '授权失败', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
return ( |
|||
<View className="mx-4 mb-4"> |
|||
<View className="bg-white rounded-2xl p-4 shadow-sm"> |
|||
<View className="flex items-center mb-3"> |
|||
<Avatar |
|||
size="60" |
|||
src={userInfo?.avatar || 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png'} |
|||
shape="round" |
|||
/> |
|||
<View className="ml-3 flex-1"> |
|||
<View className="text-xl font-medium text-gray-800 mb-1"> |
|||
{IsLogin ? (userInfo?.nickname || '小程序用户') : '小程序.App.网站.系统开发-邓'} |
|||
</View> |
|||
<View className="text-base text-gray-500"> |
|||
推荐人:{referrerName} |
|||
</View> |
|||
</View> |
|||
</View> |
|||
|
|||
<View className="border-t border-gray-100 pt-3"> |
|||
<View className="flex justify-between items-center"> |
|||
<View className="text-base text-gray-600"> |
|||
已提现金额 |
|||
</View> |
|||
<View className="text-xl font-medium text-gray-800"> |
|||
0.00元 |
|||
</View> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
export default UserCard; |
@ -0,0 +1,148 @@ |
|||
import {Cell} from '@nutui/nutui-react-taro' |
|||
import navTo from "@/utils/common"; |
|||
import Taro from '@tarojs/taro' |
|||
import {ArrowRight, ShieldCheck, LogisticsError, Location, Reward, Tips, Ask} from '@nutui/icons-react-taro' |
|||
|
|||
const UserCell = () => { |
|||
|
|||
const onLogout = () => { |
|||
Taro.showModal({ |
|||
title: '提示', |
|||
content: '确定要退出登录吗?', |
|||
success: function (res) { |
|||
if (res.confirm) { |
|||
Taro.removeStorageSync('access_token') |
|||
Taro.removeStorageSync('TenantId') |
|||
Taro.removeStorageSync('UserId') |
|||
Taro.removeStorageSync('userInfo') |
|||
Taro.reLaunch({ |
|||
url: '/pages/index/index' |
|||
}) |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
|
|||
return ( |
|||
<> |
|||
<div className={'px-4'}> |
|||
<Cell |
|||
className="nutui-cell-clickable" |
|||
style={{ |
|||
backgroundImage: 'linear-gradient(to right bottom, #54a799, #177b73)', |
|||
}} |
|||
title={ |
|||
<div style={{display: 'inline-flex', alignItems: 'center'}}> |
|||
<Reward className={'text-orange-100 '} size={16}/> |
|||
<div> |
|||
<span style={{ fontSize: '16px'}} className={'pl-3 text-orange-100 font-medium'}>开通会员</span> |
|||
</div> |
|||
<span className={'text-white opacity-80 pl-3'}>享优惠</span> |
|||
</div> |
|||
} |
|||
extra={<ArrowRight color="#cccccc" size={18}/>} |
|||
/> |
|||
<Cell.Group divider={true} description={ |
|||
<div style={{display: 'inline-flex', alignItems: 'center'}}> |
|||
<span style={{marginTop: '12px'}}>我的服务</span> |
|||
</div> |
|||
}> |
|||
<Cell |
|||
className="nutui-cell-clickable" |
|||
style={{ |
|||
display: 'none' |
|||
}} |
|||
title={ |
|||
<div style={{display: 'inline-flex', alignItems: 'center'}}> |
|||
<LogisticsError size={16}/> |
|||
<span className={'pl-3 text-sm'}>我的钱包</span> |
|||
</div> |
|||
} |
|||
align="center" |
|||
extra={<ArrowRight color="#cccccc" size={18}/>} |
|||
onClick={() => { |
|||
navTo('/user/wallet/index', true) |
|||
}} |
|||
/> |
|||
<Cell |
|||
className="nutui-cell-clickable" |
|||
title={ |
|||
<div style={{display: 'inline-flex', alignItems: 'center'}}> |
|||
<Location size={16}/> |
|||
<span className={'pl-3 text-sm'}>收货地址</span> |
|||
</div> |
|||
} |
|||
align="center" |
|||
extra={<ArrowRight color="#cccccc" size={18}/>} |
|||
onClick={() => { |
|||
navTo('/user/address/index', true) |
|||
}} |
|||
/> |
|||
<Cell |
|||
className="nutui-cell-clickable" |
|||
title={ |
|||
<div style={{display: 'inline-flex', alignItems: 'center'}}> |
|||
<ShieldCheck size={16}/> |
|||
<span className={'pl-3 text-sm'}>实名认证</span> |
|||
</div> |
|||
} |
|||
align="center" |
|||
extra={<ArrowRight color="#cccccc" size={18}/>} |
|||
onClick={() => { |
|||
navTo('/user/userVerify/index', true) |
|||
}} |
|||
/> |
|||
<Cell |
|||
className="nutui-cell-clickable" |
|||
title={ |
|||
<div style={{display: 'inline-flex', alignItems: 'center'}}> |
|||
<Ask size={16}/> |
|||
<span className={'pl-3 text-sm'}>常见问题</span> |
|||
</div> |
|||
} |
|||
align="center" |
|||
extra={<ArrowRight color="#cccccc" size={18}/>} |
|||
onClick={() => { |
|||
navTo('/user/help/index') |
|||
}} |
|||
/> |
|||
<Cell |
|||
className="nutui-cell-clickable" |
|||
title={ |
|||
<div style={{display: 'inline-flex', alignItems: 'center'}}> |
|||
<Tips size={16}/> |
|||
<span className={'pl-3 text-sm'}>关于我们</span> |
|||
</div> |
|||
} |
|||
align="center" |
|||
extra={<ArrowRight color="#cccccc" size={18}/>} |
|||
onClick={() => { |
|||
navTo('/user/about/index') |
|||
}} |
|||
/> |
|||
</Cell.Group> |
|||
<Cell.Group divider={true} description={ |
|||
<div style={{display: 'inline-flex', alignItems: 'center'}}> |
|||
<span style={{marginTop: '12px'}}>账号管理</span> |
|||
</div> |
|||
}> |
|||
<Cell |
|||
className="nutui-cell-clickable" |
|||
title="账号安全" |
|||
align="center" |
|||
extra={<ArrowRight color="#cccccc" size={18}/>} |
|||
onClick={() => navTo('/user/profile/profile', true)} |
|||
/> |
|||
<Cell |
|||
className="nutui-cell-clickable" |
|||
title="退出登录" |
|||
align="center" |
|||
extra={<ArrowRight color="#cccccc" size={18}/>} |
|||
onClick={onLogout} |
|||
/> |
|||
</Cell.Group> |
|||
</div> |
|||
</> |
|||
) |
|||
} |
|||
export default UserCell |
@ -0,0 +1,102 @@ |
|||
import {loginBySms} from "@/api/passport/login"; |
|||
import {useState} from "react"; |
|||
import Taro from '@tarojs/taro' |
|||
import {Popup} from '@nutui/nutui-react-taro' |
|||
import {UserParam} from "@/api/system/user/model"; |
|||
import {Button} from '@nutui/nutui-react-taro' |
|||
import {Form, Input} from '@nutui/nutui-react-taro' |
|||
import {Copyright, Version} from "@/config/app"; |
|||
const UserFooter = () => { |
|||
const [openLoginByPhone, setOpenLoginByPhone] = useState(false) |
|||
const [clickNum, setClickNum] = useState<number>(0) |
|||
const [FormData, setFormData] = useState<UserParam>( |
|||
{ |
|||
phone: undefined, |
|||
password: undefined |
|||
} |
|||
) |
|||
|
|||
const onLoginByPhone = () => { |
|||
setFormData({}) |
|||
setClickNum(clickNum + 1); |
|||
if (clickNum > 10) { |
|||
setOpenLoginByPhone(true); |
|||
} |
|||
} |
|||
|
|||
const closeLoginByPhone = () => { |
|||
setClickNum(0) |
|||
setOpenLoginByPhone(false) |
|||
} |
|||
|
|||
// 提交表单
|
|||
const submitByPhone = (values: any) => { |
|||
loginBySms({ |
|||
phone: values.phone, |
|||
code: values.code |
|||
}).then(() => { |
|||
setOpenLoginByPhone(false); |
|||
setTimeout(() => { |
|||
Taro.reLaunch({ |
|||
url: '/pages/index/index' |
|||
}) |
|||
},1000) |
|||
}) |
|||
} |
|||
|
|||
return ( |
|||
<> |
|||
<div className={'text-center py-4 w-full text-gray-300'} onClick={onLoginByPhone}> |
|||
<div className={'text-xs text-gray-400 py-1'}>当前版本:{Version}</div> |
|||
<div className={'text-xs text-gray-400 py-1'}>Copyright © { new Date().getFullYear() } {Copyright}</div> |
|||
</div> |
|||
|
|||
<Popup |
|||
style={{width: '350px', padding: '10px'}} |
|||
visible={openLoginByPhone} |
|||
closeOnOverlayClick={false} |
|||
closeable={true} |
|||
onClose={closeLoginByPhone} |
|||
> |
|||
<Form |
|||
style={{width: '350px',padding: '10px'}} |
|||
divider |
|||
initialValues={FormData} |
|||
labelPosition="left" |
|||
onFinish={(values) => submitByPhone(values)} |
|||
footer={ |
|||
<div |
|||
style={{ |
|||
display: 'flex', |
|||
justifyContent: 'center', |
|||
width: '100%' |
|||
}} |
|||
> |
|||
<Button nativeType="submit" block style={{backgroundColor: '#000000',color: '#ffffff'}}> |
|||
提交 |
|||
</Button> |
|||
</div> |
|||
} |
|||
> |
|||
<Form.Item |
|||
label={'手机号码'} |
|||
name="phone" |
|||
required |
|||
rules={[{message: '手机号码'}]} |
|||
> |
|||
<Input placeholder="请输入手机号码" maxLength={11} type="text"/> |
|||
</Form.Item> |
|||
<Form.Item |
|||
label={'短信验证码'} |
|||
name="code" |
|||
required |
|||
rules={[{message: '短信验证码'}]} |
|||
> |
|||
<Input placeholder="请输入短信验证码" maxLength={6} type="text"/> |
|||
</Form.Item> |
|||
</Form> |
|||
</Popup> |
|||
</> |
|||
) |
|||
} |
|||
export default UserFooter |
@ -0,0 +1,3 @@ |
|||
export default definePageConfig({ |
|||
navigationBarTitleText: '分销中心' |
|||
}) |
@ -0,0 +1,77 @@ |
|||
// 分销中心页面样式 |
|||
.dealer-page { |
|||
min-height: 100vh; |
|||
background: linear-gradient(180deg, #60A5FA 0%, #3B82F6 50%, #1D4ED8 100%); |
|||
} |
|||
|
|||
// 导航栏样式 |
|||
.navigation-bar { |
|||
position: relative; |
|||
z-index: 100; |
|||
} |
|||
|
|||
// 用户卡片样式 |
|||
.user-card { |
|||
background: white; |
|||
border-radius: 16px; |
|||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); |
|||
margin: 16px; |
|||
padding: 16px; |
|||
} |
|||
|
|||
// 收益卡片样式 |
|||
.earnings-card { |
|||
background: linear-gradient(135deg, #8B5CF6 0%, #A855F7 50%, #C084FC 100%); |
|||
border-radius: 16px; |
|||
margin: 16px; |
|||
padding: 16px; |
|||
position: relative; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
// 功能菜单样式 |
|||
.function-menu { |
|||
background: white; |
|||
border-radius: 16px; |
|||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); |
|||
margin: 16px; |
|||
padding: 16px; |
|||
|
|||
.menu-grid { |
|||
display: grid; |
|||
grid-template-columns: repeat(2, 1fr); |
|||
gap: 16px; |
|||
|
|||
.menu-item { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 24px 16px; |
|||
border-radius: 12px; |
|||
cursor: pointer; |
|||
transition: all 0.2s ease; |
|||
|
|||
&:active { |
|||
transform: scale(0.98); |
|||
} |
|||
|
|||
.menu-icon { |
|||
width: 48px; |
|||
height: 48px; |
|||
border-radius: 50%; |
|||
background: white; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
font-size: 24px; |
|||
margin-bottom: 8px; |
|||
} |
|||
|
|||
.menu-text { |
|||
font-size: 14px; |
|||
font-weight: 500; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,64 @@ |
|||
import {useEffect} from 'react' |
|||
import { View } from '@tarojs/components' |
|||
import Taro from '@tarojs/taro' |
|||
import NavigationBar from "./components/NavigationBar" |
|||
import UserCard from "./components/UserCard" |
|||
import EarningsCard from "./components/EarningsCard" |
|||
import FunctionMenu from "./components/FunctionMenu" |
|||
import './index.scss' |
|||
function Index() { |
|||
|
|||
useEffect(() => { |
|||
// 设置页面标题
|
|||
Taro.setNavigationBarTitle({ |
|||
title: '分销中心' |
|||
}) |
|||
}, []); |
|||
|
|||
const handleWithdraw = () => { |
|||
Taro.showModal({ |
|||
title: '提现', |
|||
content: '确定要进行提现操作吗?', |
|||
success: (res) => { |
|||
if (res.confirm) { |
|||
Taro.showToast({ |
|||
title: '提现申请已提交', |
|||
icon: 'success' |
|||
}) |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const handleBack = () => { |
|||
Taro.navigateBack({ |
|||
delta: 1 |
|||
}) |
|||
} |
|||
|
|||
const handleMore = () => { |
|||
Taro.showActionSheet({ |
|||
itemList: ['分享给朋友', '客服咨询', '使用帮助'], |
|||
success: (res) => { |
|||
const actions = ['分享给朋友', '客服咨询', '使用帮助'] |
|||
Taro.showToast({ |
|||
title: actions[res.tapIndex], |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
return ( |
|||
<View className="min-h-screen" style={{ |
|||
paddingTop: '20px', |
|||
background: 'linear-gradient(180deg, #60A5FA 0%, #3B82F6 50%, #1D4ED8 100%)' |
|||
}}> |
|||
<UserCard /> |
|||
<EarningsCard onWithdraw={handleWithdraw} /> |
|||
<FunctionMenu /> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
export default Index |
@ -0,0 +1,145 @@ |
|||
.distribution-orders-page { |
|||
min-height: 100vh; |
|||
background-color: #f5f5f5; |
|||
|
|||
.loading-container { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
height: 200px; |
|||
} |
|||
|
|||
.stats-card { |
|||
background: white; |
|||
margin: 16px; |
|||
border-radius: 12px; |
|||
padding: 20px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|||
|
|||
.stats-grid { |
|||
display: grid; |
|||
grid-template-columns: repeat(2, 1fr); |
|||
gap: 20px; |
|||
|
|||
.stat-item { |
|||
text-align: center; |
|||
|
|||
.stat-value { |
|||
font-size: 20px; // 对应 text-xl |
|||
font-weight: bold; |
|||
color: #1f2937; |
|||
margin-bottom: 4px; |
|||
} |
|||
|
|||
.stat-label { |
|||
font-size: 16px; // 对应 text-base |
|||
color: #6b7280; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.orders-container { |
|||
margin: 0 16px; |
|||
|
|||
.empty-container { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
height: 300px; |
|||
} |
|||
|
|||
.orders-list { |
|||
margin-top: 16px; |
|||
|
|||
.order-item { |
|||
background: white; |
|||
border-radius: 12px; |
|||
padding: 16px; |
|||
margin-bottom: 12px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|||
cursor: pointer; |
|||
transition: all 0.2s ease; |
|||
|
|||
&:active { |
|||
transform: scale(0.98); |
|||
background-color: #f9f9f9; |
|||
} |
|||
|
|||
.order-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 12px; |
|||
|
|||
.order-no { |
|||
font-size: 14px; // 对应 text-sm |
|||
color: #6b7280; |
|||
} |
|||
|
|||
.order-status { |
|||
font-size: 14px; // 对应 text-sm |
|||
font-weight: 500; |
|||
padding: 4px 12px; |
|||
border-radius: 20px; |
|||
background-color: rgba(0, 0, 0, 0.05); |
|||
} |
|||
} |
|||
|
|||
.order-content { |
|||
display: flex; |
|||
margin-bottom: 12px; |
|||
|
|||
.product-image { |
|||
width: 60px; |
|||
height: 60px; |
|||
border-radius: 8px; |
|||
margin-right: 12px; |
|||
background-color: #f3f4f6; |
|||
} |
|||
|
|||
.order-info { |
|||
flex: 1; |
|||
|
|||
.product-name { |
|||
font-size: 20px; // 对应 text-xl,主要标题 |
|||
font-weight: 500; |
|||
color: #1f2937; |
|||
margin-bottom: 6px; |
|||
line-height: 1.4; |
|||
} |
|||
|
|||
.buyer-info { |
|||
font-size: 16px; // 对应 text-base |
|||
color: #6b7280; |
|||
margin-bottom: 8px; |
|||
} |
|||
|
|||
.amount-info { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
|
|||
.order-amount { |
|||
font-size: 16px; // 对应 text-base |
|||
color: #6b7280; |
|||
} |
|||
|
|||
.commission { |
|||
font-size: 20px; // 对应 text-xl,重要数字 |
|||
font-weight: bold; |
|||
color: #ef4444; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.order-time { |
|||
font-size: 14px; // 对应 text-sm |
|||
color: #9ca3af; |
|||
text-align: right; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,238 @@ |
|||
import { useState, useEffect } from 'react' |
|||
import { View, Image } from '@tarojs/components' |
|||
import Taro from '@tarojs/taro' |
|||
import { Empty, Tabs, TabPane } from '@nutui/nutui-react-taro' |
|||
import './index.scss' |
|||
|
|||
interface DistributionOrder { |
|||
id: string |
|||
orderNo: string |
|||
productName: string |
|||
productImage: string |
|||
buyerName: string |
|||
orderAmount: number |
|||
commission: number |
|||
commissionRate: number |
|||
status: 'pending' | 'confirmed' | 'settled' |
|||
statusText: string |
|||
createTime: string |
|||
settleTime?: string |
|||
} |
|||
|
|||
function DistributionOrders() { |
|||
const [activeTab, setActiveTab] = useState('0') |
|||
const [orders, setOrders] = useState<DistributionOrder[]>([]) |
|||
const [loading, setLoading] = useState(true) |
|||
const [stats, setStats] = useState({ |
|||
totalCommission: 0, |
|||
pendingCommission: 0, |
|||
settledCommission: 0, |
|||
totalOrders: 0 |
|||
}) |
|||
|
|||
useEffect(() => { |
|||
Taro.setNavigationBarTitle({ |
|||
title: '分销订单' |
|||
}) |
|||
loadOrders() |
|||
}, []) |
|||
|
|||
const loadOrders = async () => { |
|||
try { |
|||
setLoading(true) |
|||
// 模拟数据
|
|||
const mockData: DistributionOrder[] = [ |
|||
{ |
|||
id: '1', |
|||
orderNo: 'DD202401150001', |
|||
productName: '有机蔬菜礼盒装', |
|||
productImage: 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png', |
|||
buyerName: '张***', |
|||
orderAmount: 299.00, |
|||
commission: 29.90, |
|||
commissionRate: 10, |
|||
status: 'settled', |
|||
statusText: '已结算', |
|||
createTime: '2024-01-15 14:30:00', |
|||
settleTime: '2024-01-16 10:00:00' |
|||
}, |
|||
{ |
|||
id: '2', |
|||
orderNo: 'DD202401140002', |
|||
productName: '新鲜水果组合', |
|||
productImage: 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png', |
|||
buyerName: '李***', |
|||
orderAmount: 158.00, |
|||
commission: 15.80, |
|||
commissionRate: 10, |
|||
status: 'confirmed', |
|||
statusText: '已确认', |
|||
createTime: '2024-01-14 09:20:00' |
|||
}, |
|||
{ |
|||
id: '3', |
|||
orderNo: 'DD202401130003', |
|||
productName: '农家土鸡蛋', |
|||
productImage: 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png', |
|||
buyerName: '王***', |
|||
orderAmount: 88.00, |
|||
commission: 8.80, |
|||
commissionRate: 10, |
|||
status: 'pending', |
|||
statusText: '待确认', |
|||
createTime: '2024-01-13 16:45:00' |
|||
} |
|||
] |
|||
|
|||
// 计算统计数据
|
|||
const totalCommission = mockData.reduce((sum, order) => sum + order.commission, 0) |
|||
const pendingCommission = mockData.filter(o => o.status === 'pending').reduce((sum, order) => sum + order.commission, 0) |
|||
const settledCommission = mockData.filter(o => o.status === 'settled').reduce((sum, order) => sum + order.commission, 0) |
|||
|
|||
setTimeout(() => { |
|||
setOrders(mockData) |
|||
setStats({ |
|||
totalCommission, |
|||
pendingCommission, |
|||
settledCommission, |
|||
totalOrders: mockData.length |
|||
}) |
|||
setLoading(false) |
|||
}, 1000) |
|||
} catch (error) { |
|||
console.error('加载分销订单失败:', error) |
|||
setLoading(false) |
|||
} |
|||
} |
|||
|
|||
const getStatusColor = (status: string) => { |
|||
switch (status) { |
|||
case 'settled': |
|||
return '#10B981' |
|||
case 'confirmed': |
|||
return '#3B82F6' |
|||
case 'pending': |
|||
return '#F59E0B' |
|||
default: |
|||
return '#6B7280' |
|||
} |
|||
} |
|||
|
|||
const getFilteredOrders = () => { |
|||
switch (activeTab) { |
|||
case '1': |
|||
return orders.filter(order => order.status === 'pending') |
|||
case '2': |
|||
return orders.filter(order => order.status === 'confirmed') |
|||
case '3': |
|||
return orders.filter(order => order.status === 'settled') |
|||
default: |
|||
return orders |
|||
} |
|||
} |
|||
|
|||
const handleOrderClick = (order: DistributionOrder) => { |
|||
Taro.showModal({ |
|||
title: '订单详情', |
|||
content: ` |
|||
订单号:${order.orderNo} |
|||
商品:${order.productName} |
|||
购买人:${order.buyerName} |
|||
订单金额:¥${order.orderAmount.toFixed(2)} |
|||
佣金比例:${order.commissionRate}% |
|||
佣金金额:¥${order.commission.toFixed(2)} |
|||
下单时间:${order.createTime} |
|||
${order.settleTime ? `结算时间:${order.settleTime}` : ''} |
|||
`.trim(),
|
|||
showCancel: false |
|||
}) |
|||
} |
|||
|
|||
if (loading) { |
|||
return ( |
|||
<View className="distribution-orders-page"> |
|||
<View className="loading-container"> |
|||
<View className="text-center text-gray-500">加载中...</View> |
|||
</View> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
return ( |
|||
<View className="distribution-orders-page"> |
|||
{/* 统计卡片 */} |
|||
<View className="stats-card"> |
|||
<View className="stats-grid"> |
|||
<View className="stat-item"> |
|||
<View className="stat-value">¥{stats.totalCommission.toFixed(2)}</View> |
|||
<View className="stat-label">累计佣金</View> |
|||
</View> |
|||
<View className="stat-item"> |
|||
<View className="stat-value">¥{stats.settledCommission.toFixed(2)}</View> |
|||
<View className="stat-label">已结算</View> |
|||
</View> |
|||
<View className="stat-item"> |
|||
<View className="stat-value">¥{stats.pendingCommission.toFixed(2)}</View> |
|||
<View className="stat-label">待结算</View> |
|||
</View> |
|||
<View className="stat-item"> |
|||
<View className="stat-value">{stats.totalOrders}</View> |
|||
<View className="stat-label">订单数</View> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
|
|||
{/* 订单列表 */} |
|||
<View className="orders-container"> |
|||
<Tabs value={activeTab} onChange={(value) => setActiveTab(value)}> |
|||
<TabPane title="全部" /> |
|||
<TabPane title="待确认" /> |
|||
<TabPane title="已确认" /> |
|||
<TabPane title="已结算" /> |
|||
</Tabs> |
|||
|
|||
<View className="orders-list"> |
|||
{getFilteredOrders().length === 0 ? ( |
|||
<View className="empty-container"> |
|||
<Empty description="暂无订单" /> |
|||
</View> |
|||
) : ( |
|||
getFilteredOrders().map((order) => ( |
|||
<View |
|||
key={order.id} |
|||
className="order-item" |
|||
onClick={() => handleOrderClick(order)} |
|||
> |
|||
<View className="order-header"> |
|||
<View className="order-no">订单号:{order.orderNo}</View> |
|||
<View |
|||
className="order-status" |
|||
style={{ color: getStatusColor(order.status) }} |
|||
> |
|||
{order.statusText} |
|||
</View> |
|||
</View> |
|||
|
|||
<View className="order-content"> |
|||
<Image className="product-image" src={order.productImage} /> |
|||
<View className="order-info"> |
|||
<View className="product-name">{order.productName}</View> |
|||
<View className="buyer-info">购买人:{order.buyerName}</View> |
|||
<View className="amount-info"> |
|||
<View className="order-amount">订单金额:¥{order.orderAmount.toFixed(2)}</View> |
|||
<View className="commission">佣金:¥{order.commission.toFixed(2)}</View> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
|
|||
<View className="order-time">下单时间:{order.createTime}</View> |
|||
</View> |
|||
)) |
|||
)} |
|||
</View> |
|||
</View> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
export default DistributionOrders |
@ -0,0 +1,247 @@ |
|||
.promotion-qrcode-page { |
|||
min-height: 100vh; |
|||
background-color: #f5f5f5; |
|||
padding: 16px; |
|||
|
|||
.user-card { |
|||
background: white; |
|||
border-radius: 12px; |
|||
padding: 20px; |
|||
margin-bottom: 16px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|||
display: flex; |
|||
align-items: center; |
|||
|
|||
.user-avatar { |
|||
width: 60px; |
|||
height: 60px; |
|||
border-radius: 50%; |
|||
margin-right: 16px; |
|||
} |
|||
|
|||
.user-info { |
|||
flex: 1; |
|||
|
|||
.user-name { |
|||
font-size: 20px; // 对应 text-xl |
|||
font-weight: bold; |
|||
color: #1f2937; |
|||
margin-bottom: 6px; |
|||
} |
|||
|
|||
.invite-code { |
|||
font-size: 16px; // 对应 text-base |
|||
color: #6b7280; |
|||
background-color: #f3f4f6; |
|||
padding: 6px 10px; |
|||
border-radius: 6px; |
|||
display: inline-block; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.qrcode-container { |
|||
.qrcode-card { |
|||
background: white; |
|||
border-radius: 12px; |
|||
padding: 24px; |
|||
margin-bottom: 16px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|||
text-align: center; |
|||
|
|||
.qrcode-header { |
|||
margin-bottom: 24px; |
|||
|
|||
.title { |
|||
font-size: 20px; // 对应 text-xl |
|||
font-weight: bold; |
|||
color: #1f2937; |
|||
margin-bottom: 6px; |
|||
} |
|||
|
|||
.subtitle { |
|||
font-size: 16px; // 对应 text-base |
|||
color: #6b7280; |
|||
} |
|||
} |
|||
|
|||
.qrcode-wrapper { |
|||
margin-bottom: 20px; |
|||
|
|||
.qrcode-loading { |
|||
width: 200px; |
|||
height: 200px; |
|||
margin: 0 auto; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
background-color: #f9fafb; |
|||
border-radius: 12px; |
|||
|
|||
.loading-text { |
|||
color: #6b7280; |
|||
} |
|||
} |
|||
|
|||
.qrcode-image-container { |
|||
.qrcode-placeholder { |
|||
width: 200px; |
|||
height: 200px; |
|||
margin: 0 auto 12px; |
|||
background: white; |
|||
border: 2px solid #e5e7eb; |
|||
border-radius: 12px; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
|
|||
.qr-pattern { |
|||
width: 160px; |
|||
height: 160px; |
|||
position: relative; |
|||
|
|||
.qr-corner { |
|||
position: absolute; |
|||
width: 30px; |
|||
height: 30px; |
|||
border: 3px solid #1f2937; |
|||
|
|||
&.top-left { |
|||
top: 0; |
|||
left: 0; |
|||
border-right: none; |
|||
border-bottom: none; |
|||
} |
|||
|
|||
&.top-right { |
|||
top: 0; |
|||
right: 0; |
|||
border-left: none; |
|||
border-bottom: none; |
|||
} |
|||
|
|||
&.bottom-left { |
|||
bottom: 0; |
|||
left: 0; |
|||
border-right: none; |
|||
border-top: none; |
|||
} |
|||
} |
|||
|
|||
.qr-dots { |
|||
display: grid; |
|||
grid-template-columns: repeat(5, 1fr); |
|||
gap: 8px; |
|||
padding: 40px 20px; |
|||
|
|||
.qr-dot { |
|||
width: 8px; |
|||
height: 8px; |
|||
background-color: #1f2937; |
|||
border-radius: 1px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.qrcode-tip { |
|||
font-size: 14px; // 对应 text-sm |
|||
color: #9ca3af; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.qrcode-info { |
|||
.info-item { |
|||
text-align: left; |
|||
padding: 12px; |
|||
background-color: #f9fafb; |
|||
border-radius: 8px; |
|||
|
|||
.info-label { |
|||
font-size: 16px; // 对应 text-base |
|||
color: #6b7280; |
|||
margin-bottom: 4px; |
|||
} |
|||
|
|||
.info-value { |
|||
font-size: 14px; // 对应 text-sm |
|||
color: #1f2937; |
|||
word-break: break-all; |
|||
line-height: 1.4; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.action-buttons { |
|||
margin-bottom: 24px; |
|||
|
|||
.action-btn { |
|||
width: 100%; |
|||
margin-bottom: 12px; |
|||
border-radius: 8px; |
|||
font-size: 16px; |
|||
font-weight: 500; |
|||
|
|||
&.primary { |
|||
background: linear-gradient(135deg, #3b82f6, #1d4ed8); |
|||
color: white; |
|||
border: none; |
|||
} |
|||
|
|||
&.secondary { |
|||
background: white; |
|||
color: #3b82f6; |
|||
border: 1px solid #3b82f6; |
|||
} |
|||
|
|||
&.tertiary { |
|||
background: #f3f4f6; |
|||
color: #6b7280; |
|||
border: none; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.usage-tips { |
|||
background: white; |
|||
border-radius: 12px; |
|||
padding: 20px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|||
|
|||
.tips-title { |
|||
font-size: 20px; // 对应 text-xl |
|||
font-weight: bold; |
|||
color: #1f2937; |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.tips-list { |
|||
.tip-item { |
|||
font-size: 16px; // 对应 text-base |
|||
color: #6b7280; |
|||
line-height: 1.6; |
|||
margin-bottom: 8px; |
|||
padding-left: 8px; |
|||
position: relative; |
|||
|
|||
&:before { |
|||
content: ''; |
|||
position: absolute; |
|||
left: 0; |
|||
top: 8px; |
|||
width: 4px; |
|||
height: 4px; |
|||
background-color: #3b82f6; |
|||
border-radius: 50%; |
|||
} |
|||
|
|||
&:last-child { |
|||
margin-bottom: 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,221 @@ |
|||
import { useState, useEffect } from 'react' |
|||
import { View, Canvas, Image } from '@tarojs/components' |
|||
import Taro from '@tarojs/taro' |
|||
import { Button } from '@nutui/nutui-react-taro' |
|||
import './index.scss' |
|||
|
|||
interface UserInfo { |
|||
id: string |
|||
nickname: string |
|||
avatar: string |
|||
inviteCode: string |
|||
} |
|||
|
|||
function PromotionQRCode() { |
|||
const [userInfo, setUserInfo] = useState<UserInfo>({ |
|||
id: '12345', |
|||
nickname: '分销达人', |
|||
avatar: 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png', |
|||
inviteCode: 'INV12345' |
|||
}) |
|||
const [qrCodeUrl, setQrCodeUrl] = useState('') |
|||
const [loading, setLoading] = useState(false) |
|||
|
|||
useEffect(() => { |
|||
Taro.setNavigationBarTitle({ |
|||
title: '推广二维码' |
|||
}) |
|||
generateQRCode() |
|||
}, []) |
|||
|
|||
const generateQRCode = async () => { |
|||
try { |
|||
setLoading(true) |
|||
// 模拟生成二维码
|
|||
// 实际项目中应该调用后端API生成包含邀请码的二维码
|
|||
const inviteUrl = `https://your-domain.com/invite?code=${userInfo.inviteCode}` |
|||
|
|||
// 这里使用一个模拟的二维码图片
|
|||
// 实际项目中可以使用二维码生成库或调用API
|
|||
const mockQRCode = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==' |
|||
|
|||
setTimeout(() => { |
|||
setQrCodeUrl(mockQRCode) |
|||
setLoading(false) |
|||
}, 1000) |
|||
} catch (error) { |
|||
console.error('生成二维码失败:', error) |
|||
setLoading(false) |
|||
Taro.showToast({ |
|||
title: '生成失败', |
|||
icon: 'error' |
|||
}) |
|||
} |
|||
} |
|||
|
|||
const handleSaveImage = async () => { |
|||
try { |
|||
if (!qrCodeUrl) { |
|||
Taro.showToast({ |
|||
title: '二维码未生成', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
|
|||
// 在实际项目中,这里应该将二维码保存到相册
|
|||
Taro.showModal({ |
|||
title: '保存二维码', |
|||
content: '是否保存二维码到相册?', |
|||
success: (res) => { |
|||
if (res.confirm) { |
|||
// 实际保存逻辑
|
|||
Taro.showToast({ |
|||
title: '保存成功', |
|||
icon: 'success' |
|||
}) |
|||
} |
|||
} |
|||
}) |
|||
} catch (error) { |
|||
console.error('保存图片失败:', error) |
|||
Taro.showToast({ |
|||
title: '保存失败', |
|||
icon: 'error' |
|||
}) |
|||
} |
|||
} |
|||
|
|||
const handleShareQRCode = () => { |
|||
Taro.showActionSheet({ |
|||
itemList: ['分享给朋友', '分享到朋友圈', '复制邀请链接'], |
|||
success: (res) => { |
|||
const actions = ['分享给朋友', '分享到朋友圈', '复制邀请链接'] |
|||
const action = actions[res.tapIndex] |
|||
|
|||
if (action === '复制邀请链接') { |
|||
const inviteUrl = `https://your-domain.com/invite?code=${userInfo.inviteCode}` |
|||
Taro.setClipboardData({ |
|||
data: inviteUrl, |
|||
success: () => { |
|||
Taro.showToast({ |
|||
title: '链接已复制', |
|||
icon: 'success' |
|||
}) |
|||
} |
|||
}) |
|||
} else { |
|||
Taro.showToast({ |
|||
title: action, |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const handleRefreshQRCode = () => { |
|||
Taro.showModal({ |
|||
title: '刷新二维码', |
|||
content: '确定要重新生成二维码吗?', |
|||
success: (res) => { |
|||
if (res.confirm) { |
|||
generateQRCode() |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
|
|||
return ( |
|||
<View className="promotion-qrcode-page"> |
|||
{/* 用户信息卡片 */} |
|||
<View className="user-card"> |
|||
<Image className="user-avatar" src={userInfo.avatar} /> |
|||
<View className="user-info"> |
|||
<View className="user-name">{userInfo.nickname}</View> |
|||
<View className="invite-code">邀请码:{userInfo.inviteCode}</View> |
|||
</View> |
|||
</View> |
|||
|
|||
{/* 二维码展示区域 */} |
|||
<View className="qrcode-container"> |
|||
<View className="qrcode-card"> |
|||
<View className="qrcode-header"> |
|||
<View className="title">我的专属推广二维码</View> |
|||
<View className="subtitle">扫码注册成为我的下级</View> |
|||
</View> |
|||
|
|||
<View className="qrcode-wrapper"> |
|||
{loading ? ( |
|||
<View className="qrcode-loading"> |
|||
<View className="loading-text">生成中...</View> |
|||
</View> |
|||
) : ( |
|||
<View className="qrcode-image-container"> |
|||
{/* 实际项目中这里应该显示真实的二维码 */} |
|||
<View className="qrcode-placeholder"> |
|||
<View className="qr-pattern"> |
|||
<View className="qr-corner top-left"></View> |
|||
<View className="qr-corner top-right"></View> |
|||
<View className="qr-corner bottom-left"></View> |
|||
<View className="qr-dots"> |
|||
{Array.from({ length: 25 }).map((_, index) => ( |
|||
<View key={index} className="qr-dot"></View> |
|||
))} |
|||
</View> |
|||
</View> |
|||
</View> |
|||
<View className="qrcode-tip">长按识别二维码</View> |
|||
</View> |
|||
)} |
|||
</View> |
|||
|
|||
<View className="qrcode-info"> |
|||
<View className="info-item"> |
|||
<View className="info-label">邀请链接</View> |
|||
<View className="info-value">https://your-domain.com/invite?code={userInfo.inviteCode}</View>
|
|||
</View> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
|
|||
{/* 操作按钮 */} |
|||
<View className="action-buttons"> |
|||
<Button |
|||
className="action-btn primary" |
|||
onClick={handleSaveImage} |
|||
disabled={loading} |
|||
> |
|||
保存到相册 |
|||
</Button> |
|||
<Button |
|||
className="action-btn secondary" |
|||
onClick={handleShareQRCode} |
|||
disabled={loading} |
|||
> |
|||
分享二维码 |
|||
</Button> |
|||
<Button |
|||
className="action-btn tertiary" |
|||
onClick={handleRefreshQRCode} |
|||
disabled={loading} |
|||
> |
|||
刷新二维码 |
|||
</Button> |
|||
</View> |
|||
|
|||
{/* 使用说明 */} |
|||
<View className="usage-tips"> |
|||
<View className="tips-title">使用说明</View> |
|||
<View className="tips-list"> |
|||
<View className="tip-item">1. 分享二维码给好友,好友扫码注册成为您的下级</View> |
|||
<View className="tip-item">2. 下级用户的消费订单将为您带来佣金收益</View> |
|||
<View className="tip-item">3. 可以保存二维码图片或复制邀请链接进行推广</View> |
|||
<View className="tip-item">4. 二维码长期有效,可重复使用</View> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
export default PromotionQRCode |
@ -0,0 +1,176 @@ |
|||
.my-team-page { |
|||
min-height: 100vh; |
|||
background-color: #f5f5f5; |
|||
|
|||
.loading-container { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
height: 200px; |
|||
} |
|||
|
|||
.team-stats { |
|||
background: white; |
|||
margin: 16px; |
|||
border-radius: 12px; |
|||
padding: 20px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|||
|
|||
.stats-grid { |
|||
display: grid; |
|||
grid-template-columns: repeat(2, 1fr); |
|||
gap: 20px; |
|||
|
|||
.stat-item { |
|||
text-align: center; |
|||
|
|||
.stat-value { |
|||
font-size: 20px; // 对应 text-xl |
|||
font-weight: bold; |
|||
color: #1f2937; |
|||
margin-bottom: 4px; |
|||
} |
|||
|
|||
.stat-label { |
|||
font-size: 16px; // 对应 text-base |
|||
color: #6b7280; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.level-stats { |
|||
background: white; |
|||
margin: 0 16px 16px; |
|||
border-radius: 12px; |
|||
padding: 16px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|||
|
|||
.level-item { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 12px 0; |
|||
|
|||
&:not(:last-child) { |
|||
border-bottom: 1px solid #f3f4f6; |
|||
} |
|||
|
|||
.level-info { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
width: 100%; |
|||
|
|||
.level-title { |
|||
font-size: 20px; // 对应 text-xl |
|||
color: #1f2937; |
|||
} |
|||
|
|||
.level-count { |
|||
font-size: 20px; // 对应 text-xl |
|||
font-weight: bold; |
|||
color: #3b82f6; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.members-container { |
|||
margin: 0 16px; |
|||
|
|||
.empty-container { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
height: 300px; |
|||
} |
|||
|
|||
.members-list { |
|||
margin-top: 16px; |
|||
|
|||
.member-item { |
|||
background: white; |
|||
border-radius: 12px; |
|||
padding: 16px; |
|||
margin-bottom: 12px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|||
display: flex; |
|||
align-items: center; |
|||
cursor: pointer; |
|||
transition: all 0.2s ease; |
|||
|
|||
&:active { |
|||
transform: scale(0.98); |
|||
background-color: #f9f9f9; |
|||
} |
|||
|
|||
.member-info { |
|||
flex: 1; |
|||
margin-left: 12px; |
|||
|
|||
.member-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 8px; |
|||
|
|||
.member-name { |
|||
font-size: 20px; // 对应 text-xl,成员名称是重要信息 |
|||
font-weight: 500; |
|||
color: #1f2937; |
|||
} |
|||
|
|||
.member-level { |
|||
font-size: 14px; // 对应 text-sm |
|||
padding: 4px 10px; |
|||
border-radius: 12px; |
|||
color: white; |
|||
|
|||
&.level-1 { |
|||
background-color: #3b82f6; |
|||
} |
|||
|
|||
&.level-2 { |
|||
background-color: #10b981; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.member-stats { |
|||
display: flex; |
|||
gap: 16px; |
|||
margin-bottom: 6px; |
|||
|
|||
.stat { |
|||
font-size: 16px; // 对应 text-base |
|||
color: #6b7280; |
|||
} |
|||
} |
|||
|
|||
.member-time { |
|||
font-size: 14px; // 对应 text-sm |
|||
color: #9ca3af; |
|||
} |
|||
} |
|||
|
|||
.member-status { |
|||
font-size: 14px; // 对应 text-sm |
|||
padding: 6px 10px; |
|||
border-radius: 12px; |
|||
margin-left: 12px; |
|||
|
|||
&.active { |
|||
background-color: #d1fae5; |
|||
color: #10b981; |
|||
} |
|||
|
|||
&.inactive { |
|||
background-color: #fee2e2; |
|||
color: #ef4444; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,244 @@ |
|||
import { useState, useEffect } from 'react' |
|||
import { View } from '@tarojs/components' |
|||
import Taro from '@tarojs/taro' |
|||
import { Avatar, Empty, Tabs, TabPane } from '@nutui/nutui-react-taro' |
|||
import './index.scss' |
|||
|
|||
interface TeamMember { |
|||
id: string |
|||
nickname: string |
|||
avatar: string |
|||
joinTime: string |
|||
level: number |
|||
orderCount: number |
|||
totalCommission: number |
|||
status: 'active' | 'inactive' |
|||
} |
|||
|
|||
interface TeamStats { |
|||
totalMembers: number |
|||
activeMembers: number |
|||
level1Members: number |
|||
level2Members: number |
|||
totalCommission: number |
|||
monthCommission: number |
|||
} |
|||
|
|||
function MyTeam() { |
|||
const [activeTab, setActiveTab] = useState('0') |
|||
const [members, setMembers] = useState<TeamMember[]>([]) |
|||
const [stats, setStats] = useState<TeamStats>({ |
|||
totalMembers: 0, |
|||
activeMembers: 0, |
|||
level1Members: 0, |
|||
level2Members: 0, |
|||
totalCommission: 0, |
|||
monthCommission: 0 |
|||
}) |
|||
const [loading, setLoading] = useState(true) |
|||
|
|||
useEffect(() => { |
|||
Taro.setNavigationBarTitle({ |
|||
title: '我的团队' |
|||
}) |
|||
loadTeamData() |
|||
}, []) |
|||
|
|||
const loadTeamData = async () => { |
|||
try { |
|||
setLoading(true) |
|||
// 模拟数据
|
|||
const mockMembers: TeamMember[] = [ |
|||
{ |
|||
id: '1', |
|||
nickname: '张小明', |
|||
avatar: 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png', |
|||
joinTime: '2024-01-15', |
|||
level: 1, |
|||
orderCount: 15, |
|||
totalCommission: 150.50, |
|||
status: 'active' |
|||
}, |
|||
{ |
|||
id: '2', |
|||
nickname: '李小红', |
|||
avatar: 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png', |
|||
joinTime: '2024-01-10', |
|||
level: 1, |
|||
orderCount: 8, |
|||
totalCommission: 89.20, |
|||
status: 'active' |
|||
}, |
|||
{ |
|||
id: '3', |
|||
nickname: '王小华', |
|||
avatar: 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png', |
|||
joinTime: '2024-01-08', |
|||
level: 2, |
|||
orderCount: 3, |
|||
totalCommission: 25.80, |
|||
status: 'inactive' |
|||
}, |
|||
{ |
|||
id: '4', |
|||
nickname: '赵小刚', |
|||
avatar: 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png', |
|||
joinTime: '2024-01-05', |
|||
level: 2, |
|||
orderCount: 12, |
|||
totalCommission: 98.60, |
|||
status: 'active' |
|||
} |
|||
] |
|||
|
|||
// 计算统计数据
|
|||
const totalMembers = mockMembers.length |
|||
const activeMembers = mockMembers.filter(m => m.status === 'active').length |
|||
const level1Members = mockMembers.filter(m => m.level === 1).length |
|||
const level2Members = mockMembers.filter(m => m.level === 2).length |
|||
const totalCommission = mockMembers.reduce((sum, m) => sum + m.totalCommission, 0) |
|||
|
|||
setTimeout(() => { |
|||
setMembers(mockMembers) |
|||
setStats({ |
|||
totalMembers, |
|||
activeMembers, |
|||
level1Members, |
|||
level2Members, |
|||
totalCommission, |
|||
monthCommission: totalCommission * 0.3 // 模拟本月佣金
|
|||
}) |
|||
setLoading(false) |
|||
}, 1000) |
|||
} catch (error) { |
|||
console.error('加载团队数据失败:', error) |
|||
setLoading(false) |
|||
} |
|||
} |
|||
|
|||
const getFilteredMembers = () => { |
|||
switch (activeTab) { |
|||
case '1': |
|||
return members.filter(member => member.level === 1) |
|||
case '2': |
|||
return members.filter(member => member.level === 2) |
|||
case '3': |
|||
return members.filter(member => member.status === 'active') |
|||
default: |
|||
return members |
|||
} |
|||
} |
|||
|
|||
const handleMemberClick = (member: TeamMember) => { |
|||
Taro.showModal({ |
|||
title: '成员详情', |
|||
content: ` |
|||
昵称:${member.nickname} |
|||
加入时间:${member.joinTime} |
|||
等级:${member.level}级下线 |
|||
订单数量:${member.orderCount} |
|||
累计佣金:¥${member.totalCommission.toFixed(2)} |
|||
状态:${member.status === 'active' ? '活跃' : '不活跃'} |
|||
`.trim(),
|
|||
showCancel: false |
|||
}) |
|||
} |
|||
|
|||
if (loading) { |
|||
return ( |
|||
<View className="my-team-page"> |
|||
<View className="loading-container"> |
|||
<View className="text-center text-gray-500">加载中...</View> |
|||
</View> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
return ( |
|||
<View className="my-team-page"> |
|||
{/* 团队统计 */} |
|||
<View className="team-stats"> |
|||
<View className="stats-grid"> |
|||
<View className="stat-item"> |
|||
<View className="stat-value">{stats.totalMembers}</View> |
|||
<View className="stat-label">团队总人数</View> |
|||
</View> |
|||
<View className="stat-item"> |
|||
<View className="stat-value">{stats.activeMembers}</View> |
|||
<View className="stat-label">活跃成员</View> |
|||
</View> |
|||
<View className="stat-item"> |
|||
<View className="stat-value">¥{stats.totalCommission.toFixed(2)}</View> |
|||
<View className="stat-label">累计佣金</View> |
|||
</View> |
|||
<View className="stat-item"> |
|||
<View className="stat-value">¥{stats.monthCommission.toFixed(2)}</View> |
|||
<View className="stat-label">本月佣金</View> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
|
|||
{/* 等级统计 */} |
|||
<View className="level-stats"> |
|||
<View className="level-item"> |
|||
<View className="level-info"> |
|||
<View className="level-title">一级下线</View> |
|||
<View className="level-count">{stats.level1Members}人</View> |
|||
</View> |
|||
</View> |
|||
<View className="level-item"> |
|||
<View className="level-info"> |
|||
<View className="level-title">二级下线</View> |
|||
<View className="level-count">{stats.level2Members}人</View> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
|
|||
{/* 成员列表 */} |
|||
<View className="members-container"> |
|||
<Tabs value={activeTab} onChange={(value) => setActiveTab(value)}> |
|||
<TabPane title="全部" /> |
|||
<TabPane title="一级" /> |
|||
<TabPane title="二级" /> |
|||
<TabPane title="活跃" /> |
|||
</Tabs> |
|||
|
|||
<View className="members-list"> |
|||
{getFilteredMembers().length === 0 ? ( |
|||
<View className="empty-container"> |
|||
<Empty description="暂无团队成员" /> |
|||
</View> |
|||
) : ( |
|||
getFilteredMembers().map((member) => ( |
|||
<View |
|||
key={member.id} |
|||
className="member-item" |
|||
onClick={() => handleMemberClick(member)} |
|||
> |
|||
<Avatar size="50" src={member.avatar} shape="round" /> |
|||
<View className="member-info"> |
|||
<View className="member-header"> |
|||
<View className="member-name">{member.nickname}</View> |
|||
<View className={`member-level level-${member.level}`}> |
|||
{member.level}级 |
|||
</View> |
|||
</View> |
|||
<View className="member-stats"> |
|||
<View className="stat">订单:{member.orderCount}</View> |
|||
<View className="stat">佣金:¥{member.totalCommission.toFixed(2)}</View> |
|||
</View> |
|||
<View className="member-time">加入时间:{member.joinTime}</View> |
|||
</View> |
|||
<View className={`member-status ${member.status}`}> |
|||
{member.status === 'active' ? '活跃' : '不活跃'} |
|||
</View> |
|||
</View> |
|||
)) |
|||
)} |
|||
</View> |
|||
</View> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
export default MyTeam |
@ -0,0 +1,75 @@ |
|||
.withdraw-detail-page { |
|||
min-height: 100vh; |
|||
background-color: #f5f5f5; |
|||
padding: 16px; |
|||
|
|||
.loading-container { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
height: 200px; |
|||
} |
|||
|
|||
.empty-container { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
height: 400px; |
|||
} |
|||
|
|||
.records-list { |
|||
.record-item { |
|||
background: white; |
|||
border-radius: 12px; |
|||
padding: 16px; |
|||
margin-bottom: 12px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|||
cursor: pointer; |
|||
transition: all 0.2s ease; |
|||
|
|||
&:active { |
|||
transform: scale(0.98); |
|||
background-color: #f9f9f9; |
|||
} |
|||
|
|||
.record-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 12px; |
|||
|
|||
.amount { |
|||
font-size: 20px; // 对应 text-xl,重要金额 |
|||
font-weight: bold; |
|||
color: #1f2937; |
|||
} |
|||
|
|||
.status { |
|||
font-size: 14px; // 对应 text-sm |
|||
font-weight: 500; |
|||
padding: 4px 12px; |
|||
border-radius: 20px; |
|||
background-color: rgba(0, 0, 0, 0.05); |
|||
} |
|||
} |
|||
|
|||
.record-info { |
|||
.time { |
|||
font-size: 16px; // 对应 text-base |
|||
color: #6b7280; |
|||
margin-bottom: 4px; |
|||
} |
|||
|
|||
.remark { |
|||
font-size: 16px; // 对应 text-base |
|||
color: #ef4444; |
|||
margin-top: 8px; |
|||
padding: 8px; |
|||
background-color: #fef2f2; |
|||
border-radius: 6px; |
|||
border-left: 3px solid #ef4444; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,146 @@ |
|||
import { useState, useEffect } from 'react' |
|||
import { View } from '@tarojs/components' |
|||
import Taro from '@tarojs/taro' |
|||
import { Empty } from '@nutui/nutui-react-taro' |
|||
import './index.scss' |
|||
|
|||
interface WithdrawRecord { |
|||
id: string |
|||
amount: number |
|||
status: 'pending' | 'success' | 'failed' |
|||
statusText: string |
|||
createTime: string |
|||
completeTime?: string |
|||
remark?: string |
|||
} |
|||
|
|||
function WithdrawDetail() { |
|||
const [records, setRecords] = useState<WithdrawRecord[]>([]) |
|||
const [loading, setLoading] = useState(true) |
|||
|
|||
useEffect(() => { |
|||
Taro.setNavigationBarTitle({ |
|||
title: '提现明细' |
|||
}) |
|||
loadWithdrawRecords() |
|||
}, []) |
|||
|
|||
const loadWithdrawRecords = async () => { |
|||
try { |
|||
setLoading(true) |
|||
// 模拟数据,实际应该调用API
|
|||
const mockData: WithdrawRecord[] = [ |
|||
{ |
|||
id: '1', |
|||
amount: 100.00, |
|||
status: 'success', |
|||
statusText: '提现成功', |
|||
createTime: '2024-01-15 14:30:00', |
|||
completeTime: '2024-01-15 16:45:00' |
|||
}, |
|||
{ |
|||
id: '2', |
|||
amount: 50.00, |
|||
status: 'pending', |
|||
statusText: '处理中', |
|||
createTime: '2024-01-10 09:20:00' |
|||
}, |
|||
{ |
|||
id: '3', |
|||
amount: 200.00, |
|||
status: 'failed', |
|||
statusText: '提现失败', |
|||
createTime: '2024-01-05 11:15:00', |
|||
remark: '银行卡信息有误' |
|||
} |
|||
] |
|||
|
|||
setTimeout(() => { |
|||
setRecords(mockData) |
|||
setLoading(false) |
|||
}, 1000) |
|||
} catch (error) { |
|||
console.error('加载提现记录失败:', error) |
|||
setLoading(false) |
|||
} |
|||
} |
|||
|
|||
const getStatusColor = (status: string) => { |
|||
switch (status) { |
|||
case 'success': |
|||
return '#10B981' |
|||
case 'pending': |
|||
return '#F59E0B' |
|||
case 'failed': |
|||
return '#EF4444' |
|||
default: |
|||
return '#6B7280' |
|||
} |
|||
} |
|||
|
|||
const handleRecordClick = (record: WithdrawRecord) => { |
|||
const content = ` |
|||
提现金额:¥${record.amount.toFixed(2)} |
|||
申请时间:${record.createTime} |
|||
${record.completeTime ? `完成时间:${record.completeTime}` : ''} |
|||
${record.remark ? `备注:${record.remark}` : ''} |
|||
`.trim()
|
|||
|
|||
Taro.showModal({ |
|||
title: '提现详情', |
|||
content, |
|||
showCancel: false |
|||
}) |
|||
} |
|||
|
|||
if (loading) { |
|||
return ( |
|||
<View className="withdraw-detail-page"> |
|||
<View className="loading-container"> |
|||
<View className="text-center text-gray-500">加载中...</View> |
|||
</View> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
return ( |
|||
<View className="withdraw-detail-page"> |
|||
{records.length === 0 ? ( |
|||
<View className="empty-container"> |
|||
<Empty description="暂无提现记录" /> |
|||
</View> |
|||
) : ( |
|||
<View className="records-list"> |
|||
{records.map((record) => ( |
|||
<View |
|||
key={record.id} |
|||
className="record-item" |
|||
onClick={() => handleRecordClick(record)} |
|||
> |
|||
<View className="record-header"> |
|||
<View className="amount">¥{record.amount.toFixed(2)}</View> |
|||
<View |
|||
className="status" |
|||
style={{ color: getStatusColor(record.status) }} |
|||
> |
|||
{record.statusText} |
|||
</View> |
|||
</View> |
|||
<View className="record-info"> |
|||
<View className="time">申请时间:{record.createTime}</View> |
|||
{record.completeTime && ( |
|||
<View className="time">完成时间:{record.completeTime}</View> |
|||
)} |
|||
{record.remark && ( |
|||
<View className="remark">备注:{record.remark}</View> |
|||
)} |
|||
</View> |
|||
</View> |
|||
))} |
|||
</View> |
|||
)} |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
export default WithdrawDetail |
Loading…
Reference in new issue