Files
device-voice-h5/pages/my-wallet/my-wallet.vue
sexygoat 427b211295
All checks were successful
构建并部署前端到生产环境 / build-and-deploy (push) Successful in 1m0s
fix: pay
2026-05-22 15:16:22 +08:00

1113 lines
27 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="container">
<view class="pd-20">
<view class="wallet-card">
<view class="wallet-header">
<view class="header-left">
<view class="balance-label">账户余额</view>
<view class="balance-amount">¥{{ formatMoney(walletDetail.balance) }}</view>
</view>
<button class="btn-recharge" @tap="openRechargeModal">充值</button>
</view>
<view class="wallet-info">
<view class="info-item">
<view class="info-label">冻结金额</view>
<view class="info-value">¥{{ formatMoney(walletDetail.frozen_balance) }}</view>
</view>
</view>
</view>
<view class="tab-section">
<view class="tab-header">
<view class="tab-indicator" :style="{ left: currentTab === 0 ? '8rpx' : '50%' }"></view>
<view class="tab-item" :class="{ active: currentTab === 0 }" @tap="onTabTap(0)">
<text>充值订单</text>
</view>
<view class="tab-item" :class="{ active: currentTab === 1 }" @tap="onTabTap(1)">
<text>钱包流水</text>
</view>
</view>
<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" @scrolltolower="loadMoreRecharge">
<view v-if="rechargeList.length === 0 && !rechargeLoading" class="empty-state">
<view class="empty-icon">RCG</view>
<view class="empty-title">暂无充值订单</view>
</view>
<view v-else>
<view class="card record-card" v-for="item in rechargeList" :key="getRechargeOrderSubmitKey(item)">
<view class="record-header flex-row-sb">
<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">
<view class="info-label">充值金额</view>
<view class="info-value text-danger">+¥{{ formatMoney(item.amount) }}</view>
</view>
<view class="info-row flex-row-sb">
<view class="info-label">支付方式</view>
<view class="info-value">{{ formatPaymentMethod(item.payment_method) }}</view>
</view>
<view v-if="hasAutoPurchaseStatus(item)" class="info-row flex-row-sb">
<view class="info-label">自动购包</view>
<view class="info-value">{{ getAutoPurchaseStatusText(item.auto_purchase_status) }}</view>
</view>
<view class="info-row flex-row-sb">
<view class="info-label">创建时间</view>
<view class="info-value">{{ item.created_at }}</view>
</view>
</view>
<view v-if="item.status === 0" class="record-actions">
<button
class="btn-pay"
:disabled="rechargeOrderSubmittingKey !== null"
@tap="showRechargePaymentMethods(item)"
>
{{ isRechargeOrderSubmitting(item) ? '处理中...' : '立即支付' }}
</button>
</view>
</view>
<view class="load-more" v-if="rechargeList.length > 0">
<text v-if="rechargeLoading">加载中...</text>
<text v-else-if="rechargeNoMore">没有更多了</text>
</view>
</view>
</scroll-view>
</swiper-item>
<swiper-item class="swiper-item-content">
<scroll-view scroll-y class="list-content" @scrolltolower="loadMoreTransaction">
<view v-if="transactionList.length === 0 && !transactionLoading" class="empty-state">
<view class="empty-icon">WAL</view>
<view class="empty-title">暂无钱包流水</view>
</view>
<view v-else>
<view class="card record-card" v-for="item in transactionList" :key="`${item.type}-${item.created_at}-${item.amount}`">
<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'">
{{ item.amount >= 0 ? '+' : '' }}{{ formatMoney(item.amount) }}
</view>
</view>
<view class="record-info">
<view class="info-row flex-row-sb">
<view class="info-label">变动后余额</view>
<view class="info-value">¥{{ formatMoney(item.balance_after) }}</view>
</view>
<view class="info-row flex-row-sb" v-if="item.remark">
<view class="info-label">备注</view>
<view class="info-value">{{ item.remark }}</view>
</view>
<view class="info-row flex-row-sb">
<view class="info-label">发生时间</view>
<view class="info-value">{{ item.created_at }}</view>
</view>
</view>
</view>
<view class="load-more" v-if="transactionList.length > 0">
<text v-if="transactionLoading">加载中...</text>
<text v-else-if="transactionNoMore">没有更多了</text>
</view>
</view>
</scroll-view>
</swiper-item>
</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>
<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="最低 1 元" border="surround" />
</view>
<view class="payment-methods">
<view class="method-item" :class="{ active: rechargePaymentMethod === 'wechat' }" @tap="selectRechargePaymentMethod('wechat')">
<view class="method-left">
<image class="method-icon" src="/static/wechat.png" mode="aspectFit"></image>
<text class="method-name">微信支付</text>
</view>
<view class="method-radio" :class="{ checked: rechargePaymentMethod === 'wechat' }"></view>
</view>
<view class="method-item" :class="{ active: rechargePaymentMethod === 'alipay' }" @tap="selectRechargePaymentMethod('alipay')">
<view class="method-left">
<view class="method-icon method-badge method-badge-alipay"></view>
<text class="method-name">支付宝支付</text>
</view>
<view class="method-radio" :class="{ checked: rechargePaymentMethod === 'alipay' }"></view>
</view>
</view>
<view class="popup-footer">
<button class="btn-apple btn-secondary" @tap="showRechargeModal = false">取消</button>
<button class="btn-apple btn-primary" :disabled="rechargeSubmitting" @tap="confirmRecharge">
{{ rechargeSubmitting ? '处理中...' : '确认充值' }}
</button>
</view>
</view>
</up-popup>
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import { walletApi } from '@/api/index.js';
import { useUserStore } from '@/store/index.js';
import {
consumePendingPaymentRefresh,
handlePaymentError,
isValidAlipayPaymentLink,
isValidWechatPayConfig,
openAlipayPayment,
PAYMENT_REFRESH_TARGETS,
showPaymentToast,
wechatH5Pay
} from '@/utils/payment.js';
const userStore = useUserStore();
const currentTab = ref(0);
const walletDetail = reactive({
balance: 0,
frozen_balance: 0,
updated_at: ''
});
const rechargeList = reactive([]);
const rechargeLoading = ref(false);
const rechargeNoMore = ref(false);
const rechargePage = ref(1);
const rechargePageSize = 5;
const transactionList = reactive([]);
const transactionLoading = ref(false);
const transactionNoMore = ref(false);
const transactionPage = ref(1);
const transactionPageSize = 5;
const showRechargeModal = ref(false);
const selectedAmount = ref(null);
const isCustomAmount = ref(false);
const customAmount = ref('');
const rechargePaymentMethod = ref('wechat');
const rechargeSubmitting = ref(false);
const rechargeOrderSubmittingKey = ref(null);
const paymentMethodOptions = [
{ label: '微信支付', value: 'wechat' },
{ label: '支付宝支付', value: 'alipay' }
];
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 onTabTap = (index) => {
currentTab.value = index;
};
const onSwiperChange = (e) => {
currentTab.value = e.detail.current;
};
const formatMoney = (amount) => {
if (!amount && amount !== 0) return '0.00';
return (amount / 100).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
};
const formatDisplayMoney = (amount) => {
if (!amount && amount !== 0) return '0';
return parseFloat(amount).toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
};
const formatPaymentMethod = (method) => {
const labelMap = {
wechat: '微信支付',
alipay: '支付宝支付',
wallet: '钱包支付'
};
return labelMap[method] || method || '-';
};
const getRechargeStatusClass = (status) => {
const classMap = {
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 hasAutoPurchaseStatus = (item) => Object.prototype.hasOwnProperty.call(item, 'auto_purchase_status') &&
item.auto_purchase_status !== null &&
item.auto_purchase_status !== undefined &&
item.auto_purchase_status !== '';
const getRechargeOrderSubmitKey = (rechargeOrder) => rechargeOrder.recharge_order_no ||
rechargeOrder.recharge_no ||
rechargeOrder.recharge_id ||
rechargeOrder.order_id ||
`${rechargeOrder.amount}-${rechargeOrder.created_at}`;
const isRechargeOrderSubmitting = (rechargeOrder) => {
return rechargeOrderSubmittingKey.value === getRechargeOrderSubmitKey(rechargeOrder);
};
const resetRechargeListAndLoad = () => {
rechargePage.value = 1;
rechargeNoMore.value = false;
loadRechargeList();
};
const resetTransactionListAndLoad = () => {
transactionPage.value = 1;
transactionNoMore.value = false;
loadTransactionList();
};
const syncWalletStatus = () => {
loadWalletDetail();
resetRechargeListAndLoad();
resetTransactionListAndLoad();
};
const scheduleWalletStatusRefresh = () => {
uni.showToast({
title: '正在同步支付状态',
icon: 'none'
});
syncWalletStatus();
setTimeout(() => {
syncWalletStatus();
}, 1500);
setTimeout(() => {
syncWalletStatus();
}, 3000);
};
const openRechargeModal = () => {
rechargePaymentMethod.value = 'wechat';
showRechargeModal.value = true;
};
const selectAmount = (value) => {
selectedAmount.value = value;
isCustomAmount.value = false;
customAmount.value = '';
};
const selectCustomAmount = () => {
isCustomAmount.value = true;
selectedAmount.value = null;
};
const selectRechargePaymentMethod = (method) => {
rechargePaymentMethod.value = method;
};
const loadWalletDetail = async () => {
try {
const data = await walletApi.getDetail(userStore.state.identifier);
walletDetail.balance = data.balance || 0;
walletDetail.frozen_balance = data.frozen_balance || 0;
walletDetail.updated_at = data.updated_at || '';
} catch (error) {
console.error('加载钱包详情失败', error);
}
};
const normalizeRechargeItem = (item) => {
let formattedCreatedAt = item.created_at;
if (formattedCreatedAt && formattedCreatedAt.includes('T')) {
formattedCreatedAt = formattedCreatedAt.replace('T', ' ').slice(0, 19);
}
if (item.status_name) {
return {
...item,
created_at: formattedCreatedAt
};
}
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,
created_at: formattedCreatedAt
};
};
const loadRechargeList = async (append = false) => {
if (rechargeLoading.value || (append && rechargeNoMore.value)) return;
rechargeLoading.value = true;
try {
const data = await walletApi.getRechargeOrders(
userStore.state.identifier,
rechargePage.value,
rechargePageSize
);
const newData = (data.items || []).map(normalizeRechargeItem);
if (append) {
rechargeList.push(...newData);
} else {
rechargeList.splice(0, rechargeList.length, ...newData);
}
if (newData.length < rechargePageSize) {
rechargeNoMore.value = true;
} else {
rechargePage.value += 1;
}
} catch (error) {
console.error('加载充值记录失败', error);
}
rechargeLoading.value = false;
};
const normalizeTransactionItem = (item) => {
let formattedCreatedAt = item.created_at;
if (formattedCreatedAt && formattedCreatedAt.includes('T')) {
formattedCreatedAt = formattedCreatedAt.replace('T', ' ').slice(0, 19);
}
return {
...item,
type_name: item.type === 'recharge' ? '充值' : item.type === 'purchase' ? '消费' : '其他',
created_at: formattedCreatedAt
};
};
const loadTransactionList = async (append = false) => {
if (transactionLoading.value || (append && transactionNoMore.value)) return;
transactionLoading.value = true;
try {
const data = await walletApi.getTransactions(
userStore.state.identifier,
transactionPage.value,
transactionPageSize
);
const newData = (data.items || []).map(normalizeTransactionItem);
if (append) {
transactionList.push(...newData);
} else {
transactionList.splice(0, transactionList.length, ...newData);
}
if (newData.length < transactionPageSize) {
transactionNoMore.value = true;
} else {
transactionPage.value += 1;
}
} catch (error) {
console.error('加载钱包流水失败', error);
}
transactionLoading.value = false;
};
const handleRechargeResult = async (rechargeData, paymentMethod) => {
if (paymentMethod === 'wechat') {
if (!isValidWechatPayConfig(rechargeData?.pay_config)) {
uni.showToast({
title: '支付参数获取失败',
icon: 'none'
});
return;
}
try {
await wechatH5Pay(rechargeData.pay_config);
showPaymentToast(true, '充值成功');
setTimeout(() => {
syncWalletStatus();
}, 1500);
} catch (error) {
handlePaymentError(error);
}
return;
}
if (!isValidAlipayPaymentLink(rechargeData?.payment_link)) {
uni.showToast({
title: '支付链接获取失败',
icon: 'none'
});
return;
}
await openAlipayPayment(rechargeData.payment_link, PAYMENT_REFRESH_TARGETS.MY_WALLET);
};
const confirmRecharge = async () => {
if (rechargeSubmitting.value) return;
rechargeSubmitting.value = true;
let amount = 0;
if (isCustomAmount.value) {
const customAmountNum = parseFloat(customAmount.value);
if (!customAmountNum || customAmountNum < 1) {
showRechargeModal.value = false;
rechargeSubmitting.value = false;
setTimeout(() => {
uni.showToast({
title: '请输入正确的充值金额',
icon: 'none'
});
}, 50);
return;
}
amount = Math.round(customAmountNum * 100);
} else if (selectedAmount.value) {
amount = selectedAmount.value;
} else {
showRechargeModal.value = false;
rechargeSubmitting.value = false;
setTimeout(() => {
uni.showToast({
title: '请选择充值金额',
icon: 'none'
});
}, 50);
return;
}
uni.showLoading({
title: '处理中...',
mask: true
});
try {
const checkData = await walletApi.rechargeCheck(userStore.state.identifier);
if (checkData.need_force_recharge && amount < checkData.force_recharge_amount) {
uni.hideLoading();
uni.showModal({
title: '提示',
content: `${checkData.message || '当前需先充值最低金额'} ¥${(checkData.force_recharge_amount / 100).toFixed(2)}`,
showCancel: false
});
return;
}
if (amount < checkData.min_amount || amount > checkData.max_amount) {
uni.hideLoading();
showRechargeModal.value = false;
setTimeout(() => {
uni.showToast({
title: `充值金额范围:¥${(checkData.min_amount / 100).toFixed(2)} - ¥${(checkData.max_amount / 100).toFixed(2)}`,
icon: 'none',
duration: 2500
});
}, 50);
return;
}
const rechargeData = await walletApi.recharge(
userStore.state.identifier,
amount,
rechargePaymentMethod.value
);
uni.hideLoading();
showRechargeModal.value = false;
await handleRechargeResult(rechargeData, rechargePaymentMethod.value);
} catch (error) {
uni.hideLoading();
console.error('充值失败', error);
showRechargeModal.value = false;
setTimeout(() => {
uni.showToast({
title: error.msg || error.message || '充值失败,请稍后重试',
icon: 'none'
});
}, 50);
} finally {
rechargeSubmitting.value = false;
}
};
const showRechargePaymentMethods = (rechargeOrder) => {
if (rechargeOrderSubmittingKey.value !== null) return;
uni.showActionSheet({
itemList: paymentMethodOptions.map((item) => item.label),
success: ({ tapIndex }) => {
const selectedMethod = paymentMethodOptions[tapIndex]?.value;
if (selectedMethod) {
handleRechargePayment(rechargeOrder, selectedMethod);
}
}
});
};
const handleRechargePayment = async (rechargeOrder, paymentMethod) => {
if (rechargeOrderSubmittingKey.value !== null) return;
rechargeOrderSubmittingKey.value = getRechargeOrderSubmitKey(rechargeOrder);
uni.showLoading({
title: '处理中...',
mask: true
});
try {
const rechargeData = await walletApi.recharge(
userStore.state.identifier,
rechargeOrder.amount,
paymentMethod
);
uni.hideLoading();
await handleRechargeResult(rechargeData, paymentMethod);
} catch (error) {
uni.hideLoading();
console.error('获取支付配置失败', error);
uni.showToast({
title: error.msg || error.message || '支付失败,请稍后重试',
icon: 'none'
});
} finally {
rechargeOrderSubmittingKey.value = null;
}
};
const loadMoreRecharge = () => {
if (!rechargeNoMore.value) {
loadRechargeList(true);
}
};
const loadMoreTransaction = () => {
if (!transactionNoMore.value) {
loadTransactionList(true);
}
};
onShow(() => {
if (!consumePendingPaymentRefresh(PAYMENT_REFRESH_TARGETS.MY_WALLET)) {
return;
}
scheduleWalletStatusRefresh();
});
onMounted(() => {
syncWalletStatus();
});
</script>
<style lang="scss" scoped>
.container {
min-height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
box-sizing: border-box;
max-width: 750rpx;
margin: 0 auto;
padding: 0 !important;
width: 100%;
}
.wallet-card {
background: var(--primary);
border-radius: var(--radius-large);
padding: 40rpx;
color: #fff;
.wallet-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 32rpx;
.header-left {
flex: 1;
.balance-label {
font-size: 26rpx;
opacity: 0.85;
margin-bottom: 12rpx;
font-weight: 500;
}
.balance-amount {
font-size: 64rpx;
font-weight: 700;
letter-spacing: -2rpx;
line-height: 1;
}
}
.btn-recharge {
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border: 2rpx solid rgba(255, 255, 255, 0.3);
color: #fff;
font-size: 28rpx;
padding: 0 32rpx;
border-radius: 50rpx;
font-weight: 600;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
&::after {
border: none;
}
&:active {
background: rgba(255, 255, 255, 0.3);
transform: scale(0.95);
}
}
}
.wallet-info {
padding-top: 24rpx;
border-top: 1rpx solid rgba(255, 255, 255, 0.2);
.info-item {
display: flex;
align-items: center;
justify-content: space-between;
.info-label {
font-size: 24rpx;
opacity: 0.8;
font-weight: 500;
}
.info-value {
font-size: 28rpx;
font-weight: 600;
}
}
}
}
.tab-section {
margin-top: 32rpx;
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
.tab-header {
flex-shrink: 0;
display: flex;
position: relative;
background: #fff;
border-radius: 16rpx;
padding: 8rpx;
.tab-indicator {
position: absolute;
top: 8rpx;
width: calc(50% - 16rpx);
height: calc(100% - 16rpx);
background: var(--primary);
border-radius: 12rpx;
transition: left 0.3s ease;
z-index: 0;
}
.tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx 0;
font-size: 28rpx;
font-weight: 500;
color: var(--text-tertiary);
position: relative;
z-index: 1;
transition: color 0.3s;
&.active {
color: #fff;
font-weight: 600;
}
}
}
}
.tab-swiper {
margin-top: 20rpx;
min-height: 0;
height: calc(100vh - 500rpx);
}
.swiper-item-content {
height: calc(100vh - 500rpx);
}
.list-content {
height: calc(100vh - 500rpx);
padding-top: 16rpx;
box-sizing: border-box;
}
.record-card {
margin-bottom: 24rpx;
.record-header {
margin-bottom: 20rpx;
.record-no,
.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;
}
}
}
.record-actions {
margin-top: 20rpx;
padding-top: 20rpx;
border-top: 1rpx solid var(--border-light);
display: flex;
justify-content: flex-end;
.btn-pay {
background: var(--primary);
color: #fff;
border: none;
border-radius: 8rpx;
padding: 12rpx 32rpx;
font-size: 26rpx;
font-weight: 500;
width: 100%;
&[disabled] {
opacity: 0.7;
}
&::after {
border: none;
}
}
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 40rpx;
.empty-icon {
font-size: 56rpx;
margin-bottom: 24rpx;
opacity: 0.5;
letter-spacing: 4rpx;
}
.empty-title {
font-size: 28rpx;
font-weight: 600;
color: var(--text-secondary);
}
}
.load-more {
text-align: center;
padding: 32rpx;
color: var(--text-tertiary);
font-size: 24rpx;
}
.recharge-popup {
width: 600rpx;
max-width: 90vw;
background: var(--bg-primary);
border-radius: var(--radius-large);
overflow: hidden;
box-sizing: border-box;
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid var(--border-light);
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: var(--text-primary);
}
.popup-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
color: var(--text-tertiary);
cursor: pointer;
}
}
.recharge-amounts {
padding: 30rpx;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
.amount-item {
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx solid var(--border-light);
border-radius: var(--radius-medium);
cursor: pointer;
transition: all 0.3s;
&.active {
border-color: var(--primary);
background: rgba(0, 122, 255, 0.05);
.amount-value {
color: var(--primary);
font-weight: 600;
}
}
.amount-value {
font-size: 28rpx;
color: var(--text-primary);
}
}
}
.custom-input {
padding: 0 30rpx 30rpx;
.input-label {
font-size: 26rpx;
color: var(--text-secondary);
margin-bottom: 16rpx;
}
}
.payment-methods {
padding: 0 30rpx 30rpx;
.method-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx;
margin-bottom: 20rpx;
border: 2rpx solid var(--border-light);
border-radius: var(--radius-medium);
cursor: pointer;
transition: all 0.3s;
&:last-child {
margin-bottom: 0;
}
&.active {
border-color: var(--primary);
background: rgba(0, 122, 255, 0.05);
}
.method-left {
display: flex;
align-items: center;
gap: 20rpx;
.method-icon {
width: 64rpx;
height: 64rpx;
}
.method-badge {
display: flex;
align-items: center;
justify-content: center;
border-radius: 18rpx;
font-size: 32rpx;
font-weight: 700;
color: #fff;
}
.method-badge-alipay {
background: linear-gradient(135deg, #1677ff 0%, #45a5ff 100%);
box-shadow: 0 8rpx 20rpx rgba(22, 119, 255, 0.2);
}
.method-name {
font-size: 28rpx;
font-weight: 500;
color: var(--text-primary);
}
}
.method-radio {
width: 36rpx;
height: 36rpx;
border: 2rpx solid var(--gray-400);
border-radius: 50%;
position: relative;
transition: all 0.3s;
&.checked {
border-color: var(--primary);
background: var(--primary);
&::after {
content: '✓';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: 20rpx;
font-weight: 700;
}
}
}
}
}
.popup-footer {
display: flex;
gap: 20rpx;
padding: 30rpx;
border-top: 1rpx solid var(--border-light);
.btn-apple {
flex: 1;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-medium);
font-size: 28rpx;
font-weight: 500;
border: none;
&[disabled] {
opacity: 0.7;
}
&.btn-secondary {
background: var(--gray-200);
color: var(--text-primary);
}
&.btn-primary {
background: var(--primary);
color: #fff;
}
}
}
}
</style>