Browse Source

feat: api-评卷管理

zhangjie 1 week ago
parent
commit
bed5bd9b05

+ 50 - 15
src/api/mark.ts

@@ -157,7 +157,7 @@ export function getMarkTrialList(
 export function getMarkMarkerList(
 export function getMarkMarkerList(
   params: MarkMarkerListPageParam
   params: MarkMarkerListPageParam
 ): Promise<MarkMarkerListPageRes> {
 ): Promise<MarkMarkerListPageRes> {
-  return axios.post('/api/mark/marker/list', {}, { params });
+  return axios.post('/api/admin/exam/marker/list', {}, { params });
 }
 }
 
 
 // 启用、禁用评卷员
 // 启用、禁用评卷员
@@ -168,39 +168,74 @@ export function markMarkerEnable({
   ids: number[];
   ids: number[];
   enable: boolean;
   enable: boolean;
 }): Promise<boolean> {
 }): Promise<boolean> {
-  return axios.post('/api/mark/marker/enable', {}, { params: { ids, enable } });
+  return axios.post(
+    '/api/admin/exam/marker/toggle',
+    {},
+    { params: { ids, enable } }
+  );
 }
 }
 // 解绑评卷员
 // 解绑评卷员
-export function markMarkerUnbind(id: number) {
-  return axios.post('/api/mark/marker/unbind', {}, { params: { id } });
+export function markMarkerUnbind(markerId: number): Promise<boolean> {
+  return axios.post(
+    '/api/admin/exam/marker/delete',
+    {},
+    { params: { markerId } }
+  );
 }
 }
 
 
 // 回收任务
 // 回收任务
-export function markMarkerTaskRecycle(ids: number[]) {
-  return axios.post('/api/mark/task/recycle', {}, { params: { ids } });
+export function markMarkerTaskRecycle(ids: number[]): Promise<boolean> {
+  return axios.post('/api/admin/exam/marker/release', {}, { params: { ids } });
 }
 }
 
 
 // 设置评卷数量
 // 设置评卷数量
 export function markMarkerSetTaskCount({
 export function markMarkerSetTaskCount({
   ids,
   ids,
-  count,
+  taskCount,
 }: {
 }: {
   ids: number[];
   ids: number[];
-  count: number;
-}) {
-  return axios.post('/api/mark/task/set-count', {}, { params: { ids, count } });
+  taskCount: number;
+}): Promise<boolean> {
+  return axios.post(
+    '/api/admin/exam/marker/set-task-count',
+    {},
+    { params: { ids, taskCount } }
+  );
 }
 }
 
 
 // 重置密码
 // 重置密码
-export function markMarkerResetPassword(ids: number[]) {
-  return axios.post('/api/mark/task/reset-password', {}, { params: { ids } });
+export function markMarkerResetPassword(
+  ids: number[],
+  password = '123456'
+): Promise<boolean> {
+  return axios.post(
+    '/api/admin/exam/marker/reset-password',
+    {},
+    { params: { ids, password } }
+  );
 }
 }
 // 重置已评数量
 // 重置已评数量
-export function markMarkerResetFinishCount(id: number) {
+export function markMarkerResetFinishCount(
+  id: number,
+  deleteCode: string
+): Promise<boolean> {
+  return axios.post(
+    '/api/admin/exam/marker/reset',
+    {},
+    { params: { id, deleteCode } }
+  );
+}
+// 重置是否弹窗确认
+export function checkResetNeedConfirm(): Promise<boolean> {
+  return axios.post('/api/admin/exam/marker/delete-check', {});
+}
+
+// 评卷员导入模板下载
+export function markerImportTemplate(): Promise<AxiosResponse<Blob>> {
   return axios.post(
   return axios.post(
-    '/api/mark/task/reset-finish-count',
+    '/api/admin/exam/marker/template',
     {},
     {},
-    { params: { id } }
+    { responseType: 'blob' }
   );
   );
 }
 }
 
 

+ 1 - 0
src/api/types/mark.ts

