admin.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. // @/API/admin.ts
  2. import { api8000 } from '@/utils/request';
  3. import { ElMessage } from 'element-plus';
  4. import 'element-plus/es/components/message/style/css';
  5. // 🔹 获取表格数据
  6. export const table = (params: { table: string }) => {
  7. return api8000({
  8. url: "/admin/table",
  9. method: "GET",
  10. params: params,
  11. })
  12. .then((res) => {
  13. // 适配后端返回的直接数组格式
  14. return { data: Array.isArray(res.data) ? res.data : [] };
  15. })
  16. .catch((err) => {
  17. if (err.response?.status === 401) throw err;
  18. throw new Error(err.message || "获取数据失败");
  19. });
  20. };
  21. // 🔹 新增数据
  22. export const addItem = (data: {
  23. table: string;
  24. item: Record<string, any>;
  25. }) => {
  26. return api8000({
  27. url: "/admin/add_item",
  28. method: "POST",
  29. params: { table: data.table },
  30. data: data.item,
  31. }).catch((error) => {
  32. if (error.response?.status === 400 && error.response.data.detail === "重复数据") {
  33. ElMessage.error("数据重复,请重新添加");
  34. }
  35. throw error;
  36. });
  37. };
  38. // 🔹 更新数据
  39. export const updateItem = (data: {
  40. table: string;
  41. id: number;
  42. update_data: Record<string, any>;
  43. }) => {
  44. return api8000({
  45. url: "/admin/update_item",
  46. method: "PUT",
  47. params: { table: data.table, id: data.id },
  48. data: data.update_data,
  49. }).catch((error) => {
  50. if (error.response?.status === 400 && error.response.data.detail === "重复数据") {
  51. ElMessage.error("数据重复,无法更新");
  52. }
  53. throw error;
  54. });
  55. };
  56. // 🔹 删除数据
  57. export const deleteItemApi = (params: {
  58. table: string;
  59. id: number;
  60. }) => {
  61. return api8000({
  62. url: "/admin/delete_item",
  63. method: "DELETE",
  64. params: params,
  65. });
  66. };
  67. // 🔹 导出数据
  68. export const exportData = (table: string, format: string = "xlsx") => {
  69. return api8000({
  70. url: "/admin/export_data",
  71. method: "GET",
  72. params: { table, fmt: format },
  73. responseType: "blob",
  74. })
  75. .then((response) => {
  76. const contentDisposition = response.headers["content-disposition"];
  77. if (!contentDisposition) {
  78. throw new Error("无法获取文件名");
  79. }
  80. const filenameMatch = contentDisposition.match(/filename=([^;]+)/);
  81. const filename = filenameMatch ? decodeURIComponent(filenameMatch[1]) : `${table}_data.${format === "xlsx" ? "xlsx" : "csv"}`;
  82. const blob = new Blob([response.data], {
  83. type: format === "xlsx"
  84. ? "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
  85. : "text/csv",
  86. });
  87. const url = window.URL.createObjectURL(blob);
  88. const link = document.createElement("a");
  89. link.href = url;
  90. link.download = filename;
  91. link.click();
  92. window.URL.revokeObjectURL(url);
  93. })
  94. .catch((error) => {
  95. console.error("❌ 导出数据失败:", error);
  96. throw error;
  97. });
  98. };
  99. // 🔹 导入数据
  100. export const importData = (table: string, file: File) => {
  101. // 1. 前置校验提示(文件为空/格式错误)
  102. if (!file || file.size === 0) {
  103. const errorMsg = "❌ 请选择非空的Excel/CSV文件,空文件无法导入";
  104. console.error(errorMsg);
  105. throw new Error(errorMsg);
  106. }
  107. const validExtensions = ['xlsx', 'csv'];
  108. const fileExtension = file.name.split('.').pop()?.toLowerCase();
  109. if (!fileExtension || !validExtensions.includes(fileExtension)) {
  110. const errorMsg = `❌ 仅支持 .xlsx 和 .csv 格式文件,当前文件格式为 .${fileExtension || '未知'}`;
  111. console.error(errorMsg);
  112. throw new Error(errorMsg);
  113. }
  114. // 2. 创建FormData(与后端参数名对齐)
  115. const formData = new FormData();
  116. formData.append('table', table);
  117. formData.append('file', file);
  118. return api8000({
  119. url: "/admin/import_data",
  120. method: "POST",
  121. data: formData,
  122. headers: {
  123. 'Content-Type': 'multipart/form-data'
  124. }
  125. })
  126. .then((response) => {
  127. const { total_data, new_data, duplicate_data } = response.data;
  128. let successMsg = "";
  129. if (total_data === 0) {
  130. successMsg = "✅ 文件导入成功,但文件中无有效数据(空内容)";
  131. } else {
  132. successMsg = `✅ 数据导入成功!共${total_data}条数据,新增${new_data}条,重复${duplicate_data}条`;
  133. }
  134. console.log(successMsg, "详情:", response.data);
  135. ElMessage.success(successMsg);
  136. return response.data;
  137. })
  138. .catch((error) => {
  139. let errorMsg = "❌ 导入失败,请稍后重试";
  140. if (error.response?.data) {
  141. const resData = error.response.data;
  142. if (resData.detail?.duplicate_count && resData.detail?.duplicates) {
  143. const { duplicate_count, duplicates, check_fields } = resData.detail;
  144. const duplicateDetails = duplicates.map((item: any) =>
  145. `第${item.row_number}行:${check_fields.map((f: string) => `${f}=${item.duplicate_fields[f] || '空值'}`).join(',')}`
  146. ).join('\n');
  147. errorMsg = `❌ 检测到${duplicate_count}条重复数据(基于${check_fields.join('、')}字段):\n${duplicateDetails}`;
  148. } else if (resData.detail) {
  149. errorMsg = `❌ ${resData.detail.message || resData.detail}`;
  150. } else if (resData.message) {
  151. errorMsg = `❌ ${resData.message}`;
  152. }
  153. } else if (!error.response) {
  154. errorMsg = "❌ 网络异常或服务器无响应,请检查网络连接后重试";
  155. } else {
  156. errorMsg = "❌ 未知错误,可能是文件损坏或服务器繁忙";
  157. }
  158. console.error(errorMsg, "错误详情:", error);
  159. ElMessage.error(errorMsg);
  160. throw new Error(errorMsg);
  161. });
  162. };
  163. // 🔹 下载模板
  164. export const downloadTemplate = (table: string, format: string = "excel") => {
  165. return api8000({
  166. url: "/admin/download_template",
  167. method: "GET",
  168. params: { table, format },
  169. responseType: "blob",
  170. })
  171. .then((response) => {
  172. const contentDisposition = response.headers["content-disposition"];
  173. if (!contentDisposition) {
  174. throw new Error("无法获取文件名");
  175. }
  176. const filenameMatch = contentDisposition.match(/filename=([^;]+)/);
  177. const filename = filenameMatch ? decodeURIComponent(filenameMatch[1]) : `${table}_template.${format === "excel" ? "xlsx" : "csv"}`;
  178. const blob = new Blob([response.data], {
  179. type: format === "excel"
  180. ? "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
  181. : "text/csv",
  182. });
  183. const url = window.URL.createObjectURL(blob);
  184. const link = document.createElement("a");
  185. link.href = url;
  186. link.download = filename;
  187. link.click();
  188. window.URL.revokeObjectURL(url);
  189. })
  190. .catch((error) => {
  191. console.error("❌ 下载模板失败:", error);
  192. throw error;
  193. });
  194. };