diff --git a/uniapp/youjiala/smoke.out b/uniapp/youjiala/smoke.out new file mode 100644 index 0000000..e69de29 diff --git a/uniapp/youjiala/src/common/http.interceptor.ts b/uniapp/youjiala/src/common/http.interceptor.ts index 943a61b..267f092 100644 --- a/uniapp/youjiala/src/common/http.interceptor.ts +++ b/uniapp/youjiala/src/common/http.interceptor.ts @@ -73,6 +73,21 @@ function getToken(): string { return getJwtInfo()?.AccessToken ?? ""; } +/** + * 归一化后的 Authorization 头值:ASP.NET Core JWT 认证要求 "Bearer " 前缀, + * 存量令牌可能带也可能不带前缀,统一在这里补齐—— + * 主拦截器与 uploadFile/downloadFile/上传队列等旁路通道共用这一个口径, + * 否则旁路裸发 token 会被后端 401(上传失败实测根因)。 + * @returns 无令牌时返回空串(调用方据此不挂 Authorization 头) + */ +export function bearerAuth(): string { + const token = getToken(); + if (!token) { + return ""; + } + return token.startsWith("Bearer ") ? token : `Bearer ${token}`; +} + /** * 认证失败统一收尾:必要时清登录态并跳登录页; * 已经在登录页就不再跳,避免死循环 @@ -224,6 +239,18 @@ function detectBusinessException(data: any): string { return ""; } +/** + * 静默提取业务异常消息(不弹提示):供批量传输通道使用—— + * 上传队列的失败要落在任务行上逐条展示,不能每条弹一个模态。 + * @returns 命中返回异常消息,未命中返回空串 + */ +export function extractBusinessError(data: any): string { + if (!data || typeof data !== "object" || !data.SourceCode) { + return ""; + } + return extractErrorMessage(data) || "业务处理失败"; +} + // ==================== 全局配置(httpPlugin 注册用) ==================== const httpRequestConfig: RequestConfig = { @@ -252,12 +279,9 @@ const httpInterceptor: RequestInterceptor = { config.method = String( config.method || "GET", ).toUpperCase() as RequestOptions["method"]; - const token = getToken(); - if (token) { - // ASP.NET Core JWT Bearer 认证要求 Authorization 带 "Bearer " 前缀(旧系统同) - config.header.Authorization = token.startsWith("Bearer ") - ? token - : `Bearer ${token}`; + const auth = bearerAuth(); + if (auth) { + config.header.Authorization = auth; } config.header.locale = backendLocale(); return config; @@ -360,7 +384,7 @@ export interface UploadOptions { export function uploadFile(options: UploadOptions): Promise { const { url, filePath, name = "file", formData, header = {} } = options; const fullUrl = url.indexOf("http") === 0 ? url : baseUrl + url; - const token = getToken(); + const auth = bearerAuth(); return new Promise((resolve, reject) => { uni.uploadFile({ @@ -369,7 +393,7 @@ export function uploadFile(options: UploadOptions): Promise { name, formData, timeout, - header: token ? { Authorization: token, ...header } : header, + header: auth ? { Authorization: auth, ...header } : header, success: (res) => { if (res.statusCode < 200 || res.statusCode >= 300) { const message = `上传失败[${res.statusCode}]`; @@ -448,7 +472,7 @@ function timestamp(): string { export async function downloadFile(options: DownloadOptions): Promise { const { url, method = "POST", data, filename, extension = "xlsx" } = options; const fullUrl = url.indexOf("http") === 0 ? url : baseUrl + url; - const token = getToken(); + const auth = bearerAuth(); const res = await new Promise( (resolve, reject) => { @@ -458,7 +482,7 @@ export async function downloadFile(options: DownloadOptions): Promise { data, timeout, responseType: "arraybuffer", - header: token ? { Authorization: token } : {}, + header: auth ? { Authorization: auth } : {}, success: resolve, fail: err => reject(new Error(err.errMsg || "下载失败")), }); diff --git a/uniapp/youjiala/src/components/app-page/app-page.vue b/uniapp/youjiala/src/components/app-page/app-page.vue index f89a731..ba3abe3 100644 --- a/uniapp/youjiala/src/components/app-page/app-page.vue +++ b/uniapp/youjiala/src/components/app-page/app-page.vue @@ -20,6 +20,11 @@ defineProps({ type: Boolean, default: false, }, + /** 自定义返回动作(u-navbar customBack 透传):页面做无栈兜底跳转时用 */ + customBack: { + type: Function as PropType<() => void>, + default: undefined, + }, customStyle: { type: [String, Object] as PropType>, default: "", @@ -40,11 +45,9 @@ const background = reactive({