Browse Source

feat: 识别对照页面

zhangjie 9 tháng trước cách đây
mục cha
commit
f47d87a54d

+ 117 - 3
src/render/ap/recognizeCheck.ts

@@ -1,18 +1,32 @@
 import { request } from "@/utils/request";
 
 import { ExamSubjectPageParams } from "./types/common";
-import { RecognizeConditionItem } from "./types/recognizeCheck";
+import {
+  RecognizeConditionItem,
+  RecognizeCheckListPageResult,
+  RecognizeCheckTaskSaveParams,
+  RecognizeCheckTaskActionResult,
+  RecognizeCheckBuildTaskResult,
+  RecognizeCheckResetTaskResult,
+  RecognizeCheckDeleteTaskResult,
+  RecognizeArbitrateListItem,
+  RecognizeArbitrateSaveParams,
+  RecognizeArbitrateSaveResult,
+  RecognizeArbitrateProgressResult,
+  RecognizeArbitrateHistoryParams,
+} from "./types/recognizeCheck";
 
 // 识别对照管理
-export const recognizeCheckList = (
+export const recognizeCheckListPage = (
   data: ExamSubjectPageParams
-): Promise<ImageCheckListPageResult> =>
+): Promise<RecognizeCheckListPageResult> =>
   request({
     url: "/api/admin/check/omr/group/page",
     method: "post",
     data,
   });
 
+// 识别对照任务
 // 所有可用的识别对照条件
 export const recognizeConditionsList = (): Promise<RecognizeConditionItem[]> =>
   request({
@@ -20,3 +34,103 @@ export const recognizeConditionsList = (): Promise<RecognizeConditionItem[]> =>
     method: "post",
     data,
   });
+// 创建/修改识别对照任务组
+export const recognizeCheckTaskSave = (
+  data: RecognizeCheckTaskSaveParams
+): Promise<RecognizeCheckTaskActionResult> =>
+  request({
+    url: "/api/admin/check/omr/group/save",
+    method: "post",
+    data,
+  });
+
+// 修改识别对照任务组阶段
+export const recognizeCheckTaskStatusSave = (
+  id: number
+): Promise<{ stage: string; updateTime: number }> =>
+  request({
+    url: "/api/admin/check/omr/group/toggle",
+    method: "post",
+    data: { id },
+  });
+
+// 识别对照任务组生成任务
+export const recognizeCheckBuildTask = (
+  id: number
+): Promise<RecognizeCheckBuildTaskResult> =>
+  request({
+    url: "/api/admin/check/omr/group/build",
+    method: "post",
+    data: { id },
+  });
+
+// 重置识别对照任务组
+export const recognizeCheckResetTask = (
+  id: number
+): Promise<RecognizeCheckResetTaskResult> =>
+  request({
+    url: "/api/admin/check/omr/group/reset",
+    method: "post",
+    data: { id },
+  });
+
+// 删除识别对照任务组
+export const recognizeCheckTaskDelete = (
+  id: number
+): Promise<RecognizeCheckDeleteTaskResult> =>
+  request({
+    url: "/api/admin/check/omr/group/delete",
+    method: "post",
+    data: { id },
+  });
+
+// 仲裁
+// 识别对照仲裁任务获取
+export const recognizeArbitrateListPage = (
+  groupId: number
+): Promise<RecognizeArbitrateListItem> =>
+  request({
+    url: "/api/admin/check/omr/arbitrate/get",
+    method: "post",
+    data: { groupId },
+  });
+
+// 识别对照仲裁结果提交
+export const recognizeArbitrateSave = (
+  data: RecognizeArbitrateSaveParams
+): Promise<RecognizeArbitrateSaveResult> =>
+  request({
+    url: "/api/admin/check/omr/arbitrate/save",
+    method: "post",
+    data,
+  });
+
+// 识别对照仲裁进度状态
+export const recognizeArbitrateProgress = (
+  groupId: number
+): Promise<RecognizeArbitrateProgressResult> =>
+  request({
+    url: "/api/admin/check/omr/arbitrate/get",
+    method: "post",
+    data: { groupId },
+  });
+
+// 识别对照仲裁任务释放
+export const recognizeArbitrateRelease = (
+  groupId: number
+): Promise<{ success: boolean }> =>
+  request({
+    url: "/api/admin/check/omr/arbitrate/release",
+    method: "post",
+    data: { groupId },
+  });
+
+// 识别对照仲裁任务历史
+export const recognizeArbitrateHistory = (
+  data: RecognizeArbitrateHistoryParams
+): Promise<RecognizeArbitrateListItem> =>
+  request({
+    url: "/api/admin/check/omr/arbitrate/history",
+    method: "post",
+    data,
+  });

