刘洋 8 mesiacov pred
rodič
commit
9f5fbe38cc

+ 2 - 2
builder.json

@@ -1,5 +1,5 @@
 {
-    "productName": "通用扫描管理端",
+    "productName": "scan-central",
     "appId": "com.electron.qmth.scan-admin",
     "directories": {
         "output": "out"
@@ -19,7 +19,7 @@
         "installerHeaderIcon": "./icons/icon.ico",
         "createDesktopShortcut": true,
         "createStartMenuShortcut": true,
-        "shortcutName": "通用扫描管理端"
+        "shortcutName": "scan-central"
     },
     "mac": {
         "icon": "./icons/icon.icns",

+ 5 - 4
package.json

@@ -1,7 +1,7 @@
 {
-  "name": "scan-admin",
+  "name": "scan-central",
   "version": "1.0.0",
-  "description": "通用扫描管理端",
+  "description": "scan-central",
   "author": "LiuYang",
   "scripts": {
     "start": "npm run dev",
@@ -17,13 +17,14 @@
   "main": "dist/main.js",
   "dependencies": {
     "@ant-design/icons-vue": "^7.0.1",
-    "@qmth/ui": "^1.0.16",
+    "@qmth/ui": "^1.0.18",
     "@vueuse/core": "^10.11.0",
     "axios": "^1.5.0",
     "core-js": "^3.32.2",
     "crypto-js": "^4.2.0",
     "echarts": "^5.5.1",
     "element-resize-detector": "^1.2.4",
+    "ini-parser": "^0.0.2",
     "less": "^4.2.0",
     "lodash-es": "^4.17.21",
     "mockjs": "^1.1.0",
@@ -76,4 +77,4 @@
     "webpack-cli": "^5.1.4",
     "webpack-dev-server": "^4.15.1"
   }
-}
+}

+ 24 - 0
page_size.ini

@@ -0,0 +1,24 @@
+[A3]
+width=420
+height=297
+dpi=150,200
+[A4]
+width=210
+height=297
+dpi=150,200
+[8K]
+width=368
+height=260
+dpi=150,200
+[16K]
+width=182
+height=256
+dpi=150,200
+[16K2]
+width=256
+height=182
+dpi=150,200
+[8K2]
+width=380
+height=270
+dpi=150,200

+ 11 - 0
src/main/index.ts

@@ -5,6 +5,7 @@ import installExtension, { VUEJS3_DEVTOOLS } from "electron-devtools-installer";
 
 import { exec } from "child_process";
 import process from "process";
+import IniParser from "ini-parser";
 
 const isDev = process.env.NODE_ENV === "development";
 let win: BrowserWindow | null = null;
@@ -154,6 +155,16 @@ ipcMain.on("hide-app", () => {
   win?.hide();
 });
 
+ipcMain.on("get-page-size-ini", (event: any) => {
+  const appPath = isDev ? process.cwd() : path.dirname(app.getPath("exe"));
+  const iniFilePath = path.resolve(appPath, "page_size.ini");
+  fs.readFile(iniFilePath, "utf8", (err: any, data: any) => {
+    if (err) dialog.showErrorBox("tip", `page_size.ini文件不存在!`);
+    const content = IniParser.parse(data);
+    event.sender.send("got-page-size-ini", content);
+  });
+});
+
 app.on("ready", async () => {
   if (isDev) {
     try {

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

@@ -99,4 +99,10 @@ contextBridge.exposeInMainWorld("electronApi", {
       watchStep(rs, rj);
     });
   },
+  getPageSizeIni: (cb: Function) => {
+    ipcRenderer.send("get-page-size-ini");
+    ipcRenderer.on("got-page-size-ini", (event, content) => {
+      cb(content);
+    });
+  },
 });

+ 1 - 0
src/render/ap/baseDataConfig.ts

@@ -75,6 +75,7 @@ export const importCard = async (params: {
   subjectCode: string;
   remark?: string;
   file: any;
+  dpi: number;
 }) => {
   const md5 = await getFileMD5(params.file);
   const formData = obj2formData(params);

+ 1 - 0
src/render/ap/scanManage.ts

@@ -78,6 +78,7 @@ export const exportWorkStatistics = (params: {
   examId: number | undefined;
   startTime: number | undefined;
   endTime: number | undefined;
+  sort: "DESC" | "ASC";
 }) =>
   request({
     url: "/api/admin/scanner/workload/export",

+ 1 - 1
src/render/index.html

@@ -4,7 +4,7 @@
     <meta charset="UTF-8" />
     <link rel="icon" href="/favicon.ico" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>通用扫描管理端</title>
+    <title>scan-central</title>
   </head>
   <body>
     <div id="app"></div>

+ 139 - 26
src/render/views/BaseDataConfig/ImportCardDialog.vue

@@ -25,7 +25,7 @@
   </my-modal>
 </template>
 <script name="ImportCardDialog" lang="ts" setup>
-import { ref, reactive } from "vue";
+import { ref, reactive, computed } from "vue";
 import { importCard } from "@/ap/baseDataConfig";
 import { useUserStore } from "@/store";
 import SelectSubject from "@/components/SelectSubject/index.vue";
@@ -40,39 +40,152 @@ const params = reactive({
   file: null,
   paperCount: void 0,
   singlePage: false,
+  // dpi: 150,
 });
-const fields = ref([
-  { prop: "subjectCode", cell: "subjectCode", label: "科目题卡", colSpan: 24 },
-  { prop: "remark", type: "textarea", label: "备注", colSpan: 24 },
-  { prop: "file", cell: "file", label: "选择卡格式", colSpan: 24 },
-  {
-    prop: "paperCount",
-    type: "number",
-    label: "张数",
-    colSpan: 24,
-    attrs: { min: 1 },
-  },
-  {
-    prop: "singlePage",
-    type: "radio",
-    label: "单页题卡",
-    colSpan: 24,
-    attrs: {
-      options: [
-        { value: false, label: "否" },
-        { value: true, label: "是" },
-      ],
+const cardJson = ref(null);
+const fields = computed(() => {
+  return [
+    {
+      prop: "subjectCode",
+      cell: "subjectCode",
+      label: "科目题卡",
+      colSpan: 24,
     },
-  },
-]);
+    { prop: "remark", type: "textarea", label: "备注", colSpan: 24 },
+    { prop: "file", cell: "file", label: "选择卡格式", colSpan: 24 },
+    {
+      prop: "paperCount",
+      type: "number",
+      label: "张数",
+      colSpan: 24,
+      attrs: { min: 1 },
+    },
+    {
+      prop: "singlePage",
+      type: "radio",
+      label: "单页题卡",
+      colSpan: 24,
+      attrs: {
+        options: [
+          { value: false, label: "否" },
+          { value: true, label: "是" },
+        ],
+      },
+    },
+    // params.file && isJson.value && cardJson.value && !cardJson.value.dpi
+    //   ? {
+    //       prop: "dpi",
+    //       type: "radio",
+    //       label: "DPI",
+    //       colSpan: 24,
+    //       attrs: {
+    //         options: [
+    //           { value: 150, label: "150" },
+    //           { value: 200, label: "200" },
+    //         ],
+    //         disabled: cardJson.value?.dpi,
+    //       },
+    //     }
+    //   : null,
+  ];
+});
+const isJson = ref(true);
+const cardSizePass = ref(true);
+
+const isJsonString = (str) => {
+  try {
+    const toObj = JSON.parse(str);
+    if (toObj && typeof toObj === "object") {
+      return true;
+    }
+  } catch {}
+  return false;
+};
 
 const rules = {
   subjectCode: [{ required: true, message: "请选择科目" }],
-  file: [{ required: true, message: "请上传文件" }],
-  paperCount: [{ required: true, message: "请输入张数" }],
+  file: [
+    { required: true, message: "请上传文件" },
+    {
+      validator: async () => {
+        if (!isJson) {
+          return Promise.reject("卡格式json文件内容异常,请检查");
+        } else {
+          if (cardJson.value && !cardSizePass.value) {
+            return Promise.reject("卡格式图片dpi异常!");
+          } else {
+            return Promise.resolve();
+          }
+        }
+      },
+    },
+  ],
+  paperCount: [
+    { required: true, message: "请输入张数" },
+    {
+      validator: async (rule: any, value: any) => {
+        if (cardJson.value) {
+          if (
+            (params.singlePage && cardJson.value.pages.length != value) ||
+            (!params.singlePage && cardJson.value.pages.length / 2 != value)
+          ) {
+            return Promise.reject("张数未校验通过,请检查!");
+          } else {
+            return Promise.resolve();
+          }
+        } else {
+          return Promise.resolve();
+        }
+      },
+    },
+  ],
+};
+const compareSize = (content: any, img: any) => {
+  console.log(content, img.width, img.height);
+};
+const validateCardSize = async (json) => {
+  if (!json.pages.length) {
+    cardSizePass.value = false;
+    return;
+  }
+  window.electronApi?.getPageSizeIni((content: any) => {
+    console.log(content);
+    const img = new Image();
+    img.onload = () => {
+      compareSize(content, img);
+    };
+    img.onerror = function () {
+      window.$message.error("卡格式json文件里的图片base64无效!");
+    };
+    img.src = "data:image/jpg;base64," + json.pages[0].exchange["page_image"];
+  });
+};
+const getCardJsonContent = async (file: any) => {
+  const reader = new FileReader();
+  reader.onload = function (e) {
+    try {
+      const json = JSON.parse(e.target.result);
+      console.log("json", json);
+      cardJson.value = json;
+      if (json.dpi) {
+        params.dpi = Number(json.dpi);
+      }
+      validateCardSize(json);
+    } catch (error) {
+      console.error("Error parsing JSON:", error);
+      isJson.value = false;
+    }
+  };
+
+  reader.onerror = function (error) {
+    console.error("File could not be read:", error);
+  };
+
+  reader.readAsText(file);
 };
 const getFile = (file: any) => {
   params.file = file;
+  getCardJsonContent(file);
 };
 const submitHandle = () => {
   form.value.formRef.validate().then(() => {

+ 4 - 0
src/render/views/Login/AdminLogin.vue

@@ -29,6 +29,10 @@ const userStore = useUserStore();
 const loginForm = ref();
 const emit = defineEmits(["toIndex"]);
 const params = reactive({ loginName: "", password: "" });
+if (process.env?.VITE_USER_NAME) {
+  params.loginName = process.env.VITE_USER_NAME;
+  params.password = process.env.VITE_PASSWORD;
+}
 const router = useRouter();
 const rules = {
   loginName: [{ required: true, message: "请输入用户名", trigger: "change" }],

+ 18 - 2
src/render/views/ScanManage/WorkStatistics.vue

@@ -7,6 +7,15 @@
           value-format="timestamp"
         ></qm-date-range-picker>
       </template>
+      <template #sort>
+        <a-select
+          v-model:value="sort"
+          :options="[
+            { label: '数值降序', value: 'DESC' },
+            { label: '数值升序', value: 'ASC' },
+          ]"
+        ></a-select>
+      </template>
     </qm-low-form>
     <div class="chart-wrap">
       <vue-echarts :option="chartOptions" autoresize></vue-echarts>
@@ -22,8 +31,9 @@ import { graphic } from "echarts";
 
 const userStore = useUserStore();
 const params = reactive({
-  timeArr: [],
+  timeArr: [Date.now() - 1000 * 60 * 60, Date.now()],
 });
+const sort = ref("DESC");
 const transParams = computed(() => {
   return {
     startTime: params.timeArr[0],
@@ -35,6 +45,7 @@ const data = ref([]);
 const search = () => {
   getWorkStatistics(transParams.value).then((res: any) => {
     data.value = res || [];
+    data.value.sort((a,b)=>(sort.value==='DESC'?b-a:a-b));
   });
 };
 onMounted(() => {
@@ -51,6 +62,11 @@ const fields = computed(() => {
         format: "YYYY-MM-DD HH:mm:ss",
       },
     },
+    {
+      cell: "sort",
+      label: "排序方式",
+      colSpan: 3,
+    },
     {
       type: "buttons",
       colSpan: 5,
@@ -160,7 +176,7 @@ const exportFile = async () => {
   if (exportLoading.value) return;
   exportLoading.value = true;
 
-  exportWorkStatistics(transParams.value)
+  exportWorkStatistics({ ...transParams.value, sort: sort.value })
     .then(() => {
       window.$message.success("导出成功!");
     })

+ 1 - 1
static/load/index.html

@@ -24,7 +24,7 @@
   <body>
     <div class="loader">
       <div class="cap"></div>
-      <div class="line">通用扫描v2.0.0</div>
+      <div class="line">集中扫描v1.0.0</div>
     </div>
   </body>
 </html>

+ 4 - 0
types/ini-parser.d.ts

@@ -0,0 +1,4 @@
+declare module "ini-parser" {
+  const IniParser: any;
+  export default IniParser;
+}