修复bug

This commit is contained in:
zhouyanli
2026-08-07 18:05:16 +08:00
parent 3851aebe66
commit 92dd39cf4b
7 changed files with 128 additions and 16 deletions

View File

@@ -1,6 +1,6 @@
<script setup> <script setup>
import { onLaunch,onShow,onHide } from '@dcloudio/uni-app' import { onLaunch,onShow,onHide } from '@dcloudio/uni-app'
import { isPublicPath } from '@/common/checkLogin.js' import { isPublicPath, requireLogin } from '@/common/checkLogin.js'
let cidRetryTimer = null let cidRetryTimer = null
let cidRetryCount = 0 let cidRetryCount = 0
@@ -27,10 +27,8 @@
invoke(args) { invoke(args) {
const t = uni.getStorageSync('token') const t = uni.getStorageSync('token')
if (!t && !isPublicPath(args.url)) { if (!t && !isPublicPath(args.url)) {
uni.showToast({ title: '请先登录', icon: 'none' }) // 使用 requireLogin 统一处理,自带防重复锁
setTimeout(() => { requireLogin({ message: '请先登录', redirect: true })
uni.navigateTo({ url: '/pageSubPack/loginSub/login?mode=pwd' })
}, 1000)
return false return false
} }
} }

View File

@@ -6,6 +6,10 @@
* if (!requireLogin()) return * if (!requireLogin()) return
* *
* 页面级调用即可,全局 navigateTo 拦截器App.vue onLaunch 中注册)作为兜底。 * 页面级调用即可,全局 navigateTo 拦截器App.vue onLaunch 中注册)作为兜底。
*
* 防重复机制:
* _loginPending — requireLogin 立即设置,阻止在 setTimeout 等待期内重复调度
* _loginNavigating — navigateToLogin 设置,阻止实际跳转重复
*/ */
/** 无需登录即可访问的页面路径白名单(全局拦截器使用) */ /** 无需登录即可访问的页面路径白名单(全局拦截器使用) */
@@ -18,6 +22,73 @@ export const PUBLIC_PATHS = [
'/pages/navigation/navigation' '/pages/navigation/navigation'
] ]
// ==================== 登录页跳转防重复锁 ====================
// 使用 getApp().globalData 存储,避免模块被分包/异步 chunk 多次实例化导致锁失效
function getLock() {
const app = getApp()
if (!app.globalData) app.globalData = {}
return app.globalData
}
/**
* 判断当前是否已在登录页
*/
function isOnLoginPage() {
const pages = getCurrentPages()
if (!pages || pages.length === 0) return false
const route = pages[pages.length - 1].route || ''
return route.includes('loginSub/login')
}
/**
* 跳转到登录页(统一入口,带防重复锁)
* 所有需要跳转登录页的地方都应调用此函数,不要直接调用 uni.navigateTo / uni.reLaunch
*
* @param {object} [options]
* @param {string} [options.method='navigateTo'] - 跳转方式:'navigateTo' | 'reLaunch' | 'redirectTo'
* @param {string} [options.url='/pageSubPack/loginSub/login?mode=pwd'] - 目标 url
*/
export function navigateToLogin(options = {}) {
const {
method = 'navigateTo',
url = '/pageSubPack/loginSub/login?mode=pwd'
} = options
// 已经在登录页,跳过
if (isOnLoginPage()) return
const lock = getLock()
// reLaunch 优先级最高,强制放行(用于 token 过期、退出登录等场景)
if (method !== 'reLaunch' && lock._loginNavigating) return
lock._loginNavigating = true
const navFn = uni[method]
if (!navFn) {
lock._loginNavigating = false
return
}
navFn({
url,
fail() {
// 跳转失败时重置锁,允许后续重试
lock._loginNavigating = false
}
})
}
/**
* 重置登录跳转锁(登录页 onLoad 时调用)
*/
export function resetLoginLock() {
const lock = getLock()
lock._loginPending = false
lock._loginNavigating = false
}
/** /**
* 判断指定路径是否为公开页面(无需登录) * 判断指定路径是否为公开页面(无需登录)
* @param {string} url - navigateTo 的目标 url * @param {string} url - navigateTo 的目标 url
@@ -40,15 +111,21 @@ export function isPublicPath(url) {
export function requireLogin(options = {}) { export function requireLogin(options = {}) {
const { const {
message = '请先登录', message = '请先登录',
redirect = true redirect = true
} = options } = options
const token = uni.getStorageSync('token') const token = uni.getStorageSync('token')
if (token) return true if (token) return true
uni.showToast({ title: message, icon: 'none' }) uni.showToast({ title: message, icon: 'none' })
if (redirect) { if (redirect) {
// 立即加锁,防止在 setTimeout 等待期内重复调度
const lock = getLock()
if (lock._loginPending) return false
lock._loginPending = true
setTimeout(() => { setTimeout(() => {
uni.navigateTo({ url: '/pageSubPack/loginSub/login?mode=pwd' }) lock._loginPending = false
navigateToLogin({ method: 'navigateTo' })
}, 1000) }, 1000)
} }
return false return false

View File

@@ -1,3 +1,5 @@
import { navigateToLogin } from '@/common/checkLogin.js'
let baseUrl = ''; let baseUrl = '';
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
baseUrl = 'http://192.168.1.222:8080'; // 开发环境 baseUrl = 'http://192.168.1.222:8080'; // 开发环境
@@ -42,7 +44,7 @@ export default function request(url, data = {}, method = "GET") {
if (resData.code === 401) { if (resData.code === 401) {
if (token) { if (token) {
uni.clearStorageSync('token'); uni.clearStorageSync('token');
uni.reLaunch({ url: '/pageSubPack/loginSub/login?mode=pwd' }); navigateToLogin({ method: 'reLaunch' });
} }
reject({ msg: '登录过期', code: 401 }); reject({ msg: '登录过期', code: 401 });
return; return;

View File

@@ -90,6 +90,7 @@ import { ref, computed, onUnmounted } from 'vue';
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import { getLoginBySms, getLoginPassword, getSendCode } from '../api/api.js' import { getLoginBySms, getLoginPassword, getSendCode } from '../api/api.js'
import PrivacyPopup from '@/components/privacy-popup/privacy-popup.vue' import PrivacyPopup from '@/components/privacy-popup/privacy-popup.vue'
import { resetLoginLock } from '@/common/checkLogin.js'
// ========== 模式切换 ========== // ========== 模式切换 ==========
// 'sms' = 手机验证码登录, 'pwd' = 账号密码登录 // 'sms' = 手机验证码登录, 'pwd' = 账号密码登录
@@ -113,6 +114,8 @@ const pendingAction = ref('')
// ========== onLoad: 读取 mode 参数 ========== // ========== onLoad: 读取 mode 参数 ==========
onLoad((options) => { onLoad((options) => {
// 重置登录跳转锁,允许下次未登录时再次跳转
resetLoginLock()
if (options && options.mode === 'sms') { if (options && options.mode === 'sms') {
loginMode.value = 'sms' loginMode.value = 'sms'
} else if (options && options.mode === 'pwd') { } else if (options && options.mode === 'pwd') {

View File

@@ -83,6 +83,7 @@
const id = ref(undefined) const id = ref(undefined)
const type = ref('') const type = ref('')
const isSubmitting = ref(false)
const formData = ref({ const formData = ref({
// carNum: uni.getStorageSync('carNum'), // carNum: uni.getStorageSync('carNum'),
// carBrand: uni.getStorageSync('carBrand'), // carBrand: uni.getStorageSync('carBrand'),
@@ -202,14 +203,25 @@
} }
} }
// 车牌号正则:省份简称 + 城市代码字母 + 5位(蓝牌)或6位(新能源绿牌)字母数字
const CAR_NUM_REGEX = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤川青藏琼宁][A-Z][A-Z0-9]{5,6}$/
const saveCar = async () => { const saveCar = async () => {
if (isSubmitting.value) return
if (!formData.value.carNum) { if (!formData.value.carNum) {
return uni.showToast({ return uni.showToast({
title: "请输入车牌号", title: "请输入车牌号",
icon: "none" icon: "none"
}) })
} }
if (!CAR_NUM_REGEX.test(formData.value.carNum)) {
return uni.showToast({
title: "请输入正确的车牌号",
icon: "none"
})
}
isSubmitting.value = true
uni.showLoading({ uni.showLoading({
title: '提交中...', title: '提交中...',
mask: true mask: true
@@ -232,6 +244,7 @@
submitCarData(carImageUrl) submitCarData(carImageUrl)
} catch (err) { } catch (err) {
isSubmitting.value = false
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: err.msg || '网络异常', title: err.msg || '网络异常',
@@ -260,6 +273,7 @@
params.id = id.value params.id = id.value
putCar(params).then(res => { putCar(params).then(res => {
console.log(res); console.log(res);
isSubmitting.value = false
uni.hideLoading() uni.hideLoading()
if (res.code === 200) { if (res.code === 200) {
uni.showToast({ uni.showToast({
@@ -274,9 +288,10 @@
uni.removeStorageSync("carImageUrl") uni.removeStorageSync("carImageUrl")
uni.removeStorageSync("carBrandId") uni.removeStorageSync("carBrandId")
uni.removeStorageSync("checkType") uni.removeStorageSync("checkType")
uni.redirectTo({ url: "/pageSubPack/my/myCarIndex" }) uni.navigateBack()
}, 1500) }, 1500)
} else { } else {
isSubmitting.value = false
uni.showToast({ uni.showToast({
title: res.msg || '提交失败', title: res.msg || '提交失败',
icon: 'none' icon: 'none'
@@ -285,6 +300,7 @@
}) })
}else{ }else{
postCar(params).then(res => { postCar(params).then(res => {
isSubmitting.value = false
uni.hideLoading() uni.hideLoading()
if (res.code === 200) { if (res.code === 200) {
uni.showToast({ uni.showToast({
@@ -299,9 +315,10 @@
uni.removeStorageSync("carImageUrl") uni.removeStorageSync("carImageUrl")
uni.removeStorageSync("carBrandId") uni.removeStorageSync("carBrandId")
uni.removeStorageSync("checkType") uni.removeStorageSync("checkType")
uni.redirectTo({ url: "/pageSubPack/my/myCarIndex" }) uni.navigateBack()
}, 1500) }, 1500)
} else { } else {
isSubmitting.value = false
uni.showToast({ uni.showToast({
title: res.msg || '提交失败', title: res.msg || '提交失败',
icon: 'none' icon: 'none'
@@ -467,4 +484,4 @@
color: #999 !important; color: #999 !important;
font-size: 28rpx !important; font-size: 28rpx !important;
} }
</style> </style>

View File

@@ -63,6 +63,7 @@
const statusBarHeight = ref(20) const statusBarHeight = ref(20)
const currentStatus = ref('') const currentStatus = ref('')
const isDeleting = ref(false)
const id = ref(undefined) const id = ref(undefined)
const form = ref({ const form = ref({
plateNo: '', plateNo: '',
@@ -110,11 +111,13 @@
} }
const onDelete = () => { const onDelete = () => {
if (isDeleting.value) return
uni.showModal({ uni.showModal({
title: "确认删除", title: "确认删除",
content: "确认删除此车辆信息吗?", content: "确认删除此车辆信息吗?",
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
isDeleting.value = true
uni.showLoading({ uni.showLoading({
title: '删除中...', title: '删除中...',
mask: true mask: true
@@ -122,6 +125,7 @@
deleteCar({ deleteCar({
carId: id.value carId: id.value
}).then(deleteRes => { }).then(deleteRes => {
isDeleting.value = false
uni.hideLoading() uni.hideLoading()
if (deleteRes.code === 200) { if (deleteRes.code === 200) {
uni.showToast({ uni.showToast({
@@ -129,7 +133,7 @@
icon: 'success' icon: 'success'
}) })
setTimeout(() => { setTimeout(() => {
uni.redirectTo({ url: "/pageSubPack/my/myCarIndex" }) uni.navigateBack()
}, 1500) }, 1500)
} else { } else {
uni.showToast({ uni.showToast({

View File

@@ -40,6 +40,9 @@
import { import {
getMyCarList getMyCarList
} from '@/pageSubPack/api/apiSub.js' } from '@/pageSubPack/api/apiSub.js'
import {
onShow
} from '@dcloudio/uni-app'
const statusBarHeight = ref(20) const statusBarHeight = ref(20)
const defaultCarImg = "http://47.104.199.163:9090/images/propertyImgs/defaultCarImg.png" const defaultCarImg = "http://47.104.199.163:9090/images/propertyImgs/defaultCarImg.png"
const carList = ref([]) const carList = ref([])
@@ -61,9 +64,17 @@
} }
// 页面加载时请求第一页 // 页面加载时请求第一页
onMounted(() => { onMounted(() => {
getList() getList()
}) })
// 从子页面返回时刷新列表(如新增/编辑/删除车辆后)
onShow(() => {
pageNum.value = 1
noMoreData.value = false
loadingMore.value = false
carList.value = []
getList()
})
const getList = async () => { const getList = async () => {
// 防重复请求 // 防重复请求
if (loadingMore.value || noMoreData.value) return if (loadingMore.value || noMoreData.value) return
@@ -268,4 +279,4 @@
width: 100vw; width: 100vw;
height: 46px; height: 46px;
} }
</style> </style>