From 7d071218c28ab193cbed1dedcba99ed1c3549e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=AE=B6=E8=AE=AD?= Date: Thu, 3 Sep 2026 18:35:40 +0800 Subject: [PATCH] feat: show login error message; add request timeout & network guard; silence NavigationDuplicated --- src/libs/axios.js | 31 +++++++++++++++++-------------- src/router/index.js | 9 +++++++++ src/store/module/user.js | 7 ++++--- src/view/login/login.vue | 12 ++++++++++-- 4 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/libs/axios.js b/src/libs/axios.js index 9b804d5..2ab052c 100644 --- a/src/libs/axios.js +++ b/src/libs/axios.js @@ -8,6 +8,8 @@ import { TOKEN_KEY, EXPIRETIME_KEY } from '@/libs/util' // import { Spin } from 'view-design' const addErrorLog = errorInfo => { + // 网络错误/超时时 error.response 可能为 undefined,需兜底避免二次报错 + if (!errorInfo || !errorInfo.request) return const { statusText, status, request: { responseURL } } = errorInfo let info = { type: 'ajax', @@ -15,7 +17,7 @@ const addErrorLog = errorInfo => { mes: statusText, url: responseURL } - if (!responseURL.includes('save_error_logger')) store.dispatch('addErrorLog', info) + if (responseURL && !responseURL.includes('save_error_logger')) store.dispatch('addErrorLog', info) } class HttpRequest { @@ -29,6 +31,8 @@ class HttpRequest { headers: { // }, + // 请求超时时间(毫秒),避免后端不可用时页面一直“卡住”无反馈 + timeout: 30000, /** transformRequest 允许在向服务器发送前,修改请求数据,只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream**/ transformRequest: [function (data) { // 对data进行任意转化处理 @@ -57,18 +61,12 @@ class HttpRequest { } this.queue[url] = true - // 根据项目需求添加请求拦截 - //判断请求不是登陆接口的时候,字段是不是过期了,(根据业务逻辑做对应的处理) - //console.log("进入请求拦截器...") - //console.log(url) - - if ((url.indexOf('login') < 0) && (config.url.indexOf('logout') )) { + // 判断请求不是登录/登出接口的时候,检查 token 是否过期(根据业务逻辑做对应的处理) + if ((url.indexOf('login') < 0) && (config.url.indexOf('logout') < 0)) { let curTime = new Date().getTime() let expireTime = store.state.user.expireTime // 获取token过期时间 - //console.log(expireTime) if (expireTime) { let lessTime = eval(expireTime - curTime); // 后台返回的过期时间与现在的进行计算 - //console.log(lessTime) if (lessTime <= 0) { Notice.warning({ title: '登录过期', @@ -76,7 +74,6 @@ class HttpRequest { }) Cookies.remove(TOKEN_KEY); Cookies.remove(EXPIRETIME_KEY); - } } } @@ -87,11 +84,17 @@ class HttpRequest { }) // 响应拦截 instance.interceptors.response.use(res => { - // 根据项目需求添加响应拦截 - //console.log("进入响应拦截器...") - //console.log(res.data.code ) - let code = res.data.code + + // 登录接口:业务失败(HTTP 200 + code!=200,如用户名或密码错误) + // 直接 reject,由登录页展示后端返回的 message,不触发全局登出逻辑 + if (url.indexOf('/login') > -1) { + this.destroy(url) + const { data, status } = res + if (code && code !== 200) return Promise.reject(res.data) + return { data, status } + } + if (code === 415){ // 1、用户名或密码错误 Notice.warning({ diff --git a/src/router/index.js b/src/router/index.js index 45ba77d..1c84c8f 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -11,6 +11,15 @@ import { getPermissionList } from '@/api/sys/sys-permission' const {homeName} = config Vue.use(Router) + +// vue-router 对“重复导航到当前路由”会抛出 NavigationDuplicated(Promise rejection), +// 在关闭标签页、快速点击菜单等场景会向控制台报错,这里统一静默掉这种冗余导航 +const originalPush = Router.prototype.push +Router.prototype.push = function push (location, onResolve, onReject) { + if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject) + return originalPush.call(this, location).catch(err => err) +} + const router = new Router({ routes, mode: 'history' diff --git a/src/store/module/user.js b/src/store/module/user.js index b7e3995..a5c4ba4 100644 --- a/src/store/module/user.js +++ b/src/store/module/user.js @@ -51,16 +51,17 @@ export default { ip }).then(res => { let data = res.data - console.log("data:", data) if (data.code === 200) { let token = data.token - console.log("token:", token) commit('setToken', token) let curDate = new Date() let expireTime = curDate.getTime() +24*60*60*1000 // 假设过期时间为后一天24*60*60*1000 commit('setExpireTime',expireTime) + resolve(data) + } else { + // 登录失败(如用户名或密码错误):把错误抛出给页面提示 + reject(new Error(data.message || '登录失败,请稍后重试')) } - resolve(data) }).catch(err => { reject(err) }) diff --git a/src/view/login/login.vue b/src/view/login/login.vue index 5d4a930..a82e419 100644 --- a/src/view/login/login.vue +++ b/src/view/login/login.vue @@ -47,7 +47,8 @@ 'getUserInfo' ]), handleSubmit({username, password}) { - var ip = returnCitySN["cip"] + // returnCitySN 由 index.html 引用的第三方脚本定义,可能加载失败,需兜底 + var ip = (typeof returnCitySN !== 'undefined' && returnCitySN) ? returnCitySN["cip"] : '' this.$refs.loginForm.loading = true this.handleLogin({username, password, ip}).then(res => { setTimeout(() => { @@ -56,7 +57,14 @@ name: this.$config.homeName }) }, 400) - + }).catch(err => { + // 登录失败 / 后端不可用 / 请求超时统一在此提示,并复位按钮 loading + this.$refs.loginForm.loading = false + let msg = (err && err.message) ? err.message : '' + if (!msg || msg.indexOf('Network Error') > -1 || msg.indexOf('timeout') > -1 || msg.indexOf('ECONNABORTED') > -1) { + msg = '网络异常或服务不可用,请稍后重试' + } + this.$Message.error(msg) }) } }