补分销中心页面

This commit is contained in:
2025-08-10 01:38:17 +08:00
parent 44fd815fe7
commit afe54770a8
20 changed files with 2499 additions and 4 deletions

View 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

View 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

View 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

View 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

View 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;

View 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

View 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