vending-machine/mobile/pages/index/index.vue
2026-04-11 16:31:36 +08:00

400 lines
9.3 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.

<template>
<view class="home-page">
<!-- Banner轮播图 -->
<view class="banner-wrap">
<swiper class="banner-swiper" autoplay circular indicator-dots indicator-color="rgba(255,255,255,0.4)" indicator-active-color="#ffffff">
<swiper-item v-for="banner in banners" :key="banner.id" @click="onBannerClick(banner)">
<image class="banner-image" :src="resolveImageUrl(banner.imageUrl)" mode="aspectFill" />
</swiper-item>
</swiper>
</view>
<!-- 成为会员入口 -->
<view class="membership-entry" @click="onEntryClick({ key: 'membership' })">
<view class="membership-entry-left">
<view class="membership-icon-wrap">
<image class="membership-icon-img" src="/static/ic_vip.png" mode="aspectFit" />
</view>
<view class="membership-text">
<text class="membership-title">{{ t('home.membership') }}</text>
<text class="membership-sub">Unlock VIP Benefits</text>
</view>
</view>
<text class="membership-arrow"></text>
</view>
<!-- 功能入口(节日印花 + 会员二维码) -->
<view class="entry-row">
<view class="entry-card" @click="onEntryClick({ key: 'stamps' })">
<image class="entry-icon" src="/static/ic_stamp.png" mode="aspectFit" />
<text class="entry-label">{{ t('home.stamps') }}</text>
</view>
<view class="entry-card" @click="onEntryClick({ key: 'qrcode' })">
<image class="entry-icon" src="/static/ic_qrcode.png" mode="aspectFit" />
<text class="entry-label">{{ t('home.qrcode') }}</text>
</view>
</view>
<!-- 可兑换优惠券 -->
<view class="coupon-header">
<text class="coupon-title">{{ t('home.redeemableCoupons') || '可兑换优惠券' }}</text>
<view class="coupon-guide-link" @click="handleGuideEntry">
<text class="guide-icon">ⓘ</text>
<text class="guide-text">{{ t('home.guide') }}</text>
</view>
</view>
<view class="coupon-list">
<view class="coupon-card" v-for="coupon in coupons" :key="coupon.couponId">
<view class="coupon-info">
<text class="coupon-name">{{ coupon.name }}</text>
<view class="coupon-detail-row">
<text class="coupon-dot">●</text>
<text class="coupon-detail-label">到期时间:</text>
<text class="coupon-detail-value highlight">{{ formatExpire(coupon.expireAt) }}</text>
</view>
<view class="coupon-detail-row">
<text class="coupon-dot">●</text>
<text class="coupon-detail-label">兑换条件:</text>
<text class="coupon-detail-value highlight">{{ coupon.requiredPoints }}积分</text>
</view>
</view>
<view class="coupon-action">
<view class="redeem-btn" @click="onRedeemClick(coupon)">
<text class="redeem-btn-text">兑换</text>
</view>
</view>
</view>
</view>
<!-- 使用说明弹窗 -->
<CouponGuidePopup
:visible="showGuide"
:content="guideContent"
@close="showGuide = false"
/>
<!-- 兑换确认弹窗 -->
<RedeemPopup
:visible="showRedeem"
:coupon="selectedCoupon"
@confirm="onRedeemConfirm"
@cancel="showRedeem = false"
/>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { getBanners, getEntries, getCouponGuide } from '../../api/content.js'
import { getRedeemableCoupons, redeemCoupon } from '../../api/coupon.js'
import { useUserStore } from '../../stores/user.js'
import { navigateBanner } from '../../utils/navigation.js'
import { resolveImageUrl } from '../../utils/image.js'
import RedeemPopup from '../../components/RedeemPopup.vue'
import CouponGuidePopup from '../../components/CouponGuidePopup.vue'
const { t } = useI18n()
const userStore = useUserStore()
// 数据
const banners = ref([])
const entries = ref([])
const coupons = ref([])
// 弹窗状态
const showGuide = ref(false)
const guideContent = ref('')
const showRedeem = ref(false)
const selectedCoupon = ref(null)
const showQrcode = ref(false)
// Banner点击导航
function onBannerClick(banner) {
navigateBanner(banner)
}
// 功能入口点击
async function onEntryClick(entry) {
switch (entry.key) {
case 'membership':
uni.navigateTo({ url: '/pages/membership/membership' })
break
case 'stamps':
uni.navigateTo({ url: '/pages/stamps/stamps' })
break
case 'qrcode':
await handleQrcodeEntry()
break
case 'guide':
await handleGuideEntry()
break
}
}
// 会员二维码入口:判断会员状态
async function handleQrcodeEntry() {
if (!userStore.isLoggedIn) {
uni.navigateTo({ url: '/pages/login/login' })
return
}
try {
await userStore.fetchUserInfo()
if (userStore.userInfo?.isMember) {
showQrcode.value = true
} else {
uni.navigateTo({ url: '/pages/membership/membership' })
}
} catch (e) {}
}
// 使用说明入口
async function handleGuideEntry() {
try {
const res = await getCouponGuide()
guideContent.value = res.data?.content || res.data || ''
showGuide.value = true
} catch (e) {}
}
// 格式化到期时间
function formatExpire(dateStr) {
if (!dateStr) return '无限制'
const d = new Date(dateStr)
return `${d.getFullYear()}${d.getMonth() + 1}${d.getDate()}`
}
// 点击兑换按钮
function onRedeemClick(coupon) {
selectedCoupon.value = coupon
showRedeem.value = true
}
// 确认兑换
async function onRedeemConfirm() {
if (!selectedCoupon.value) return
try {
await redeemCoupon(selectedCoupon.value.couponId)
showRedeem.value = false
selectedCoupon.value = null
uni.showToast({ title: t('home.redeemSuccess'), icon: 'none' })
await loadCoupons()
} catch (e) {
showRedeem.value = false
}
}
// 加载数据
async function loadBanners() {
try {
const res = await getBanners()
banners.value = res.data || []
} catch (e) {}
}
async function loadEntries() {
try {
const res = await getEntries()
entries.value = res.data || []
} catch (e) {}
}
async function loadCoupons() {
try {
const res = await getRedeemableCoupons()
coupons.value = res.data || []
} catch (e) {}
}
onMounted(() => {
loadBanners()
loadEntries()
loadCoupons()
})
</script>
<style scoped>
.home-page {
min-height: 100vh;
background-color: #f5f0e8;
}
/* Banner */
.banner-wrap {
padding: 24rpx 24rpx 0;
}
.banner-swiper {
width: 100%;
height: 340rpx;
border-radius: 20rpx;
overflow: hidden;
}
.banner-image {
width: 100%;
height: 100%;
border-radius: 20rpx;
}
/* 成为会员入口 */
.membership-entry {
margin: 24rpx 24rpx 0;
background: linear-gradient(135deg, #4a5d4a, #3d4f3d);
border-radius: 20rpx;
padding: 30rpx 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.membership-entry-left {
display: flex;
align-items: center;
}
.membership-icon-wrap {
width: 64rpx;
height: 64rpx;
background: rgba(255, 255, 255, 0.15);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
}
.membership-icon-img {
width: 40rpx;
height: 40rpx;
}
.membership-text {
display: flex;
flex-direction: column;
}
.membership-title {
font-size: 30rpx;
color: #ffffff;
font-weight: 600;
margin-bottom: 4rpx;
}
.membership-sub {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.7);
}
.membership-arrow {
font-size: 40rpx;
color: rgba(255, 255, 255, 0.6);
}
/* 功能入口行 */
.entry-row {
display: flex;
padding: 24rpx 24rpx 0;
gap: 24rpx;
}
.entry-card {
flex: 1;
background-color: #ffffff;
border-radius: 20rpx;
padding: 36rpx 0;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
}
.entry-icon {
width: 72rpx;
height: 72rpx;
margin-bottom: 16rpx;
}
.entry-label {
font-size: 26rpx;
color: #333;
}
/* 优惠券标题行 */
.coupon-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 36rpx 24rpx 16rpx;
}
.coupon-title {
font-size: 32rpx;
color: #333;
font-weight: 600;
}
.coupon-guide-link {
display: flex;
align-items: center;
}
.guide-icon {
font-size: 28rpx;
color: #999;
margin-right: 6rpx;
}
.guide-text {
font-size: 24rpx;
color: #999;
}
/* 优惠券列表 */
.coupon-list {
padding: 0 24rpx 24rpx;
}
.coupon-card {
background-color: #ffffff;
border-radius: 20rpx;
padding: 32rpx;
margin-bottom: 20rpx;
display: flex;
align-items: flex-end;
justify-content: space-between;
}
.coupon-info {
flex: 1;
margin-right: 20rpx;
}
.coupon-name {
font-size: 34rpx;
color: #333;
font-weight: 600;
display: block;
margin-bottom: 16rpx;
}
.coupon-detail-row {
display: flex;
align-items: center;
margin-bottom: 8rpx;
}
.coupon-dot {
font-size: 14rpx;
color: #c9a96e;
margin-right: 10rpx;
}
.coupon-detail-label {
font-size: 24rpx;
color: #666;
}
.coupon-detail-value {
font-size: 24rpx;
color: #666;
}
.coupon-detail-value.highlight {
color: #c9a96e;
font-weight: 500;
}
/* 兑换按钮 */
.coupon-action {
flex-shrink: 0;
}
.redeem-btn {
background: linear-gradient(135deg, #d4a855, #c9a96e);
border-radius: 36rpx;
padding: 16rpx 40rpx;
}
.redeem-btn-text {
font-size: 26rpx;
color: #ffffff;
white-space: nowrap;
}
</style>