This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<UserInfoCard :currentCardNo="currentCardNo" :deviceInfo="deviceInfo" :onlineStatus="onlineStatus"
|
||||
:isDevice="userInfo.isDevice" :networkStatus="deviceInfo.network_status" />
|
||||
:isDevice="userInfo.isDevice" :networkStatus="deviceInfo.network_status"
|
||||
:isExpiring="deviceInfo.isExpiring" :expiryDays="deviceInfo.expiryDays" />
|
||||
|
||||
<DeviceStatusCard v-if="userInfo.isDevice" :deviceInfo="deviceInfo" :isRealName="isRealName"
|
||||
:isDevice="userInfo.isDevice" @authentication="enterDetail('authentication')" />
|
||||
@@ -156,6 +157,9 @@
|
||||
status: 1,
|
||||
packageName: '-',
|
||||
expireDate: '-',
|
||||
isExpiring: false,
|
||||
expiryDays: null,
|
||||
expiryEstimateStatus: 'none',
|
||||
walletBalance: 0,
|
||||
iccid: '-',
|
||||
currentIccid: '-',
|
||||
@@ -250,12 +254,16 @@
|
||||
deviceInfo.asset_type = data.asset_type || 'device';
|
||||
deviceInfo.bound_phone = data.bound_phone || '';
|
||||
deviceInfo.packageName = data.current_package || '-';
|
||||
deviceInfo.expireDate = formatDate(data.current_package_expires_at);
|
||||
deviceInfo.expireDate = formatDate(data.estimated_final_expires_at);
|
||||
deviceInfo.isExpiring = data.is_expiring === true;
|
||||
deviceInfo.expiryDays = data.days_until_final_expiry ?? null;
|
||||
deviceInfo.expiryEstimateStatus = data.expiry_estimate_status || 'none';
|
||||
deviceInfo.iccid = data.iccid || '-';
|
||||
deviceInfo.walletBalance = data.wallet_balance ?? 0;
|
||||
const currentCard = resolveCurrentCard(data.cards || []);
|
||||
isRealName.value = !!currentCard?.real_name_at || currentCard?.real_name_status === 1 || data.real_name_status === 1;
|
||||
isRealName.value = Number(data.real_name_status) === 1;
|
||||
realNameStatus.value = isRealName.value ? '已实名' : '未实名';
|
||||
userStore.setRealNameStatus(data.real_name_status);
|
||||
boundPhone.value = data.bound_phone || '';
|
||||
alreadyBindPhone.value = !!data.bound_phone;
|
||||
|
||||
@@ -312,7 +320,7 @@
|
||||
deviceInfo.signal_bad_reason = '';
|
||||
}
|
||||
|
||||
// 到期时间由 TrafficCard 组件获取套餐信息时一并返回
|
||||
// 最终到期时间和临期状态均以后端 asset/info 返回值为准。
|
||||
return data;
|
||||
} catch (e) {
|
||||
console.error('加载资产信息失败', e);
|
||||
@@ -342,18 +350,6 @@
|
||||
}
|
||||
};
|
||||
|
||||
// 处理套餐加载完成事件(从 TrafficCard 组件传递过来)
|
||||
const handlePackageLoaded = (activePackage) => {
|
||||
if (activePackage && activePackage.expires_at) {
|
||||
// 格式化到期时间
|
||||
const date = new Date(activePackage.expires_at);
|
||||
deviceInfo.expireDate =
|
||||
`${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||
} else {
|
||||
deviceInfo.expireDate = '-';
|
||||
}
|
||||
};
|
||||
|
||||
const modifyWifi = () => {
|
||||
wifi_info.ssid = deviceInfo.ssidName;
|
||||
wifi_info.pwd = deviceInfo.ssidPwd;
|
||||
@@ -810,7 +806,9 @@
|
||||
const loadUnreadNotifications = async () => {
|
||||
try {
|
||||
const data = await notificationApi.getList(1, 50, false);
|
||||
const items = (data?.items || []).filter(item => !item.is_read);
|
||||
const items = (data?.items || [])
|
||||
.filter(item => !item.is_read)
|
||||
.sort((a, b) => getNotificationPriority(b) - getNotificationPriority(a));
|
||||
if (!items.length) return;
|
||||
|
||||
notificationItems.value = items;
|
||||
@@ -823,6 +821,17 @@
|
||||
}
|
||||
};
|
||||
|
||||
const getNotificationPriority = (item) => {
|
||||
const severityRank = { info: 10, warning: 20, error: 30, critical: 40 };
|
||||
const remainingDays = Number(item?.days_until_expiry ?? item?.days_remaining ?? item?.remaining_days);
|
||||
const expiryLevel = String(item?.expiry_level || '').toLowerCase();
|
||||
if (item?.category === 'expiry' && ((Number.isFinite(remainingDays) && remainingDays >= 0 && remainingDays <= 3) ||
|
||||
['0_3', '0-3', '0~3', 'critical'].includes(expiryLevel))) {
|
||||
return 100;
|
||||
}
|
||||
return severityRank[item?.severity] || 0;
|
||||
};
|
||||
|
||||
const onNotificationChange = (event) => {
|
||||
const index = Number(event?.detail?.current ?? event?.current ?? 0);
|
||||
notificationPopupCurrent.value = index;
|
||||
|
||||
@@ -245,14 +245,27 @@
|
||||
|
||||
const doLogin = async () => {
|
||||
loading.value = true;
|
||||
// 丢弃上一轮未完成授权留下的临时凭证,避免校验失败时继续复用。
|
||||
if (typeof sessionStorage !== 'undefined') {
|
||||
sessionStorage.removeItem('assetToken');
|
||||
}
|
||||
try {
|
||||
const verifyData = await authApi.verifyAsset(identifier.value);
|
||||
if (!verifyData?.asset_token) {
|
||||
throw { msg: '资产校验未返回有效登录凭证' };
|
||||
}
|
||||
|
||||
userStore.setAssetToken(verifyData.asset_token);
|
||||
userStore.setIdentifier(identifier.value);
|
||||
|
||||
await redirectToWxAuth(verifyData.asset_token);
|
||||
} catch (e) {
|
||||
console.error('登录失败', e);
|
||||
uni.showToast({
|
||||
title: e?.msg || e?.message || '资产校验失败,请稍后重试',
|
||||
icon: 'none',
|
||||
duration: 2500
|
||||
});
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -155,13 +155,17 @@
|
||||
</view>
|
||||
|
||||
<view class="payment-methods">
|
||||
<view class="method-item" :class="{ active: rechargePaymentMethod === 'alipay' }" @tap="selectRechargePaymentMethod('alipay')">
|
||||
<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">
|
||||
<view class="method-icon method-badge method-badge-alipay">支</view>
|
||||
<text class="method-name">支付宝支付</text>
|
||||
<view v-if="method.value === 'alipay'" class="method-icon method-badge method-badge-alipay">支</view>
|
||||
<view v-else-if="method.value === 'wechat'" class="method-icon method-badge">微</view>
|
||||
<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 === 'alipay' }"></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">
|
||||
@@ -180,7 +184,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import { assetApi, walletApi } from '@/api/index.js';
|
||||
import { walletApi } from '@/api/index.js';
|
||||
import { useUserStore } from '@/store/index.js';
|
||||
import {
|
||||
consumePendingPaymentRefresh,
|
||||
@@ -192,6 +196,8 @@
|
||||
showPaymentToast,
|
||||
wechatH5Pay
|
||||
} from '@/utils/payment.js';
|
||||
import { formatMoney } from '@/utils/display.js';
|
||||
import { getDefaultPaymentMethod, getPaymentMethodOptions, normalizePaymentMethods } from '@/utils/payment-methods.js';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
@@ -221,11 +227,8 @@
|
||||
const rechargePaymentMethod = ref('alipay');
|
||||
const rechargeSubmitting = ref(false);
|
||||
const rechargeOrderSubmittingKey = ref(null);
|
||||
const isDeviceAsset = ref(true);
|
||||
let assetTypePromise = null;
|
||||
const paymentMethodOptions = computed(() => [
|
||||
{ label: '支付宝支付', value: 'alipay' }
|
||||
]);
|
||||
const rechargeAllowedPaymentMethods = ref([]);
|
||||
const paymentMethodOptions = computed(() => getPaymentMethodOptions(rechargeAllowedPaymentMethods.value));
|
||||
const rechargeAmounts = [
|
||||
{ value: 1000, label: '10' },
|
||||
{ value: 2000, label: '20' },
|
||||
@@ -243,11 +246,6 @@
|
||||
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, ',');
|
||||
@@ -298,34 +296,13 @@
|
||||
return rechargeOrderSubmittingKey.value === getRechargeOrderSubmitKey(rechargeOrder);
|
||||
};
|
||||
|
||||
const getDefaultRechargePaymentMethod = () => 'alipay';
|
||||
|
||||
const normalizeRechargePaymentMethod = () => {
|
||||
const defaultMethod = getDefaultRechargePaymentMethod();
|
||||
if (rechargePaymentMethod.value !== defaultMethod) {
|
||||
const defaultMethod = getDefaultPaymentMethod(rechargeAllowedPaymentMethods.value);
|
||||
if (!paymentMethodOptions.value.some((item) => item.value === rechargePaymentMethod.value)) {
|
||||
rechargePaymentMethod.value = defaultMethod;
|
||||
}
|
||||
};
|
||||
|
||||
const loadAssetType = async () => {
|
||||
if (!userStore.state.identifier) return;
|
||||
if (assetTypePromise) return assetTypePromise;
|
||||
|
||||
assetTypePromise = assetApi.getInfo(userStore.state.identifier)
|
||||
.then((data) => {
|
||||
isDeviceAsset.value = data.asset_type === 'device';
|
||||
normalizeRechargePaymentMethod();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('加载资产类型失败', error);
|
||||
})
|
||||
.finally(() => {
|
||||
assetTypePromise = null;
|
||||
});
|
||||
|
||||
return assetTypePromise;
|
||||
};
|
||||
|
||||
const resetRechargeListAndLoad = () => {
|
||||
rechargePage.value = 1;
|
||||
rechargeNoMore.value = false;
|
||||
@@ -339,7 +316,6 @@
|
||||
};
|
||||
|
||||
const syncWalletStatus = () => {
|
||||
loadAssetType();
|
||||
loadWalletDetail();
|
||||
resetRechargeListAndLoad();
|
||||
resetTransactionListAndLoad();
|
||||
@@ -359,10 +335,30 @@
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const applyRechargeCheck = (data) => {
|
||||
if (Array.isArray(data?.allowed_payment_methods)) {
|
||||
rechargeAllowedPaymentMethods.value = normalizePaymentMethods(data.allowed_payment_methods);
|
||||
} else {
|
||||
rechargeAllowedPaymentMethods.value = [];
|
||||
}
|
||||
normalizeRechargePaymentMethod();
|
||||
};
|
||||
|
||||
const openRechargeModal = async () => {
|
||||
await loadAssetType();
|
||||
rechargePaymentMethod.value = getDefaultRechargePaymentMethod();
|
||||
showRechargeModal.value = true;
|
||||
try {
|
||||
const data = await walletApi.rechargeCheck(userStore.state.identifier);
|
||||
applyRechargeCheck(data);
|
||||
if (!paymentMethodOptions.value.length) {
|
||||
uni.showToast({ title: '暂无可用支付方式', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
rechargePaymentMethod.value = getDefaultPaymentMethod(
|
||||
rechargeAllowedPaymentMethods.value
|
||||
);
|
||||
showRechargeModal.value = true;
|
||||
} catch (error) {
|
||||
console.error('充值前校验失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
const selectAmount = (value) => {
|
||||
@@ -377,7 +373,7 @@
|
||||
};
|
||||
|
||||
const selectRechargePaymentMethod = (method) => {
|
||||
if (method !== getDefaultRechargePaymentMethod()) return;
|
||||
if (!paymentMethodOptions.value.some((item) => item.value === method)) return;
|
||||
rechargePaymentMethod.value = method;
|
||||
};
|
||||
|
||||
@@ -504,6 +500,12 @@
|
||||
};
|
||||
|
||||
const handleRechargeResult = async (rechargeData, paymentMethod) => {
|
||||
if (paymentMethod === 'wallet') {
|
||||
showPaymentToast(true, '充值成功');
|
||||
setTimeout(() => syncWalletStatus(), 1500);
|
||||
return;
|
||||
}
|
||||
|
||||
if (paymentMethod === 'wechat') {
|
||||
if (!isValidWechatPayConfig(rechargeData?.pay_config)) {
|
||||
uni.showToast({
|
||||
@@ -577,23 +579,31 @@
|
||||
|
||||
try {
|
||||
const checkData = await walletApi.rechargeCheck(userStore.state.identifier);
|
||||
applyRechargeCheck(checkData);
|
||||
if (!paymentMethodOptions.value.some((item) => item.value === rechargePaymentMethod.value)) {
|
||||
throw { msg: '当前支付方式不可用,请重新选择' };
|
||||
}
|
||||
|
||||
if (checkData.need_force_recharge && amount < checkData.force_recharge_amount) {
|
||||
const forceAmount = Number(checkData.force_recharge_amount || 0);
|
||||
const minAmount = Number(checkData.min_amount || 0);
|
||||
const maxAmount = Number(checkData.max_amount || Number.MAX_SAFE_INTEGER);
|
||||
|
||||
if (checkData.need_force_recharge && amount < forceAmount) {
|
||||
uni.hideLoading();
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: `${checkData.message || '当前需先充值最低金额'} ¥${(checkData.force_recharge_amount / 100).toFixed(2)}`,
|
||||
content: `${checkData.message || '当前需先充值最低金额'} ¥${formatMoney(forceAmount)}`,
|
||||
showCancel: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (amount < checkData.min_amount || amount > checkData.max_amount) {
|
||||
if (amount < minAmount || amount > maxAmount) {
|
||||
uni.hideLoading();
|
||||
showRechargeModal.value = false;
|
||||
setTimeout(() => {
|
||||
uni.showToast({
|
||||
title: `充值金额范围:¥${(checkData.min_amount / 100).toFixed(2)} - ¥${(checkData.max_amount / 100).toFixed(2)}`,
|
||||
title: `充值金额范围:¥${formatMoney(minAmount)} - ¥${formatMoney(maxAmount)}`,
|
||||
icon: 'none',
|
||||
duration: 2500
|
||||
});
|
||||
@@ -628,7 +638,11 @@
|
||||
|
||||
const showRechargePaymentMethods = async (rechargeOrder) => {
|
||||
if (rechargeOrderSubmittingKey.value !== null) return;
|
||||
await loadAssetType();
|
||||
try {
|
||||
applyRechargeCheck(await walletApi.rechargeCheck(userStore.state.identifier));
|
||||
} catch (error) {
|
||||
console.error('加载充值支付方式失败', error);
|
||||
}
|
||||
const options = paymentMethodOptions.value;
|
||||
|
||||
uni.showActionSheet({
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
const getCategoryText = (category) => ({
|
||||
approval: '审批',
|
||||
expiry: '临期',
|
||||
exchange: '换货',
|
||||
sync: '同步',
|
||||
system: '系统'
|
||||
}[category] || '通知');
|
||||
@@ -68,6 +69,17 @@
|
||||
critical: '严重'
|
||||
}[severity] || '提示');
|
||||
|
||||
const getNotificationPriority = (item) => {
|
||||
const severityRank = { info: 10, warning: 20, error: 30, critical: 40 };
|
||||
const remainingDays = Number(item?.days_until_expiry ?? item?.days_remaining ?? item?.remaining_days);
|
||||
const expiryLevel = String(item?.expiry_level || '').toLowerCase();
|
||||
if (item?.category === 'expiry' && ((Number.isFinite(remainingDays) && remainingDays >= 0 && remainingDays <= 3) ||
|
||||
['0_3', '0-3', '0~3', 'critical'].includes(expiryLevel))) {
|
||||
return 100;
|
||||
}
|
||||
return severityRank[item?.severity] || 0;
|
||||
};
|
||||
|
||||
const loadUnreadCount = async () => {
|
||||
try {
|
||||
const data = await notificationApi.getUnreadCount();
|
||||
@@ -82,7 +94,7 @@
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await notificationApi.getList(page.value, pageSize);
|
||||
const items = data?.items || [];
|
||||
const items = (data?.items || []).sort((a, b) => getNotificationPriority(b) - getNotificationPriority(a));
|
||||
if (append) {
|
||||
notifications.push(...items);
|
||||
} else {
|
||||
|
||||
80
pages/order-detail/order-detail.vue
Normal file
80
pages/order-detail/order-detail.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view v-if="loading" class="state">加载中...</view>
|
||||
<view v-else-if="!order" class="state">订单信息不存在</view>
|
||||
<view v-else>
|
||||
<view class="card">
|
||||
<view class="card-title">订单信息</view>
|
||||
<view class="info-row"><text>订单号</text><text>{{ order.order_no || '-' }}</text></view>
|
||||
<view class="info-row"><text>购买角色</text><text>{{ order.purchase_role || '-' }}</text></view>
|
||||
<view class="info-row"><text>资产标识</text><text>{{ order.asset_identifier || '-' }}</text></view>
|
||||
<view class="info-row"><text>支付方式</text><text>{{ formatPaymentMethod(order.payment_method) }}</text></view>
|
||||
<view class="info-row"><text>支付状态</text><text>{{ order.payment_status_name || getStatusText(order.payment_status) }}</text></view>
|
||||
<view class="info-row"><text>订单金额</text><text class="amount">¥{{ formatMoney(order.total_amount) }}</text></view>
|
||||
<view class="info-row"><text>创建时间</text><text>{{ formatDateTime(order.created_at) }}</text></view>
|
||||
<view class="info-row"><text>支付时间</text><text>{{ formatDateTime(order.paid_at) }}</text></view>
|
||||
<view class="info-row"><text>完成时间</text><text>{{ formatDateTime(order.completed_at) }}</text></view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="card-title">套餐明细</view>
|
||||
<view v-if="!order.packages?.length" class="empty-detail">暂无套餐明细</view>
|
||||
<view v-for="item in order.packages || []" :key="item.package_id" class="package-row">
|
||||
<view>
|
||||
<view class="package-name">{{ item.package_name || '-' }}</view>
|
||||
<view class="package-type">{{ item.package_type === 'addon' ? '加油包' : '正式套餐' }} × {{ item.quantity || 1 }}</view>
|
||||
</view>
|
||||
<view class="amount">¥{{ formatMoney(item.price) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { orderApi } from '@/api/index.js';
|
||||
import { formatDateTime, formatMoney } from '@/utils/display.js';
|
||||
|
||||
const order = ref(null);
|
||||
const loading = ref(false);
|
||||
|
||||
const formatPaymentMethod = (method) => ({
|
||||
wechat: '微信支付',
|
||||
alipay: '支付宝支付',
|
||||
wallet: '钱包支付'
|
||||
}[method] || method || '-');
|
||||
|
||||
const getStatusText = (status) => ({
|
||||
1: '待支付',
|
||||
2: '已支付',
|
||||
3: '已取消',
|
||||
4: '已退款'
|
||||
}[status] || '未知');
|
||||
|
||||
onLoad(async ({ id }) => {
|
||||
if (!id) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
order.value = await orderApi.getDetail(id);
|
||||
} catch (error) {
|
||||
console.error('加载订单详情失败', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container { min-height: 100vh; padding: 24rpx; background: var(--bg-secondary); box-sizing: border-box; }
|
||||
.card { margin-bottom: 24rpx; padding: 28rpx; background: #fff; border-radius: 20rpx; }
|
||||
.card-title { margin-bottom: 20rpx; font-size: 32rpx; font-weight: 600; color: var(--text-primary); }
|
||||
.info-row, .package-row { display: flex; justify-content: space-between; gap: 24rpx; padding: 18rpx 0; border-bottom: 1rpx solid var(--gray-200); color: var(--text-secondary); font-size: 26rpx; }
|
||||
.info-row:last-child, .package-row:last-child { border-bottom: 0; }
|
||||
.info-row text:last-child { color: var(--text-primary); text-align: right; word-break: break-all; }
|
||||
.amount { color: var(--danger) !important; font-weight: 600; }
|
||||
.package-name { color: var(--text-primary); }
|
||||
.package-type, .empty-detail, .state { color: var(--text-tertiary); }
|
||||
.state { padding: 200rpx 0; text-align: center; }
|
||||
</style>
|
||||
@@ -21,7 +21,7 @@
|
||||
</view>
|
||||
|
||||
<view v-else class="order-list">
|
||||
<view class="order-card" v-for="item in orderList" :key="item.order_id">
|
||||
<view class="order-card" v-for="item in orderList" :key="item.order_id" @tap="openOrderDetail(item)">
|
||||
<view class="card-header">
|
||||
<view class="header-left">
|
||||
<view class="header-row">
|
||||
@@ -44,6 +44,14 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">购买角色</view>
|
||||
<view class="info-value">{{ item.purchase_role || '-' }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">资产标识</view>
|
||||
<view class="info-value">{{ item.asset_identifier || '-' }}</view>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<view class="info-label">订单金额</view>
|
||||
<view class="info-value amount">¥{{ formatMoney(item.total_amount) }}</view>
|
||||
@@ -55,10 +63,13 @@
|
||||
</view>
|
||||
|
||||
<view v-if="item.payment_status === 1" class="card-footer">
|
||||
<button class="btn-pay" :disabled="orderPayingId !== null" @tap="showOrderPaymentMethods(item)">
|
||||
<button class="btn-pay" :disabled="orderPayingId !== null" @tap.stop="showOrderPaymentMethods(item)">
|
||||
{{ isOrderPaying(item) ? '处理中...' : '立即支付' }}
|
||||
</button>
|
||||
</view>
|
||||
<view v-else-if="item.payment_status === 2 && item.package_ids?.length" class="card-footer">
|
||||
<button class="btn-pay btn-renew" @tap.stop="startHistoricalRenewal(item)">继续续费</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -87,6 +98,8 @@
|
||||
showPaymentToast,
|
||||
wechatH5Pay
|
||||
} from '@/utils/payment.js';
|
||||
import { formatMoney } from '@/utils/display.js';
|
||||
import { getPaymentMethodOptions, normalizePaymentMethods } from '@/utils/payment-methods.js';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
@@ -97,12 +110,9 @@
|
||||
const orderPayingId = ref(null);
|
||||
const pageSize = 10;
|
||||
const filterIndex = ref(0);
|
||||
const isDeviceAsset = ref(true);
|
||||
const allowedPaymentMethods = ref([]);
|
||||
let assetTypePromise = null;
|
||||
const paymentMethodOptions = computed(() => [
|
||||
{ label: '支付宝支付', value: 'alipay' },
|
||||
{ label: '钱包支付', value: 'wallet' }
|
||||
]);
|
||||
const paymentMethodOptions = computed(() => getPaymentMethodOptions(allowedPaymentMethods.value));
|
||||
const filterOptions = [
|
||||
{ label: '全部', value: null },
|
||||
{ label: '待支付', value: 1 },
|
||||
@@ -111,11 +121,6 @@
|
||||
{ label: '已退款', value: 4 }
|
||||
];
|
||||
|
||||
const formatMoney = (amount) => {
|
||||
if (!amount && amount !== 0) return '0.00';
|
||||
return (amount / 100).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
};
|
||||
|
||||
const getStatusClass = (status) => {
|
||||
const classMap = {
|
||||
1: 'tag-warning',
|
||||
@@ -132,7 +137,7 @@
|
||||
|
||||
assetTypePromise = assetApi.getInfo(userStore.state.identifier)
|
||||
.then((data) => {
|
||||
isDeviceAsset.value = data.asset_type === 'device';
|
||||
allowedPaymentMethods.value = normalizePaymentMethods(data.allowed_payment_methods);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('加载资产类型失败', error);
|
||||
@@ -185,6 +190,7 @@
|
||||
|
||||
const newData = (data.items || []).map((item) => ({
|
||||
...item,
|
||||
asset_identifier: item.asset_identifier || item.virtual_no || item.VirtualNo || item.imei || '',
|
||||
payment_status_name: item.payment_status === 1
|
||||
? '待支付'
|
||||
: item.payment_status === 2
|
||||
@@ -223,10 +229,27 @@
|
||||
|
||||
const isOrderPaying = (order) => orderPayingId.value === order.order_id;
|
||||
|
||||
const openOrderDetail = (order) => {
|
||||
if (!order?.order_id || orderPayingId.value !== null) return;
|
||||
uni.navigateTo({ url: `/pages/order-detail/order-detail?id=${order.order_id}` });
|
||||
};
|
||||
|
||||
const startHistoricalRenewal = (order) => {
|
||||
const packageIds = Array.isArray(order?.package_ids) ? order.package_ids.filter(Boolean) : [];
|
||||
if (!packageIds.length) return;
|
||||
const packageNames = Array.isArray(order.package_names) ? order.package_names : [];
|
||||
const query = `renewal_package_ids=${encodeURIComponent(packageIds.join(','))}&renewal_package_names=${encodeURIComponent(packageNames.join('|'))}`;
|
||||
uni.navigateTo({ url: `/pages/package-order/package-order?${query}` });
|
||||
};
|
||||
|
||||
const showOrderPaymentMethods = async (order) => {
|
||||
if (orderPayingId.value !== null || !order?.order_id) return;
|
||||
await loadAssetType();
|
||||
const options = paymentMethodOptions.value;
|
||||
if (!options.length) {
|
||||
uni.showToast({ title: '暂无可用支付方式', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showActionSheet({
|
||||
itemList: options.map((item) => item.label),
|
||||
@@ -253,7 +276,7 @@
|
||||
uni.hideLoading();
|
||||
|
||||
if (paymentMethod === 'wallet') {
|
||||
showPaymentToast(true, '支付成功');
|
||||
await confirmOrderStatus(order);
|
||||
setTimeout(() => {
|
||||
resetOrderListAndLoad();
|
||||
}, 1500);
|
||||
@@ -271,7 +294,7 @@
|
||||
|
||||
try {
|
||||
await wechatH5Pay(payData.pay_config);
|
||||
showPaymentToast(true, '支付成功');
|
||||
await confirmOrderStatus(order);
|
||||
setTimeout(() => {
|
||||
resetOrderListAndLoad();
|
||||
}, 1500);
|
||||
@@ -302,6 +325,17 @@
|
||||
}
|
||||
};
|
||||
|
||||
const confirmOrderStatus = async (order) => {
|
||||
try {
|
||||
const detail = await orderApi.getDetail(order.order_id);
|
||||
const status = detail?.payment_status ?? detail?.order?.payment_status;
|
||||
showPaymentToast(status === 2, status === 2 ? '支付成功' : '支付结果确认中');
|
||||
} catch (error) {
|
||||
console.error('确认订单支付状态失败', error);
|
||||
showPaymentToast(false, '支付结果确认失败,请稍后查看订单');
|
||||
}
|
||||
};
|
||||
|
||||
onShow(() => {
|
||||
if (!consumePendingPaymentRefresh(PAYMENT_REFRESH_TARGETS.ORDER_LIST)) {
|
||||
return;
|
||||
@@ -491,7 +525,7 @@
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.btn-pay {
|
||||
.btn-pay {
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
@@ -508,6 +542,8 @@
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.btn-renew { background: #52c41a; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<view class="card package-card" v-for="item in packageList" :key="item.package_id">
|
||||
<view class="package-header">
|
||||
<view class="package-name">{{ item.package_name }}</view>
|
||||
<view class="tag-apple" :class="item.is_addon ? 'tag-warning' : 'tag-primary'">
|
||||
{{ item.is_addon ? '加油包' : '正式套餐' }}
|
||||
<view class="tag-apple" :class="item.is_renewal ? 'tag-warning' : (item.is_addon ? 'tag-warning' : 'tag-primary')">
|
||||
{{ item.is_renewal ? '续费套餐' : (item.is_addon ? '加油包' : '正式套餐') }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="package-main">
|
||||
@@ -27,7 +27,7 @@
|
||||
<view class="package-footer">
|
||||
<view class="price-block">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="package-price">{{ formatMoney(item.retail_price) }}</text>
|
||||
<text class="package-price">{{ formatPackagePrice(item.retail_price) }}</text>
|
||||
</view>
|
||||
<view class="btn">
|
||||
<up-button type="primary" @click="buyPackage(item)">立即订购</up-button>
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
<view class="package-summary">
|
||||
<view class="summary-name">{{ currentPackage?.package_name }}</view>
|
||||
<view class="summary-price">¥{{ formatMoney(currentPackage?.retail_price) }}</view>
|
||||
<view class="summary-price">¥{{ formatPackagePrice(currentPackage?.retail_price) }}</view>
|
||||
<view class="summary-details">
|
||||
<view class="detail-item">
|
||||
<text class="detail-label">套餐流量</text>
|
||||
@@ -59,21 +59,17 @@
|
||||
</view>
|
||||
|
||||
<view class="payment-methods">
|
||||
<view class="method-item" :class="{ active: paymentMethod === 'alipay' }" @tap="selectPaymentMethod('alipay')">
|
||||
<view v-for="method in paymentMethodOptions" :key="method.value" class="method-item"
|
||||
:class="{ active: paymentMethod === method.value }" @tap="selectPaymentMethod(method.value)">
|
||||
<view class="method-left">
|
||||
<view class="method-icon method-badge method-badge-alipay">支</view>
|
||||
<text class="method-name">支付宝支付</text>
|
||||
<view v-if="method.value === 'alipay'" class="method-icon method-badge method-badge-alipay">支</view>
|
||||
<image v-else-if="method.value === 'wallet'" class="method-icon" src="/static/wallet.png" mode="aspectFit"></image>
|
||||
<view v-else class="method-icon method-badge">微</view>
|
||||
<text class="method-name">{{ method.label }}<template v-if="method.value === 'wallet'">(¥{{ formatMoney(walletBalance) }})</template></text>
|
||||
</view>
|
||||
<view class="method-radio" :class="{ checked: paymentMethod === 'alipay' }"></view>
|
||||
</view>
|
||||
|
||||
<view class="method-item" :class="{ active: paymentMethod === 'wallet' }" @tap="selectPaymentMethod('wallet')">
|
||||
<view class="method-left">
|
||||
<image class="method-icon" src="/static/wallet.png" mode="aspectFit"></image>
|
||||
<text class="method-name">账户余额(¥{{ formatMoney(walletBalance) }})</text>
|
||||
</view>
|
||||
<view class="method-radio" :class="{ checked: paymentMethod === 'wallet' }"></view>
|
||||
<view class="method-radio" :class="{ checked: paymentMethod === method.value }"></view>
|
||||
</view>
|
||||
<view v-if="paymentMethodOptions.length === 0" class="method-empty">暂无可用支付方式</view>
|
||||
</view>
|
||||
|
||||
<view class="popup-footer">
|
||||
@@ -88,8 +84,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import { assetApi, orderApi, walletApi } from '@/api/index.js';
|
||||
import { useUserStore } from '@/store/index.js';
|
||||
import {
|
||||
@@ -102,6 +98,8 @@
|
||||
showPaymentToast,
|
||||
wechatH5Pay
|
||||
} from '@/utils/payment.js';
|
||||
import { formatMoney } from '@/utils/display.js';
|
||||
import { getDefaultPaymentMethod, getPaymentMethodOptions, normalizePaymentMethods } from '@/utils/payment-methods.js';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
@@ -112,13 +110,12 @@
|
||||
const paymentMethod = ref('alipay');
|
||||
const walletBalance = ref(0);
|
||||
const paySubmitting = ref(false);
|
||||
const isDeviceAsset = ref(true);
|
||||
const assetInfo = ref({});
|
||||
const allowedPaymentMethods = ref([]);
|
||||
const renewalPackageIds = ref([]);
|
||||
const renewalPackageNames = ref([]);
|
||||
let assetTypePromise = null;
|
||||
|
||||
const formatMoney = (amount) => {
|
||||
if (!amount && amount !== 0) return '0.00';
|
||||
return (amount / 100).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
};
|
||||
const paymentMethodOptions = computed(() => getPaymentMethodOptions(allowedPaymentMethods.value));
|
||||
|
||||
const formatData = (allowance, unit) => {
|
||||
if (unit === 'MB') {
|
||||
@@ -127,18 +124,15 @@
|
||||
return `${allowance} ${unit}`;
|
||||
};
|
||||
|
||||
const getDefaultPaymentMethod = () => 'alipay';
|
||||
const formatPackagePrice = (amount) => amount === null || amount === undefined ? '-' : formatMoney(amount);
|
||||
|
||||
const isPaymentMethodAvailable = (method) => {
|
||||
if (method === 'wallet') return true;
|
||||
if (method === 'wechat') return false;
|
||||
if (method === 'alipay') return true;
|
||||
return false;
|
||||
return paymentMethodOptions.value.some((item) => item.value === method);
|
||||
};
|
||||
|
||||
const normalizePaymentMethod = () => {
|
||||
if (!isPaymentMethodAvailable(paymentMethod.value)) {
|
||||
paymentMethod.value = getDefaultPaymentMethod();
|
||||
paymentMethod.value = getDefaultPaymentMethod(allowedPaymentMethods.value);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -148,7 +142,8 @@
|
||||
|
||||
assetTypePromise = assetApi.getInfo(userStore.state.identifier)
|
||||
.then((data) => {
|
||||
isDeviceAsset.value = data.asset_type === 'device';
|
||||
assetInfo.value = data;
|
||||
allowedPaymentMethods.value = normalizePaymentMethods(data.allowed_payment_methods);
|
||||
normalizePaymentMethod();
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -165,7 +160,31 @@
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await assetApi.getPackages(userStore.state.identifier);
|
||||
packageList.splice(0, packageList.length, ...(data.packages || []));
|
||||
const currentPackageId = Number(assetInfo.value.current_package_id || 0);
|
||||
const packages = (data.packages || []).filter((item) => {
|
||||
const status = String(item.status || '').toLowerCase();
|
||||
const discontinued = item.is_on_sale === false || item.on_sale === false ||
|
||||
['off_shelf', 'offline', 'discontinued', '下架'].includes(status);
|
||||
return !discontinued || Number(item.package_id) === currentPackageId;
|
||||
}).map((item) => ({
|
||||
...item,
|
||||
is_renewal: Number(item.package_id) === currentPackageId &&
|
||||
(item.is_on_sale === false || item.on_sale === false ||
|
||||
['off_shelf', 'offline', 'discontinued', '下架'].includes(String(item.status || '').toLowerCase()))
|
||||
}));
|
||||
const packageIdsInList = new Set(packages.map((item) => Number(item.package_id)));
|
||||
const renewalOnlyPackages = renewalPackageIds.value
|
||||
.filter((packageId) => !packageIdsInList.has(Number(packageId)))
|
||||
.map((packageId, index) => ({
|
||||
package_id: Number(packageId),
|
||||
package_name: renewalPackageNames.value[index] || `历史套餐 ${packageId}`,
|
||||
retail_price: null,
|
||||
data_allowance: 0,
|
||||
data_unit: 'MB',
|
||||
validity_days: '-',
|
||||
is_renewal: true
|
||||
}));
|
||||
packageList.splice(0, packageList.length, ...packages, ...renewalOnlyPackages);
|
||||
} catch (error) {
|
||||
console.error('加载套餐列表失败', error);
|
||||
}
|
||||
@@ -181,16 +200,32 @@
|
||||
}
|
||||
};
|
||||
|
||||
const syncPackagePageState = () => {
|
||||
loadAssetType();
|
||||
loadPackages();
|
||||
loadWalletBalance();
|
||||
const syncPackagePageState = async () => {
|
||||
await loadAssetType();
|
||||
await Promise.all([loadPackages(), loadWalletBalance()]);
|
||||
};
|
||||
|
||||
const buyPackage = async (item) => {
|
||||
await loadAssetType();
|
||||
if (assetInfo.value.effective_realname_policy === 'before_order' &&
|
||||
assetInfo.value.realname_required && Number(assetInfo.value.real_name_status) !== 1) {
|
||||
uni.showModal({
|
||||
title: '需要实名认证',
|
||||
content: '当前资产下单前需要完成实名认证',
|
||||
confirmText: '去实名',
|
||||
cancelText: '取消',
|
||||
success: ({ confirm }) => {
|
||||
if (confirm) uni.navigateTo({ url: '/pages/auth/auth' });
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!paymentMethodOptions.value.length) {
|
||||
uni.showToast({ title: '暂无可用支付方式', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
currentPackage.value = item;
|
||||
paymentMethod.value = getDefaultPaymentMethod();
|
||||
paymentMethod.value = getDefaultPaymentMethod(allowedPaymentMethods.value);
|
||||
showModal.value = true;
|
||||
};
|
||||
|
||||
@@ -204,10 +239,16 @@
|
||||
isValidAlipayPaymentLink(paymentData?.payment_link);
|
||||
};
|
||||
|
||||
const handleWechatPay = async (payConfig, isForceRecharge = false) => {
|
||||
const handleWechatPay = async (payConfig, isForceRecharge = false, orderId = null) => {
|
||||
try {
|
||||
await wechatH5Pay(payConfig);
|
||||
showPaymentToast(true, isForceRecharge ? '充值成功,套餐将自动购买' : '支付成功');
|
||||
if (orderId) {
|
||||
const detail = await orderApi.getDetail(orderId);
|
||||
const status = detail?.payment_status ?? detail?.order?.payment_status;
|
||||
showPaymentToast(status === 2, status === 2 ? '支付成功' : '支付结果确认中');
|
||||
} else {
|
||||
showPaymentToast(true, isForceRecharge ? '充值成功,套餐将自动购买' : '支付成功');
|
||||
}
|
||||
setTimeout(() => {
|
||||
loadWalletBalance();
|
||||
}, 1500);
|
||||
@@ -216,9 +257,9 @@
|
||||
}
|
||||
};
|
||||
|
||||
const handlePreparedPayment = async (paymentData, isForceRecharge = false) => {
|
||||
const handlePreparedPayment = async (paymentData, isForceRecharge = false, orderId = null) => {
|
||||
if (isValidWechatPayConfig(paymentData?.pay_config)) {
|
||||
await handleWechatPay(paymentData.pay_config, isForceRecharge);
|
||||
await handleWechatPay(paymentData.pay_config, isForceRecharge, orderId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -234,11 +275,13 @@
|
||||
};
|
||||
|
||||
const getCreateOrderPaymentMethod = () => {
|
||||
if (paymentMethod.value === 'alipay') return 'alipay';
|
||||
if (paymentMethod.value === 'wallet') return 'alipay';
|
||||
return undefined;
|
||||
return paymentMethod.value;
|
||||
};
|
||||
|
||||
const getCreateOrderPackageIds = () => renewalPackageIds.value.length
|
||||
? renewalPackageIds.value
|
||||
: [currentPackage.value.package_id];
|
||||
|
||||
const confirmPay = async () => {
|
||||
if (paySubmitting.value || !currentPackage.value) return;
|
||||
|
||||
@@ -253,7 +296,7 @@
|
||||
try {
|
||||
const orderResult = await orderApi.create(
|
||||
userStore.state.identifier,
|
||||
[currentPackage.value.package_id],
|
||||
getCreateOrderPackageIds(),
|
||||
getCreateOrderPaymentMethod()
|
||||
);
|
||||
|
||||
@@ -348,17 +391,20 @@
|
||||
showModal.value = false;
|
||||
|
||||
if (paymentMethod.value === 'wallet') {
|
||||
uni.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success'
|
||||
});
|
||||
try {
|
||||
const detail = await orderApi.getDetail(orderResult.order.order_id);
|
||||
const status = detail?.payment_status ?? detail?.order?.payment_status;
|
||||
showPaymentToast(status === 2, status === 2 ? '支付成功' : '支付结果确认中');
|
||||
} catch (error) {
|
||||
showPaymentToast(false, '支付结果确认失败,请稍后查看订单');
|
||||
}
|
||||
setTimeout(() => {
|
||||
loadWalletBalance();
|
||||
}, 1500);
|
||||
return;
|
||||
}
|
||||
|
||||
await handlePreparedPayment(payResult);
|
||||
await handlePreparedPayment(payResult, false, orderResult.order.order_id);
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
showModal.value = false;
|
||||
@@ -408,6 +454,21 @@
|
||||
});
|
||||
});
|
||||
|
||||
onLoad((query = {}) => {
|
||||
const ids = String(query.renewal_package_ids || '')
|
||||
.split(',')
|
||||
.map((value) => Number(value))
|
||||
.filter((value) => Number.isInteger(value) && value > 0);
|
||||
renewalPackageIds.value = [...new Set(ids)];
|
||||
try {
|
||||
renewalPackageNames.value = decodeURIComponent(String(query.renewal_package_names || ''))
|
||||
.split('|')
|
||||
.filter(Boolean);
|
||||
} catch (error) {
|
||||
renewalPackageNames.value = [];
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
syncPackagePageState();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user