zhangjie преди 1 месец
родител
ревизия
ed28ec2af6

+ 1 - 0
package.json

@@ -25,6 +25,7 @@
     "echarts": "^5.5.1",
     "electron-log": "^5.2.0",
     "element-resize-detector": "^1.2.4",
+    "form-data": "^4.0.2",
     "ini-parser": "^0.0.2",
     "less": "^4.2.0",
     "lodash-es": "^4.17.21",

+ 81 - 37
pnpm-lock.yaml

@@ -32,6 +32,7 @@ specifiers:
   electron-log: ^5.2.0
   element-resize-detector: ^1.2.4
   esbuild-loader: ^4.0.2
+  form-data: ^4.0.2
   ini-parser: ^0.0.2
   less: ^4.2.0
   lodash-es: ^4.17.21
@@ -73,6 +74,7 @@ dependencies:
   echarts: 5.5.1
   electron-log: 5.2.0
   element-resize-detector: 1.2.4
+  form-data: 4.0.2
   ini-parser: 0.0.2
   less: 4.2.0
   lodash-es: 4.17.21
@@ -3470,7 +3472,7 @@ packages:
       ejs: 3.1.10
       electron-builder-squirrel-windows: 24.13.3_dmg-builder@24.13.3
       electron-publish: 24.13.1
-      form-data: 4.0.0
+      form-data: 4.0.2
       fs-extra: 10.1.0
       hosted-git-info: 4.1.0
       is-ci: 3.0.1
@@ -3591,7 +3593,7 @@ packages:
     resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==}
     dependencies:
       follow-redirects: 1.15.8
-      form-data: 4.0.0
+      form-data: 4.0.2
       proxy-from-env: 1.1.0
     transitivePeerDependencies:
       - debug
@@ -3872,14 +3874,21 @@ packages:
       responselike: 2.0.1
     dev: true
 
+  /call-bind-apply-helpers/1.0.2:
+    resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      es-errors: 1.3.0
+      function-bind: 1.1.2
+
   /call-bind/1.0.7:
     resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
     engines: {node: '>= 0.4'}
     dependencies:
-      es-define-property: 1.0.0
+      es-define-property: 1.0.1
       es-errors: 1.3.0
       function-bind: 1.1.2
-      get-intrinsic: 1.2.4
+      get-intrinsic: 1.3.0
       set-function-length: 1.2.2
     dev: true
 
@@ -4361,9 +4370,9 @@ packages:
     resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
     engines: {node: '>= 0.4'}
     dependencies:
-      es-define-property: 1.0.0
+      es-define-property: 1.0.1
       es-errors: 1.3.0
-      gopd: 1.0.1
+      gopd: 1.2.0
     dev: true
 
   /define-lazy-prop/2.0.0:
@@ -4484,6 +4493,14 @@ packages:
     engines: {node: '>=10'}
     dev: true
 
+  /dunder-proto/1.0.1:
+    resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      call-bind-apply-helpers: 1.0.2
+      es-errors: 1.3.0
+      gopd: 1.2.0
+
   /duplexer/0.1.2:
     resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
     dev: true
@@ -4677,22 +4694,33 @@ packages:
       is-arrayish: 0.2.1
     dev: true
 
-  /es-define-property/1.0.0:
-    resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==}
+  /es-define-property/1.0.1:
+    resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
     engines: {node: '>= 0.4'}
-    dependencies:
-      get-intrinsic: 1.2.4
-    dev: true
 
   /es-errors/1.3.0:
     resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
     engines: {node: '>= 0.4'}
-    dev: true
 
   /es-module-lexer/1.5.4:
     resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==}
     dev: true
 
+  /es-object-atoms/1.1.1:
+    resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      es-errors: 1.3.0
+
+  /es-set-tostringtag/2.1.0:
+    resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      es-errors: 1.3.0
+      get-intrinsic: 1.3.0
+      has-tostringtag: 1.0.2
+      hasown: 2.0.2
+
   /es6-error/4.1.1:
     resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==}
     dev: true
@@ -5068,12 +5096,13 @@ packages:
       cross-spawn: 7.0.3
       signal-exit: 4.1.0
 
