import { api8000 } from "@/utils/request"; // 确保这是您项目中正确的请求函数 import { ElMessage } from 'element-plus'; import 'element-plus/es/components/message/style/css'; // 🔹 定义查询参数类型 interface QueryTableParams { table: string; filters?: string; // JSON 字符串格式的过滤条件 page?: number; page_size?: number; sort_by?: string; sort_order?: 'asc' | 'desc'; } // 🔹 定义查询返回结果类型 interface QueryTableResponse { data: Record[]; pagination: { total: number; page: number; page_size: number; total_pages: number; }; } /// 🔹 通用查询表格数据 (增强版) export const queryTable = (params: QueryTableParams): Promise => { // 设置默认分页参数 const requestParams = { page: 1, page_size: 10, sort_order: 'asc' as const, // 默认升序 ...params, // 覆盖默认值 }; return api8000({ url: "/admin/query_table", // 确保后端有此对应路由 method: "GET", params: requestParams, }) .then((res) => { // 假设后端严格按照定义返回结构 return res.data as QueryTableResponse; }) .catch((err) => { console.error("查询数据失败:", err); if (err.response?.status === 401) throw err; throw new Error(err.message || "获取数据失败"); }); }; // 🔹 获取表格数据 (保持兼容性,仅获取所有数据) export const table = (params: { table: string }) => { return api8000({ url: "/admin/table", method: "GET", params: params, }) .then((res) => { // 适配后端返回的直接数组格式 return { data: Array.isArray(res.data) ? res.data : [] }; }) .catch((err) => { if (err.response?.status === 401) throw err; throw new Error(err.message || "获取数据失败"); }); }; // 🔹 新增数据 export const addItem = (data: { table: string; item: Record; }) => { return api8000({ url: "/admin/add_item", method: "POST", params: { table: data.table }, data: data.item, }).catch((error) => { if (error.response?.status === 400 && error.response.data.detail === "重复数据") { ElMessage.error("数据重复,请重新添加"); } throw error; }); }; // 🔹 更新数据 export const updateItem = (data: { table: string; id: number; update_data: Record; }) => { return api8000({ url: "/admin/update_item", method: "PUT", params: { table: data.table, id: data.id }, data: data.update_data, }).catch((error) => { if (error.response?.status === 400 && error.response.data.detail === "重复数据") { ElMessage.error("数据重复,无法更新"); } throw error; }); }; // 🔹 删除数据 export const deleteItemApi = (params: { table: string; id: number; }) => { return api8000({ url: "/admin/delete_item", method: "DELETE", params: params, }); }; // 🔹 导出数据 (改进版) export const exportData = (table: string, format: string = "xlsx") => { return api8000({ url: "/admin/export_data", method: "GET", params: { table, fmt: format }, // 注意后端参数是 fmt responseType: "blob", }) .then((response) => { // --- 修改从这里开始 --- let filename = `${table}_data.${format === "xlsx" ? "xlsx" : "csv"}`; // 默认文件名 const contentDisposition = response.headers["content-disposition"]; if (contentDisposition) { const filenameMatch = contentDisposition.match(/filename[^;]*=([^;]+)/); // 使用 filename*= 优先匹配 UTF-8 编码的文件名 const utf8FilenameMatch = contentDisposition.match(/filename\*=UTF-8''(.+)/); if (utf8FilenameMatch) { try { // 解码 UTF-8 文件名 filename = decodeURIComponent(utf8FilenameMatch[1]); } catch (e) { console.warn("解码 UTF-8 文件名失败:", e); } } else if (filenameMatch) { try { // 解码普通文件名 (可能包含引号) filename = decodeURIComponent(filenameMatch[1].trim().replace(/['"]/g, '')); } catch (e) { console.warn("解码文件名失败:", e); } } } else { console.warn("响应头中未找到 Content-Disposition,使用默认文件名"); } // --- 修改到这里结束 --- const blob = new Blob([response.data], { type: format === "xlsx" ? "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" : "text/csv", }); const url = window.URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = filename; // 使用解析或默认的文件名 document.body.appendChild(link); // 兼容性建议 link.click(); document.body.removeChild(link); // 兼容性建议 window.URL.revokeObjectURL(url); }) .catch((error) => { console.error("❌ 导出数据失败:", error); throw error; }); }; // 🔹 导入数据 (修正:使用 api8000 替代 customRequest) export const importData = (table: string, file: File) => { // <--- 添加类型注解 here const formData = new FormData(); formData.append("file", file); formData.append("table", table); // 返回 api8000 的 Promise 链 return api8000({ url: "/admin/import_data", method: "POST", data: formData, headers: { "Content-Type": "multipart/form-data", }, }).then(response => { // 检查响应数据 if (response.data && response.data.total_data === 0) { // 如果 total_data 为 0,认为是空数据导入,抛出一个错误 // 可以自定义错误信息或使用 Error 对象 throw new Error('导入的数据为空,请检查上传的文件是否包含有效数据。'); // 或者使用后端可能返回的特定错误码/信息 // throw { code: 'EMPTY_IMPORT_DATA', message: '导入的数据为空...', details: response.data }; } // 如果数据不为空,正常返回响应 return response; }); // 调用者可以通过 .catch() 或 async/await 的 try...catch 捕获上面抛出的错误 }; // 🔹 下载模板 // 辅助函数:解析 Content-Disposition 头以获取文件名 function getFileNameFromContentDisposition(contentDispositionHeader: string): string | null { if (!contentDispositionHeader) { return null; } let fileName: string | null = null; // 尝试匹配 filename* (支持编码) // 格式: filename*=UTF-8''%E4%B8%AD%E6%96%87%E6%96%87%E4%BB%B6%E5%90%8D.xlsx const fileNameStarRegex = /filename\*=(?:UTF-8'')?([^;]+)/i; const fileNameStarMatch = contentDispositionHeader.match(fileNameStarRegex); if (fileNameStarMatch && fileNameStarMatch[1]) { try { // filename* 的值通常是百分号编码的 (Percent-encoding) fileName = decodeURIComponent(fileNameStarMatch[1]); return fileName; } catch (e) { console.warn("解码 filename* 失败:", e); // 如果解码失败,继续尝试 filename } } // 如果 filename* 不存在或解码失败,则尝试匹配 filename const fileNameRegex = /filename=([^;]+)/i; // 移除了开头的 ^,以防前面有空格或其他内容 const fileNameMatch = contentDispositionHeader.match(fileNameRegex); if (fileNameMatch && fileNameMatch[1]) { fileName = fileNameMatch[1].trim(); // 去除首尾空格 // 检查是否被双引号包围 if (fileName.startsWith('"') && fileName.endsWith('"')) { fileName = fileName.substring(1, fileName.length - 1); // 移除双引号 } // 尝试解码,以防它是 URL 编码的 (虽然不如 filename* 标准) try { fileName = decodeURIComponent(fileName); } catch (e) { console.warn("解码 filename 失败:", e); // 如果解码失败,就使用原始提取的字符串(可能包含引号或编码字符) // 或者可以在这里决定是返回 null 还是原始字符串 } return fileName; } // 如果都未匹配到,则返回 null return null; } // 🔹 下载模板 (已修复文件名解析) export const downloadTemplate = (table: string, format: string = "excel") => { return api8000({ url: "/admin/download_template", method: "GET", params: { table, format }, responseType: "blob", }) .then((response) => { const contentDisposition = response.headers["content-disposition"]; let filename: string | null = null; if (contentDisposition) { filename = getFileNameFromContentDisposition(contentDisposition); } // 如果未能从响应头解析出文件名,则使用默认文件名 if (!filename) { console.warn("无法从 Content-Disposition 头解析文件名,使用默认文件名。"); filename = `${table}_template.${format === "excel" ? "xlsx" : "csv"}`; } const blob = new Blob([response.data], { type: format === "excel" ? "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" : "text/csv", }); const url = window.URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = filename; // 使用解析或默认的文件名 document.body.appendChild(link); // 推荐添加到 DOM 再点击 link.click(); document.body.removeChild(link); // 点击后移除 window.URL.revokeObjectURL(url); }) .catch((error) => { console.error("❌ 下载模板失败:", error); // 可以在这里添加用户提示,例如 ElMessage.error("模板下载失败"); throw error; }); };