forked from gxwebsoft/mp-10550
补分销中心页面
This commit is contained in:
@@ -42,6 +42,16 @@ export default defineAppConfig({
|
||||
"points/points"
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "dealer",
|
||||
"pages": [
|
||||
"index",
|
||||
"withdraw/index",
|
||||
"orders/index",
|
||||
"team/index",
|
||||
"qrcode/index"
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "shop",
|
||||
"pages": [
|
||||
|
||||
75
src/dealer/components/EarningsCard.tsx
Normal file
75
src/dealer/components/EarningsCard.tsx
Normal file
@@ -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
|
||||
102
src/dealer/components/FunctionMenu.tsx
Normal file
102
src/dealer/components/FunctionMenu.tsx
Normal file
@@ -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
|
||||
78
src/dealer/components/NavigationBar.tsx
Normal file
78
src/dealer/components/NavigationBar.tsx
Normal file
@@ -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
|
||||
211
src/dealer/components/OrderIcon.tsx
Normal file
211
src/dealer/components/OrderIcon.tsx
Normal file
@@ -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
|
||||
135
src/dealer/components/UserCard.tsx
Normal file
135
src/dealer/components/UserCard.tsx
Normal file
@@ -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;
|
||||
148
src/dealer/components/UserCell.tsx
Normal file
148
src/dealer/components/UserCell.tsx
Normal file
@@ -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
|
||||
102
src/dealer/components/UserFooter.tsx
Normal file
102
src/dealer/components/UserFooter.tsx
Normal file
@@ -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
|
||||
3
src/dealer/index.config.ts
Normal file
3
src/dealer/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '分销中心'
|
||||
})
|
||||
77
src/dealer/index.scss
Normal file
77
src/dealer/index.scss
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
64
src/dealer/index.tsx
Normal file
64
src/dealer/index.tsx
Normal file
@@ -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
|
||||
145
src/dealer/orders/index.scss
Normal file
145
src/dealer/orders/index.scss
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
238
src/dealer/orders/index.tsx
Normal file
238
src/dealer/orders/index.tsx
Normal file
@@ -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
|
||||
247
src/dealer/qrcode/index.scss
Normal file
247
src/dealer/qrcode/index.scss
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
221
src/dealer/qrcode/index.tsx
Normal file
221
src/dealer/qrcode/index.tsx
Normal file
@@ -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
|
||||
176
src/dealer/team/index.scss
Normal file
176
src/dealer/team/index.scss
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
244
src/dealer/team/index.tsx
Normal file
244
src/dealer/team/index.tsx
Normal file
@@ -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
|
||||
75
src/dealer/withdraw/index.scss
Normal file
75
src/dealer/withdraw/index.scss
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
146
src/dealer/withdraw/index.tsx
Normal file
146
src/dealer/withdraw/index.tsx
Normal file
@@ -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
|
||||
@@ -32,11 +32,9 @@ const UserCell = () => {
|
||||
backgroundImage: 'linear-gradient(to right bottom, #54a799, #177b73)',
|
||||
}}
|
||||
title={
|
||||
<div style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<div style={{display: 'inline-flex', alignItems: 'center'}} onClick={() => navTo('/dealer/index', true)}>
|
||||
<Reward className={'text-orange-100 '} size={16}/>
|
||||
<div>
|
||||
<span style={{ fontSize: '16px'}} className={'pl-3 text-orange-100 font-medium'}>开通会员</span>
|
||||
</div>
|
||||
<span style={{fontSize: '16px'}} className={'pl-3 text-orange-100 font-medium'}>开通会员</span>
|
||||
<span className={'text-white opacity-80 pl-3'}>享优惠</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user