vending-machine/mobile/pages/points/points.vue
2026-04-08 20:45:41 +08:00

220 lines
4.4 KiB
Vue

<template>
<view class="points-page">
<!-- Tab切换 -->
<view class="tab-bar">
<view
class="tab-item"
:class="{ active: currentTab === 'earn' }"
@click="switchTab('earn')"
>
<text class="tab-text">{{ t('points.earnTab') }}</text>
</view>
<view
class="tab-item"
:class="{ active: currentTab === 'spend' }"
@click="switchTab('spend')"
>
<text class="tab-text">{{ t('points.spendTab') }}</text>
</view>
</view>
<!-- 积分记录列表 -->
<scroll-view
class="record-list"
scroll-y
@scrolltolower="loadMore"
>
<view
v-for="record in records"
:key="record.id"
class="record-item"
>
<view class="record-info">
<text class="record-source">{{ record.source }}</text>
<text class="record-time">{{ formatTime(record.createdAt) }}</text>
</view>
<text
class="record-amount"
:class="{ earn: currentTab === 'earn', spend: currentTab === 'spend' }"
>
{{ currentTab === 'earn' ? '+' : '-' }}{{ record.amount }}
</text>
</view>
<!-- 加载状态 -->
<view v-if="loading" class="loading-tip">
<text>{{ t('common.loading') }}</text>
</view>
<view v-if="noMore && records.length > 0" class="loading-tip">
<text>{{ t('common.noMore') }}</text>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { getEarnRecords, getSpendRecords } from '../../api/points.js'
const { t } = useI18n()
const currentTab = ref('earn')
const records = ref([])
const page = ref(1)
const pageSize = 20
const loading = ref(false)
const noMore = ref(false)
// 格式化时间
function formatTime(dateStr) {
if (!dateStr) return ''
const d = new Date(dateStr)
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
}
// 加载记录
async function loadRecords(isLoadMore = false) {
if (loading.value) return
loading.value = true
try {
const fetchFn = currentTab.value === 'earn' ? getEarnRecords : getSpendRecords
const res = await fetchFn({ page: page.value, pageSize })
const list = res.data?.records || res.data || []
if (isLoadMore) {
records.value = [...records.value, ...list]
} else {
records.value = list
}
// 判断是否还有更多数据
noMore.value = list.length < pageSize
} catch (e) {
// 错误已统一处理
} finally {
loading.value = false
}
}
// 切换Tab
function switchTab(tab) {
if (currentTab.value === tab) return
currentTab.value = tab
page.value = 1
noMore.value = false
records.value = []
loadRecords()
}
// 加载更多
function loadMore() {
if (noMore.value || loading.value) return
page.value++
loadRecords(true)
}
// 初始加载
loadRecords()
</script>
<style scoped>
.points-page {
min-height: 100vh;
background-color: #f5f5f5;
display: flex;
flex-direction: column;
}
.tab-bar {
display: flex;
background-color: #ffffff;
border-bottom: 1rpx solid #f0f0f0;
}
.tab-item {
flex: 1;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background-color: #007aff;
border-radius: 2rpx;
}
.tab-text {
font-size: 30rpx;
color: #666;
}
.tab-item.active .tab-text {
color: #007aff;
font-weight: 500;
}
.record-list {
flex: 1;
padding: 20rpx 24rpx;
}
.record-item {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #ffffff;
padding: 28rpx 30rpx;
border-radius: 12rpx;
margin-bottom: 16rpx;
}
.record-info {
flex: 1;
margin-right: 20rpx;
}
.record-source {
font-size: 28rpx;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.record-time {
font-size: 22rpx;
color: #999;
display: block;
}
.record-amount {
font-size: 32rpx;
font-weight: 600;
}
.record-amount.earn {
color: #4caf50;
}
.record-amount.spend {
color: #ff3b30;
}
.loading-tip {
text-align: center;
padding: 30rpx;
font-size: 24rpx;
color: #999;
}
</style>