@@ -143,6 +143,7 @@ export interface MarkTaskItem {
   secretNumber: string;
   secretNumber: string;
   // 状态
   // 状态
   status: string;
   status: string;
+  statusText: string;
   // 打回原因
   // 打回原因
   rejectReason: string;
   rejectReason: string;
   // 评卷员
   // 评卷员

+ 0 - 1
src/components/table-field/index.vue

@@ -10,7 +10,6 @@
     width="700px"
     width="700px"
     :close-on-click-modal="false"
     :close-on-click-modal="false"
     :close-on-press-escape="false"
     :close-on-press-escape="false"
-    top="10vh"
     append-to-body
     append-to-body
     align-center
     align-center
     :close-close="handleClose"
     :close-close="handleClose"

+ 3 - 0
src/utils/download-export.ts

@@ -37,6 +37,7 @@ import {
   exportGroupArbitrateStatList,
   exportGroupArbitrateStatList,
   exportArbitrationList,
   exportArbitrationList,
   exportMarkTaskList,
   exportMarkTaskList,
+  markerImportTemplate,
 } from '@/api/mark';
 } from '@/api/mark';
 
 
 import useLoading from '@/hooks/loading';
 import useLoading from '@/hooks/loading';
@@ -115,6 +116,8 @@ const downloadConfig = {
   exportArbitrationList,
   exportArbitrationList,
   // 导出任务列表
   // 导出任务列表
   exportMarkTaskList,
   exportMarkTaskList,
+  // 评卷员导入模板下载
+  markerImportTemplate,
 };
 };
 type DownloadType = keyof typeof downloadConfig;
 type DownloadType = keyof typeof downloadConfig;
 
 

+ 27 - 42
src/views/mark/MarkerManage.vue

@@ -62,12 +62,7 @@
       <el-table-column prop="name" label="姓名" width="100" />
       <el-table-column prop="name" label="姓名" width="100" />
       <el-table-column prop="subjectCode" label="科目" width="120" />
       <el-table-column prop="subjectCode" label="科目" width="120" />
       <el-table-column prop="groupNumber" label="分组" width="100" sortable />
       <el-table-column prop="groupNumber" label="分组" width="100" sortable />
-      <el-table-column label="状态" width="100">
-        <template #default="scope">
-          <el-tag :type="getStatusType(scope.row.status)" size="small">
-            {{ getStatusText(scope.row.status) }}
-          </el-tag>
-        </template>
+      <el-table-column prop="statusText" label="状态" width="100">
       </el-table-column>
       </el-table-column>
       <el-table-column prop="markedCount" label="已评数量" width="120" sortable>
       <el-table-column prop="markedCount" label="已评数量" width="120" sortable>
         <template #default="scope">
         <template #default="scope">
@@ -170,12 +165,20 @@
   <!-- 导入 -->
   <!-- 导入 -->
   <ImportDialog
   <ImportDialog
     ref="importDialogRef"
     ref="importDialogRef"
-    title="导入数据"
-    upload-url="/api/admin/site/import"
+    title="导入评卷员"
+    upload-url="/api/admin/exam/marker/import"
+    :upload-data="uploadData"
     :format="['xls', 'xlsx']"
     :format="['xls', 'xlsx']"
-    :download-handle="downloadTemplate"
+    :download-handle="() => downloadExport('markerImportTemplate')"
     download-filename="评卷员导入模板.xlsx"
     download-filename="评卷员导入模板.xlsx"
   />
   />
+
+  <!-- 授权码 -->
+  <CodeConfirmDialog
+    ref="codeConfirmDialogRef"
+    :progress="curMarkerGroupProgress"
+    @modified="onResetFinishConfirm"
+  />
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
@@ -192,8 +195,10 @@
   import { MarkMarkerItem, MarkMarkerListFilter } from '@/api/types/mark';
   import { MarkMarkerItem, MarkMarkerListFilter } from '@/api/types/mark';
   import useTable from '@/hooks/table';
   import useTable from '@/hooks/table';
   import { modalConfirm } from '@/utils/ui';
   import { modalConfirm } from '@/utils/ui';
+  import { downloadExport } from '@/utils/download-export';
 
 
   import SetMarkCountDialog from './components/SetMarkCountDialog.vue';
   import SetMarkCountDialog from './components/SetMarkCountDialog.vue';
