381 lines
9.3 KiB
Vue
381 lines
9.3 KiB
Vue
<template>
|
||
<view class="points-page">
|
||
<NavBar title="我的积分" :showBack="false" />
|
||
|
||
<!-- 积分卡片 -->
|
||
<view class="balance-card">
|
||
<text class="balance-label">我的积分</text>
|
||
<text class="balance-value">{{ balance }}</text>
|
||
<view class="balance-actions">
|
||
<view class="action-btn" @click="showPointsDesc">
|
||
<text class="action-btn-text">积分说明</text>
|
||
</view>
|
||
<view class="action-btn" @click="goDetail">
|
||
<text class="action-btn-text">积分明细</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 门店筛选 -->
|
||
<view class="filter-row">
|
||
<picker :range="storeOptions" range-key="label" @change="onStoreChange">
|
||
<view class="store-filter">
|
||
<text class="filter-label">适用门店:{{ currentStoreName }}</text>
|
||
<text class="filter-arrow">∨</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<!-- 可兑换优惠券列表 -->
|
||
<view class="coupon-list">
|
||
<view class="coupon-card" v-for="item in filteredList" :key="item.id">
|
||
<!-- 左侧标签 -->
|
||
<view class="coupon-left" :class="item.couponType === 'discount' ? 'left-discount' : 'left-free'">
|
||
<template v-if="item.couponType === 'discount'">
|
||
<text class="coupon-price">¥{{ item.discountAmount }}</text>
|
||
<text class="coupon-type-label">折扣券</text>
|
||
</template>
|
||
<template v-else>
|
||
<text class="coupon-price">免费</text>
|
||
<text class="coupon-type-label">洗车券</text>
|
||
</template>
|
||
</view>
|
||
<!-- 右侧信息 -->
|
||
<view class="coupon-right">
|
||
<text class="coupon-name">{{ item.name }}</text>
|
||
<text class="coupon-info">券码:{{ item.code || 'XXXX' }}</text>
|
||
<text class="coupon-info">有效期:{{ formatDate(item.validStart) }}至{{ formatDate(item.validEnd) }}</text>
|
||
<text class="coupon-info">剩余数量:{{ item.remainingCount }} 张</text>
|
||
<text class="coupon-info" v-if="item.source === 'ygl'">使用方式:请在驿公里App中使用</text>
|
||
<view class="coupon-bottom">
|
||
<view class="exchange-btn" @click="onExchange(item)" :class="{ disabled: item.remainingCount <= 0 }">
|
||
<text class="exchange-btn-text">{{ item.pointsCost }} 积分兑换</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-if="!filteredList.length && !loading" class="empty-state">
|
||
<image class="empty-icon" src="/static/ic_none.png" mode="aspectFit"></image>
|
||
<text class="empty-text">暂无可兑换优惠券</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 积分说明弹窗 -->
|
||
<RichContentPopup
|
||
:visible="pointsDescVisible"
|
||
title="积分说明"
|
||
:content="pointsDescContent"
|
||
icon="/static/ic_points.png"
|
||
@close="pointsDescVisible = false"
|
||
/>
|
||
|
||
<!-- 兑换确认弹窗 -->
|
||
<ConfirmDialog
|
||
:visible="confirmVisible"
|
||
title="确认兑换"
|
||
:message="confirmMessage"
|
||
@confirm="doExchange"
|
||
@cancel="confirmVisible = false"
|
||
/>
|
||
|
||
<!-- 驿公里券提示弹窗 -->
|
||
<ConfirmDialog
|
||
:visible="yglTipVisible"
|
||
title="温馨提示"
|
||
message="该优惠券为驿公里券,兑换后需在驿公里APP/小程序中使用,确定兑换吗?"
|
||
@confirm="doExchangeYgl"
|
||
@cancel="yglTipVisible = false"
|
||
/>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { getBalance } from '@/api/points'
|
||
import { getAvailable, exchange } from '@/api/coupon'
|
||
import { getStores } from '@/api/store'
|
||
import { getPopup } from '@/api/config'
|
||
import RichContentPopup from '@/components/RichContentPopup.vue'
|
||
import ConfirmDialog from '@/components/ConfirmDialog.vue'
|
||
import NavBar from '@/components/NavBar.vue'
|
||
|
||
export default {
|
||
components: { RichContentPopup, ConfirmDialog, NavBar },
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
balance: 0,
|
||
availableList: [],
|
||
storeOptions: [{ label: '全部', value: '' }],
|
||
currentStoreId: '',
|
||
currentStoreName: '全部',
|
||
pointsDescVisible: false,
|
||
pointsDescContent: '',
|
||
confirmVisible: false,
|
||
confirmMessage: '',
|
||
yglTipVisible: false,
|
||
currentItem: null
|
||
}
|
||
},
|
||
computed: {
|
||
filteredList() {
|
||
if (!this.currentStoreId) return this.availableList
|
||
return this.availableList.filter(item => item.storeId == this.currentStoreId)
|
||
}
|
||
},
|
||
onShow() {
|
||
this.loadData()
|
||
},
|
||
methods: {
|
||
formatDate(d) {
|
||
if (!d) return ''
|
||
return d.substring(0, 10)
|
||
},
|
||
async loadData() {
|
||
this.loading = true
|
||
try {
|
||
const [balanceRes, availableRes, storesRes] = await Promise.all([
|
||
getBalance(), getAvailable(), getStores()
|
||
])
|
||
this.balance = balanceRes.balance ?? balanceRes.data?.balance ?? 0
|
||
this.availableList = availableRes.data || availableRes || []
|
||
const stores = storesRes.data || storesRes || []
|
||
this.storeOptions = [
|
||
{ label: '全部', value: '' },
|
||
...stores.map(s => ({ label: s.name, value: s.id }))
|
||
]
|
||
} catch (err) {
|
||
console.error('加载积分数据失败:', err)
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
onStoreChange(e) {
|
||
const idx = e.detail.value
|
||
const opt = this.storeOptions[idx]
|
||
this.currentStoreId = opt.value
|
||
this.currentStoreName = opt.label
|
||
},
|
||
async showPointsDesc() {
|
||
try {
|
||
const res = await getPopup('points-desc')
|
||
this.pointsDescContent = res.content || res.data?.content || ''
|
||
this.pointsDescVisible = true
|
||
} catch (err) {
|
||
console.error('获取积分说明失败:', err)
|
||
}
|
||
},
|
||
goDetail() {
|
||
uni.navigateTo({ url: '/pages/points/detail' })
|
||
},
|
||
onExchange(item) {
|
||
if (item.remainingCount <= 0) return
|
||
if (item.pointsCost > 0 && this.balance < item.pointsCost) {
|
||
uni.showToast({ title: '积分不足', icon: 'none' })
|
||
return
|
||
}
|
||
this.currentItem = item
|
||
if (item.source === 'ygl') {
|
||
this.yglTipVisible = true
|
||
} else {
|
||
this.confirmMessage = `确定使用 ${item.pointsCost} 积分兑换「${item.name}」吗?`
|
||
this.confirmVisible = true
|
||
}
|
||
},
|
||
async doExchange() {
|
||
this.confirmVisible = false
|
||
await this.executeExchange()
|
||
},
|
||
async doExchangeYgl() {
|
||
this.yglTipVisible = false
|
||
await this.executeExchange()
|
||
},
|
||
async executeExchange() {
|
||
if (!this.currentItem) return
|
||
try {
|
||
await exchange({ templateId: this.currentItem.id })
|
||
uni.showToast({ title: '兑换成功', icon: 'success' })
|
||
this.loadData()
|
||
} catch (err) {
|
||
console.error('兑换失败:', err)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.points-page {
|
||
background: #f5f5f5;
|
||
min-height: 100vh;
|
||
padding-bottom: 40rpx;
|
||
}
|
||
|
||
/* 积分卡片 */
|
||
.balance-card {
|
||
margin: 20rpx 24rpx 0;
|
||
background: linear-gradient(135deg, #F4E0CB, #EBCDB4);
|
||
border-radius: 24rpx;
|
||
padding: 48rpx 40rpx 40rpx;
|
||
text-align: center;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.balance-card::after {
|
||
content: '';
|
||
position: absolute;
|
||
right: -40rpx;
|
||
bottom: -20rpx;
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
background: rgba(255,255,255,0.1);
|
||
border-radius: 50%;
|
||
}
|
||
.balance-label {
|
||
font-size: 28rpx;
|
||
color: #AF6547;
|
||
}
|
||
.balance-value {
|
||
display: block;
|
||
font-size: 80rpx;
|
||
font-weight: bold;
|
||
color: #AF6547;
|
||
margin: 12rpx 0 32rpx;
|
||
}
|
||
.balance-actions {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 32rpx;
|
||
}
|
||
.action-btn {
|
||
background: #FAE6D4;
|
||
border: 2rpx solid #FFF4E5;
|
||
border-radius: 40rpx;
|
||
padding: 12rpx 40rpx;
|
||
}
|
||
.action-btn:active { opacity: 0.7; }
|
||
.action-btn-text {
|
||
font-size: 26rpx;
|
||
color: #AF6547;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 门店筛选 */
|
||
.filter-row {
|
||
padding: 24rpx 24rpx 8rpx;
|
||
}
|
||
.store-filter {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
}
|
||
.filter-label {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
.filter-arrow {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
/* 优惠券列表 */
|
||
.coupon-list {
|
||
padding: 0 24rpx;
|
||
}
|
||
.coupon-card {
|
||
display: flex;
|
||
background: #fff;
|
||
border-radius: 20rpx;
|
||
margin-bottom: 20rpx;
|
||
overflow: hidden;
|
||
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05);
|
||
}
|
||
|
||
/* 左侧标签 */
|
||
.coupon-left {
|
||
width: 160rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 24rpx 0;
|
||
flex-shrink: 0;
|
||
}
|
||
.left-free {
|
||
background: linear-gradient(180deg, #F5A623, #E8850C);
|
||
}
|
||
.left-discount {
|
||
background: linear-gradient(180deg, #FF6B6B, #E84545);
|
||
}
|
||
.coupon-price {
|
||
font-size: 40rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
line-height: 1.2;
|
||
}
|
||
.coupon-type-label {
|
||
font-size: 22rpx;
|
||
color: rgba(255,255,255,0.85);
|
||
margin-top: 6rpx;
|
||
}
|
||
|
||
/* 右侧信息 */
|
||
.coupon-right {
|
||
flex: 1;
|
||
padding: 24rpx 24rpx 20rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-width: 0;
|
||
}
|
||
.coupon-name {
|
||
font-size: 30rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 8rpx;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.coupon-info {
|
||
font-size: 22rpx;
|
||
color: #999;
|
||
line-height: 1.6;
|
||
}
|
||
.coupon-bottom {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-top: 12rpx;
|
||
}
|
||
.exchange-btn {
|
||
background: linear-gradient(135deg, #F5A623, #E8850C);
|
||
border-radius: 28rpx;
|
||
padding: 10rpx 28rpx;
|
||
}
|
||
.exchange-btn:active { opacity: 0.8; }
|
||
.exchange-btn.disabled {
|
||
background: #ccc;
|
||
}
|
||
.exchange-btn-text {
|
||
font-size: 24rpx;
|
||
color: #fff;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.empty-state {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 100rpx 0;
|
||
}
|
||
.empty-icon {
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
margin-bottom: 20rpx;
|
||
opacity: 0.6;
|
||
}
|
||
.empty-text {
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
}
|
||
</style>
|