文件
Henglianshutong/uniapp/pages/commission/team.vue

1771 行
53 KiB
Vue

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<!-- 团队作战管理中心 -->
<template>
<s-layout title="我的团队" :class="state.scrollTop ? 'team-wrap' : ''" navbar="inner">
<!-- 头部看板区 -->
<view
class="header-box"
:style="[
{
marginTop: '-' + Number(statusBarHeight + 88) + 'rpx',
paddingTop: Number(statusBarHeight + 108) + 'rpx',
},
]"
>
<!-- 推荐人 -->
<view v-if="userInfo.parent_user" class="referrer-box ss-flex ss-col-center">
<text class="referrer-label">推荐人:</text>
<image
class="referrer-avatar ss-m-r-10"
:src="sheep.$url.cdn(userInfo.parent_user.avatar)"
mode="aspectFill"
></image>
<text class="referrer-name">{{ userInfo.parent_user.nickname }}</text>
</view>
<!-- 专属邀请码与拓展卡片 -->
<view class="invite-action-card ss-flex ss-row-between ss-col-center">
<view class="invite-code-wrap ss-flex ss-col-center">
<text class="invite-label">专属邀请码:</text>
<text class="invite-code">HL{{ userInfo.id || '---' }}</text>
<button class="ss-reset-button copy-code-btn ss-m-l-10" @tap="onCopyInviteCode">复制</button>
</view>
<view class="invite-btn-group ss-flex ss-col-center">
<button class="ss-reset-button invite-btn-poster ss-flex ss-col-center" @tap="onOpenShareModal">
<uni-icons type="image" size="14" color="#ffffff"></uni-icons>
<text class="ss-m-l-6">邀请海报</text>
</button>
<button class="ss-reset-button invite-btn-link ss-m-l-12 ss-flex ss-col-center" @tap="onCopyShareLink">
<uni-icons type="link" size="14" color="#ff6000"></uni-icons>
<text class="ss-m-l-6">复制链接</text>
</button>
</view>
</view>
<!-- 团队核心数据总览卡片(直推人数 与 团队总人数) -->
<view class="team-data-box ss-flex ss-col-center ss-row-between">
<!-- 直推人数卡片 -->
<view class="data-card ss-flex-1 ss-m-r-16">
<view class="item-title">
{{ currentDrillUser ? (currentDrillUser.nickname + '的直推(人)') : '直推人数(人)' }}
</view>
<view class="total-num text-orange">
{{ getDirectCount() }}
</view>
</view>
<!-- 团队总人数卡片 -->
<view class="data-card ss-flex-1">
<view class="item-title">
{{ currentDrillUser ? (currentDrillUser.nickname + '的团队(人)') : '团队总人数(人)' }}
</view>
<view class="total-num">
{{ getTeamCount() }}
</view>
</view>
</view>
<!-- 团队收益与业绩统计大盘 -->
<view class="team-wealth-box ss-flex ss-col-center ss-row-between">
<view class="wealth-card ss-flex-1 ss-m-r-16">
<view class="wealth-title">团队贡献总收益(元)</view>
<view class="wealth-num text-orange">¥{{ state.summary.team_total_reward || '0.00' }}</view>
<view class="wealth-sub">本月贡献: ¥{{ state.summary.team_month_reward || '0.00' }}</view>
</view>
<view class="wealth-card ss-flex-1">
<view class="wealth-title">团队自购总业绩(元)</view>
<view class="wealth-num text-gold">¥{{ state.summary.team_total_consume || '0.00' }}</view>
<view class="wealth-sub">团队累计订单: {{ state.summary.team_order_count || 0 }} 笔</view>
</view>
</view>
<!-- 30天自购保活健康度胶囊条 -->
<view class="health-bar ss-flex ss-row-between ss-col-center">
<view class="health-title">保活健康度:</view>
<view class="health-pills ss-flex ss-col-center">
<view
class="health-pill pill-active"
:class="{ 'pill-selected': state.currentFilter === 'active' }"
@tap="onToggleStatusFilter('active')"
>
<text class="status-dot dot-green"></text>
<text class="pill-label">正常活跃 {{ state.summary.active_count || 0 }}</text>
</view>
<view
class="health-pill pill-expiring"
:class="{ 'pill-selected': state.currentFilter === 'expiring' }"
@tap="onToggleStatusFilter('expiring')"
>
<text class="status-dot dot-yellow"></text>
<text class="pill-label">临期预警 {{ state.summary.expiring_count || 0 }}</text>
</view>
<view
class="health-pill pill-inactive"
:class="{ 'pill-selected': state.currentFilter === 'inactive' }"
@tap="onToggleStatusFilter('inactive')"
>
<text class="status-dot dot-gray"></text>
<text class="pill-label">已暂停 {{ state.summary.inactive_count || 0 }}</text>
</view>
</view>
</view>
</view>
<!-- 吸顶控制台(搜索 + 层级Tab + 快捷筛选) -->
<su-sticky bgColor="#ffffff">
<view class="control-panel">
<!-- 搜索输入栏 -->
<view class="search-bar-box ss-flex ss-col-center">
<view class="search-input-wrap ss-flex ss-col-center ss-flex-1">
<uni-icons type="search" size="18" color="#999999"></uni-icons>
<input
class="search-input"
type="text"
v-model="state.keyword"
confirm-type="search"
placeholder="搜索成员昵称 / 手机号 / ID"
placeholder-class="search-placeholder"
@confirm="onSearchConfirm"
/>
<view v-if="state.keyword" class="clear-btn" @tap="onClearKeyword">
<uni-icons type="clear" size="16" color="#bbbbbb"></uni-icons>
</view>
</view>
<button class="ss-reset-button search-btn" @tap="onSearchConfirm">搜索</button>
</view>
<!-- 层级下钻路径与导航栏 -->
<view v-if="state.drillStack.length === 0" class="drill-nav-bar root-nav ss-flex ss-row-between ss-col-center">
<view class="nav-left ss-flex ss-col-center">
<view class="nav-icon-box ss-flex ss-row-center ss-col-center">
<uni-icons type="personadd-filled" size="14" color="#ff6000"></uni-icons>
</view>
<text class="nav-root-title ss-m-l-10">我的直推团队</text>
<text class="nav-root-count ss-m-l-8">({{ state.pagination.total || 0 }}人)</text>
</view>
<view class="nav-hint ss-flex ss-col-center">
<text class="hint-text">点击成员可下钻其直推</text>
<uni-icons type="forward" size="12" color="#999999"></uni-icons>
</view>
</view>
<view v-else class="drill-nav-bar in-drill-nav ss-flex ss-row-between ss-col-center">
<view class="drill-back-btn ss-flex ss-col-center" @tap="onDrillBack">
<uni-icons type="left" size="16" color="#ff6000"></uni-icons>
<text class="back-text">返回上一层</text>
</view>
<view class="drill-breadcrumb ss-flex ss-col-center ss-flex-1 ss-m-x-12">
<scroll-view scroll-x :show-scrollbar="false" class="breadcrumb-scroll">
<view class="breadcrumb-inner ss-flex ss-col-center">
<text class="crumb-link" @tap="onDrillRoot">我的直推</text>
<template v-for="(node, index) in state.drillStack" :key="node.id">
<text class="crumb-sep">/</text>
<text
class="crumb-node"
:class="{ 'crumb-current': index === state.drillStack.length - 1 }"
@tap="onJumpDrillIndex(index)"
>
{{ node.nickname }}
</text>
</template>
</view>
</scroll-view>
</view>
<button class="ss-reset-button drill-reset-btn" @tap="onDrillRoot">回到我的</button>
</view>
<!-- 快捷多维胶囊筛选条 -->
<scroll-view class="filter-scroll-wrap" scroll-x :show-scrollbar="false">
<view class="filter-scroll-content ss-flex ss-col-center">
<view
v-for="pill in filterPills"
:key="pill.value"
class="filter-chip"
:class="{ 'chip-active': state.currentFilter === pill.value && state.currentSort === 'createtime' }"
@tap="onFilterChipChange(pill.value)"
>
{{ pill.label }}
</view>
<view class="chip-divider"></view>
<view
v-for="sortItem in sortOptions"
:key="sortItem.value"
class="filter-chip sort-chip"
:class="{ 'chip-active': state.currentSort === sortItem.value }"
@tap="onSortOptionChange(sortItem.value)"
>
{{ sortItem.label }}
</view>
</view>
</scroll-view>
</view>
</su-sticky>
<!-- 成员卡片列表 -->
<view class="member-list-box ss-p-t-20">
<view
v-for="item in state.pagination.data"
:key="item.id"
class="member-card"
@tap="onOpenMemberDetail(item)"
>
<!-- 成员卡片头部 -->
<view class="member-card-header ss-flex ss-col-center ss-row-between">
<view class="member-base ss-flex ss-col-center">
<image
class="member-avatar"
:src="sheep.$url.cdn(item.avatar)"
mode="aspectFill"
></image>
<view class="member-profile ss-m-l-16">
<view class="member-name-row ss-flex ss-col-center">
<text class="member-name ss-line-1">{{ item.nickname || '微信用户' }}</text>
<text class="level-badge badge-direct">直推成员</text>
</view>
<view class="member-sub-info ss-flex ss-col-center ss-m-t-4" v-if="item.mobile_hide || item.id">
<text class="member-phone" v-if="item.mobile_hide">{{ item.mobile_hide }}</text>
<text class="member-id-tag ss-m-l-8">ID: {{ item.id }}</text>
</view>
<view class="member-meta-row ss-flex ss-col-center ss-m-t-8">
<!-- 身份徽章 -->
<view v-if="memberLevelName(item)" class="tag-box ss-flex ss-col-center">
<image
v-if="item.agent?.level_info?.image"
class="tag-img"
:src="sheep.$url.cdn(item.agent.level_info.image)"
mode="aspectFill"
></image>
<text class="tag-title">{{ memberLevelName(item) }}</text>
</view>
<view
v-if="item.agent?.is_venue || item.agent?.identities?.includes('venue')"
class="mini-tag tag-venue"
>
{{ roleNames.venue || '酒道馆' }}
</view>
<view
v-if="item.agent?.is_county || item.agent?.identities?.includes('county')"
class="mini-tag tag-county"
>
{{ roleNames.county || '区县代理' }}
</view>
<view
v-if="item.agent?.is_partner || item.agent?.identities?.includes('partner')"
class="mini-tag tag-partner"
>
{{ roleNames.partner || '合伙股东' }}
</view>
</view>
</view>
</view>
<!-- 活跃状态指示灯 -->
<view class="status-indicator">
<view v-if="item.active_status === 'active'" class="status-pill pill-green">
<text class="status-dot dot-green"></text>
<text>活跃中</text>
</view>
<view v-else-if="item.active_status === 'expiring'" class="status-pill pill-amber">
<text class="status-dot dot-yellow"></text>
<text>剩 {{ item.days_remaining }} 天</text>
</view>
<view v-else class="status-pill pill-gray">
<text class="status-dot dot-gray"></text>
<text>奖励已暂停</text>
</view>
</view>
</view>
<!-- 经营指标看板 -->
<view class="metrics-grid ss-flex ss-col-center ss-row-between ss-m-t-20">
<view
class="metric-col ss-flex-col ss-col-center"
:class="{ 'metric-clickable': Number(item.child_user_count_1) > 0 }"
@tap.stop="Number(item.child_user_count_1) > 0 && onDrillDown(item)"
>
<view class="metric-val-wrap ss-flex ss-col-center">
<text class="metric-val" :class="{ 'text-orange': Number(item.child_user_count_1) > 0 }">
{{ item.child_user_count_1 || 0 }}
</text>
<text v-if="Number(item.child_user_count_1) > 0" class="drill-tag ss-m-l-6">下钻</text>
</view>
<view class="metric-lbl">直推好友(人)</view>
</view>
<view class="metric-divider"></view>
<view class="metric-col ss-flex-col ss-col-center">
<view class="metric-val">{{ item.order_count || 0 }}</view>
<view class="metric-lbl">累计订单(单)</view>
</view>
<view class="metric-divider"></view>
<view class="metric-col ss-flex-col ss-col-center">
<view class="metric-val">¥{{ item.consume_amount || '0.00' }}</view>
<view class="metric-lbl">自购业绩(元)</view>
</view>
<view class="metric-divider"></view>
<view class="metric-col ss-flex-col ss-col-center">
<view class="metric-val">
{{ item.agent?.direct_order_seq !== undefined ? '第' + item.agent.direct_order_seq + '单' : '--' }}
</view>
<view class="metric-lbl">提成循环序号</view>
</view>
</view>
<!-- 底部快捷触达与操作栏 -->
<view class="member-card-footer ss-flex ss-col-center ss-row-between ss-m-t-20">
<view class="register-time">
注册时间:{{ formatTime(item.createtime) }}
</view>
<view class="actions-wrap ss-flex ss-col-center">
<button
v-if="Number(item.child_user_count_1) > 0"
class="ss-reset-button action-btn btn-drill"
@tap.stop="onDrillDown(item)"
>
<uni-icons type="staff-filled" size="13" color="#ff6000"></uni-icons>
<text class="ss-m-l-4">下钻团队</text>
</button>
<button
v-if="item.mobile_raw"
class="ss-reset-button action-btn btn-call ss-m-l-10"
@tap.stop="onMakePhoneCall(item.mobile_raw)"
>
<uni-icons type="phone-filled" size="13" color="#ff6000"></uni-icons>
<text class="ss-m-l-4">拨打</text>
</button>
<button
v-if="item.mobile_raw || item.mobile_hide"
class="ss-reset-button action-btn btn-copy ss-m-l-10"
@tap.stop="onCopyText(item.mobile_raw || item.mobile_hide, '手机号已复制')"
>
<uni-icons type="paperclip" size="13" color="#666666"></uni-icons>
<text class="ss-m-l-4">复制</text>
</button>
<button
class="ss-reset-button action-btn btn-reward ss-m-l-10"
@tap.stop="onJumpRewards(item.id, item.nickname)"
>
<uni-icons type="wallet" size="13" color="#ff6000"></uni-icons>
<text class="ss-m-l-4">收益</text>
</button>
<button
class="ss-reset-button action-btn btn-detail ss-m-l-10"
@tap.stop="onOpenMemberDetail(item)"
>
<text>详情</text>
<uni-icons type="right" size="12" color="#999999"></uni-icons>
</button>
</view>
</view>
</view>
</view>
<!-- 空状态处理与行动引导 -->
<view v-if="state.pagination.total === 0 && state.loadStatus !== 'loading'" class="empty-wrap">
<!-- 搜索或筛选结果为空 -->
<view v-if="state.keyword || state.currentFilter !== 'all' || state.drillStack.length > 0" class="filter-empty ss-flex-col ss-col-center">
<s-empty icon="/static/data-empty.png" text="未找到符合条件的成员"></s-empty>
<button class="ss-reset-button reset-filter-btn ss-m-t-20" @tap="resetFilters">
重置所有筛选
</button>
</view>
<!-- 真实无团队成员空状态:引导裂变海报 -->
<view v-else class="team-empty ss-flex-col ss-col-center ss-p-b-60">
<s-empty icon="/static/data-empty.png" text="您还没有团队成员,快去邀请好友加入吧!"></s-empty>
<button class="ss-reset-button invite-cta-btn ss-m-t-30" @tap="onOpenShareModal">
<text class="cicon-forward ss-m-r-10"></text>
立即生成邀请海报
</button>
</view>
</view>
<!-- 加载更多指示器 -->
<uni-load-more
v-if="state.pagination.total > 0"
:status="state.loadStatus"
:content-text="{
contentdown: '上拉加载更多',
contentrefresh: '正在加载...',
nomore: '没有更多成员了',
}"
/>
<!-- 成员画像与经营管理抽屉 -->
<su-popup
:show="state.showDetail"
type="bottom"
round="24"
@close="state.showDetail = false"
showClose
>
<view v-if="state.currentMember" class="drawer-content ss-p-x-32 ss-p-b-40 ss-p-t-30">
<view class="drawer-header ss-flex ss-col-center ss-m-b-24">
<image
class="drawer-avatar ss-m-r-20"
:src="sheep.$url.cdn(state.currentMember.avatar)"
mode="aspectFill"
></image>
<view class="drawer-user-info">
<view class="drawer-nickname ss-flex ss-col-center">
<text class="ss-font-32 ss-bold ss-m-r-12">{{ state.currentMember.nickname }}</text>
<text class="level-badge badge-direct">直推成员</text>
</view>
<view class="drawer-sub ss-flex ss-col-center ss-m-t-8">
<text class="user-id">ID: {{ state.currentMember.id }}</text>
<text class="dot-split">·</text>
<text class="user-phone">手机: {{ state.currentMember.mobile_hide || '未绑定' }}</text>
<text class="dot-split">·</text>
<text class="join-time">{{ formatTime(state.currentMember.createtime) }} 加入</text>
</view>
</view>
</view>
<!-- 身份体系卡 -->
<view class="drawer-section ss-m-b-24">
<view class="section-title">业务身份</view>
<view class="section-box identity-box ss-flex ss-col-center ss-flex-wrap">
<view class="id-pill tag-box ss-flex ss-col-center ss-m-r-12 ss-m-b-10">
<image
v-if="state.currentMember.agent?.level_info?.image"
class="tag-img"
:src="sheep.$url.cdn(state.currentMember.agent.level_info.image)"
mode="aspectFill"
></image>
<text class="tag-title">{{ memberLevelName(state.currentMember) || '普通会员' }}</text>
</view>
<view
v-if="state.currentMember.agent?.is_venue || state.currentMember.agent?.identities?.includes('venue')"
class="id-pill mini-tag tag-venue ss-m-r-12 ss-m-b-10"
>
{{ roleNames.venue || '酒道馆' }}
</view>
<view
v-if="state.currentMember.agent?.is_county || state.currentMember.agent?.identities?.includes('county')"
class="id-pill mini-tag tag-county ss-m-r-12 ss-m-b-10"
>
{{ roleNames.county || '区县代理' }}
</view>
<view
v-if="state.currentMember.agent?.is_partner || state.currentMember.agent?.identities?.includes('partner')"
class="id-pill mini-tag tag-partner ss-m-r-12 ss-m-b-10"
>
{{ roleNames.partner || '合伙股东' }}
</view>
</view>
</view>
<!-- 30天收益保活详情 -->
<view class="drawer-section ss-m-b-24">
<view class="section-title ss-flex ss-row-between ss-col-center">
<text>30天收益保活状态</text>
<text
class="status-text-bold"
:class="{
'text-green': state.currentMember.active_status === 'active',
'text-yellow': state.currentMember.active_status === 'expiring',
'text-gray': state.currentMember.active_status === 'inactive',
}"
>
{{
state.currentMember.active_status === 'active'
? '正常领取中'
: state.currentMember.active_status === 'expiring'
? '临期预警(剩' + state.currentMember.days_remaining + '天)'
: '业务奖励已暂停'
}}
</text>
</view>
<view class="section-box active-detail-box">
<view class="detail-line ss-flex ss-row-between">
<text class="line-label">最近有效订单时间:</text>
<text class="line-val">
{{ formatLastOrderTime(state.currentMember.agent?.last_self_order_time) }}
</text>
</view>
<view class="detail-line ss-flex ss-row-between ss-m-t-12">
<text class="line-label">推荐提成序号:</text>
<text class="line-val">
{{ state.currentMember.agent?.direct_order_seq !== undefined ? '当前第 ' + state.currentMember.agent.direct_order_seq + ' 单' : '0 单' }}
</text>
</view>
<view class="notice-tip ss-m-t-16">
💡 规则提示:近 30 日内需有本人订单方可正常享受收益。如已暂停,次日完成自购后即可自动恢复。
</view>
</view>
</view>
<!-- 经营产出看板 -->
<view class="drawer-section ss-m-b-24">
<view class="section-title">团队与业绩产出</view>
<view class="section-box performance-box ss-flex ss-row-between">
<view class="perf-col ss-flex-col ss-col-center">
<view class="perf-num">{{ state.currentMember.child_user_count_1 || 0 }}</view>
<view class="perf-lbl">直推下级(人)</view>
</view>
<view class="perf-col ss-flex-col ss-col-center">
<view class="perf-num">{{ state.currentMember.order_count || 0 }}</view>
<view class="perf-lbl">累计成单(笔)</view>
</view>
<view class="perf-col ss-flex-col ss-col-center">
<view class="perf-num">¥{{ state.currentMember.consume_amount || '0.00' }}</view>
<view class="perf-lbl">自购贡献(元)</view>
</view>
</view>
</view>
<!-- 下钻查看直推团队卡片 -->
<view
v-if="Number(state.currentMember.child_user_count_1) > 0"
class="drawer-drill-card ss-m-b-24 ss-flex ss-row-between ss-col-center"
@tap="onDrillFromDrawer(state.currentMember)"
>
<view class="drill-card-left ss-flex ss-col-center">
<view class="drill-icon-box ss-flex ss-row-center ss-col-center">
<uni-icons type="staff-filled" size="20" color="#ff6000"></uni-icons>
</view>
<view class="drill-info ss-m-l-16">
<view class="drill-title">下钻查看 Ta 的直推团队</view>
<view class="drill-desc">Ta 名下共有 {{ state.currentMember.child_user_count_1 }} 位直推成员</view>
</view>
</view>
<view class="drill-card-right ss-flex ss-col-center">
<text class="drill-btn-text">立即进入</text>
<uni-icons type="right" size="14" color="#ff6000"></uni-icons>
</view>
</view>
<!-- 触达与管理动作条 -->
<view class="drawer-actions ss-flex ss-col-center ss-row-between">
<button
v-if="state.currentMember.mobile_raw"
class="ss-reset-button drawer-btn drawer-btn-primary ss-flex-1 ss-m-r-16"
@tap="onMakePhoneCall(state.currentMember.mobile_raw)"
>
<uni-icons type="phone-filled" size="16" color="#ffffff"></uni-icons>
<text class="ss-m-l-8">呼叫成员</text>
</button>
<button
v-if="state.currentMember.mobile_raw || state.currentMember.mobile_hide"
class="ss-reset-button drawer-btn drawer-btn-sub ss-flex-1 ss-m-r-16"
@tap="onCopyText(state.currentMember.mobile_raw || state.currentMember.mobile_hide, '手机号已复制')"
>
<uni-icons type="paperclip" size="16" color="#ff6000"></uni-icons>
<text class="ss-m-l-8">复制电话</text>
</button>
<button
class="ss-reset-button drawer-btn drawer-btn-outline ss-flex-1"
@tap="onJumpRewards(state.currentMember.id, state.currentMember.nickname)"
>
<uni-icons type="list" size="16" color="#666666"></uni-icons>
<text class="ss-m-l-8">查收益流水</text>
</button>
</view>
</view>
</su-popup>
</s-layout>
</template>
<script setup>
import sheep from '@/sheep';
import { onLoad, onShow, onReachBottom, onPullDownRefresh, onPageScroll } from '@dcloudio/uni-app';
import { computed, reactive } from 'vue';
import { showShareModal } from '@/sheep/hooks/useModal';
import _ from 'lodash';
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
const agentInfo = computed(() => sheep.$store('user').agentInfo);
const userInfo = computed(() => sheep.$store('user').userInfo);
const headerBg = sheep.$url.css('/assets/addons/shopro/uniapp/user/withdraw_bg.png');
const roleNames = computed(() => {
return (
userInfo.value?.custom_role_names ||
userInfo.value?.role_names ||
agentInfo.value?.custom_role_names ||
agentInfo.value?.role_names ||
{}
);
});
// 控制吸顶及页面滚动
onPageScroll((e) => {
state.scrollTop = e.scrollTop > 100;
});
const filterPills = computed(() => [
{ label: '全部', value: 'all' },
{ label: roleNames.value.partner || '合伙股东', value: 'partner' },
{ label: '正常活跃', value: 'active' },
{ label: '临期预警', value: 'expiring' },
{ label: '已暂停', value: 'inactive' },
]);
const sortOptions = [
{ label: '最新加入', value: 'createtime' },
{ label: '裂变最多', value: 'child_count' },
{ label: '消费最高', value: 'consume' },
];
const state = reactive({
scrollTop: false,
keyword: '',
drillStack: [], // 下钻栈:[{ id, nickname, avatar, child_user_count_1 }]
currentFilter: 'all', // all, partner, active, expiring, inactive
currentSort: 'createtime', // createtime, child_count, consume
showDetail: false,
currentMember: null,
summary: {
total_count: 0,
active_count: 0,
expiring_count: 0,
inactive_count: 0,
partner_count: 0,
team_total_consume: '0.00',
team_total_reward: '0.00',
team_month_reward: '0.00',
team_order_count: 0,
},
pagination: {
data: [],
current_page: 1,
total: 0,
last_page: 1,
},
loadStatus: '',
});
// 当前正在下钻的目标成员
const currentDrillUser = computed(() => {
if (!state.drillStack || state.drillStack.length === 0) return null;
return state.drillStack[state.drillStack.length - 1];
});
// 获取直推人数(支持当前团队与多级下钻)
function getDirectCount() {
if (currentDrillUser.value) {
return (
state.summary.direct_count ??
state.summary.total_count ??
currentDrillUser.value.child_user_count_1 ??
0
);
}
const count = Number(agentInfo.value?.child_user_count_1 || 0);
if (count > 0) return count;
if (state.summary.direct_count !== undefined && state.summary.direct_count > 0) {
return state.summary.direct_count;
}
return Number(state.pagination.total || 0);
}
// 获取团队总人数(支持当前团队与多级下钻)
function getTeamCount() {
if (currentDrillUser.value) {
return (
state.summary.team_count ??
currentDrillUser.value.child_user_count_all ??
state.summary.direct_count ??
state.summary.total_count ??
0
);
}
const count = Number(agentInfo.value?.child_user_count_all || 0);
if (count > 0) return count;
if (state.summary.team_count !== undefined && state.summary.team_count > 0) {
return state.summary.team_count;
}
return getDirectCount();
}
function memberLevelName(item) {
const rNames =
item?.agent?.custom_role_names ||
item?.agent?.role_names ||
roleNames.value ||
{};
if (item?.agent?.maker_level_name) {
return item.agent.maker_level_name;
}
if (item?.agent?.identity_text) {
return item.agent.identity_text;
}
const rawName = item?.agent?.level_info?.name;
if (rawName) {
if (rNames.maker && rawName.includes('创客')) {
return rawName.replace('创客', rNames.maker);
}
if (rNames.taster && rawName.includes('品酒师')) {
return rawName.replace('品酒师', rNames.taster);
}
return rawName;
}
return item?.agent?.identity_text || '';
}
function formatTime(timestamp) {
if (!timestamp) return '--';
const date = new Date(Number(timestamp) * 1000);
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
}
function formatLastOrderTime(timestamp) {
if (!timestamp || Number(timestamp) === 0) {
return '近 30 日内暂无本人自购记录';
}
const date = new Date(Number(timestamp) * 1000);
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
const h = String(date.getHours()).padStart(2, '0');
const min = String(date.getMinutes()).padStart(2, '0');
return `${y}-${m}-${d} ${h}:${min}`;
}
// 头部卡片联动
function onSelectHeaderTab(filter) {
state.currentFilter = filter;
state.currentSort = 'createtime';
getTeamList(1);
}
// 状态胶囊切换
function onToggleStatusFilter(status) {
if (state.currentFilter === status) {
state.currentFilter = 'all';
} else {
state.currentFilter = status;
}
getTeamList(1);
}
// 筛选胶囊切换
function onFilterChipChange(filter) {
if (state.currentFilter === filter && state.currentSort === 'createtime') return;
state.currentFilter = filter;
state.currentSort = 'createtime';
getTeamList(1);
}
// 排序切换
function onSortOptionChange(sort) {
if (state.currentSort === sort) return;
state.currentSort = sort;
getTeamList(1);
}
// 搜索处理
function onSearchConfirm() {
getTeamList(1);
}
function onClearKeyword() {
state.keyword = '';
getTeamList(1);
}
function resetFilters() {
state.keyword = '';
state.currentFilter = 'all';
state.currentSort = 'createtime';
state.drillStack = [];
getTeamList(1);
}
// 成员下钻逻辑
function onDrillDown(member) {
if (!member || !member.id) return;
if (state.drillStack.some((item) => item.id === member.id)) {
sheep.$helper.toast('当前已在此成员层级中');
return;
}
state.drillStack.push({
id: member.id,
nickname: member.nickname || '直推成员',
avatar: member.avatar,
child_user_count_1: member.child_user_count_1 || 0,
});
state.keyword = '';
getTeamList(1);
}
function onDrillBack() {
if (state.drillStack.length === 0) return;
state.drillStack.pop();
state.keyword = '';
getTeamList(1);
}
function onDrillRoot() {
if (state.drillStack.length === 0) return;
state.drillStack = [];
state.keyword = '';
getTeamList(1);
}
function onJumpDrillIndex(index) {
if (index >= state.drillStack.length - 1) return;
state.drillStack = state.drillStack.slice(0, index + 1);
state.keyword = '';
getTeamList(1);
}
function onDrillFromDrawer(member) {
state.showDetail = false;
onDrillDown(member);
}
// 触达动作
function onMakePhoneCall(phone) {
if (!phone) {
sheep.$helper.toast('未提供联系电话');
return;
}
uni.makePhoneCall({
phoneNumber: phone,
fail: () => {
sheep.$helper.toast('取消拨打');
},
});
}
function onCopyText(text, tip = '已复制') {
if (!text) return;
sheep.$helper.copyText(text);
}
function onOpenMemberDetail(item) {
state.currentMember = item;
state.showDetail = true;
}
function onJumpRewards(buyerId, buyerName = '') {
state.showDetail = false;
const nameParam = buyerName ? `&buyer_name=${encodeURIComponent(buyerName)}` : '';
sheep.$router.go(`/pages/commission/rewards?buyer_id=${buyerId}${nameParam}`);
}
function onCopyInviteCode() {
if (!userInfo.value?.id) {
sheep.$helper.toast('请先登录');
return;
}
const code = `HL${userInfo.value.id}`;
sheep.$helper.copyText(code, '专属邀请码已复制');
}
function onCopyShareLink() {
if (!userInfo.value?.id) {
sheep.$helper.toast('请先登录');
return;
}
const shareQuery = sheep.$platform.share.buildSpmQuery({
page: '1',
query: '0',
});
const shareUrl = sheep.$platform.share.buildSpmPath(shareQuery);
sheep.$helper.copyText(shareUrl, '专属推广链接已复制');
}
function onOpenShareModal() {
showShareModal();
}
// 列表数据请求
async function getTeamList(page = 1, list_rows = 10) {
state.loadStatus = 'loading';
const params = {
page,
list_rows,
drill_user_id: currentDrillUser.value ? currentDrillUser.value.id : 0,
filter_type: state.currentFilter,
sort: state.currentSort,
keyword: state.keyword,
};
let res = await sheep.$api.commission.team(params);
if (res.code === 1) {
const rows = res.data?.data || [];
state.pagination = {
...res.data,
data: page === 1 ? rows : _.concat(state.pagination.data, rows),
};
if (res.data?.summary) {
state.summary = res.data.summary;
}
if (state.pagination.current_page < state.pagination.last_page) {
state.loadStatus = 'more';
} else {
state.loadStatus = 'noMore';
}
} else {
sheep.$helper.toast(res.msg || '获取团队列表失败');
state.loadStatus = 'noMore';
}
}
async function refreshHeader() {
await Promise.all([sheep.$store('user').getAgentInfo(), sheep.$store('user').getInfo()]);
}
async function refreshPage() {
await Promise.all([refreshHeader(), getTeamList(1)]);
}
onLoad(() => {
refreshPage();
});
onShow(() => {
refreshHeader();
});
onPullDownRefresh(async () => {
await refreshPage();
uni.stopPullDownRefresh();
});
onReachBottom(() => {
if (state.loadStatus !== 'noMore') {
getTeamList(state.pagination.current_page + 1);
}
});
</script>
<style lang="scss" scoped>
.header-box {
box-sizing: border-box;
padding: 0 20rpx 24rpx 20rpx;
width: 750rpx;
z-index: 3;
position: relative;
background: v-bind(headerBg) no-repeat,
linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
background-size: 750rpx 100%;
// 推荐人
.referrer-box {
font-size: 26rpx;
font-weight: 500;
color: #ffffff;
padding: 10rpx 8rpx 20rpx 8rpx;
.referrer-avatar {
width: 38rpx;
height: 38rpx;
border-radius: 50%;
border: 2rpx solid rgba(255, 255, 255, 0.6);
}
.referrer-name {
font-weight: bold;
}
}
// 专属邀请码卡片
.invite-action-card {
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border-radius: 16rpx;
padding: 12rpx 20rpx;
margin-bottom: 20rpx;
.invite-code-wrap {
.invite-label {
font-size: 24rpx;
color: #ffffff;
font-weight: 500;
}
.invite-code {
font-size: 28rpx;
font-weight: bold;
color: #ffffff;
font-family: OPPOSANS;
letter-spacing: 1rpx;
}
.copy-code-btn {
font-size: 20rpx;
padding: 4rpx 14rpx;
border-radius: 16rpx;
background: #ffffff;
color: #ff6000;
font-weight: 500;
}
}
.invite-btn-group {
.invite-btn-poster {
height: 48rpx;
padding: 0 16rpx;
border-radius: 24rpx;
background: #ff6000;
color: #ffffff;
font-size: 22rpx;
font-weight: 500;
}
.invite-btn-link {
height: 48rpx;
padding: 0 16rpx;
border-radius: 24rpx;
background: #ffffff;
color: #ff6000;
font-size: 22rpx;
font-weight: 500;
}
}
}
// 团队信息总览
.team-data-box {
.data-card {
background: #ffffff;
border-radius: 20rpx;
padding: 22rpx 24rpx;
box-sizing: border-box;
box-shadow: 0 4rpx 14rpx rgba(0, 0, 0, 0.03);
.item-title {
font-size: 24rpx;
font-weight: 500;
color: #999999;
line-height: 32rpx;
margin-bottom: 12rpx;
}
.total-num {
font-size: 44rpx;
font-weight: bold;
color: #333333;
font-family: OPPOSANS;
line-height: 48rpx;
&.text-orange {
color: #ff6000;
}
}
}
}
// 团队收益与业绩看板
.team-wealth-box {
margin-top: 20rpx;
.wealth-card {
background: #ffffff;
border-radius: 20rpx;
padding: 18rpx 20rpx;
box-sizing: border-box;
.wealth-title {
font-size: 22rpx;
color: #999999;
line-height: 28rpx;
margin-bottom: 6rpx;
}
.wealth-num {
font-size: 32rpx;
font-weight: bold;
font-family: OPPOSANS;
line-height: 38rpx;
margin-bottom: 6rpx;
&.text-orange {
color: #ff6000;
}
&.text-gold {
color: #c48a3a;
}
}
.wealth-sub {
font-size: 20rpx;
color: #888888;
line-height: 24rpx;
}
}
}
// 健康度胶囊条
.health-bar {
margin-top: 20rpx;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border-radius: 36rpx;
padding: 10rpx 18rpx;
.health-title {
font-size: 22rpx;
color: #ffffff;
font-weight: 500;
}
.health-pills {
gap: 10rpx;
.health-pill {
display: flex;
align-items: center;
padding: 6rpx 14rpx;
border-radius: 20rpx;
background: rgba(255, 255, 255, 0.85);
font-size: 20rpx;
line-height: 22rpx;
color: #444444;
transition: all 0.2s;
&.pill-selected {
background: #ffffff;
font-weight: bold;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
transform: scale(1.04);
}
.pill-label {
margin-left: 6rpx;
}
}
}
}
}
// 状态圆点
.status-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
display: inline-block;
&.dot-green {
background: #10b981;
box-shadow: 0 0 6rpx #10b981;
}
&.dot-yellow {
background: #f59e0b;
box-shadow: 0 0 6rpx #f59e0b;
}
&.dot-gray {
background: #9ca3af;
}
}
// 控制台
.control-panel {
background: #ffffff;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
// 搜索框
.search-bar-box {
padding: 18rpx 24rpx 12rpx 24rpx;
.search-input-wrap {
background: #f5f5f7;
border-radius: 32rpx;
height: 64rpx;
padding: 0 20rpx;
.search-input {
flex: 1;
margin-left: 12rpx;
font-size: 24rpx;
color: #333333;
}
.search-placeholder {
color: #a0a0a0;
font-size: 24rpx;
}
.clear-btn {
padding: 8rpx;
}
}
.search-btn {
margin-left: 16rpx;
font-size: 26rpx;
color: #ff6000;
font-weight: 500;
padding: 8rpx 12rpx;
}
}
// 下钻导航与面包屑
.drill-nav-bar {
border-bottom: 1rpx solid #f2f2f2;
padding: 16rpx 24rpx;
&.root-nav {
background: #fafbfc;
.nav-left {
.nav-icon-box {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
background: #fff0e6;
}
.nav-root-title {
font-size: 26rpx;
font-weight: bold;
color: #222222;
}
.nav-root-count {
font-size: 24rpx;
color: #ff6000;
font-weight: 500;
font-family: OPPOSANS;
}
}
.nav-hint {
.hint-text {
font-size: 22rpx;
color: #999999;
margin-right: 4rpx;
}
}
}
&.in-drill-nav {
background: #fff8f2;
.drill-back-btn {
padding: 8rpx 14rpx;
border-radius: 24rpx;
background: #ffffff;
border: 1rpx solid #ffd8bf;
box-shadow: 0 2rpx 6rpx rgba(255, 96, 0, 0.08);
.back-text {
font-size: 24rpx;
font-weight: bold;
color: #ff6000;
margin-left: 4rpx;
}
}
.drill-breadcrumb {
min-width: 0;
overflow: hidden;
.breadcrumb-scroll {
white-space: nowrap;
width: 100%;
}
.breadcrumb-inner {
display: inline-flex;
align-items: center;
.crumb-link {
font-size: 24rpx;
color: #666666;
}
.crumb-sep {
font-size: 22rpx;
color: #bbbbbb;
margin: 0 8rpx;
}
.crumb-node {
font-size: 24rpx;
color: #666666;
&.crumb-current {
color: #ff6000;
font-weight: bold;
background: #fff0e6;
padding: 2rpx 12rpx;
border-radius: 12rpx;
}
}
}
}
.drill-reset-btn {
font-size: 22rpx;
color: #ff6000;
padding: 6rpx 14rpx;
border-radius: 20rpx;
background: rgba(255, 96, 0, 0.1);
font-weight: 500;
white-space: nowrap;
}
}
}
// 筛选滚动条
.filter-scroll-wrap {
white-space: nowrap;
padding: 14rpx 20rpx;
box-sizing: border-box;
.filter-scroll-content {
display: inline-flex;
align-items: center;
gap: 12rpx;
}
.filter-chip {
display: inline-flex;
align-items: center;
padding: 8rpx 20rpx;
background: #f5f5f7;
border-radius: 24rpx;
font-size: 22rpx;
color: #666666;
transition: all 0.2s;
&.chip-active {
background: #fff0e6;
color: #ff6000;
font-weight: bold;
}
&.sort-chip {
background: #f0f4f8;
color: #4a5568;
&.chip-active {
background: #e2e8f0;
color: #1a202c;
font-weight: bold;
}
}
}
.chip-divider {
width: 1rpx;
height: 28rpx;
background: #e2e8f0;
margin: 0 6rpx;
}
}
}
// 成员卡片列表
.member-list-box {
padding: 20rpx;
.member-card {
background: #ffffff;
border-radius: 18rpx;
padding: 24rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 14rpx rgba(0, 0, 0, 0.03);
position: relative;
.member-card-header {
.member-avatar {
width: 88rpx;
height: 88rpx;
border-radius: 50%;
border: 2rpx solid #ffefe0;
flex-shrink: 0;
}
.member-name-row {
.member-name {
font-size: 28rpx;
font-weight: bold;
color: #333333;
max-width: 220rpx;
}
.level-badge {
margin-left: 10rpx;
padding: 2rpx 10rpx;
border-radius: 8rpx;
font-size: 20rpx;
line-height: 28rpx;
&.badge-direct {
background: #fff3e0;
color: #ff6f00;
}
&.badge-indirect {
background: #e0f2fe;
color: #0284c7;
}
}
}
.member-sub-info {
font-size: 20rpx;
color: #999999;
font-family: OPPOSANS;
.member-phone {
color: #666666;
font-size: 22rpx;
}
.member-id-tag {
background: #f3f4f6;
color: #9ca3af;
padding: 2rpx 8rpx;
border-radius: 6rpx;
font-size: 18rpx;
}
}
.member-meta-row {
gap: 8rpx;
}
.status-pill {
display: flex;
align-items: center;
padding: 6rpx 16rpx;
border-radius: 20rpx;
font-size: 20rpx;
font-weight: 500;
&.pill-green {
background: #ecfdf5;
color: #059669;
border: 1rpx solid #a7f3d0;
}
&.pill-amber {
background: #fffbeb;
color: #d97706;
border: 1rpx solid #fde68a;
}
&.pill-gray {
background: #f3f4f6;
color: #6b7280;
border: 1rpx solid #e5e7eb;
}
text:last-child {
margin-left: 6rpx;
}
}
}
// 指标网格
.metrics-grid {
background: #fafafc;
border-radius: 12rpx;
padding: 16rpx 10rpx;
.metric-col {
flex: 1;
text-align: center;
&.metric-clickable {
cursor: pointer;
&:active {
opacity: 0.8;
}
}
.metric-val {
font-size: 28rpx;
font-weight: bold;
color: #222222;
font-family: OPPOSANS;
line-height: 34rpx;
&.text-orange {
color: #ff6000;
}
}
.drill-tag {
font-size: 18rpx;
color: #ff6000;
background: #fff0e6;
padding: 2rpx 8rpx;
border-radius: 6rpx;
line-height: 22rpx;
font-weight: normal;
}
.metric-lbl {
font-size: 20rpx;
color: #999999;
margin-top: 4rpx;
}
}
.metric-divider {
width: 1rpx;
height: 36rpx;
background: #ebebed;
}
}
// 底部操作栏
.member-card-footer {
border-top: 1rpx solid #f5f5f5;
padding-top: 16rpx;
.register-time {
font-size: 22rpx;
color: #999999;
}
.actions-wrap {
.action-btn {
height: 48rpx;
padding: 0 16rpx;
border-radius: 24rpx;
font-size: 22rpx;
display: inline-flex;
align-items: center;
&.btn-drill {
background: linear-gradient(90deg, #fff0e6, #ffe6d6);
color: #ff6000;
font-weight: bold;
border: 1rpx solid #ffd8bf;
}
&.btn-call {
background: #fff0e6;
color: #ff6000;
}
&.btn-copy {
background: #f3f4f6;
color: #4b5563;
}
&.btn-reward {
background: #fff7ed;
color: #ea580c;
}
&.btn-detail {
background: #ffffff;
color: #666666;
}
}
}
}
}
}
// 身份徽章样式
.tag-box {
background: #c48a3a;
border-radius: 18rpx;
padding: 2rpx 10rpx;
display: inline-flex;
align-items: center;
.tag-img {
width: 28rpx;
height: 28rpx;
margin-right: 6rpx;
border-radius: 50%;
}
.tag-title {
font-size: 18rpx;
font-weight: 500;
color: #ffffff;
line-height: 24rpx;
}
}
.mini-tag {
padding: 2rpx 10rpx;
height: 30rpx;
line-height: 30rpx;
border-radius: 15rpx;
font-size: 18rpx;
color: #ffffff;
display: inline-flex;
align-items: center;
&.tag-venue {
background: #c48a3a;
}
&.tag-county {
background: #b45309;
}
&.tag-partner {
background: #dc2626;
}
}
// 空状态
.empty-wrap {
padding: 40rpx 0;
.reset-filter-btn {
padding: 14rpx 36rpx;
background: #f3f4f6;
border-radius: 30rpx;
font-size: 24rpx;
color: #4b5563;
}
.invite-cta-btn {
padding: 18rpx 50rpx;
background: linear-gradient(90deg, #ff6000, #ff8533);
color: #ffffff;
border-radius: 36rpx;
font-size: 28rpx;
font-weight: bold;
box-shadow: 0 6rpx 20rpx rgba(255, 96, 0, 0.25);
}
}
// 抽屉详情
.drawer-content {
.drawer-header {
.drawer-avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
border: 4rpx solid #ffefe0;
}
.user-id {
font-size: 22rpx;
color: #888888;
}
.dot-split {
margin: 0 8rpx;
color: #bbbbbb;
}
.join-time {
font-size: 22rpx;
color: #888888;
}
}
.drawer-section {
.section-title {
font-size: 26rpx;
font-weight: bold;
color: #333333;
margin-bottom: 12rpx;
}
.section-box {
background: #f9fafb;
border-radius: 14rpx;
padding: 20rpx;
&.active-detail-box {
.line-label {
font-size: 24rpx;
color: #6b7280;
}
.line-val {
font-size: 24rpx;
font-weight: 500;
color: #1f2937;
}
.notice-tip {
font-size: 22rpx;
color: #92400e;
background: #fef3c7;
border-radius: 8rpx;
padding: 10rpx 14rpx;
line-height: 32rpx;
}
}
&.performance-box {
.perf-col {
flex: 1;
text-align: center;
.perf-num {
font-size: 32rpx;
font-weight: bold;
color: #111827;
font-family: OPPOSANS;
}
.perf-lbl {
font-size: 22rpx;
color: #6b7280;
margin-top: 6rpx;
}
}
}
}
}
.status-text-bold {
font-size: 24rpx;
font-weight: bold;
&.text-green {
color: #059669;
}
&.text-yellow {
color: #d97706;
}
&.text-gray {
color: #6b7280;
}
}
.drawer-drill-card {
background: linear-gradient(135deg, #fff7ed 0%, #ffedd5 100%);
border: 1rpx solid #fed7aa;
border-radius: 16rpx;
padding: 20rpx 24rpx;
transition: all 0.2s;
&:active {
opacity: 0.9;
transform: scale(0.99);
}
.drill-card-left {
.drill-icon-box {
width: 56rpx;
height: 56rpx;
border-radius: 28rpx;
background: #ffffff;
box-shadow: 0 4rpx 10rpx rgba(255, 96, 0, 0.15);
}
.drill-info {
.drill-title {
font-size: 26rpx;
font-weight: bold;
color: #9a3412;
line-height: 34rpx;
}
.drill-desc {
font-size: 22rpx;
color: #c2410c;
margin-top: 4rpx;
line-height: 28rpx;
}
}
}
.drill-card-right {
.drill-btn-text {
font-size: 24rpx;
font-weight: bold;
color: #ea580c;
margin-right: 4rpx;
}
}
}
.drawer-actions {
.drawer-btn {
height: 72rpx;
border-radius: 36rpx;
font-size: 26rpx;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
&.drawer-btn-primary {
background: #ff6000;
color: #ffffff;
}
&.drawer-btn-sub {
background: #fff0e6;
color: #ff6000;
}
&.drawer-btn-outline {
background: #f3f4f6;
color: #4b5563;
}
}
}
}
</style>