This commit is contained in:
72
miniprogram/src/subpackages/distributor/_shared.scss
Normal file
72
miniprogram/src/subpackages/distributor/_shared.scss
Normal file
@@ -0,0 +1,72 @@
|
||||
// Tech/Cyberpunk Theme Variables & Mixins
|
||||
|
||||
// Colors
|
||||
$bg-dark: #050505;
|
||||
$primary-cyan: #00f0ff;
|
||||
$primary-green: #00b96b;
|
||||
$primary-purple: #bd00ff;
|
||||
$text-main: #ffffff;
|
||||
$text-secondary: rgba(255, 255, 255, 0.7);
|
||||
$text-muted: rgba(255, 255, 255, 0.4);
|
||||
|
||||
// Mixins
|
||||
@mixin page-container {
|
||||
min-height: 100vh;
|
||||
background-color: $bg-dark;
|
||||
background-image:
|
||||
radial-gradient(circle at 10% 10%, rgba(0, 240, 255, 0.1) 0%, transparent 40%),
|
||||
radial-gradient(circle at 90% 90%, rgba(189, 0, 255, 0.1) 0%, transparent 40%);
|
||||
color: $text-main;
|
||||
padding: 30px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@mixin glass-card {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
@mixin neon-text($color: $primary-cyan) {
|
||||
color: $color;
|
||||
text-shadow: 0 0 10px rgba($color, 0.5), 0 0 20px rgba($color, 0.3);
|
||||
}
|
||||
|
||||
@mixin neon-button($color: $primary-cyan) {
|
||||
background: rgba($color, 0.1);
|
||||
border: 1px solid rgba($color, 0.5);
|
||||
color: $color;
|
||||
box-shadow: 0 0 15px rgba($color, 0.2);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
background: rgba($color, 0.2);
|
||||
box-shadow: 0 0 25px rgba($color, 0.4);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin tech-border {
|
||||
position: relative;
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -1px; left: -1px;
|
||||
width: 20px; height: 20px;
|
||||
border-top: 2px solid $primary-cyan;
|
||||
border-left: 2px solid $primary-cyan;
|
||||
border-radius: 4px 0 0 0;
|
||||
}
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -1px; right: -1px;
|
||||
width: 20px; height: 20px;
|
||||
border-bottom: 2px solid $primary-cyan;
|
||||
border-right: 2px solid $primary-cyan;
|
||||
border-radius: 0 0 4px 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '收益明细'
|
||||
})
|
||||
56
miniprogram/src/subpackages/distributor/earnings.scss
Normal file
56
miniprogram/src/subpackages/distributor/earnings.scss
Normal file
@@ -0,0 +1,56 @@
|
||||
@import './_shared.scss';
|
||||
|
||||
.page-container {
|
||||
@include page-container;
|
||||
}
|
||||
|
||||
.item {
|
||||
@include glass-card;
|
||||
padding: 30px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
align-items: center;
|
||||
|
||||
.type {
|
||||
font-size: 28px;
|
||||
color: $text-main;
|
||||
font-weight: bold;
|
||||
}
|
||||
.amount {
|
||||
font-size: 32px;
|
||||
@include neon-text($primary-green);
|
||||
font-weight: bold;
|
||||
font-family: 'DIN Alternate', sans-serif;
|
||||
}
|
||||
.source {
|
||||
font-size: 24px;
|
||||
color: $text-secondary;
|
||||
}
|
||||
.status {
|
||||
font-size: 24px;
|
||||
color: $primary-cyan;
|
||||
background: rgba(0, 240, 255, 0.1);
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
.time {
|
||||
font-size: 22px;
|
||||
color: $text-muted;
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 100px 0;
|
||||
text-align: center;
|
||||
color: $text-muted;
|
||||
font-size: 28px;
|
||||
}
|
||||
55
miniprogram/src/subpackages/distributor/earnings.tsx
Normal file
55
miniprogram/src/subpackages/distributor/earnings.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { View, Text } from '@tarojs/components'
|
||||
import Taro, { useLoad } from '@tarojs/taro'
|
||||
import { useState } from 'react'
|
||||
import { distributorEarnings } from '../../api'
|
||||
import './earnings.scss'
|
||||
|
||||
export default function Earnings() {
|
||||
const [list, setList] = useState<any[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useLoad(() => {
|
||||
fetchData()
|
||||
})
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const res: any = await distributorEarnings()
|
||||
// Pagination support check? The backend returns { count, next, previous, results } or just list if no pagination
|
||||
if (res.results) {
|
||||
setList(res.results)
|
||||
} else if (Array.isArray(res)) {
|
||||
setList(res)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
Taro.showToast({ title: '加载失败', icon: 'none' })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='page-container'>
|
||||
{list.length > 0 ? (
|
||||
list.map((item: any) => (
|
||||
<View className='item' key={item.id}>
|
||||
<View className='row'>
|
||||
<Text className='type'>{item.level === 1 ? '直接推广' : '团队奖励'}</Text>
|
||||
<Text className='amount'>+{item.amount}</Text>
|
||||
</View>
|
||||
<View className='row'>
|
||||
<Text className='source'>
|
||||
{item.order_info?.customer_name} - 订单金额 ¥{item.order_info?.total_price}
|
||||
</Text>
|
||||
<Text className='status'>{item.status === 'settled' ? '已结算' : '待结算'}</Text>
|
||||
</View>
|
||||
<Text className='time'>{item.created_at?.replace('T', ' ').substring(0, 19)}</Text>
|
||||
</View>
|
||||
))
|
||||
) : (
|
||||
<View className='empty'>暂无收益记录</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
3
miniprogram/src/subpackages/distributor/index.config.ts
Normal file
3
miniprogram/src/subpackages/distributor/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '分销中心'
|
||||
})
|
||||
112
miniprogram/src/subpackages/distributor/index.scss
Normal file
112
miniprogram/src/subpackages/distributor/index.scss
Normal file
@@ -0,0 +1,112 @@
|
||||
@import './_shared.scss';
|
||||
|
||||
.page-container {
|
||||
@include page-container;
|
||||
}
|
||||
|
||||
.header-card {
|
||||
@include glass-card;
|
||||
@include tech-border;
|
||||
padding: 40px 30px;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
// Background accent
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, rgba(0, 240, 255, 0.05), rgba(0, 185, 107, 0.05));
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 16px;
|
||||
color: $text-secondary;
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 56px;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 30px;
|
||||
@include neon-text($primary-cyan);
|
||||
font-family: 'DIN Alternate', sans-serif; // Use a tech-looking font if available
|
||||
}
|
||||
|
||||
.btn-withdraw {
|
||||
@include neon-button($primary-green);
|
||||
border-radius: 30px;
|
||||
font-size: 18px;
|
||||
padding: 0 40px;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: flex;
|
||||
@include glass-card;
|
||||
padding: 30px 0;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.1);
|
||||
|
||||
&:last-child { border-right: none; }
|
||||
|
||||
.val {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: $text-main;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
text-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
.lbl {
|
||||
font-size: 14px;
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
@include glass-card;
|
||||
padding: 0 20px;
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 70px; // Larger touch target
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
font-size: 18px;
|
||||
color: $text-main;
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
|
||||
.arrow {
|
||||
color: $primary-cyan;
|
||||
opacity: 0.7;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
miniprogram/src/subpackages/distributor/index.tsx
Normal file
83
miniprogram/src/subpackages/distributor/index.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { View, Text, Button } from '@tarojs/components'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
import { useState } from 'react'
|
||||
import { distributorInfo } from '../../api'
|
||||
import './index.scss'
|
||||
|
||||
export default function DistributorIndex() {
|
||||
const [info, setInfo] = useState<any>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useDidShow(() => {
|
||||
fetchInfo()
|
||||
})
|
||||
|
||||
const fetchInfo = async () => {
|
||||
try {
|
||||
const res = await distributorInfo()
|
||||
setInfo(res)
|
||||
} catch (err: any) {
|
||||
if (err.statusCode === 404) {
|
||||
// Not registered
|
||||
Taro.redirectTo({ url: '/subpackages/distributor/register' })
|
||||
} else {
|
||||
Taro.showToast({ title: '加载失败', icon: 'none' })
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const goInvite = () => Taro.navigateTo({ url: '/subpackages/distributor/invite' })
|
||||
const goWithdraw = () => Taro.navigateTo({ url: '/subpackages/distributor/withdraw' })
|
||||
const goTeam = () => Taro.navigateTo({ url: '/subpackages/distributor/team' })
|
||||
const goEarnings = () => Taro.navigateTo({ url: '/subpackages/distributor/earnings' })
|
||||
const goOrders = () => Taro.navigateTo({ url: '/subpackages/distributor/orders' })
|
||||
|
||||
if (loading) return <View>Loading...</View>
|
||||
if (!info) return <View>Error</View>
|
||||
|
||||
return (
|
||||
<View className='page-container'>
|
||||
<View className='header-card'>
|
||||
<Text className='label'>可提现余额</Text>
|
||||
<Text className='amount'>¥{info.withdrawable_balance}</Text>
|
||||
<Button className='btn-withdraw' onClick={goWithdraw}>提现</Button>
|
||||
</View>
|
||||
|
||||
<View className='stats-grid'>
|
||||
<View className='item'>
|
||||
<Text className='val'>¥{info.total_earnings}</Text>
|
||||
<Text className='lbl'>累计收益</Text>
|
||||
</View>
|
||||
<View className='item'>
|
||||
<Text className='val'>Lv.{info.level}</Text>
|
||||
<Text className='lbl'>当前等级</Text>
|
||||
</View>
|
||||
<View className='item'>
|
||||
<Text className='val'>{(Number(info.commission_rate) * 100).toFixed(1)}%</Text>
|
||||
<Text className='lbl'>分佣比例</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='menu-list'>
|
||||
<View className='menu-item' onClick={goInvite}>
|
||||
<Text>推广二维码</Text>
|
||||
<Text className='arrow'>{'>'}</Text>
|
||||
</View>
|
||||
<View className='menu-item' onClick={goTeam}>
|
||||
<Text>我的团队</Text>
|
||||
<Text className='arrow'>{'>'}</Text>
|
||||
</View>
|
||||
<View className='menu-item' onClick={goEarnings}>
|
||||
<Text>收益明细</Text>
|
||||
<Text className='arrow'>{'>'}</Text>
|
||||
</View>
|
||||
<View className='menu-item' onClick={goOrders}>
|
||||
<Text>分销订单</Text>
|
||||
<Text className='arrow'>{'>'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
3
miniprogram/src/subpackages/distributor/invite.config.ts
Normal file
3
miniprogram/src/subpackages/distributor/invite.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '推广邀请'
|
||||
})
|
||||
49
miniprogram/src/subpackages/distributor/invite.scss
Normal file
49
miniprogram/src/subpackages/distributor/invite.scss
Normal file
@@ -0,0 +1,49 @@
|
||||
@import './_shared.scss';
|
||||
|
||||
.page-container {
|
||||
@include page-container;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.qr-card {
|
||||
@include glass-card;
|
||||
@include tech-border;
|
||||
padding: 40px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 40px;
|
||||
|
||||
.qr-img {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
margin-bottom: 40px;
|
||||
border: 1px solid $primary-cyan;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
color: $text-main;
|
||||
font-size: 28px;
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
margin-top: 60px;
|
||||
width: 100%;
|
||||
@include neon-button($primary-cyan);
|
||||
height: 90px;
|
||||
line-height: 90px;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
border-radius: 45px;
|
||||
}
|
||||
57
miniprogram/src/subpackages/distributor/invite.tsx
Normal file
57
miniprogram/src/subpackages/distributor/invite.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { View, Text, Image, Button } from '@tarojs/components'
|
||||
import Taro, { useLoad } from '@tarojs/taro'
|
||||
import { useState } from 'react'
|
||||
import { distributorInvite } from '../../api'
|
||||
import './invite.scss'
|
||||
|
||||
export default function Invite() {
|
||||
const [qrCode, setQrCode] = useState('')
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useLoad(() => {
|
||||
fetchQr()
|
||||
})
|
||||
|
||||
const fetchQr = async () => {
|
||||
try {
|
||||
const res: any = await distributorInvite()
|
||||
setQrCode(res.qr_code_url)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
Taro.showToast({ title: '获取二维码失败', icon: 'none' })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const saveImage = () => {
|
||||
if (!qrCode) return
|
||||
Taro.downloadFile({
|
||||
url: qrCode,
|
||||
success: (res) => {
|
||||
Taro.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: () => Taro.showToast({ title: '已保存', icon: 'success' }),
|
||||
fail: () => Taro.showToast({ title: '保存失败', icon: 'none' })
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='page-container'>
|
||||
<View className='qr-card'>
|
||||
{loading ? (
|
||||
<View className='qr-img' style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Text>Loading...</Text>
|
||||
</View>
|
||||
) : (
|
||||
<Image src={qrCode} className='qr-img' mode='aspectFit' />
|
||||
)}
|
||||
<Text className='tip'>扫码加入我的团队{'\n'}一起推广赚佣金</Text>
|
||||
</View>
|
||||
|
||||
<Button className='btn-save' onClick={saveImage} disabled={!qrCode}>保存二维码</Button>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
3
miniprogram/src/subpackages/distributor/orders.config.ts
Normal file
3
miniprogram/src/subpackages/distributor/orders.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '分销订单'
|
||||
})
|
||||
72
miniprogram/src/subpackages/distributor/orders.scss
Normal file
72
miniprogram/src/subpackages/distributor/orders.scss
Normal file
@@ -0,0 +1,72 @@
|
||||
@import './_shared.scss';
|
||||
|
||||
.page-container {
|
||||
@include page-container;
|
||||
}
|
||||
|
||||
.item {
|
||||
@include glass-card;
|
||||
padding: 30px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.order-no {
|
||||
font-size: 24px;
|
||||
color: $text-secondary;
|
||||
font-family: monospace;
|
||||
}
|
||||
.status {
|
||||
font-size: 24px;
|
||||
color: $primary-green;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.img {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
margin-right: 24px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
color: $text-main;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.price {
|
||||
font-size: 32px;
|
||||
color: $primary-cyan;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 24px;
|
||||
color: $text-muted;
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 100px 0;
|
||||
text-align: center;
|
||||
color: $text-muted;
|
||||
font-size: 28px;
|
||||
}
|
||||
58
miniprogram/src/subpackages/distributor/orders.tsx
Normal file
58
miniprogram/src/subpackages/distributor/orders.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { View, Text, Image } from '@tarojs/components'
|
||||
import Taro, { useLoad } from '@tarojs/taro'
|
||||
import { useState } from 'react'
|
||||
import { distributorOrders } from '../../api'
|
||||
import './orders.scss'
|
||||
|
||||
export default function Orders() {
|
||||
const [list, setList] = useState<any[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useLoad(() => {
|
||||
fetchData()
|
||||
})
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const res: any = await distributorOrders()
|
||||
if (res.results) {
|
||||
setList(res.results)
|
||||
} else if (Array.isArray(res)) {
|
||||
setList(res)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
Taro.showToast({ title: '加载失败', icon: 'none' })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='page-container'>
|
||||
{list.length > 0 ? (
|
||||
list.map((item: any) => (
|
||||
<View className='item' key={item.id}>
|
||||
<View className='row'>
|
||||
<Text className='order-no'>订单号: {item.wechat_trade_no || item.id}</Text>
|
||||
<Text className='status'>{item.status === 'paid' ? '已支付' : item.status}</Text>
|
||||
</View>
|
||||
<View className='content'>
|
||||
<Image className='img' src={item.config_image || ''} mode='aspectFill' />
|
||||
<View className='info'>
|
||||
<Text className='title'>{item.config_name || item.course_title || '商品'}</Text>
|
||||
<Text className='price'>¥{item.total_price}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className='footer'>
|
||||
<Text className='customer'>买家: {item.customer_name}</Text>
|
||||
<Text className='time'>{item.created_at?.split('T')[0]}</Text>
|
||||
</View>
|
||||
</View>
|
||||
))
|
||||
) : (
|
||||
<View className='empty'>暂无分销订单</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '申请分销员'
|
||||
})
|
||||
46
miniprogram/src/subpackages/distributor/register.scss
Normal file
46
miniprogram/src/subpackages/distributor/register.scss
Normal file
@@ -0,0 +1,46 @@
|
||||
@import './_shared.scss';
|
||||
|
||||
.page-container {
|
||||
@include page-container;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card {
|
||||
@include glass-card;
|
||||
@include tech-border;
|
||||
padding: 50px 30px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
|
||||
.title {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
@include neon-text($primary-purple);
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 16px;
|
||||
color: $text-secondary;
|
||||
display: block;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.btn-register {
|
||||
@include neon-button($primary-green);
|
||||
background: linear-gradient(90deg, rgba(0, 185, 107, 0.2), rgba(0, 240, 255, 0.2));
|
||||
border: 1px solid $primary-green;
|
||||
border-radius: 30px;
|
||||
height: 56px;
|
||||
line-height: 56px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
}
|
||||
32
miniprogram/src/subpackages/distributor/register.tsx
Normal file
32
miniprogram/src/subpackages/distributor/register.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { View, Button, Text } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { distributorRegister } from '../../api'
|
||||
import './register.scss'
|
||||
|
||||
export default function Register() {
|
||||
const handleRegister = async () => {
|
||||
try {
|
||||
await distributorRegister({})
|
||||
Taro.showToast({ title: '申请已提交', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
Taro.redirectTo({ url: '/subpackages/distributor/index' })
|
||||
}, 1500)
|
||||
} catch (err: any) {
|
||||
if (err.data?.error === 'Already registered') {
|
||||
Taro.redirectTo({ url: '/subpackages/distributor/index' })
|
||||
} else {
|
||||
Taro.showToast({ title: '申请失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='page-container'>
|
||||
<View className='card'>
|
||||
<Text className='title'>加入我们</Text>
|
||||
<Text className='desc'>成为分销员,分享赚取佣金</Text>
|
||||
<Button className='btn-register' onClick={handleRegister}>立即申请</Button>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
3
miniprogram/src/subpackages/distributor/team.config.ts
Normal file
3
miniprogram/src/subpackages/distributor/team.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '我的团队'
|
||||
})
|
||||
85
miniprogram/src/subpackages/distributor/team.scss
Normal file
85
miniprogram/src/subpackages/distributor/team.scss
Normal file
@@ -0,0 +1,85 @@
|
||||
@import './_shared.scss';
|
||||
|
||||
.page-container {
|
||||
@include page-container;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.header {
|
||||
@include glass-card;
|
||||
padding: 30px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.val {
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
@include neon-text($primary-cyan);
|
||||
}
|
||||
.lbl {
|
||||
font-size: 24px;
|
||||
color: $text-secondary;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
@include glass-card;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
.list-header {
|
||||
padding: 24px 30px;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $text-main;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24px 30px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
|
||||
.avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
margin-right: 24px;
|
||||
background: #333;
|
||||
border: 2px solid $primary-purple;
|
||||
}
|
||||
.info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.name {
|
||||
font-size: 28px;
|
||||
color: $text-main;
|
||||
}
|
||||
.time {
|
||||
font-size: 22px;
|
||||
color: $text-muted;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
.level {
|
||||
font-size: 24px;
|
||||
color: $primary-purple;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.empty {
|
||||
padding: 50px;
|
||||
text-align: center;
|
||||
color: $text-muted;
|
||||
}
|
||||
}
|
||||
62
miniprogram/src/subpackages/distributor/team.tsx
Normal file
62
miniprogram/src/subpackages/distributor/team.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { View, Text, Image } from '@tarojs/components'
|
||||
import Taro, { useLoad } from '@tarojs/taro'
|
||||
import { useState } from 'react'
|
||||
import { distributorTeam } from '../../api'
|
||||
import './team.scss'
|
||||
|
||||
export default function Team() {
|
||||
const [data, setData] = useState<any>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useLoad(() => {
|
||||
fetchData()
|
||||
})
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const res = await distributorTeam()
|
||||
setData(res)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
Taro.showToast({ title: '加载失败', icon: 'none' })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) return <View className='page-container'>Loading...</View>
|
||||
if (!data) return <View className='page-container'>Error</View>
|
||||
|
||||
return (
|
||||
<View className='page-container'>
|
||||
<View className='header'>
|
||||
<View className='stat'>
|
||||
<Text className='val'>{data.children_count}</Text>
|
||||
<Text className='lbl'>直推人数</Text>
|
||||
</View>
|
||||
<View className='stat'>
|
||||
<Text className='val'>¥{data.second_level_earnings}</Text>
|
||||
<Text className='lbl'>团队贡献收益</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='list'>
|
||||
<View className='list-header'>我的团队成员</View>
|
||||
{data.children?.length > 0 ? (
|
||||
data.children.map((item: any) => (
|
||||
<View className='item' key={item.id}>
|
||||
<Image className='avatar' src={item.user_info?.avatar_url || ''} />
|
||||
<View className='info'>
|
||||
<Text className='name'>{item.user_info?.nickname || '用户'}</Text>
|
||||
<Text className='time'>加入时间: {item.created_at?.split('T')[0]}</Text>
|
||||
</View>
|
||||
<Text className='level'>Lv.{item.level}</Text>
|
||||
</View>
|
||||
))
|
||||
) : (
|
||||
<View className='empty'>暂无成员</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '申请提现'
|
||||
})
|
||||
60
miniprogram/src/subpackages/distributor/withdraw.scss
Normal file
60
miniprogram/src/subpackages/distributor/withdraw.scss
Normal file
@@ -0,0 +1,60 @@
|
||||
@import './_shared.scss';
|
||||
|
||||
.page-container {
|
||||
@include page-container;
|
||||
}
|
||||
|
||||
.card {
|
||||
@include glass-card;
|
||||
padding: 40px;
|
||||
|
||||
.label {
|
||||
font-size: 28px;
|
||||
color: $text-secondary;
|
||||
margin-bottom: 24px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.input-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.symbol {
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
color: $text-main;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.input {
|
||||
flex: 1;
|
||||
height: 60px;
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
color: $primary-green;
|
||||
}
|
||||
}
|
||||
|
||||
.balance-tip {
|
||||
font-size: 24px;
|
||||
color: $text-muted;
|
||||
|
||||
.all {
|
||||
color: $primary-cyan;
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
margin-top: 80px;
|
||||
@include neon-button($primary-green);
|
||||
height: 88px;
|
||||
line-height: 88px;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
border-radius: 44px;
|
||||
}
|
||||
}
|
||||
73
miniprogram/src/subpackages/distributor/withdraw.tsx
Normal file
73
miniprogram/src/subpackages/distributor/withdraw.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { View, Text, Button, Input } from '@tarojs/components'
|
||||
import Taro, { useLoad } from '@tarojs/taro'
|
||||
import { useState } from 'react'
|
||||
import { distributorInfo, distributorWithdraw } from '../../api'
|
||||
import './withdraw.scss'
|
||||
|
||||
export default function Withdraw() {
|
||||
const [balance, setBalance] = useState(0)
|
||||
const [amount, setAmount] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
useLoad(() => {
|
||||
fetchInfo()
|
||||
})
|
||||
|
||||
const fetchInfo = async () => {
|
||||
try {
|
||||
const res: any = await distributorInfo()
|
||||
setBalance(Number(res.withdrawable_balance))
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleWithdraw = async () => {
|
||||
const val = Number(amount)
|
||||
if (!val || val <= 0) {
|
||||
Taro.showToast({ title: '请输入有效金额', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (val > balance) {
|
||||
Taro.showToast({ title: '余额不足', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
await distributorWithdraw(val)
|
||||
Taro.showToast({ title: '申请已提交', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
Taro.navigateBack()
|
||||
}, 1500)
|
||||
} catch (err) {
|
||||
Taro.showToast({ title: '提现失败', icon: 'none' })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='page-container'>
|
||||
<View className='card'>
|
||||
<Text className='label'>提现金额</Text>
|
||||
<View className='input-box'>
|
||||
<Text className='symbol'>¥</Text>
|
||||
<Input
|
||||
className='input'
|
||||
type='digit'
|
||||
value={amount}
|
||||
onInput={(e) => setAmount(e.detail.value)}
|
||||
placeholder='0.00'
|
||||
/>
|
||||
</View>
|
||||
<View className='balance-tip'>
|
||||
<Text>可提现余额 ¥{balance.toFixed(2)}</Text>
|
||||
<Text className='all' onClick={() => setAmount(balance.toString())}>全部提现</Text>
|
||||
</View>
|
||||
|
||||
<Button className='btn-submit' onClick={handleWithdraw} loading={loading}>确认提现</Button>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user