api.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import { StudentCheckItem, AllCheckFilter } from "./types";
  2. import { useAppStore } from "@/store";
  3. import { randomCode } from "@/utils/tool";
  4. import axios from "axios";
  5. import Papa from "papaparse";
  6. const appStore = useAppStore();
  7. export const allCheckList = async (
  8. params: AllCheckFilter
  9. ): Promise<StudentCheckItem[]> => {
  10. // if (!params.isCheck && !params.imageName) {
  11. // return Promise.resolve([]);
  12. // }
  13. const fname = params.isCheck ? `check-${params.menuKey}` : "success";
  14. const url = `${appStore.serverUrl}/${fname}.csv?${randomCode()}`;
  15. let students: StudentCheckItem[] = await fetchAndParseData(url).catch(
  16. () => []
  17. );
  18. if (params.imageName) {
  19. students = students.filter((item) => item.imageName === params.imageName);
  20. }
  21. // console.log(students);
  22. const cacheList = await fetchCacheList();
  23. const data = (students || []).map((item) => {
  24. return {
  25. ...item,
  26. checked: cacheList.includes(item.imageName),
  27. };
  28. });
  29. // console.log(data);
  30. return Promise.resolve(data);
  31. };
  32. export const getExamNumberInfo = async (
  33. imageName: string
  34. ): Promise<DataCheckListResult> => {
  35. const url = `${appStore.serverUrl}/cache/${imageName}.csv?${randomCode()}`;
  36. const students = await fetchAndParseData(url).catch(() => []);
  37. if (!students || students.length === 0) {
  38. return Promise.resolve(null);
  39. }
  40. const item = students[0];
  41. const questions = (item.smda ? item.smda.split("|") : []).map((item) =>
  42. item.trim().replace(/[\.\?]/g, "#")
  43. );
  44. return Promise.resolve({ ...students[0], questions });
  45. };
  46. export const fetchCacheList = async () => {
  47. try {
  48. const res = await axios.get(`${appStore.serverUrl}/cache/`);
  49. const parser = new DOMParser();
  50. const doc = parser.parseFromString(res.data, "text/html");
  51. const links = Array.from(doc.querySelectorAll("a"))
  52. .map((a) => a.getAttribute("href"))
  53. .filter((href) => href && /\/cache\/.*\.csv$/.test(href))
  54. .map((href) => {
  55. // Extract filename without extension
  56. const parts = href.split("/");
  57. const filenameWithExtension = parts[parts.length - 1];
  58. return filenameWithExtension.replace(".csv", "");
  59. });
  60. // console.log("缓存文件列表:", links);
  61. return links; // Return the extracted list
  62. } catch (err) {
  63. return [];
  64. console.error("获取失败:", err);
  65. }
  66. };
  67. export const saveCheck = async (data: string[][], filename): Promise<any> => {
  68. const url = `${appStore.serverUrl}/upload?path=/cache`;
  69. return uploadCsvData(url, data, filename);
  70. };
  71. /**
  72. * 请求给定远程地址,获取文件流数据(CSV 或 JSON),并解析其内容。
  73. * @param remoteUrl 远程文件地址
  74. * @returns 解析后的数据
  75. */
  76. export const fetchAndParseData = async (remoteUrl: string): Promise<any> => {
  77. try {
  78. const response = await axios.get(remoteUrl, {
  79. responseType: "text", // Fetch as text to handle both JSON and CSV
  80. });
  81. const contentType = response.headers["content-type"];
  82. let data;
  83. if (contentType && contentType.includes("application/json")) {
  84. // Try parsing as JSON
  85. try {
  86. data = JSON.parse(response.data);
  87. } catch (error) {
  88. console.error("Error parsing JSON:", error);
  89. throw new Error("Failed to parse JSON data");
  90. }
  91. } else if (contentType && contentType.includes("text/csv")) {
  92. // Try parsing as CSV
  93. return new Promise((resolve, reject) => {
  94. Papa.parse(response.data, {
  95. header: true, // Assume header row
  96. skipEmptyLines: true,
  97. complete: (results) => {
  98. resolve(results.data);
  99. },
  100. error: (error: any) => {
  101. console.error("Error parsing CSV:", error);
  102. reject(new Error("Failed to parse CSV data"));
  103. },
  104. });
  105. });
  106. } else {
  107. // Attempt to guess format or throw error
  108. try {
  109. // Try JSON first
  110. data = JSON.parse(response.data);
  111. } catch (jsonError) {
  112. // If JSON fails, try CSV
  113. return new Promise((resolve, reject) => {
  114. Papa.parse(response.data, {
  115. header: true,
  116. skipEmptyLines: true,
  117. complete: (results) => {
  118. if (results.errors.length > 0) {
  119. console.error("CSV parsing errors:", results.errors);
  120. // If CSV parsing also has issues, reject
  121. reject(
  122. new Error(
  123. "Failed to parse data: Unknown format or invalid content"
  124. )
  125. );
  126. } else {
  127. resolve(results.data);
  128. }
  129. },
  130. error: (csvError: any) => {
  131. console.error("Error parsing CSV:", csvError);
  132. reject(
  133. new Error(
  134. "Failed to parse data: Could not determine format or invalid content"
  135. )
  136. );
  137. },
  138. });
  139. });
  140. }
  141. }
  142. return data;
  143. } catch (error) {
  144. console.error("Error fetching or parsing data:", error);
  145. throw new Error("Failed to fetch or parse data from the remote URL");
  146. }
  147. };
  148. /**
  149. * 构建 CSV 文件内容并上传到指定 URL。
  150. * 注意:渲染进程中上传成功后,会触发一个重定向,想要不处理重定向只能在主进程中处理上传
  151. * @param uploadUrl 上传的目标 URL
  152. * @param data 要转换为 CSV 并上传的数据数组
  153. * @param filename 上传的文件名
  154. * @returns 上传结果
  155. */
  156. export const uploadCsvData = async (
  157. uploadUrl: string,
  158. data: string[][],
  159. filename: string = "upload.csv"
  160. ): Promise<any> => {
  161. try {
  162. // Convert data array to CSV string
  163. const csvString = Papa.unparse(data);
  164. // Send data to main process for upload
  165. // Assuming 'window.electron.ipcRenderer.invoke' is available for IPC
  166. // The channel name 'upload-csv' is an example; it should match the handler in the main process.
  167. const result = await window.electronApi.uploadCsvData({
  168. uploadUrl,
  169. csvString,
  170. filename,
  171. });
  172. if (!result.error) return result.data;
  173. throw new Error(result.message || "Failed to send CSV data for upload");
  174. } catch (error) {
  175. console.error("Error preparing or sending CSV data for upload:", error);
  176. // It's good practice to re-throw or handle the error appropriately
  177. // For instance, if the error is already an Error object with a message, just re-throw it
  178. if (error instanceof Error) {
  179. throw error;
  180. }
  181. throw new Error("Failed to prepare or send CSV data for upload");
  182. }
  183. };