+ 111 - 11
src/render/ap/types/recognizeCheck.ts

@@ -6,25 +6,28 @@ export interface RecognizeConditionItem {
   needValue: boolean;
 }
 
+export interface RecognizeCheckTaskConditionItem {
+  code: string;
+  name: string;
+  value: number | null;
+}
+
 // 识别对照管理
 export interface RecognizeCheckListItem {
   id: number;
-  conditions: Array<{
-    code: string;
-    value: string | number;
-  }>;
+  conditions: RecognizeCheckTaskConditionItem[];
   subjectCode: string;
   subjectName: string;
-  //默认任务组为true,不能修改删除,自动生成任务
-  //非默认任务组允许手动生成任务
+  // 默认任务组为true,不能修改删除,自动生成任务
+  // 非默认任务组允许手动生成任务
   fixed: boolean;
-  //初始阶段为FIRST
+  // 初始阶段为FIRST
   stage: string;
-  //是否正在重置任务
+  // 是否正在重置任务
   reseting: boolean;
-  //是否正在生成任务
+  // 是否正在生成任务
   building: boolean;
-  //是否正在删除
+  // 是否正在删除
   deleting: boolean;
   totalCount: number;
   finishCount: number;
@@ -32,4 +35,101 @@ export interface RecognizeCheckListItem {
   arbitratedCount: number;
   updateTime: number;
 }
-export type ImageCheckListPageResult = PageResult<ImageCheckListItem>;
+export type RecognizeCheckListPageResult = PageResult<RecognizeCheckListItem>;
+
+export interface RecognizeCheckTaskSetConditionItem {
+  code: string;
+  name: string;
+  needValue: boolean;
+  value: number | null;
+}
+
+export interface RecognizeCheckTaskSaveParams {
+  // 没有id则表示新增
+  id?: number;
+  examId: number;
+  subjectCode: string;
+  conditions: RecognizeCheckTaskSetConditionItem[];
+}
+
+export interface RecognizeCheckTaskActionResult {
+  id: number;
+  updateTime: number;
+}
+export interface RecognizeCheckBuildTaskResult
+  extends RecognizeCheckTaskActionResult {
+  building: boolean;
+}
+export interface RecognizeCheckResetTaskResult
+  extends RecognizeCheckTaskActionResult {
+  reseting: boolean;
+}
+export interface RecognizeCheckDeleteTaskResult
+  extends RecognizeCheckTaskActionResult {
+  deleting: boolean;
+}
+
+// 仲裁
+
+export interface RecognizeArbitrateListItemPage {
+  index: number;
+  uri: string;
+  // 字段为null表示不需要仲裁处理
+  absent: boolean | null;
+  breach: boolean | null;
+  paperType: string;
+  // key为识别结果集中的序号,从1开始
+  // 仲裁任务value数组有两个结果,分别表示一评和二评结果
+  question: Record<number, string>;
+  selective: null | boolean;
+  recogData: string;
+}
+
+export interface RecognizeArbitrateListItem {
+  // 任务ID
+  id: number;
+  examNumber: string;
+  name: string;
+  studentCode: string;
+  subjectCode: string;
+  subjectName: string;
+  // 考生最新使用的卡格式编号
+  cardNumber: number;
+  paperId: number;
+  paperNumber: number;
+  pages: RecognizeArbitrateListItemPage[];
+}
+
+export type RecognizeArbitrateListPageResult =
+  PageResult<RecognizeArbitrateListItem>;
+
+export interface RecognizeArbitrateSavePage {
+  index: number;
+  // 与任务对应,有需要处理的才传递结果,否则为null
+  // 所有需要仲裁的处理项需要全部提交
+  absent: boolean | null;
+  breach: boolean | null;
+  paperType: string;
+  question: Record<number, string>;
+  selective: null | boolean;
+}
+export interface RecognizeArbitrateSaveParams {
+  id: number;
+  pages: RecognizeArbitrateSavePage[];
+}
+
+export interface RecognizeArbitrateSaveResult
+  extends RecognizeCheckTaskActionResult {
+  status: string;
+}
+
+export interface RecognizeArbitrateProgressResult {
+  finishCOunt: number;
+  todoCount: number;
+}
+
+export interface RecognizeArbitrateHistoryParams {
+  groupId: number;
+  id?: number;
+  next?: boolean;
+}

+ 19 - 0
src/render/router/routes.ts

@@ -38,6 +38,25 @@ const routes: RouteRecordRaw[] = [
           title: "扫描管理",
         },
       },
