20260929
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user