Files
quanxiyun-app/src/pages/b-team/index.vue
T
2026-09-16 15:04:08 +08:00

346 lines
9.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page-container">
<BTopBar title="团队" :showBell="false" />
<!-- 采用 flex: 1 动态自适应屏幕剩余高度 -->
<scroll-view
scroll-y
class="page-content"
:refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
>
<view class="content-pad">
<!-- Team Stats Hero -->
<view class="team-hero">
<view class="team-hero-top">
<view class="th-left">
<text class="th-label">下属用户总数</text>
<text class="th-val">{{ teamStats.userCount }}<text class="th-unit"> </text></text>
</view>
<view class="th-right">
<text class="th-label">团队总贡献积分</text>
<text class="th-val2">{{ teamStats.totalContribution }}</text>
</view>
</view>
<view class="team-hero-stats">
<view class="ths-item">
<text class="ths-val">{{ teamStats.directPush }}</text>
<text class="ths-label">团长直接推荐C端</text>
</view>
<view class="ths-item">
<text class="ths-val">{{ teamStats.deviceCount }}</text>
<text class="ths-label">设备总数()</text>
</view>
<view class="ths-item">
<text class="ths-val">{{ teamStats.totalSpend }}</text>
<text class="ths-label">总消费金额</text>
</view>
</view>
</view>
<!-- Tab -->
<view class="tabs">
<view class="tab active">直接推荐C端 ({{ teamMembers.length }})</view>
</view>
<!-- Summary Card -->
<view class="summary-card">
<text class="summary-title">直接推荐C端用户·贡献收益汇总</text>
<view class="summary-badges">
<text class="badge badge-blue">直接推荐C端 {{ teamMembers.length }}</text>
<text class="badge badge-gray">设备总数 {{ teamStats.deviceCount }}</text>
</view>
</view>
<!-- User List -->
<view class="list-group">
<!-- 缺省状态处理 -->
<view v-if="teamMembers.length === 0" class="empty-holder">
<text class="empty-text">暂无下属成员记录</text>
</view>
<view class="list-item" v-for="(member, idx) in teamMembers" :key="idx">
<view class="member-avatar" :style="{ background: member.avatarBg }">
{{ member.avatar }}
</view>
<view class="li-body">
<text class="li-title">{{ member.name }}</text>
<text class="li-sub">由团长"{{ member.promoter }}"直接推荐 · {{ member.devices }}台设备</text>
</view>
<view class="li-right">
<text class="li-points">{{ formatThousands(member.points) }}</text>
<text class="li-sub-label">贡献积分</text>
</view>
</view>
</view>
</view>
<!-- 底部安全区占位兼容 CustomTabBar 高度 -->
<view class="safe-bottom-spacer"></view>
</scroll-view>
<custom-tab-bar :current="1"></custom-tab-bar>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import BTopBar from '@/components/BTopBar.vue'
import CustomTabBar from '@/components/CustomTabBar.vue'
import request from '@/utils/request'
import { formatThousands } from '@/utils/util'
const refreshing = ref(false)
const teamStats = ref({
userCount: 0,
totalContribution: '0',
directPush: 0,
deviceCount: 0,
totalSpend: '0'
})
const teamMembers = ref([])
const AVATAR_BGS = [
'linear-gradient(135deg,#1677FF,#4096FF)'
]
const fetchTeam = async () => {
try {
const res = await request.get('/c/leader/team', { showLoading: false })
if (res.data) {
const d = res.data
teamStats.value = {
userCount: d.stats?.user_count || 0,
totalContribution: formatThousands(d.stats?.total_contribution || 0),
directPush: d.stats?.direct_push || 0,
deviceCount: d.stats?.device_count || 0,
totalSpend: formatThousands(d.stats?.total_spend || 0)
}
teamMembers.value = (d.members || []).map((m, idx) => ({
name: m.name,
avatar: m.avatar || (m.name || '用')[0],
avatarBg: AVATAR_BGS[idx % AVATAR_BGS.length],
promoter: m.promoter || '--',
devices: m.device_count || 0,
points: m.contribution || 0
}))
}
} catch (e) { console.error('获取团队数据失败', e) }
}
onMounted(() => { fetchTeam() })
const onRefresh = () => {
refreshing.value = true
fetchTeam().finally(() => { refreshing.value = false })
}
onShow(() => {
uni.$emit('pageShow')
})
</script>
<style lang="scss" scoped>
/* 全局盒模型规则[cite: 7] */
view, text, scroll-view {
box-sizing: border-box;
}
.page-container {
height: 100vh;
display: flex;
flex-direction: column;
background: #F2F4F8;
overflow: hidden;
}
.page-content {
flex: 1;
height: 0; /* 触发 flex 压缩,保证跨端自适应不溢出 */
}
.content-pad { padding: 24rpx; }
/* Team Hero */
.team-hero {
background: linear-gradient(135deg, #0A1733 0%, #0D2B6E 50%, #1677FF 100%);
border-radius: 28rpx;
padding: 32rpx;
color: #fff;
position: relative;
overflow: hidden;
margin-bottom: 20rpx;
box-shadow: 0 12rpx 36rpx rgba(13,43,110,.2);
}
.team-hero-top {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
position: relative;
z-index: 1;
}
.th-label { font-size: 24rpx; opacity: 0.65; display: block; }
.th-val { font-size: 56rpx; font-weight: 800; font-family: 'DIN Alternate', 'Helvetica Neue', sans-serif; }
.th-unit { font-size: 26rpx; font-weight: 400; opacity: 0.7; }
.th-val2 { font-size: 40rpx; font-weight: 700; color: #7FFFD4; font-family: 'DIN Alternate', 'Helvetica Neue', sans-serif; display: block; margin-top: 4rpx; }
.team-hero-stats {
display: flex;
gap: 16rpx;
position: relative;
z-index: 1;
}
.ths-item {
flex: 1;
background: rgba(255,255,255,.08);
border-radius: 20rpx;
padding: 16rpx;
text-align: center;
backdrop-filter: blur(4px);
}
.ths-val { font-size: 36rpx; font-weight: 700; font-family: 'DIN Alternate', 'Helvetica Neue', sans-serif; display: block; }
.ths-label { font-size: 20rpx; opacity: 0.65; display: block; margin-top: 4rpx; }
/* Tabs */
.tabs {
display: flex;
gap: 0;
border-bottom: 1rpx solid #EAEDF3;
margin-bottom: 24rpx;
}
.tab {
padding: 22rpx 0;
font-size: 26rpx;
font-weight: 600;
color: #1677FF;
position: relative;
}
.tab::after {
content: '';
position: absolute;
bottom: -1rpx;
left: 50%;
transform: translateX(-50%);
width: 44rpx;
height: 5rpx;
background: linear-gradient(90deg, #4096FF, #1677FF);
border-radius: 4rpx;
box-shadow: 0 0 12rpx rgba(22,119,255,.4);
}
/* Summary Card */
.summary-card {
background: linear-gradient(135deg, #F9F0FF, #FAF5FF);
border-radius: 28rpx;
padding: 28rpx;
text-align: center;
margin-bottom: 20rpx;
}
.summary-title {
font-size: 26rpx;
color: #1677FF;
line-height: 1.6;
font-weight: 600;
display: block;
margin-bottom: 12rpx;
}
.summary-badges {
display: flex;
gap: 16rpx;
justify-content: center;
}
.badge {
display: inline-block;
padding: 6rpx 16rpx;
border-radius: 10rpx;
font-size: 22rpx;
font-weight: 500;
}
.badge-blue { background: #E6F4FF; color: #1677FF; }
.badge-gray { background: #F3F5F9; color: #5A6072; }
/* List Group */
.list-group {
background: #fff;
border-radius: 28rpx;
overflow: hidden;
box-shadow: 0 2rpx 6rpx rgba(15,18,38,.04);
}
.empty-holder {
padding: 60rpx 0;
text-align: center;
}
.empty-text {
font-size: 24rpx;
color: #BCC2CE;
}
.list-item {
display: flex;
align-items: center;
padding: 28rpx 32rpx;
gap: 24rpx;
transition: background 0.15s;
&:active { background: #F8F9FC; }
}
.list-item + .list-item { border-top: 1rpx solid #F3F5F9; }
.member-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: 700;
flex-shrink: 0;
}
.li-body { flex: 1; min-width: 0; }
.li-title {
font-size: 28rpx;
font-weight: 500;
color: #0F1226;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.li-sub { font-size: 22rpx; color: #8B91A1; margin-top: 4rpx; display: block; }
.li-right { text-align: right; flex-shrink: 0; }
.li-points { font-size: 26rpx; font-weight: 700; color: #1677FF; font-family: 'DIN Alternate', 'Helvetica Neue', sans-serif; display: block; }
.li-sub-label { font-size: 20rpx; color: #BCC2CE; display: block; margin-top: 2rpx; }
.safe-bottom-spacer {
height: calc(140rpx + env(safe-area-inset-bottom));
}
</style>