+      {
+        path: "recognize-check",
+        name: "RecognizeCheck",
+        component: () => import("@/views/RecognizeCheck/index.vue"),
+        meta: {
+          title: "识别对照",
+        },
+        children: [
+          {
+            path: "recognize-arbitrate/:groupId",
+            name: "RecognizeArbitrate",
+            component: () =>
+              import("@/views/RecognizeCheck/RecognizeArbitrate.vue"),
+            meta: {
+              title: "仲裁",
+            },
+          },
+        ],
+      },
       {
         path: "data-check",
         name: "DataCheck",

+ 1 - 0
src/render/views/CurExam/index.vue

@@ -130,6 +130,7 @@
               <div
                 class="flex items-center cursor-pointer"
                 :style="{ color: token.colorPrimary }"
+                @click="toPage('RecognizeCheck')"
               >
                 <span>进入</span>
                 <RightOutlined />

+ 207 - 0
src/render/views/RecognizeCheck/ModifyRecognizeCheckTask.vue

@@ -0,0 +1,207 @@
+<template>
+  <a-modal
+    v-model:open="visible"
+    :width="450"
+    style="top: 10vh"
+    :confirm-loading="loading"
+    @ok="confirm"
+  >
+    <template #title> {{ title }} </template>
+
+    <a-form
+      ref="formRef"
+      :model="formData"
+      :rules="rules"
+      :label-col="{ style: { width: '120px' } }"
+    >
+      <a-form-item name="subjectCode" label="科目代码">
+        <select-course v-model:value="formData.subjectCode"></select-course>
+      </a-form-item>
+      <a-space
+        v-for="(condition, index) in formData.conditions"
+        :key="condition.code"
+        align="baseline"
+      >
+        <a-form-item
+          :label="index === 0 ? '任务条件' : ''"
+          :name="['conditions', index, 'code']"
+          :rules="{
+            required: true,
+            message: '请选择条件类型',
+            trigger: 'change',
+          }"
+        >
+          <a-select
+            v-model:value="condition.code"
+            :options="conditionList"
+            @change="(val) => conditionChange(val, index)"
+          ></a-select>
+        </a-form-item>
+        <a-form-item
+          v-if="formData.conditions[index].needValue"
+          :name="['conditions', index, 'value']"
+          :rules="{
+            required: true,
+            message: '请输入',
+            trigger: 'change',
+          }"
+        >
+          <a-input-number
+            v-model:value="condition.value"
+            placeholder="数值"
+            style="width: 60px; margin-right: 10px"
+          />
+        </a-form-item>
+
+        <a-space-compact>
+          <a-button @click="onAddCondition(index)">
+            <template #icon><PlusOutlined /></template>
+          </a-button>
+          <a-button @click="onDeleteCondition(index)">
+            <template #icon><MinusOutlined /></template>
+          </a-button>
+        </a-space-compact>
+      </a-space>
+    </a-form>
+  </a-modal>
+</template>
+
+<script setup lang="ts">
+import { onMounted, computed, reactive, ref, watch } from "vue";
+import type { UnwrapRef } from "vue";
+import { message } from "ant-design-vue";
+import { PlusOutlined, MinusOutlined } from "@ant-design/icons-vue";
+
+import useLoading from "@/hooks/useLoading";
+import useModal from "@/hooks/useModal";
+import {
+  recognizeCheckTaskSave,
+  recognizeConditionsList,
+} from "@/ap/recognizeCheck";
+import {
+  RecognizeConditionItem,
+  RecognizeCheckListItem,
+  RecognizeCheckTaskSaveParams,
+  RecognizeCheckTaskSetConditionItem,
+} from "@/ap/types/recognizeCheck";
+
+defineOptions({
+  name: "ModifyRecognizeCheckTask",
+});
+
+/* modal */
+const { visible, open, close } = useModal();
+defineExpose({ open, close });
+
+const defaultFormData = {
+  id: 0,
+  examId: 0,
+  subjectCode: "",
+  conditions: [] as RecognizeCheckTaskSetConditionItem[],
+};
+
+const props = defineProps<{
+  rowData: RecognizeCheckListItem | null;
+}>();
+const emit = defineEmits(["modified"]);
+
+const isEdit = computed(() => !!props.rowData.id);
+const title = computed(() => `${isEdit.value ? "编辑" : "新增"}任务`);
+
+const formRef = ref();
+const formData: UnwrapRef<RecognizeCheckTaskSaveParams> = reactive({
+  ...defaultFormData,
+});
+const rules: FormRules<keyof RecognizeCheckTaskSaveParams> = {
+  subjectCode: [
+    {
+      required: true,
+      message: "请选择",
+      trigger: "change",
+    },
+  ],
+};
+
+// condition action
+const conditionList = ref([]);
+async function getConditionList() {
+  const res = await recognizeConditionsList();
+
+  conditionList.value = res || [];
+}
+
+function conditionChange(val: RecognizeConditionItem, index: number) {
+  formData.conditions[index].needValue = val.needValue;
+  formData.conditions[index].name = val.name;
+}
+
+function onAddCondition(index: number) {
+  formData.conditions.splice(index, 0, {
+    code: "",
+    name: "",
+    needValue: false,
+    value: null,
+  });
+}
+function onDeleteCondition(index: number) {
+  formData.conditions.splice(index, 1);
+}
+
+/* confirm */
+const { loading, setLoading } = useLoading();
+async function confirm() {
+  const valid = await formRef.value?.validate().catch(() => false);
+  if (!valid) return;
+
+  setLoading(true);
+  const res = await recognizeCheckTaskSave(formData).catch(() => false);
+  setLoading(false);
+  if (!res) return;
+  message.success("保存成功!");
+  emit("modified", datas);
+  close();
+}
+
+/* init modal */
+watch(
+  () => visible.value,
+  (val) => {
+    if (val) {
+      modalOpenHandle();
+    }
+  },
+  {
+    immediate: true,
+  }
+);
+
+function modalOpenHandle() {
+  const conditionMap: Record<string, RecognizeConditionItem> = {};
+  conditionList.forEach((item) => {
+    conditionMap[item.code] = item;
+  });
+
+  if (props.rowData) {
+    formData.id = props.rowData.id;
+    formData.subjectCode = props.rowData.subjectCode;
+    formData.examId = window.examId;
+    formData.conditions = props.rowData.conditions.map((item) => {
+      return {
+        ...item,
+        needValue: conditionMap[item.code]?.needValue,
+      };
+    });
+  } else {
+    formData.id = undefined;
+    formData.subjectCode = "";
+    formData.examId = window.examId;
+    formData.conditions = [
+      { code: "", name: "", needValue: false, value: null },
+    ];
+  }
+}
+
+onMounted(() => {
+  // getConditionList();
+});
+</script>

