fix: 优化体验
All checks were successful
构建并部署前端到生产环境 / build-and-deploy (push) Successful in 1m37s

This commit is contained in:
sexygoat
2026-05-09 16:14:06 +08:00
parent c798cdba7f
commit 3f997063f4
27 changed files with 1191 additions and 62 deletions

View File

@@ -41,6 +41,11 @@
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { authApi } from '@/api/index.js';
import { useUserStore } from '@/store/index.js';
const POST_BIND_RELOGIN_NOTICE_KEY = 'postBindReloginNotice';
const userStore = useUserStore();
let bind = reactive({
phone: '',
@@ -60,6 +65,23 @@
fromLogin.value = options.fromLogin === 'true';
});
const prepareManualRelogin = () => {
const preservedIdentifier = userStore.state.identifier || uni.getStorageSync('identifier') || '';
userStore.clearUser();
if (preservedIdentifier) {
userStore.setIdentifier(preservedIdentifier);
}
uni.setStorageSync(POST_BIND_RELOGIN_NOTICE_KEY, '1');
// #ifdef H5
if (typeof sessionStorage !== 'undefined') {
sessionStorage.removeItem('assetToken');
}
// #endif
};
const getCode = async () => {
if (!bind.phone) {
uni.showToast({ title: '请输入手机号', icon: 'none' });
@@ -100,15 +122,17 @@
try {
await authApi.bindPhone(bind.phone, bind.code);
if (fromLogin.value) {
prepareManualRelogin();
uni.reLaunch({ url: '/pages/login/login' });
return;
}
uni.showToast({ title: '绑定成功', icon: 'success' });
setTimeout(() => {
if (fromLogin.value) {
// 从登录页面过来的,绑定成功后跳转到首页
uni.redirectTo({ url: '/pages/index/index' });
} else {
// 从其他页面过来的,返回上一页
uni.navigateBack();
}
// 从其他页面过来的,返回上一页
uni.navigateBack();
}, 1500);
} catch (e) {
console.error('绑定手机号失败', e);
@@ -193,4 +217,4 @@
.container {
padding: var(--space-md);
}
</style>
</style>

View File

@@ -113,6 +113,7 @@
onMounted,
computed
} from 'vue';
import { onShow } from '@dcloudio/uni-app';
import UserInfoCard from '@/components/UserInfoCard.vue';
import DeviceStatusCard from '@/components/DeviceStatusCard.vue';
import VoiceCard from '@/components/VoiceCard.vue';
@@ -207,6 +208,7 @@
let showSmsCode = ref(false);
let smsCodePhone = ref('');
let smsCode = ref('');
let indexEntryChecking = ref(false);
const formatDate = (dateStr) => {
if (!dateStr) return '-';
@@ -293,10 +295,33 @@
}
// 到期时间由 TrafficCard 组件获取套餐信息时一并返回
return data;
} catch (e) {
console.error('加载资产信息失败', e);
return null;
} finally {
loading.value = false;
}
};
const handleIndexEntry = async () => {
const token = uni.getStorageSync('token');
if (!token) {
uni.reLaunch({ url: '/pages/login/login' });
return;
}
if (indexEntryChecking.value) return;
indexEntryChecking.value = true;
try {
const data = await loadAssetInfo();
if (data && !data.bound_phone) {
uni.redirectTo({ url: '/pages/bind/bind?fromLogin=true' });
}
} finally {
indexEntryChecking.value = false;
}
loading.value = false;
};
// 处理套餐加载完成事件(从 TrafficCard 组件传递过来)
@@ -736,13 +761,11 @@
};
onMounted(() => {
const token = uni.getStorageSync('token');
if (!token) {
uni.reLaunch({ url: '/pages/login/login' });
return;
}
initCurrentMonth();
loadAssetInfo();
});
onShow(() => {
handleIndexEntry();
});
</script>

View File

