Browse Source

补分销中心页面

master
科技小王子 2 weeks ago
parent
commit
afe54770a8
  1. 10
      src/app.config.ts
  2. 75
      src/dealer/components/EarningsCard.tsx
  3. 102
      src/dealer/components/FunctionMenu.tsx
  4. 78
      src/dealer/components/NavigationBar.tsx
  5. 211
      src/dealer/components/OrderIcon.tsx
  6. 135
      src/dealer/components/UserCard.tsx
  7. 148
      src/dealer/components/UserCell.tsx
  8. 102
      src/dealer/components/UserFooter.tsx
  9. 3
      src/dealer/index.config.ts
  10. 77
      src/dealer/index.scss
  11. 64
      src/dealer/index.tsx
  12. 145
      src/dealer/orders/index.scss
  13. 238
      src/dealer/orders/index.tsx
  14. 247
      src/dealer/qrcode/index.scss
  15. 221
      src/dealer/qrcode/index.tsx
  16. 176
      src/dealer/team/index.scss
  17. 244
      src/dealer/team/index.tsx
  18. 75
      src/dealer/withdraw/index.scss
  19. 146
      src/dealer/withdraw/index.tsx
  20. 4
      src/pages/user/components/UserCell.tsx

10
src/app.config.ts

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '分销中心'
})

77
src/dealer/index.scss

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

4
src/pages/user/components/UserCell.tsx

@ -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 className={'text-white opacity-80 pl-3'}></span>
</div>
}

Loading…
Cancel
Save