Forráskód Böngészése

feat: 导出dbf进度

zhangjie 9 hónapja
szülő
commit
618e0ea593

+ 33 - 0
src/render/hooks/useLoop.ts

@@ -0,0 +1,33 @@
+type ActionFunc = () => Promise<unknown>;
+
+export default function useLoop(action: ActionFunc, interval: number) {
+  let st = null;
+  let stoped = true;
+
+  async function run() {
+    if (stoped) return;
+    await action().catch(() => {});
+
+    if (stoped) return;
+    st = setTimeout(run, interval);
+  }
+
+  function start() {
+    stoped = false;
+    run();
+  }
+
+  function stop() {
+    stoped = true;
+    if (st) {
+      clearTimeout(st);
+      st = null;
+    }
+  }
+
+  return {
+    stoped,
+    stop,
+    start,
+  };
+}

+ 21 - 0
src/render/hooks/useTimout.ts

@@ -0,0 +1,21 @@
+export default function useTimeout() {
+  const times: Record<string, NodeJS.Timeout[]> = {};
+
+  function addSetTimeout(key: string, action: () => void, time: number) {
+    if (!times[key]) times[key] = [] as NodeJS.Timeout[];
+    times[key].push(setTimeout(action, time));
+  }
+
+  function clearSetTimeout(key: string) {
+    if (!times[key]) return;
+    times[key].forEach((item) => {
+      clearTimeout(item);
+    });
+    times[key] = [];
+  }
+
+  return {
+    addSetTimeout,
+    clearSetTimeout,
+  };
+}

+ 21 - 8
src/render/styles/reset.less

@@ -25,8 +25,8 @@ html {
   -moz-tab-size: 4; /* 3 */
   tab-size: 4; /* 3 */
   font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
-    'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
-    'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; /* 4 */
+    "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
+    "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 4 */
 }
 html,
 body,
@@ -107,7 +107,7 @@ kbd,
 samp,
 pre {
   font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
-    'Liberation Mono', 'Courier New', monospace; /* 1 */
+    "Liberation Mono", "Courier New", monospace; /* 1 */
   font-size: 1em; /* 2 */
 }
 
