vending-machine/mobile/pages/points/points.vue
18631081161 539b58ea87
All checks were successful
continuous-integration/drone/push Build is passing
细节修改
2026-04-13 14:25:51 +08:00

267 lines
5.5 KiB
Vue

<template>
<view class="points-page">
<!-- 自定义导航栏 -->
<view class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-inner">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back2.png" mode="aspectFit" />
</view>
<text class="nav-title">{{ t('points.title') }}</text>
<view class="nav-placeholder" />
</view>
</view>
<!-- 页面内容 -->
<view :style="{ paddingTop: (statusBarHeight + 44) + 'px' }">
<!-- 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">
{{ currentTab === 'earn' ? '+' : '-' }}{{ record.amount }}
</text>
</view>
<!-- 加载状态 -->
<view v-if="loading" class="loading-tip">
<text>{{ t('common.loading') }}</text>
</view>
<EmptyState v-if="!loading && records.length === 0" text="暂无积分记录" />
<view v-if="noMore && records.length > 0" class="loading-tip">
<text>{{ t('common.noMore') }}</text>
</view>
</scroll-view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { getEarnRecords, getSpendRecords } from '../../api/points.js'
import EmptyState from '../../components/EmptyState.vue'
const { t } = useI18n()
// 状态栏高度
const statusBarHeight = ref(0)
try {
const sysInfo = uni.getSystemInfoSync()
statusBarHeight.value = sysInfo.statusBarHeight || 0
} catch (e) {}
function goBack() {
uni.navigateBack()
}
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, size: pageSize })
const list = res.data?.items || 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: #ffffff;
display: flex;
flex-direction: column;
}
/* 自定义导航栏 */
.custom-nav {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 100;
background-color: #DBDBDB;
}
.nav-inner {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16rpx;
}
.nav-back {
width: 60rpx;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.nav-title {
font-size: 34rpx;
font-weight: 600;
color: #333;
}
.nav-placeholder {
width: 60rpx;
}
.tab-bar {
display: flex;
background-color: #ffffff;
}
.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: #333;
border-radius: 2rpx;
}
.tab-text {
font-size: 30rpx;
color: #999;
}
.tab-item.active .tab-text {
color: #333;
font-weight: 500;
}
.record-list {
flex: 1;
}
.record-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx 32rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.record-info {
flex: 1;
margin-right: 20rpx;
}
.record-source {
font-size: 28rpx;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.record-time {
font-size: 24rpx;
color: #999;
display: block;
}
.record-amount {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.loading-tip {
text-align: center;
padding: 30rpx;
font-size: 24rpx;
color: #999;
}
</style>