feat: show login error message; add request timeout & network guard; silence NavigationDuplicated
CI/CD Deploy (Web) / 构建前端镜像并部署 (push) Successful in 29s

This commit is contained in:
张家训
2026-09-03 18:35:40 +08:00
parent 867647d2cd
commit 7d071218c2
4 changed files with 40 additions and 19 deletions
+17 -14
View File
@@ -8,6 +8,8 @@ import { TOKEN_KEY, EXPIRETIME_KEY } from '@/libs/util'
// import { Spin } from 'view-design' // import { Spin } from 'view-design'
const addErrorLog = errorInfo => { const addErrorLog = errorInfo => {
// 网络错误/超时时 error.response 可能为 undefined,需兜底避免二次报错
if (!errorInfo || !errorInfo.request) return
const { statusText, status, request: { responseURL } } = errorInfo const { statusText, status, request: { responseURL } } = errorInfo
let info = { let info = {
type: 'ajax', type: 'ajax',
@@ -15,7 +17,7 @@ const addErrorLog = errorInfo => {
mes: statusText, mes: statusText,
url: responseURL 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 { class HttpRequest {
@@ -29,6 +31,8 @@ class HttpRequest {
headers: { headers: {
// //
}, },
// 请求超时时间(毫秒),避免后端不可用时页面一直“卡住”无反馈
timeout: 30000,
/** transformRequest 允许在向服务器发送前,修改请求数据,只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream**/ /** transformRequest 允许在向服务器发送前,修改请求数据,只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream**/
transformRequest: [function (data) { transformRequest: [function (data) {
// 对data进行任意转化处理 // 对data进行任意转化处理
@@ -57,18 +61,12 @@ class HttpRequest {
} }
this.queue[url] = true this.queue[url] = true
// 根据项目需求添加请求拦截 // 判断请求不是登录/登出接口的时候,检查 token 是否过期(根据业务逻辑做对应的处理)
//判断请求不是登陆接口的时候,字段是不是过期了,(根据业务逻辑做对应的处理) if ((url.indexOf('login') < 0) && (config.url.indexOf('logout') < 0)) {
//console.log("进入请求拦截器...")
//console.log(url)
if ((url.indexOf('login') < 0) && (config.url.indexOf('logout') )) {
let curTime = new Date().getTime() let curTime = new Date().getTime()
let expireTime = store.state.user.expireTime // 获取token过期时间 let expireTime = store.state.user.expireTime // 获取token过期时间
//console.log(expireTime)
if (expireTime) { if (expireTime) {
let lessTime = eval(expireTime - curTime); // 后台返回的过期时间与现在的进行计算 let lessTime = eval(expireTime - curTime); // 后台返回的过期时间与现在的进行计算
//console.log(lessTime)
if (lessTime <= 0) { if (lessTime <= 0) {
Notice.warning({ Notice.warning({
title: '登录过期', title: '登录过期',
@@ -76,7 +74,6 @@ class HttpRequest {
}) })
Cookies.remove(TOKEN_KEY); Cookies.remove(TOKEN_KEY);
Cookies.remove(EXPIRETIME_KEY); Cookies.remove(EXPIRETIME_KEY);
} }
} }
} }
@@ -87,11 +84,17 @@ class HttpRequest {
}) })
// 响应拦截 // 响应拦截
instance.interceptors.response.use(res => { instance.interceptors.response.use(res => {
// 根据项目需求添加响应拦截
//console.log("进入响应拦截器...")
//console.log(res.data.code )
let code = 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){ if (code === 415){
// 1、用户名或密码错误 // 1、用户名或密码错误
Notice.warning({ Notice.warning({
+9
View File
@@ -11,6 +11,15 @@ import { getPermissionList } from '@/api/sys/sys-permission'
const {homeName} = config const {homeName} = config
Vue.use(Router) 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({ const router = new Router({
routes, routes,
mode: 'history' mode: 'history'
+4 -3
View File
@@ -51,16 +51,17 @@ export default {
ip ip
}).then(res => { }).then(res => {
let data = res.data let data = res.data
console.log("data:", data)
if (data.code === 200) { if (data.code === 200) {
let token = data.token let token = data.token
console.log("token:", token)
commit('setToken', token) commit('setToken', token)
let curDate = new Date() let curDate = new Date()
let expireTime = curDate.getTime() +24*60*60*1000 // 假设过期时间为后一天24*60*60*1000 let expireTime = curDate.getTime() +24*60*60*1000 // 假设过期时间为后一天24*60*60*1000
commit('setExpireTime',expireTime) commit('setExpireTime',expireTime)
}
resolve(data) resolve(data)
} else {
// 登录失败(如用户名或密码错误):把错误抛出给页面提示
reject(new Error(data.message || '登录失败,请稍后重试'))
}
}).catch(err => { }).catch(err => {
reject(err) reject(err)
}) })
+10 -2
View File
@@ -47,7 +47,8 @@
'getUserInfo' 'getUserInfo'
]), ]),
handleSubmit({username, password}) { handleSubmit({username, password}) {
var ip = returnCitySN["cip"] // returnCitySN 由 index.html 引用的第三方脚本定义,可能加载失败,需兜底
var ip = (typeof returnCitySN !== 'undefined' && returnCitySN) ? returnCitySN["cip"] : ''
this.$refs.loginForm.loading = true this.$refs.loginForm.loading = true
this.handleLogin({username, password, ip}).then(res => { this.handleLogin({username, password, ip}).then(res => {
setTimeout(() => { setTimeout(() => {
@@ -56,7 +57,14 @@
name: this.$config.homeName name: this.$config.homeName
}) })
}, 400) }, 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)
}) })
} }
} }