feat(distribution): 添加分销员状态检查与加载指示

- 在多个分销相关页面中引入分销员状态检查,确保用户在访问前具备有效分销资格。
- 增加加载指示器以改善用户体验,提示用户正在加载数据。
- 更新相关逻辑以处理分销员资格的不同状态,确保用户在不符合条件时获得适当反馈。
This commit is contained in:
田香琪
2026-09-09 13:21:53 +08:00
parent 4fdd266dec
commit c12c76f6f5
21 changed files with 419 additions and 83 deletions

View File

@@ -1,5 +1,9 @@
<template> <template>
<view class="page" :style="themeStyle"> <view class="page" :style="themeStyle">
<view v-if="!pageReady" class="state-wrap">
<text class="state-text">加载中...</text>
</view>
<template v-else>
<view class="filter-bar"> <view class="filter-bar">
<scroll-view scroll-x class="filter-scroll" :show-scrollbar="false"> <scroll-view scroll-x class="filter-scroll" :show-scrollbar="false">
<view class="filter-tabs"> <view class="filter-tabs">
@@ -126,6 +130,7 @@
<view class="guide-btn" @click="showGuide = false">知道了</view> <view class="guide-btn" @click="showGuide = false">知道了</view>
</view> </view>
</u-popup> </u-popup>
</template>
</view> </view>
</template> </template>
@@ -135,6 +140,7 @@ import { onShow, onLoad } from '@dcloudio/uni-app'
import { useStore } from '@/store' import { useStore } from '@/store'
import { getThemeStyle } from '@/utils/theme' import { getThemeStyle } from '@/utils/theme'
import { getDistributionPerformance } from '@/api/distribution' import { getDistributionPerformance } from '@/api/distribution'
import { assertActiveDistributor } from '@/utils/distributionStatus'
const store = useStore() const store = useStore()
const themeStyle = computed(() => getThemeStyle(store.state.theme)) const themeStyle = computed(() => getThemeStyle(store.state.theme))
@@ -146,6 +152,7 @@ const rangeTabs = [
{ label: '近七日', value: 'LAST_7_DAYS' }, { label: '近七日', value: 'LAST_7_DAYS' },
] ]
const pageReady = ref(false)
const activeRange = ref('ALL') const activeRange = ref('ALL')
const loading = ref(false) const loading = ref(false)
const customVisible = ref(false) const customVisible = ref(false)
@@ -176,7 +183,13 @@ onLoad((options: Record<string, string>) => {
}) })
onShow(() => { onShow(() => {
loadPerformance() assertActiveDistributor().then((d) => {
if (!d) {
return
}
pageReady.value = true
loadPerformance()
})
}) })
function formatMoney(val: number | string) { function formatMoney(val: number | string) {

View File

@@ -7,7 +7,7 @@
</view> </view>
<!-- 账户列表 --> <!-- 账户列表 -->
<view v-else-if="accounts.length" class="account-list"> <view v-else-if="pageReady && accounts.length" class="account-list">
<view <view
class="account-item" class="account-item"
v-for="item in accounts" v-for="item in accounts"
@@ -39,12 +39,12 @@
</view> </view>
<!-- 空状态 --> <!-- 空状态 -->
<view v-else class="empty-wrap"> <view v-else-if="pageReady" class="empty-wrap">
<text class="empty-text">暂无银行卡请添加</text> <text class="empty-text">暂无银行卡请添加</text>
</view> </view>
<!-- 添加账户按钮 --> <!-- 添加账户按钮 -->
<view class="add-btn-wrap"> <view v-if="pageReady" class="add-btn-wrap">
<view class="add-btn" @click="openAddPopup">+ 添加账户</view> <view class="add-btn" @click="openAddPopup">+ 添加账户</view>
</view> </view>
</view> </view>
@@ -102,6 +102,7 @@ import { onLoad, onShow } from '@dcloudio/uni-app'
import { useStore } from '@/store' import { useStore } from '@/store'
import { getThemeStyle } from '@/utils/theme' import { getThemeStyle } from '@/utils/theme'
import { getBankCards, addBankCard, deleteBankCard, setDefaultBankCard } from '@/api/distribution' import { getBankCards, addBankCard, deleteBankCard, setDefaultBankCard } from '@/api/distribution'
import { assertActiveDistributor } from '@/utils/distributionStatus'
const store = useStore() const store = useStore()
const themeStyle = computed(() => getThemeStyle(store.state.theme)) const themeStyle = computed(() => getThemeStyle(store.state.theme))
@@ -113,7 +114,8 @@ const isSelectMode = ref(false)
const accounts = ref<any[]>([]) const accounts = ref<any[]>([])
const selectedId = ref('') const selectedId = ref('')
const loading = ref(false) const pageReady = ref(false)
const loading = ref(true)
const addPopupVisible = ref(false) const addPopupVisible = ref(false)
const saveLoading = ref(false) const saveLoading = ref(false)
const form = ref({ holderName: '', bankName: '', cardNo: '' }) const form = ref({ holderName: '', bankName: '', cardNo: '' })
@@ -127,7 +129,14 @@ onLoad((options: any) => {
}) })
onShow(() => { onShow(() => {
loadAccounts() loading.value = true
assertActiveDistributor().then((d) => {
if (!d) {
return
}
pageReady.value = true
loadAccounts()
})
}) })
function loadAccounts() { function loadAccounts() {

View File

@@ -33,6 +33,7 @@ import { onLoad, onReachBottom } from '@dcloudio/uni-app'
import { useStore } from '@/store' import { useStore } from '@/store'
import { getThemeStyle } from '@/utils/theme' import { getThemeStyle } from '@/utils/theme'
import { cashLog } from '@/api/distribution' import { cashLog } from '@/api/distribution'
import { assertActiveDistributor } from '@/utils/distributionStatus'
const store = useStore() const store = useStore()
const themeStyle = computed(() => getThemeStyle(store.state.theme)) const themeStyle = computed(() => getThemeStyle(store.state.theme))
@@ -40,13 +41,18 @@ const themeStyle = computed(() => getThemeStyle(store.state.theme))
const cashList = ref<any[]>([]) const cashList = ref<any[]>([])
const cashPage = ref(1) const cashPage = ref(1)
const cashTotal = ref(0) const cashTotal = ref(0)
const cashLoading = ref(false) const cashLoading = ref(true)
const cashLoadingMore = ref(false) const cashLoadingMore = ref(false)
const cashFinished = ref(false) const cashFinished = ref(false)
const PAGE_SIZE = 15 const PAGE_SIZE = 15
onLoad(() => { onLoad(() => {
loadCashHistory(true) assertActiveDistributor().then((d) => {
if (!d) {
return
}
loadCashHistory(true)
})
}) })
onReachBottom(() => { onReachBottom(() => {

View File

@@ -19,7 +19,10 @@
</view> </view>
</view> </view>
<template v-if="detailId"> <view v-if="!pageReady" class="state-wrap">
<text class="state-text">加载中...</text>
</view>
<template v-else-if="detailId">
<view v-if="detailLoading" class="state-wrap"> <view v-if="detailLoading" class="state-wrap">
<text class="state-text">加载中...</text> <text class="state-text">加载中...</text>
</view> </view>
@@ -62,7 +65,7 @@
</view> </view>
</template> </template>
<template v-else> <template v-else-if="pageReady">
<view class="main-tabs"> <view class="main-tabs">
<view <view
v-for="item in mainTabs" v-for="item in mainTabs"
@@ -184,6 +187,7 @@ import {
updateDistributionCustomerStarred, updateDistributionCustomerStarred,
} from '@/api/distribution' } from '@/api/distribution'
import { parseGoodsImageUrl } from '@/utils/filters.js' import { parseGoodsImageUrl } from '@/utils/filters.js'
import { assertActiveDistributor } from '@/utils/distributionStatus'
const store = useStore() const store = useStore()
const themeStyle = computed(() => getThemeStyle(store.state.theme)) const themeStyle = computed(() => getThemeStyle(store.state.theme))
@@ -207,6 +211,7 @@ const rangeTabs = [
const activeFilter = ref('ALL') const activeFilter = ref('ALL')
const activeRange = ref('ALL') const activeRange = ref('ALL')
const keyword = ref('') const keyword = ref('')
const pageReady = ref(false)
const loading = ref(false) const loading = ref(false)
const loadingMore = ref(false) const loadingMore = ref(false)
const finished = ref(false) const finished = ref(false)
@@ -245,9 +250,15 @@ onShow(() => {
if (current?.options) { if (current?.options) {
applyRouteOptions(current.options) applyRouteOptions(current.options)
} }
if (!detailId.value) { assertActiveDistributor().then((d) => {
resetAndLoad() if (!d) {
} return
}
pageReady.value = true
if (!detailId.value) {
resetAndLoad()
}
})
}) })
onReachBottom(() => { onReachBottom(() => {

View File

@@ -119,7 +119,8 @@ import { onShow } from '@dcloudio/uni-app'
import { useStore } from '@/store' import { useStore } from '@/store'
import { getThemeStyle } from '@/utils/theme' import { getThemeStyle } from '@/utils/theme'
import config from '@/config/config' import config from '@/config/config'
import { distribution, getDistributionGrades } from '@/api/distribution' import { getDistributionGrades } from '@/api/distribution'
import { assertActiveDistributor } from '@/utils/distributionStatus'
import { getUserInfo } from '@/api/members' import { getUserInfo } from '@/api/members'
const store = useStore() const store = useStore()
@@ -166,18 +167,21 @@ function refreshUserInfo() {
function loadPageData() { function loadPageData() {
loading.value = true loading.value = true
Promise.all([fetchGrades(), fetchDistribution()]).finally(() => { let allowed = false
loading.value = false assertActiveDistributor()
}) .then((result) => {
} if (!result) {
return
function fetchDistribution() { }
return distribution().then((res) => { allowed = true
if (!res.data?.result) { distributionData.value = result
return return fetchGrades()
} })
distributionData.value = res.data.result .finally(() => {
}) if (allowed) {
loading.value = false
}
})
} }
function fetchGrades() { function fetchGrades() {

View File

@@ -58,6 +58,7 @@ import { onLoad, onReachBottom } from '@dcloudio/uni-app'
import { useStore } from '@/store' import { useStore } from '@/store'
import { cashLog, getDistributionOrders } from '@/api/distribution' import { cashLog, getDistributionOrders } from '@/api/distribution'
import { unitPrice } from '@/utils/filters.js' import { unitPrice } from '@/utils/filters.js'
import { assertActiveDistributor } from '@/utils/distributionStatus'
const store = useStore() const store = useStore()
@@ -79,7 +80,12 @@ onLoad((option) => {
uni.setNavigationBarTitle({ uni.setNavigationBarTitle({
title: type === 0 ? '分销业绩' : '提现记录', title: type === 0 ? '分销业绩' : '提现记录',
}) })
type === 0 ? fetchAchievementList() : fetchWithdrawLog() assertActiveDistributor().then((d) => {
if (!d) {
return
}
type === 0 ? fetchAchievementList() : fetchWithdrawLog()
})
}) })
onReachBottom(() => { onReachBottom(() => {

View File

@@ -10,6 +10,8 @@
<view class="nav-placeholder"></view> <view class="nav-placeholder"></view>
</view> </view>
<view v-if="checking" class="center-pending">加载中...</view>
<template v-else-if="centerReady">
<!-- 顶部用户信息 --> <!-- 顶部用户信息 -->
<view class="header"> <view class="header">
<view class="user-row" @click="goGrade"> <view class="user-row" @click="goGrade">
@@ -199,6 +201,7 @@
</view> </view>
</view> </view>
</view> </view>
</template>
</view> </view>
</template> </template>
@@ -209,7 +212,6 @@ import { useStore } from '@/store'
import config from '@/config/config' import config from '@/config/config'
import { getThemeStyle } from '@/utils/theme' import { getThemeStyle } from '@/utils/theme'
import { import {
distribution,
getDistributionFeaturedGoods, getDistributionFeaturedGoods,
getDistributionGrades, getDistributionGrades,
getDistributionCustomers, getDistributionCustomers,
@@ -218,6 +220,7 @@ import {
getDistributionPerformance, getDistributionPerformance,
} from '@/api/distribution' } from '@/api/distribution'
import { getUserInfo } from '@/api/members' import { getUserInfo } from '@/api/members'
import { assertActiveDistributor } from '@/utils/distributionStatus'
const store = useStore() const store = useStore()
const themeStyle = computed(() => getThemeStyle(store.state.theme)) const themeStyle = computed(() => getThemeStyle(store.state.theme))
@@ -226,6 +229,8 @@ const statusBarHeight = ref(20)
const expanded = ref(false) const expanded = ref(false)
const distributionData = ref<Record<string, any>>({}) const distributionData = ref<Record<string, any>>({})
const checking = ref(true)
const centerReady = ref(false)
const gradeList = ref<any[]>([]) const gradeList = ref<any[]>([])
const featuredGoods = ref<any[]>([]) const featuredGoods = ref<any[]>([])
const todayOrderCount = ref(0) const todayOrderCount = ref(0)
@@ -288,25 +293,38 @@ function refreshUserInfo() {
} }
function loadPageData() { function loadPageData() {
Promise.all([ checking.value = true
fetchDistribution(), centerReady.value = false
fetchGrades(), distributionData.value = {}
fetchTodayOrders(), fetchDistribution()
fetchTodayEarnings(), .then((active) => {
fetchTodayCustomers(), if (!active) {
fetchTodayInvites(), return
fetchFeaturedGoods(), }
]) centerReady.value = true
Promise.all([
fetchGrades(),
fetchTodayOrders(),
fetchTodayEarnings(),
fetchTodayCustomers(),
fetchTodayInvites(),
fetchFeaturedGoods(),
])
})
.finally(() => {
if (centerReady.value) {
checking.value = false
}
})
} }
function fetchDistribution() { function fetchDistribution() {
return distribution().then((res) => { return assertActiveDistributor().then((result) => {
if (!res.data?.result) { if (!result) {
uni.showToast({ title: '您还不是分销员', icon: 'none' }) return false
setTimeout(() => goBack(), 1500)
return
} }
distributionData.value = res.data.result distributionData.value = result
return true
}) })
} }
@@ -497,6 +515,13 @@ function goOrder() {
width: 72rpx; width: 72rpx;
} }
.center-pending {
padding: 120rpx 0;
color: #999;
font-size: 28rpx;
text-align: center;
}
.header { .header {
background: linear-gradient(180deg, #1f2228 0%, #2a2e36 100%); background: linear-gradient(180deg, #1f2228 0%, #2a2e36 100%);
padding: 24rpx 32rpx 40rpx; padding: 24rpx 32rpx 40rpx;

View File

@@ -1,5 +1,9 @@
<template> <template>
<view class="page" :style="themeStyle"> <view class="page" :style="themeStyle">
<view v-if="!pageReady" class="state-wrap">
<text class="state-text">加载中...</text>
</view>
<template v-else>
<view class="hero"> <view class="hero">
<view class="hero-title">邀好友 赚奖励</view> <view class="hero-title">邀好友 赚奖励</view>
</view> </view>
@@ -54,6 +58,7 @@
<view class="view-all" @click="goInviteList">查看我的邀请</view> <view class="view-all" @click="goInviteList">查看我的邀请</view>
</view> </view>
</template>
</view> </view>
</template> </template>
@@ -65,6 +70,7 @@ import config from '@/config/config'
import { getThemeStyle } from '@/utils/theme' import { getThemeStyle } from '@/utils/theme'
import { getDistributionInvitees } from '@/api/distribution' import { getDistributionInvitees } from '@/api/distribution'
import { parseGoodsImageUrl } from '@/utils/filters.js' import { parseGoodsImageUrl } from '@/utils/filters.js'
import { assertActiveDistributor } from '@/utils/distributionStatus'
const store = useStore() const store = useStore()
const themeStyle = computed(() => getThemeStyle(store.state.theme)) const themeStyle = computed(() => getThemeStyle(store.state.theme))
@@ -91,12 +97,19 @@ const flowSteps = [
}, },
] ]
const pageReady = ref(false)
const loading = ref(false) const loading = ref(false)
const inviteTotal = ref(0) const inviteTotal = ref(0)
const inviteList = ref<any[]>([]) const inviteList = ref<any[]>([])
onShow(() => { onShow(() => {
loadPreview() assertActiveDistributor().then((d) => {
if (!d) {
return
}
pageReady.value = true
loadPreview()
})
}) })
function loadPreview() { function loadPreview() {

View File

@@ -1,5 +1,9 @@
<template> <template>
<view class="page" :style="themeStyle"> <view class="page" :style="themeStyle">
<view v-if="!pageReady" class="state-wrap">
<text class="state-text">加载中...</text>
</view>
<template v-else>
<view class="filter-bar"> <view class="filter-bar">
<scroll-view scroll-x class="filter-scroll" :show-scrollbar="false"> <scroll-view scroll-x class="filter-scroll" :show-scrollbar="false">
<view class="filter-tabs"> <view class="filter-tabs">
@@ -80,6 +84,7 @@
</view> </view>
</view> </view>
</u-popup> </u-popup>
</template>
</view> </view>
</template> </template>
@@ -91,6 +96,7 @@ import config from '@/config/config'
import { getThemeStyle } from '@/utils/theme' import { getThemeStyle } from '@/utils/theme'
import { getDistributionInvitees } from '@/api/distribution' import { getDistributionInvitees } from '@/api/distribution'
import { parseGoodsImageUrl } from '@/utils/filters.js' import { parseGoodsImageUrl } from '@/utils/filters.js'
import { assertActiveDistributor } from '@/utils/distributionStatus'
const store = useStore() const store = useStore()
const themeStyle = computed(() => getThemeStyle(store.state.theme)) const themeStyle = computed(() => getThemeStyle(store.state.theme))
@@ -104,6 +110,7 @@ const rangeTabs = [
] ]
const activeRange = ref('ALL') const activeRange = ref('ALL')
const pageReady = ref(false)
const loading = ref(false) const loading = ref(false)
const loadingMore = ref(false) const loadingMore = ref(false)
const finished = ref(false) const finished = ref(false)
@@ -138,7 +145,13 @@ onShow(() => {
if (current?.options) { if (current?.options) {
applyRouteOptions(current.options) applyRouteOptions(current.options)
} }
resetAndLoad() assertActiveDistributor().then((d) => {
if (!d) {
return
}
pageReady.value = true
resetAndLoad()
})
}) })
onReachBottom(() => { onReachBottom(() => {

View File

@@ -58,6 +58,7 @@ import { onLoad, onReady, onShareAppMessage } from '@dcloudio/uni-app'
import { useStore } from '@/store' import { useStore } from '@/store'
import { getThemeStyle } from '@/utils/theme' import { getThemeStyle } from '@/utils/theme'
import { getInviteCard } from '@/api/distribution' import { getInviteCard } from '@/api/distribution'
import { assertActiveDistributor } from '@/utils/distributionStatus'
import { getMpCode } from '@/api/goods' import { getMpCode } from '@/api/goods'
import { parseGoodsImageUrl } from '@/utils/filters.js' import { parseGoodsImageUrl } from '@/utils/filters.js'
import { import {
@@ -282,6 +283,11 @@ async function loadInviteCardSetting() {
qrError.value = '' qrError.value = ''
composeError.value = '' composeError.value = ''
const distributor = await assertActiveDistributor()
if (!distributor) {
return
}
try { try {
const res = await getInviteCard() const res = await getInviteCard()
if (!res.data?.success || !res.data?.result) { if (!res.data?.success || !res.data?.result) {

View File

@@ -1,6 +1,13 @@
<template> <template>
<view class="wrapper" :style="themeStyle"> <view class="wrapper" :style="themeStyle">
<view v-if="loading" class="loading">加载中...</view> <view v-if="loading" class="loading">加载中...</view>
<view v-else-if="isRetreated" class="page-body">
<view class="section result-section">
<view class="result-icon retreated">!</view>
<view class="result-title">分销资格已清退</view>
<view class="result-desc">{{ DISTRIBUTION_RETREAT_MESSAGE }}</view>
</view>
</view>
<view v-else-if="!plan"> <view v-else-if="!plan">
<view class="empty">暂未开放线上招募</view> <view class="empty">暂未开放线上招募</view>
</view> </view>
@@ -171,6 +178,7 @@ import config from '@/config/config'
import { parseGoodsImageUrl, tipsToLogin } from '@/utils/filters.js' import { parseGoodsImageUrl, tipsToLogin } from '@/utils/filters.js'
import { resolveRecruitDistributionIdFromScene } from '@/utils/distributionBind.js' import { resolveRecruitDistributionIdFromScene } from '@/utils/distributionBind.js'
import { getRecruitPage, getRecruitProgress, getRecruitInviter, submitRecruitApplication } from '@/api/distribution' import { getRecruitPage, getRecruitProgress, getRecruitInviter, submitRecruitApplication } from '@/api/distribution'
import { DISTRIBUTION_RETREAT_MESSAGE, DISTRIBUTION_STATUS } from '@/utils/distributionStatus'
const defaultAvatar = config.defaultUserPhoto const defaultAvatar = config.defaultUserPhoto
@@ -198,7 +206,12 @@ const fields = computed(() => pageInfo.value.fields || [])
const recruitGoods = computed(() => plan.value?.recruitGoods || []) const recruitGoods = computed(() => plan.value?.recruitGoods || [])
const agreementContent = computed(() => plan.value?.agreementContent || '') const agreementContent = computed(() => plan.value?.agreementContent || '')
const distributionStatus = computed(() => pageInfo.value.distributionStatus || '')
const currentApplication = computed(() => pageInfo.value.currentApplication || null)
const isRetreated = computed(() => distributionStatus.value === DISTRIBUTION_STATUS.RETREAT)
const showPoster = computed(() => { const showPoster = computed(() => {
if (isRetreated.value) return false
const p = plan.value const p = plan.value
if (!p) return false if (!p) return false
if (p.posterEnabled === false) return false if (p.posterEnabled === false) return false
@@ -211,45 +224,50 @@ const posterRuleText = computed(() => plan.value?.posterRuleText || '')
const posterContact = computed(() => plan.value?.posterContact || '') const posterContact = computed(() => plan.value?.posterContact || '')
const hasJoinCondition = computed(() => { const hasJoinCondition = computed(() => {
if (isRetreated.value) return false
const p = plan.value const p = plan.value
if (!p || p.joinConditionType !== 'CONDITIONAL') return false if (!p || p.joinConditionType !== 'CONDITIONAL') return false
return !!(p.requireSelfPurchaseAmount || p.requireConsumeCount || p.requirePurchaseGoods) return !!(p.requireSelfPurchaseAmount || p.requireConsumeCount || p.requirePurchaseGoods)
}) })
const conditionsMet = computed(() => { const conditionsMet = computed(() => {
if (isRetreated.value) return false
if (!hasJoinCondition.value) return true if (!hasJoinCondition.value) return true
return !!progress.value?.satisfied return !!progress.value?.satisfied
}) })
const distributionStatus = computed(() => pageInfo.value.distributionStatus || '')
const currentApplication = computed(() => pageInfo.value.currentApplication || null)
const isApplyApproved = computed(() => { const isApplyApproved = computed(() => {
if (distributionStatus.value === 'PASS') return true if (isRetreated.value) return false
if (distributionStatus.value === DISTRIBUTION_STATUS.PASS) return true
if (submitApplicationStatus.value === 'APPROVED') return true if (submitApplicationStatus.value === 'APPROVED') return true
if (applySubmitted.value && submitAuditMode.value === 'AUTO_PASS') return true if (applySubmitted.value && submitAuditMode.value === 'AUTO_PASS') return true
return currentApplication.value?.applicationStatus === 'APPROVED' return false
}) })
const isApplyPending = computed(() => { const isApplyPending = computed(() => {
if (isRetreated.value) return false
if (isApplyApproved.value) return false if (isApplyApproved.value) return false
if (distributionStatus.value === 'APPLY') return true if (distributionStatus.value === DISTRIBUTION_STATUS.APPLY) return true
if (submitApplicationStatus.value === 'PENDING') return true if (submitApplicationStatus.value === 'PENDING') return true
if (applySubmitted.value && submitAuditMode.value === 'MANUAL') return true if (applySubmitted.value && submitAuditMode.value === 'MANUAL') return true
return currentApplication.value?.applicationStatus === 'PENDING' return currentApplication.value?.applicationStatus === 'PENDING'
}) })
const showApplyResult = computed(() => isApplyApproved.value || isApplyPending.value) const showApplyResult = computed(() => {
if (isRetreated.value) return false
return isApplyApproved.value || isApplyPending.value
})
const isManualApply = computed(() => { const isManualApply = computed(() => {
return (plan.value?.applyMode || 'MANUAL') === 'MANUAL' return (plan.value?.applyMode || 'MANUAL') === 'MANUAL'
}) })
const showLoginFooter = computed(() => { const showLoginFooter = computed(() => {
return !!plan.value && !isUserLoggedIn.value && !loading.value return !isRetreated.value && !!plan.value && !isUserLoggedIn.value && !loading.value
}) })
const showInviterHeader = computed(() => { const showInviterHeader = computed(() => {
if (isRetreated.value) return false
if (!recruitToken.value) return false if (!recruitToken.value) return false
const info = inviterInfo.value const info = inviterInfo.value
return !!(info?.memberName || info?.memberAvatar) return !!(info?.memberName || info?.memberAvatar)
@@ -259,15 +277,15 @@ const inviterName = computed(() => inviterInfo.value?.memberName || '分销员')
const inviterAvatar = computed(() => parseGoodsImageUrl(inviterInfo.value?.memberAvatar) || defaultAvatar) const inviterAvatar = computed(() => parseGoodsImageUrl(inviterInfo.value?.memberAvatar) || defaultAvatar)
const showManualApply = computed(() => { const showManualApply = computed(() => {
return isUserLoggedIn.value && !showApplyResult.value && conditionsMet.value && isManualApply.value return !isRetreated.value && isUserLoggedIn.value && !showApplyResult.value && conditionsMet.value && isManualApply.value
}) })
const showAutoApplyTip = computed(() => { const showAutoApplyTip = computed(() => {
return isUserLoggedIn.value && !showApplyResult.value && conditionsMet.value && plan.value?.applyMode === 'AUTO' return !isRetreated.value && isUserLoggedIn.value && !showApplyResult.value && conditionsMet.value && plan.value?.applyMode === 'AUTO'
}) })
const showApplyForm = computed(() => { const showApplyForm = computed(() => {
return !!plan.value?.requireApplyInfo && fields.value.length > 0 return !isRetreated.value && !!plan.value?.requireApplyInfo && fields.value.length > 0
}) })
const canApply = computed(() => { const canApply = computed(() => {
@@ -445,7 +463,7 @@ function handleApplySuccess(application: any) {
submitAuditMode.value = auditMode submitAuditMode.value = auditMode
submitApplicationStatus.value = status submitApplicationStatus.value = status
if (auditMode === 'AUTO_PASS' || status === 'APPROVED') { if (auditMode === 'AUTO_PASS' || status === 'APPROVED') {
pageInfo.value.distributionStatus = 'PASS' pageInfo.value.distributionStatus = DISTRIBUTION_STATUS.PASS
if (pageInfo.value.currentApplication) { if (pageInfo.value.currentApplication) {
pageInfo.value.currentApplication.applicationStatus = 'APPROVED' pageInfo.value.currentApplication.applicationStatus = 'APPROVED'
} }
@@ -458,6 +476,10 @@ function submit() {
if (!tipsToLogin('normal')) { if (!tipsToLogin('normal')) {
return return
} }
if (isRetreated.value) {
uni.showToast({ title: DISTRIBUTION_RETREAT_MESSAGE, icon: 'none' })
return
}
if (!showManualApply.value) { if (!showManualApply.value) {
return return
} }
@@ -738,6 +760,11 @@ function submit() {
color: #fa8c16; color: #fa8c16;
} }
.result-icon.retreated {
background: #fff2f0;
color: #ff4d4f;
}
.result-title { .result-title {
font-size: 36rpx; font-size: 36rpx;
font-weight: 600; font-weight: 600;

View File

@@ -1,5 +1,9 @@
<template> <template>
<view class="page"> <view class="page">
<view v-if="!pageReady" class="state-wrap">
<text class="state-text">加载中...</text>
</view>
<template v-else>
<view class="search-bar"> <view class="search-bar">
<view class="search-inner"> <view class="search-inner">
<u-icon name="search" color="#bbb" size="18" /> <u-icon name="search" color="#bbb" size="18" />
@@ -81,6 +85,7 @@
<text>{{ finished ? '没有更多了' : (loadingMore ? '加载中...' : '上拉加载更多') }}</text> <text>{{ finished ? '没有更多了' : (loadingMore ? '加载中...' : '上拉加载更多') }}</text>
</view> </view>
</scroll-view> </scroll-view>
</template>
</view> </view>
</template> </template>
@@ -88,6 +93,7 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { distributionGoods, getDistributionGoodsSetting } from '@/api/distribution' import { distributionGoods, getDistributionGoodsSetting } from '@/api/distribution'
import { assertActiveDistributor } from '@/utils/distributionStatus'
const DEFAULT_SORT_MAP: Record<string, { field: string; order: string }> = { const DEFAULT_SORT_MAP: Record<string, { field: string; order: string }> = {
HIGHEST_COMMISSION: { field: 'COMMISSION', order: 'DESC' }, HIGHEST_COMMISSION: { field: 'COMMISSION', order: 'DESC' },
@@ -103,6 +109,7 @@ const sortTabs = [
{ field: 'PRICE', label: '价格' }, { field: 'PRICE', label: '价格' },
] ]
const pageReady = ref(false)
const routeQuery = ref<Record<string, string>>({}) const routeQuery = ref<Record<string, string>>({})
const keyword = ref('') const keyword = ref('')
const sortField = ref('COMMISSION') const sortField = ref('COMMISSION')
@@ -128,6 +135,11 @@ onLoad((options) => {
}) })
async function initPage() { async function initPage() {
const distributor = await assertActiveDistributor()
if (!distributor) {
return
}
pageReady.value = true
await loadGoodsSetting() await loadGoodsSetting()
resetAndFetch() resetAndFetch()
} }

View File

@@ -9,7 +9,7 @@
<view class="nav-placeholder"></view> <view class="nav-placeholder"></view>
</view> </view>
<view class="hero"> <view v-if="!loading && detail.orderItemSn" class="hero">
<view class="hero-status">{{ detail.settlementStatusText || '待结算' }}</view> <view class="hero-status">{{ detail.settlementStatusText || '待结算' }}</view>
<view class="hero-sub">分销订单详情</view> <view class="hero-sub">分销订单详情</view>
</view> </view>
@@ -109,11 +109,12 @@ import { useStore } from '@/store'
import { getThemeStyle } from '@/utils/theme' import { getThemeStyle } from '@/utils/theme'
import { getDistributionOrderDetail } from '@/api/distribution' import { getDistributionOrderDetail } from '@/api/distribution'
import { parseGoodsImageUrl, formatGoodsSpecs } from '@/utils/filters.js' import { parseGoodsImageUrl, formatGoodsSpecs } from '@/utils/filters.js'
import { assertActiveDistributor } from '@/utils/distributionStatus'
const store = useStore() const store = useStore()
const themeStyle = computed(() => getThemeStyle(store.state.theme)) const themeStyle = computed(() => getThemeStyle(store.state.theme))
const statusBarHeight = ref(20) const statusBarHeight = ref(20)
const loading = ref(false) const loading = ref(true)
const detail = ref<Record<string, any>>({}) const detail = ref<Record<string, any>>({})
const orderItemSn = ref('') const orderItemSn = ref('')
const orderType = ref('PROMOTION') const orderType = ref('PROMOTION')
@@ -122,7 +123,12 @@ onLoad((options) => {
statusBarHeight.value = uni.getWindowInfo?.()?.statusBarHeight || 20 statusBarHeight.value = uni.getWindowInfo?.()?.statusBarHeight || 20
orderItemSn.value = options?.orderItemSn || '' orderItemSn.value = options?.orderItemSn || ''
orderType.value = options?.orderType || 'PROMOTION' orderType.value = options?.orderType || 'PROMOTION'
loadDetail() assertActiveDistributor().then((d) => {
if (!d) {
return
}
loadDetail()
})
}) })
const totalGoodsNum = computed(() => Number(detail.value.totalGoodsNum || detail.value.num || 0)) const totalGoodsNum = computed(() => Number(detail.value.totalGoodsNum || detail.value.num || 0))

View File

@@ -1,5 +1,9 @@
<template> <template>
<view class="page" :style="themeStyle"> <view class="page" :style="themeStyle">
<view v-if="!pageReady" class="state-wrap">
<text class="state-text">加载中...</text>
</view>
<template v-else>
<view class="search-bar"> <view class="search-bar">
<view class="search-inner"> <view class="search-inner">
<u-icon name="search" color="#bbb" size="18" /> <u-icon name="search" color="#bbb" size="18" />
@@ -132,6 +136,7 @@
</view> </view>
</view> </view>
</u-popup> </u-popup>
</template>
</view> </view>
</template> </template>
@@ -142,6 +147,7 @@ import { useStore } from '@/store'
import { getThemeStyle } from '@/utils/theme' import { getThemeStyle } from '@/utils/theme'
import { getDistributionOrders } from '@/api/distribution' import { getDistributionOrders } from '@/api/distribution'
import { parseGoodsImageUrl, formatGoodsSpecs } from '@/utils/filters.js' import { parseGoodsImageUrl, formatGoodsSpecs } from '@/utils/filters.js'
import { assertActiveDistributor } from '@/utils/distributionStatus'
const store = useStore() const store = useStore()
const themeStyle = computed(() => getThemeStyle(store.state.theme)) const themeStyle = computed(() => getThemeStyle(store.state.theme))
@@ -161,6 +167,7 @@ const rangeTabs = [
const activeTab = ref('PROMOTION') const activeTab = ref('PROMOTION')
const activeRange = ref('ALL') const activeRange = ref('ALL')
const keyword = ref('') const keyword = ref('')
const pageReady = ref(false)
const loading = ref(false) const loading = ref(false)
const loadingMore = ref(false) const loadingMore = ref(false)
const finished = ref(false) const finished = ref(false)
@@ -202,7 +209,13 @@ onShow(() => {
if (current?.options) { if (current?.options) {
applyRouteOptions(current.options) applyRouteOptions(current.options)
} }
resetAndLoad() assertActiveDistributor().then((d) => {
if (!d) {
return
}
pageReady.value = true
resetAndLoad()
})
}) })
onReachBottom(() => { onReachBottom(() => {

View File

@@ -50,6 +50,7 @@ import { onLoad, onReady, onShareAppMessage } from '@dcloudio/uni-app'
import { useStore } from '@/store' import { useStore } from '@/store'
import { getThemeStyle } from '@/utils/theme' import { getThemeStyle } from '@/utils/theme'
import { getPromotionPoster } from '@/api/distribution' import { getPromotionPoster } from '@/api/distribution'
import { assertActiveDistributor } from '@/utils/distributionStatus'
import { getMpCode } from '@/api/goods' import { getMpCode } from '@/api/goods'
import { parseGoodsImageUrl } from '@/utils/filters.js' import { parseGoodsImageUrl } from '@/utils/filters.js'
import { import {
@@ -269,6 +270,11 @@ async function loadPosterSetting() {
qrError.value = '' qrError.value = ''
composeError.value = '' composeError.value = ''
const distributor = await assertActiveDistributor()
if (!distributor) {
return
}
try { try {
const res = await getPromotionPoster() const res = await getPromotionPoster()
if (!res.data?.success || !res.data?.result) { if (!res.data?.success || !res.data?.result) {

View File

@@ -1,6 +1,10 @@
<template> <template>
<view class="page"> <view class="page">
<view v-if="!pageReady" class="state-wrap">
<text class="state-text">加载中...</text>
</view>
<distribution-goods-share <distribution-goods-share
v-else
:sku-id="query.skuId" :sku-id="query.skuId"
:goods-id="query.goodsId" :goods-id="query.goodsId"
:distribution-id="query.distributionId" :distribution-id="query.distributionId"
@@ -12,11 +16,19 @@
import { ref } from 'vue' import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import DistributionGoodsShare from '@/components/distribution-goods-share/index.vue' import DistributionGoodsShare from '@/components/distribution-goods-share/index.vue'
import { assertActiveDistributor } from '@/utils/distributionStatus'
const query = ref<Record<string, string>>({}) const query = ref<Record<string, string>>({})
const pageReady = ref(false)
onLoad((options) => { onLoad((options) => {
query.value = (options || {}) as Record<string, string> query.value = (options || {}) as Record<string, string>
assertActiveDistributor().then((d) => {
if (!d) {
return
}
pageReady.value = true
})
}) })
</script> </script>
@@ -25,4 +37,14 @@ onLoad((options) => {
min-height: 100vh; min-height: 100vh;
background: #fff; background: #fff;
} }
.state-wrap {
padding: 120rpx 0;
text-align: center;
}
.state-text {
color: #999;
font-size: 26rpx;
}
</style> </style>

View File

@@ -32,6 +32,7 @@ import { onLoad, onReachBottom } from '@dcloudio/uni-app'
import { useStore } from '@/store' import { useStore } from '@/store'
import { getThemeStyle } from '@/utils/theme' import { getThemeStyle } from '@/utils/theme'
import { getWalletLog } from '@/api/distribution' import { getWalletLog } from '@/api/distribution'
import { assertActiveDistributor } from '@/utils/distributionStatus'
const store = useStore() const store = useStore()
const themeStyle = computed(() => getThemeStyle(store.state.theme)) const themeStyle = computed(() => getThemeStyle(store.state.theme))
@@ -39,13 +40,18 @@ const themeStyle = computed(() => getThemeStyle(store.state.theme))
const walletList = ref<any[]>([]) const walletList = ref<any[]>([])
const walletPage = ref(1) const walletPage = ref(1)
const walletTotal = ref(0) const walletTotal = ref(0)
const walletLoading = ref(false) const walletLoading = ref(true)
const walletLoadingMore = ref(false) const walletLoadingMore = ref(false)
const walletFinished = ref(false) const walletFinished = ref(false)
const PAGE_SIZE = 15 const PAGE_SIZE = 15
onLoad(() => { onLoad(() => {
loadWalletLog(true) assertActiveDistributor().then((d) => {
if (!d) {
return
}
loadWalletLog(true)
})
}) })
onReachBottom(() => { onReachBottom(() => {

View File

@@ -1,6 +1,9 @@
<template> <template>
<view class="page" :style="themeStyle"> <view class="page" :style="themeStyle">
<view class="content"> <view v-if="!pageReady" class="state-wrap">
<text class="state-text">加载中...</text>
</view>
<view v-else class="content">
<!-- 到账账户 --> <!-- 到账账户 -->
<view class="section-card"> <view class="section-card">
<view class="account-row" @click="openAccountDrawer"> <view class="account-row" @click="openAccountDrawer">
@@ -139,11 +142,13 @@ import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { useStore } from '@/store' import { useStore } from '@/store'
import { getThemeStyle } from '@/utils/theme' import { getThemeStyle } from '@/utils/theme'
import { distribution, cash, getBankCards, addBankCard } from '@/api/distribution' import { cash, getBankCards, addBankCard } from '@/api/distribution'
import { assertActiveDistributor } from '@/utils/distributionStatus'
const store = useStore() const store = useStore()
const themeStyle = computed(() => getThemeStyle(store.state.theme)) const themeStyle = computed(() => getThemeStyle(store.state.theme))
const pageReady = ref(false)
const canRebate = ref(0) const canRebate = ref(0)
const amount = ref('') const amount = ref('')
const submitLoading = ref(false) const submitLoading = ref(false)
@@ -161,14 +166,17 @@ const saveLoading = ref(false)
const form = ref({ holderName: '', bankName: '', cardNo: '' }) const form = ref({ holderName: '', bankName: '', cardNo: '' })
function loadInfo() { function loadInfo() {
distribution().then((res: any) => { return assertActiveDistributor().then((d) => {
const d = res?.data?.result || {} if (!d) {
return null
}
canRebate.value = Number(d.canRebate || 0) canRebate.value = Number(d.canRebate || 0)
if (!inited.value) { if (!inited.value) {
const prefill = Math.min(canRebate.value, 9999) const prefill = Math.min(canRebate.value, 9999)
amount.value = prefill > 0 ? prefill.toFixed(2) : '' amount.value = prefill > 0 ? prefill.toFixed(2) : ''
inited.value = true inited.value = true
} }
return d
}) })
} }
@@ -188,8 +196,13 @@ function loadCards() {
} }
onLoad(() => { onLoad(() => {
loadInfo() loadInfo().then((d) => {
loadCards() if (!d) {
return
}
pageReady.value = true
loadCards()
})
}) })
function openAccountDrawer() { function openAccountDrawer() {
@@ -304,6 +317,16 @@ function maskCard(cardNo: string) {
.content { padding: 24rpx; } .content { padding: 24rpx; }
.state-wrap {
padding: 120rpx 0;
text-align: center;
}
.state-text {
color: #999;
font-size: 26rpx;
}
.section-card { .section-card {
background: #fff; background: #fff;
border-radius: 20rpx; border-radius: 20rpx;

View File

@@ -1,5 +1,9 @@
<template> <template>
<view class="wrapper" :style="themeStyle"> <view class="wrapper" :style="themeStyle">
<view v-if="!pageReady" class="state-wrap">
<text class="state-text">加载中...</text>
</view>
<template v-else>
<view class="feedBack-box"> <view class="feedBack-box">
<view class="box-title">提现金额</view> <view class="box-title">提现金额</view>
<view class="amount-row"> <view class="amount-row">
@@ -23,13 +27,15 @@
</view> </view>
<view class="submit" @click="submitWithdraw">提现</view> <view class="submit" @click="submitWithdraw">提现</view>
</template>
</view> </view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, getCurrentInstance, onMounted } from 'vue' import { ref, computed, getCurrentInstance, onMounted } from 'vue'
import { useStore } from '@/store' import { useStore } from '@/store'
import { distribution, cash } from '@/api/distribution' import { cash } from '@/api/distribution'
import { assertActiveDistributor } from '@/utils/distributionStatus'
import { unitPrice } from '@/utils/filters.js' import { unitPrice } from '@/utils/filters.js'
import { getThemeStyle } from '@/utils/theme' import { getThemeStyle } from '@/utils/theme'
@@ -38,23 +44,20 @@ const { proxy } = getCurrentInstance()!
const themeStyle = computed(() => getThemeStyle(store.state.theme)) const themeStyle = computed(() => getThemeStyle(store.state.theme))
const price = ref('') const price = ref('')
const pageReady = ref(false)
const distributionData = ref<Record<string, any>>({}) const distributionData = ref<Record<string, any>>({})
onMounted(() => { onMounted(() => {
fetchDistributionInfo() fetchDistributionInfo()
}) })
function hideLoadingIfNeeded() {
if (store.state.isShowToast) uni.hideLoading()
}
function fetchDistributionInfo() { function fetchDistributionInfo() {
uni.showLoading({ title: '加载中' }) assertActiveDistributor().then((result) => {
distribution().then((res) => { if (!result) {
if (res.data.result) { return
distributionData.value = res.data.result
} }
hideLoadingIfNeeded() distributionData.value = result
pageReady.value = true
}) })
} }
@@ -98,6 +101,16 @@ page {
background: #f8f8f8; background: #f8f8f8;
} }
.state-wrap {
padding: 120rpx 0;
text-align: center;
}
.state-text {
color: #999;
font-size: 26rpx;
}
.feedBack-box { .feedBack-box {
background: #fff; background: #fff;
border-radius: 20rpx; border-radius: 20rpx;

View File

@@ -136,6 +136,7 @@ import { distribution as fetchDistribution } from '@/api/distribution'
import configs from '@/config/config' import configs from '@/config/config'
import storage from '@/utils/storage' import storage from '@/utils/storage'
import { tipsToLogin, setClipboard } from '@/utils/filters.js' import { tipsToLogin, setClipboard } from '@/utils/filters.js'
import { DISTRIBUTION_RETREAT_MESSAGE, DISTRIBUTION_STATUS } from '@/utils/distributionStatus'
const sharingShow = ref(false) const sharingShow = ref(false)
const sharingLink = ref('') const sharingLink = ref('')
@@ -194,18 +195,19 @@ function distribution() {
if (!tipsToLogin('normal')) { if (!tipsToLogin('normal')) {
return return
} }
// 入口按 distributionStatus 路由:有对象 ≠ 有效分销员。清退停住,不进 join / home。
fetchDistribution().then((res) => { fetchDistribution().then((res) => {
if (res.data.result) { if (res.data.result) {
const type = res.data.result.distributionStatus const type = res.data.result.distributionStatus
if (type == 'PASS') { if (type == DISTRIBUTION_STATUS.PASS) {
uni.navigateTo({ uni.navigateTo({
url: '/pages/mine/distribution/home', url: '/pages/mine/distribution/home',
}) })
} else if (type == 'REFUSE') { } else if (type == DISTRIBUTION_STATUS.REFUSE) {
goRecruitJoin() goRecruitJoin()
} else if (type == 'RETREAT') { } else if (type == DISTRIBUTION_STATUS.RETREAT) {
uni.showToast({ uni.showToast({
title: '您的分销资格已被清退。请联系管理员!', title: DISTRIBUTION_RETREAT_MESSAGE,
duration: 2000, duration: 2000,
icon: 'none', icon: 'none',
}) })

100
utils/distributionStatus.js Normal file
View File

@@ -0,0 +1,100 @@
/**
* 分销员资格状态(与后端 DistributionStatusEnum 对齐)。
*
* 入口按 distributionStatus 分支;能力页只有 PASS 才渲染业务。
* 有返回 ≠ 有效分销员。
*
* @author Mike
* @date 2026-09-09
*/
import { distribution } from '@/api/distribution'
export const DISTRIBUTION_STATUS = {
PASS: 'PASS',
APPLY: 'APPLY',
REFUSE: 'REFUSE',
RETREAT: 'RETREAT',
}
/** 全端统一清退文案 */
export const DISTRIBUTION_RETREAT_MESSAGE = '您的分销资格已被清退。请联系管理员!'
export const DISTRIBUTION_APPLY_MESSAGE = '您的信息正在审核'
export const DISTRIBUTION_NOT_MEMBER_MESSAGE = '您还不是分销员'
const JOIN_PATH = '/pages/mine/distribution/join'
const MINE_TAB = '/pages/tabbar/user/my'
const TOAST_DURATION = 2000
export function getDistributionStatus(distribution) {
return distribution && distribution.distributionStatus ? distribution.distributionStatus : null
}
/** 能力页:仅在职分销员可看业绩 / 提现 / 等级等 */
export function isActiveDistributor(distribution) {
return getDistributionStatus(distribution) === DISTRIBUTION_STATUS.PASS
}
function showStatusToast(title) {
uni.showToast({ title, duration: TOAST_DURATION, icon: 'none' })
}
function leaveCapabilityPage() {
setTimeout(() => {
const pages = typeof getCurrentPages === 'function' ? getCurrentPages() : []
if (pages.length > 1) {
uni.navigateBack({ delta: 1 })
} else {
uni.switchTab({ url: MINE_TAB })
}
}, TOAST_DURATION)
}
function goRecruitJoin() {
uni.redirectTo({
url: JOIN_PATH,
fail: () => {
uni.navigateTo({ url: JOIN_PATH })
},
})
}
/**
* 能力页非 PASS 提示并退出。RETREAT 用统一清退文案APPLY / REFUSE 与入口一致。
*/
export function leaveInactiveDistributor(member) {
const status = getDistributionStatus(member)
if (status === DISTRIBUTION_STATUS.RETREAT) {
showStatusToast(DISTRIBUTION_RETREAT_MESSAGE)
leaveCapabilityPage()
return
}
if (status === DISTRIBUTION_STATUS.REFUSE) {
goRecruitJoin()
return
}
if (status === DISTRIBUTION_STATUS.APPLY) {
showStatusToast(DISTRIBUTION_APPLY_MESSAGE)
leaveCapabilityPage()
return
}
showStatusToast(DISTRIBUTION_NOT_MEMBER_MESSAGE)
leaveCapabilityPage()
}
/**
* 能力页守卫:请求 GET /distribution仅 PASS 返回对象,否则提示并退出。
* @returns {Promise<object|null>}
*/
export function assertActiveDistributor() {
return distribution().then((res) => {
const result = res && res.data ? res.data.result : null
if (isActiveDistributor(result)) {
return result
}
leaveInactiveDistributor(result)
return null
})
}