Files
taimed-international-app/pages/user/wallet/index.vue

524 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.
<template>
<view class="wallet-page">
<!-- 自定义导航栏 -->
<nav-bar :title="$t('user.subscribe')"></nav-bar>
<view class="wallet-content">
<!-- VIP套餐列表 -->
<view class="package-section">
<text class="section-title">{{ $t('user.subscribe') }}</text>
<view class="package-list">
<view
v-for="(pkg, index) in packages"
:key="pkg.id"
:class="['package-item', { active: selectedIndex === index }]"
@click="selectPackage(index)"
>
<view class="package-price">
<image src="/static/icon/currency.png" class="currency-icon" />
<text class="price">{{ pkg.dictType }}</text>
<text class="unit">NZD</text>
</view>
<text class="package-duration">{{ getDuration(pkg.remark) }}</text>
</view>
</view>
</view>
<!-- 支付方式 -->
<view class="payment-section">
<text class="section-title">{{ $t('user.paymentMethod') }}</text>
<radio-group class="payment-group">
<view class="payment-item">
<radio :checked="true" color="#54a966" />
<text>{{ paymentName }}</text>
</view>
</radio-group>
</view>
<!-- 协议勾选 -->
<view class="agreement-section">
<view class="agreement-checkbox" @click="toggleAgreement">
<view :class="['checkbox', { checked: agreed }]">
<wd-icon v-if="agreed" name="check" size="16px" color="#fff" />
</view>
<text class="agreement-text">
{{ $t('user.agreeText') }}
<text class="link" @click.stop="showAgreementModal">{{ $t('user.agreement') }}</text>
</text>
</view>
</view>
<!-- 订阅按钮 -->
<view class="subscribe-button">
<wd-button
type="success"
block
@click="handleSubscribe"
>
{{ $t('user.subscribe') }}
</wd-button>
</view>
</view>
<!-- 协议弹窗 -->
<wd-popup v-model="agreementShow" position="bottom" :closeable="true">
<view class="agreement-modal">
<view class="modal-title">{{ agreementData.title }}</view>
<scroll-view scroll-y class="modal-content">
<view v-html="agreementData.content"></view>
</scroll-view>
</view>
</wd-popup>
</view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useUserStore } from '@/stores/user'
import { getVipPackages, createOrder, verifyGooglePay, verifyIAP } from '@/api/modules/user'
import { commonApi } from '@/api/modules/common'
import type { IVipPackage } from '@/types/user'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const userStore = useUserStore()
// 导航栏高度
const statusBarHeight = ref(0)
const navbarHeight = ref('44px')
// VIP套餐列表
const packages = ref<IVipPackage[]>([])
const selectedIndex = ref<number | null>(null)
const selectedPackage = computed(() =>
selectedIndex.value !== null ? packages.value[selectedIndex.value] : null
)
// 平台和支付方式
const platform = ref('')
const paymentMethod = ref(5) // 3-iOS IAP, 5-Google Pay
const paymentName = computed(() =>
platform.value === 'android' ? 'Google Pay' : 'Apple Pay'
)
// 协议相关
const agreed = ref(false)
const agreementShow = ref(false)
const agreementData = ref({
title: '',
content: ''
})
// 订单号
const orderSn = ref('')
/**
* 获取导航栏高度
*/
const getNavbarHeight = () => {
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight || 0
let navBarHeight = 44
if (systemInfo.model.indexOf('iPhone') !== -1 && parseInt(systemInfo.model.slice(-2)) >= 11) {
navBarHeight = 48
}
const totalHeight = statusBarHeight.value + navBarHeight
navbarHeight.value = totalHeight + 'px'
}
/**
* 获取平台信息
*/
const getPlatform = () => {
const systemInfo = uni.getSystemInfoSync()
platform.value = systemInfo.platform === 'android' ? 'android' : 'ios'
paymentMethod.value = platform.value === 'android' ? 5 : 3
}
/**
* 获取VIP套餐列表
*/
const getPackages = async () => {
try {
uni.showLoading({
title: t('common.loading')
})
const res = await getVipPackages()
uni.hideLoading()
if (res.sysDictDatas && res.sysDictDatas.length > 0) {
packages.value = res.sysDictDatas
// 检查是否有缓存的选择
const cachedIndex = uni.getStorageSync('selectVipCard')
if (cachedIndex !== null && cachedIndex < packages.value.length) {
selectedIndex.value = cachedIndex
} else {
selectedIndex.value = 0
}
}
} catch (error) {
console.error('获取套餐列表失败:', error)
uni.hideLoading()
}
}
/**
* 选择套餐
*/
const selectPackage = (index: number) => {
selectedIndex.value = index
uni.setStorageSync('selectVipCard', index)
}
/**
* 获取时长文本
*/
const getDuration = (remark: string) => {
const days = parseInt(remark)
if (days === 30) {
return t('user.monthCard')
} else if (days === 90) {
return t('user.seasonCard')
} else if (days === 365) {
return t('user.yearCard')
}
return `${days} ${t('user.days')}`
}
/**
* 切换协议勾选
*/
const toggleAgreement = () => {
agreed.value = !agreed.value
uni.setStorageSync('Agreements_agreed', agreed.value)
}
/**
* 显示协议弹窗
*/
const showAgreementModal = async () => {
try {
const res = await commonApi.getAgreement(113)
if (res) {
let content = res.content || ''
content = content.replace(
/<h5>/g,
'<view style="font-weight: bold;font-size: 32rpx;margin-top: 20rpx;margin-bottom: 20rpx;">'
)
content = content.replace(/<\/h5>/g, '</view>')
agreementData.value = {
title: res.title,
content: content
}
agreementShow.value = true
}
} catch (error) {
console.error('获取协议失败:', error)
}
}
/**
* 订阅处理
*/
const handleSubscribe = async () => {
// 验证是否勾选协议
if (!agreed.value) {
uni.showToast({
title: t('user.agreeFirst'),
icon: 'none'
})
return
}
// 验证是否选择套餐
if (selectedIndex.value === null || !selectedPackage.value) {
uni.showToast({
title: t('user.selectPackage'),
icon: 'none'
})
return
}
try {
uni.showLoading({
title: t('common.loading')
})
// 创建订单
const orderData = {
paymentMethod: paymentMethod.value,
orderMoney: selectedPackage.value.money,
vipBuyConfigId: selectedPackage.value.priceTypeId,
orderType: 'abroadVip',
abroadVipId: selectedPackage.value.id
}
const orderRes = await createOrder(orderData)
if (orderRes.orderSn) {
orderSn.value = orderRes.orderSn
// 根据平台调起支付
if (platform.value === 'android') {
await initiateGooglePay()
} else {
await initiateIAP()
}
}
} catch (error) {
console.error('创建订单失败:', error)
uni.hideLoading()
}
}
/**
* Google Pay 支付Android
*/
const initiateGooglePay = async () => {
// TODO: 集成 Google Pay SDK
// 这里需要使用 sn-googlepay5 插件
uni.hideLoading()
uni.showToast({
title: 'Google Pay 功能开发中',
icon: 'none'
})
}
/**
* IAP 支付iOS
*/
const initiateIAP = async () => {
// TODO: 集成 IAP SDK
uni.hideLoading()
uni.showToast({
title: 'IAP 功能开发中',
icon: 'none'
})
}
/**
* 返回上一页
*/
const goBack = () => {
uni.navigateBack({
delta: 1
})
}
onMounted(() => {
getNavbarHeight()
getPlatform()
getPackages()
})
onUnmounted(() => {
// 清除缓存
uni.removeStorageSync('selectVipCard')
})
</script>
<style lang="scss" scoped>
$theme-color: #54a966;
.wallet-page {
min-height: 100vh;
background-color: #f7faf9;
}
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background-color: #fff;
border-bottom: 1rpx solid #e5e5e5;
.navbar-content {
display: flex;
align-items: center;
justify-content: center;
height: 44px;
position: relative;
.navbar-left {
position: absolute;
left: 10px;
display: flex;
align-items: center;
padding: 10rpx;
}
.navbar-title {
font-size: 16px;
font-weight: 700;
color: #333;
}
}
}
.wallet-content {
padding: 20rpx;
}
.section-title {
display: block;
font-size: 34rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.package-section {
background: #fff;
border-radius: 15rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.package-list {
display: flex;
flex-wrap: wrap;
margin: -10rpx;
}
.package-item {
width: calc(33.333% - 20rpx);
margin: 10rpx;
padding: 30rpx 0;
border: 2rpx solid #ebebeb;
border-radius: 10rpx;
text-align: center;
transition: all 0.3s;
&.active {
border-color: $theme-color;
background: rgba(84, 169, 102, 0.1);
}
.package-price {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
.currency-icon {
width: 25rpx;
height: 25rpx;
margin-right: 8rpx;
}
.price {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.unit {
font-size: 26rpx;
color: #333;
margin-left: 5rpx;
}
}
.package-duration {
display: block;
font-size: 26rpx;
color: #666;
}
}
.payment-section {
background: #fff;
border-radius: 15rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.payment-group {
margin-top: 20rpx;
}
.payment-item {
display: flex;
align-items: center;
padding: 10rpx 0;
text {
font-size: 30rpx;
color: #333;
margin-left: 10rpx;
}
}
.agreement-section {
padding: 20rpx 30rpx;
}
.agreement-checkbox {
display: flex;
align-items: flex-start;
.checkbox {
width: 36rpx;
height: 36rpx;
border: 2rpx solid #ddd;
border-radius: 4rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15rpx;
flex-shrink: 0;
margin-top: 2rpx;
&.checked {
background-color: $theme-color;
border-color: $theme-color;
}
}
.agreement-text {
flex: 1;
font-size: 28rpx;
color: #333;
line-height: 40rpx;
.link {
color: $theme-color;
}
}
}
.subscribe-button {
padding: 40rpx 30rpx 60rpx;
}
.agreement-modal {
padding: 40rpx 30rpx;
max-height: 70vh;
.modal-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
text-align: center;
margin-bottom: 30rpx;
}
.modal-content {
max-height: 60vh;
font-size: 28rpx;
color: #555;
line-height: 45rpx;
}
}
</style>