@@ -70,13 +70,24 @@
import { useUserStore } from '@/store/index.js';
import { initWxConfig, wxScan, isInWechat } from '@/utils/wxsdk.js';
const POST_BIND_RELOGIN_NOTICE_KEY = 'postBindReloginNotice';
const userStore = useUserStore();
const identifier = ref(uni.getStorageSync('identifier') || '');
const loading = ref(false);
const agreed = ref(true);
const inputFocus = ref(false);
const showError = ref(false);
const wechatAppId = ref('');
onMounted(async () => {
const token = uni.getStorageSync('token');
if (token) {
uni.reLaunch({ url: '/pages/index/index' });
return;
}
showPostBindReloginNotice();
handleWechatCallback();
getPathDeviceId();
@@ -93,12 +104,18 @@
// #endif
});
const identifier = ref('');
const loading = ref(false);
const agreed = ref(true);
const inputFocus = ref(false);
const showError = ref(false);
const wechatAppId = ref('');
const showPostBindReloginNotice = () => {
if (uni.getStorageSync(POST_BIND_RELOGIN_NOTICE_KEY) !== '1') {
return;
}
uni.removeStorageSync(POST_BIND_RELOGIN_NOTICE_KEY);
uni.showToast({
title: '绑定手机号成功,请重新登录',
icon: 'none',
duration: 2500
});
};
const getCode = () => {
const params = new URLSearchParams(window.location.search);
@@ -152,7 +169,7 @@
doLogin();
};
const handleScanLogin = async () => {
const handleScanLogin = async () => {
// #ifdef H5
try {
// 检查是否在微信环境
@@ -218,7 +235,6 @@ const handleScanLogin = async () => {
};
const handleWechatCallback = async () => {
const code = getCode();
const assetToken = sessionStorage.getItem('assetToken');

View File

@@ -53,7 +53,7 @@
<view class="info-label">支付方式</view>
<view class="info-value">{{ item.payment_method || '-' }}</view>
</view>
<view class="info-row flex-row-sb">
<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>
@@ -64,7 +64,9 @@
</view>
</view>
<view v-if="item.status === 0" class="record-actions">
<button class="btn-pay" @tap="handleRechargePayment(item)">立即支付</button>
<button class="btn-pay" :disabled="rechargeOrderSubmittingKey !== null" @tap="handleRechargePayment(item)">
{{ isRechargeOrderSubmitting(item) ? '处理中...' : '立即支付' }}
</button>
</view>
</view>
@@ -93,10 +95,6 @@
</view>
</view>
<view class="record-info">
<view class="info-row flex-row-sb">
<view class="info-label">流水ID</view>
<view class="info-value text-ellipsis">{{ item.transaction_id }}</view>
</view>
<view class="info-row flex-row-sb">
<view class="info-label">变动后余额</view>
<view class="info-value">¥{{ formatMoney(item.balance_after) }}</view>
@@ -106,7 +104,7 @@
<view class="info-value">{{ item.remark }}</view>
</view>
<view class="info-row flex-row-sb">
<view class="info-label">创建时间</view>
<view class="info-label">支付时间</view>
<view class="info-value">{{ item.created_at }}</view>
</view>
</view>
@@ -143,12 +141,14 @@
<view class="custom-input" v-if="isCustomAmount">
<view class="input-label">请输入充值金额</view>
<up-input v-model="customAmount" type="number" placeholder="最低0.01元" border="surround" />
<up-input v-model="customAmount" type="number" placeholder="最低1元" 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>
<button class="btn-apple btn-primary" :disabled="rechargeSubmitting" @tap="confirmRecharge">
{{ rechargeSubmitting ? '处理中...' : '确认充值' }}
</button>
</view>
</view>
</up-popup>
@@ -228,6 +228,8 @@
let selectedAmount = ref(null);
let isCustomAmount = ref(false);
let customAmount = ref('');
let rechargeSubmitting = ref(false);
let rechargeOrderSubmittingKey = ref(null);
const rechargeAmounts = [{
value: 1000,
label: '10'
@@ -266,13 +268,17 @@
};
const confirmRecharge = async () => {
if (rechargeSubmitting.value) return;
rechargeSubmitting.value = true;
let amount = 0;
if (isCustomAmount.value) {
// 自定义金额
const customAmountNum = parseFloat(customAmount.value);
if (!customAmountNum || customAmountNum < 0.01) {
if (!customAmountNum || customAmountNum < 1) {
showRechargeModal.value = false;
rechargeSubmitting.value = false;
setTimeout(() => {
uni.showToast({
title: '请输入正确的充值金额',
@@ -287,6 +293,7 @@
amount = selectedAmount.value;
} else {
showRechargeModal.value = false;
rechargeSubmitting.value = false;
setTimeout(() => {
uni.showToast({
title: '请选择充值金额',
@@ -378,6 +385,8 @@
icon: 'none'
});
}, 50);
} finally {
rechargeSubmitting.value = false;
}
};
@@ -402,6 +411,20 @@
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) => rechargeOrderSubmittingKey.value ===
getRechargeOrderSubmitKey(rechargeOrder);
const loadWalletDetail = async () => {
try {
const data = await walletApi.getDetail(userStore.state.identifier);
@@ -480,6 +503,9 @@
};
const handleRechargePayment = async (rechargeOrder) => {
if (rechargeOrderSubmittingKey.value !== null) return;
rechargeOrderSubmittingKey.value = getRechargeOrderSubmitKey(rechargeOrder);
uni.showLoading({
title: '处理中...',
mask: true
@@ -528,6 +554,8 @@
title: errorMsg,
icon: 'none'
});
} finally {
rechargeOrderSubmittingKey.value = null;
}
};
@@ -787,9 +815,9 @@
}
}
.record-actions {
margin-top: 20rpx;
padding-top: 20rpx;
.record-actions {
margin-top: 20rpx;
padding-top: 20rpx;
border-top: 1rpx solid var(--border-light);
display: flex;
justify-content: flex-end;
@@ -800,12 +828,16 @@
border: none;
border-radius: 8rpx;
padding: 12rpx 32rpx;
font-size: 26rpx;
font-weight: 500;
width:100%;
font-size: 26rpx;
font-weight: 500;
width:100%;
&::after {
border: none;
&[disabled] {
opacity: 0.7;
}
&::after {
border: none;
}
}
}
@@ -931,6 +963,10 @@
font-weight: 500;
border: none;
&[disabled] {
opacity: 0.7;
}
&.btn-secondary {
background: var(--gray-200);
color: var(--text-primary);
@@ -943,4 +979,4 @@
}
}
}
</style>
</style>

View File

@@ -62,7 +62,9 @@
</view> -->
<view v-if="item.payment_status === 1" class="card-footer">
<button class="btn-pay" @tap="handleOrderPayment(item)">立即支付</button>
<button class="btn-pay" :disabled="orderPayingId !== null" @tap="handleOrderPayment(item)">
{{ isOrderPaying(item) ? '处理中...' : '立即支付' }}
</button>
</view>
</view>
</view>
@@ -97,6 +99,7 @@
let loading = ref(false);
let noMore = ref(false);
let page = ref(1);
let orderPayingId = ref(null);
const pageSize = 10;
let filterIndex = ref(0);
let filterOptions = [{
@@ -188,7 +191,12 @@
}
};
const isOrderPaying = (order) => orderPayingId.value === order.order_id;
const handleOrderPayment = async (order) => {
if (orderPayingId.value !== null || !order?.order_id) return;
orderPayingId.value = order.order_id;
uni.showLoading({
title: '处理中...',
mask: true
@@ -230,6 +238,8 @@
title: errorMsg,
icon: 'none'
});
} finally {
orderPayingId.value = null;
}
};
@@ -445,6 +455,10 @@
font-weight: 500;
width:100%;
&[disabled] {
opacity: 0.7;
}
&::after {
border: none;
}
@@ -486,4 +500,4 @@
color: #999;
font-size: 24rpx;
}
</style>
</style>

View File

@@ -70,7 +70,9 @@
<view class="popup-footer">
<button class="btn-apple btn-secondary" @tap="showModal=false">取消</button>
<button class="btn-apple btn-primary" @tap="confirmPay">确认支付</button>
<button class="btn-apple btn-primary" :disabled="paySubmitting" @tap="confirmPay">
{{ paySubmitting ? '处理中...' : '确认支付' }}
</button>
</view>
</view>
</up-popup>
@@ -91,6 +93,7 @@
let loading = ref(false);
let paymentMethod = ref('wechat');
let walletBalance = ref(0);
let paySubmitting = ref(false);
const formatMoney = (amount) => {
if (!amount && amount !== 0) return '0.00';
@@ -135,7 +138,9 @@
};
const confirmPay = async () => {
if (!currentPackage.value) return;
if (paySubmitting.value || !currentPackage.value) return;
paySubmitting.value = true;
let releaseInModalCallback = false;
uni.showLoading({ title: '创建订单...', mask: true });
@@ -200,6 +205,7 @@
// 提示用户需要强充
const packageNameStr = packageNames.length > 0 ? packageNames.join('、') : '套餐';
const message = `购买${packageNameStr}需要先充值 ¥${formatMoney(forceRechargeAmount)},充值成功后将自动购买套餐`;
releaseInModalCallback = true;
uni.showModal({
title: '需要充值',
@@ -207,10 +213,17 @@
confirmText: '去充值',
cancelText: '取消',
success: async (res) => {
if (res.confirm) {
// 调起微信支付
await handleWechatPay(orderResult.pay_config, true);
try {
if (res.confirm) {
// 调起微信支付
await handleWechatPay(orderResult.pay_config, true);
}
} finally {
paySubmitting.value = false;
}
},
fail: () => {
paySubmitting.value = false;
}
});
return;
@@ -273,6 +286,10 @@
// 其他错误提示
const errorMsg = e.msg || e.message || '操作失败,请稍后重试';
uni.showToast({ title: errorMsg, icon: 'none' });
} finally {
if (!releaseInModalCallback) {
paySubmitting.value = false;
}
}
};
@@ -515,20 +532,24 @@
padding: 30rpx;
border-top: 1rpx solid var(--border-light);
.btn-apple {
flex: 1;
height: 88rpx;
display: flex;
.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;
font-size: 28rpx;
font-weight: 500;
border: none;
&.btn-secondary {
background: var(--gray-200);
color: var(--text-primary);
&[disabled] {
opacity: 0.7;
}
&.btn-secondary {
background: var(--gray-200);
color: var(--text-primary);
}
&.btn-primary {
@@ -538,4 +559,4 @@
}
}
}
</style>
</style>