vending-machine/mobile/pages/points/points.vue
18631081161 d43406380c
Some checks reported errors
continuous-integration/drone/push Build encountered an error
逻辑优化
2026-04-13 13:43:06 +08:00

267 lines
5.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="points-page">
<!-- 自定义导航栏 -->
<view class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-inner">
<view class="nav-back" @click="goBack">
<text class="nav-back-icon"></text>
</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;
}
.nav-back-icon {
font-size: 48rpx;
color: #333;
}
.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>