Răsfoiți Sursa

feat: 图片检查

zhangjie 9 luni în urmă
părinte
comite
22b23b21f6

+ 32 - 4
src/render/ap/audit.ts

@@ -1,6 +1,6 @@
 import { request } from "@/utils/request";
 import { ExamParams, RequestActionResult } from "./types/common";
-import { ExamOverviewResult, IntimeAuditBatchResult } from "./types/audit";
+import { ExamOverviewResult, AuditBatchResult } from "./types/audit";
 
 export const examOverview = (data: ExamParams): Promise<ExamOverviewResult> =>
   request({
@@ -10,9 +10,7 @@ export const examOverview = (data: ExamParams): Promise<ExamOverviewResult> =>
   });
 
 // 实时审核任务
-export const intimeAuditBatch = (
-  data: ExamParams
-): Promise<IntimeAuditBatchResult> =>
+export const intimeAuditBatch = (data: ExamParams): Promise<AuditBatchResult> =>
   request({
     url: "/api/auditor/batch/verify/get",
     method: "post",
@@ -39,3 +37,33 @@ export const intimeAuditBatchRelease = (
     method: "post",
     data,
   });
+
+// 图片抽查
+// 获取批次图片抽查任务
+export const imageAuditBatch = (data: ExamParams): Promise<AuditBatchResult> =>
+  request({
+    url: "/api/auditor/batch/inspect/get",
+    method: "post",
+    data,
+  });
+
+// 批次图片抽查任务提交
+export const imageAuditSave = (data: {
+  examId: number;
+  batchId: number;
+}): Promise<RequestActionResult> =>
+  request({
+    url: "/api/auditor/batch/inspect/submit",
+    method: "post",
+    data,
+  });
+
+// 批次图片抽查任务释放
+export const imageAuditRelease = (
+  data: ExamParams
+): Promise<RequestActionResult> =>
+  request({
+    url: "/api/auditor/batch/inspect/release",
+    method: "post",
+    data,
+  });

+ 8 - 5
src/render/ap/types/audit.ts

@@ -17,7 +17,7 @@ export interface ExamOverviewResult {
   };
 }
 
