zhangjie 1 tydzień temu
rodzic
commit
e20cd35b2b

+ 5 - 0
src/api/exam.ts

@@ -36,3 +36,8 @@ export function getExamStatisticInfo(
 ): Promise<ExamStatDetailInfo> {
   return axios.post('/api/admin/exam/detail', {}, { params: { examId } });
 }
+
+// 结束考试
+export function finishExam(ids: number[]): Promise<boolean> {
+  return axios.post('/api/admin/exam/finish', {}, { params: { ids } });
+}

+ 14 - 0
src/api/subject.ts

@@ -172,3 +172,17 @@ export function deleteOptionalQuestionRule(
     }
   );
 }
+
+// 设置一键未选做
+export function setSubjectUnselectiveConfig(
+  subjectCode: string,
+  enableOneKeyUnselective: boolean
+): Promise<boolean> {
+  return axios.post(
+    '/api/admin/selective/group/unselective',
+    {},
+    {
+      params: { subjectCode, enableOneKeyUnselective },
+    }
+  );
+}

+ 2 - 0
src/api/types/subject.ts

@@ -25,6 +25,8 @@ export interface SubjectItem {
   category: string;
   // 选做科目
   selective: boolean;
+  // 一键未选做
+  enableAllSelective: boolean;
   // 试卷
   paperUrl: string;
   // 答案

+ 1 - 1
src/api/types/user.ts

@@ -109,7 +109,7 @@ export interface BatchCreateUserParam {
   // 密码
   password?: string;
   // 科目
-  subjectCodeString: string;
+  subjectCodes: string[];
 }
 
 // 系统版本等信息

+ 4 - 1
src/api/user.ts

@@ -58,7 +58,10 @@ export function updateUser(data: UserUpdateParam): Promise<boolean> {
 }
 // 批量新增用户
 export function batchAddUser(datas: BatchCreateUserParam): Promise<boolean> {
-  return axios.post('/api/admin/user/batchSave', datas);
+  const params = { ...datas };
+  params.subjectCodeString = datas.subjectCodes.join();
+  params.subjectCodes = undefined;
+  return axios.post('/api/admin/user/batchSave', params);
 }
 // 删除用户: 暂时没有此功能
 export function deleteUser(id: number): Promise<boolean> {

+ 3 - 0
src/store/modules/app/index.ts

@@ -50,6 +50,9 @@ const useAppStore = defineStore('app', {
     appInfo(state: AppState): AppState {
       return { ...state };
     },
+    isMultiExam() {
+      return this.curExam && this.curExam.type === 'MULTI_MEDIA';
+    },
   },
 
   actions: {

+ 2 - 5
src/store/modules/user/index.ts

@@ -22,11 +22,8 @@ const useUserStore = defineStore('user', {
     userInfo(state: UserState): UserState {
       return { ...state };
     },
-    isAdmin(state: UserState): boolean {
-      return state.role === 'ADMIN';
-    },
-    isExamTeacher(state: UserState): boolean {
-      return state.role === 'TEACHING';
+    isSchoolAdmin(state: UserState): boolean {
+      return state.role === 'SCHOOL_ADMIN';
     },
   },
 

+ 1 - 1
src/views/exam/ExamEdit.vue

@@ -336,7 +336,7 @@
   import { CoverArea } from '@/api/types/common';
 
   import SelectImgArea from '@/components/select-img-area/index.vue';
-  import ExamAdvancedDialog from './ExamAdvancedDialog.vue';
+  import ExamAdvancedDialog from './components/ExamAdvancedDialog.vue';
 
   defineOptions({
     name: 'ExamEdit',

+ 45 - 6
src/views/exam/ExamManage.vue

@@ -42,6 +42,9 @@
       </el-form-item>
       <el-form-item>
         <el-button type="primary" @click="toPage(1)">查询</el-button>
+        <el-button type="success" :loading="finishLoading" @click="onFinish"
+          >结束考试</el-button
+        >
         <el-button type="success" @click="onAdd">新建考试</el-button>
       </el-form-item>
     </el-form>
@@ -53,6 +56,7 @@
       :loading="loading"
       border
       stripe
+      @selection-change="handleSelectionChange"
     >
       <el-table-column type="index" label="序号" width="60" />
       <el-table-column prop="id" label="ID" width="80" />
@@ -122,15 +126,18 @@
 <script setup lang="ts">
   import { reactive, ref } from 'vue';
   import { useRouter } from 'vue-router';
-  import { getExamList } from '@/api/exam';
+  import { ElMessage } from 'element-plus';
+  import { getExamList, finishExam } from '@/api/exam';
   import { ExamQueryItem, ExamListFilter } from '@/api/types/exam';
   import { EXAM_STATUS, EXAM_TYPE } from '@/constants/enumerate';
   import useTable from '@/hooks/table';
+  import useLoading from '@/hooks/loading';
   import { dictFilter } from '@/utils/filter';
+  import { modalConfirm } from '@/utils/ui';
 
-  import ExamStatDialog from './ExamStatDialog.vue';
-  import IssuePaperTypeDialog from './IssuePaperTypeDialog.vue';
-  import RejectTypeDialog from './RejectTypeDialog.vue';
+  import ExamStatDialog from './components/ExamStatDialog.vue';
+  import IssuePaperTypeDialog from './components/IssuePaperTypeDialog.vue';
+  import RejectTypeDialog from './components/RejectTypeDialog.vue';
 
   defineOptions({
     name: 'ExamManage',
@@ -144,8 +151,15 @@
     status: undefined,
   });
 
-  const { dataList, pagination, loading, toPage, pageSizeChange } =
-    useTable<ExamQueryItem>(getExamList, searchModel, false);
+  const {
+    dataList,
+    pagination,
+    loading,
+    selectedRows,
+    toPage,
+    pageSizeChange,
+    handleSelectionChange,
+  } = useTable<ExamQueryItem>(getExamList, searchModel, false);
 
   // action相关
   const currentExamId = ref(0);
@@ -161,6 +175,31 @@
     statDialogRef.value?.open();
   }
 
+  const { loading: finishLoading, setLoading: setFinishLoading } = useLoading();
+  async function onFinish() {
+    if (finishLoading.value) return;
+    if (selectedRows.value.length === 0) {
+      ElMessage.warning('请先选择考试');
+      return;
+    }
+
+    const confirm = await modalConfirm(
+      `确定要结束所选的${selectedRows.value.length}个考试吗?`,
+      '提示'
+    ).catch(() => false);
+    if (!confirm) return;
+
+    setFinishLoading(true);
+    try {
+      await finishExam({ ids: selectedRows.value.map((item) => item.id) });
+      ElMessage.success('操作成功!');
+    } catch (error) {
+      console.error('操作失败:', error);
+    } finally {
+      setFinishLoading(false);
+    }
+  }
+
   function onAdd() {
     router.push({ name: 'ExamEdit' });
   }

+ 0 - 0
src/views/exam/ExamAdvancedDialog.vue → src/views/exam/components/ExamAdvancedDialog.vue


+ 0 - 0
src/views/exam/ExamStatDialog.vue → src/views/exam/components/ExamStatDialog.vue


+ 8 - 2
src/views/exam/IssuePaperTypeDialog.vue → src/views/exam/components/IssuePaperTypeDialog.vue

@@ -28,15 +28,21 @@
         <el-table-column prop="name" label="分类名称" />
         <el-table-column prop="custom" label="类型" width="80">
           <template #default="scope">
-            {{ scope.row.custom ? '自定义' : '系统' }}
+            {{ scope.row.custom ? '自定义' : '默认' }}
           </template>
         </el-table-column>
         <el-table-column label="操作" width="120">
           <template #default="scope">
-            <el-button size="small" link @click="onEdit(scope.row)">
+            <el-button
+              v-if="scope.row.custom"
+              size="small"
+              link
+              @click="onEdit(scope.row)"
+            >
               编辑
             </el-button>
             <el-button
+              v-if="scope.row.custom"
               size="small"
               link
               type="danger"

+ 0 - 0
src/views/exam/ModifyIssuePaperType.vue → src/views/exam/components/ModifyIssuePaperType.vue


+ 0 - 0
src/views/exam/ModifyRejectType.vue → src/views/exam/components/ModifyRejectType.vue


+ 8 - 2
src/views/exam/RejectTypeDialog.vue → src/views/exam/components/RejectTypeDialog.vue

@@ -28,15 +28,21 @@
         <el-table-column prop="name" label="分类名称" />
         <el-table-column prop="custom" label="类型" width="80">
           <template #default="scope">
-            {{ scope.row.custom ? '自定义' : '系统' }}
+            {{ scope.row.custom ? '自定义' : '默认' }}
           </template>
         </el-table-column>
         <el-table-column label="操作" width="120">
           <template #default="scope">
-            <el-button size="small" link @click="onEdit(scope.row)">
+            <el-button
+              v-if="scope.row.custom"
+              size="small"
+              link
+              @click="onEdit(scope.row)"
+            >
               编辑
             </el-button>
             <el-button
+              v-if="scope.row.custom"
               size="small"
               link
               type="danger"

+ 92 - 35
src/views/student/StudentManage.vue

@@ -190,6 +190,7 @@
       :loading="loading"
       border
       stripe
+      @sort-change="handleSortChange"
     >
       <el-table-column type="index" label="序号" width="60" />
       <el-table-column
@@ -197,30 +198,35 @@
         prop="examNumber"
         label="准考证号"
         width="120"
+        sortable
       />
       <el-table-column
         v-if="checkFieldVisible('secretNumber')"
         prop="secretNumber"
         label="密号"
-        min-width="100"
+        width="120"
+        sortable
       />
       <el-table-column
         v-if="checkFieldVisible('name')"
         prop="name"
         label="姓名"
         min-width="100"
+        sortable
       />
       <el-table-column
         v-if="checkFieldVisible('studentCode')"
         prop="studentCode"
         label="学号"
         width="120"
+        sortable
       />
       <el-table-column
-        v-if="checkFieldVisible('subjectName')"
-        prop="subjectName"
+        v-if="checkFieldVisible('subjectCode')"
+        prop="subjectCode"
         label="科目"
-        min-width="100"
+        min-width="120"
+        sortable
       />
       <el-table-column
         v-if="checkFieldVisible('paperType')"
@@ -245,93 +251,115 @@
         label="扫描识别"
         width="100"
       >
-        <!-- TODO: -->
-        <template #default="scope">
-          <el-tag :type="scope.row.upload ? 'success' : 'danger'">
-            {{ scope.row.upload ? '是' : '否' }}
-          </el-tag>
-          <el-button
-            v-if="scope.row.sheetUrls.length > 0"
-            size="small"
-            type="primary"
-            link
-            style="margin-left: 5px"
-            @click="onPreviewImg(scope.row)"
-          >
-            原图
-          </el-button>
+        <template #default="{ row }">
+          <span v-if="!row.upload">未上传</span>
+          <template v-else>
+            <!-- 上传 -->
+            <span v-if="appStore.isMultiExam">已上传</span>
+            <el-button v-else type="primary" link @click="onPreviewImg(row)"
+              >已上传</el-button
+            >
+            <!-- 缺考 -->
+            <span class="ml-10">{{ row.absent ? '缺考' : '正常' }}</span>
+          </template>
         </template>
       </el-table-column>
       <el-table-column
         v-if="checkFieldVisible('sheetCount')"
         prop="sheetCount"
         label="扫描张数"
-        width="100"
+        width="120"
+        sortable
       />
       <el-table-column
         v-if="checkFieldVisible('manualAbsent')"
         label="人工指定"
         width="100"
       >
-        <template #default="scope">
-          <el-tag :type="scope.row.manualAbsent ? 'success' : 'danger'">
-            {{ scope.row.manualAbsent ? '是' : '否' }}
-          </el-tag>
+        <template #default="{ row }">
+          <!-- 违纪 -->
+          <template v-if="row.breach">
+            <el-button
+              v-if="userStore.isSchoolAdmin"
+              type="danger"
+              link
+              @click="onChangeBreach(row)"
+              >违纪</el-button
+            >
+            <span v-else>违纪</span>
+          </template>
+          <span v-else>正常</span>
+          <!-- 人工指定缺考 -->
+          <span class="ml-10">{{
+            row.manualAbsent ? '人工指定' : '正常'
+          }}</span>
         </template>
       </el-table-column>
       <el-table-column
         v-if="checkFieldVisible('batchCode')"
         prop="batchCode"
         label="批次编号"
-        width="100"
+        width="120"
+        sortable
       />
       <el-table-column
         v-if="checkFieldVisible('packageCode')"
         prop="packageCode"
         label="签到表编号"
         width="120"
+        sortable
       />
       <el-table-column
         v-if="checkFieldVisible('college')"
         prop="college"
         label="学院"
         width="120"
+        sortable
       />
       <el-table-column
         v-if="checkFieldVisible('className')"
         prop="className"
         label="班级"
         width="100"
+        sortable
       />
       <el-table-column
         v-if="checkFieldVisible('teacher')"
         prop="teacher"
         label="任课老师"
-        width="100"
+        width="120"
+        sortable
       />
       <el-table-column
         v-if="checkFieldVisible('examSite')"
         prop="examSite"
         label="考点"
         width="100"
+        sortable
       />
       <el-table-column
         v-if="checkFieldVisible('examRoom')"
         prop="examRoom"
         label="考场"
         width="80"
+        sortable
       />
       <el-table-column label="操作" width="120" fixed="right">
-        <template #default="scope">
+        <template v-if="userStore.isSchoolAdmin" #default="scope">
           <el-button
             size="small"
             type="primary"
             link
             @click="onEdit(scope.row)"
           >
-            编辑
+            修改
           </el-button>
-          <el-button size="small" type="danger" link @click="onDelete(row)"
+          <el-button
+            v-if="!appStore.isMultiExam"
+            size="small"
+            type="danger"
+            link
+            @click="onDelete(row)"
             >删除</el-button
           >
         </template>
@@ -387,13 +415,13 @@
   import { reactive, ref, computed } from 'vue';
   import { CaretBottom } from '@element-plus/icons-vue';
   import { ElMessage } from 'element-plus';
-  import { getStudentList, deleteStudent } from '@/api/student';
+  import { getStudentList, deleteStudent, resetBreach } from '@/api/student';
   import { StudentItem, StudentListFilter } from '@/api/types/student';
   import useTable from '@/hooks/table';
   import useLoading from '@/hooks/loading';
   import { modalConfirm } from '@/utils/ui';
   import { downloadExport } from '@/utils/download-export';
-  import { useAppStore } from '@/store';
+  import { useAppStore, useUserStore } from '@/store';
 
   import ModifyStudent from './ModifyStudent.vue';
 
@@ -402,6 +430,7 @@
   });
 
   const appStore = useAppStore();
+  const userStore = useUserStore();
 
   const searchModel = reactive<StudentListFilter>({
     name: '',
@@ -425,8 +454,15 @@
     sheetCount: undefined,
   });
 
-  const { dataList, pagination, loading, getList, toPage, pageSizeChange } =
-    useTable<StudentItem>(getStudentList, searchModel, false);
+  const {
+    dataList,
+    pagination,
+    loading,
+    getList,
+    toPage,
+    pageSizeChange,
+    handleSortChange,
+  } = useTable<StudentItem>(getStudentList, searchModel, false);
 
   // 表头配置
   const tableFields = [
@@ -434,7 +470,7 @@
     { name: '密号', field: 'secretNumber' },
     { name: '姓名', field: 'name' },
     { name: '学号', field: 'studentCode' },
-    { name: '科目', field: 'subjectName' },
+    { name: '科目', field: 'subjectCode' },
     { name: '试卷类型', field: 'paperType' },
     { name: '层次', field: 'subjectLevel' },
     { name: '专业类型', field: 'subjectCategory' },
@@ -468,12 +504,33 @@
     modifyStudentRef.value?.open();
   }
 
+  const { loading: breachLoading, setLoading: setBreachLoading } = useLoading();
+  async function onChangeBreach(row: StudentItem) {
+    if (breachLoading.value) return;
+    const confirm = await modalConfirm(
+      `确认要将该考生违纪重置为正常?`,
+      '删除确认'
+    ).catch(() => false);
+    if (!confirm) return;
+
+    try {
+      setBreachLoading(true);
+      await resetBreach(row.id);
+      ElMessage.success('重置成功');
+      getList();
+    } catch (error) {
+      console.log('重置失败:', error);
+    } finally {
+      setBreachLoading(false);
+    }
+  }
+
   // 删除
   const { loading: deleteLoading, setLoading: setDeleteLoading } = useLoading();
   async function onDelete(row: StudentItem) {
     if (deleteLoading.value) return;
     const confirm = await modalConfirm(
-      `确定要删除分类"${row.name}"吗?`,
+      `确定要删除考生"${row.name}"吗?`,
       '删除确认'
     ).catch(() => false);
     if (!confirm) return;

+ 20 - 0
src/views/subject/OptionalRuleEdit.vue

@@ -13,6 +13,9 @@
       >
       <el-button type="primary" @click="onAdd">新增</el-button>
       <el-button type="primary" @click="onEdit"> 编辑 </el-button>
+      <el-button type="primary" @click="onSetUnselective">
+        一键未选做
+      </el-button>
       <el-button @click="onReturn">返回</el-button>
     </el-space>
   </div>
@@ -57,6 +60,13 @@
     :row-data="{}"
     @modified="getList"
   />
+  <!-- 一键未选做 -->
+  <SetUnselectiveDialog
+    ref="setUnselectiveDialogRef"
+    :subject-code="subjectCode"
+    :value="subjectInfo.enableAllSelective"
+    @modified="getSubjectDetailInfo"
+  />
 </template>
 
 <script setup lang="ts">
@@ -69,6 +79,7 @@
 
   import OptionalRuleDialog from './components/OptionalRuleDialog.vue';
   import ModifyOptionalQuestionRule from './components/ModifyOptionalQuestionRule.vue';
+  import SetUnselectiveDialog from './components/SetUnselectiveDialog.vue';
 
   defineOptions({
     name: 'OptionalRuleEdit',
@@ -86,6 +97,7 @@
     objectiveScore: 0,
     subjectiveScore: 0,
     totalScore: 0,
+    enableAllSelective: false,
   });
   // 获取科目详情信息
   const getSubjectDetailInfo = async () => {
@@ -95,6 +107,7 @@
     subjectInfo.objectiveScore = res.objectiveScore;
     subjectInfo.subjectiveScore = res.subjectiveScore;
     subjectInfo.totalScore = res.totalScore;
+    subjectInfo.enableAllSelective = res.enableAllSelective;
   };
 
   // 获取试卷结构列表
@@ -121,6 +134,13 @@
     optionalRuleDialogRef.value?.open();
   };
 
+  // 一键未选做
+  const setUnselectiveDialogRef =
+    ref<InstanceType<typeof SetUnselectiveDialog>>();
+  const onSetUnselective = () => {
+    setUnselectiveDialogRef.value?.open();
+  };
+
   const onReturn = () => {
     router.back();
   };

+ 132 - 0
src/views/subject/components/SetUnselectiveDialog.vue

@@ -0,0 +1,132 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    title="一键未选做"
+    width="500px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    top="10vh"
+    append-to-body
+    @close="handleClose"
+    @open="modalBeforeOpen"
+  >
+    <div class="unselective-content">
+      <div class="checkbox-section">
+        <el-checkbox v-model="formModel.enableOneKeyUnselective">
+          启用一键未选做
+        </el-checkbox>
+      </div>
+
+      <div class="warning-text">
+        <span style="color: #f56c6c">
+          *启用一键未选做后,当前科目所有选做题给分板下均支持一键未选做的操作。
+        </span>
+      </div>
+    </div>
+
+    <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 { reactive } from 'vue';
+  import { ElMessage } from 'element-plus';
+  import useModal from '@/hooks/modal';
+  import useLoading from '@/hooks/loading';
+  import { setSubjectUnselectiveConfig } from '@/api/subject';
+
+  defineOptions({
+    name: 'SetUnselectiveDialog',
+  });
+
+  /* modal */
+  const { visible, open, close } = useModal();
+  defineExpose({ open, close });
+
+  interface Props {
+    subjectCode?: string; // 科目代码
+    value: boolean;
+  }
+
+  const props = withDefaults(defineProps<Props>(), {
+    subjectCode: '',
+    value: false,
+  });
+
+  const emit = defineEmits(['modified']);
+
+  interface FormModel {
+    subjectCode: string;
+    enableOneKeyUnselective: boolean;
+  }
+
+  const initialFormState: FormModel = {
+    subjectCode: '',
+    enableOneKeyUnselective: false,
+  };
+
+  const formModel = reactive<FormModel>({ ...initialFormState });
+
+  const handleClose = () => {
+    Object.assign(formModel, initialFormState);
+  };
+
+  /* confirm */
+  const { loading, setLoading } = useLoading();
+  async function confirm() {
+    setLoading(true);
+
+    try {
+      await setSubjectUnselectiveConfig({
+        subjectCode: formModel.subjectCode,
+        enableOneKeyUnselective: formModel.enableOneKeyUnselective,
+      });
+
+      ElMessage.success(
+        formModel.enableOneKeyUnselective
+          ? '启用一键未选做成功!'
+          : '关闭一键未选做成功!'
+      );
+      emit('modified');
+      close();
+    } catch (error) {
+      ElMessage.error('操作失败,请重试');
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  /* init modal */
+  function modalBeforeOpen() {
+    formModel.subjectCode = props.subjectCode;
+    formModel.enableOneKeyUnselective = props.value;
+  }
+</script>
+
+<style scoped>
+  .unselective-content {
+    padding: 20px 0;
+  }
+
+  .checkbox-section {
+    margin-bottom: 20px;
+  }
+
+  .warning-text {
+    font-size: 14px;
+    line-height: 1.5;
+  }
+
+  :deep(.el-checkbox) {
+    font-size: 16px;
+  }
+
+  :deep(.el-checkbox__label) {
+    font-weight: 500;
+  }
+</style>

+ 33 - 20
src/views/user/components/BatchCreateUserDialog.vue

@@ -57,12 +57,12 @@
           show-password
         />
       </el-form-item>
-      <el-form-item label="选择科目" prop="subjectCodeString">
+      <el-form-item label="选择科目" prop="subjectCodes">
         <el-button type="primary" @click="openSelectSubjectDialog"
           >设置</el-button
         >
-        <div v-if="selectedSubjects.length > 0" style="margin-left: 10px">
-          已选择 {{ selectedSubjects.length }} 个科目
+        <div v-if="formModel.subjectCodes.length > 0" style="margin-left: 10px">
+          已选择 {{ formModel.subjectCodes.length }} 个科目
         </div>
       </el-form-item>
     </el-form>
@@ -78,7 +78,7 @@
 
   <SelectSubjectDialog
     ref="selectSubjectDialogRef"
-    :select-ids="formModel.subjectIds"
+    :select-codes="formModel.subjectCodes"
     @selected="handleSubjectsSelected"
   />
 </template>
@@ -94,7 +94,7 @@
   import { BATCH_ADD_ROLE } from '@/constants/enumerate';
   import { batchAddUser } from '@/api/user';
   import type { BatchCreateUserParam } from '@/api/types/user';
-  import type { SubjectItem } from '@/api/types/base';
+  import { modalConfirm } from '@/utils/ui';
 
   import SelectSubjectDialog from './SelectSubjectDialog.vue';
 
@@ -115,7 +115,6 @@
 
   const rules: FormRules<keyof BatchCreateUserParam> = {
     role: [{ required: true, message: '请选择角色', trigger: 'change' }],
-    prefix: [{ required: true, message: '请输入命名规则', trigger: 'change' }],
     number: [
       { required: true, message: '请输入每分组账号数', trigger: 'change' },
     ],
@@ -126,11 +125,11 @@
         trigger: 'change',
       },
     ],
-    subjectCodeString: [
+    subjectCodes: [
       {
         required: true,
-        validator: (rule: any, value: string, callback: any) => {
-          if (!value) {
+        validator: (rule: any, value: string[], callback: any) => {
+          if (!value || value.length === 0) {
             callback(new Error('请选择科目'));
           } else {
             callback();
@@ -148,12 +147,11 @@
       number: undefined,
       random: false,
       password: '',
-      subjectCodeString: '',
+      subjectCodes: [],
     };
   }
 
   const formModel = reactive<BatchCreateUserParam>(getInitialFormModel());
-  const selectedSubjects = ref<SubjectItem[]>([]);
   const selectSubjectDialogRef = ref<InstanceType<
     typeof SelectSubjectDialog
   > | null>(null);
@@ -169,27 +167,43 @@
   };
 
   const openSelectSubjectDialog = () => {
-    selectSubjectDialogRef.value?.open(selectedSubjects.value.map((s) => s.id));
+    selectSubjectDialogRef.value?.open();
   };
 
-  const handleSubjectsSelected = (subjects: SubjectItem[]) => {
-    selectedSubjects.value = subjects;
-    formModel.subjectCodeString = subjects.map((s) => s.code).join(',');
-    ElMessage.success(`已选择 ${subjects.length} 个科目`);
-    formRef.value?.validateField('subjectCodeString');
+  const handleSubjectsSelected = (subjectCodes: string[]) => {
+    formModel.subjectCodes = subjectCodes;
+    formRef.value?.validateField('subjectCodes');
   };
 
+  function getTips() {
+    const subjectCount = selectSubjectDialogRef.value?.getSubjectCount();
+    const selectCount = formModel.subjectCodes.length;
+    const codeString = formModel.subjectCodes.join('、');
+    const roleName = BATCH_ADD_ROLE[formModel.role];
+    if (selectCount === subjectCount) {
+      return `确认创建 <strong>全部科目</strong> 的“${roleName}”,每组${formModel.number}个账号吗?`;
+    }
+
+    if (selectCount > 5) {
+      return `确认创建${selectCount}个科目“${roleName}”,每组${formModel.number}个账号吗?`;
+    }
+
+    return `确认创建${codeString}科目“${roleName}”,每组${formModel.number}个账号吗?`;
+  }
+
   const { loading, setLoading } = useLoading();
   const handleSave = async () => {
     if (!formRef.value) return;
-
     const valid = await formRef.value.validate().catch(() => false);
     if (!valid) return;
 
+    const confirm = await modalConfirm(getTips(), '提示').catch(() => false);
+    if (!confirm) return;
+
     try {
       setLoading(true);
       let res = true;
-      await batchAddUser({ ...formModel }).catch(() => {
+      await batchAddUser(formModel).catch(() => {
         res = false;
       });
       setLoading(false);
@@ -205,6 +219,5 @@
 
   const modalBeforeOpen = () => {
     Object.assign(formModel, getInitialFormModel());
-    selectedSubjects.value = [];
   };
 </script>

+ 15 - 54
src/views/user/components/SelectSubjectDialog.vue

@@ -45,13 +45,7 @@
       <el-table-column prop="objectiveScore" label="客观总分" />
       <el-table-column prop="subjectiveScore" label="主观总分" />
       <el-table-column prop="totalScore" label="试卷总分" />
-      <el-table-column prop="status" label="状态">
-        <template #default="{ row }">
-          <el-tag :type="row.status === 'normal' ? 'success' : 'danger'">
-            {{ row.status === 'normal' ? '正常' : '异常' }}
-          </el-tag>
-        </template>
-      </el-table-column>
+      <el-table-column prop="status" label="状态"> 正常 </el-table-column>
     </el-table>
 
     <template #footer>
@@ -77,18 +71,18 @@
   import { subjectQuery } from '@/api/base'; // Assuming API for subjects exists
   import { useAppStore } from '@/store';
 
-  import type { SubjectItem } from '@/api/types/base'; // Assuming SubjectItem type exists
+  import type { SubjectItem } from '@/api/types/subject';
 
   defineOptions({
     name: 'SelectSubjectDialog',
   });
 
   interface Props {
-    selectIds?: number[];
+    selectCodes?: string[];
   }
 
   const props = withDefaults(defineProps<Props>(), {
-    selectIds: () => [],
+    selectCodes: () => [],
   });
 
   interface SearchModel {
@@ -97,48 +91,12 @@
 
   /* modal */
   const { visible, open, close } = useModal();
-  defineExpose({ open, close });
+  defineExpose({ open, close, getSubjectCount });
 
   const appStore = useAppStore();
 
   const emit = defineEmits(['selected']);
 
-  // Mock API call for subject list
-  const mockSubjectList: SubjectItem[] = [
-    {
-      id: 1,
-      name: '201904516-工程图学2',
-      objectiveScore: 0,
-      subjectiveScore: 0,
-      totalScore: 0,
-      status: 'normal',
-    },
-    {
-      id: 2,
-      name: '201904516_1-工程图学2',
-      objectiveScore: 60,
-      subjectiveScore: 40,
-      totalScore: 100,
-      status: 'normal',
-    },
-    {
-      id: 3,
-      name: '语文',
-      objectiveScore: 50,
-      subjectiveScore: 50,
-      totalScore: 100,
-      status: 'normal',
-    },
-    {
-      id: 4,
-      name: '数学',
-      objectiveScore: 70,
-      subjectiveScore: 30,
-      totalScore: 100,
-      status: 'disabled',
-    },
-  ];
-
   const { loading, setLoading } = useLoading();
   const searchModel = reactive<SearchModel>({ subjectCode: undefined });
   const subjectList = ref<SubjectItem[]>([]);
@@ -163,6 +121,10 @@
     }
   };
 
+  function getSubjectCount() {
+    return subjectList.value.length;
+  }
+
   const search = async () => {
     if (!searchModel.subjectCode) {
       dataList.value = subjectList.value;
@@ -185,7 +147,10 @@
       ElMessage.warning('请至少选择一个科目');
       return;
     }
-    emit('selected', [...selectedSubjects.value]); // Emit a copy to avoid reactivity issues
+    emit(
+      'selected',
+      [...selectedSubjects.value].map((item) => item.code)
+    );
     close();
   };
 
@@ -195,15 +160,11 @@
   };
 
   const modalBeforeOpen = async () => {
-    // ceshi
-    subjectList.value = mockSubjectList;
-    dataList.value = mockSubjectList;
-
     await fetchSubjectList();
-    if (props.selectIds.length > 0) {
+    if (props.selectCodes.length > 0) {
       await nextTick();
       selectedSubjects.value = dataList.value.filter((item) => {
-        return props.selectIds.includes(item.id);
+        return props.selectCodes.includes(item.code);
       });
       if (selectedSubjects.value.length > 0) {
         selectedSubjects.value.forEach((item) => {