| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- 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<string, any>[];
- pagination: {
- total: number;
- page: number;
- page_size: number;
- total_pages: number;
- };
- }
- /// 🔹 通用查询表格数据 (增强版)
- export const queryTable = (params: QueryTableParams): Promise<QueryTableResponse> => {
- // 设置默认分页参数
- 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<string, any>;
- }) => {
- 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<string, any>;
- }) => {
- 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;
- });
- };
|