-  /form-data/4.0.0:
-    resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
+  /form-data/4.0.2:
+    resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
     engines: {node: '>= 6'}
     dependencies:
       asynckit: 0.4.0
       combined-stream: 1.0.8
+      es-set-tostringtag: 2.1.0
       mime-types: 2.1.35
 
   /forwarded/0.2.0:
@@ -5143,7 +5172,6 @@ packages:
 
   /function-bind/1.1.2:
     resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
-    dev: true
 
   /gensync/1.0.0-beta.2:
     resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
@@ -5155,16 +5183,27 @@ packages:
     engines: {node: 6.* || 8.* || >= 10.*}
     dev: true
 
-  /get-intrinsic/1.2.4:
-    resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
+  /get-intrinsic/1.3.0:
+    resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
     engines: {node: '>= 0.4'}
     dependencies:
+      call-bind-apply-helpers: 1.0.2
+      es-define-property: 1.0.1
       es-errors: 1.3.0
+      es-object-atoms: 1.1.1
       function-bind: 1.1.2
-      has-proto: 1.0.3
-      has-symbols: 1.0.3
+      get-proto: 1.0.1
+      gopd: 1.2.0
+      has-symbols: 1.1.0
       hasown: 2.0.2
-    dev: true
+      math-intrinsics: 1.1.0
+
+  /get-proto/1.0.1:
+    resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      dunder-proto: 1.0.1
+      es-object-atoms: 1.1.1
 
   /get-stream/5.2.0:
     resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
@@ -5242,7 +5281,7 @@ packages:
     engines: {node: '>= 0.4'}
     dependencies:
       define-properties: 1.2.1
-      gopd: 1.0.1
+      gopd: 1.2.0
     dev: true
     optional: true
 
@@ -5258,11 +5297,9 @@ packages:
       slash: 3.0.0
     dev: true
 
-  /gopd/1.0.1:
-    resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
-    dependencies:
-      get-intrinsic: 1.2.4
-    dev: true
+  /gopd/1.2.0:
+    resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+    engines: {node: '>= 0.4'}
 
   /got/11.8.6:
     resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==}
@@ -5308,25 +5345,28 @@ packages:
   /has-property-descriptors/1.0.2:
     resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
     dependencies:
-      es-define-property: 1.0.0
-    dev: true
-
-  /has-proto/1.0.3:
-    resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==}
-    engines: {node: '>= 0.4'}
+      es-define-property: 1.0.1
     dev: true
 
   /has-symbols/1.0.3:
     resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
     engines: {node: '>= 0.4'}
-    dev: true
+
+  /has-symbols/1.1.0:
+    resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+    engines: {node: '>= 0.4'}
+
+  /has-tostringtag/1.0.2:
+    resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+    engines: {node: '>= 0.4'}
+    dependencies:
+      has-symbols: 1.0.3
 
   /hasown/2.0.2:
     resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
     engines: {node: '>= 0.4'}
     dependencies:
       function-bind: 1.1.2
-    dev: true
 
   /he/1.2.0:
     resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
@@ -6067,6 +6107,10 @@ packages:
     dev: true
     optional: true
 
+  /math-intrinsics/1.1.0:
+    resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+    engines: {node: '>= 0.4'}
+
   /mdn-data/2.0.30:
     resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
     dev: true
@@ -7193,8 +7237,8 @@ packages:
       define-data-property: 1.1.4
       es-errors: 1.3.0
       function-bind: 1.1.2
-      get-intrinsic: 1.2.4
-      gopd: 1.0.1
+      get-intrinsic: 1.3.0
+      gopd: 1.2.0
       has-property-descriptors: 1.0.2
     dev: true
 
@@ -7241,7 +7285,7 @@ packages:
     dependencies:
       call-bind: 1.0.7
       es-errors: 1.3.0
-      get-intrinsic: 1.2.4
+      get-intrinsic: 1.3.0
       object-inspect: 1.13.2
     dev: true
 

+ 48 - 0
src/main/index.ts

@@ -7,6 +7,8 @@ import { exec } from "child_process";
 import process from "process";
 import IniParser from "ini-parser";
 import logger from "./logger";