+ 9 - 0
src/render/views/RecognizeCheck/RecognizeArbitrate.vue

@@ -0,0 +1,9 @@
+<template>
+  <div></div>
+</template>
+
+<script setup lang="ts">
+defineOptions({
+  name: "RecognizeArbitrate",
+});
+</script>

+ 195 - 1
src/render/views/RecognizeCheck/index.vue

@@ -1,9 +1,203 @@
 <template>
-  <div></div>
+  <a-form class="m-b-16px" layout="inline">
+    <a-form-item label="科目">
+      <SelectCourse />
+    </a-form-item>
+    <a-form-item>
+      <a-button>查询</a-button>
+    </a-form-item>
+    <a-divider type="vertical" />
+    <qm-button type="primary" :icon="h(PlusCircleOutlined)" @click="onAdd"
+      >新增评卷点信息</qm-button
+    >
+  </a-form>
+
+  <a-table
+    :columns="columns"
+    :row-key="(record) => record.id"
+    :data-source="dataList"
+    :pagination="pagination"
+    :loading="loading"
+    bordered
+  >
+    <template #bodyCell="{ column, index }">
+      <template v-if="column.dataIndex === 'condition'">
+        {{ getConditionContent(index) }}
+      </template>
+      <template v-if="column.dataIndex === 'finishCount'">
+        {{ getConditionProgress(index) }}
+      </template>
+      <template v-if="column.dataIndex === 'operation'">
+        <qm-button type="text" @click="onArbitrate(index)">仲裁</qm-button>
+        <qm-button type="text" @click="onEdit(index)">修改</qm-button>
+        <qm-button type="text" @click="onReset(index)">重置</qm-button>
+        <qm-button type="text" @click="onRecheck(index)">复查</qm-button>
+        <qm-button type="text" @click="onBuildTask(index)">生成任务</qm-button>
+        <qm-button type="text" @click="onDelete(index)">删除</qm-button>
+      </template>
+    </template>
+  </a-table>
+
+  <ModifyRecognizeCheckTask
+    ref="modifyRecognizeCheckTaskRef"
+    :row-data="curRow"
+    @modified="getList"
+  />
 </template>
 
 <script setup lang="ts">