-export interface IntimeAuditBatchStudentPaper {
+export interface AuditBatchStudentPaper {
   number: number;
   // 是否本张为人工绑定
   assigned: boolean;
@@ -25,21 +25,24 @@ export interface IntimeAuditBatchStudentPaper {
   pages: string[];
 }
 
-export interface IntimeAuditBatchStudent {
+export interface AuditBatchStudent {
   examNumber: string;
   name: string;
   studentCode: string;
   subjectCode: string;
   subjectName: string;
   seatNumber: string;
-  papers: IntimeAuditBatchStudentPaper[];
+  papers: AuditBatchStudentPaper[];
 }
-export interface IntimeAuditBatchResult {
+export interface AuditBatchResult {
   // 批次ID
   batchId: number;
   device: string;
+  deviceName: string;
   createTime: number;
   // 实时审核批次此字段有值
   packageCode: string;
-  students: IntimeAuditBatchStudent[];
+  students: AuditBatchStudent[];
 }
+
+export type AuditBatchData = Omit<AuditBatchResult, "students">;

+ 23 - 12
src/render/hooks/useLoop.ts

@@ -1,28 +1,39 @@
+import { ref } from "vue";
+
 type ActionFunc = () => Promise<unknown>;
 
 export default function useLoop(action: ActionFunc, interval: number) {
-  let st = null;
-  let stoped = true;
+  let sts: NodeJS.Timeout[] = [];
+  const stoped = ref(true);
 
   async function run() {
-    if (stoped) return;
-    await action().catch(() => {});
+    clear();
+    if (stoped.value) return;
+    try {
+      await action();
+    } catch (error) {
+      console.log(error);
+    }
 
-    if (stoped) return;
-    st = setTimeout(run, interval);
+    if (stoped.value) return;
+    sts.push(setTimeout(run, interval));
   }
 
   function start() {
-    stoped = false;
+    stoped.value = false;
     run();
   }
 
-  function stop() {
-    stoped = true;
-    if (st) {
+  function clear() {
+    sts.forEach((st) => {
       clearTimeout(st);
-      st = null;
-    }
+    });
+    sts = [];
+  }
+
+  function stop() {
+    stoped.value = true;
+    clear();
   }
 
   return {

+ 1 - 1
src/render/router/routes.ts

@@ -130,7 +130,7 @@ const routes: RouteRecordRaw[] = [
       },
       // 审核员图片检查
       {
-        path: "review-audit",
+        path: "image-check-audit",
         name: "ImageCheckAudit",
         component: () => import("@/views/Audit/ImageCheck/index.vue"),
         meta: {

+ 1 - 1
src/render/store/modules/user/index.ts

@@ -14,7 +14,7 @@ export const useUserStore = defineStore<
     },
     {
       storage: localStorage,
-      paths: ["curExam"],
+      paths: ["curExam", "imageCheckLoopTime"],
     },
   ],
   state: () => ({

+ 16 - 0
src/render/styles/antui-reset.less

@@ -51,3 +51,19 @@
     opacity: 0.8;
   }
 }
+.ant-btn-error-light {
+  background: #ffece8;
+  color: @error-color;
+
+  &:hover {
+    opacity: 0.8;
+  }
+}
+.ant-btn-primary-light {
+  background: #e8f3ff;
+  color: @brand-color;
+
+  &:hover {
+    opacity: 0.8;
+  }
+}

+ 71 - 27
src/render/styles/pages.less

@@ -654,9 +654,8 @@
     }
   }
 }
-
-// intime
-.intime {
+// audit
+.audit {
   height: 100%;
   position: relative;
 
@@ -677,28 +676,73 @@
   &-head {
     position: absolute;
     width: 100%;
-    height: 55px;
+    height: 54px;
     top: 0;
     left: 0;
     z-index: 9;
     border-bottom: 1px solid @border-color1;
-    padding: 15px 0;
-    line-height: 24px;
+    padding: 11px 8px 10px;
+    line-height: 32px;
     text-align: center;
     color: @text-color2;
+    overflow: hidden;
+
+    .ant-col {
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
 
     .ant-col:not(:last-child) {
-      border-right: 1px solid @border-color1;
+      &::after {
+        content: "";
+        display: block;
+        position: absolute;
+        height: 22px;
+        border-right: 1px solid @border-color1;
+        right: 0;
+        top: 50%;
+        margin-top: -11px;
+      }
     }
     .head-label {
       color: @text-color3;
     }
   }
 
+  &-body {
+    position: absolute;
+    top: 54px;
+    left: 0;
+    bottom: 0;
+    right: 0;
+    background-color: @background-color;
+    padding: 10px;
+    overflow-y: auto;
+    overflow-x: hidden;
+  }
+
+  &-topinfo {
+    position: fixed;
+    top: 11px;
+    left: 50%;
+    transform: translateX(-50%);
+    z-index: 9;
+
+    height: 32px;
+    background: @background-color;
+    color: @text-color2;
+    border-radius: 6px;
+    padding: 5px 12px;
+    line-height: 22px;
+  }
+}
+// intime
+.intime {
   &-side {
     position: absolute;
     width: 357px;
-    top: 55px;
+    top: 54px;
     bottom: 0;
     left: 0;
     z-index: 9;
@@ -721,30 +765,12 @@
 
       .ant-btn {
         width: 146px;
-
-        &.ant-btn-dangerous {
-          background: #ffece8 !important;
-          color: @error-color!important;
-
-          &:hover {
-            background: @error-color !important;
-            color: #fff !important;
-          }
-        }
       }
     }
   }
 
-  &-body {
-    position: absolute;
-    top: 55px;
+  .audit-body {
     left: 357px;
-    bottom: 0;
-    right: 0;
-    background-color: @background-color;
-    padding: 10px;
-    overflow-y: auto;
-    overflow-x: hidden;
 
     .paper-img {
       display: block;
@@ -754,3 +780,21 @@
     }
   }
 }
+// audit-image-check
+.audit-image-check {
+  .remain-time {
+    margin-right: 16px;
+    color: @error-color;
+    min-width: 30px;
+    display: inline-block;
+  }
+  .audit-body {
+    padding: 0;
+    img {
+      display: block;
+      height: 100%;
+      width: auto;
+      margin: 0 auto;
+    }
+  }
+}

+ 273 - 1
src/render/views/Audit/ImageCheck/index.vue

@@ -1,9 +1,281 @@
 <template>
-  <div>ImageCheckAudit</div>
+  <div v-if="hasTask" class="audit audit-image-check">
+    <div class="audit-head">
+      <a-row :gutter="8">
+        <a-col :span="3">
+          <span class="head-label">姓名:</span>
+          <span class="head-cont">{{ curImage.name }}</span>
+        </a-col>
+        <a-col :span="4">
+          <span class="head-label">准考证号:</span>
+          <span class="head-cont">{{ curImage.examNumber }}</span>
+        </a-col>
+        <a-col :span="4">
+          <span class="head-label">科目:</span>
+          <span class="head-cont">{{ batchInfo.device }}</span>
+        </a-col>
+        <a-col :span="3">
+          <span class="head-label">张数:</span>
+          <span class="head-cont">{{ batchInfo.batchId }}</span>
+        </a-col>
+        <a-col :span="5">
+          <span class="head-label">倒计时:</span>
+          <span class="head-cont remain-time">{{ remainTime }}秒</span>
+          <a-button
+            v-if="loopImageStoped"
+            type="primary-light"
+            @click="startImageLoop"
+          >
+            <template #icon><PlayCircleOutlined /></template>
+            开始
+          </a-button>
+          <a-button v-else type="error-light" @click="stopImageLoop">
+            <template #icon><PauseCircleOutlined /></template>
+            暂停
+          </a-button>
+        </a-col>
+        <a-col :span="5">
+          <a-space>
+            <a-button :disabled="!prevEnable" @click="getPrevImage">
+              <template #icon><ArrowLeftOutlined /></template>
+              上一个
+            </a-button>
+            <span>{{ curImageIndex + 1 }} / {{ imageList.length }}</span>
+            <a-button @click="getNextImage">
+              <template #icon><ArrowRightOutlined /></template>
+              下一个
+            </a-button>
+          </a-space>
+        </a-col>
+      </a-row>
+    </div>
+    <div class="audit-body">
+      <!-- <img src="" alt=""> -->
+      <img src="../../RecognizeCheck/data/202302040117-1.jpg" />
+    </div>
+    <div class="audit-topinfo">
+      <a-space :size="6">
+        <template #split>
+          <a-divider type="vertical" style="height: 16px" />
+        </template>
+        <span>批次ID:{{ batchInfo.batchId }}</span>
+        <span>扫描员:{{ batchInfo.deviceName }}</span>
+        <span>扫描时间:{{ dateFormat(batchInfo.createTime) }}</span>
+      </a-space>
+    </div>
+  </div>
+  <div v-else class="audit is-wait">
+    <div>
+      <img src="@/assets/imgs/bg-wait.png" alt="等待" />
+      <p>等待审核结果…</p>
+    </div>
+  </div>
 </template>
 
 <script setup lang="ts">
+import { computed, onBeforeUnmount, onMounted, ref } from "vue";
+import {
+  ArrowLeftOutlined,
+  ArrowRightOutlined,
+  PlayCircleOutlined,
+  PauseCircleOutlined,
+} from "@ant-design/icons-vue";
+import { message } from "ant-design-vue";
+
+import { AuditBatchResult, AuditBatchStudent } from "@/ap/types/audit";
+import { imageAuditBatch, imageAuditSave, imageAuditRelease } from "@/ap/audit";
+import { useUserStore } from "@/store";
+import { dateFormat } from "@/utils/tool";
+import useLoop from "@/hooks/useLoop";
+
 defineOptions({
   name: "ImageCheckAudit",
 });
+
+interface ImageItem {
+  url: string;
+  pageName: string;
+  name: string;
+  examNumber: string;
+  subjectName: string;
+}
+
+const userStore = useUserStore();
+const imageList = ref<ImageItem[]>([]);
+const curImage = ref<ImageItem | null>(null);
+const curImageIndex = ref(0);
+const batchInfo = ref({} as AuditBatchResult);
+const hasTask = ref(false);
+
+const imageCheckLoopTime = computed(() => {
+  return userStore.imageCheckLoopTime || 0;
+});
+
+// 计时
+const loopImageStoped = ref(false);
+const remainTime = ref(0);
+const { start: startLoopRemainTime, stop: stopLoopRemainTime } = useLoop(
+  updateRemainTime,
+  1000
+);
+
+function updateRemainTime() {
+  remainTime.value--;
+
+  if (remainTime.value <= 0) {
+    stopLoopRemainTime();
+    // 跳出loop,再开启新的loop
+    setTimeout(() => {
+      getNextImage();
+    });
+  }
+}
+
+function stopImageLoop() {
+  loopImageStoped.value = true;
+  stopLoopRemainTime();
+}
+function startImageLoop() {
+  loopImageStoped.value = false;
+  startLoopRemainTime();
+}
+
+const prevEnable = computed(() => {
+  return curImageIndex.value > 0;
+});
+
+// 获取批次数据
+const { start: startLoopGetData, stop: stopLoopGetData } = useLoop(
+  getData,
+  2000
+);
+async function getData() {
+  const res = await imageAuditBatch({ examId: userStore.curExam.id });
+  if (!res) {
+    hasTask.value = false;
+    return;
+  }
+  stopLoopGetData();
+  hasTask.value = true;
+  batchInfo.value = res;
+
+  initImageData();
+  setCurImage(0);
+}
+
+function initImageData() {
+  if (!hasTask.value) return;
+
+  const images: ImageItem[] = [];
+  batchInfo.value.students.forEach((student) => {
+    student.papers.forEach((paper, paperIndex) => {
+      paper.pages.forEach((page, pageIndex) => {
+        const pageName = `第${paperIndex + 1}张${
+          pageIndex === 0 ? "正" : "反"
+        }面`;
+        images.push({
+          url: page,
+          pageName,
+          name: student.name,
+          examNumber: student.examNumber,
+          subjectName: student.subjectName,
+        });
+      });
+    });
+  });
+
+  imageList.value = images;
+}
+
+async function submitBatch() {
+  await imageAuditSave({
+    examId: userStore.curExam.id,
+    batchId: batchInfo.value.batchId,
+  });
+}
+
+async function releaseBatch() {
+  await imageAuditRelease({ examId: userStore.curExam.id });
+}
+
+function setCurImage(index: number) {
+  curImageIndex.value = index;
+  curImage.value = imageList.value[curImageIndex.value];
+  stopLoopRemainTime();
+
+  remainTime.value = imageCheckLoopTime.value;
+  if (!loopImageStoped.value) {
+    // 开启loop时会调用一次action,所这里提前加1
+    remainTime.value++;
+    startLoopRemainTime();
+  }
+}
+
+async function getNextImage() {
+  if (curImageIndex.value >= imageList.value.length - 1) {
+    await submitBatch();
+    // 获取下一批次
+    await startLoopGetData();
+    return;
+  }
+
+  setCurImage(++curImageIndex.value);
+}
+
+function getPrevImage() {
+  if (curImageIndex.value <= 0) return;
+
+  setCurImage(--curImageIndex.value);
+}
+
+// init
+// onMounted(async () => {
+//   await releaseBatch();
+//   startLoopGetData();
+// });
+// onBeforeUnmount(async () => {
+//   stopLoopGetData();
+//   stopLoopRemainTime();
+//   await releaseBatch();
+// });
+
+// TODO:测试数据
+onMounted(() => {
+  const students = "#"
+    .repeat(30)
+    .split("")
+    .map((item, index) => {
+      return {
+        examNumber: `3600802404012${index}`,
+        name: `张三${index + 1}`,
+        studentCode: `36008${index + 1}`,
+        subjectCode: "sx001",
+        subjectName: "数学",
+        seatNumber: "11",
+        papers: [
+          {
+            number: 1,
+            // 是否本张为人工绑定
+            assigned: true,
+            // 数组为空表示缺纸
+            pages: ["xxx.jpg", "xxx.png"],
+          },
+        ],
+      };
+    });
+  batchInfo.value = {
+    batchId: 123,
+    device: "192.168.0.1",
+    deviceName: "scan01",
+    createTime: Date.now(),
+    // 实时审核批次此字段有值
+    packageCode: "ET01245124",
+    students,
+  };
+  hasTask.value = true;
+  loopImageStoped.value = true;
+
+  initImageData();
+  setCurImage(0);
+});
 </script>

+ 20 - 19
src/render/views/Audit/InTime/index.vue

@@ -1,6 +1,6 @@
 <template>
-  <div v-if="hasTask" class="intime">
-    <div class="intime-head">
+  <div v-if="hasTask" class="audit intime">
+    <div class="audit-head">
       <a-row>
         <a-col :span="6">
           <span class="head-label">扫描员:</span>
@@ -61,14 +61,14 @@
             <template #icon><CheckCircleOutlined /></template>
             通过
           </a-button>
-          <a-button danger plain type="primary" @click="onConfirm(false)">
+          <a-button type="error-light" @click="onConfirm(false)">
             <template #icon><CloseCircleOutlined /></template>
             拒绝
           </a-button>
         </a-space>
       </div>
     </div>
-    <div class="intime-body">
+    <div class="audit-body">
       <template v-if="curStudent">
         <div v-for="paper in curStudent.papers" :key="paper.number">
           <img
@@ -93,7 +93,7 @@
       </template>
     </div>
   </div>
-  <div v-else class="intime is-wait">
+  <div v-else class="audit is-wait">
     <div>
       <img src="@/assets/imgs/bg-wait.png" alt="等待" />
       <p>等待审核结果…</p>
@@ -102,7 +102,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted } from "vue";
+import { ref, onMounted, onBeforeUnmount } from "vue";
 import {
   CheckCircleOutlined,
   CloseCircleOutlined,
@@ -112,8 +112,9 @@ import { message } from "ant-design-vue";
 import { omit } from "lodash-es";
 
 import {
-  IntimeAuditBatchResult,
-  IntimeAuditBatchStudent,
+  AuditBatchResult,
+  AuditBatchStudent,
+  AuditBatchData,
 } from "@/ap/types/audit";
 import { intimeAuditBatch, intimeAuditBatchSubmit } from "@/ap/audit";
 import { useUserStore } from "@/store";
@@ -128,15 +129,14 @@ defineOptions({
 
 const userStore = useUserStore();
 
-interface StudentItem extends IntimeAuditBatchStudent {
+interface StudentItem extends AuditBatchStudent {
   status: boolean;
 }
-type BatchData = Omit<IntimeAuditBatchResult, "students">;
 
 const dataList = ref<StudentItem[]>([]);
 const curStudent = ref<StudentItem | null>(null);
 const curStudentIndex = ref(0);
-const batchInfo = ref({} as BatchData);
+const batchInfo = ref({} as AuditBatchData);
 const hasTask = ref(false);
 
 const { start: startLoopGetData, stop: stopLoopGetData } = useLoop(
@@ -153,8 +153,7 @@ async function getData() {
   hasTask.value = true;
   batchInfo.value = omit(res, "students");
   dataList.value = res.students || [];
-  curStudentIndex.value = 0;
-  setCurStudent();
+  setCurStudent(0);
 }
 
 function onMark() {
@@ -163,21 +162,19 @@ function onMark() {
 }
 
 // task
-function setCurStudent(index: number | undefined) {
-  if (index !== undefined) curStudentIndex.value = index;
+function setCurStudent(index: number) {
+  curStudentIndex.value = index;
   curStudent.value = dataList.value[curStudentIndex.value];
 }
 function getNextStudent() {
   if (curStudentIndex.value === dataList.value.length - 1) return;
 
-  curStudentIndex.value++;
-  setCurStudent();
+  setCurStudent(++curStudentIndex.value);
 }
 function getPrevStudent() {
   if (curStudentIndex.value === 0) return;
 
-  curStudentIndex.value--;
-  setCurStudent();
+  setCurStudent(--curStudentIndex.value);
 }
 
 // confirm
@@ -190,6 +187,10 @@ async function onConfirm(confirm: boolean) {
   startLoopGetData();
 }
 
+onBeforeUnmount(() => {
+  stopLoopGetData();
+});
+
 onMounted(() => {
   // startLoopGetData();
 

+ 112 - 68
src/render/views/Audit/Main/index.vue

@@ -17,84 +17,86 @@
       </div>
     </div>
     <div class="home-body">
-      <div class="audit-box">
-        <div class="audit-box-head">
-          <h4>实时审核</h4>
-        </div>
-        <div class="audit-box-body">
-          <div class="audit-card">
-            <div class="audit-card-icon audit-wait"></div>
-            <div class="audit-card-content">
-              <p>待审核</p>
-              <p>1</p>
-            </div>
-            <div class="audit-card-action" @click="toPage('IntimeAudit')">
-              进入 <RightOutlined />
+      <template v-if="overviewData">
+        <div class="audit-box">
+          <div class="audit-box-head">
+            <h4>实时审核</h4>
+          </div>
+          <div class="audit-box-body">
+            <div class="audit-card">
+              <div class="audit-card-icon audit-wait"></div>
+              <div class="audit-card-content">
+                <p>待审核</p>
+                <p>{{ overviewData.verifyTask.todoCount }}</p>
+              </div>
+              <div class="audit-card-action" @click="toPage('IntimeAudit')">
+                进入 <RightOutlined />
+              </div>
             </div>
           </div>
         </div>
-      </div>
 
-      <div class="audit-box">
-        <div class="audit-box-head">
-          <h4>人工绑定审核</h4>
-        </div>
-        <div class="audit-box-body">
-          <div class="audit-card">
-            <div class="audit-card-icon audit-wait"></div>
-            <div class="audit-card-content">
-              <p>待审核</p>
-              <p>1</p>
-            </div>
-            <div class="audit-card-action" @click="toPage('ReviewAudit')">
-              进入 <RightOutlined />
+        <div class="audit-box">
+          <div class="audit-box-head">
+            <h4>人工绑定审核</h4>
+          </div>
+          <div class="audit-box-body">
+            <div class="audit-card">
+              <div class="audit-card-icon audit-wait"></div>
+              <div class="audit-card-content">
+                <p>待审核</p>
+                <p>{{ overviewData.assignedCheck.todoCount }}</p>
+              </div>
+              <div class="audit-card-action" @click="toPage('ReviewAudit')">
+                进入 <RightOutlined />
+              </div>
             </div>
           </div>
         </div>
-      </div>
 
-      <div class="audit-box img-check">
-        <div class="audit-box-head">
-          <h4>图片检查</h4>
-        </div>
-        <div class="audit-box-body">
-          <div class="audit-card">
-            <div class="audit-card-icon audit-done"></div>
-            <div class="audit-card-content">
-              <p>已审核</p>
-              <p>1</p>
-            </div>
+        <div class="audit-box img-check">
+          <div class="audit-box-head">
+            <h4>图片检查</h4>
           </div>
-
-          <div class="audit-card">
-            <div class="audit-card-icon audit-wait"></div>
-            <div class="audit-card-content">
-              <p>待审核</p>
-              <p>1</p>
+          <div class="audit-box-body">
+            <div class="audit-card">
+              <div class="audit-card-icon audit-done"></div>
+              <div class="audit-card-content">
+                <p>已审核</p>
+                <p>{{ overviewData.imageCheckTask.finishCount }}</p>
+              </div>
             </div>
-            <div class="audit-card-action" @click="toPage('ImageCheckAudit')">
-              进入 <RightOutlined />
+
+            <div class="audit-card">
+              <div class="audit-card-icon audit-wait"></div>
+              <div class="audit-card-content">
+                <p>待审核</p>
+                <p>{{ overviewData.imageCheckTask.todoCount }}</p>
+              </div>
+              <div class="audit-card-action" @click="toPage('ImageCheckAudit')">
+                进入 <RightOutlined />
+              </div>
             </div>
           </div>
+          <div class="audit-box-foot">
+            <a-tag :bordered="false">
+              <template #icon><PieChartFilled /></template>抽查比例:0%
+            </a-tag>
+            <a-space :size="8">
+              <span>轮播时间配置:</span>
+              <a-input-number
+                v-model:value="imageCheckLoopTime"
+                :min="1"
+                :max="999"
+                :precision="0"
+                :controls="false"
+              ></a-input-number>
+              <span>秒/张</span>
+              <a-button type="primary" @click="onSetLoopTime">设置</a-button>
+            </a-space>
+          </div>
         </div>
-        <div class="audit-box-foot">
-          <a-tag :bordered="false">
-            <template #icon><PieChartFilled /></template>抽查比例:0%
-          </a-tag>
-          <a-space :size="8">
-            <span>轮播时间配置:</span>
-            <a-input-number
-              v-model:value="imageCheckLoopTime"
-              :min="0"
-              :max="9999"
-              :precision="0"
-              :controls="false"
-            ></a-input-number>
-            <span>秒/张</span>
-            <a-button type="primary" @click="onSetLoopTime">设置</a-button>
-          </a-space>
-        </div>
-      </div>
+      </template>
     </div>
   </div>
 
@@ -103,7 +105,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from "vue";
+import { ref, watch } from "vue";
 import {
   SwapOutlined,
   RightOutlined,
@@ -111,6 +113,9 @@ import {
 } from "@ant-design/icons-vue";
 import { message } from "ant-design-vue";
 
+import { examOverview } from "@/ap/audit";
+import { ExamOverviewResult } from "@/ap/types/audit";
+
 import { useRouter } from "vue-router";
 import { useUserStore } from "@/store";
 import SelectExamDialog from "./SelectExamDialog.vue";
@@ -122,6 +127,15 @@ defineOptions({
 const router = useRouter();
 const userStore = useUserStore();
 
+const overviewData = ref<ExamOverviewResult | null>(null);
+
+async function getOverviewData() {
+  if (!userStore.curExam) return;
+
+  const res = await examOverview({ examId: userStore.curExam.id });
+  overviewData.value = res || null;
+}
+
 function toPage(name: string) {
   router.push({ name });
 }
@@ -131,13 +145,43 @@ function onSwitchExam() {
   selectExamDialogRef.value?.open();
 }
 
-const imageCheckLoopTime = ref(userStore.imageCheckLoopTime);
+const imageCheckLoopTime = ref(userStore.imageCheckLoopTime || undefined);
 function onSetLoopTime() {
-  if (!imageCheckLoopTime.value && imageCheckLoopTime.value !== 0) {
+  if (!imageCheckLoopTime.value) {
     message.error("请输入轮播时间");
     return;
   }
 
   userStore.setImageCheckLoopTime(imageCheckLoopTime.value);
+  message.success("设置成功!");
 }
+
+watch(
+  () => userStore.curExam,
+  (val) => {
+    if (!val) return;
+    // getOverviewData();
+  },
+  { immediate: true }
+);
+
+// TODO:测试
+overviewData.value = {
+  //实时审核任务
+  verifyTask: {
+    todoCount: 5,
+  },
+  //图片审核
+  imageCheckTask: {
+    //抽查比例
+    checkRatio: 0.5,
+    finishCount: 50,
+    //全部未处理数量
+    todoCount: 20,
+  },
+  //人工绑定审核
+  assignedCheck: {
+    todoCount: 30,
+  },
+};
 </script>