Files
xdd-uniapp-new/pkg_product/views/experienceCoupon.vue
T
2026-03-12 18:05:38 +08:00

455 lines
11 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.
<script>
import { getLatestExperienceCoupon } from '@/api/product'
import { getExperienceStoreCategoryList } from '@/api/join'
export default {
data() {
const webUrl = this.$VUE_APP_RESOURCES_URL
return {
webUrl,
headerImage: webUrl + '/experienceCoupon/header.png',
totalCount: 0,
selectedCount: 0,
remainNum: 0,
startTime: '',
endTime: '',
expireDate: '',
categoryList: [
{ id: 'all', name: '全部体验' }
],
activeCategoryId: 'all',
experienceList: [
{
id: 1,
title: 'Wave Restaurant',
subTitle: '一种咖啡 两种体验',
distance: '距选地点1.8km',
businessTime: '09:30-02:00',
tags: '两种体验咖啡',
categoryId: 'all',
image: webUrl + '/experienceCoupon/experience-1.png',
selected: true
},
{
id: 2,
title: '老信食堂',
subTitle: '云南特色卤菜',
distance: '距选地点1.8km',
businessTime: '09:30-02:00',
tags: '云南特色美食',
categoryId: 'all',
image: webUrl + '/experienceCoupon/experience-2.png',
selected: true
},
{
id: 3,
title: '桃矢小馆·缘味小融合',
subTitle: '会员可享受氛围小店',
distance: '距选地点1.8km',
businessTime: '09:30-02:00',
tags: '氛围小店美食',
categoryId: 'all',
image: webUrl + '/experienceCoupon/experience-3.png',
selected: false
},
{
id: 4,
title: '哇猫博物馆',
subTitle: '非遗猫文化体验',
distance: '距选地点1.8km',
businessTime: '09:30-02:00',
tags: '非遗手作体验',
categoryId: 'all',
image: webUrl + '/experienceCoupon/experience-4.png',
selected: false
}
]
}
},
computed: {
filteredList() {
if (this.activeCategoryId === 'all') {
return this.experienceList
}
return this.experienceList.filter(item => item.categoryId === this.activeCategoryId)
},
remainCount() {
if (this.remainNum != null) {
return this.remainNum
}
return this.totalCount - this.selectedCount
}
},
onLoad() {
this.initPage()
},
methods: {
initPage() {
this.fetchExperienceCoupon()
this.fetchCategoryList()
},
fetchExperienceCoupon() {
getLatestExperienceCoupon().then(({ data }) => {
if (!data) {
return
}
this.totalCount = data.totalNum || 0
const remain = data.remainNum != null ? data.remainNum : 0
this.remainNum = remain
this.selectedCount = Math.max(this.totalCount - remain, 0)
this.startTime = data.startTime || ''
this.endTime = data.endTime || ''
this.expireDate = this.endTime || this.expireDate
this.headerImage = data.experienceCouponImage || this.headerImage
}).catch(() => {})
},
fetchCategoryList() {
getExperienceStoreCategoryList().then(({ data }) => {
const list = data || []
this.categoryList = [
{ id: 'all', name: '全部体验' },
...list.map(item => ({
id: item.id,
name: item.name
}))
]
if (list.length > 0) {
const ids = list.map(item => item.id)
if (this.experienceList[0]) {
this.experienceList[0].categoryId = ids[0]
}
if (this.experienceList[1]) {
this.experienceList[1].categoryId = ids[Math.min(1, ids.length - 1)]
}
if (this.experienceList[2]) {
this.experienceList[2].categoryId = ids[Math.min(2, ids.length - 1)]
}
if (this.experienceList[3]) {
this.experienceList[3].categoryId = ids[Math.min(3, ids.length - 1)]
}
}
}).catch(() => {})
},
changeCategory(id) {
this.activeCategoryId = id
},
toggleSelect(item) {
if (!item.selected && this.remainCount === 0) {
uni.showToast({
title: '体验券数量已达上限',
icon: 'none'
})
return
}
item.selected = !item.selected
this.selectedCount = this.experienceList.filter(it => it.selected).length
},
handleContinueSelect() {
uni.showToast({
title: '请继续选择体验券',
icon: 'none'
})
},
handleGoExperience() {
uni.showToast({
title: '请前往体验门店使用',
icon: 'none'
})
},
handleConfirm() {
uni.showToast({
title: '已确认体验券选择',
icon: 'none'
})
}
}
}
</script>
<template>
<view class="experience-coupon-page">
<view class="header">
<image
:src="headerImage"
class="header-bg"
mode="aspectFill"
/>
<view class="header-mask" />
<view class="header-content">
<view class="header-tag">
选择您的免费体验券
</view>
<view class="header-main">
<view class="line">
根据您的套票您有
<text class="strong">{{ totalCount }}</text>
张体验券可以选择
</view>
<view class="line flex jc-between ai-center">
<view>
已选清单
<text class="strong">{{ selectedCount }}/{{ totalCount }}</text>
</view>
</view>
<view class="line">
请在
<text class="strong">{{ expireDate }}</text>
内选择使用
</view>
</view>
</view>
</view>
<view class="content">
<view class="section-header flex jc-between ai-center">
<view class="title">
可用体验券库
</view>
</view>
<view class="tabs flex">
<view
v-for="item in categoryList"
:key="item.id"
class="tab-item"
:class="{ active: activeCategoryId === item.id }"
@click="changeCategory(item.id)"
>
{{ item.name }}
</view>
</view>
<view class="card-list">
<view
v-for="item in filteredList"
:key="item.id"
class="card"
>
<image
:src="item.image"
class="card-image"
mode="aspectFill"
/>
<view class="card-body">
<view class="card-title">
{{ item.title }}
</view>
<view class="card-subtitle more-t">
{{ item.subTitle }}
</view>
<view class="card-meta flex jc-between">
<text class="meta-distance">{{ item.distance }}</text>
<text class="meta-time">营业时间{{ item.businessTime }}</text>
</view>
<view class="card-footer flex jc-between ai-center">
<view class="card-tags more-t">
{{ item.tags }}
</view>
<view
class="card-btn"
:class="{ selected: item.selected }"
@click.stop="toggleSelect(item)"
>
{{ item.selected ? '取消选择' : '立即选择' }}
</view>
</view>
</view>
</view>
</view>
</view>
<view class="bottom-bar">
<view
v-if="remainCount > 0"
class="bottom-group flex jc-between ai-center"
>
<view
class="btn-outline"
@click="handleContinueSelect"
>
继续选择还可以选{{ remainCount }}
</view>
<view
class="btn-primary"
@click="handleGoExperience"
>
先去体验
</view>
</view>
<view
v-else
class="bottom-single"
>
<view
class="btn-primary"
@click="handleConfirm"
>
确认
</view>
</view>
</view>
</view>
</template>
<style scoped lang="less">
.experience-coupon-page {
min-height: 100vh;
padding-bottom: 140rpx;
background: #f5f5f5;
color: #333;
}
.header {
position: relative;
height: 360rpx;
overflow: hidden;
}
.header-bg {
width: 100%;
height: 100%;
}
.header-mask {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 220rpx;
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 100%);
}
.header-content {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 32rpx 32rpx 40rpx;
box-sizing: border-box;
color: #fff;
}
.header-tag {
display: inline-block;
padding: 6rpx 24rpx;
margin-bottom: 16rpx;
border-radius: 40rpx;
background: #C52733;
font-size: 22rpx;
}
.header-main .line {
margin-top: 8rpx;
font-size: 24rpx;
}
.header-main .strong {
margin: 0 4rpx;
font-size: 28rpx;
font-weight: 600;
}
.content {
padding: 24rpx 24rpx 0;
}
.section-header .title {
font-size: 30rpx;
font-weight: 600;
}
.tabs {
margin-top: 24rpx;
padding: 4rpx;
border-radius: 40rpx;
background: #f0f0f0;
}
.tab-item {
flex: 1;
padding: 16rpx 0;
text-align: center;
font-size: 24rpx;
color: #666;
border-radius: 32rpx;
}
.tab-item.active {
background: #C52733;
color: #fff;
}
.card-list {
margin-top: 24rpx;
}
.card {
margin-bottom: 24rpx;
border-radius: 20rpx;
overflow: hidden;
background: #fff;
}
.card-image {
width: 100%;
height: 260rpx;
}
.card-body {
padding: 20rpx 24rpx 24rpx;
}
.card-title {
font-size: 30rpx;
font-weight: 600;
}
.card-subtitle {
margin-top: 8rpx;
font-size: 24rpx;
color: #666;
}
.card-meta {
margin-top: 12rpx;
font-size: 22rpx;
color: #999;
}
.card-footer {
margin-top: 16rpx;
}
.card-tags {
flex: 1;
margin-right: 16rpx;
font-size: 22rpx;
color: #999;
}
.card-btn {
min-width: 160rpx;
padding: 12rpx 24rpx;
border-radius: 40rpx;
text-align: center;
font-size: 24rpx;
color: #C52733;
border: 1rpx solid #C52733;
}
.card-btn.selected {
background: #C52733;
color: #fff;
}
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 16rpx 24rpx 32rpx;
box-sizing: border-box;
background: #fff;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.04);
}
.bottom-group {
column-gap: 16rpx;
}
.btn-outline {
flex: 1;
height: 80rpx;
border-radius: 40rpx;
border: 1rpx solid #C52733;
text-align: center;
font-size: 26rpx;
line-height: 80rpx;
color: #C52733;
background: #fff;
}
.btn-primary {
flex: 1;
height: 80rpx;
border-radius: 40rpx;
text-align: center;
font-size: 28rpx;
line-height: 80rpx;
color: #fff;
background: #C52733;
}
.bottom-single .btn-primary {
width: 100%;
}
</style>