383 lines
8.2 KiB
Vue
383 lines
8.2 KiB
Vue
<template>
|
||
<view class="points-record-page">
|
||
<!-- 积分总览 -->
|
||
<view class="points-header">
|
||
<view class="points-total">
|
||
<view class="label">当前积分</view>
|
||
<view class="amount">{{ totalPoints }}</view>
|
||
</view>
|
||
<view class="points-stats">
|
||
<view class="stat-item">
|
||
<view class="value">{{ earnedPoints }}</view>
|
||
<view class="label">累计获得</view>
|
||
</view>
|
||
<view class="stat-item">
|
||
<view class="value">{{ usedPoints }}</view>
|
||
<view class="label">累计使用</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 筛选栏 -->
|
||
<view class="filter-bar">
|
||
<view class="filter-tabs">
|
||
<view
|
||
class="tab-item"
|
||
:class="{ active: currentType === item.value }"
|
||
v-for="item in typeList"
|
||
:key="item.value"
|
||
@click="changeType(item.value)"
|
||
>
|
||
{{ item.label }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 积分记录列表 -->
|
||
<view class="record-list">
|
||
<view class="record-item" v-for="item in list" :key="item.id">
|
||
<view class="item-left">
|
||
<view class="item-icon" :class="item.type === 'earn' ? 'icon-earn' : 'icon-use'">
|
||
<text class="iconfont" :class="item.type === 'earn' ? 'icon-add' : 'icon-minus'"></text>
|
||
</view>
|
||
<view class="item-info">
|
||
<view class="item-title">{{ item.description }}</view>
|
||
<view class="item-time">{{ item.createTime }}</view>
|
||
</view>
|
||
</view>
|
||
<view class="item-right">
|
||
<view class="item-points" :class="item.type === 'earn' ? 'earn' : 'use'">
|
||
{{ item.type === 'earn' ? '+' : '-' }}{{ item.points }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 加载更多 -->
|
||
<view class="load-more" v-if="hasMore">
|
||
<text v-if="loading">加载<E58AA0><E8BDBD>?..</text>
|
||
<text v-else @click="loadMore">加载更多</text>
|
||
</view>
|
||
|
||
<!-- 没有更多 -->
|
||
<view class="no-more" v-if="!hasMore && list.length > 0">
|
||
<text>没有更多<E69BB4><E5A49A>?/text>
|
||
</view>
|
||
|
||
<!-- 空状<E7A9BA><E78AB6>?-->
|
||
<view class="empty" v-if="list.length === 0 && !loading">
|
||
<image class="empty-img" src="/static/images/empty-points.png" mode="aspectFit"></image>
|
||
<text class="empty-text">暂无积分记录</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 积分规则 -->
|
||
<view class="rules-btn" @click="showRules">
|
||
<text class="iconfont icon-help"></text>
|
||
<text>积分规则</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { pointsApi } from '@/api/index.js'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
totalPoints: 0,
|
||
earnedPoints: 0,
|
||
usedPoints: 0,
|
||
currentType: 'all',
|
||
typeList: [
|
||
{ label: '全部', value: 'all' },
|
||
{ label: '获得', value: 'earn' },
|
||
{ label: '使用', value: 'use' }
|
||
],
|
||
list: [],
|
||
page: 1,
|
||
pageSize: 20,
|
||
hasMore: true,
|
||
loading: false
|
||
}
|
||
},
|
||
onLoad() {
|
||
this.loadPointsInfo()
|
||
this.loadList()
|
||
},
|
||
onReachBottom() {
|
||
if (this.hasMore && !this.loading) {
|
||
this.loadMore()
|
||
}
|
||
},
|
||
onPullDownRefresh() {
|
||
this.loadPointsInfo()
|
||
this.page = 1
|
||
this.list = []
|
||
this.hasMore = true
|
||
this.loadList().then(() => {
|
||
uni.stopPullDownRefresh()
|
||
})
|
||
},
|
||
methods: {
|
||
async loadPointsInfo() {
|
||
try {
|
||
const res = await pointsApi.getPointsInfo()
|
||
this.totalPoints = res.data.totalPoints || 0
|
||
this.earnedPoints = res.data.earnedPoints || 0
|
||
this.usedPoints = res.data.usedPoints || 0
|
||
} catch (e) {
|
||
console.error('加载积分信息失败', e)
|
||
}
|
||
},
|
||
async loadList() {
|
||
if (this.loading) return
|
||
|
||
this.loading = true
|
||
try {
|
||
const res = await pointsApi.getPointsRecords({
|
||
type: this.currentType === 'all' ? '' : this.currentType,
|
||
page: this.page,
|
||
pageSize: this.pageSize
|
||
})
|
||
|
||
if (this.page === 1) {
|
||
this.list = res.data.records || []
|
||
} else {
|
||
this.list = [...this.list, ...(res.data.records || [])]
|
||
}
|
||
|
||
this.hasMore = this.list.length < res.data.total
|
||
} catch (e) {
|
||
console.error('加载积分记录失败', e)
|
||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
changeType(type) {
|
||
if (this.currentType === type) return
|
||
this.currentType = type
|
||
this.page = 1
|
||
this.list = []
|
||
this.hasMore = true
|
||
this.loadList()
|
||
},
|
||
loadMore() {
|
||
this.page++
|
||
this.loadList()
|
||
},
|
||
showRules() {
|
||
uni.navigateTo({
|
||
url: '/pages/points/rules'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.points-record-page {
|
||
min-height: 100vh;
|
||
background: #f0f9f7;
|
||
padding-bottom: 100rpx;
|
||
}
|
||
|
||
.points-header {
|
||
background: linear-gradient(135deg, #5fc9ba 0%, #7dd3c0 100%);
|
||
padding: 60rpx 40rpx;
|
||
color: #fff;
|
||
|
||
.points-total {
|
||
text-align: center;
|
||
margin-bottom: 40rpx;
|
||
|
||
.label {
|
||
font-size: 28rpx;
|
||
opacity: 0.9;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.amount {
|
||
font-size: 80rpx;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
|
||
.points-stats {
|
||
display: flex;
|
||
justify-content: space-around;
|
||
|
||
.stat-item {
|
||
text-align: center;
|
||
|
||
.value {
|
||
font-size: 40rpx;
|
||
font-weight: bold;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.label {
|
||
font-size: 24rpx;
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.filter-bar {
|
||
background: #fff;
|
||
padding: 20rpx;
|
||
|
||
.filter-tabs {
|
||
display: flex;
|
||
justify-content: space-around;
|
||
|
||
.tab-item {
|
||
flex: 1;
|
||
text-align: center;
|
||
padding: 20rpx 0;
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
position: relative;
|
||
|
||
&.active {
|
||
color: #5fc9ba;
|
||
font-weight: bold;
|
||
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 60rpx;
|
||
height: 4rpx;
|
||
background: #5fc9ba;
|
||
border-radius: 2rpx;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.record-list {
|
||
padding: 20rpx;
|
||
|
||
.record-item {
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 30rpx;
|
||
margin-bottom: 20rpx;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
.item-left {
|
||
display: flex;
|
||
align-items: center;
|
||
flex: 1;
|
||
|
||
.item-icon {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 20rpx;
|
||
|
||
&.icon-earn {
|
||
background: #e8f5e9;
|
||
color: #4caf50;
|
||
}
|
||
|
||
&.icon-use {
|
||
background: #ffebee;
|
||
color: #f44336;
|
||
}
|
||
|
||
.iconfont {
|
||
font-size: 40rpx;
|
||
}
|
||
}
|
||
|
||
.item-info {
|
||
flex: 1;
|
||
|
||
.item-title {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.item-time {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
}
|
||
|
||
.item-right {
|
||
.item-points {
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
|
||
&.earn {
|
||
color: #4caf50;
|
||
}
|
||
|
||
&.use {
|
||
color: #f44336;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.load-more, .no-more {
|
||
text-align: center;
|
||
padding: 40rpx 0;
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.empty {
|
||
text-align: center;
|
||
padding: 200rpx 0;
|
||
|
||
.empty-img {
|
||
width: 300rpx;
|
||
height: 300rpx;
|
||
margin-bottom: 40rpx;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
}
|
||
|
||
.rules-btn {
|
||
position: fixed;
|
||
bottom: 40rpx;
|
||
right: 40rpx;
|
||
background: #5fc9ba;
|
||
color: #fff;
|
||
padding: 20rpx 40rpx;
|
||
border-radius: 50rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
box-shadow: 0 4rpx 20rpx rgba(45, 150, 135, 0.3);
|
||
|
||
.iconfont {
|
||
font-size: 32rpx;
|
||
margin-right: 10rpx;
|
||
}
|
||
|
||
text {
|
||
font-size: 28rpx;
|
||
}
|
||
}
|
||
</style>
|
||
|