peixue-dev/peidu/uniapp/user-package/pages/user/points.vue

410 lines
9.1 KiB
Vue

<template>
<view class="points-page">
<!-- 积分余额卡片 -->
<view class="points-balance-card">
<view class="balance-info">
<text class="label">我的积分</text>
<text class="value">{{ pointsBalance }}</text>
</view>
<button class="btn-exchange" @click="goExchange">积分兑换</button>
</view>
<!-- 积分规则 -->
<view class="rules-card">
<view class="card-title">积分获取规则</view>
<view class="rule-list">
<view class="rule-item">
<text class="rule-icon">🎁</text>
<view class="rule-content">
<text class="rule-name">新用户注册</text>
<text class="rule-desc">赠送100积分</text>
</view>
<text class="rule-points">+100</text>
</view>
<view class="rule-item">
<text class="rule-icon">💰</text>
<view class="rule-content">
<text class="rule-name">订单消费</text>
<text class="rule-desc">每消费1元获得1积分</text>
</view>
<text class="rule-points">+1/</text>
</view>
<view class="rule-item">
<text class="rule-icon">👥</text>
<view class="rule-content">
<text class="rule-name">邀请好友</text>
<text class="rule-desc">成功邀请赠送200积分</text>
</view>
<text class="rule-points">+200</text>
</view>
<view class="rule-item">
<text class="rule-icon"></text>
<view class="rule-content">
<text class="rule-name">完成订单</text>
<text class="rule-desc">每完成一单赠送50积分</text>
</view>
<text class="rule-points">+50</text>
</view>
</view>
</view>
<!-- 积分记录 -->
<view class="records-section">
<view class="section-header">
<text class="section-title">积分明细</text>
<picker :range="typeOptions" range-key="label" @change="onTypeChange">
<view class="filter-btn">
{{ currentType.label }}
<text class="arrow"></text>
</view>
</picker>
</view>
<view v-if="records.length === 0" class="empty-state">
<text class="empty-icon">📭</text>
<text class="empty-text">暂无积分记录</text>
</view>
<view v-else class="record-list">
<view v-for="item in records" :key="item.id" class="record-item">
<view class="item-left">
<text class="item-desc">{{ item.description }}</text>
<text class="item-time">{{ formatTime(item.createTime) }}</text>
<text class="item-expire" v-if="item.expireDate">
有效期至: {{ item.expireDate }}
</text>
</view>
<view class="item-right">
<text class="item-points" :class="item.points > 0 ? 'earn' : 'use'">
{{ item.points > 0 ? '+' : '' }}{{ item.points }}
</text>
<text class="item-balance">余额: {{ item.balance }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { pointsApi } from '@/api/index.js'
export default {
data() {
return {
pointsBalance: 0,
records: [],
typeOptions: [
{ label: '全部', value: '' },
{ label: '获得', value: 'earn' },
{ label: '使用', value: 'use' },
{ label: '过期', value: 'expire' }
],
currentType: { label: '全部', value: '' },
page: 1,
size: 20,
loading: false,
finished: false
}
},
onLoad() {
this.loadPointsBalance()
this.loadRecords()
},
onReachBottom() {
if (!this.loading && !this.finished) {
this.page++
this.loadRecords()
}
},
methods: {
async loadPointsBalance() {
try {
uni.showLoading({ title: '加载中...' })
const res = await pointsApi.getBalance()
// request.js 响应拦截器直接返回 res.data
if (res) {
this.pointsBalance = res.balance || 0
}
} catch (e) {
console.error('加载积分余额失败', e)
this.pointsBalance = 0
} finally {
uni.hideLoading()
}
},
async loadRecords() {
if (this.loading) return
this.loading = true
try {
const params = {
page: this.page,
size: this.size
}
if (this.currentType.value) {
params.type = this.currentType.value
}
const res = await pointsApi.getRecords(params)
// request.js 响应拦截器直接返回 res.data
if (res) {
const records = res.records || []
if (this.page === 1) {
this.records = records
} else {
this.records = [...this.records, ...records]
}
if (records.length < this.size) {
this.finished = true
}
}
} catch (e) {
console.error('加载积分记录失败', e)
this.records = []
} finally {
this.loading = false
}
},
onTypeChange(e) {
this.currentType = this.typeOptions[e.detail.value]
this.page = 1
this.finished = false
this.records = []
this.loadRecords()
},
formatTime(datetime) {
if (!datetime) return ''
return datetime.replace('T', ' ').substring(0, 16)
},
goExchange() {
uni.showToast({
title: '积分兑换功能开发中',
icon: 'none'
})
}
}
}
</script>
<style lang="scss" scoped>
@import '@/static/css/common.scss';
.points-page {
min-height: 100vh;
background: $bg-color;
padding-bottom: 40rpx;
}
.points-balance-card {
background: linear-gradient(135deg, #ff9500 0%, #ff6b00 100%);
padding: 60rpx 40rpx;
display: flex;
justify-content: space-between;
align-items: center;
.balance-info {
.label {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 16rpx;
}
.value {
display: block;
font-size: 72rpx;
font-weight: bold;
color: #fff;
}
}
.btn-exchange {
width: 180rpx;
height: 72rpx;
line-height: 72rpx;
background: #fff;
color: #ff9500;
border-radius: 36rpx;
font-size: 28rpx;
font-weight: bold;
border: none;
}
}
.rules-card {
background: #fff;
margin: 20rpx 30rpx;
padding: 30rpx;
border-radius: 16rpx;
.card-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 30rpx;
}
.rule-list {
.rule-item {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.rule-icon {
font-size: 48rpx;
margin-right: 20rpx;
}
.rule-content {
flex: 1;
.rule-name {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 8rpx;
}
.rule-desc {
display: block;
font-size: 24rpx;
color: #999;
}
}
.rule-points {
font-size: 28rpx;
color: #ff9500;
font-weight: bold;
}
}
}
}
.records-section {
background: #fff;
margin: 20rpx 30rpx;
border-radius: 16rpx;
padding: 30rpx;
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.filter-btn {
font-size: 28rpx;
color: #666;
.arrow {
margin-left: 8rpx;
font-size: 20rpx;
}
}
}
}
.empty-state {
text-align: center;
padding: 80rpx 0;
.empty-image {
width: 300rpx;
height: 300rpx;
margin-bottom: 40rpx;
}
.empty-text {
display: block;
font-size: 28rpx;
color: #999;
}
}
.record-list {
.record-item {
display: flex;
justify-content: space-between;
padding: 24rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.item-left {
flex: 1;
.item-desc {
display: block;
font-size: 30rpx;
color: #333;
margin-bottom: 8rpx;
}
.item-time {
display: block;
font-size: 24rpx;
color: #999;
margin-bottom: 4rpx;
}
.item-expire {
display: block;
font-size: 24rpx;
color: #ff9500;
}
}
.item-right {
text-align: right;
.item-points {
display: block;
font-size: 32rpx;
font-weight: bold;
margin-bottom: 8rpx;
&.earn {
color: #ff9500;
}
&.use {
color: #52c41a;
}
}
.item-balance {
display: block;
font-size: 24rpx;
color: #999;
}
}
}
}
</style>