fix: 修改充值提醒,钱包显示
All checks were successful
构建并部署前端到生产环境 / build-and-deploy (push) Successful in 47s

This commit is contained in:
sexygoat
2026-04-15 12:09:00 +08:00
parent 1336b86fc2
commit 7ef8615103
3 changed files with 286 additions and 95 deletions

View File

@@ -34,6 +34,24 @@ export const walletApi = {
});
},
// 新的充值订单接口
getRechargeOrders(identifier, page, page_size, status) {
return request({
url: '/api/c/v1/recharge-orders',
method: 'GET',
data: { identifier, page, page_size, status }
});
},
// 获取充值订单详情
getRechargeOrderDetail(identifier, rechargeOrderId) {
return request({
url: `/api/c/v1/recharge-orders/${rechargeOrderId}`,
method: 'GET',
data: { identifier }
});
},
getTransactions(identifier, page, page_size, params = {}) {
return request({
url: '/api/c/v1/wallet/transactions',

View File

@@ -34,7 +34,8 @@
</view>
</view>
<swiper class="tab-swiper" :current="currentTab" @change="onSwiperChange" :circular="false" :acceleration="true">
<swiper class="tab-swiper" :current="currentTab" @change="onSwiperChange" :circular="false"
:acceleration="true">
<swiper-item class="swiper-item-content">
<scroll-view scroll-y class="list-content">
<view v-if="rechargeList.length === 0 && !rechargeLoading" class="empty-state">
@@ -45,8 +46,9 @@
<view v-else>
<view class="card record-card" v-for="(item, index) in rechargeList" :key="index">
<view class="record-header flex-row-sb">
<view class="record-no">{{ item.recharge_no }}</view>
<view class="tag-apple" :class="getRechargeStatusClass(item.status)">{{ item.status_name }}</view>
<view class="record-no">{{ item.recharge_order_no || item.recharge_no }}</view>
<view class="tag-apple" :class="getRechargeStatusClass(item.status)">
{{ item.status_name }}</view>
</view>
<view class="record-info">
<view class="info-row flex-row-sb">
@@ -59,7 +61,8 @@
</view>
<view class="info-row flex-row-sb">
<view class="info-label">自动购包</view>
<view class="info-value">{{ item.auto_purchase_status === '1' ? '已开启' : '未开启' }}</view>
<view class="info-value">
{{ getAutoPurchaseStatusText(item.auto_purchase_status) }}</view>
</view>
<view class="info-row flex-row-sb">
<view class="info-label">创建时间</view>
@@ -88,7 +91,8 @@
<view class="card record-card" v-for="(item, index) in transactionList" :key="index">
<view class="record-header flex-row-sb">
<view class="record-type">{{ item.type_name }}</view>
<view class="record-amount" :class="item.amount >= 0 ? 'text-success' : 'text-danger'">
<view class="record-amount"
:class="item.amount >= 0 ? 'text-success' : 'text-danger'">
{{ item.amount >= 0 ? '+' : '' }}{{ formatMoney(item.amount) }}
</view>
</view>
@@ -123,56 +127,53 @@
</swiper>
<!-- 充值弹窗 -->
<up-popup :show="showRechargeModal" mode="center" @close="showRechargeModal=false">
<view class="recharge-popup">
<view class="popup-header">
<view class="popup-title">账户充值</view>
<view class="popup-close" @tap="showRechargeModal=false">×</view>
</view>
<view class="recharge-amounts">
<view
class="amount-item"
v-for="item in rechargeAmounts"
:key="item.value"
:class="{ active: selectedAmount === item.value }"
@tap="selectAmount(item.value)"
>
<text class="amount-value">¥{{ formatDisplayMoney(item.label) }}</text>
<view>
<up-popup :show="showRechargeModal" mode="center" @close="showRechargeModal=false">
<view class="recharge-popup">
<view class="popup-header">
<view class="popup-title">账户充值</view>
<view class="popup-close" @tap="showRechargeModal=false">×</view>
</view>
<view
class="amount-item custom"
:class="{ active: isCustomAmount }"
@tap="selectCustomAmount"
>
<text class="amount-value">自定义</text>
<view class="recharge-amounts">
<view class="amount-item" v-for="item in rechargeAmounts" :key="item.value"
:class="{ active: selectedAmount === item.value }" @tap="selectAmount(item.value)">
<text class="amount-value">¥{{ formatDisplayMoney(item.label) }}</text>
</view>
<view class="amount-item custom" :class="{ active: isCustomAmount }"
@tap="selectCustomAmount">
<text class="amount-value">自定义</text>
</view>
</view>
<view class="custom-input" v-if="isCustomAmount">
<view class="input-label">请输入充值金额</view>
<up-input v-model="customAmount" type="number" placeholder="最低0.01元" border="surround" />
</view>
<view class="popup-footer">
<button class="btn-apple btn-secondary" @tap="showRechargeModal=false">取消</button>
<button class="btn-apple btn-primary" @tap="confirmRecharge">确认充值</button>
</view>
</view>
<view class="custom-input" v-if="isCustomAmount">
<view class="input-label">请输入充值金额</view>
<up-input
v-model="customAmount"
type="number"
placeholder="最低0.01元"
border="surround"
/>
</view>
<view class="popup-footer">
<button class="btn-apple btn-secondary" @tap="showRechargeModal=false">取消</button>
<button class="btn-apple btn-primary" @tap="confirmRecharge">确认充值</button>
</view>
</view>
</up-popup>
</up-popup>
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { walletApi } from '@/api/index.js';
import { useUserStore } from '@/store/index.js';
import {
ref,
reactive,
onMounted
} from 'vue';
import {
walletApi
} from '@/api/index.js';
import {
useUserStore
} from '@/store/index.js';
const userStore = useUserStore();
@@ -230,13 +231,30 @@
let selectedAmount = ref(null);
let isCustomAmount = ref(false);
let customAmount = ref('');
const rechargeAmounts = [
{ value: 1000, label: '10' },
{ value: 2000, label: '20' },
{ value: 5000, label: '50' },
{ value: 10000, label: '100' },
{ value: 20000, label: '200' },
{ value: 50000, label: '500' }
const rechargeAmounts = [{
value: 1000,
label: '10'
},
{
value: 2000,
label: '20'
},
{
value: 5000,
label: '50'
},
{
value: 10000,
label: '100'
},
{
value: 20000,
label: '200'
},
{
value: 50000,
label: '500'
}
];
const selectAmount = (value) => {
@@ -257,7 +275,10 @@
// 自定义金额
const customAmountNum = parseFloat(customAmount.value);
if (!customAmountNum || customAmountNum < 0.01) {
uni.showToast({ title: '请输入正确的充值金额', icon: 'none' });
uni.showToast({
title: '请输入正确的充值金额',
icon: 'none'
});
return;
}
amount = Math.round(customAmountNum * 100); // 转换为分
@@ -265,11 +286,17 @@
// 预设金额
amount = selectedAmount.value;
} else {
uni.showToast({ title: '请选择充值金额', icon: 'none' });
uni.showToast({
title: '请选择充值金额',
icon: 'none'
});
return;
}
uni.showLoading({ title: '处理中...', mask: true });
uni.showLoading({
title: '处理中...',
mask: true
});
try {
// 第一步:充值前校验
@@ -309,14 +336,20 @@
// 验证支付配置
if (!rechargeData.pay_config || !rechargeData.pay_config.package) {
uni.showToast({ title: '支付参数获取失败', icon: 'none' });
uni.showToast({
title: '支付参数获取失败',
icon: 'none'
});
return;
}
// 第三步:调起微信支付
// #ifdef H5
if (typeof WeixinJSBridge === 'undefined') {
uni.showToast({ title: '请在微信中打开', icon: 'none' });
uni.showToast({
title: '请在微信中打开',
icon: 'none'
});
return;
}
@@ -330,7 +363,10 @@
}, function(res) {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
console.log('支付成功', res);
uni.showToast({ title: '充值成功', icon: 'success' });
uni.showToast({
title: '充值成功',
icon: 'success'
});
setTimeout(() => {
loadWalletDetail();
rechargePage.value = 1;
@@ -338,10 +374,16 @@
loadRechargeList();
}, 1500);
} else if (res.err_msg === 'get_brand_wcpay_request:cancel') {
uni.showToast({ title: '已取消支付', icon: 'none' });
uni.showToast({
title: '已取消支付',
icon: 'none'
});
} else {
console.error('支付失败', res);
uni.showToast({ title: '支付失败', icon: 'none' });
uni.showToast({
title: '支付失败',
icon: 'none'
});
}
});
// #endif
@@ -355,7 +397,10 @@
paySign: rechargeData.pay_config.pay_sign,
success: (res) => {
console.log('支付成功', res);
uni.showToast({ title: '充值成功', icon: 'success' });
uni.showToast({
title: '充值成功',
icon: 'success'
});
setTimeout(() => {
loadWalletDetail();
rechargePage.value = 1;
@@ -366,9 +411,15 @@
fail: (err) => {
console.error('支付失败', err);
if (err.errMsg && (err.errMsg.includes('cancel') || err.errMsg.includes('用户取消'))) {
uni.showToast({ title: '已取消支付', icon: 'none' });
uni.showToast({
title: '已取消支付',
icon: 'none'
});
} else {
uni.showToast({ title: '支付失败', icon: 'none' });
uni.showToast({
title: '支付失败',
icon: 'none'
});
}
}
});
@@ -378,19 +429,34 @@
uni.hideLoading();
console.error('充值失败', e);
const errorMsg = e.msg || e.message || '充值失败,请稍后重试';
uni.showToast({ title: errorMsg, icon: 'none' });
uni.showToast({
title: errorMsg,
icon: 'none'
});
}
};
const getRechargeStatusClass = (status) => {
const classMap = {
'0': 'tag-warning',
'1': 'tag-success',
'2': 'tag-danger'
0: 'tag-warning', // 待支付
1: 'tag-success', // 已支付
2: 'tag-danger', // 已关闭
3: 'tag-danger' // 已退款
};
return classMap[status] || '';
};
const getAutoPurchaseStatusText = (status) => {
const statusMap = {
'pending': '处理中',
'success': '已完成',
'failed': '失败',
'1': '已开启', // 兼容旧版
'0': '未开启' // 兼容旧版
};
return statusMap[status] || '未开启';
};
const loadWalletDetail = async () => {
try {
const data = await walletApi.getDetail(userStore.state.identifier);
@@ -407,16 +473,39 @@
rechargeLoading.value = true;
try {
const data = await walletApi.getRecharges(
// 使用新的充值订单接口
const data = await walletApi.getRechargeOrders(
userStore.state.identifier,
rechargePage.value,
rechargePageSize
);
const newData = (data.items || []).map(item => ({
...item,
status_name: item.status === 1 ? '已支付' : item.status === 0 ? '待支付' : '已关闭'
}));
const newData = (data.items || []).map(item => {
// 后端已返回 status_name优先使用
if (item.status_name) {
return item;
}
// 兼容旧版本,手动映射
let statusName = '未知';
switch (item.status) {
case 0:
statusName = '待支付';
break;
case 1:
statusName = '已支付';
break;
case 2:
statusName = '已关闭';
break;
case 3:
statusName = '已退款';
break;
}
return {
...item,
status_name: statusName
};
});
if (append) {
rechargeList.push(...newData);
@@ -506,12 +595,18 @@
.balance-section {
margin-bottom: 28rpx;
.balance-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8rpx;
.balance-label { font-size: 24rpx; opacity: 0.75; }
.balance-label {
font-size: 24rpx;
opacity: 0.75;
}
.recharge-btn-wrapper {
.btn-recharge {
background: #fff;
@@ -522,11 +617,19 @@
border-radius: 40rpx;
font-weight: 600;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
&::after { border: none; }
&::after {
border: none;
}
}
}
}
.balance-amount { font-size: 72rpx; font-weight: 700; letter-spacing: -2rpx; }
.balance-amount {
font-size: 72rpx;
font-weight: 700;
letter-spacing: -2rpx;
}
}
.wallet-footer {
@@ -534,11 +637,27 @@
align-items: center;
padding-top: 24rpx;
border-top: 1rpx solid rgba(255, 255, 255, 0.2);
.footer-divider { width: 1rpx; height: 40rpx; background: rgba(255, 255, 255, 0.3); margin: 0 32rpx; }
.footer-divider {
width: 1rpx;
height: 40rpx;
background: rgba(255, 255, 255, 0.3);
margin: 0 32rpx;
}
.footer-item {
flex: 1;
.footer-label { font-size: 22rpx; opacity: 0.7; margin-bottom: 6rpx; }
.footer-value { font-size: 26rpx; font-weight: 600; }
.footer-label {
font-size: 22rpx;
opacity: 0.7;
margin-bottom: 6rpx;
}
.footer-value {
font-size: 26rpx;
font-weight: 600;
}
}
}
}
@@ -549,6 +668,7 @@
min-height: 0;
display: flex;
flex-direction: column;
.tab-header {
flex-shrink: 0;
display: flex;
@@ -556,6 +676,7 @@
background: #fff;
border-radius: 16rpx;
padding: 8rpx;
.tab-indicator {
position: absolute;
top: 8rpx;
@@ -566,6 +687,7 @@
transition: left 0.3s ease;
z-index: 0;
}
.tab-item {
flex: 1;
display: flex;
@@ -578,6 +700,7 @@
position: relative;
z-index: 1;
transition: color 0.3s;
&.active {
color: #fff;
font-weight: 600;
@@ -604,17 +727,45 @@
.record-card {
margin-bottom: 24rpx;
.record-header {
margin-bottom: 20rpx;
.record-no { font-size: 28rpx; font-weight: 600; color: var(--text-primary); }
.record-type { font-size: 28rpx; font-weight: 600; color: var(--text-primary); }
.record-amount { font-size: 32rpx; font-weight: 700; }
.record-no {
font-size: 28rpx;
font-weight: 600;
color: var(--text-primary);
}
.record-type {
font-size: 28rpx;
font-weight: 600;
color: var(--text-primary);
}
.record-amount {
font-size: 32rpx;
font-weight: 700;
}
}
.record-info {
.info-row {
padding: 10rpx 0;
.info-label { font-size: 24rpx; color: var(--text-tertiary); }
.info-value { font-size: 24rpx; color: var(--text-primary); max-width: 380rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.info-label {
font-size: 24rpx;
color: var(--text-tertiary);
}
.info-value {
font-size: 24rpx;
color: var(--text-primary);
max-width: 380rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
@@ -625,8 +776,18 @@
align-items: center;
justify-content: center;
padding: 120rpx 40rpx;
.empty-icon { font-size: 100rpx; margin-bottom: 24rpx; opacity: 0.5; }
.empty-title { font-size: 28rpx; font-weight: 600; color: var(--text-secondary); }
.empty-icon {
font-size: 100rpx;
margin-bottom: 24rpx;
opacity: 0.5;
}
.empty-title {
font-size: 28rpx;
font-weight: 600;
color: var(--text-secondary);
}
}
.load-more {

View File

@@ -142,16 +142,32 @@
);
// 判断是否为强充订单
if (orderResult.order_type === 'recharge' && orderResult.recharge && orderResult.pay_config) {
// 强充订单:显示提示信息并直接使用返回的 pay_config 支付
if (!orderResult.pay_config.package) {
if (orderResult.order_type === 'recharge' && orderResult.recharge) {
const recharge = orderResult.recharge;
// 检查充值订单状态
if (recharge.status === 2 || recharge.status === 3) {
// 已关闭或已退款的订单
uni.hideLoading();
showModal.value = false;
uni.showToast({
title: `充值订单${recharge.status_name},请重新下单`,
icon: 'none',
duration: 2000
});
return;
}
// 检查是否有支付配置(待支付状态应该有)
if (!orderResult.pay_config || !orderResult.pay_config.package) {
uni.hideLoading();
showModal.value = false;
uni.showToast({ title: '支付参数获取失败', icon: 'none' });
return;
}
// 获取强充信息
const forceRechargeAmount = orderResult.linked_package_info?.force_recharge_amount || 0;
const forceRechargeAmount = orderResult.linked_package_info?.force_recharge_amount || recharge.amount;
const packageNames = orderResult.linked_package_info?.package_names || [];
uni.hideLoading();
@@ -258,7 +274,6 @@
paySign: payConfig.pay_sign
}, function(res) {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
console.log('支付成功', res);
const successMsg = isForceRecharge ? '充值成功,套餐将自动购买' : '支付成功';
uni.showToast({ title: successMsg, icon: 'success', duration: 2000 });
setTimeout(() => {
@@ -267,7 +282,6 @@
} else if (res.err_msg === 'get_brand_wcpay_request:cancel') {
uni.showToast({ title: '已取消支付', icon: 'none' });
} else {
console.error('支付失败', res);
uni.showToast({ title: '支付失败', icon: 'none' });
}
});
@@ -281,7 +295,6 @@
signType: payConfig.sign_type,
paySign: payConfig.pay_sign,
success: (res) => {
console.log('支付成功', res);
const successMsg = isForceRecharge ? '充值成功,套餐将自动购买' : '支付成功';
uni.showToast({ title: successMsg, icon: 'success', duration: 2000 });
setTimeout(() => {
@@ -289,7 +302,6 @@
}, 1500);
},
fail: (err) => {
console.error('支付失败', err);
if (err.errMsg && (err.errMsg.includes('cancel') || err.errMsg.includes('用户取消'))) {
uni.showToast({ title: '已取消支付', icon: 'none' });
} else {