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