@@ -185,9 +185,9 @@ select {
 */
 
 button,
-[type='button'],
-[type='reset'],
-[type='submit'] {
+[type="button"],
+[type="reset"],
+[type="submit"] {
   -webkit-appearance: button; /* 1 */
   /* background-color: transparent; 2 */
   background-image: none; /* 2 */
@@ -231,7 +231,7 @@ Correct the cursor style of increment and decrement buttons in Safari.
 2. Correct the outline style in Safari.
 */
 
-[type='search'] {
+[type="search"] {
   -webkit-appearance: textfield; /* 1 */
   outline-offset: -2px; /* 2 */
 }
@@ -323,7 +323,7 @@ Set the default cursor for buttons.
 */
 
 button,
-[role='button'] {
+[role="button"] {
   cursor: pointer;
 }
 
@@ -392,3 +392,16 @@ Ensure the default browser behavior of the `hidden` attribute.
   background-color: rgba(0, 0, 0, 0);
   border-radius: 0;
 }
+
+// ant-design
+.operation-cell {
+  .ant-btn-link {
+    padding: 0;
+    height: auto;
+    border: none;
+
+    + .ant-btn-link {
+      margin-left: 12px;
+    }
+  }
+}

+ 5 - 0
src/render/views/ImageCheck/ImageFailed.vue

@@ -73,6 +73,11 @@ const columns: TableProps["columns"] = [
     title: "有图片异常",
     dataIndex: "failed",
     width: "100px",
+    customCell: () => {
+      return {
+        class: "operation-cell",
+      };
+    },
   },
 ];
 

+ 5 - 0
src/render/views/ImageCheck/index.vue

@@ -54,6 +54,11 @@ const columns: TableProps["columns"] = [
     title: "操作",
     dataIndex: "operation",
     width: "100px",
+    customCell: () => {
+      return {
+        class: "operation-cell",
+      };
+    },
   },
 ];
 const loading = ref(false);

+ 5 - 0
src/render/views/ResultExport/BreachImport.vue

@@ -71,6 +71,11 @@ const columns: TableProps["columns"] = [
     title: "操作",
     dataIndex: "operation",
     width: "100px",
+    customCell: () => {
+      return {
+        class: "operation-cell",
+      };
+    },
   },
 ];
 

+ 35 - 6
src/render/views/ResultExport/DbfExport.vue

@@ -17,10 +17,10 @@
   >
     <template #bodyCell="{ column, index }">
       <template v-if="column.dataIndex === 'operation'">
-        <qm-button type="text" @click="onExportAnswer(index)"
+        <qm-button type="link" @click="onExportAnswer(index)"
           >导出扫描答案DBF</qm-button
         >
-        <qm-button type="text" @click="onExportPackage(index)"
+        <qm-button type="link" @click="onExportPackage(index)"
           >导出打包DBF</qm-button
         >
       </template>
@@ -33,6 +33,11 @@
     :row-data="siteCodeData"
     @modified="siteCodeModified"
   />
+  <!-- ExportTaskProgressDialog -->
+  <ExportTaskProgressDialog
+    ref="exportTaskProgressDialogRef"
+    :task="curExportTask"
+  />
 </template>
 
 <script setup lang="ts">
@@ -42,10 +47,15 @@ import type { TableProps } from "ant-design-vue";
 import { SubjectItem } from "@/ap/types/base";
 
 import { subjectList } from "@/ap/base";
-import { markSiteCodeInfo } from "@/ap/resultExport";
+import {
+  markSiteCodeInfo,
+  dbfAnswerExport,
+  dbfPackageExport,
+} from "@/ap/resultExport";
 import { markSiteSetParams } from "@/ap/types/resultExport";
 
 import ModifySiteCode from "./ModifySiteCode.vue";
+import ExportTaskProgressDialog from "./ExportTaskProgressDialog.vue";
 
 defineOptions({
   name: "DbfExport",
@@ -53,6 +63,7 @@ defineOptions({
 
 const loading = ref(false);
 const dataList = ref<SubjectItem[]>([]);
+const curExportTask = ref({ id: "", name: "" });
 
 const columns: TableProps["columns"] = [
   {
@@ -66,7 +77,12 @@ const columns: TableProps["columns"] = [
   {
     title: "操作",
     dataIndex: "operation",
-    width: "180px",
+    width: "240px",
+    customCell: () => {
+      return {
+        class: "operation-cell",
+      };
+    },
   },
 ];
 
@@ -90,14 +106,27 @@ async function getData() {
 }
 
 async function onExportAnswer(index: number) {
-  console.log(index);
+  const record = dataList.value[index];
+  const res = await dbfAnswerExport({
+    examId: "",
+    subjectCode: record.subjectCode,
+  });
+  curExportTask.value = { id: res.taskId, name: "扫描答案DBF" };
+  exportTaskProgressDialogRef.value?.open();
 }
 
 async function onExportPackage(index: number) {
-  console.log(index);
+  const record = dataList.value[index];
+  const res = await dbfPackageExport({
+    examId: "",
+    subjectCode: record.subjectCode,
+  });
+  curExportTask.value = { id: res.taskId, name: "打包DBF" };
+  exportTaskProgressDialogRef.value?.open();
 }
 
 onMounted(() => {
+  dataList.value = [{ subjectCode: "8956145235", subjectName: "语法基础" }];
   // getScanSiteCode()
   // getData()
 });

+ 105 - 0
src/render/views/ResultExport/ExportTaskProgressDialog.vue

@@ -0,0 +1,105 @@
+<template>
+  <a-modal
+    v-model:open="visible"
+    :width="320"
+    :closable="false"
+    :footer="null"
+    :keyboard="false"
+    :mask-closable="false"
+  >
+    <template #title> 导出进度 </template>
+
+    <p>{{ tips }}</p>
+    <a-progress :percent="curProgress" />
+  </a-modal>
+</template>
+
+<script setup lang="ts">
+import { ref, watch, computed } from "vue";
+import { message } from "ant-design-vue";
+import { dbfExportProgress } from "@/ap/resultExport";
+
+import useLoading from "@/hooks/useLoading";
+import useModal from "@/hooks/useModal";
+import useLoop from "@/hooks/useLoop";
+import { downloadByUrl } from "@/utils/download";
+
+defineOptions({
+  name: "ExportTaskProgressDialog",
+});
+
+/* modal */
+const { visible, open, close } = useModal();
+defineExpose({ open, close });
+
+const props = defineProps<{
+  task: {
+    id: string;
+    name: string;
+  };
+}>();
+
+const curProgress = ref(0);
+const taskFileUrl = ref("");
+
+const tips = computed(() => {
+  return curProgress.value >= 100
+    ? `${props.task.name}导出中……`
+    : `文件生成完毕,正在下载……`;
+});
+
+const { start, stop } = useLoop(getProgress, 1000);
+async function getProgress() {
+  const result = await dbfExportProgress(props.task.id).catch(() => undefined);
+  if (!result) return;
+  curProgress.value = result.progress;
+  taskFileUrl.value = result.url;
+
+  // 文件生成成功,开始下载
+  if (result.progress >= 100) {
+    stop();
+    downloadTaskFile();
+  }
+}
+
+function downloadTaskFile() {
+  if (!taskFileUrl.value) {
+    message.error("下载链接丢失!");
+    return;
+  }
+
+  downloadByUrl(taskFileUrl.value);
+  message.success("文件开始下载!");
+
+  setTimeout(() => {
+    close();
+  }, 200);
+}
+
+watch(
+  () => visible.value,
+  (val) => {
+    if (val) {
+      modalOpenHandle();
+    } else {
+      stop();
+    }
+  },
+  {
+    immediate: true,
+  }
+);
+
+/* init modal */
+function modalOpenHandle() {
+  curProgress.value = 0;
+  taskFileUrl.value = "";
+
+  if (!props.task.id) {
+    message.error("任务丢失!");
+    return;
+  }
+
+  start();
+}
+</script>

+ 5 - 0
src/render/views/ResultExport/MarkSite.vue

@@ -73,6 +73,11 @@ const columns: TableProps["columns"] = [
     title: "操作",
     dataIndex: "operation",
     width: "140px",
+    customCell: () => {
+      return {
+        class: "operation-cell",
+      };
+    },
   },
 ];
 const curRow = ref({} as MarkSiteListItem);

+ 1 - 1
src/render/views/ResultExport/ModifySiteCode.vue

@@ -25,7 +25,7 @@
 </template>
 
 <script setup lang="ts">
-import { computed, reactive, ref, watch } from "vue";
+import { reactive, ref, watch } from "vue";
 import type { UnwrapRef } from "vue";
 import { message } from "ant-design-vue";
 import { markSiteCodeSet } from "@/ap/resultExport";

+ 5 - 0
src/render/views/ResultExport/StudentStatus.vue

@@ -78,6 +78,11 @@ const columns: TableProps["columns"] = [
     title: "操作",
     dataIndex: "operation",
     width: "100px",
+    customCell: () => {
+      return {
+        class: "operation-cell",
+      };
+    },
   },
 ];