+import { ref, h } from "vue";
+import { useRouter } from "vue-router";
+import { PlusCircleOutlined } from "@ant-design/icons-vue";
+import { message } from "ant-design-vue";
+import type { TableProps } from "ant-design-vue";
+
+import useTable from "@/hooks/useTable";
+import { RecognizeCheckListItem } from "@/ap/types/recognizeCheck";
+import {
+  recognizeCheckListPage,
+  recognizeCheckTaskDelete,
+  recognizeCheckResetTask,
+  recognizeCheckBuildTask,
+  recognizeCheckTaskStatusSave,
+} from "@/ap/recognizeCheck";
+import { showConfirm } from "@/utils/uiUtils";
+
+import ModifyRecognizeCheckTask from "./ModifyRecognizeCheckTask.vue";
+
 defineOptions({
   name: "RecognizeCheck",
 });
+
+const router = useRouter();
+
+const columns: TableProps["columns"] = [
+  {
+    title: "任务条件",
+    dataIndex: "condition",
+  },
+  {
+    title: "科目",
+    dataIndex: "subjectName",
+  },
+  {
+    title: "状态",
+    dataIndex: "stage",
+    width: "120px",
+  },
+  {
+    title: "任务总数",
+    dataIndex: "totalCount",
+    width: "100px",
+  },
+  {
+    title: "完成进度",
+    dataIndex: "finishCount",
+    width: "100px",
+  },
+  {
+    title: "已仲裁数量",
+    dataIndex: "arbitratedCount",
+    width: "120px",
+  },
+  {
+    title: "待仲裁数量",
+    dataIndex: "unarbitrateCount",
+    width: "120px",
+  },
+  {
+    title: "操作",
+    dataIndex: "operation",
+    width: "280px",
+    customCell: () => {
+      return {
+        class: "operation-cell",
+      };
+    },
+  },
+];
+const curRow = ref(null as RecognizeCheckTaskSaveParams | null);
+
+const { dataList, pagination, loading, getList, toPage, deletePageLastItem } =
+  useTable<RecognizeCheckListItem>(
+    recognizeCheckListPage,
+    { examId: 1 },
+    false
+  );
+
+function getConditionContent(index: number): string {
+  const record = dataList.value[index];
+  return record.conditions.map((item) => `${item.name}${item.value}`).join(";");
+}
+function getConditionProgress(index: number): string {
+  const record = dataList.value[index];
+  const progress = ((100 * record.finishCount) / record.totalCount).toFixed(2);
+  return `${progress}%`;
+}
+
+const modifyRecognizeCheckTaskRef = ref();
+function onAdd() {
+  curRow.value = null;
+  modifyRecognizeCheckTaskRef.value?.open();
+}
+function onEdit(index: number) {
+  curRow.value = dataList.value[index];
+  modifyRecognizeCheckTaskRef.value?.open();
+}
+
+function onArbitrate(index) {
+  const record = dataList.value[index];
+  router.push({ name: "RecognizeArbitrate", params: { groupId: record.id } });
+}
+
+async function onReset(index: number) {
+  const confirm = await showConfirm({
+    content: "确定要重置任务吗?",
+  }).catch(() => false);
+  if (confirm !== "confirm") return;
+
+  const record = dataList.value[index];
+  const res = await recognizeCheckResetTask(record.id).catch(() => false);
+  if (!res) return;
+
+  message.success("操作成功");
+  await getList();
+}
+
+async function onRecheck(index: number) {
+  const confirm = await showConfirm({
+    content: "确定要复查任务吗?",
+  }).catch(() => false);
+  if (confirm !== "confirm") return;
+
+  const record = dataList.value[index];
+  const res = await recognizeCheckTaskStatusSave(record.id).catch(() => false);
+  if (!res) return;
+
+  message.success("操作成功");
+  await getList();
+}
+
+async function onBuildTask(index: number) {
+  const record = dataList.value[index];
+  const res = await recognizeCheckBuildTask(record.id).catch(() => false);
+  if (!res) return;
+
+  message.success("操作成功");
+  await getList();
+}
+
+async function onDelete(index: number) {
+  const confirm = await showConfirm({
+    content: "确定要删除任务吗?",
+  }).catch(() => false);
+  if (confirm !== "confirm") return;
+
+  const record = dataList.value[index];
+  const res = await recognizeCheckTaskDelete(record.id).catch(() => false);
+  if (!res) return;
+
+  deletePageLastItem();
+  message.success("操作成功");
+}
 </script>

+ 1 - 0
types/global.d.ts

@@ -11,6 +11,7 @@ declare global {
     $confirm: any;
     $destroyAll: any;
     electronApi: any;
+    examId: number;
   }
 
   type FormRules<T extends string> = Partial<Record<T, Rule[]>>;