feat(bonus): 奖金页面调整

This commit is contained in:
woody 2025-06-12 20:21:33 +08:00
parent f5cae88636
commit d98b9bd93c
8 changed files with 659 additions and 98 deletions

View File

@ -79,7 +79,7 @@ export default {
methods: { methods: {
async open() { async open() {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
if (this.areaTree.length === 0) { if (this.areaTree?.length === 0) {
await this.loadAreaTree() await this.loadAreaTree()
} }
this.popupVisible = true this.popupVisible = true

View File

@ -20,3 +20,7 @@ export const nextRound = params =>
export const realTimeBonus = () => export const realTimeBonus = () =>
http.post('/bonus/api/bonus/query-current-bonus') http.post('/bonus/api/bonus/query-current-bonus')
//奖金明细列表
export const queryBonusList = params =>
http.post('/bonus/api/bonus/query-bonus-first/', { params })

View File

@ -53,10 +53,6 @@ export const updateData = data =>
export const queryBonusDetail = (data, data1) => export const queryBonusDetail = (data, data1) =>
http.post('/bonus/api/bonus/query-bonus-detail/' + data1, data) http.post('/bonus/api/bonus/query-bonus-detail/' + data1, data)
//首购收益
export const queryBonusFirst = (data, data1) =>
http.post('/bonus/api/bonus/query-bonus-first/' + data1, data)
//复购收益 //复购收益
export const queryBonusRepurchase = (data, data1) => export const queryBonusRepurchase = (data, data1) =>
http.post('/bonus/api/bonus/query-bonus-repurchase/' + data1, data) http.post('/bonus/api/bonus/query-bonus-repurchase/' + data1, data)

View File

@ -302,6 +302,20 @@
"navigationBarBackgroundColor": "#fff" "navigationBarBackgroundColor": "#fff"
} }
}, },
{
"path": "pages/mine/marketDynamic/achievement-list",
"style": {
"navigationBarTitleText": "市场动态",
"navigationBarBackgroundColor": "#fff"
}
},
{
"path": "pages/mine/marketDynamic/box-list",
"style": {
"navigationBarTitleText": "市场盒数",
"navigationBarBackgroundColor": "#fff"
}
},
{ {
"path": "pages/shareRegist/success", "path": "pages/shareRegist/success",
"style": { "style": {

View File

@ -5,9 +5,7 @@
<view class="summary-bar"> <view class="summary-bar">
<text class="summary-text" <text class="summary-text"
>今日实发合计: >今日实发合计:
<text class="summary-amount">{{ <text class="summary-amount">{{ todayTotal }}</text></text
formatAmount(todayTotal)
}}</text></text
> >
</view> </view>
@ -58,16 +56,12 @@
class="bonus-item" class="bonus-item"
> >
<text class="item-label">{{ fieldName }}(¥)</text> <text class="item-label">{{ fieldName }}(¥)</text>
<text class="item-value">{{ <text class="item-value">{{ dailyBonus[fieldKey] }}</text>
formatAmount(dailyBonus[fieldKey])
}}</text>
</view> </view>
</view> </view>
<view class="card-footer"> <view class="card-footer">
<text class="subtotal-label">小计(¥)</text> <text class="subtotal-label">小计(¥)</text>
<text class="subtotal-value">{{ <text class="subtotal-value">{{ dailyBonus.subtotal }}</text>
formatAmount(dailyBonus.subtotal)
}}</text>
</view> </view>
</view> </view>
</template> </template>
@ -100,7 +94,7 @@
// : API // : API
// import { getBonusDetailsByDate, getTodayBonusTotal } from '@/config/bonus.js'; // import { getBonusDetailsByDate, getTodayBonusTotal } from '@/config/bonus.js';
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { queryBonusTotal, queryBonusList } from '@/config/bonus'
export default { export default {
data() { data() {
return { return {
@ -119,26 +113,21 @@ export default {
pageSize: 10, pageSize: 10,
}, },
BONUS_FIELD_MAP: { BONUS_FIELD_MAP: {
levelGapIncome: '直推收益', retailRangeIncome: '直推收益',
peerIncome: '平级收益', retailSameLevelIncome: '平级收益',
regionalIncome: '区域收益', retailAreaIncome: '区域收益',
welfareLevelGapIncome: '福利级差收益', // welfareLevelGapIncome: '',
welfareDividendIncome: '福利分红收益', // welfareDividendIncome: '',
repeatConsumptionIncome: '重消收益', backPoints: '重消收益',
}, },
} }
}, },
onLoad() { onLoad() {
this.setDefaultDateRange() this.setDefaultDateRange()
this.handleSearch() this.handleSearch()
this.getBonusTotal()
}, },
methods: { methods: {
formatAmount(amount) {
if (typeof amount !== 'number') {
return '0.00'
}
return amount.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
},
setDefaultDateRange() { setDefaultDateRange() {
const end = new Date() const end = new Date()
const start = new Date() const start = new Date()
@ -189,80 +178,34 @@ export default {
this.page.pageNum++ this.page.pageNum++
this.fetchBonusData() this.fetchBonusData()
}, },
// getBonusTotal() {
queryBonusTotal().then(res => {
this.todayTotal = res.data.todayTotal
})
},
async fetchBonusData() { async fetchBonusData() {
if (this.loading) return if (this.loading) return
this.loading = true this.loading = true
// API try {
await new Promise(resolve => setTimeout(resolve, 800)) const params = {
startDate: this.startDate,
// TODO: API endDate: this.endDate,
// : pageNum: this.page.pageNum,
// try { pageSize: this.page.pageSize,
// const params = { startDate: this.startDate, endDate: this.endDate, pageNum: this.page.pageNum, pageSize: this.page.pageSize };
// const res = await getBonusDetailsByDate(params);
// if (res.data.list.length < this.page.pageSize) {
// this.hasMore = false;
// }
// this.bonusList = [...this.bonusList, ...res.data.list];
// } catch (error) {
// console.error("Failed to fetch bonus data:", error);
// uni.showToast({ title: '', icon: 'none' });
// } finally {
// this.loading = false;
// }
// --- ---
this.todayTotal = Math.random() * 1000
const mockData = []
// ""
if (this.page.pageNum > 3) {
this.hasMore = false
this.loading = false
return
}
if (this.startDate && this.endDate) {
//
for (let i = 0; i < this.page.pageSize; i++) {
const dayOffset = (this.page.pageNum - 1) * this.page.pageSize + i
const date = dayjs(this.endDate).subtract(dayOffset, 'day')
if (date.isBefore(dayjs(this.startDate))) {
this.hasMore = false
break
}
const bonusDetails = {
levelGapIncome: Math.random() * 100,
peerIncome: Math.random() * 200,
regionalIncome: 0,
welfareLevelGapIncome: 0,
welfareDividendIncome: 0,
repeatConsumptionIncome: Math.random() * 50,
}
const subtotal = Object.values(bonusDetails).reduce(
(sum, value) => sum + value,
0
)
mockData.push({
date: date.format('YYYY-MM-DD'),
...bonusDetails,
subtotal: subtotal,
})
} }
const res = await queryBonusList(params)
console.log(res)
if (res.rows.length < this.page.pageSize) {
this.hasMore = false
}
this.bonusList = [...this.bonusList, ...res.rows]
} catch (error) {
console.error('Failed to fetch bonus data:', error)
uni.showToast({ title: '数据加载失败', icon: 'none' })
} finally {
this.loading = false
} }
if (mockData.length < this.page.pageSize) {
this.hasMore = false
}
this.bonusList = [...this.bonusList, ...mockData]
// --- ---
this.loading = false
}, },
}, },
} }

View File

@ -269,7 +269,7 @@
<view class="market-stats-container"> <view class="market-stats-container">
<view <view
class="stat-block primary" class="stat-block primary"
@click="goTo('/pages/performanceEchart/index')" @click="goTo('/pages/mine/marketDynamic/achievement-list')"
> >
<view class="stat-content"> <view class="stat-content">
<view class="stat-item"> <view class="stat-item">
@ -302,7 +302,7 @@
</view> </view>
<view <view
class="stat-block secondary" class="stat-block secondary"
@click="goTo('/pages/mine/order/index')" @click="goTo('/pages/mine/marketDynamic/box-list')"
> >
<view class="stat-content"> <view class="stat-content">
<view class="stat-item"> <view class="stat-item">
@ -746,7 +746,6 @@ export default {
} }
}, },
getMarketDynamicBoxCount() { getMarketDynamicBoxCount() {
console.log('🌈ad', this.userInfo)
getMarketDynamicBoxCount({ getMarketDynamicBoxCount({
pkBigMember: this.userInfo.memberCode, pkBigMember: this.userInfo.memberCode,
}).then(res => { }).then(res => {

View File

@ -0,0 +1,301 @@
<template>
<view class="container">
<!-- <view class="search-bar">
<u-search
placeholder="请输入会员编号或姓名查询"
v-model="keyword"
@custom="search"
@search="search"
></u-search>
</view> -->
<!-- 特殊会员信息 -->
<view class="top-member-card" v-if="topMember">
<view class="member-info-header">
<text>会员信息</text>
<text class="member-info-text">{{ formatMemberInfo(topMember) }}</text>
</view>
<view class="stats-grid">
<view class="stat-item">
<text class="stat-value">{{ topMember.todayAchievement }}</text>
<text class="stat-label">今日业绩</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ topMember.yesterdayAchievement }}</text>
<text class="stat-label">昨日业绩</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ topMember.monthAchievement }}</text>
<text class="stat-label">本月业绩</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ topMember.lastMonthAchievement }}</text>
<text class="stat-label">上月业绩</text>
</view>
</view>
</view>
<!-- 列表 -->
<view class="member-list">
<view class="member-item" v-for="item in memberList" :key="item.memberId">
<view class="member-info-header list-header">
<text>会员信息</text>
<text>{{ formatMemberInfo(item) }}</text>
</view>
<view class="stats-grid list-grid">
<view class="stat-item">
<text class="stat-value">{{ item.todayAchievement }}</text>
<text class="stat-label">今日业绩</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ item.yesterdayAchievement }}</text>
<text class="stat-label">昨日业绩</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ item.monthAchievement }}</text>
<text class="stat-label">本月业绩</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ item.lastMonthAchievement }}</text>
<text class="stat-label">上月业绩</text>
</view>
</view>
</view>
</view>
<u-loadmore :status="loadStatus" />
</view>
</template>
<script>
// API
// import { getMarketAchievementTop, getMarketAchievementList } from '@/config/mine.js';
export default {
data() {
return {
keyword: '',
topMember: null, //
memberList: [], //
page: 1,
pageSize: 10,
loadStatus: 'loadmore', // loadmore, loading, nomore
}
},
onLoad() {
uni.setNavigationBarTitle({
title: '市场动态-业绩',
})
this.fetchTopMember()
this.fetchMemberList(true)
},
onReachBottom() {
if (this.loadStatus === 'nomore' || this.loadStatus === 'loading') {
return
}
this.page++
this.fetchMemberList()
},
methods: {
formatMemberInfo(member) {
if (!member) return ''
const { memberCode, nickName, grade, award } = member
return `${memberCode}/${nickName}/${grade}/${award}`
},
search() {
this.memberList = []
this.page = 1
this.fetchTopMember()
this.fetchMemberList(true)
},
//
async fetchTopMember() {
// --- MOCK DATA ---
this.topMember = {
memberId: 'HZS32223336', // Using memberId as key
memberCode: 'HZS32223336',
nickName: '小新哥',
grade: 'V5',
award: 'W1',
todayAchievement: 0.0,
yesterdayAchievement: 1.0978,
monthAchievement: 2.3395,
lastMonthAchievement: 10.7727,
}
// --- REAL API CALL ---
// try {
// const res = await getMarketAchievementTop({ keyword: this.keyword });
// if (res.code === 200) {
// this.topMember = res.data;
// }
// } catch (e) {
// console.error(e);
// }
},
//
async fetchMemberList(isRefresh = false) {
if (isRefresh) {
this.page = 1
this.memberList = []
}
this.loadStatus = 'loading'
// --- MOCK DATA ---
const mockData = [
{
memberId: 'HZS81617255',
memberCode: 'HZS81617255',
nickName: '小星哥',
grade: 'V5',
award: 'W1',
todayAchievement: 0.0,
yesterdayAchievement: 0.0499,
monthAchievement: 1.2881,
lastMonthAchievement: 26.3515,
},
{
memberId: 'HZS93817578',
memberCode: 'HZS93817578',
nickName: '婉琳',
grade: 'V5',
award: 'W1',
todayAchievement: 0.0,
yesterdayAchievement: 0.0,
monthAchievement: 0.508,
lastMonthAchievement: 2.422,
},
{
memberId: 'HZS55865236',
memberCode: 'HZS55865236',
nickName: '谭谭',
grade: 'V5',
award: 'W1',
todayAchievement: 0.0,
yesterdayAchievement: 0.0,
monthAchievement: 0.4261,
lastMonthAchievement: 8.5454,
},
{
memberId: 'HZS67896897',
memberCode: 'HZS67896897',
nickName: '楠杉',
grade: 'V5',
award: 'W1',
todayAchievement: 0.0,
yesterdayAchievement: 0.0,
monthAchievement: 0.0,
lastMonthAchievement: 0.0,
},
]
// Simulate network delay and pagination
setTimeout(() => {
// Mock: stop after 2 pages
if (this.page > 2) {
this.loadStatus = 'nomore'
return
}
const moreData = mockData.map(item => ({
...item,
memberId: item.memberId + this.page, // unique key for v-for
}))
this.memberList = [...this.memberList, ...moreData]
// After adding data, check if we should be able to load more
this.loadStatus = this.page >= 2 ? 'nomore' : 'loadmore'
}, 500)
// --- REAL API CALL ---
// try {
// const res = await getMarketAchievementList({ page: this.page, size: this.pageSize, keyword: this.keyword });
// if (res.code === 200) {
// this.memberList = [...this.memberList, ...res.data.records];
// if (res.data.records.length < this.pageSize) {
// this.loadStatus = 'nomore';
// } else {
// this.loadStatus = 'loadmore';
// }
// }
// } catch(e) {
// this.loadStatus = 'loadmore';
// console.error(e);
// }
},
},
}
</script>
<style lang="scss" scoped>
.container {
padding: 20rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.search-bar {
margin-bottom: 20rpx;
}
.top-member-card {
background: linear-gradient(135deg, #005bac, #007bff);
border-radius: 16rpx;
padding: 20rpx;
color: #ffffff;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 91, 172, 0.3);
}
.member-info-header {
font-size: 28rpx;
margin-bottom: 20rpx;
.member-info-text {
font-weight: bold;
}
}
.stats-grid {
display: flex;
justify-content: space-around;
text-align: center;
}
.stat-item {
display: flex;
flex-direction: column;
gap: 10rpx;
}
.stat-value {
font-size: 32rpx;
font-weight: bold;
}
.stat-label {
font-size: 24rpx;
opacity: 0.9;
}
.member-list {
.member-item {
background: #ffffff;
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
.list-header {
color: #333;
font-size: 26rpx;
}
.list-grid {
.stat-value {
color: #333;
}
.stat-label {
color: #666;
}
}
}
}
</style>

View File

@ -0,0 +1,304 @@
<template>
<view class="container">
<!-- <view class="search-bar">
<u-search
placeholder="请输入会员编号或姓名查询"
v-model="keyword"
@custom="search"
@search="search"
></u-search>
</view> -->
<!-- 特殊会员信息 -->
<view class="top-member-card" v-if="topMember">
<view class="member-info-header">
<text>会员信息</text>
<text class="member-info-text">{{ formatMemberInfo(topMember) }}</text>
</view>
<view class="stats-grid">
<view class="stat-item">
<text class="stat-value">{{ toInt(topMember.todayBox) }}</text>
<text class="stat-label">今日盒数</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ topMember.yesterdayBox }}</text>
<text class="stat-label">昨日盒数</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ topMember.monthBox }}</text>
<text class="stat-label">本月盒数</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ topMember.lastMonthBox }}</text>
<text class="stat-label">上月盒数</text>
</view>
</view>
</view>
<!-- 列表 -->
<view class="member-list">
<view class="member-item" v-for="item in memberList" :key="item.memberId">
<view class="member-info-header list-header">
<text>会员信息</text>
<text>{{ formatMemberInfo(item) }}</text>
</view>
<view class="stats-grid list-grid">
<view class="stat-item">
<text class="stat-value">{{ toInt(item.todayBox) }}</text>
<text class="stat-label">今日盒数</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ item.yesterdayBox }}</text>
<text class="stat-label">昨日盒数</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ item.monthBox }}</text>
<text class="stat-label">本月盒数</text>
</view>
<view class="stat-item">
<text class="stat-value">{{ item.lastMonthBox }}</text>
<text class="stat-label">上月盒数</text>
</view>
</view>
</view>
</view>
<u-loadmore :status="loadStatus" />
</view>
</template>
<script>
// API
// import { getMarketBoxTop, getMarketBoxList } from '@/config/mine.js';
export default {
data() {
return {
keyword: '',
topMember: null, //
memberList: [], //
page: 1,
pageSize: 10,
loadStatus: 'loadmore', // loadmore, loading, nomore
}
},
onLoad() {
this.fetchTopMember()
this.fetchMemberList(true)
},
onReachBottom() {
if (this.loadStatus === 'nomore' || this.loadStatus === 'loading') {
return
}
this.page++
this.fetchMemberList()
},
methods: {
toInt(value) {
const intValue = parseInt(value, 10)
return isNaN(intValue) ? value : intValue
},
formatMemberInfo(member) {
if (!member) return ''
const { memberCode, nickName, grade, award } = member
return `${memberCode}/${nickName || '未知'}/${grade}/${award}`
},
search() {
this.memberList = []
this.page = 1
this.fetchTopMember()
this.fetchMemberList(true)
},
//
async fetchTopMember() {
// --- MOCK DATA from screenshot ---
this.topMember = {
memberId: 'HZS55197913',
memberCode: 'HZS55197913',
nickName: '未知', // Screenshot has no name, API might
grade: 'V5',
award: 'S2',
todayBox: 3361.0,
yesterdayBox: 114,
monthBox: 3475,
lastMonthBox: 3206,
}
// --- REAL API CALL ---
// try {
// const res = await getMarketBoxTop({ keyword: this.keyword });
// if (res.code === 200) {
// // Real data mapping from fields like realizedBox, upMonthBox
// this.topMember = { ...res.data, todayBox: res.data.realizedBox, lastMonthBox: res.data.upMonthBox };
// }
// } catch (e) {
// console.error(e);
// }
},
//
async fetchMemberList(isRefresh = false) {
if (isRefresh) {
this.page = 1
this.memberList = []
}
this.loadStatus = 'loading'
// --- MOCK DATA from screenshot ---
const mockData = [
{
memberId: 'HZS931295626',
memberCode: 'HZS931295626',
nickName: '未知',
grade: 'V5',
award: 'S1',
todayBox: 38.0,
yesterdayBox: 0,
monthBox: 38,
lastMonthBox: 503,
},
{
memberId: 'HZS938173516',
memberCode: 'HZS938173516',
nickName: '未知',
grade: 'V3',
award: 'S0',
todayBox: 25.0,
yesterdayBox: 0,
monthBox: 25,
lastMonthBox: 0,
},
{
memberId: 'HZS61632161',
memberCode: 'HZS61632161',
nickName: '未知',
grade: 'V3',
award: 'S0',
todayBox: 16.0,
yesterdayBox: 0,
monthBox: 16,
lastMonthBox: 0,
},
{
memberId: 'HZS978982363',
memberCode: 'HZS978982363',
nickName: '未知',
grade: 'V3',
award: 'S0',
todayBox: 15.0,
yesterdayBox: 0,
monthBox: 15,
lastMonthBox: 0,
},
]
// Simulate network delay and pagination
setTimeout(() => {
// Mock: stop after 2 pages
if (this.page > 2) {
this.loadStatus = 'nomore'
return
}
const moreData = mockData.map(item => ({
...item,
memberId: item.memberId + this.page, // unique key for v-for
}))
this.memberList = [...this.memberList, ...moreData]
// After adding data, check if we should be able to load more
this.loadStatus = this.page >= 2 ? 'nomore' : 'loadmore'
}, 500)
// --- REAL API CALL ---
// try {
// const res = await getMarketBoxList({ page: this.page, size: this.pageSize, keyword: this.keyword });
// if (res.code === 200) {
// const newList = res.data.records.map(item => ({ ...item, todayBox: item.realizedBox, lastMonthBox: item.upMonthBox }));
// this.memberList = [...this.memberList, ...newList];
// if (res.data.records.length < this.pageSize) {
// this.loadStatus = 'nomore';
// } else {
// this.loadStatus = 'loadmore';
// }
// }
// } catch(e) {
// this.loadStatus = 'loadmore';
// console.error(e);
// }
},
},
}
</script>
<style lang="scss" scoped>
.container {
padding: 20rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.search-bar {
margin-bottom: 20rpx;
}
.top-member-card {
background: linear-gradient(135deg, #005bac, #007bff);
border-radius: 16rpx;
padding: 20rpx;
color: #ffffff;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 91, 172, 0.3);
}
.member-info-header {
font-size: 28rpx;
margin-bottom: 20rpx;
.member-info-text {
font-weight: bold;
}
}
.stats-grid {
display: flex;
justify-content: space-around;
text-align: center;
}
.stat-item {
display: flex;
flex-direction: column;
gap: 10rpx;
}
.stat-value {
font-size: 32rpx;
font-weight: bold;
}
.stat-label {
font-size: 24rpx;
opacity: 0.9;
}
.member-list {
.member-item {
background: #ffffff;
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
.list-header {
color: #333;
font-size: 26rpx;
}
.list-grid {
.stat-value {
color: #333;
}
.stat-label {
color: #666;
}
}
}
}
</style>