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

487 lines
11 KiB
Vue

<template>
<view class="wallet-page">
<!-- 钱包余额卡片 -->
<view class="balance-card">
<view class="balance-header">
<text class="title">钱包余额</text>
<text class="status" :class="wallet.status === 1 ? 'normal' : 'frozen'">
{{ wallet.statusName }}
</text>
</view>
<view class="balance-amount">
<text class="symbol">¥</text>
<text class="value">{{ wallet.balance || 0 }}</text>
</view>
<view class="balance-stats">
<view class="stat-item">
<text class="stat-label">冻结金额</text>
<text class="stat-value">¥{{ wallet.frozenAmount || 0 }}</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-label">累计充值</text>
<text class="stat-value">¥{{ wallet.totalRecharge || 0 }}</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-label">累计消费</text>
<text class="stat-value">¥{{ wallet.totalConsume || 0 }}</text>
</view>
</view>
<view class="action-buttons">
<button class="btn-recharge" @click="goRecharge">充值</button>
<button class="btn-withdraw" @click="goWithdraw">提现</button>
</view>
</view>
<!-- 积分卡片 -->
<view class="points-card" @click="goPoints">
<view class="points-left">
<text class="points-label">我的积分</text>
<text class="points-value">{{ wallet.points || 0 }}</text>
</view>
<view class="points-right">
<text class="points-link">积分明细 ></text>
</view>
</view>
<!-- 交易记录 -->
<view class="transactions-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="transactions.length === 0" class="empty-state">
<text class="empty-icon">📭</text>
<text class="empty-text">暂无交易记录</text>
</view>
<view v-else class="transaction-list">
<view
v-for="item in transactions"
:key="item.id"
class="transaction-item">
<view class="item-left">
<text class="item-type">{{ getTypeName(item.type) }}</text>
<text class="item-time">{{ formatTime(item.createTime) }}</text>
<text class="item-desc" v-if="item.description">{{ item.description }}</text>
</view>
<view class="item-right">
<text class="item-amount" :class="['recharge', 'refund'].includes(item.type) ? 'income' : 'expense'">
{{ ['recharge', 'refund'].includes(item.type) ? '+' : '-' }}¥{{ item.amount }}
</text>
<text class="item-balance">余额: ¥{{ item.balanceAfter }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { walletApi } from '@/api/index.js'
export default {
data() {
return {
wallet: {},
transactions: [],
typeOptions: [
{ label: '全部', value: '' },
{ label: '充值', value: 'recharge' },
{ label: '消费', value: 'consume' },
{ label: '退款', value: 'refund' }
],
currentType: { label: '全部', value: '' },
page: 1,
size: 20,
loading: false,
finished: false
}
},
onLoad() {
this.loadWalletInfo()
this.loadTransactions()
},
onReachBottom() {
if (!this.loading && !this.finished) {
this.page++
this.loadTransactions()
}
},
methods: {
async loadWalletInfo() {
try {
uni.showLoading({ title: '加载中...' })
const res = await walletApi.getWalletInfo()
// request.js 响应拦截器直接返回 res.data
if (res) {
this.wallet = res
}
} catch (e) {
console.error('加载钱包信息失败', e)
// 设置默认值
this.wallet = { balance: 0, frozenAmount: 0, totalRecharge: 0, totalConsume: 0, points: 0, statusName: '正常' }
} finally {
uni.hideLoading()
}
},
async loadTransactions() {
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 walletApi.getTransactions(params)
// request.js 响应拦截器直接返回 res.data
if (res) {
const records = res.records || []
if (this.page === 1) {
this.transactions = records
} else {
this.transactions = [...this.transactions, ...records]
}
if (records.length < this.size) {
this.finished = true
}
}
} catch (e) {
console.error('加载交易记录失败', e)
this.transactions = []
} finally {
this.loading = false
}
},
onTypeChange(e) {
this.currentType = this.typeOptions[e.detail.value]
this.page = 1
this.finished = false
this.transactions = []
this.loadTransactions()
},
getTypeName(type) {
const names = {
'recharge': '充值',
'consume': '消费',
'refund': '退款',
'withdraw': '提现',
'adjust': '调整'
}
return names[type] || type
},
getAmountClass(type) {
return ['recharge', 'refund'].includes(type) ? 'income' : 'expense'
},
getAmountPrefix(type) {
return ['recharge', 'refund'].includes(type) ? '+' : '-'
},
formatTime(datetime) {
if (!datetime) return ''
return datetime.replace('T', ' ').substring(0, 16)
},
goRecharge() {
uni.showToast({
title: '充值功能开发中',
icon: 'none'
})
},
goWithdraw() {
uni.showToast({
title: '提现功能开发中',
icon: 'none'
})
},
goPoints() {
uni.showToast({
title: '积分明细功能开发中',
icon: 'none'
})
}
}
}
</script>
<style lang="scss" scoped>
@import '@/static/css/common.scss';
.wallet-page {
min-height: 100vh;
background: $bg-color;
padding-bottom: 40rpx;
}
.balance-card {
background: linear-gradient(135deg, #4a9b9f 0%, #3d8185 100%);
padding: 60rpx 40rpx;
color: #fff;
.balance-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
.title {
font-size: 28rpx;
opacity: 0.9;
}
.status {
padding: 4rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
background: rgba(255, 255, 255, 0.2);
&.frozen {
background: rgba(255, 77, 79, 0.3);
}
}
}
.balance-amount {
margin-bottom: 40rpx;
.symbol {
font-size: 40rpx;
margin-right: 8rpx;
}
.value {
font-size: 72rpx;
font-weight: bold;
}
}
.balance-stats {
display: flex;
justify-content: space-around;
padding: 30rpx 0;
border-top: 1rpx solid rgba(255, 255, 255, 0.2);
border-bottom: 1rpx solid rgba(255, 255, 255, 0.2);
margin-bottom: 30rpx;
.stat-item {
flex: 1;
text-align: center;
.stat-label {
display: block;
font-size: 24rpx;
opacity: 0.8;
margin-bottom: 12rpx;
}
.stat-value {
display: block;
font-size: 28rpx;
font-weight: bold;
}
}
.stat-divider {
width: 2rpx;
background: rgba(255, 255, 255, 0.2);
}
}
.action-buttons {
display: flex;
gap: 20rpx;
button {
flex: 1;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
font-size: 28rpx;
font-weight: bold;
border: none;
}
.btn-recharge {
background: #fff;
color: #4a9b9f;
}
.btn-withdraw {
background: rgba(255, 255, 255, 0.2);
color: #fff;
}
}
}
.points-card {
background: #fff;
margin: 20rpx 30rpx;
padding: 30rpx;
border-radius: 16rpx;
display: flex;
justify-content: space-between;
align-items: center;
.points-left {
.points-label {
display: block;
font-size: 28rpx;
color: #666;
margin-bottom: 12rpx;
}
.points-value {
display: block;
font-size: 48rpx;
font-weight: bold;
color: #ff9500;
}
}
.points-right {
.points-link {
font-size: 28rpx;
color: #4a9b9f;
}
}
}
.transactions-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;
}
}
.transaction-list {
.transaction-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-type {
display: block;
font-size: 30rpx;
color: #333;
font-weight: 500;
margin-bottom: 8rpx;
}
.item-time {
display: block;
font-size: 24rpx;
color: #999;
margin-bottom: 4rpx;
}
.item-desc {
display: block;
font-size: 24rpx;
color: #666;
}
}
.item-right {
text-align: right;
.item-amount {
display: block;
font-size: 32rpx;
font-weight: bold;
margin-bottom: 8rpx;
&.income {
color: #ff4d4f;
}
&.expense {
color: #52c41a;
}
}
.item-balance {
display: block;
font-size: 24rpx;
color: #999;
}
}
}
}
</style>