This commit is contained in:
@@ -55,10 +55,6 @@
|
||||
<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>
|
||||
@@ -173,6 +169,53 @@
|
||||
<button class="btn-apple btn-primary" :disabled="rechargeSubmitting" @tap="confirmRecharge">
|
||||
{{ rechargeSubmitting ? '处理中...' : '确认充值' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
|
||||
<up-popup :show="showRechargeOrderPaymentModal" mode="center" @close="closeRechargeOrderPaymentModal">
|
||||
<view class="recharge-popup recharge-order-payment-popup">
|
||||
<view class="popup-header">
|
||||
<view class="popup-title">选择支付方式</view>
|
||||
<view class="popup-close" @tap="closeRechargeOrderPaymentModal">×</view>
|
||||
</view>
|
||||
|
||||
<view class="package-summary">
|
||||
<view class="summary-label">充值订单</view>
|
||||
<view class="summary-name">{{ selectedRechargeOrder?.recharge_order_no || selectedRechargeOrder?.recharge_no || '充值订单' }}</view>
|
||||
<view class="summary-price">¥{{ formatMoney(selectedRechargeOrder?.amount) }}</view>
|
||||
<view class="summary-details">
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">充值金额</text>
|
||||
<text class="detail-value">¥{{ formatMoney(selectedRechargeOrder?.amount) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="payment-methods">
|
||||
<view
|
||||
v-for="method in paymentMethodOptions"
|
||||
:key="method.value"
|
||||
class="method-item"
|
||||
:class="{ active: rechargePaymentMethod === method.value }"
|
||||
@tap="selectRechargePaymentMethod(method.value)"
|
||||
>
|
||||
<view class="method-left">
|
||||
<image v-if="method.value === 'alipay'" class="method-icon" src="/static/支付宝支付.png" mode="aspectFit"></image>
|
||||
<image v-else-if="method.value === 'wechat'" class="method-icon" src="/static/微信支付.png" mode="aspectFit"></image>
|
||||
<image v-else class="method-icon" src="/static/wallet.png" mode="aspectFit"></image>
|
||||
<text class="method-name">{{ method.label }}</text>
|
||||
</view>
|
||||
<view class="method-radio" :class="{ checked: rechargePaymentMethod === method.value }"></view>
|
||||
</view>
|
||||
<view v-if="paymentMethodOptions.length === 0" class="method-empty">暂无可用支付方式</view>
|
||||
</view>
|
||||
|
||||
<view class="popup-footer">
|
||||
<button class="btn-apple btn-secondary" @tap="closeRechargeOrderPaymentModal">取消</button>
|
||||
<button class="btn-apple btn-primary" :disabled="rechargeOrderSubmittingKey !== null" @tap="confirmRechargeOrderPayment">
|
||||
{{ rechargeOrderSubmittingKey !== null ? '处理中...' : '确认支付' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
@@ -221,6 +264,8 @@
|
||||
const transactionPageSize = 5;
|
||||
|
||||
const showRechargeModal = ref(false);
|
||||
const showRechargeOrderPaymentModal = ref(false);
|
||||
const selectedRechargeOrder = ref(null);
|
||||
const selectedAmount = ref(null);
|
||||
const isCustomAmount = ref(false);
|
||||
const customAmount = ref('');
|
||||
@@ -251,15 +296,6 @@
|
||||
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',
|
||||
@@ -377,6 +413,12 @@
|
||||
rechargePaymentMethod.value = method;
|
||||
};
|
||||
|
||||
const closeRechargeOrderPaymentModal = () => {
|
||||
if (rechargeOrderSubmittingKey.value !== null) return;
|
||||
showRechargeOrderPaymentModal.value = false;
|
||||
selectedRechargeOrder.value = null;
|
||||
};
|
||||
|
||||
const loadWalletDetail = async () => {
|
||||
try {
|
||||
const data = await walletApi.getDetail(userStore.state.identifier);
|
||||
@@ -642,18 +684,27 @@
|
||||
applyRechargeCheck(await walletApi.rechargeCheck(userStore.state.identifier));
|
||||
} catch (error) {
|
||||
console.error('加载充值支付方式失败', error);
|
||||
uni.showToast({ title: error.msg || error.message || '加载支付方式失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const options = paymentMethodOptions.value;
|
||||
if (!paymentMethodOptions.value.length) {
|
||||
uni.showToast({ title: '暂无可用支付方式', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
rechargePaymentMethod.value = getDefaultPaymentMethod(rechargeAllowedPaymentMethods.value);
|
||||
selectedRechargeOrder.value = rechargeOrder;
|
||||
showRechargeOrderPaymentModal.value = true;
|
||||
};
|
||||
|
||||
uni.showActionSheet({
|
||||
itemList: options.map((item) => item.label),
|
||||
success: ({ tapIndex }) => {
|
||||
const selectedMethod = options[tapIndex]?.value;
|
||||
if (selectedMethod) {
|
||||
handleRechargePayment(rechargeOrder, selectedMethod);
|
||||
}
|
||||
}
|
||||
});
|
||||
const confirmRechargeOrderPayment = async () => {
|
||||
if (!selectedRechargeOrder.value || rechargeOrderSubmittingKey.value !== null) return;
|
||||
const rechargeOrder = selectedRechargeOrder.value;
|
||||
const paymentMethod = rechargePaymentMethod.value;
|
||||
showRechargeOrderPaymentModal.value = false;
|
||||
await handleRechargePayment(rechargeOrder, paymentMethod);
|
||||
if (rechargeOrderSubmittingKey.value === null) {
|
||||
selectedRechargeOrder.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRechargePayment = async (rechargeOrder, paymentMethod) => {
|
||||
@@ -1149,4 +1200,64 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.recharge-order-payment-popup {
|
||||
.package-summary {
|
||||
padding: 40rpx 30rpx 30rpx;
|
||||
background: linear-gradient(135deg, rgba(85, 171, 92, 0.05) 0%, rgba(85, 171, 92, 0.02) 100%);
|
||||
border-bottom: 1rpx solid var(--border-light);
|
||||
text-align: center;
|
||||
|
||||
.summary-label {
|
||||
font-size: 22rpx;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.summary-name {
|
||||
margin: 20rpx 0 0;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.summary-price {
|
||||
margin: 20rpx 0 24rpx;
|
||||
font-size: 56rpx;
|
||||
font-weight: 700;
|
||||
letter-spacing: -1rpx;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.summary-details {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx solid rgba(85, 171, 92, 0.1);
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
|
||||
.detail-label {
|
||||
font-size: 22rpx;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.payment-methods {
|
||||
padding: 30rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="filter-tabs">
|
||||
<view class="filter-tab-indicator" :style="filterIndicatorStyle"></view>
|
||||
<view
|
||||
v-for="(item, index) in filterOptions"
|
||||
:key="index"
|
||||
@@ -9,11 +10,18 @@
|
||||
@tap="onFilterChange(index)"
|
||||
>
|
||||
{{ item.label }}
|
||||
<view v-if="filterIndex === index" class="tab-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="scroll-container" :style="scrollViewStyle" @scrolltolower="loadMore">
|
||||
<swiper
|
||||
class="order-swiper"
|
||||
:current="filterIndex"
|
||||
:circular="false"
|
||||
:acceleration="true"
|
||||
@change="onFilterSwiperChange"
|
||||
>
|
||||
<swiper-item v-for="(item, index) in filterOptions" :key="item.value ?? 'all'" class="order-swiper-item">
|
||||
<scroll-view v-if="filterIndex === index" scroll-y class="scroll-container" :style="scrollViewStyle" @scrolltolower="loadMore">
|
||||
<view v-if="orderList.length === 0 && !loading" class="empty-state">
|
||||
<image class="empty-icon" src="/static/order.png" mode="aspectFit" alt="我的订单"></image>
|
||||
<view class="empty-title">暂无订单</view>
|
||||
@@ -66,7 +74,9 @@
|
||||
<text v-else>上拉加载更多</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</scroll-view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<RenewalPaymentPopup ref="renewalPopupRef" :identifier="userStore.state.identifier"
|
||||
paymentRefreshTarget="order-list" @completed="resetOrderListAndLoad" />
|
||||
@@ -106,6 +116,9 @@
|
||||
const scrollViewStyle = computed(() => scrollViewHeight.value > 0
|
||||
? { height: `${scrollViewHeight.value}px`, flex: 'none' }
|
||||
: {});
|
||||
const filterIndicatorStyle = computed(() => ({
|
||||
left: `calc(${filterIndex.value * 20}% + 8rpx)`
|
||||
}));
|
||||
|
||||
const updateScrollViewHeight = () => {
|
||||
const windowInfo = typeof uni.getWindowInfo === 'function'
|
||||
@@ -154,10 +167,18 @@
|
||||
};
|
||||
|
||||
const onFilterChange = (index) => {
|
||||
if (filterIndex.value === index) return;
|
||||
filterIndex.value = index;
|
||||
resetOrderListAndLoad();
|
||||
};
|
||||
|
||||
const onFilterSwiperChange = (event) => {
|
||||
const nextIndex = Number(event?.detail?.current);
|
||||
if (Number.isInteger(nextIndex) && nextIndex >= 0 && nextIndex < filterOptions.length) {
|
||||
onFilterChange(nextIndex);
|
||||
}
|
||||
};
|
||||
|
||||
const loadOrderList = async (append = false) => {
|
||||
if (loading.value || (append && noMore.value)) return;
|
||||
|
||||
@@ -238,6 +259,10 @@
|
||||
const showOrderPaymentMethods = (order) => {
|
||||
if (!order?.order_id) return;
|
||||
const packageNames = Array.isArray(order.package_names) ? order.package_names : [];
|
||||
if (String(order.payment_method || '').toLowerCase() === 'wallet') {
|
||||
renewalPopupRef.value?.payOrderDirectly(order.order_id, 'wallet');
|
||||
return;
|
||||
}
|
||||
renewalPopupRef.value?.openOrderPayment(order.order_id, packageNames);
|
||||
};
|
||||
|
||||
@@ -532,6 +557,59 @@
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.container .filter-tabs {
|
||||
position: relative;
|
||||
margin-top: 0;
|
||||
padding: 8rpx;
|
||||
background: #fff;
|
||||
border: 0;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.container .filter-tabs .tab-item {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-width: 0;
|
||||
padding: 20rpx 4rpx;
|
||||
color: var(--text-tertiary);
|
||||
font-size: 26rpx;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.container .filter-tabs .tab-item.active {
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.filter-tab-indicator {
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
width: calc(20% - 16rpx);
|
||||
height: calc(100% - 16rpx);
|
||||
background: var(--primary);
|
||||
border-radius: 12rpx;
|
||||
transition: left 0.3s ease;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.order-swiper,
|
||||
.order-swiper-item {
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.order-swiper {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.order-swiper-item {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
flex: 1 1 0;
|
||||
height: auto;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="package-tabs">
|
||||
<view class="package-tab-indicator" :style="{ left: packageTypeIndex === 0 ? '8rpx' : '50%' }"></view>
|
||||
<view
|
||||
v-for="tab in packageTypeTabs"
|
||||
:key="tab.value"
|
||||
@@ -12,34 +13,46 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="packageList.length === 0 && !loading" class="empty-state">
|
||||
<image class="empty-icon" src="/static/shop.png" mode="aspectFit" alt="套餐订购"></image>
|
||||
<view class="empty-title">暂无{{ activePackageType === 'formal' ? '正式套餐' : '加油包' }}</view>
|
||||
<view class="empty-desc">当前资产暂无适用的套餐</view>
|
||||
</view>
|
||||
<swiper
|
||||
class="package-swiper"
|
||||
:current="packageTypeIndex"
|
||||
:circular="false"
|
||||
:acceleration="true"
|
||||
@change="onPackageSwiperChange"
|
||||
>
|
||||
<swiper-item v-for="tab in packageTypeTabs" :key="tab.value" class="package-swiper-item">
|
||||
<scroll-view v-if="activePackageType === tab.value" scroll-y class="package-scroll">
|
||||
<view v-if="packageList.length === 0 && !loading" class="empty-state">
|
||||
<image class="empty-icon" src="/static/shop.png" mode="aspectFit" alt="套餐订购"></image>
|
||||
<view class="empty-title">暂无{{ activePackageType === 'formal' ? '正式套餐' : '加油包' }}</view>
|
||||
<view class="empty-desc">当前资产暂无适用的套餐</view>
|
||||
</view>
|
||||
|
||||
<view class="package-list">
|
||||
<view class="package-card" v-for="item in packageList" :key="item.package_id">
|
||||
<view class="package-info">
|
||||
<view class="package-name">{{ item.package_name }}</view>
|
||||
<view class="package-meta">
|
||||
<view class="package-meta-item">
|
||||
<image class="package-meta-icon" src="/static/有效期.png" mode="aspectFit" />
|
||||
<text>{{ formatValidity(item.validity_days) }}</text>
|
||||
</view>
|
||||
<view class="package-meta-item">
|
||||
<image class="package-meta-icon" src="/static/流量.png" mode="aspectFit" />
|
||||
<text>{{ formatData(item.data_allowance, item.data_unit) }}</text>
|
||||
<view v-else class="package-list">
|
||||
<view class="package-card" v-for="item in packageList" :key="item.package_id">
|
||||
<view class="package-info">
|
||||
<view class="package-name">{{ item.package_name }}</view>
|
||||
<view class="package-meta">
|
||||
<view class="package-meta-item">
|
||||
<image class="package-meta-icon" src="/static/有效期.png" mode="aspectFit" />
|
||||
<text>{{ formatValidity(item.validity_days) }}</text>
|
||||
</view>
|
||||
<view class="package-meta-item">
|
||||
<image class="package-meta-icon" src="/static/流量.png" mode="aspectFit" />
|
||||
<text>{{ formatData(item.data_allowance, item.data_unit) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="package-description">高速流量 · 全国通用 · 不限速</view>
|
||||
</view>
|
||||
<view class="package-side">
|
||||
<view class="package-price"><text class="price-symbol">¥</text>{{ formatPackagePrice(item.retail_price) }}</view>
|
||||
<button class="btn-apple btn-primary package-buy-button" @tap="buyPackage(item)">立即订购</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="package-description">高速流量 · 全国通用 · 不限速</view>
|
||||
</view>
|
||||
<view class="package-side">
|
||||
<view class="package-price"><text class="price-symbol">¥</text>{{ formatPackagePrice(item.retail_price) }}</view>
|
||||
<button class="btn-apple btn-primary package-buy-button" @tap="buyPackage(item)">立即订购</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<up-popup :show="showModal" mode="center" @close="showModal = false">
|
||||
<view class="payment-popup">
|
||||
@@ -121,6 +134,7 @@
|
||||
const renewalPackageIds = ref([]);
|
||||
const renewalPackageNames = ref([]);
|
||||
const activePackageType = ref('formal');
|
||||
const packageTypeIndex = ref(0);
|
||||
const packageTypeTabs = [
|
||||
{ label: '正式套餐', value: 'formal' },
|
||||
{ label: '加油包', value: 'addon' }
|
||||
@@ -217,11 +231,20 @@
|
||||
};
|
||||
|
||||
const switchPackageType = (packageType) => {
|
||||
const nextIndex = packageTypeTabs.findIndex((item) => item.value === packageType);
|
||||
if (nextIndex < 0) return;
|
||||
packageTypeIndex.value = nextIndex;
|
||||
if (packageType === activePackageType.value) return;
|
||||
activePackageType.value = packageType;
|
||||
loadPackages(packageType);
|
||||
};
|
||||
|
||||
const onPackageSwiperChange = (event) => {
|
||||
const nextIndex = Number(event?.detail?.current);
|
||||
const nextTab = packageTypeTabs[nextIndex];
|
||||
if (nextTab) switchPackageType(nextTab.value);
|
||||
};
|
||||
|
||||
const loadWalletBalance = async () => {
|
||||
try {
|
||||
const data = await walletApi.getDetail(userStore.state.identifier);
|
||||
@@ -683,6 +706,8 @@
|
||||
|
||||
/* Screenshot-aligned package list skin */
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18rpx;
|
||||
padding: 20rpx 24rpx 48rpx;
|
||||
min-height: 100vh;
|
||||
@@ -712,12 +737,55 @@
|
||||
}
|
||||
|
||||
.container .package-tab.active {
|
||||
background: #fff;
|
||||
box-shadow: 0 4rpx 14rpx rgba(31, 35, 41, 0.06);
|
||||
color: var(--primary);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.container .package-tabs {
|
||||
position: relative;
|
||||
padding: 8rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.container .package-tab {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 20rpx 0;
|
||||
color: var(--text-tertiary);
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.package-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;
|
||||
}
|
||||
|
||||
.package-swiper,
|
||||
.package-swiper-item,
|
||||
.package-scroll {
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.package-swiper {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.package-swiper-item,
|
||||
.package-scroll {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.package-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user