+import axios from "axios";
+import FormData from "form-data";
 
 const isDev = process.env.NODE_ENV === "development";
 let win: BrowserWindow | null = null;
@@ -103,6 +105,52 @@ function registEvent() {
       return null; // 返回 null 表示失败
     }
   });
+
+  ipcMain.handle(
+    "upload-csv",
+    async (event, { uploadUrl, csvString, filename }) => {
+      try {
+        const form = new FormData();
+        form.append("file", Buffer.from(csvString), {
+          filename: filename,
+          contentType: "text/csv;charset=utf-8;",
+        });
+
+        const response = await axios.post(uploadUrl, form, {
+          headers: {
+            ...form.getHeaders(), // Important for form-data to set Content-Type correctly
+          },
+          maxRedirects: 0,
+          validateStatus: function (status) {
+            return (status >= 200 && status < 400) || status === 303; // Accept 2xx and 3xx responses without throwing an error
+          },
+        });
+        // If it's a redirect, the actual data might be in response.headers.location
+        // Or the body might contain information about the redirect
+        // For now, we return the full response data and status
+        return {
+          status: response.status,
+          data: response.data,
+        };
+      } catch (error: any) {
+        logger.error(`Error uploading CSV to ${uploadUrl}: ${error.message}`);
+        // Distinguish between Axios errors and other errors
+        if (axios.isAxiosError(error)) {
+          // Handle Axios-specific error properties (e.g., error.response)
+          return {
+            error: true,
+            message: error.message,
+            status: error.response?.status,
+            data: error.response?.data,
+          };
+        }
+        return {
+          error: true,
+          message: error.message || "An unknown error occurred during upload",
+        };
+      }
+    }
+  );
 }
 
 app.on("ready", async () => {

+ 7 - 0
src/main/preload/index.ts

@@ -108,4 +108,11 @@ contextBridge.exposeInMainWorld("electronApi", {
   getAppIni: () => {
     return ipcRenderer.invoke("get-app-ini");
   },
+  uploadCsvData: (data: {
+    uploadUrl: string;
+    csvString: string;
+    filename: string;
+  }) => {
+    return ipcRenderer.invoke("upload-csv", data);
+  },
 });

+ 0 - 1
src/render/components.d.ts

@@ -16,7 +16,6 @@ declare module 'vue' {
     ADescriptions: typeof import('@qmth/ui')['Descriptions']
     ADescriptionsItem: typeof import('@qmth/ui')['DescriptionsItem']
     ADivider: typeof import('@qmth/ui')['Divider']
-    ADrawer: typeof import('@qmth/ui')['Drawer']
     AForm: typeof import('@qmth/ui')['Form']
     AFormItem: typeof import('@qmth/ui')['FormItem']
     AInput: typeof import('@qmth/ui')['Input']

+ 10 - 0
src/render/styles/pages.less

@@ -857,6 +857,16 @@
     }
   }
 }
+.edit-exam-number-dialog {
+  top: 50% !important;
+  left: 50% !important;
+  right: auto !important;
+  bottom: auto !important;
+  transform: translate(-50%, -50%);
+  overflow: hidden !important;
+  border-radius: 12px;
+  box-shadow: 0px 10px 10px 0px rgba(54, 61, 89, 0.2);
+}
 
 // recog-edit-dialog
 .recog-edit-dialog {

+ 3 - 1
src/render/views/ExamNumberCheck/EditExamNumberDialog.vue

@@ -4,10 +4,11 @@
     :width="380"
     style="bottom: 0"
     :footer="false"
-    :closable="false"
     :mask="false"
     :maskClosable="false"
     :keyboard="false"
+    centered
+    wrapClassName="edit-exam-number-dialog"
     title="输入准考证号"
   >
     <a-form ref="formRef" :label-col="{ style: { width: '80px' } }">
@@ -19,6 +20,7 @@
             allow-clear
           ></a-input>
         </div>
+        <a-button style="margin-right: 10px" @click="close">取消</a-button>
         <a-button type="primary" @click="confirm">保存</a-button>
       </a-form-item>
     </a-form>

+ 10 - 0
src/render/views/ExamNumberCheck/QuestionPanel.vue

@@ -245,6 +245,16 @@ watch(
   }
 );
 
