269 行
6.8 KiB
Vue
269 行
6.8 KiB
Vue
<template>
|
||
<view class="recharge fc-page">
|
||
<!-- 规范化顶部导航 -->
|
||
<fc-navbar title="余额充值" theme="gradient" :show-back="true" />
|
||
<!-- 余额卡 -->
|
||
<view class="recharge__hero">
|
||
<text class="recharge__hero-label">账户余额(元)</text>
|
||
<text class="recharge__hero-num">{{ assets.balance != null ? assets.balance : '0.00' }}</text>
|
||
<text class="recharge__hero-tip">充值后可用于付费发布、会员开通与联系方式解锁</text>
|
||
</view>
|
||
|
||
<!-- 充值金额 -->
|
||
<view class="fc-card">
|
||
<text class="recharge__title">选择充值金额</text>
|
||
<view class="recharge__tiers">
|
||
<view v-for="t in tiers" :key="t.value" class="recharge__tier"
|
||
:class="{ 'recharge__tier--active': Number(amount) === t.value }" @click="pickTier(t.value)">
|
||
<text class="recharge__tier-amount">¥{{ t.value }}</text>
|
||
<text v-if="t.tag" class="recharge__tier-tag">{{ t.tag }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="recharge__custom">
|
||
<text class="recharge__custom-symbol">¥</text>
|
||
<input v-model="amount" class="recharge__custom-input" type="digit" placeholder="自定义充值金额(元)" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 支付方式:充值只支持微信支付(余额「充值」= 用余额给余额加钱,服务端已禁止,防资损) -->
|
||
<view class="fc-card">
|
||
<text class="recharge__title">支付方式</text>
|
||
<view class="recharge__pay">
|
||
<view class="recharge__pay-type recharge__pay-type--active">
|
||
<image class="fc-icon fc-icon--sm" :src="iconUrl('paywechat')" mode="aspectFit" />
|
||
<text class="recharge__pay-text">微信支付</text>
|
||
</view>
|
||
</view>
|
||
<view class="recharge__btn" @click="submit">
|
||
<text>立即充值 ¥{{ payAmount }}</text>
|
||
</view>
|
||
<text class="recharge__note">充值仅支持微信支付,支付成功后余额由微信回调自动入账(通常几秒内到账)。</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import FcNavbar from '@/components/fc-navbar/fc-navbar.vue'
|
||
import memberApi from '@/common/api/member.js'
|
||
import orderApi from '@/common/api/order.js'
|
||
import { iconUrl, loadUiConfig } from '@/common/store/ui.js'
|
||
import { pay } from '@/common/utils/pay.js'
|
||
import config from '@/common/config/index.js'
|
||
|
||
export default {
|
||
components: { FcNavbar },
|
||
data() {
|
||
return {
|
||
assets: {},
|
||
tiers: [
|
||
{ value: 50, tag: '' },
|
||
{ value: 100, tag: '推荐' },
|
||
{ value: 200, tag: '' },
|
||
{ value: 500, tag: '' },
|
||
{ value: 1000, tag: '' },
|
||
],
|
||
amount: '',
|
||
// 充值只允许微信支付(服务端 /order/recharge 已禁止 payType=balance,防「用余额给余额加钱」资损)
|
||
payType: config.constants.PAY_TYPE.WXPAY,
|
||
submitting: false,
|
||
}
|
||
},
|
||
computed: {
|
||
/** 待充值金额(展示用,保留两位小数) */
|
||
payAmount() {
|
||
const n = Number(this.amount)
|
||
return !n || n <= 0 ? '0.00' : n.toFixed(2)
|
||
},
|
||
},
|
||
onLoad() {
|
||
loadUiConfig()
|
||
this.loadAssets()
|
||
},
|
||
/** 从收银台返回时重新拉余额:微信回调入账后这里能刷新到最新值 */
|
||
onShow() {
|
||
this.loadAssets()
|
||
},
|
||
methods: {
|
||
iconUrl,
|
||
async loadAssets() {
|
||
try {
|
||
this.assets = await memberApi.assets()
|
||
} catch (e) {
|
||
this.assets = { balance: 0 }
|
||
}
|
||
},
|
||
pickTier(v) {
|
||
this.amount = String(v)
|
||
},
|
||
async submit() {
|
||
if (this.submitting) return
|
||
const num = Number(this.amount)
|
||
if (!num || num <= 0) {
|
||
uni.showToast({ title: '请输入正确的充值金额', icon: 'none' })
|
||
return
|
||
}
|
||
if (num > 100000) {
|
||
uni.showToast({ title: '单次充值不能超过 100000 元', icon: 'none' })
|
||
return
|
||
}
|
||
this.submitting = true
|
||
try {
|
||
const res = await orderApi.recharge({ amount: num, payType: this.payType })
|
||
// 微信支付:下单只落 pending,付款后由 /pay/notify 回调入账(余额不会立刻变化)
|
||
await pay({ orderNo: res.orderNo, payType: this.payType })
|
||
await this.loadAssets()
|
||
this.amount = ''
|
||
uni.showToast({ title: '支付成功,到账略有延迟', icon: 'none' })
|
||
} catch (e) {
|
||
uni.showToast({ title: e.message || '充值失败', icon: 'none' })
|
||
} finally {
|
||
this.submitting = false
|
||
}
|
||
},
|
||
},
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.recharge {
|
||
padding-bottom: 60rpx;
|
||
}
|
||
.recharge__hero {
|
||
background: $fc-gradient-hero;
|
||
padding: 48rpx 40rpx 60rpx;
|
||
border-radius: 0 0 40rpx 40rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
.recharge__hero-label {
|
||
color: $fc-text-secondary;
|
||
font-weight: 500;
|
||
font-size: $fc-font-sm;
|
||
}
|
||
.recharge__hero-num {
|
||
margin-top: 16rpx;
|
||
color: $fc-text;
|
||
letter-spacing: -1rpx;
|
||
font-size: 72rpx;
|
||
font-weight: 700;
|
||
}
|
||
.recharge__hero-tip {
|
||
margin-top: 16rpx;
|
||
color: $fc-text-placeholder;
|
||
font-size: $fc-font-xs;
|
||
}
|
||
.recharge__title {
|
||
font-size: $fc-font-lg;
|
||
font-weight: 700;
|
||
color: $fc-text;
|
||
}
|
||
.recharge__tiers {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 16rpx;
|
||
margin-top: 24rpx;
|
||
}
|
||
.recharge__tier {
|
||
position: relative;
|
||
padding: 28rpx 0;
|
||
border: 2rpx solid $fc-border;
|
||
border-radius: $fc-radius;
|
||
background: $fc-bg;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
&--active {
|
||
border-color: $fc-primary-text;
|
||
background: $fc-primary-lighter;
|
||
box-shadow: 0 4rpx 14rpx rgba(4, 135, 74, 0.15);
|
||
}
|
||
}
|
||
.recharge__tier-amount {
|
||
font-size: $fc-font-lg;
|
||
font-weight: 700;
|
||
color: $fc-text;
|
||
}
|
||
.recharge__tier-tag {
|
||
position: absolute;
|
||
top: -14rpx;
|
||
right: 10rpx;
|
||
padding: 2rpx 12rpx;
|
||
border-radius: $fc-radius-pill;
|
||
background: $fc-gradient-warm;
|
||
color: #ffffff;
|
||
font-size: 18rpx;
|
||
}
|
||
.recharge__custom {
|
||
margin-top: 8rpx;
|
||
height: 96rpx;
|
||
border-radius: $fc-radius;
|
||
background: $fc-bg;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 28rpx;
|
||
}
|
||
.recharge__custom-symbol {
|
||
font-size: 40rpx;
|
||
font-weight: 700;
|
||
color: $fc-price;
|
||
}
|
||
.recharge__custom-input {
|
||
flex: 1;
|
||
margin-left: 12rpx;
|
||
font-size: $fc-font-lg;
|
||
color: $fc-text;
|
||
}
|
||
.recharge__pay {
|
||
display: flex;
|
||
margin-top: 24rpx;
|
||
}
|
||
.recharge__pay-type {
|
||
flex: 1;
|
||
height: 80rpx;
|
||
border-radius: $fc-radius;
|
||
border: 2rpx solid $fc-border;
|
||
background: $fc-bg;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: $fc-font-sm;
|
||
color: $fc-text-regular;
|
||
&:first-child {
|
||
margin-right: 16rpx;
|
||
}
|
||
&--active {
|
||
border-color: $fc-primary-text;
|
||
background: $fc-primary-lighter;
|
||
color: $fc-primary-text;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
.recharge__pay-text {
|
||
margin-left: 10rpx;
|
||
}
|
||
.recharge__btn {
|
||
margin-top: 28rpx;
|
||
height: 90rpx;
|
||
border-radius: $fc-radius-pill;
|
||
background: $fc-gradient-primary;
|
||
color: $fc-primary-ink;
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 10rpx 28rpx rgba(4, 48, 30, 0.3);
|
||
}
|
||
.recharge__note {
|
||
display: block;
|
||
margin-top: 20rpx;
|
||
font-size: $fc-font-xs;
|
||
color: $fc-text-placeholder;
|
||
line-height: $fc-leading;
|
||
}
|
||
</style>
|