瀏覽代碼

feat: 考试打回/问题类型编辑

zhangjie 1 周之前
父節點
當前提交
3a78f8fbf6

+ 5 - 1
src/api/issue-paper.ts

@@ -26,8 +26,12 @@ export function getIssuePaperTypeList(
 }
 }
 
 
 // 新增/修改问题卷类型
 // 新增/修改问题卷类型
-export function addIssuePaperType(
+export function updateIssuePaperType(
   params: IssuePaperTypeUpdateParam
   params: IssuePaperTypeUpdateParam
 ): Promise<any> {
 ): Promise<any> {
   return axios.post('/api/score/type/add', params);
   return axios.post('/api/score/type/add', params);
 }
 }
+// 删除问题卷类型
+export function deleteIssuePaperType(id: number): Promise<any> {
+  return axios.post('/api/score/type/delete', {}, { params: { id } });
+}

+ 4 - 0
src/api/reject.ts

@@ -36,3 +36,7 @@ export function getRejectTypeList(examId: number): Promise<RejectTypeItem[]> {
 export function updateRejectType(params: RejectTypeUpdateParam): Promise<any> {
 export function updateRejectType(params: RejectTypeUpdateParam): Promise<any> {
   return axios.post('/api/student/list', params);
   return axios.post('/api/student/list', params);
 }
 }
+// 删除打回类型
+export function deleteRejectType(id: number): Promise<any> {
+  return axios.post('/api/student/list', {}, { params: { id } });
+}

+ 3 - 0
src/assets/style/base.less

@@ -264,6 +264,9 @@
 .align-right {
 .align-right {
   text-align: right;
   text-align: right;
 }
 }
+.mt-10 {
+  margin-top: 10px;
+}
 .mr-10 {
 .mr-10 {
   margin-right: 10px;
   margin-right: 10px;
 }
 }

+ 38 - 14
src/views/exam/ExamManage.vue

@@ -45,7 +45,7 @@
       <el-table-column property="id" label="ID" width="80" />
       <el-table-column property="id" label="ID" width="80" />
       <el-table-column property="name" label="考试名称" min-width="200">
       <el-table-column property="name" label="考试名称" min-width="200">
         <template #default="scope">
         <template #default="scope">
-          <el-button type="pirmary" link @click="onViewDetail(scope.row)">{{
+          <el-button type="primary" link @click="onViewDetail(scope.row)">{{
             scope.row.name
             scope.row.name
           }}</el-button>
           }}</el-button>
         </template>
         </template>
@@ -71,19 +71,19 @@
         </template>
         </template>
       </el-table-column>
       </el-table-column>
 
 
-      <el-table-column label="操作" width="200" fixed="right">
+      <el-table-column label="操作" width="300" fixed="right">
         <template #default="scope">
         <template #default="scope">
-          <el-button size="small" link @click="onViewDetail(scope.row)">
+          <el-button type="primary" link @click="onViewDetail(scope.row)">
             详情
             详情
           </el-button>
           </el-button>
-          <el-button size="small" link @click="onEdit(scope.row)">
+          <el-button type="primary" link @click="onEdit(scope.row)">
             编辑
             编辑
           </el-button>
           </el-button>
-          <el-button size="small" link @click="onCopy(scope.row)">
-            复制
+          <el-button type="primary" link @click="onEditIssueType(scope.row)">
+            问题卷分类
           </el-button>
           </el-button>
-          <el-button size="small" link @click="onAnalysis(scope.row)">
-            分析
+          <el-button type="primary" link @click="onEditRejectType(scope.row)">
+            打回卷分类
           </el-button>
           </el-button>
         </template>
         </template>
       </el-table-column>
       </el-table-column>
@@ -100,6 +100,12 @@
 
 
   <!-- 统计信息弹窗 -->
   <!-- 统计信息弹窗 -->
   <ExamStatDialog ref="statDialogRef" :exam-id="currentExamId" />
   <ExamStatDialog ref="statDialogRef" :exam-id="currentExamId" />
+
+  <!-- 问题卷分类管理弹窗 -->
+  <IssuePaperTypeDialog ref="issueTypeDialogRef" :exam-id="currentExamId" />
+
+  <!-- 打回卷分类管理弹窗 -->
+  <RejectTypeDialog ref="rejectTypeDialogRef" :exam-id="currentExamId" />
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
@@ -109,6 +115,8 @@
   import { ExamQueryItem, ExamListFilter } from '@/api/types/exam';
   import { ExamQueryItem, ExamListFilter } from '@/api/types/exam';
   import useTable from '@/hooks/table';
   import useTable from '@/hooks/table';
   import ExamStatDialog from './ExamStatDialog.vue';
   import ExamStatDialog from './ExamStatDialog.vue';
+  import IssuePaperTypeDialog from './IssuePaperTypeDialog.vue';
+  import RejectTypeDialog from './RejectTypeDialog.vue';
 
 
   defineOptions({
   defineOptions({
     name: 'ExamManage',
     name: 'ExamManage',
@@ -125,6 +133,17 @@
   const { dataList, pagination, loading, toPage, pageSizeChange } =
   const { dataList, pagination, loading, toPage, pageSizeChange } =
     useTable<ExamQueryItem>(getExamList, searchModel, false);
     useTable<ExamQueryItem>(getExamList, searchModel, false);
 
 
+  dataList.value = [
+    {
+      id: 1,
+      name: '考试名称1',
+      type: 1,
+      examDate: '2023-01-01',
+      forceMark: true,
+      status: 1,
+    },
+  ];
+
   function getTypeLabel(type: number): string {
   function getTypeLabel(type: number): string {
     const typeMap: Record<number, string> = {
     const typeMap: Record<number, string> = {
       1: '类型1',
       1: '类型1',
@@ -138,6 +157,11 @@
   const currentExamId = ref(0);
   const currentExamId = ref(0);
   // 详情统计弹窗
   // 详情统计弹窗
   const statDialogRef = ref();
   const statDialogRef = ref();
+  // 问题卷分类弹窗
+  const issueTypeDialogRef = ref();
+  // 打回卷分类弹窗
+  const rejectTypeDialogRef = ref();
+
   function onViewDetail(row: ExamQueryItem) {
   function onViewDetail(row: ExamQueryItem) {
     currentExamId.value = row.id;
     currentExamId.value = row.id;
     statDialogRef.value?.open();
     statDialogRef.value?.open();
@@ -154,13 +178,13 @@
     });
     });
   }
   }
 
 
-  function onCopy(row: ExamQueryItem) {
-    // TODO: 实现复制功能
-    console.log('复制考试:', row);
+  function onEditIssueType(row: ExamQueryItem) {
+    currentExamId.value = row.id;
+    issueTypeDialogRef.value?.open();
   }
   }
 
 
-  function onAnalysis(row: ExamQueryItem) {
-    // TODO: 实现分析功能
-    console.log('分析考试:', row);
+  function onEditRejectType(row: ExamQueryItem) {
+    currentExamId.value = row.id;
+    rejectTypeDialogRef.value?.open();
   }
   }
 </script>
 </script>

+ 4 - 2
src/views/exam/ExamStatDialog.vue

@@ -21,7 +21,9 @@
           </template>
           </template>
         </el-table-column>
         </el-table-column>
       </el-table>
       </el-table>
-      <el-button @click="onViewReport">验收报告</el-button>
+      <div class="mt-10">
+        <el-button @click="onViewReport">验收报告</el-button>
+      </div>
     </div>
     </div>
     <template #footer>
     <template #footer>
       <span class="dialog-footer">
       <span class="dialog-footer">
@@ -57,7 +59,7 @@
 
 
   const { loading, setLoading } = useLoading();
   const { loading, setLoading } = useLoading();
 
 
-  const router = useRouter;
+  const router = useRouter();
 
 
   const statInfo = ref<ExamStatDetailInfo>({
   const statInfo = ref<ExamStatDetailInfo>({
     studentCount: 0,
     studentCount: 0,

+ 168 - 0
src/views/exam/IssuePaperTypeDialog.vue

@@ -0,0 +1,168 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    title="问题卷分类管理"
+    width="800px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    top="10vh"
+    append-to-body
+    @close="handleClose"
+    @open="modalBeforeOpen"
+  >
+    <div class="issue-type-content">
+      <!-- 操作按钮 -->
+      <div class="action-bar">
+        <el-button type="primary" @click="onAdd">新增</el-button>
+      </div>
+
+      <!-- 表格 -->
+      <el-table :data="typeList" :loading="loading" class="type-table">
+        <el-table-column property="id" label="编号" width="80" />
+        <el-table-column property="name" label="分类名称" />
+        <el-table-column property="type" label="类型" />
+        <el-table-column label="操作" width="120">
+          <template #default="scope">
+            <el-button size="small" link @click="onEdit(scope.row)">
+              编辑
+            </el-button>
+            <el-button
+              size="small"
+              link
+              type="danger"
+              @click="onDelete(scope.row)"
+            >
+              删除
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <template #footer> </template>
+  </el-dialog>
+
+  <!-- 新增/编辑问题卷分类弹窗 -->
+  <ModifyIssuePaperType
+    ref="modifyTypeRef"
+    :row-data="curRow"
+    :exam-id="examId"
+    @modified="getTypeList"
+  />
+</template>
+
+<script setup lang="ts">
+  import { ref } from 'vue';
+  import { ElMessage, ElMessageBox } from 'element-plus';
+  import type { IssuePaperTypeItem } from '@/api/types/issue-paper';
+  import {
+    getIssuePaperTypeList,
+    deleteIssuePaperType,
+  } from '@/api/issue-paper';
+  import useModal from '@/hooks/modal';
+  import useLoading from '@/hooks/loading';
+  import { modalConfirm } from '@/utils/ui';
+
+  import ModifyIssuePaperType from './ModifyIssuePaperType.vue';
+
+  defineOptions({
+    name: 'IssuePaperTypeDialog',
+  });
+
+  interface Props {
+    examId: number;
+  }
+
+  const props = withDefaults(defineProps<Props>(), {
+    examId: 0,
+  });
+
+  /* modal */
+  const { visible, open, close } = useModal();
+  defineExpose({ open, close });
+
+  const { loading, setLoading } = useLoading();
+
+  // 问题卷分类列表
+  const typeList = ref<IssuePaperTypeItem[]>([]);
+
+  // 获取问题卷分类列表
+  async function getTypeList() {
+    if (!props.examId) return;
+
+    setLoading(true);
+    try {
+      const result = await getIssuePaperTypeList(props.examId);
+      typeList.value = result;
+    } catch (error) {
+      console.error('获取问题卷分类列表失败:', error);
+      ElMessage.error('获取问题卷分类列表失败');
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  // 新增/编辑相关
+  const curRow = ref({} as IssuePaperTypeItem);
+  const modifyTypeRef = ref();
+
+  function onAdd() {
+    curRow.value = {} as IssuePaperTypeItem;
+    modifyTypeRef.value?.open();
+  }
+
+  function onEdit(row: IssuePaperTypeItem) {
+    curRow.value = row;
+    modifyTypeRef.value?.open();
+  }
+
+  // 删除
+  async function onDelete(row: IssuePaperTypeItem) {
+    const confirm = await modalConfirm(
+      `确定要删除分类"${row.name}"吗?`,
+      '删除确认'
+    ).catch(() => false);
+    if (!confirm) return;
+
+    try {
+      setLoading(true);
+      await deleteIssuePaperType(row.id);
+      ElMessage.success('删除成功');
+      getTypeList();
+    } catch (error) {
+      console.log('删除失败', error);
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  const handleClose = () => {
+    // 清理数据
+  };
+
+  /* init modal */
+  function modalBeforeOpen() {
+    getTypeList();
+  }
+</script>
+
+<style scoped>
+  .issue-type-content {
+    min-height: 400px;
+  }
+
+  .action-bar {
+    margin-bottom: 16px;
+    display: flex;
+    justify-content: flex-start;
+  }
+
+  .type-table {
+    width: 100%;
+  }
+
+  .dialog-footer {
+    display: flex;
+    justify-content: flex-end;
+  }
+</style>

+ 122 - 0
src/views/exam/ModifyIssuePaperType.vue

@@ -0,0 +1,122 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    :title="title"
+    width="500px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    top="10vh"
+    append-to-body
+    @close="handleClose"
+    @open="modalBeforeOpen"
+  >
+    <el-form
+      ref="formRef"
+      :model="formModel"
+      :rules="rules"
+      label-width="100px"
+    >
+      <el-form-item label="分类名称" prop="name">
+        <el-input
+          v-model="formModel.name"
+          placeholder="请输入分类名称"
+          maxlength="50"
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="close">取消</el-button>
+      <el-button type="primary" :loading="loading" @click="confirm">
+        保存
+      </el-button>
+    </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 type {
+    IssuePaperTypeItem,
+    IssuePaperTypeUpdateParam,
+  } from '@/api/types/issue-paper';
+  import useModal from '@/hooks/modal';
+  import useLoading from '@/hooks/loading';
+  import { objAssign, objModifyAssign } from '@/utils/utils';
+  import { updateIssuePaperType } from '@/api/issue-paper';
+
+  defineOptions({
+    name: 'ModifyIssuePaperType',
+  });
+
+  /* modal */
+  const { visible, open, close } = useModal();
+  defineExpose({ open, close });
+
+  interface Props {
+    rowData: IssuePaperTypeItem;
+    examId: number;
+  }
+
+  const props = withDefaults(defineProps<Props>(), {
+    rowData: {} as IssuePaperTypeItem,
+    examId: 0,
+  });
+  const emit = defineEmits(['modified']);
+
+  const isEdit = computed(() => !!props.rowData?.id);
+  const title = computed(() => `${isEdit.value ? '编辑' : '新增'}问题卷分类`);
+
+  const formRef = ref<FormInstance>();
+  const initialFormState: Partial<IssuePaperTypeUpdateParam> = {
+    name: '',
+  };
+
+  const formModel = reactive({ ...initialFormState });
+
+  const rules: FormRules<keyof IssuePaperTypeUpdateParam> = {
+    name: [{ required: true, message: '请输入分类名称', trigger: 'change' }],
+  };
+
+  const handleClose = () => {
+    formRef.value?.resetFields();
+  };
+
+  /* confirm */
+  const { loading, setLoading } = useLoading();
+  async function confirm() {
+    const valid = await formRef.value?.validate().catch(() => false);
+    if (!valid) return;
+
+    setLoading(true);
+    try {
+      const data = objAssign(formModel, {}) as IssuePaperTypeUpdateParam;
+      data.examId = props.examId;
+
+      if (isEdit.value) {
+        data.id = props.rowData.id;
+      }
+
+      await updateIssuePaperType(data);
+      ElMessage.success(`${isEdit.value ? '编辑' : '新增'}成功!`);
+      emit('modified', data);
+      close();
+    } catch (error) {
+      console.error('保存失败:', error);
+      ElMessage.error('保存失败');
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  /* init modal */
+  function modalBeforeOpen() {
+    if (props.rowData.id) {
+      // 编辑模式,映射字段
+      objModifyAssign(formModel, props.rowData);
+    } else {
+      objModifyAssign(formModel, initialFormState);
+    }
+  }
+</script>

+ 123 - 0
src/views/exam/ModifyRejectType.vue

@@ -0,0 +1,123 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    :title="title"
+    width="500px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    top="10vh"
+    append-to-body
+    @close="handleClose"
+    @open="modalBeforeOpen"
+  >
+    <el-form
+      ref="formRef"
+      :model="formModel"
+      :rules="rules"
+      label-width="100px"
+    >
+      <el-form-item label="分类名称" prop="name">
+        <el-input
+          v-model="formModel.name"
+          placeholder="请输入分类名称"
+          maxlength="50"
+        />
+      </el-form-item>
+    </el-form>
+
+    <template #footer>
+      <el-button @click="close">取消</el-button>
+      <el-button type="primary" :loading="loading" @click="confirm">
+        保存
+      </el-button>
+    </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 type {
+    RejectTypeItem,
+    RejectTypeUpdateParam,
+  } from '@/api/types/reject';
+  import useModal from '@/hooks/modal';
+  import useLoading from '@/hooks/loading';
+  import { objAssign, objModifyAssign } from '@/utils/utils';
+  import { updateRejectType } from '@/api/reject';
+
+  defineOptions({
+    name: 'ModifyRejectType',
+  });
+
+  /* modal */
+  const { visible, open, close } = useModal();
+  defineExpose({ open, close });
+
+  interface Props {
+    rowData: RejectTypeItem;
+    examId: number;
+  }
+
+  const props = withDefaults(defineProps<Props>(), {
+    rowData: {} as RejectTypeItem,
+    examId: 0,
+  });
+  const emit = defineEmits(['modified']);
+
+  const isEdit = computed(() => !!props.rowData?.id);
+  const title = computed(() => `${isEdit.value ? '编辑' : '新增'}打回卷分类`);
+
+  const formRef = ref<FormInstance>();
+  const initialFormState: Partial<RejectTypeUpdateParam> = {
+    name: '',
+  };
+
+  const formModel = reactive({ ...initialFormState });
+
+  const rules: FormRules<keyof RejectTypeUpdateParam> = {
+    name: [{ required: true, message: '请输入分类名称', trigger: 'blur' }],
+  };
+
+  const handleClose = () => {
+    formRef.value?.resetFields();
+  };
+
+  /* confirm */
+  const { loading, setLoading } = useLoading();
+  async function confirm() {
+    const valid = await formRef.value?.validate().catch(() => false);
+    if (!valid) return;
+
+    setLoading(true);
+    try {
+      const data = objAssign(formModel, {}) as RejectTypeUpdateParam;
+      data.examId = props.examId;
+
+      if (isEdit.value) {
+        data.id = props.rowData.id;
+      }
+
+      await updateRejectType(data);
+      ElMessage.success(`${isEdit.value ? '编辑' : '新增'}成功!`);
+      emit('modified', data);
+      close();
+    } catch (error) {
+      console.error('保存失败:', error);
+      ElMessage.error('保存失败');
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  /* init modal */
+  function modalBeforeOpen() {
+    if (props.rowData.id) {
+      // 编辑模式,映射字段
+      objModifyAssign(formModel, props.rowData);
+    } else {
+      objModifyAssign(formModel, initialFormState);
+    }
+  }
+</script>

+ 169 - 0
src/views/exam/RejectTypeDialog.vue

@@ -0,0 +1,169 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    title="打回卷分类管理"
+    width="800px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    top="10vh"
+    append-to-body
+    @close="handleClose"
+    @open="modalBeforeOpen"
+  >
+    <div class="reject-type-content">
+      <!-- 操作按钮 -->
+      <div class="action-bar">
+        <el-button type="primary" @click="onAdd">新增</el-button>
+      </div>
+
+      <!-- 表格 -->
+      <el-table :data="typeList" :loading="loading" class="type-table">
+        <el-table-column property="id" label="编号" width="80" />
+        <el-table-column property="name" label="分类名称" />
+        <el-table-column property="type" label="类型" />
+        <el-table-column label="操作" width="120">
+          <template #default="scope">
+            <el-button size="small" link @click="onEdit(scope.row)">
+              编辑
+            </el-button>
+            <el-button
+              size="small"
+              link
+              type="danger"
+              @click="onDelete(scope.row)"
+            >
+              删除
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="close">关闭</el-button>
+      </span>
+    </template>
+  </el-dialog>
+
+  <!-- 新增/编辑打回卷分类弹窗 -->
+  <ModifyRejectType
+    ref="modifyTypeRef"
+    :row-data="curRow"
+    :exam-id="examId"
+    @modified="getTypeList"
+  />
+</template>
+
+<script setup lang="ts">
+  import { ref } from 'vue';
+  import { ElMessage, ElMessageBox } from 'element-plus';
+  import type { RejectTypeItem } from '@/api/types/reject';
+  import { getRejectTypeList, deleteRejectType } from '@/api/reject';
+  import useModal from '@/hooks/modal';
+  import useLoading from '@/hooks/loading';
+  import { modalConfirm } from '@/utils/ui';
+
+  import ModifyRejectType from './ModifyRejectType.vue';
+
+  defineOptions({
+    name: 'RejectTypeDialog',
+  });
+
+  interface Props {
+    examId: number;
+  }
+
+  const props = withDefaults(defineProps<Props>(), {
+    examId: 0,
+  });
+
+  /* modal */
+  const { visible, open, close } = useModal();
+  defineExpose({ open, close });
+
+  const { loading, setLoading } = useLoading();
+
+  // 打回卷分类列表
+  const typeList = ref<RejectTypeItem[]>([]);
+
+  // 获取打回卷分类列表
+  async function getTypeList() {
+    if (!props.examId) return;
+
+    setLoading(true);
+    try {
+      const result = await getRejectTypeList(props.examId);
+      typeList.value = result;
+    } catch (error) {
+      console.error('获取打回卷分类列表失败:', error);
+      ElMessage.error('获取打回卷分类列表失败');
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  // 新增/编辑相关
+  const curRow = ref({} as RejectTypeItem);
+  const modifyTypeRef = ref();
+
+  function onAdd() {
+    curRow.value = {} as RejectTypeItem;
+    modifyTypeRef.value?.open();
+  }
+
+  function onEdit(row: RejectTypeItem) {
+    curRow.value = row;
+    modifyTypeRef.value?.open();
+  }
+
+  // 删除
+  async function onDelete(row: RejectTypeItem) {
+    const confirm = await modalConfirm(
+      `确定要删除分类"${row.name}"吗?`,
+      '删除确认'
+    ).catch(() => false);
+    if (!confirm) return;
+
+    try {
+      setLoading(true);
+      await deleteRejectType(row.id);
+      ElMessage.success('删除成功');
+      getTypeList();
+    } catch (error) {
+      console.log('删除失败:', error);
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  const handleClose = () => {
+    // 清理数据
+  };
+
+  /* init modal */
+  function modalBeforeOpen() {
+    getTypeList();
+  }
+</script>
+
+<style scoped>
+  .reject-type-content {
+    min-height: 400px;
+  }
+
+  .action-bar {
+    margin-bottom: 16px;
+    display: flex;
+    justify-content: flex-start;
+  }
+
+  .type-table {
+    width: 100%;
+  }
+
+  .dialog-footer {
+    display: flex;
+    justify-content: flex-end;
+  }
+</style>