Pārlūkot izejas kodu

feat: 新调整

zhangjie 1 mēnesi atpakaļ
vecāks
revīzija
c03801635c

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

@@ -9,9 +9,17 @@ declare module 'vue' {
   export interface GlobalComponents {
     AButton: typeof import('@qmth/ui')['Button']
     ACard: typeof import('@qmth/ui')['Card']
+    ACheckbox: typeof import('@qmth/ui')['Checkbox']
     ACol: typeof import('@qmth/ui')['Col']
+    ACollapse: typeof import('@qmth/ui')['Collapse']
+    ACollapsePanel: typeof import('@qmth/ui')['CollapsePanel']
     ADivider: typeof import('@qmth/ui')['Divider']
+    AForm: typeof import('@qmth/ui')['Form']
+    AFormItem: typeof import('@qmth/ui')['FormItem']
+    AInput: typeof import('@qmth/ui')['Input']
     AInputNumber: typeof import('@qmth/ui')['InputNumber']
+    AModal: typeof import('@qmth/ui')['Modal']
+    APagination: typeof import('@qmth/ui')['Pagination']
     ARadio: typeof import('@qmth/ui')['Radio']
     ARadioGroup: typeof import('@qmth/ui')['RadioGroup']
     ARow: typeof import('@qmth/ui')['Row']
@@ -22,6 +30,7 @@ declare module 'vue' {
     ATabPane: typeof import('@qmth/ui')['TabPane']
     ATabs: typeof import('@qmth/ui')['Tabs']
     ATag: typeof import('@qmth/ui')['Tag']
+    ATooltip: typeof import('@qmth/ui')['Tooltip']
     AUpload: typeof import('@qmth/ui')['Upload']
     QmButton: typeof import('@qmth/ui')['QmButton']
     QmConfigProvider: typeof import('@qmth/ui')['QmConfigProvider']

+ 56 - 22
src/render/views/ExamNumberCheck/EditExamNumberDialog.vue

@@ -3,35 +3,38 @@
     v-model:open="visible"
     :width="380"
     style="bottom: 0"
-    :footer="false"
     :mask="false"
     :maskClosable="false"
     :keyboard="false"
     centered
     wrapClassName="edit-exam-number-dialog"
     title="输入准考证号"
+    @ok="confirm"
   >
-    <a-form ref="formRef" :label-col="{ style: { width: '80px' } }">
-      <a-form-item label="准考证号">
-        <div class="exam-number" style="margin-bottom: 10px">
-          <a-input
-            v-model:value="examNumber"
-            placeholder="请输入"
-            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
+      ref="formRef"
+      :model="formData"
+      :rules="rules"
+      :label-col="{ style: { width: '80px' } }"
+    >
+      <a-form-item name="examNumber" label="准考证号">
+        <a-input
+          v-model:value="formData.examNumber"
+          placeholder="请输入准考证号"
+          allow-clear
+          ref="examNumberRef"
+          @press-enter="confirm"
+        ></a-input>
       </a-form-item>
     </a-form>
   </a-modal>
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref, watch } from "vue";
-import { message } from "ant-design-vue";
+import { onMounted, ref, watch, reactive, nextTick } from "vue";
 import { getTextAreaRows } from "@/utils";
 import useModal from "@/hooks/useModal";
+import { debounce } from "lodash-es";
 
 defineOptions({
   name: "EditExamNumberDialog",
@@ -47,14 +50,40 @@ const props = defineProps<{
 
 const emit = defineEmits(["confirm"]);
 
-const examNumber = ref("");
+interface ModalFormParams {
+  examNumber: string;
+}
 
-async function confirm() {
-  if (!examNumber.value) {
-    message.warning("请输入考号");
-    return;
-  }
-  emit("confirm", examNumber.value);
+const defaultFormData: ModalFormParams = {
+  examNumber: "",
+};
+const examNumberRef = ref();
+const formRef = ref();
+const formData: UnwrapRef<ModalFormParams> = reactive({
+  ...defaultFormData,
+});
+
+const rules: FormRules<keyof ModalFormParams> = {
+  examNumber: [
+    {
+      required: true,
+      message: "请输入准考证号",
+      trigger: "change",
+    },
+    {
+      pattern: /^\d{15}$/,
+      message: "只能输入15位数字的准考证号",
+      trigger: "change",
+    },
+  ],
+};
+
+const confirm = debounce(save, 300); // 防抖保存,防止多次点击保存按钮
+async function save() {
+  const valid = await formRef.value?.validate().catch(() => false);
+  if (!valid) return;
+
+  emit("confirm", formData.examNumber);
   close();
 }
 
@@ -62,7 +91,12 @@ watch(
   () => visible.value,
   (val) => {
     if (val) {
-      examNumber.value = props.data || "";
+      formData.examNumber = props.data || "";
+
+      nextTick(() => {
+        formRef.value?.resetFields();
+        examNumberRef.value?.focus();
+      });
     }
   },
   {

+ 4 - 2
src/render/views/ExamNumberCheck/ScanImage/ConfirmTips.vue

@@ -8,7 +8,7 @@
   </a-modal>
 </template>
 <script lang="ts" setup>
-import { watch, onUnmounted } from "vue";
+import { watch, onUnmounted, nextTick } from "vue";
 import useModal from "@/hooks/useModal";
 
 defineOptions({
@@ -33,7 +33,9 @@ const handleKeyDown = (event: KeyboardEvent) => {
 
 watch(visible, (newValue) => {
   if (newValue) {
-    document.addEventListener("keydown", handleKeyDown);
+    nextTick(() => {
+      document.addEventListener("keydown", handleKeyDown);
+    });
   } else {
     document.removeEventListener("keydown", handleKeyDown);
   }

+ 17 - 12
src/render/views/ExamNumberCheck/ScanImage/RecogEditDialog.vue

@@ -77,12 +77,13 @@
 </template>
 
 <script setup lang="ts">
-import { ref, computed, watch } from "vue";
+import { ref, computed, watch, nextTick } from "vue";
 import { message } from "ant-design-vue";
 import useModal from "@/hooks/useModal";
 import { RecogBlock } from "@/utils/recog/recog";
 import { getBoxImageSize, recogResultTransform } from "@/utils/tool";
 import { useUserStore, useDataCheckStore } from "@/store";
+import { debounce } from "lodash-es";
 
 defineOptions({
   name: "RecogEditDialog",
@@ -199,6 +200,19 @@ function selectOption(option: string) {
   );
 }
 
+// save
+const onConfirm = debounce(save, 300);
+function save() {
+  console.log(`save:${Date.now()}`);
+
+  if (!selectResult.value.length) {
+    message.error("请选择答案");
+    return;
+  }
+
+  emit("confirm", selectResult.value);
+  close();
+}
 // 键盘事件
 function registKeyEvent() {
   document.addEventListener("keydown", keyEventHandle);
@@ -221,16 +235,6 @@ function keyEventHandle(e: KeyboardEvent) {
   }
 }
 
-function onConfirm() {
-  if (!selectResult.value.length) {
-    message.error("请选择答案");
-    return;
-  }
-
-  emit("confirm", selectResult.value);
-  close();
-}
-
 function afterClose() {
   emit("close");
 }
@@ -259,8 +263,9 @@ watch(
   }
 );
 
-function modalOpenHandle() {
+async function modalOpenHandle() {
   selectResult.value = [...props.recogData.result];
+  await nextTick();
   registKeyEvent();
 }
 </script>

+ 5 - 3
src/render/views/ExamNumberCheck/api.ts

@@ -15,11 +15,13 @@ export const allCheckList = async (
 
   const fname = params.isCheck ? `check-${params.menuKey}` : "success";
   const url = `${appStore.serverUrl}/${fname}.csv?${randomCode()}`;
-  let students: StudentCheckItem[] = await fetchAndParseData(url);
+  let students: StudentCheckItem[] = await fetchAndParseData(url).catch(
+    () => []
+  );
   if (params.imageName) {
     students = students.filter((item) => item.imageName === params.imageName);
   }
-  console.log(students);
+  // console.log(students);
 
   const cacheList = await fetchCacheList();
   const data = (students || []).map((item) => {
@@ -43,7 +45,7 @@ export const getExamNumberInfo = async (
   }
   const item = students[0];
   const questions = (item.smda ? item.smda.split("|") : []).map((item) =>
-    item.trim().replace(/[\.\?]/g, "")
+    item.trim().replace(/[\.\?]/g, "#")
   );
   return Promise.resolve({ ...students[0], questions });
 };

+ 20 - 21
src/render/views/ExamNumberCheck/index.vue

@@ -39,8 +39,8 @@
         v-if="dataCheckStore.curPage"
         :key="dataCheckStore.curPage.kid"
         :cant-change-img="true"
-        @prev="onPrevPage"
-        @next="onNextPage"
+        @prev="onPrevStudent"
+        @next="onNextStudent"
       />
     </div>
 
@@ -119,7 +119,7 @@ function buildStudentList(dataList: StudentCheckItem[]): DataCheckListItem[] {
   const data: DataCheckListItem[] = (dataList || []).map((item) => {
     const studentId = `${item.imageName}_${randomCode(8)}`;
     const questions = (item.smda ? item.smda.split("|") : []).map((item) =>
-      item.trim().replace(/[\.\?]/g, "")
+      item.trim().replace(/[\.\?]/g, "#")
     );
     return {
       ...item,
@@ -257,7 +257,6 @@ async function onPrevStudent() {
 async function onNextStudent() {
   if (!dataCheckStore.curStudent) return;
 
-  dataCheckStore.curStudent.checked = true;
   if (dataCheckStore.curStudentIndex >= studentList.value.length - 1) {
     if (pageNumber.value >= pageCount.value) {
       message.error("没有下一个学生了");
@@ -361,7 +360,9 @@ function registShortcut() {
 function removeShortcut() {
   document.removeEventListener("keydown", shortcutHandle);
 }
-function shortcutHandle(e: KeyboardEvent) {
+const studentChanging = ref(false);
+async function shortcutHandle(e: KeyboardEvent) {
+  if (studentChanging.value) return;
   const moveAction = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"];
   if (!moveAction.includes(e.code) || e.repeat) {
     return;
@@ -369,22 +370,18 @@ function shortcutHandle(e: KeyboardEvent) {
 
   e.preventDefault();
 
-  if (e.code === "ArrowUp") {
-    onPrevStudent();
-    return;
-  }
-  if (e.code === "ArrowDown") {
-    onNextStudent();
-    return;
-  }
-  if (e.code === "ArrowLeft") {
-    onPrevPage();
-    return;
-  }
-  if (e.code === "ArrowRight") {
-    onNextPage();
-    return;
+  try {
+    studentChanging.value = true;
+    if (e.code === "ArrowUp" || e.code === "ArrowLeft") {
+      await onPrevStudent();
+    } else if (e.code === "ArrowDown" || e.code === "ArrowRight") {
+      await onNextStudent();
+    }
+  } catch (error) {
+    console.error(error);
   }
+
+  studentChanging.value = false;
 }
 
 onMounted(async () => {
@@ -393,7 +390,9 @@ onMounted(async () => {
     message.error("请先设置服务器地址");
     return;
   }
-  appStore.setState({ serverUrl: appConfig.base.serverUrl });
+  appStore.setState({
+    serverUrl: process.env.VITE_APP_PROXY_URL || appConfig.base.serverUrl,
+  });
   registShortcut();
   menuChange();
 });

+ 7 - 6
src/render/views/ExamNumberCheck/useTask.ts

@@ -15,7 +15,7 @@ export default function useTask() {
 
     const taskList = [] as TaskItem[];
     const examNumber = dataCheckStore.curStudent.examNumber;
-    if (!examNumber || examNumber.includes(".")) {
+    if (!checkExamNumber(examNumber)) {
       taskList.push({
         id: `examNumber_${curPage.studentId}`,
         type: "examNumber",
@@ -28,7 +28,7 @@ export default function useTask() {
     }
 
     curPage.question?.result.map((item, index) => {
-      if (!item || item.length > 1) {
+      if (checkQuestionError(item)) {
         taskList.push({
           id: `question_${curPage.studentId}_${curPage.paperIndex}_${curPage.pageIndex}_${index}`,
           type: "question",
@@ -45,6 +45,9 @@ export default function useTask() {
       taskList,
       curTask: taskList[0],
     });
+    if (taskList.length === 0) {
+      dataCheckStore.curStudent.checked = true;
+    }
   }
 
   function updateNextUnfinishTask() {
@@ -63,13 +66,11 @@ export default function useTask() {
 
   function checkExamNumber(examNumber: string) {
     if (!examNumber) return false;
-    if (examNumber.includes(".")) return false;
-    return true;
+    return /^\d{15}$/.test(examNumber);
   }
 
   function checkQuestionError(cont: string) {
-    if (!cont) return true;
-    if (cont.length > 1) return true;
+    if (!cont || cont.length > 1 || cont.includes("#")) return true;
     return false;
   }
 

+ 1 - 1
src/render/views/ExamNumberCheck/useUpload.ts

@@ -10,7 +10,7 @@ export default function useUpload() {
 
     const smda =
       dataCheckStore.curStudent?.papers[0]?.pages[0]?.question?.result
-        .map((item) => item || ".")
+        .map((item) => (!item || item === "#" ? "." : item))
         .join("|");
 
     const data = [