406 lines
10 KiB
Vue
406 lines
10 KiB
Vue
<template>
|
||
<view class="scroll-container theme-light">
|
||
<view class="recharge-page">
|
||
<MemberInfoSubNav title="储值卡充值" @back="goBack" />
|
||
<view class="recharge-page__body">
|
||
<!-- 当前余额 -->
|
||
<view class="rc-balance-card">
|
||
<view class="rc-balance-card__inner">
|
||
<text class="rc-balance-card__label">当前余额</text>
|
||
<view class="rc-balance-card__amount">
|
||
<text class="rc-balance-card__symbol">¥</text>
|
||
<text class="rc-balance-card__num">{{ currentBalance }}</text>
|
||
</view>
|
||
<text class="rc-balance-card__card-name">{{ currentCardName }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 充值档位 -->
|
||
<view class="rc-section">
|
||
<text class="rc-section__title">选择充值金额</text>
|
||
<view class="rc-grid">
|
||
<view
|
||
v-for="(item, index) in rechargeOptions"
|
||
:key="index"
|
||
class="rc-grid-item"
|
||
:class="{ 'rc-grid-item--selected': selectedIndex === index }"
|
||
@tap="selectRecharge(index)"
|
||
>
|
||
<view class="rc-grid-item__top">
|
||
<text class="rc-grid-item__amount">¥{{ item.amount }}</text>
|
||
</view>
|
||
<view class="rc-grid-item__bottom">
|
||
<text class="rc-grid-item__bonus">赠¥{{ item.bonus }}</text>
|
||
</view>
|
||
<view class="rc-grid-item__check" v-if="selectedIndex === index">
|
||
<text>✓</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 充值说明 -->
|
||
<view class="rc-rules">
|
||
<text class="rc-rules__title">充值说明</text>
|
||
<view class="rc-rules__list">
|
||
<view class="rc-rules__item">
|
||
<text>1. 充值金额实时到账,赠送金额同步到账</text>
|
||
</view>
|
||
<view class="rc-rules__item">
|
||
<text>2. 余额可用于购买会员卡、消费等</text>
|
||
</view>
|
||
<view class="rc-rules__item">
|
||
<text>3. 充值金额一经到账,不予退还</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部按钮 -->
|
||
<view class="rc-bottom">
|
||
<view class="rc-bottom__info">
|
||
<text class="rc-bottom__label">实付金额</text>
|
||
<view class="rc-bottom__amount">
|
||
<text class="rc-bottom__symbol">¥</text>
|
||
<text class="rc-bottom__num">{{ selectedAmount }}</text>
|
||
</view>
|
||
</view>
|
||
<view
|
||
class="rc-bottom__btn"
|
||
:class="{ 'rc-bottom__btn--disabled': loading }"
|
||
@tap="handleRecharge"
|
||
>
|
||
<text>{{ loading ? '处理中...' : '立即充值' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue'
|
||
import { onLoad } from '@dcloudio/uni-app'
|
||
import MemberInfoSubNav from '@/components/memberInfo/MemberInfoSubNav.vue'
|
||
import { getMemberId } from '@/utils/request.js'
|
||
import { checkPayPasswordSet, getStoredCardInfo } from '@/api/main.js'
|
||
import { navigateToPage, PAGE } from '@/common/constants/routes.js'
|
||
|
||
const currentBalance = ref('0.00')
|
||
const selectedIndex = ref(0)
|
||
const loading = ref(false)
|
||
|
||
const rechargeOptions = ref([
|
||
{ amount: 100, bonus: 10, payAmount: 100 },
|
||
{ amount: 200, bonus: 20, payAmount: 200 },
|
||
{ amount: 300, bonus: 30, payAmount: 300 }
|
||
])
|
||
|
||
const selectedAmount = computed(() => {
|
||
return rechargeOptions.value[selectedIndex.value]?.payAmount || 0
|
||
})
|
||
|
||
onLoad(() => {
|
||
loadBalance()
|
||
})
|
||
|
||
async function loadBalance() {
|
||
const memberId = getMemberId()
|
||
if (!memberId) return
|
||
|
||
try {
|
||
const res = await getStoredCardInfo(memberId)
|
||
const data = res.data || res
|
||
currentBalance.value = ((data.balance || 0).toFixed(2))
|
||
} catch (e) {
|
||
console.error('获取储值卡余额失败:', e)
|
||
}
|
||
}
|
||
|
||
function selectRecharge(index) {
|
||
selectedIndex.value = index
|
||
}
|
||
|
||
async function handleRecharge() {
|
||
if (loading.value) return
|
||
|
||
const memberId = getMemberId()
|
||
|
||
if (!memberId) {
|
||
uni.showToast({ title: '用户未登录', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
// 检查支付密码
|
||
uni.showLoading({ title: '检查中...' })
|
||
try {
|
||
const checkRes = await checkPayPasswordSet(memberId)
|
||
uni.hideLoading()
|
||
|
||
const isSet = checkRes.isSet || checkRes.data?.isSet || false
|
||
|
||
if (!isSet) {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '您尚未设置支付密码,请先设置支付密码后再进行充值',
|
||
confirmText: '去设置',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
navigateToPage(PAGE.SET_PAY_PASSWORD)
|
||
}
|
||
}
|
||
})
|
||
return
|
||
}
|
||
|
||
// 确认充值
|
||
const option = rechargeOptions.value[selectedIndex.value]
|
||
uni.showModal({
|
||
title: '确认充值',
|
||
content: `确认充值 ${option.amount} 元?\n到账金额:${option.amount + option.bonus} 元\n(含赠送 ${option.bonus} 元)`,
|
||
confirmText: '确认充值',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
await initiatePayment(option)
|
||
}
|
||
}
|
||
})
|
||
} catch (e) {
|
||
uni.hideLoading()
|
||
console.error('检查支付密码失败:', e)
|
||
uni.showToast({ title: '检查失败,请稍后重试', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
async function initiatePayment(option) {
|
||
const amount = option.payAmount
|
||
const desc = `储值卡充值:充${option.amount}得${option.amount + option.bonus}`
|
||
const remark = `rechargeAmount=${option.amount}&bonus=${option.bonus}`
|
||
|
||
uni.navigateTo({
|
||
url: `/pages/memberInfo/confirmPayment?orderType=STORED_CARD_RECHARGE&amount=${amount}&desc=${encodeURIComponent(desc)}&remark=${encodeURIComponent(remark)}&returnPage=memberCard&cardType=stored`,
|
||
fail: (err) => {
|
||
console.error('跳转支付页面失败:', err)
|
||
uni.showToast({ title: '跳转支付页面失败', icon: 'none' })
|
||
}
|
||
})
|
||
}
|
||
|
||
function goBack() {
|
||
uni.navigateBack()
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
@import '@/common/style/base.css';
|
||
@import '@/common/style/memberInfo/pages/page-reset.css';
|
||
@import '@/common/style/memberInfo/pages/sub-page-base.css';
|
||
|
||
.recharge-page {
|
||
min-height: 100vh;
|
||
background: #F5F7FA;
|
||
padding-bottom: 100px;
|
||
}
|
||
|
||
.recharge-page__body {
|
||
padding: 16px;
|
||
}
|
||
|
||
.rc-balance-card {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.rc-balance-card__inner {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
border-radius: 16px;
|
||
padding: 24px;
|
||
color: #FFFFFF;
|
||
}
|
||
|
||
.rc-balance-card__label {
|
||
font-size: 13px;
|
||
opacity: 0.8;
|
||
margin-bottom: 8px;
|
||
display: block;
|
||
}
|
||
|
||
.rc-balance-card__amount {
|
||
display: flex;
|
||
align-items: baseline;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.rc-balance-card__symbol {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
.rc-balance-card__num {
|
||
font-size: 36px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.rc-balance-card__card-name {
|
||
font-size: 13px;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.rc-section {
|
||
background: #FFFFFF;
|
||
border-radius: 12px;
|
||
padding: 16px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.rc-section__title {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
color: #1A202C;
|
||
margin-bottom: 16px;
|
||
display: block;
|
||
}
|
||
|
||
.rc-grid {
|
||
display: flex;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.rc-grid-item {
|
||
width: calc(33.33% - 8px);
|
||
background: #F7FAFC;
|
||
border: 2px solid transparent;
|
||
border-radius: 12px;
|
||
padding: 16px 8px;
|
||
text-align: center;
|
||
position: relative;
|
||
transition: all 0.2s;
|
||
|
||
&--selected {
|
||
background: #F0F4FF;
|
||
border-color: #1677FF;
|
||
}
|
||
}
|
||
|
||
.rc-grid-item__top {
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.rc-grid-item__amount {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
color: #1A202C;
|
||
}
|
||
|
||
.rc-grid-item__bottom {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.rc-grid-item__bonus {
|
||
font-size: 12px;
|
||
color: #E53E3E;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.rc-grid-item__check {
|
||
position: absolute;
|
||
top: -1px;
|
||
right: -1px;
|
||
width: 20px;
|
||
height: 20px;
|
||
background: #1677FF;
|
||
border-radius: 0 10px 0 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
text {
|
||
color: #FFFFFF;
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
|
||
.rc-rules {
|
||
background: #FFFFFF;
|
||
border-radius: 12px;
|
||
padding: 16px;
|
||
}
|
||
|
||
.rc-rules__title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #1A202C;
|
||
margin-bottom: 12px;
|
||
display: block;
|
||
}
|
||
|
||
.rc-rules__list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.rc-rules__item {
|
||
font-size: 13px;
|
||
color: #718096;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.rc-bottom {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: #FFFFFF;
|
||
padding: 12px 16px;
|
||
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.rc-bottom__info {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 8px;
|
||
}
|
||
|
||
.rc-bottom__label {
|
||
font-size: 13px;
|
||
color: #718096;
|
||
}
|
||
|
||
.rc-bottom__amount {
|
||
display: flex;
|
||
align-items: baseline;
|
||
}
|
||
|
||
.rc-bottom__symbol {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #E53E3E;
|
||
margin-right: 2px;
|
||
}
|
||
|
||
.rc-bottom__num {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
color: #E53E3E;
|
||
}
|
||
|
||
.rc-bottom__btn {
|
||
padding: 12px 32px;
|
||
background: #1677FF;
|
||
border-radius: 24px;
|
||
color: #FFFFFF;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
|
||
&--disabled {
|
||
opacity: 0.6;
|
||
}
|
||
}
|
||
</style>
|