346 lines
8.3 KiB
Vue
346 lines
8.3 KiB
Vue
<template>
|
|
<view class="points-page">
|
|
<view class="page-hero">
|
|
<AppNavbar title="我的积分" show-back/>
|
|
<view class="intro-card">
|
|
<view class="card-title">
|
|
<view class="title-bar"></view>
|
|
<text>我的积分</text>
|
|
</view>
|
|
<text class="intro-text">查看积分余额和积分明细记录。</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="card summary-card">
|
|
<view class="summary-item">
|
|
<view class="summary-value">{{ summary.currentPoints }}</view>
|
|
<view class="summary-label">当前积分</view>
|
|
</view>
|
|
<view class="summary-item">
|
|
<view class="summary-value">{{ summary.totalEarned }}</view>
|
|
<view class="summary-label">累计获得</view>
|
|
</view>
|
|
<!-- <view class="summary-item">-->
|
|
<!-- <view class="summary-value">{{ summary.totalUsed }}</view>-->
|
|
<!-- <view class="summary-label">累计消耗</view>-->
|
|
<!-- </view>-->
|
|
</view>
|
|
|
|
<view class="card block">
|
|
<view class="section-title">积分明细</view>
|
|
<view v-if="loading && !records.length" class="empty">加载中...</view>
|
|
<view v-else-if="!records.length" class="empty">暂无积分记录</view>
|
|
<view v-else class="record-list">
|
|
<view class="record-item" v-for="item in records" :key="item.id">
|
|
<view class="record-main">
|
|
<view class="record-title-row">
|
|
<view class="record-title">{{ item.title }}</view>
|
|
<view :class="['record-points', item.delta >= 0 ? 'record-points--plus' : 'record-points--minus']">
|
|
{{ item.deltaText }}
|
|
</view>
|
|
</view>
|
|
<view class="record-meta">{{ dayjs(item.date).format('YYYY-MM-DD HH:mm:ss') }}</view>
|
|
<view class="record-desc" v-if="item.remark">{{ item.remark }}</view>
|
|
</view>
|
|
</view>
|
|
<view class="list-status">{{ finished ? '没有更多了' : '上拉加载更多' }}</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import AppNavbar from "@/components/AppNavbar.vue";
|
|
import {userPointLogUserListReq} from "@/api/shop";
|
|
import {userInfoReq} from "@/api/user";
|
|
import dayjs from 'dayjs'
|
|
|
|
export default {
|
|
components: {AppNavbar},
|
|
data() {
|
|
return {
|
|
loading: false,
|
|
page: 1,
|
|
limit: 20,
|
|
finished: false,
|
|
records: [],
|
|
user: {},
|
|
summary: {
|
|
currentPoints: 0,
|
|
totalEarned: 0,
|
|
totalUsed: 0
|
|
}
|
|
}
|
|
},
|
|
onShow() {
|
|
this.refreshData()
|
|
},
|
|
onPullDownRefresh() {
|
|
this.refreshData().finally(() => {
|
|
uni.stopPullDownRefresh()
|
|
})
|
|
},
|
|
onReachBottom() {
|
|
if (this.loading || this.finished) return
|
|
this.fetchPointLogs()
|
|
},
|
|
methods: {
|
|
dayjs,
|
|
async refreshData() {
|
|
this.page = 1
|
|
this.finished = false
|
|
this.records = []
|
|
await Promise.all([
|
|
this.getUserData(),
|
|
this.fetchPointLogs(true)
|
|
])
|
|
},
|
|
async getUserData() {
|
|
try {
|
|
const {data} = await userInfoReq()
|
|
this.user = data || {}
|
|
this.summary.currentPoints = this.resolveSummaryPoints()
|
|
} catch (error) {
|
|
this.user = {}
|
|
}
|
|
},
|
|
async fetchPointLogs(refresh = false) {
|
|
this.loading = true
|
|
try {
|
|
const currentPage = refresh ? 1 : this.page
|
|
const {data} = await userPointLogUserListReq({
|
|
page: currentPage,
|
|
limit: this.limit
|
|
})
|
|
const rawList = Array.isArray(data)
|
|
? data
|
|
: (data && Array.isArray(data.list) ? data.list : [])
|
|
const normalizedList = rawList.map(this.normalizeRecord)
|
|
this.records = refresh ? normalizedList : this.records.concat(normalizedList)
|
|
this.page = currentPage + 1
|
|
this.finished = !rawList.length || rawList.length < this.limit
|
|
this.updateSummary(data)
|
|
} catch (error) {
|
|
if (refresh) {
|
|
this.records = []
|
|
}
|
|
uni.showToast({
|
|
title: '积分加载失败',
|
|
icon: 'none'
|
|
})
|
|
} finally {
|
|
this.loading = false
|
|
}
|
|
},
|
|
updateSummary(payload) {
|
|
const totalEarned = this.records
|
|
.filter(item => item.delta > 0)
|
|
.reduce((sum, item) => sum + item.delta, 0)
|
|
const totalUsed = this.records
|
|
.filter(item => item.delta < 0)
|
|
.reduce((sum, item) => sum + Math.abs(item.delta), 0)
|
|
this.summary = {
|
|
currentPoints: this.resolveSummaryPoints(payload, this.records),
|
|
totalEarned,
|
|
totalUsed
|
|
}
|
|
},
|
|
resolveSummaryPoints(payload = {}, records = this.records) {
|
|
const candidates = [
|
|
payload.currentPoints,
|
|
payload.currentPoint,
|
|
payload.points,
|
|
payload.point,
|
|
payload.totalPoints,
|
|
payload.totalPoint,
|
|
this.user.currentPoints,
|
|
this.user.currentPoint,
|
|
this.user.points,
|
|
this.user.point,
|
|
this.user.integral,
|
|
this.user.score
|
|
]
|
|
for (let i = 0; i < candidates.length; i++) {
|
|
const value = Number(candidates[i])
|
|
if (Number.isFinite(value)) return value
|
|
}
|
|
return records.reduce((sum, item) => sum + item.delta, 0)
|
|
},
|
|
resolveDelta(item = {}) {
|
|
const candidates = [
|
|
item.changePoint,
|
|
item.changePoints,
|
|
item.changeNum,
|
|
item.delta,
|
|
item.num,
|
|
item.amount,
|
|
item.value,
|
|
item.point,
|
|
item.points,
|
|
item.integral,
|
|
item.score
|
|
]
|
|
for (let i = 0; i < candidates.length; i++) {
|
|
const value = Number(candidates[i])
|
|
if (Number.isFinite(value)) return value
|
|
}
|
|
return 0
|
|
},
|
|
normalizeRecord(item = {}) {
|
|
const delta = this.resolveDelta(item)
|
|
return {
|
|
id: item.id || item.logId || item.userPointLogId || `${item.createTime || item.createdAt || ''}-${delta}`,
|
|
title: item.title || item.typeName || item.bizTypeName || item.remark || '积分变动',
|
|
remark: item.description || item.content || item.memo || '',
|
|
date: item.createTime || item.createdAt || item.date || '-',
|
|
delta,
|
|
deltaText: `${delta > 0 ? '+' : ''}${delta}`
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.points-page {
|
|
min-height: 100vh;
|
|
background: #f5f5f5;
|
|
}
|
|
|
|
.page-hero {
|
|
background: url('https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/462bfa04cf0eff8698884c38b9d17519.png') no-repeat top center / 100% auto;
|
|
}
|
|
|
|
.intro-card {
|
|
margin: 18rpx 20rpx 0;
|
|
padding: 20rpx 18rpx;
|
|
border-radius: 20rpx;
|
|
background: rgba(255, 255, 255, 0.9);
|
|
box-shadow: 0 10rpx 20rpx rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
.card-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12rpx;
|
|
font-size: 28rpx;
|
|
font-weight: 700;
|
|
color: #1f2937;
|
|
}
|
|
|
|
.title-bar {
|
|
width: 6rpx;
|
|
height: 30rpx;
|
|
border-radius: 999rpx;
|
|
background: #2fc6a7;
|
|
}
|
|
|
|
.intro-text {
|
|
margin-top: 12rpx;
|
|
font-size: 26rpx;
|
|
color: #6b7280;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.card {
|
|
margin: 18rpx 20rpx 0;
|
|
background: #ffffff;
|
|
border-radius: 20rpx;
|
|
padding: 20rpx 18rpx;
|
|
box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.06);
|
|
}
|
|
|
|
.summary-card {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 12rpx;
|
|
}
|
|
|
|
.summary-item {
|
|
padding: 18rpx 12rpx;
|
|
border-radius: 16rpx;
|
|
text-align: center;
|
|
background: linear-gradient(135deg, #f0fffb, #ffffff);
|
|
border: 1rpx solid #ccfbf1;
|
|
}
|
|
|
|
.summary-value {
|
|
font-size: 34rpx;
|
|
font-weight: 800;
|
|
color: #2fc6a7;
|
|
}
|
|
|
|
.summary-label {
|
|
margin-top: 8rpx;
|
|
font-size: 22rpx;
|
|
color: #6b7280;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 28rpx;
|
|
font-weight: 700;
|
|
color: #1f2937;
|
|
margin-bottom: 14rpx;
|
|
}
|
|
|
|
.record-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 14rpx;
|
|
}
|
|
|
|
.record-item {
|
|
padding: 18rpx 16rpx;
|
|
border-radius: 16rpx;
|
|
background: #f8fafc;
|
|
border: 1rpx solid #e5e7eb;
|
|
}
|
|
|
|
.record-title-row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12rpx;
|
|
}
|
|
|
|
.record-title {
|
|
font-size: 28rpx;
|
|
font-weight: 600;
|
|
color: #1f2937;
|
|
}
|
|
|
|
.record-points {
|
|
font-size: 30rpx;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.record-points--plus {
|
|
color: #16a34a;
|
|
}
|
|
|
|
.record-points--minus {
|
|
color: #ef4444;
|
|
}
|
|
|
|
.record-meta {
|
|
margin-top: 10rpx;
|
|
font-size: 22rpx;
|
|
color: #94a3b8;
|
|
}
|
|
|
|
.record-desc {
|
|
margin-top: 8rpx;
|
|
font-size: 24rpx;
|
|
color: #64748b;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.empty,
|
|
.list-status {
|
|
padding: 36rpx 0 18rpx;
|
|
text-align: center;
|
|
font-size: 24rpx;
|
|
color: #94a3b8;
|
|
}
|
|
</style>
|