From ae6b060c25766d81c3b9e0587de28c018a9c4a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=AB=E5=9C=B0=E5=83=A7?= <357099073@qq.com> Date: Thu, 27 Aug 2026 23:02:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=88=86=E7=B1=BB=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/api/file.js | 21 ++++++++++++------- backend/src/utils/request.js | 18 +++++++++++----- .../article/type/components/CategoryNode.vue | 17 ++++++++++++--- .../apps/cms/article/type/components/edit.vue | 16 +++++++------- .../src/views/apps/cms/article/type/index.vue | 7 +++++-- 5 files changed, 54 insertions(+), 25 deletions(-) diff --git a/backend/src/api/file.js b/backend/src/api/file.js index 33b6ec4..afa984f 100644 --- a/backend/src/api/file.js +++ b/backend/src/api/file.js @@ -100,22 +100,27 @@ export function getFileById(id) { * 上传文件 * @param {FormData} formData 文件数据 * @param {Object} options 额外选项 - * @param {string} [options.cate] + * @param {string|number} [options.cate] 文件分组ID,0 为未分类 + * @param {(e: { loaded: number; total?: number }) => void} [options.onUploadProgress] 上传进度 * @returns {Promise} */ export function uploadFile(formData, options = {}) { - if (options.cate) { - formData.append('cate', options.cate); + // 0 表示「未分类」,不能用 truthy 判断 + if (options.cate !== undefined && options.cate !== null && options.cate !== "") { + formData.append("cate", String(options.cate)); } - return request({ + const config = { url: "/backend/uploadfile", method: "post", data: formData, - headers: { - "Content-Type": "multipart/form-data" - } - }); + // 上传大文件不设超时;勿手写 Content-Type(需浏览器生成 multipart boundary) + timeout: 0, + }; + if (typeof options.onUploadProgress === "function") { + config.onUploadProgress = options.onUploadProgress; + } + return request(config); } /** diff --git a/backend/src/utils/request.js b/backend/src/utils/request.js index 805e569..256efe5 100644 --- a/backend/src/utils/request.js +++ b/backend/src/utils/request.js @@ -3,10 +3,10 @@ import axios from 'axios'; // 获取API基础URL,添加调试信息 const apiBaseURL = import.meta.env.VITE_API_BASE_URL; -// 创建axios实例 +// 创建axios实例(普通接口 5min;大文件上传在 api/file.js 单独设置更长 timeout) const service = axios.create({ baseURL: apiBaseURL, - timeout: 10000, + timeout: 300000, withCredentials: false // JWT 不需要 Cookie }); @@ -18,9 +18,13 @@ service.interceptors.request.use( config.headers['Authorization'] = `Bearer ${token}`; } - // 对于有 body 的请求(POST、PUT、PATCH),确保设置 Content-Type + // 对于有 body 的请求(POST、PUT、PATCH),默认 JSON; + // FormData 必须由浏览器自行设置 Content-Type,否则缺少 multipart boundary,服务端无法解析 if (config.data && ['post', 'put', 'patch'].includes(config.method?.toLowerCase())) { - if (!config.headers['Content-Type'] && !config.headers['content-type']) { + if (typeof FormData !== 'undefined' && config.data instanceof FormData) { + delete config.headers['Content-Type']; + delete config.headers['content-type']; + } else if (!config.headers['Content-Type'] && !config.headers['content-type']) { config.headers['Content-Type'] = 'application/json'; } } @@ -54,7 +58,11 @@ service.interceptors.response.use( console.error('请求失败,请稍后再试'); } } else if (error.request) { - console.error('请求失败,请检查网络连接'); + if (error.code === 'ECONNABORTED') { + console.error('请求超时,请稍后重试'); + } else { + console.error('请求失败,请检查网络连接'); + } } else { console.error('请求配置错误'); } diff --git a/backend/src/views/apps/cms/article/type/components/CategoryNode.vue b/backend/src/views/apps/cms/article/type/components/CategoryNode.vue index f78a170..10130dc 100644 --- a/backend/src/views/apps/cms/article/type/components/CategoryNode.vue +++ b/backend/src/views/apps/cms/article/type/components/CategoryNode.vue @@ -47,14 +47,19 @@