493 lines
11 KiB
Vue
493 lines
11 KiB
Vue
<template>
|
||
<view class="points-page">
|
||
<NavBar title="我的积分" :showBack="false" />
|
||
|
||
<!-- 积分卡片 -->
|
||
<view class="balance-card">
|
||
<image v-if="pointsBgUrl" :src="pointsBgUrl" class="balance-bg" mode="aspectFill" />
|
||
<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">
|
||
<!-- 背景图 -->
|
||
<image class="coupon-card-bg" src="/static/item_bg2.png" mode="scaleToFill" />
|
||
<!-- 左侧标签 -->
|
||
<view class="coupon-left">
|
||
<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">有效期:{{ isPermanent(item) ? '永久有效' : formatDate(item.validStart) + '至' + formatDate(item.validEnd) }}</text>
|
||
<text class="coupon-info">剩余数量:{{ item.remainingCount }} 张</text>
|
||
<text class="coupon-info" v-if="item.source === 'ygl'">使用方式:请在驿公里洗车小程序中使用</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="该优惠券为驿公里券,兑换后需在驿公里洗车小程序中使用,确定兑换吗?"
|
||
@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,
|
||
selectedStoreId: null,
|
||
pointsBgUrl: ''
|
||
}
|
||
},
|
||
computed: {
|
||
filteredList() {
|
||
if (!this.currentStoreId) return this.availableList
|
||
// 筛选包含该门店的优惠券
|
||
return this.availableList.filter(item => {
|
||
if (item.stores && item.stores.length) {
|
||
return item.stores.some(s => s.id == this.currentStoreId)
|
||
}
|
||
return false
|
||
})
|
||
}
|
||
},
|
||
onShow() {
|
||
this.loadData()
|
||
},
|
||
methods: {
|
||
formatDate(d) {
|
||
if (!d) return ''
|
||
return d.substring(0, 10)
|
||
},
|
||
isPermanent(item) {
|
||
if (!item.validEnd) return false
|
||
return new Date(item.validEnd).getFullYear() >= 2099
|
||
},
|
||
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
|
||
}))
|
||
]
|
||
// 加载积分页背景图配置
|
||
try {
|
||
const bgRes = await getPopup('points-bg')
|
||
this.pointsBgUrl = bgRes.content || bgRes.data?.content || ''
|
||
} catch (e) {
|
||
console.error('加载背景图失败:', e)
|
||
}
|
||
} 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
|
||
|
||
// 多门店时需要先选择门店
|
||
const stores = item.stores || []
|
||
if (stores.length === 0) {
|
||
uni.showToast({ title: '该优惠券未配置门店', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
if (stores.length === 1) {
|
||
// 只有一个门店,直接使用
|
||
this.selectedStoreId = stores[0].id
|
||
this.showExchangeConfirm(item)
|
||
} else {
|
||
// 多个门店,弹出选择
|
||
const names = stores.map(s => s.name)
|
||
uni.showActionSheet({
|
||
itemList: names,
|
||
success: (res) => {
|
||
this.selectedStoreId = stores[res.tapIndex].id
|
||
this.showExchangeConfirm(item)
|
||
}
|
||
})
|
||
}
|
||
},
|
||
showExchangeConfirm(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 || !this.selectedStoreId) return
|
||
try {
|
||
await exchange({
|
||
templateId: this.currentItem.id,
|
||
storeId: this.selectedStoreId
|
||
})
|
||
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-bg {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 0;
|
||
}
|
||
|
||
.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;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.balance-value {
|
||
display: block;
|
||
font-size: 80rpx;
|
||
font-weight: bold;
|
||
color: #AF6547;
|
||
margin: 12rpx 0 32rpx;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.balance-actions {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 32rpx;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.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;
|
||
margin-bottom: 20rpx;
|
||
overflow: hidden;
|
||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||
position: relative;
|
||
}
|
||
|
||
.coupon-card-bg {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 0;
|
||
}
|
||
|
||
/* 左侧标签 */
|
||
.coupon-left {
|
||
width: 160rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 24rpx 0 24rpx 20rpx;
|
||
flex-shrink: 0;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.coupon-price {
|
||
font-size: 65.13rpx;
|
||
font-weight: bold;
|
||
color: #E8C7AA;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.coupon-type-label {
|
||
font-size: 32.57rpx;
|
||
color: #E8C7AA;
|
||
margin-top: 6rpx;
|
||
}
|
||
|
||
/* 右侧信息 */
|
||
.coupon-right {
|
||
flex: 1;
|
||
padding: 24rpx 24rpx 20rpx;
|
||
display: flex;
|
||
margin-left: 20rpx;
|
||
flex-direction: column;
|
||
min-width: 0;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.coupon-name {
|
||
font-size: 35.71rpx;
|
||
font-weight: bold;
|
||
color: #FFFFFF;
|
||
margin-bottom: 8rpx;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.coupon-info {
|
||
font-size: 25.79rpx;
|
||
color: #FFFFFF;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.coupon-bottom {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-top: 12rpx;
|
||
}
|
||
|
||
.exchange-btn {
|
||
background: #FFF8E8;
|
||
border-radius: 10rpx;
|
||
padding: 10rpx 28rpx;
|
||
}
|
||
|
||
.exchange-btn:active {
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.exchange-btn.disabled {
|
||
background: #ccc;
|
||
}
|
||
|
||
.exchange-btn-text {
|
||
font-size: 24rpx;
|
||
color: #E8C7AA;
|
||
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> |