+  import CodeConfirmDialog from './components/CodeConfirmDialog.vue';
 
 
   defineOptions({
   defineOptions({
     name: 'MarkerManage',
     name: 'MarkerManage',
@@ -209,6 +214,9 @@
 
 
   const setMarkCountDialogRef = ref<InstanceType<typeof SetMarkCountDialog>>();
   const setMarkCountDialogRef = ref<InstanceType<typeof SetMarkCountDialog>>();
   const curMarkerIds = ref<number[]>([]);
   const curMarkerIds = ref<number[]>([]);
+  const curMarkerGroupProgress = ref<number>(0);
+  const curRow = ref<MarkMarkerItem>();
+  const codeConfirmDialogRef = ref<InstanceType<typeof CodeConfirmDialog>>();
 
 
   const {
   const {
     dataList,
     dataList,
@@ -227,26 +235,6 @@
     return selectedRows.value.map((row) => row.id);
     return selectedRows.value.map((row) => row.id);
   });
   });
 
 
-  // 获取状态类型
-  function getStatusType(status: string) {
-    const statusMap: Record<string, string> = {
-      enabled: 'success',
-      disabled: 'danger',
-      binding: 'warning',
-    };
-    return statusMap[status] || 'info';
-  }
-
-  // 获取状态文本
-  function getStatusText(status: string) {
-    const statusMap: Record<string, string> = {
-      enabled: '已启用',
-      disabled: '已禁用',
-      binding: '绑定中',
-    };
-    return statusMap[status] || status;
-  }
-
   // 启用/禁用评卷员
   // 启用/禁用评卷员
   async function onEnable(row: MarkMarkerItem, enable: boolean) {
   async function onEnable(row: MarkMarkerItem, enable: boolean) {
     const action = enable ? '启用' : '禁用';
     const action = enable ? '启用' : '禁用';
@@ -393,14 +381,15 @@
 
 
   // 重置已评数量
   // 重置已评数量
   async function onResetFinishCount(row: MarkMarkerItem) {
   async function onResetFinishCount(row: MarkMarkerItem) {
-    const confirm = await modalConfirm(
-      `确认重置评卷员 ${row.name}(${row.loginName}) 的已评数量吗?`,
-      '提示 '
-    ).catch(() => false);
-    if (!confirm) return;
+    // TODO:获取进度
+    // curMarkerGroupProgress.value = row.progress;
+    curRow.value = row;
+    codeConfirmDialogRef.value?.open();
+  }
 
 
+  async function onResetFinishConfirm(deleteCode: string) {
     try {
     try {
-      await markMarkerResetFinishCount(row.id);
+      await markMarkerResetFinishCount(curRow.value.id, deleteCode);
       ElMessage.success('重置已评数量成功');
       ElMessage.success('重置已评数量成功');
       getList();
       getList();
     } catch (error) {
     } catch (error) {
@@ -410,14 +399,10 @@
 
 
   // 导入
   // 导入
   const importDialogRef = ref();
   const importDialogRef = ref();
+  const uploadData = computed(() => {
+    return { subjectCode: searchModel.subjectCode };
+  });
   const onImport = () => {
   const onImport = () => {
     importDialogRef.value?.open();
     importDialogRef.value?.open();
   };
   };
-  async function downloadTemplate() {
-    // const res = await downloadByApi(() => agentTemplate()).catch((e) => {
-    //   Message.error(e || '下载失败,请重新尝试!');
-    // });
-    // if (!res) return;
-    // Message.success('下载成功!');
-  }
 </script>
 </script>

+ 206 - 0
src/views/mark/components/CodeConfirmDialog.vue

@@ -0,0 +1,206 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    title="授权码确认"
+    width="500px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    align-center
+    append-to-body
+    @close="handleClose"
+    @open="modalBeforeOpen"
+  >
+    <div class="code-confirm-content">
+      <div class="progress-info">
+        <p>当前进度:{{ progress }}%</p>
+        <p>需要输入授权码 {{ requiredTimes }} 次进行确认</p>
+        <p>已输入次数:{{ inputHistory.length }}</p>
+      </div>
+
+      <el-form
+        ref="formRef"
+        :model="formModel"
+        :rules="rules"
+        label-width="80px"
+      >
+        <el-form-item label="授权码" prop="currentCode">
+          <el-input
+            v-model.trim="formModel.currentCode"
+            type="password"
+            placeholder="请输入授权码"
+            show-password
+            maxlength="20"
+            @keyup.enter="confirmCurrentCode"
+          />
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="close">取消</el-button>
+        <el-button
+          type="primary"
+          :loading="loading"
+          :disabled="!canSubmit"
+          @click="confirmCurrentCode"
+        >
+          {{ isLastStep ? '提交' : '确认' }}
+        </el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive, computed } from 'vue';
+  import type { FormInstance, FormRules } from 'element-plus';
+  import { ElMessage } from 'element-plus';
+  import useModal from '@/hooks/modal';
+
+  defineOptions({
+    name: 'CodeConfirmDialog',
+  });
+
+  interface Props {
+    progress: number; // 进度值
+  }
+
+  const props = defineProps<Props>();
+  const emit = defineEmits(['confirmed', 'cancelled']);
+
+  /* modal */
+  const { visible, open, close } = useModal();
+  defineExpose({ open, close });
+
+  const formRef = ref<FormInstance>();
+
+  interface FormModel {
+    currentCode: string;
+  }
+
+  const initialFormState: FormModel = {
+    currentCode: '',
+  };
+
+  const formModel = reactive<FormModel>({ ...initialFormState });
+  const inputHistory = ref<string[]>([]);
+  const currentStep = ref(1);
+
+  // 计算需要输入的次数
+  const requiredTimes = computed(() => {
+    if (props.progress <= 80) {
+      return 2;
+    }
+    if (props.progress > 80 && props.progress < 100) {
+      return 3;
+    }
+    if (props.progress === 100) {
+      return 4;
+    }
+    return 2;
+  });
+
+  // 是否是最后一步
+  const isLastStep = computed(() => {
+    return currentStep.value === requiredTimes.value;
+  });
+
+  // 是否可以提交
+  const canSubmit = computed(() => {
+    return formModel.currentCode.trim().length > 0;
+  });
+
+  const rules: FormRules<keyof FormModel> = {
+    currentCode: [
+      { required: true, message: '请输入授权码', trigger: 'blur' },
+      {
+        validator: (rule, value, callback) => {
+          // 如果不是第一次输入,需要与上一次输入进行比对
+          if (inputHistory.value.length > 0) {
+            const lastCode = inputHistory.value[inputHistory.value.length - 1];
+            if (value !== lastCode) {
+              callback(new Error('授权码与上一次输入不一致,请重新输入'));
+              return;
+            }
+          }
+          callback();
+        },
+        trigger: 'blur',
+      },
+    ],
+  };
+
+  // 确认当前输入的授权码
+  async function confirmCurrentCode() {
+    if (!formRef.value) return;
+
+    const valid = await formRef.value.validate().catch(() => false);
+    if (!valid) return;
+
+    const currentCode = formModel.currentCode.trim();
+
+    // 记录当前输入
+    inputHistory.value.push(currentCode);
+
+    // 检查是否已完成所有输入
+    if (inputHistory.value.length >= requiredTimes.value) {
+      // 提交授权码
+      submitCode(currentCode);
+    } else {
+      // 继续下一次输入
+      currentStep.value++;
+      formModel.currentCode = '';
+      ElMessage.success(`第${inputHistory.value.length}次确认成功,请继续输入`);
+    }
+  }
+
+  // 提交授权码
+  function submitCode(code: string) {
+    emit('confirmed', code);
+    close();
+  }
+
+  // 重置表单状态
+  const handleClose = () => {
+    formRef.value?.resetFields();
+    Object.assign(formModel, initialFormState);
+    inputHistory.value = [];
+    currentStep.value = 1;
+    emit('cancelled');
+  };
+
+  // 弹窗打开前的初始化
+  function modalBeforeOpen() {
+    // 重置所有状态
+    Object.assign(formModel, initialFormState);
+    inputHistory.value = [];
+    currentStep.value = 1;
+  }
+</script>
+
+<style scoped>
+  .code-confirm-content {
+    padding: 10px 0;
+  }
+
+  .progress-info {
+    margin-bottom: 20px;
+    padding: 15px;
+    background-color: #f5f7fa;
+    border-radius: 4px;
+  }
+
+  .progress-info p {
+    margin: 0;
+    margin-bottom: 8px;
+    color: #606266;
+    font-size: 14px;
+  }
+
+  .progress-info p:last-child {
+    margin-bottom: 0;
+    font-weight: 500;
+    color: #409eff;
+  }
+</style>