You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

736 lines
17 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="container">
<!-- 顶部余额显示 -->
<view class="balance-section">
<view class="balance-card">
<view class="balance-info">
<view class="balance-label">当前余额</view>
<view class="balance-amount">¥{{ memberInfo.wallet_balance || 0 }}</view>
</view>
<view class="balance-icon">
<uv-icon name="empty-coupon" color="#4285f4" size="40"></uv-icon>
</view>
</view>
</view>
<!-- 充值金额选择 -->
<view class="recharge-section">
<view class="section-title">选择充值金额</view>
<view class="amount-grid">
<view class="amount-item" :class="{ active: selectedAmount === item.value }"
v-for="item in rechargeAmounts" :key="item.value" @click="selectAmount(item.value)">
<view class="amount-value">¥{{ item.value }}</view>
<!-- <view v-if="item.bonus" class="amount-bonus">送{{ item.bonus }}元</view> -->
</view>
</view>
<!-- 自定义金额 -->
<view class="custom-amount">
<view class="custom-label">自定义金额</view>
<view class="custom-input">
<text class="currency">¥</text>
<input class="amount-input" v-model="customAmount" type="digit" placeholder="请输入充值金额"
@input="onCustomAmountInput" />
</view>
<!-- <view class="amount-tips">充值金额范围¥0.01 - ¥50000</view> -->
</view>
</view>
<!-- 支付方式 -->
<view class="payment-section">
<view class="section-title">选择支付方式</view>
<view class="payment-list">
<!-- 微信支付 -->
<view v-if="isWechat" class="payment-item" :class="{ active: selectedPayment === 'wxpay' }"
@click="selectPayment('wxpay')">
<view class="payment-info">
<uv-icon name="weixin-fill" color="#07c160" size="28"></uv-icon>
<view class="payment-details">
<view class="payment-name">微信支付</view>
<view class="payment-desc">安全快捷,支持零钱和银行卡</view>
</view>
</view>
<uv-icon name="checkmark-circle-fill" :color="selectedPayment === 'wxpay' ? '#4285f4' : '#ccc'"
size="20"></uv-icon>
</view>
<!-- 支付宝支付 - 仅H5显示 -->
<view v-if="showAlipay" class="payment-item" :class="{ active: selectedPayment === 'alipay' }"
@click="selectPayment('alipay')">
<view class="payment-info">
<uv-icon name="/static/imgs/alipay.png" color="#1677ff" size="28"></uv-icon>
<view class="payment-details">
<view class="payment-name">支付宝</view>
<view class="payment-desc">便民生活,轻松支付</view>
</view>
</view>
<uv-icon name="checkmark-circle-fill" :color="selectedPayment === 'alipay' ? '#4285f4' : '#ccc'"
size="20"></uv-icon>
</view>
</view>
</view>
<!-- 充值说明 -->
<view class="notice-section">
<view class="section-title">充值说明</view>
<view class="notice-list">
<view class="notice-item">
<uv-icon name="info-circle" color="#999" size="16"></uv-icon>
<text class="notice-text">充值金额将实时到账,可用于购买会员服务</text>
</view>
<view class="notice-item">
<uv-icon name="info-circle" color="#999" size="16"></uv-icon>
<text class="notice-text">充值金额不可提现,请根据需要合理充值</text>
</view>
<view class="notice-item">
<uv-icon name="info-circle" color="#999" size="16"></uv-icon>
<text class="notice-text">如有疑问,请联系客服处理</text>
</view>
</view>
</view>
<!-- 底部充值按钮 -->
<view class="bottom-section">
<view class="recharge-info">
<text class="recharge-text">充值金额:¥{{ finalAmount }}</text>
<!-- <text v-if="bonusAmount > 0" class="bonus-text">(含赠送¥{{ bonusAmount }}</text> -->
</view>
<uv-button type="primary" size="large" @click="confirmRecharge" :loading="recharging"
:disabled="finalAmount <= 0">
</uv-button>
</view>
</view>
</template>
<script setup>
import {
ref,
computed,
onMounted
} from "vue";
import {
onShow
} from "@dcloudio/uni-app";
import {
useAuthStore,
useUserStore
} from "@/store";
import RPC from "@/utils/request";
const authStore = useAuthStore();
const userStore = useUserStore();
const memberInfo = ref({})
const getMemberInfo = () => {
uni.showLoading();
RPC.post('/membership/user/info', {
user_id: userStore.userInfo.id
}).then(res => {
memberInfo.value = res || {}
}).finally(() => {
uni.hideLoading();
})
}
// 响应式数据
const currentBalance = ref(0);
const selectedAmount = ref(0);
const customAmount = ref('');
const selectedPayment = ref('wxpay');
const recharging = ref(false);
const showAlipay = ref(true);
const isWechat = ref(false)
// 充值金额选项
const rechargeAmounts = ref([{
value: 10,
bonus: 0
},
{
value: 50,
bonus: 0
},
{
value: 100,
bonus: 0
},
{
value: 200,
bonus: 0
},
{
value: 500,
bonus: 0
},
{
value: 1000,
bonus: 0
}
]);
// 计算最终充值金额
const finalAmount = computed(() => {
if (customAmount.value && parseFloat(customAmount.value) > 0) {
return parseFloat(customAmount.value);
}
return selectedAmount.value;
});
// 计算赠送金额
const bonusAmount = computed(() => {
if (selectedAmount.value > 0) {
const selected = rechargeAmounts.value.find(item => item.value === selectedAmount.value);
return selected ? selected.bonus : 0;
}
return 0;
});
// 选择充值金额
const selectAmount = (amount) => {
selectedAmount.value = amount;
customAmount.value = '';
};
// 自定义金额输入
const onCustomAmountInput = () => {
selectedAmount.value = 0;
// 限制输入范围
const amount = parseFloat(customAmount.value);
if (amount > 50000) {
customAmount.value = '50000';
uni.showToast({
title: '充值金额不能超过50000元',
icon: 'none'
});
}
};
// 选择支付方式
const selectPayment = (payment) => {
selectedPayment.value = payment;
};
// 检查环境
const checkEnvironment = () => {
// #ifdef MP-WEIXIN
// 微信小程序环境,隐藏支付宝选项
showAlipay.value = false;
isWechat.value = true;
selectedPayment.value = 'wxpay';
// #endif
// #ifdef H5
// H5环境显示所有支付选项
showAlipay.value = true;
selectedPayment.value = 'alipay';
// #endif
};
// 微信支付
const wechatPay = async (orderInfo) => {
// #ifdef MP-WEIXIN
// 微信小程序支付
try {
const paymentResult = await uni.requestPayment({
provider: 'wxpay',
timeStamp: orderInfo.timeStamp,
nonceStr: orderInfo.nonceStr,
package: orderInfo.package,
signType: orderInfo.signType,
paySign: orderInfo.paySign
});
console.log('微信支付成功:', paymentResult);
return {
success: true,
data: paymentResult
};
} catch (error) {
console.error('微信支付失败:', error);
if (error.errMsg === 'requestPayment:fail cancel') {
throw new Error('用户取消支付');
}
throw new Error('微信支付失败');
}
// #endif
// #ifdef H5
// H5微信支付
if (typeof WeixinJSBridge !== 'undefined') {
return new Promise((resolve, reject) => {
WeixinJSBridge.invoke('getBrandWCPayRequest', {
appId: orderInfo.appId,
timeStamp: orderInfo.timeStamp,
nonceStr: orderInfo.nonceStr,
package: orderInfo.package,
signType: orderInfo.signType,
paySign: orderInfo.paySign
}, (res) => {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
resolve({
success: true,
data: res
});
} else if (res.err_msg === 'get_brand_wcpay_request:cancel') {
reject(new Error('用户取消支付'));
} else {
reject(new Error('微信支付失败'));
}
});
});
} else {
// 不在微信环境,跳转到支付页面
const payUrl = orderInfo.mwebUrl;
window.location.href = payUrl;
return {
success: true,
redirect: true
};
}
// #endif
};
// 支付宝支付仅H5环境
const alipayPay = async (orderInfo) => {
// #ifdef H5
// H5支付宝支付
const payUrl = orderInfo.pay_url;
window.location.href = payUrl;
return {
success: true,
redirect: true
};
// #endif
};
// 确认充值
const confirmRecharge = async () => {
if (finalAmount.value <= 0) {
uni.showToast({
title: '请选择充值金额',
icon: 'none'
});
return;
}
// if (finalAmount.value < 1) {
// uni.showToast({
// title: '充值金额不能少于1元',
// icon: 'none'
// });
// return;
// }
if (!selectedPayment.value) {
uni.showToast({
title: '请选择支付方式',
icon: 'none'
});
return;
}
recharging.value = true;
try {
// 创建充值订单参数
// const orderParams = {
// type: 'recharge',
// amount: finalAmount.value,
// bonusAmount: bonusAmount.value,
// paymentMethod: selectedPayment.value,
// userId: userStore.userInfo?.id || 'mock_user_id',
// // #ifdef MP-WEIXIN
// platform: 'mp-weixin',
// // #endif
// // #ifdef H5
// platform: 'h5',
// // #endif
// };
uni.showLoading({
title: '正在创建订单...'
});
// 调用后端创建充值订单API
const orderNum = await RPC.post('/wallet/recharge', {
user_id: userStore.userInfo.id,
amount_cents: finalAmount.value * 100,
pay_channel: selectedPayment.value
})
console.log('orderNum', orderNum)
// 模拟创建订单返回的数据
// const orderResult = {
// orderId: 'RECHARGE_' + Date.now(),
// amount: orderParams.amount,
// // 微信支付参数
// wechatPayInfo: {
// appId: 'wx1234567890',
// timeStamp: String(Date.now()),
// nonceStr: 'random_string_' + Date.now(),
// package: 'prepay_id=wx_prepay_id_123456',
// signType: 'RSA',
// paySign: 'mock_pay_sign_123456',
// mwebUrl: 'https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx_prepay_id_123456'
// },
// // 支付宝支付参数
// alipayInfo: {
// payUrl: 'https://openapi.alipay.com/gateway.do?mock_recharge_url'
// }
// };
uni.hideLoading();
// 根据支付方式调用对应的支付方法
let paymentResult;
if (selectedPayment.value === 'wxpay') {
const res = await uni.login({
provider: 'weixin'
})
const orderResult = await RPC.post('/membership/order/pay_by_wechat_mini', {
code: res.code,
"order_no": orderNum.order_no
})
uni.showLoading({
title: "正在跳转微信支付...",
});
console.log('orderResult', orderResult)
paymentResult = await wechatPay(orderResult);
} else if (selectedPayment.value === 'alipay') {
const orderResult = await RPC.post('/membership/order/pay_by_alipay', {
order_no: orderNum.order_no
})
uni.showLoading({
title: '正在跳转支付宝...'
});
paymentResult = await alipayPay(orderResult);
}
uni.hideLoading();
// 处理支付结果
if (paymentResult.success) {
if (paymentResult.redirect) {
// H5跳转支付
uni.showToast({
title: '正在跳转支付页面...',
icon: 'loading',
duration: 2000
});
} else {
// 支付成功
uni.showToast({
title: '充值成功',
icon: 'success'
});
// 重置选择
selectedAmount.value = 0;
customAmount.value = '';
// setTimeout(() => {
// getMemberInfo()
// },1000)
// 延迟返回上一页
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
}
} catch (error) {
uni.hideLoading();
console.error('充值失败:', error);
let errorMessage = '充值失败,请重试';
if (error.message === '用户取消支付') {
errorMessage = '支付已取消';
} else if (error.message) {
errorMessage = error.message;
}
uni.showToast({
title: errorMessage,
icon: 'none'
});
} finally {
recharging.value = false;
}
};
// 处理支付回调
const handlePaymentCallback = () => {
// #ifdef H5
const urlParams = new URLSearchParams(window.location.search);
const orderId = urlParams.get('orderId');
const payResult = urlParams.get('payResult');
if (orderId && payResult) {
// 清理URL参数
const url = new URL(window.location);
url.searchParams.delete('orderId');
url.searchParams.delete('payResult');
window.history.replaceState({}, document.title, url.pathname + url.search);
// 检查支付结果
if (payResult === 'success') {
uni.showToast({
title: '充值成功',
icon: 'success'
});
// 刷新余额
loadUserBalance();
// 重置选择
selectedAmount.value = 0;
customAmount.value = '';
} else {
uni.showToast({
title: '充值失败或取消',
icon: 'none'
});
}
}
// #endif
};
// 加载用户余额
const loadUserBalance = async () => {
try {
// 调用获取用户余额API
// const result = await RPC.get('/api/user/balance');
// currentBalance.value = result.data.balance;
} catch (error) {
console.error('加载余额失败:', error);
}
};
onMounted(() => {
checkEnvironment();
// loadUserBalance();
// handlePaymentCallback();
});
onShow(() => {
handlePaymentCallback();
getMemberInfo()
});
</script>
<style lang="scss" scoped>
.container {
min-height: 100vh;
background-color: #f8f8f8;
padding-bottom: 120px;
}
.balance-section {
padding: 20px 16px;
background: linear-gradient(135deg, #4285f4 0%, #6fa8f5 100%);
.balance-card {
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 20px;
.balance-info {
.balance-label {
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
margin-bottom: 8px;
}
.balance-amount {
color: white;
font-size: 32px;
font-weight: bold;
}
}
.balance-icon {
opacity: 0.8;
}
}
}
.recharge-section,
.payment-section,
.notice-section {
margin: 16px;
background: white;
border-radius: 12px;
padding: 20px;
.section-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 16px;
}
}
.amount-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-bottom: 20px;
.amount-item {
background: #f8f9fa;
border: 2px solid transparent;
border-radius: 8px;
padding: 16px 8px;
text-align: center;
transition: all 0.3s;
&.active {
border-color: #4285f4;
background-color: #f0f7ff;
}
.amount-value {
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 4px;
}
.amount-bonus {
font-size: 12px;
color: #ff4d4f;
}
}
}
.custom-amount {
.custom-label {
font-size: 14px;
color: #666;
margin-bottom: 12px;
}
.custom-input {
display: flex;
align-items: center;
background: #f8f9fa;
border-radius: 8px;
padding: 0 16px;
margin-bottom: 8px;
.currency {
font-size: 18px;
color: #333;
margin-right: 8px;
}
.amount-input {
flex: 1;
height: 48px;
font-size: 16px;
border: none;
background: transparent;
}
}
.amount-tips {
font-size: 12px;
color: #999;
}
}
.payment-list {
.payment-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border: 1px solid #e5e5e5;
border-radius: 8px;
margin-bottom: 12px;
transition: all 0.3s;
&.active {
border-color: #4285f4;
background-color: #f0f7ff;
}
&:last-child {
margin-bottom: 0;
}
.payment-info {
display: flex;
align-items: center;
gap: 12px;
.payment-details {
.payment-name {
font-size: 16px;
color: #333;
margin-bottom: 4px;
}
.payment-desc {
font-size: 12px;
color: #999;
}
}
}
}
}
.notice-list {
.notice-item {
display: flex;
align-items: flex-start;
gap: 8px;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
.notice-text {
flex: 1;
font-size: 13px;
color: #666;
line-height: 1.4;
}
}
}
.bottom-section {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
padding: 16px;
border-top: 1px solid #e5e5e5;
.recharge-info {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12px;
.recharge-text {
font-size: 16px;
color: #333;
font-weight: 500;
}
.bonus-text {
font-size: 14px;
color: #ff4d4f;
margin-left: 4px;
}
}
}
</style>