vending-machine/mobile/pages/membership/membership.vue
2026-04-08 21:03:13 +08:00

210 lines
4.6 KiB
Vue

<template>
<view class="membership-page">
<!-- 会员宣传长图 -->
<image
v-if="bannerUrl"
class="membership-banner"
:src="bannerUrl"
mode="widthFix"
/>
<!-- 会员商品区域 -->
<view class="products-section" v-if="products.length">
<view
v-for="product in products"
:key="product.productId"
class="product-card"
>
<view class="product-info">
<text class="product-name">{{ product.name }}</text>
<text class="product-price">¥{{ product.price }}</text>
</view>
<!-- 按钮状态逻辑 -->
<template v-if="product.type === 'monthly'">
<!-- 单月会员:已开通时隐藏 -->
<button
v-if="!isMember"
class="buy-btn"
@click="onPurchase(product)"
>
{{ t('membership.joinBtn') }}
</button>
<button
v-else
class="buy-btn disabled"
disabled
>
{{ t('membership.joinedBtn') }}
</button>
</template>
<template v-if="product.type === 'subscription'">
<!-- 订阅会员 -->
<button
v-if="!isSubscribed"
class="buy-btn subscribe-btn"
@click="onSubscribe(product)"
>
{{ t('membership.subscribeBtn') }}
</button>
<button
v-else
class="buy-btn disabled"
disabled
>
{{ t('membership.subscribedBtn') }}
</button>
</template>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useMembershipStore } from '../../stores/membership.js'
import { getMembershipBanner } from '../../api/content.js'
import { resolveImageUrl } from '../../utils/image.js'
const { t } = useI18n()
const membershipStore = useMembershipStore()
// 宣传Banner图URL
const bannerUrl = ref('')
// 商品列表
const products = computed(() => membershipStore.products)
// 是否为会员
const isMember = computed(() => {
return !!membershipStore.membershipInfo?.isMember
})
// 是否已订阅
const isSubscribed = computed(() => {
return !!membershipStore.subscriptionStatus?.isSubscribed
})
/**
* 购买单月会员
*/
async function onPurchase(product) {
try {
await membershipStore.purchase(product.productId, product.price)
uni.showToast({ title: t('membership.joinedBtn'), icon: 'success' })
} catch (e) {
if (e.message === 'cancelled') return
// 支付成功但后端确认失败
if (e.message?.includes('purchase')) {
uni.showToast({ title: t('membership.payConfirmPending'), icon: 'none' })
}
}
}
/**
* 订阅会员
*/
async function onSubscribe(product) {
try {
await membershipStore.subscribe(product.productId, product.price)
uni.showToast({ title: t('membership.subscribedBtn'), icon: 'success' })
} catch (e) {
if (e.message === 'cancelled') return
if (e.message?.includes('subscribe')) {
uni.showToast({ title: t('membership.payConfirmPending'), icon: 'none' })
}
}
}
/**
* 加载页面数据
*/
async function loadData() {
try {
const res = await getMembershipBanner()
bannerUrl.value = resolveImageUrl(res.data?.imageUrl || res.data || '')
} catch (e) { /* 错误已统一处理 */ }
try {
await membershipStore.fetchProducts()
} catch (e) { /* 错误已统一处理 */ }
try {
await membershipStore.fetchMembershipInfo()
} catch (e) { /* 错误已统一处理 */ }
try {
await membershipStore.fetchSubscriptionStatus()
} catch (e) { /* 错误已统一处理 */ }
}
onMounted(() => {
loadData()
})
</script>
<style scoped>
.membership-page {
min-height: 100vh;
background-color: #f5f5f5;
}
.membership-banner {
width: 100%;
}
.products-section {
padding: 30rpx 24rpx;
}
.product-card {
background-color: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.product-info {
display: flex;
flex-direction: column;
}
.product-name {
font-size: 30rpx;
color: #333;
margin-bottom: 8rpx;
}
.product-price {
font-size: 36rpx;
color: #ff6600;
font-weight: bold;
}
.buy-btn {
min-width: 200rpx;
height: 72rpx;
line-height: 72rpx;
text-align: center;
background-color: #ff6600;
color: #ffffff;
border-radius: 36rpx;
font-size: 28rpx;
border: none;
}
.subscribe-btn {
background-color: #6c5ce7;
}
.buy-btn.disabled {
background-color: #cccccc;
color: #999999;
}
</style>