فهرست منبع

feat: api-分组管理

zhangjie 1 هفته پیش
والد
کامیت
02864a2050

+ 74 - 16
src/api/mark.ts

@@ -17,6 +17,9 @@ import {
   MarkGroupListPageParam,
   MarkGroupListPageRes,
   MarkGroupUpdateParams,
+  MarkGroupResetParams,
+  MarkGroupItem,
+  MarkGroupUpdateStatusParams,
   MarkStatInfo,
   MarkStatListPageParam,
   MarkStatListPageRes,
@@ -244,52 +247,107 @@ export function markerImportTemplate(): Promise<AxiosResponse<Blob>> {
 export function getMarkGroupList(
   params: MarkGroupListPageParam
 ): Promise<MarkGroupListPageRes> {
-  return axios.post('/api/mark/group/list', {}, { params });
-}
-// 结束分组
-export function markGroupEnd(id: number): Promise<boolean> {
-  return axios.post('/api/mark/group/end', {}, { params: { id } });
+  return axios.post('/api/admin/exam/group/list', {}, { params });
 }
 
 // 获取分组信息
-export function getMarkGroupInfo(id: number): Promise<MarkGroupUpdateParams> {
-  return axios.post('/api/mark/group/info', {}, { params: { id } });
+export function getMarkGroupInfo({
+  subjectCode,
+  groupNumber,
+}: {
+  subjectCode: string;
+  groupNumber: number;
+}): Promise<MarkGroupItem> {
+  return axios.post(
+    '/api/mark/group/info',
+    {},
+    { params: { subjectCode, groupNumber } }
+  );
+}
+
+function transformGroupUpdateData(data: MarkGroupUpdateParams) {
+  const ndata = { ...data };
+  ndata.intervalScoreList = data.questions.map((item) => item.intervalScore);
+  ndata.arbitrateThresholdList = data.questions.map(
+    (item) => item.arbitrateThreshold
+  );
+  ndata.questionIds = data.questions.map((item) => item.id);
+  ndata.number = data.groupNumber;
+  return ndata;
 }
 
 // 修改分组
 export function markGroupUpdate(
-  params: MarkGroupUpdateParams
+  params: MarkGroupUpdateParams,
+  editType: 'update' | 'add'
+): Promise<boolean> {
+  if (editType === 'update') {
+    return axios.post(
+      '/api/admin/exam/group/update',
+      transformGroupUpdateData(params)
+    );
+  }
+  return axios.post(
+    '/api/admin/exam/group/insert',
+    transformGroupUpdateData(params)
+  );
+}
+
+// 修改分组状态
+export function markGroupUpdateStatus(
+  params: MarkGroupUpdateStatusParams
+): Promise<boolean> {
+  return axios.post('/api/admin/exam/group/changeStatus', {}, { params });
+}
+
+// 删除分组
+export function markGroupDelete(
+  params: MarkGroupResetParams
 ): Promise<boolean> {
-  return axios.post('/api/mark/group/update', params);
+  return axios.post('/api/admin/exam/group/delete', {}, { params });
+}
+// 重置分组
+export function markGroupReset(params: MarkGroupResetParams): Promise<boolean> {
+  return axios.post('/api/admin/exam/group/reset', {}, { params });
+}
+// 回收分组任务
+export function markGroupTaskRecycle(
+  params: MarkGroupResetParams
+): Promise<boolean> {
+  return axios.post('/api/admin/exam/group/release', {}, { params });
 }
 
 // 设置试评数量
 export function markGroupSetTrialCount({
   subjectCode,
-  count,
+  trialCount,
 }: {
   subjectCode: string;
-  count: number;
+  trialCount: number;
 }): Promise<boolean> {
   return axios.post(
-    '/api/mark/group/set-trial-count',
+    '/api/admin/exam/group/updateTrialCount',
     {},
-    { params: { subjectCode, count } }
+    { params: { subjectCode, trialCount } }
   );
 }
 
 // 数据校对
 export function markGroupDataCheck(subjectCode: string) {
   return axios.post(
-    '/api/mark/group/data-check',
+    '/api/admin/exam/group/checkCount',
     {},
     { params: { subjectCode } }
   );
 }
 
 // 关闭分组
-export function markGroupClose(ids: number) {
-  return axios.post('/api/mark/group/close', {}, { params: { ids } });
+export function markGroupClose(subjectCode: string, groupNumbers: number[]) {
+  return axios.post(
+    '/api/admin/exam/group/close',
+    {},
+    { params: { subjectCode, groupNumbers } }
+  );
 }
 
 // 评卷进度统计

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

@@ -2,6 +2,7 @@ import {
   CombineScoreStrategy,
   ArbitrationType,
   ThreeEvaluationRule,
+  GroupStatus,
 } from '@/constants/enumerate';
 import { PageResult, PageParams, CoverArea } from './common';
 
@@ -273,6 +274,7 @@ export type MarkMarkerListPageParam = PageParams<MarkMarkerListFilter>;
 // 分组管理列表:分组序号	大题号	大题名称	步骤分	包含选做题	评卷员人数	任务总数	完成总数	剩余总数	正在评卷	进度	评卷区设置	状态
 export interface MarkGroupItem {
   id: number;
+  subjectCode: string;
   // 分组序号
   groupNumber: number;
   // 大题号
@@ -315,6 +317,8 @@ export interface MarkGroupUpdateParams {
   groupNumber: number;
   // 名称
   groupName: string;
+  // 重置
+  reset: boolean;
   // 题目
   questions: Array<{
     // 大题号
@@ -340,6 +344,25 @@ export interface MarkGroupUpdateParams {
   scorePolicy?: CombineScoreStrategy;
   // 三评规则
   thirdPolicy?: ThreeEvaluationRule;
+  // 授权码
+  deleteCode?: string;
+}
+
+export interface MarkGroupResetParams {
+  // 科目
+  subjectCode: string;
+  // 分组序号
+  number: number;
+  // 授权码
+  deleteCode?: string;
+}
+export interface MarkGroupUpdateStatusParams {
+  // 科目
+  subjectCode: string;
+  // 分组序号
+  number: number;
+  // 状态:TRIAL,FORMAL,FINISH
+  status: GroupStatus;
 }
 
 // 评卷进度

+ 8 - 0
src/constants/enumerate.ts

@@ -154,3 +154,11 @@ export const ISSUE_PAPER_STATUS = {
   BACK: '已处理',
 };
 export type IssuePaperStatus = keyof typeof ISSUE_PAPER_STATUS;
+
+// 分组状态 TRIAL:试评,FORMAL:正式,FINISH:已完成
+export const GROUP_STATUS = {
+  TRIAL: '试评',
+  FORMAL: '正式',
+  FINISH: '已完成',
+};
+export type GroupStatus = keyof typeof GROUP_STATUS;

+ 1 - 1
src/router/routes/modules/base.ts

@@ -242,7 +242,7 @@ const BASE: AppRouteRecordRaw = {
         },
         {
           // 编辑分组
-          path: '/mark-manage/group/:subjectCode/edit/:groupId?',
+          path: '/mark-manage/group/:subjectCode/edit/:groupNumber?',
           name: 'GroupEdit',
           component: () => import('@/views/mark/group-edit/GroupEdit.vue'),
           meta: {

+ 163 - 11
src/views/mark/GroupManage.vue

@@ -10,8 +10,12 @@
       <el-space wrap>
         <el-button type="primary" @click="toPage(1)">查询</el-button>
         <el-button @click="onAdd">新增</el-button>
-        <el-button @click="onDataCheck">数量校对</el-button>
-        <el-button @click="onClose">关闭</el-button>
+        <el-button :disabled="checkLoading" @click="onDataCheck"
+          >数量校对</el-button
+        >
+        <el-button :disabled="closeLoading" @click="onBatchClose"
+          >关闭</el-button
+        >
         <el-button @click="onSetTrialCount">设置试评数量</el-button>
       </el-space>
     </div>
@@ -72,6 +76,40 @@
           >
             修改
           </el-button>
+          <el-button
+            type="primary"
+            size="small"
+            link
+            :disabled="startMarkLoading"
+            @click="onStartMark(scope.row)"
+          >
+            开始正评
+          </el-button>
+          <el-button
+            type="danger"
+            size="small"
+            link
+            :disabled="releaseLoading"
+            @click="onRelease(scope.row)"
+            >回收</el-button
+          >
+          <el-button
+            type="danger"
+            size="small"
+            link
+            :disabled="closeLoading"
+            @click="onClose(scope.row)"
+            >结束</el-button
+          >
+          <el-button
+            type="danger"
+            size="small"
+            link
+            :disabled="deleteLoading"
+            @click="onDelete(scope.row)"
+          >
+            删除
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -98,9 +136,13 @@
     getMarkGroupList,
     markGroupDataCheck,
     markGroupClose,
+    markGroupDelete,
+    markGroupTaskRecycle,
+    markGroupUpdateStatus,
   } from '@/api/mark';
   import { MarkGroupItem, MarkGroupListFilter } from '@/api/types/mark';
   import useTable from '@/hooks/table';
+  import useLoading from '@/hooks/loading';
   import { modalConfirm } from '@/utils/ui';
 
   import SetTrialCountDialog from './components/SetTrialCountDialog.vue';
@@ -112,6 +154,7 @@
   const searchModel = reactive<MarkGroupListFilter>({
     subjectCode: null,
   });
+  // const curRow = ref<MarkGroupItem>();
 
   const setTrialCountDialogRef =
     ref<InstanceType<typeof SetTrialCountDialog>>();
@@ -127,9 +170,9 @@
     handleSelectionChange,
   } = useTable<MarkGroupItem>(getMarkGroupList, searchModel, false);
 
-  // 计算选中的分组ID列表
-  const selectedGroupIds = computed(() => {
-    return selectedRows.value.map((row) => row.id);
+  // 计算选中的分组列表
+  const selectedGroupNumbers = computed(() => {
+    return selectedRows.value.map((row) => row.groupNumber);
   });
 
   // 新增分组
@@ -144,24 +187,36 @@
     // TODO: 实现修改分组的逻辑,功能空着留着后面做
   }
 
+  // 设置试评数量
+  function onSetTrialCount() {
+    setTrialCountDialogRef.value?.open();
+  }
+
   // 数量校对
+  const { loading: checkLoading, setLoading: setCheckLoading } = useLoading();
   async function onDataCheck() {
+    if (checkLoading.value) return;
     if (!searchModel.subjectCode) {
       ElMessage.warning('请选择科目');
       return;
     }
 
+    setCheckLoading(true);
     try {
       await markGroupDataCheck(searchModel.subjectCode);
       ElMessage.success('数量校对成功');
       getList();
     } catch (error) {
       console.error('数量校对失败:', error);
+    } finally {
+      setCheckLoading(false);
     }
   }
 
-  // 关闭分组
-  async function onClose() {
+  // 批量关闭分组
+  const { loading: closeLoading, setLoading: setCloseLoading } = useLoading();
+  async function onBatchClose() {
+    if (closeLoading.value) return;
     if (!selectedRows.value.length) {
       ElMessage.warning('请选择分组');
       return;
@@ -173,17 +228,114 @@
     ).catch(() => false);
     if (!confirm) return;
 
+    setCloseLoading(true);
     try {
-      await markGroupClose(selectedGroupIds.value);
+      await markGroupClose(
+        searchModel.subjectCode!,
+        selectedGroupNumbers.value
+      );
       ElMessage.success('批量关闭成功');
       getList();
     } catch (error) {
       console.error('批量关闭失败:', error);
+    } finally {
+      setCloseLoading(false);
     }
   }
 
-  // 设置试评数量
-  function onSetTrialCount() {
-    setTrialCountDialogRef.value?.open();
+  // 关闭分组
+  async function onClose(row: MarkGroupItem) {
+    if (closeLoading.value) return;
+    const confirm = await modalConfirm(
+      `确认关闭分组 ${row.groupNumber} 的评卷吗?`,
+      '提示 '
+    ).catch(() => false);
+    if (!confirm) return;
+
+    setCloseLoading(true);
+    try {
+      await markGroupClose(searchModel.subjectCode!, [row.groupNumber!]);
+      ElMessage.success('关闭成功');
+      getList();
+    } catch (error) {
+      console.error('关闭失败:', error);
+    } finally {
+      setCloseLoading(false);
+    }
+  }
+
+  // 回收分组任务
+  const { loading: releaseLoading, setLoading: setReleaseLoading } =
+    useLoading();
+  // 回收分组任务
+  async function onRelease(row: MarkGroupItem) {
+    if (releaseLoading.value) return;
+    const confirm = await modalConfirm(
+      `确认回收分组 ${row.groupNumber} 的任务吗?`,
+      '提示 '
+    ).catch(() => false);
+    if (!confirm) return;
+
+    setReleaseLoading(true);
+    try {
+      await markGroupTaskRecycle({
+        subjectCode: row.subjectCode!,
+        number: row.groupNumber!,
+      });
+      ElMessage.success('回收成功');
+      getList();
+    } catch (error) {
+      console.error('回收失败:', error);
+    } finally {
+      setReleaseLoading(false);
+    }
+  }
+
+  // 删除
+  const { loading: deleteLoading, setLoading: setDeleteLoading } = useLoading();
+  async function onDelete(row: MarkGroupItem) {
+    if (deleteLoading.value) return;
+
+    setDeleteLoading(true);
+    const params = {
+      subjectCode: row.subjectCode!,
+      number: row.groupNumber!,
+    };
+    let res = true;
+    await markGroupDelete(params).catch(() => {
+      res = false;
+    });
+    setDeleteLoading(false);
+    if (!res) return;
+
+    ElMessage.success(`删除成功!`);
+    getList();
+  }
+
+  // 开始正评
+  const { loading: startMarkLoading, setLoading: setStartMarkLoading } =
+    useLoading();
+  async function onStartMark(row: MarkGroupItem) {
+    if (startMarkLoading.value) return;
+    const confirm = await modalConfirm(
+      `确认开始分组 ${row.groupNumber} 的正评吗?`,
+      '提示 '
+    ).catch(() => false);
+    if (!confirm) return;
+
+    setStartMarkLoading(true);
+    try {
+      await markGroupUpdateStatus({
+        subjectCode: row.subjectCode!,
+        number: row.groupNumber!,
+        status: 'FORMAL',
+      });
+      ElMessage.success('开始正评成功');
+      getList();
+    } catch (error) {
+      console.error('开始正评失败:', error);
+    } finally {
+      setStartMarkLoading(false);
+    }
   }
 </script>

+ 7 - 5
src/views/mark/components/SetTrialCountDialog.vue

@@ -16,9 +16,9 @@
       :rules="rules"
       label-width="100px"
     >
-      <el-form-item label="试评数量" prop="count">
+      <el-form-item label="试评数量" prop="trialCount">
         <el-input-number
-          v-model="formModel.count"
+          v-model="formModel.trialCount"
           placeholder="请输入试评数量"
           :min="0"
           :max="9999"
@@ -66,18 +66,20 @@
 
   interface FormModel {
     subjectCode: number;
-    count: number;
+    trialCount: number;
   }
 
   const initialFormState: FormModel = {
     subjectCode: 0,
-    count: 0,
+    trialCount: 0,
   };
 
   const formModel = reactive<FormModel>({ ...initialFormState });
 
   const rules: FormRules<keyof FormModel> = {
-    count: [{ required: true, message: '请输入试评数量', trigger: 'change' }],
+    trialCount: [
+      { required: true, message: '请输入试评数量', trigger: 'change' },
+    ],
   };
 
   const handleClose = () => {

+ 51 - 9
src/views/mark/group-edit/GroupEdit.vue

@@ -34,6 +34,7 @@
     <el-button
       v-if="currentStep === 1 && isReset"
       type="danger"
+      :loading="deleteLoading"
       @click="onDelete"
     >
       删除
@@ -54,20 +55,31 @@
       保存
     </el-button>
   </div>
+
+  <!-- 授权码 -->
+  <CodeConfirmDialog
+    ref="codeConfirmDialogRef"
+    :progress="curMarkerGroupProgress"
+    @modified="onCodeConfirm"
+  />
 </template>
 
 <script setup lang="ts">
   import { ref, computed, reactive, onMounted } from 'vue';
   import { useRoute, useRouter } from 'vue-router';
   import { ElMessage } from 'element-plus';
-  import { getMarkGroupInfo, markGroupUpdate } from '@/api/mark';
+  import {
+    getMarkGroupInfo,
+    markGroupUpdate,
+    markGroupDelete,
+  } from '@/api/mark';
   import type { MarkGroupUpdateParams } from '@/api/types/mark';
   import useLoading from '@/hooks/loading';
-
   import { objModifyAssign } from '@/utils/utils';
 
   import GroupRuleForm from './GroupRuleForm.vue';
   import GroupQuestionSelect from './GroupQuestionSelect.vue';
+  import CodeConfirmDialog from '../components/CodeConfirmDialog.vue';
 
   defineOptions({
     name: 'GroupEdit',
@@ -91,9 +103,10 @@
   // 规则表单数据
   const getInitialRuleFormData = (): MarkGroupUpdateParams => {
     return {
-      id: route.params?.groupId,
+      id: undefined,
       subjectCode: route.params.subjectCode,
-      groupNumber: 0,
+      groupNumber: Number(route.params.groupNumber),
+      reset: false,
       groupName: '',
       picList: [],
       questions: [],
@@ -103,6 +116,7 @@
       arbitrateThreshold: undefined,
       scorePolicy: 'AVG_SCORE',
       thirdPolicy: 'CLOSE',
+      deleteCode: undefined,
     };
   };
   const ruleFormData = reactive(getInitialRuleFormData());
@@ -131,11 +145,32 @@
   const onReset = () => {
     editType.value = 'reset';
     currentStep.value = 0;
+    ruleFormData.reset = true;
   };
 
-  const onDelete = async () => {
-    // TODO: 删除分组
+  const { loading: deleteLoading, setLoading: setDeleteLoading } = useLoading();
+  const codeConfirmDialogRef = ref<InstanceType<typeof CodeConfirmDialog>>();
+  const curMarkerGroupProgress = ref(0);
+  const onDelete = () => {
+    codeConfirmDialogRef.value?.open();
   };
+  async function onCodeConfirm(deleteCode: string) {
+    setDeleteLoading(true);
+    const params = {
+      subjectCode: ruleFormData.subjectCode!,
+      number: ruleFormData.groupNumber!,
+      deleteCode,
+    };
+    let res = true;
+    await markGroupDelete(params).catch(() => {
+      res = false;
+    });
+    setDeleteLoading(false);
+    if (!res) return;
+
+    ElMessage.success(`删除成功!`);
+    router.go(-1);
+  }
 
   // 提交
   const { loading, setLoading } = useLoading();
@@ -151,7 +186,10 @@
     };
 
     let res = true;
-    await markGroupUpdate(params).catch(() => {
+    await markGroupUpdate(
+      params,
+      editType.value === 'add' ? 'add' : 'update'
+    ).catch(() => {
       res = false;
     });
     setLoading(false);
@@ -162,9 +200,13 @@
 
   /* init modal */
   async function initData() {
-    if (route.params?.groupId) {
+    if (route.params?.groupNumber) {
       // 编辑模式
-      const res = await getMarkGroupInfo(route.params.groupId);
+      const res = await getMarkGroupInfo({
+        subjectCode: route.params.subjectCode,
+        groupNumber: Number(route.params.groupNumber),
+      });
+      curMarkerGroupProgress.value = res.percent;
       objModifyAssign(ruleFormData, res);
       editType.value = 'edit';
       currentStep.value = 1;