+watch(
+  () => dataCheckStore.curTask,
+  () => {
+    editExamNumberDialogRef.value?.close();
+  },
+  {
+    immediate: true,
+  }
+);
+
 onMounted(() => {});
 </script>
 

+ 19 - 15
src/render/views/ExamNumberCheck/api.ts

@@ -26,7 +26,7 @@ export const allCheckList = async (
       checked: cacheList.includes(item.imageName),
     };
   });
-  console.log(data);
+  // console.log(data);
 
   return Promise.resolve(data);
 };
@@ -64,6 +64,7 @@ export const fetchCacheList = async () => {
     // console.log("缓存文件列表:", links);
     return links; // Return the extracted list
   } catch (err) {
+    return [];
     console.error("获取失败:", err);
   }
 };
@@ -156,6 +157,7 @@ export const fetchAndParseData = async (remoteUrl: string): Promise<any> => {
 
 /**
  * 构建 CSV 文件内容并上传到指定 URL。
+ * 注意:渲染进程中上传成功后,会触发一个重定向,想要不处理重定向只能在主进程中处理上传
  * @param uploadUrl 上传的目标 URL
  * @param data 要转换为 CSV 并上传的数据数组
  * @param filename 上传的文件名
@@ -170,23 +172,25 @@ export const uploadCsvData = async (
     // Convert data array to CSV string
     const csvString = Papa.unparse(data);
 
-    // Create a Blob from the CSV string
-    const blob = new Blob([csvString], { type: "text/csv;charset=utf-8;" });
+    // Send data to main process for upload
+    // Assuming 'window.electron.ipcRenderer.invoke' is available for IPC
+    // The channel name 'upload-csv' is an example; it should match the handler in the main process.
 
-    // Create FormData and append the Blob as a file
-    const formData = new FormData();
-    formData.append("file", blob, filename); // 'file' is a common field name for uploads, adjust if needed
-
-    // Upload the FormData using axios
-    const response = await axios.post(uploadUrl, formData, {
-      headers: {
-        "Content-Type": "multipart/form-data",
-      },
+    const result = await window.electronApi.uploadCsvData({
+      uploadUrl,
+      csvString,
+      filename,
     });
+    if (!result.error) return result.data;
 
-    return response.data; // Return the response from the server
+    throw new Error(result.message || "Failed to send CSV data for upload");
   } catch (error) {
-    console.error("Error building or uploading CSV data:", error);
-    throw new Error("Failed to build or upload CSV data");
+    console.error("Error preparing or sending CSV data for upload:", error);
+    // It's good practice to re-throw or handle the error appropriately
+    // For instance, if the error is already an Error object with a message, just re-throw it
+    if (error instanceof Error) {
+      throw error;
+    }
+    throw new Error("Failed to prepare or send CSV data for upload");
   }
 };

+ 6 - 1
src/render/views/ExamNumberCheck/index.vue

@@ -97,7 +97,12 @@ async function getAllStudents(data: AllCheckFilter) {
     examNumber: res.filter(
       (item) => !item.info || item.info.includes("准考证号")
     ),
-    question: res.filter((item) => item.info.includes("客观题")),
+    question: res.filter(
+      (item) =>
+        item.info &&
+        !item.info.includes("准考证号") &&
+        item.info.includes("客观题")
+    ),
   };
 
   allStudentList.value = res;

+ 7 - 0
src/render/views/ExamNumberCheck/useUpload.ts

@@ -1,5 +1,6 @@
 import { useDataCheckStore } from "@/store";
 import { saveCheck } from "./api";
+import { message } from "ant-design-vue";
 
 export default function useUpload() {
   const dataCheckStore = useDataCheckStore();
@@ -21,9 +22,15 @@ export default function useUpload() {
       ],
     ];
     const filename = `${dataCheckStore.curStudent.imageName}.csv`;
+    let result = true;
     await saveCheck(data, filename).catch((err) => {
+      result = false;
       console.log(err);
+      message.error(err.message || "保存失败,请重新尝试!");
     });
+
+    if (!result) return;
+    dataCheckStore.curStudent.checked = true;
   };
 
   return { save };