From 41487b9a8ad1353c21dd1ac2bfafcf8d1d4f34e4 Mon Sep 17 00:00:00 2001
From: xyc <jc_xiong@hotmail.com>
Date: 星期五, 28 八月 2026 17:09:47 +0800
Subject: [PATCH] feat: 所有前端 API 请求加超时保护,超时/网络异常优雅提示,不再无限 pending
---
traffic-audit-web/src/views/ReportExport.vue | 11 +++--
traffic-audit-web/src/views/RuleManage.vue | 6 +-
traffic-audit-web/src/views/DataImport.vue | 6 ++-
traffic-audit-web/src/views/Login.vue | 7 ++-
traffic-audit-web/src/components/Captcha3D.vue | 4 +-
traffic-audit-web/src/views/DataView.vue | 3 +
traffic-audit-web/src/views/UserManage.vue | 3 +
traffic-audit-web/src/views/AuditResult.vue | 22 +++++-----
traffic-audit-web/src/components/AiChatDialog.vue | 4 +-
traffic-audit-web/src/api/index.js | 11 +++++
traffic-audit-web/src/views/ExplainReview.vue | 3 +
11 files changed, 48 insertions(+), 32 deletions(-)
diff --git a/traffic-audit-web/src/api/index.js b/traffic-audit-web/src/api/index.js
index b50c451..bc8a6c6 100644
--- a/traffic-audit-web/src/api/index.js
+++ b/traffic-audit-web/src/api/index.js
@@ -1,8 +1,9 @@
锘縤mport axios from 'axios'
+import { Message } from 'element-ui'
const api = axios.create({
baseURL: '/api',
- timeout: 30000
+ timeout: 15000
})
api.interceptors.request.use(config => {
@@ -17,6 +18,14 @@
if (error.response && error.response.status === 401) {
localStorage.removeItem('token')
window.location.href = '/login'
+ return Promise.reject(error)
+ }
+ if (error.code === 'ECONNABORTED') {
+ error._toast = true
+ Message.error('璇锋眰瓒呮椂锛氭湇鍔″櫒绻佸繖鎴栨棤鍝嶅簲锛岃绋嶅悗閲嶈瘯')
+ } else if (!error.response) {
+ error._toast = true
+ Message.error('缃戠粶寮傚父锛氭棤娉曡繛鎺ユ湇鍔″櫒锛岃妫�鏌ョ綉缁滃悗閲嶈瘯')
}
return Promise.reject(error)
}
diff --git a/traffic-audit-web/src/components/AiChatDialog.vue b/traffic-audit-web/src/components/AiChatDialog.vue
index efee353..82afaad 100644
--- a/traffic-audit-web/src/components/AiChatDialog.vue
+++ b/traffic-audit-web/src/components/AiChatDialog.vue
@@ -113,7 +113,7 @@
api.post('/llm/chat', {
messages: [{ role: 'user', content: '璇风敤缁撴瀯鍖� Markdown锛堝皬鑺傛爣棰樸�佸姞绮椼�佸垪琛級鍒嗘瀽璇ヤ紒涓氱殑瑙i噴鏄惁鍚堢悊锛屽厛缁欑粨璁猴紝鍐嶅垪鏁版嵁渚濇嵁锛屾渶鍚庣粰鍑哄缃缓璁��' }],
context: this.context
- })
+ }, { timeout: 120000 })
.then(res => { this.pushAssistant(res.data || 'AI 鏈繑鍥炲唴瀹癸紝璇烽噸璇�') })
.catch(() => { this.pushAssistant('AI 鍒嗘瀽澶辫触锛岃绋嶅悗閲嶈瘯鎴栦汉宸ュ垽鏂�') })
.finally(() => { this.$nextTick(() => this.scrollToBottom()) })
@@ -147,7 +147,7 @@
this.userScrolledUp = false
this.chatLoading = true
this.$nextTick(() => this.scrollToBottom())
- api.post('/llm/chat', { messages: this.chatMessages, context: this.context })
+ api.post('/llm/chat', { messages: this.chatMessages, context: this.context }, { timeout: 120000 })
.then(res => { this.pushAssistant(res.data || 'AI 鏈繑鍥炲唴瀹癸紝璇烽噸璇�') })
.catch(() => { this.pushAssistant('AI 鍒嗘瀽澶辫触锛岃绋嶅悗閲嶈瘯鎴栦汉宸ュ垽鏂�') })
.finally(() => { this.$nextTick(() => this.scrollToBottom()) })
diff --git a/traffic-audit-web/src/components/Captcha3D.vue b/traffic-audit-web/src/components/Captcha3D.vue
index 0264cc7..d710b33 100644
--- a/traffic-audit-web/src/components/Captcha3D.vue
+++ b/traffic-audit-web/src/components/Captcha3D.vue
@@ -119,7 +119,7 @@
this.selectedIds = []
this.errorMsg = ''
this.$emit('reset')
- axios.get('/api/auth/captcha')
+ axios.get('/api/auth/captcha', { timeout: 10000 })
.then(res => {
this.loading = false
if (res.data.code === 200) {
@@ -176,7 +176,7 @@
axios.post('/api/auth/captcha/verify', {
captchaId: this.challenge.captchaId,
captchaClickIds: this.selectedIds.slice()
- })
+ }, { timeout: 10000 })
.then(res => {
this.loading = false
const d = res.data
diff --git a/traffic-audit-web/src/views/AuditResult.vue b/traffic-audit-web/src/views/AuditResult.vue
index 7ef313c..9f19f80 100644
--- a/traffic-audit-web/src/views/AuditResult.vue
+++ b/traffic-audit-web/src/views/AuditResult.vue
@@ -336,7 +336,7 @@
const fd = new FormData()
fd.append('period', this.period)
fd.append('reportType', this.reportType)
- api.post('/audit/execute', fd)
+ api.post('/audit/execute', fd, { timeout: 120000 })
.then(res => {
const count = (res.data || []).length
return api.get('/audit/ruleStats', { params: { period: this.period, reportType: this.reportType } })
@@ -361,7 +361,7 @@
.then(() => api.get('/audit/results', { params: { period: this.period, reportType: this.reportType } }))
.then(res => { this.applyResults(res.data) })
.then(() => this.loadRuleStats())
- .catch(() => { this.$message.error('瀹℃牳鎵ц澶辫触') })
+ .catch(e => { if (e && e._toast) return; this.$message.error('瀹℃牳鎵ц澶辫触') })
.finally(() => { this.loading = false })
},
markAudited() {
@@ -370,7 +370,7 @@
const fd = new FormData()
fd.append('period', this.period)
fd.append('reportType', this.reportType)
- api.post('/audit/markAudited', fd)
+ api.post('/audit/markAudited', fd, { timeout: 60000 })
.then(res => {
if (res.data) {
this.$message.success('宸叉爣璁拌鏈熴��' + this.reportType + '銆嶄负宸插鏍革紝鎶ヨ〃瀵煎嚭涓嶅啀鎻愮ず鏈鏍�')
@@ -380,7 +380,7 @@
return api.get('/audit/results', { params: { period: this.period, reportType: this.reportType } })
})
.then(res => { this.applyResults(res.data) })
- .catch(() => { this.$message.error('鏍囪澶辫触') })
+ .catch(e => { if (e && e._toast) return; this.$message.error('鏍囪澶辫触') })
.finally(() => { this.marking = false })
},
queryResults() {
@@ -389,7 +389,7 @@
api.get('/audit/results', { params: { period: this.period, reportType: this.reportType } })
.then(res => { this.applyResults(res.data) })
.then(() => this.loadRuleStats())
- .catch(() => { this.$message.error('鏌ヨ澶辫触') })
+ .catch(e => { if (e && e._toast) return; this.$message.error('鏌ヨ澶辫触') })
.finally(() => { this.loading = false })
},
toggleExplain(reportId) {
@@ -412,13 +412,13 @@
fd.append('reportType', this.reportType)
fd.append('reportId', reportId)
fd.append('explanation', explanation)
- api.post('/explain/save', fd)
+ api.post('/explain/save', fd, { timeout: 60000 })
.then(() => {
this.$message.success('浼佷笟瑙i噴宸蹭繚瀛�')
this.$set(this.explainDrafts, reportId, explanation)
this.results.forEach(r => { if (r.reportId === reportId) r.verifyExplanation = explanation })
})
- .catch(e => { this.$message.error((e.response && e.response.data && e.response.data.message) || '淇濆瓨澶辫触') })
+ .catch(e => { if (e && e._toast) return; this.$message.error((e.response && e.response.data && e.response.data.message) || '淇濆瓨澶辫触') })
.finally(() => { this.savingExplainId = null })
},
resetDraft(reportId) {
@@ -450,9 +450,9 @@
fd.append('id', row.id)
fd.append('status', status)
if (comment) fd.append('comment', comment)
- api.post('/audit/review', fd)
+ api.post('/audit/review', fd, { timeout: 60000 })
.then(() => { row.status = status; this.$message.success(status === 'CONFIRMED' ? '宸茬‘璁�' : '宸插拷鐣�') })
- .catch(() => { this.$message.error('鎿嶄綔澶辫触') })
+ .catch(e => { if (e && e._toast) return; this.$message.error('鎿嶄綔澶辫触') })
},
updateBatchSelection() { this.batchSelection = this.filteredResults.filter(r => r._selected) },
toggleSelectAll(value) {
@@ -478,9 +478,9 @@
fd.append('id', item.id)
fd.append('status', status)
if (comment) fd.append('comment', comment)
- api.post('/audit/review', fd)
+ api.post('/audit/review', fd, { timeout: 60000 })
.then(() => { item.status = status; done++; if (done === list.length) this.$message.success('宸�' + (status === 'CONFIRMED' ? '纭' : '蹇界暐') + ' ' + done + ' 鏉�') })
- .catch(() => { this.$message.error('閮ㄥ垎鎿嶄綔澶辫触') })
+ .catch(e => { if (e && e._toast) return; this.$message.error('閮ㄥ垎鎿嶄綔澶辫触') })
})
},
clearFilters() { this.enterpriseFilter = ''; this.ruleFilter = '' },
diff --git a/traffic-audit-web/src/views/DataImport.vue b/traffic-audit-web/src/views/DataImport.vue
index 7216ff3..dd789fa 100644
--- a/traffic-audit-web/src/views/DataImport.vue
+++ b/traffic-audit-web/src/views/DataImport.vue
@@ -437,7 +437,7 @@
this.dirFiles = d.files || []
this.dirFilesTruncated = !!d.filesTruncated
})
- .catch(e => { this.$message.error((e.response && e.response.data && e.response.data.message) || '鐩綍鍔犺浇澶辫触') })
+ .catch(e => { if (e && e._toast) return; this.$message.error((e.response && e.response.data && e.response.data.message) || '鐩綍鍔犺浇澶辫触') })
.finally(() => { this.dirLoading = false })
},
goDirParent() { if (this.dirParent) this.loadDirs(this.dirParent) },
@@ -505,6 +505,7 @@
})
.catch(e => {
this.dirImporting = false
+ if (e && e._toast) return
this.$message.error((e.response && e.response.data && e.response.data.message) || '鐩綍涓嶅瓨鍦ㄦ垨鏃犳硶璁块棶锛�' + dir)
})
},
@@ -585,6 +586,7 @@
this.afterImport()
})
.catch(e => {
+ if (e && e._toast) return
this.$message.error((e.response && e.response.data && e.response.data.message) || '鎵归噺瀵煎叆澶辫触')
})
.finally(() => { this.cityDirLoading = false })
@@ -676,7 +678,7 @@
})
this.overview = map
})
- .catch(() => {})
+ .catch(e => { if (!e || !e._toast) this.$message.error('瀵煎叆璁板綍鍔犺浇澶辫触锛岃鍒锋柊閲嶈瘯') })
.finally(() => { this.batchLoading = false })
},
batchKeyToType(k) {
diff --git a/traffic-audit-web/src/views/DataView.vue b/traffic-audit-web/src/views/DataView.vue
index ace9b12..6084401 100644
--- a/traffic-audit-web/src/views/DataView.vue
+++ b/traffic-audit-web/src/views/DataView.vue
@@ -102,7 +102,8 @@
this.page = 1
this.loadData()
}
- }).catch(() => {
+ }).catch(e => {
+ if (e && e._toast) return
this.$message.error('鏁版嵁鏌ヨ澶辫触')
}).finally(() => {
this.loading = false
diff --git a/traffic-audit-web/src/views/ExplainReview.vue b/traffic-audit-web/src/views/ExplainReview.vue
index 246cef6..c714ed7 100644
--- a/traffic-audit-web/src/views/ExplainReview.vue
+++ b/traffic-audit-web/src/views/ExplainReview.vue
@@ -166,7 +166,8 @@
this.page = 1
this.loadData()
}
- }).catch(() => {
+ }).catch(e => {
+ if (e && e._toast) return
this.$message.error('瑙i噴鏌ヨ澶辫触')
}).finally(() => {
this.loading = false
diff --git a/traffic-audit-web/src/views/Login.vue b/traffic-audit-web/src/views/Login.vue
index 2227171..f7dc1a7 100644
--- a/traffic-audit-web/src/views/Login.vue
+++ b/traffic-audit-web/src/views/Login.vue
@@ -44,7 +44,7 @@
}
},
created() {
- axios.get('/api/auth/captcha-config')
+ axios.get('/api/auth/captcha-config', { timeout: 10000 })
.then(res => {
const d = res.data
if (d && d.code === 200) this.captchaRequired = d.data.required !== false
@@ -75,7 +75,7 @@
password: this.form.password,
captchaId: this.captchaId,
captchaClickIds: this.clickIds
- }).then(res => {
+ }, { timeout: 15000 }).then(res => {
this.loggingIn = false
const d = res.data
if (d.code === 200) {
@@ -95,8 +95,9 @@
this.$message.error(d.message || '鐧诲綍澶辫触锛岃閲嶈瘯')
if (this.$refs.captcha) this.$refs.captcha.load()
}
- }).catch(() => {
+ }).catch(e => {
this.loggingIn = false
+ if (e && e.code === 'ECONNABORTED') { this.$message.error('鐧诲綍璇锋眰瓒呮椂锛岃閲嶈瘯'); return }
this.$message.error('缃戠粶閿欒锛岀櫥褰曞け璐�')
})
})
diff --git a/traffic-audit-web/src/views/ReportExport.vue b/traffic-audit-web/src/views/ReportExport.vue
index ca276fb..c7935b4 100644
--- a/traffic-audit-web/src/views/ReportExport.vue
+++ b/traffic-audit-web/src/views/ReportExport.vue
@@ -304,7 +304,7 @@
const m = this.curModule
if (!m || !this.period) return
const params = Object.assign(this.buildParams(), { types: m.items.map(i => i.key).join(',') })
- api.get('/report/ready', { params })
+ api.get('/report/ready', { params, timeout: 60000 })
.then(res => {
const list = (res && res.data && res.data.items) || []
list.forEach(r => {
@@ -349,7 +349,7 @@
if (!items.length) { this.$message.warning('璇烽�夋嫨瑕侀瑙堢殑鎶ヨ〃'); return }
this.loading = 'preview-' + m.key
const params = Object.assign(this.buildParams(), { types: items.map(i => i.key).join(',') })
- api.get('/report/preview', { params })
+ api.get('/report/preview', { params, timeout: 60000 })
.then(res => {
const list = (res && res.data && res.data.items) || []
this.previewItems = list.map(it => {
@@ -387,18 +387,18 @@
const params = this.buildParams()
const doExport = () => {
if (items.length === 1) {
- api.get('/report/export/' + items[0].key, { params, responseType: 'blob' })
+ api.get('/report/export/' + items[0].key, { params, responseType: 'blob', timeout: 120000 })
.then(res => { this.downloadBlob(res, items[0].fileName); this.$message.success('鎶ヨ〃瀵煎嚭鎴愬姛'); this.loading = '' })
.catch(err => this.failWith(err))
return
}
const zipParams = Object.assign({}, params, { types: items.map(i => i.key).join(',') })
- api.get('/report/export/selected', { params: zipParams, responseType: 'blob' })
+ api.get('/report/export/selected', { params: zipParams, responseType: 'blob', timeout: 120000 })
.then(res => { this.downloadBlob(res, '鐢熸垚_鎵�閫夋姤琛╛' + this.period + '.zip'); this.$message.success('宸插鍑� ' + items.length + ' 寮犳姤琛紙zip锛�'); this.loading = '' })
.catch(err => this.failWith(err))
}
const readyParams = Object.assign({}, params, { types: items.map(i => i.key).join(',') })
- api.get('/report/ready', { params: readyParams })
+ api.get('/report/ready', { params: readyParams, timeout: 60000 })
.then(rd => {
const list = (rd && rd.data && rd.data.items) || []
const missing = list.filter(i => !i.ready)
@@ -420,6 +420,7 @@
.catch(() => doExport())
},
failWith(err) {
+ if (err && err._toast) { this.loading = ''; return }
let msg = ''
const d = err && err.response && err.response.data
if (d && typeof d === 'object' && d.message) msg = d.message
diff --git a/traffic-audit-web/src/views/RuleManage.vue b/traffic-audit-web/src/views/RuleManage.vue
index 30d2f57..e39ff03 100644
--- a/traffic-audit-web/src/views/RuleManage.vue
+++ b/traffic-audit-web/src/views/RuleManage.vue
@@ -91,19 +91,19 @@
this.loading = true
api.get('/rules/list', { params: { reportType: this.reportType } })
.then(res => { this.rules = res.data || [] })
- .catch(() => { this.$message.error('瑙勫垯鍔犺浇澶辫触') })
+ .catch(e => { if (e && e._toast) return; this.$message.error('瑙勫垯鍔犺浇澶辫触') })
.finally(() => { this.loading = false })
},
toggleRule(row) {
api.post('/rules/toggle', null, { params: { id: row.id, enabled: row.isEnabled } })
.then(() => { this.$message.success((row.isEnabled ? '宸插惎鐢細' : '宸插仠鐢細') + row.ruleCode) })
- .catch(() => { row.isEnabled = row.isEnabled ? 0 : 1; this.$message.error('鎿嶄綔澶辫触') })
+ .catch(e => { if (e && e._toast) return; row.isEnabled = row.isEnabled ? 0 : 1; this.$message.error('鎿嶄綔澶辫触') })
},
saveThreshold(row) {
api.post('/rules/update', {
id: row.id, threshold: row.threshold, alertLevel: row.alertLevel
}).then(() => { this.$message.success('宸蹭繚瀛橈細' + row.ruleCode) })
- .catch(() => { this.$message.error('淇濆瓨澶辫触') })
+ .catch(e => { if (e && e._toast) return; this.$message.error('淇濆瓨澶辫触') })
}
},
mounted() { this.loadRules() }
diff --git a/traffic-audit-web/src/views/UserManage.vue b/traffic-audit-web/src/views/UserManage.vue
index abae3fe..6f9cc03 100644
--- a/traffic-audit-web/src/views/UserManage.vue
+++ b/traffic-audit-web/src/views/UserManage.vue
@@ -124,7 +124,7 @@
this.loading = true
api.get('/user/list', { params: { keyword: this.keyword } })
.then(res => { this.list = res.data || [] })
- .catch(() => this.$message.error('鐢ㄦ埛鍒楄〃鍔犺浇澶辫触'))
+ .catch(e => { if (e && e._toast) return; this.$message.error('鐢ㄦ埛鍒楄〃鍔犺浇澶辫触') })
.finally(() => { this.loading = false })
},
moduleKeys(modules) {
@@ -164,6 +164,7 @@
this.loadList()
})
.catch(err => {
+ if (err && err._toast) return
const d = err && err.response && err.response.data
this.$message.error((d && d.message) || '淇濆瓨澶辫触')
})
--
Gitblit v1.9.1