feat(auth/exam): 登录重试同源 + 静默刷新透传 + 交卷链路加固(R5/R8)
前端重试与后端闸门同源(须成对上线) - src/api/request.js: 新增 parseRetryAfterMs / computeLoginRetryDelayMs, 退避取 max(指数退避, Retry-After) + ±20% 抖动 + 服务端值封顶; silent 透传(doSilentRefresh 不再吞掉 silent 标记)。 - src/api/auth.js / src/stores/auth.js: 登录 429 走同源重试;错误分类区分 "TCP 未握手(可重试)"与"业务码失败(不可重试)"。 - src/stores/exam.js: 交卷链路顺序红线(取卷 -> 作答 -> submit-all 严格顺序), 答案不丢 / 重复不脏 / 窗口不锁死。 - src/views/Competition.vue: 中职/高职综合大题区块(3 x textarea,2000 字上限 与右下角计数);本科/教师组保留 Word 上传入口。 验证:源码抽取测试 8/8(含判别力自证:不读 header 的对照实现必 FAIL); npm run build BUILD_RC=0(12.82s / 1711 modules);真实交卷端到端 code=0。
This commit is contained in:
@@ -31,13 +31,14 @@ export function activate(data) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 参赛者登录(身份证号 + 密码)
|
||||
* @param {string} idNumber - 身份证号
|
||||
* 参赛者登录(手机号 + 密码)
|
||||
* @param {string} phone - 手机号
|
||||
* @param {string} password - 密码
|
||||
* @param {object} [config] - 透传给 axios 的配置(如 { silent: true } 抑制拦截器逐次错误提示)
|
||||
* @returns {Promise} { token, user }
|
||||
*/
|
||||
export function login(phone, password) {
|
||||
return request.post('/auth/login', { phone, password })
|
||||
export function login(phone, password, config) {
|
||||
return request.post('/auth/login', { phone, password }, config)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -175,6 +175,16 @@ request.interceptors.response.use(
|
||||
return res
|
||||
},
|
||||
async (error) => {
|
||||
// silent 模式(2026-09-21 新增):调用方可在请求 config 上传 silent: true
|
||||
// 来抑制**逐次**错误提示。供自带走重试的调用方(登录 / 交卷 / 逐题保存)使用,
|
||||
// 避免重试期间连弹多条相同报错、让考生误以为系统崩了。
|
||||
// ⛔ 不传 silent 的既有调用方行为**完全不变**(silent 为 undefined → falsy)。
|
||||
// 注:401 分支的提示由 forceLogout 负责(token 真失效必须让用户看到),不受此影响。
|
||||
const silent = !!(error.config && error.config.silent)
|
||||
const notifyError = (msg) => {
|
||||
if (!silent) ElMessage.error(msg)
|
||||
}
|
||||
|
||||
// 处理 HTTP 状态码错误
|
||||
if (error.response) {
|
||||
const { status } = error.response
|
||||
@@ -240,26 +250,26 @@ request.interceptors.response.use(
|
||||
}
|
||||
|
||||
case 403:
|
||||
ElMessage.error('您没有权限执行此操作')
|
||||
notifyError('您没有权限执行此操作')
|
||||
break
|
||||
|
||||
case 404:
|
||||
ElMessage.error('请求的资源不存在')
|
||||
notifyError('请求的资源不存在')
|
||||
break
|
||||
|
||||
case 500:
|
||||
ElMessage.error('服务器内部错误,请稍后重试')
|
||||
notifyError('服务器内部错误,请稍后重试')
|
||||
break
|
||||
|
||||
default:
|
||||
ElMessage.error(error.response.data?.message || `请求失败 (${status})`)
|
||||
notifyError(error.response.data?.message || `请求失败 (${status})`)
|
||||
}
|
||||
} else if (error.code === 'ECONNABORTED') {
|
||||
// 请求超时
|
||||
ElMessage.error('请求超时,请检查网络连接')
|
||||
notifyError('请求超时,请检查网络连接')
|
||||
} else {
|
||||
// 网络错误或其他错误
|
||||
ElMessage.error('网络连接异常,请检查网络')
|
||||
notifyError('网络连接异常,请检查网络')
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
|
||||
Reference in New Issue
Block a user