【新增】试卷任务训练、考试、高考模拟前端
This commit is contained in:
		| @@ -0,0 +1,356 @@ | ||||
| <template> | ||||
|   <Dialog | ||||
|     v-model="visible" | ||||
|     :title="isUpdate ? '修改试卷方案' : '添加试卷方案'" | ||||
|     width="460" | ||||
|     center | ||||
|   > | ||||
|     <el-form | ||||
|       ref="formRef" | ||||
|       :model="form" | ||||
|       :rules="rules" | ||||
|       label-width="80px" | ||||
|       @submit.prevent="" | ||||
|     > | ||||
|       <!-- 知识点 --> | ||||
|       <el-form-item label="知识点" prop="pointName"> | ||||
|         <el-tree | ||||
|           ref="treeRef" | ||||
|           :data="pointOptions" | ||||
|           show-checkbox | ||||
|           node-key="value" | ||||
|           default-expand-all | ||||
|           :props="{ label: 'label', children: 'children' }" | ||||
|           highlight-current | ||||
|           :disabled="isUpdate" | ||||
|           @check="handleCheckChange" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <!-- 题型 --> | ||||
|       <el-form-item label="题型" prop="spName"> | ||||
|         <el-select v-model="form.spName" placeholder="请选择题型" clearable :disabled="isUpdate"> | ||||
|           <el-option | ||||
|             v-for="item in CourseOptions" | ||||
|             :key="item" | ||||
|             :label="item" | ||||
|             :value="item" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <!-- 难度 --> | ||||
|       <el-form-item label="难度" prop="quLevel"> | ||||
|         <el-select v-model="form.quLevel" placeholder="请选择难度" :disabled="isUpdate"> | ||||
|           <el-option label="全部" :value="3" /> | ||||
|           <el-option label="简单" :value="0" /> | ||||
|           <el-option label="一般" :value="1" /> | ||||
|           <el-option label="困难" :value="2" /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <!-- 关键字 --> | ||||
|       <el-form-item label="关键字" prop="keyword"> | ||||
|         <el-select | ||||
|           v-model="form.keyword" | ||||
|           placeholder="请选择关键字" | ||||
|           multiple | ||||
|           clearable | ||||
|           :disabled="isUpdate" | ||||
|         > | ||||
|           <el-option | ||||
|             v-for="item in keyOptions" | ||||
|             :key="item" | ||||
|             :label="item" | ||||
|             :value="item" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <!-- 数量、分数 --> | ||||
|       <el-form-item label="试题数量" prop="quNumbers"> | ||||
|         <el-input-number v-model="form.quNumbers" placeholder="请输入试题数量" :disabled="isUpdate" /> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <el-form-item label="每题分数" prop="quScores"> | ||||
|         <el-input-number v-model="form.quScores" placeholder="请输入每题分数" :disabled="isUpdate" /> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <el-form-item label="小计分数" prop="subtotalScore"> | ||||
|         <el-input-number v-model="form.subtotalScore" :disabled="true" /> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <el-form-item label="可用数量"> | ||||
|         <el-text>{{ availableCount }}</el-text> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|  | ||||
|     <template #footer> | ||||
|       <el-button @click="visible = false">取 消</el-button> | ||||
|       <el-button type="primary" :loading="loading" @click="save" :disabled="isUpdate">保存</el-button> | ||||
|     </template> | ||||
|   </Dialog> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ref, reactive, nextTick } from 'vue' | ||||
| import { | ||||
|   addScheme, | ||||
|   updateScheme, | ||||
|   getCourseList, | ||||
|   getKeyList, | ||||
|   getPoints, | ||||
|   fetchQuestionCount | ||||
| } from '@/api/system/scheme' | ||||
|  | ||||
| const message = useMessage() // 消息弹窗 | ||||
| const emit = defineEmits(['done']) | ||||
| const props = defineProps({ | ||||
|   taskSpecialty: String, | ||||
|   taskId: String | ||||
| }) | ||||
|  | ||||
| /** --------- 状态定义 --------- **/ | ||||
| const visible = defineModel({ type: Boolean }); | ||||
|    | ||||
| const isUpdate = ref(false) | ||||
| const loading = ref(false) | ||||
| const formRef = ref() | ||||
| const treeRef = ref() | ||||
| const availableCount = ref<number | string>('') | ||||
|  | ||||
| const CourseOptions = ref<string[]>([]) | ||||
| const keyOptions = ref<string[]>([]) | ||||
|  | ||||
| // 扁平接口返回的原始结构 | ||||
| interface PointRaw { | ||||
|   spId: number | ||||
|   spName: string | ||||
|   parentId: number | ||||
|   orderNum?: number | ||||
|   ancestors?: string | ||||
|   [key: string]: any | ||||
| } | ||||
| // 构造给 el-tree 用的节点结构 | ||||
| interface TreeNode extends PointRaw { | ||||
|   label: string | ||||
|   value: number | ||||
|   children: TreeNode[] | ||||
| } | ||||
|  | ||||
| const pointOptions = ref<TreeNode[]>([]) | ||||
|  | ||||
| const form = ref<{ | ||||
|   schemeId?: string | ||||
|   taskId?: string | ||||
|   spName?: string | ||||
|   quLevel?: number | ||||
|   keyword: string[] | ||||
|   pointName: string[] | ||||
|   quNumbers?: number | ||||
|   quScores?: number | ||||
|   subtotalScore?: number | ||||
|   taskSpecialty?: string | ||||
| }>({ | ||||
|   keyword: [], | ||||
|   pointName: [] | ||||
| }) | ||||
|  | ||||
| const rules = reactive({ | ||||
|   spName: [{ required: true, message: '请选择题型', trigger: 'change' }], | ||||
|   quLevel: [{ required: true, message: '请选择难度', trigger: 'change' }], | ||||
|   pointName: [{ required: true, message: '请选择知识点', trigger: 'change' }], | ||||
|   quNumbers: [{ required: true, message: '请输入试题数量', trigger: 'blur' }], | ||||
|   quScores: [{ required: true, message: '请输入每题分数', trigger: 'blur' }] | ||||
| }) | ||||
|  | ||||
| /** --------- 打开弹窗 --------- **/ | ||||
| const open = async (type: 'create' | 'update', data?: any) => { | ||||
|   visible.value = true | ||||
|   isUpdate.value = type === 'update' | ||||
|  | ||||
|   // 1. 并行拉下拉所需的选项 | ||||
|   const [courses, keywords] = await Promise.all([ | ||||
|     getCourseList(), | ||||
|     getKeyList() | ||||
|   ]) | ||||
|   CourseOptions.value = courses | ||||
|   keyOptions.value = keywords | ||||
|  | ||||
|   // 2. 拉知识点并建树 | ||||
|   await loadPointTree() | ||||
|  | ||||
|   // 3. 填表单 | ||||
|   if (isUpdate.value && data) { | ||||
|     form.value = { | ||||
|       ...data, | ||||
|       keyword: data.keywords?.split(',') || [], | ||||
|       pointName: data.pointNames?.split(',') || [], | ||||
|       taskId: props.taskId, | ||||
|       taskSpecialty: props.taskSpecialty | ||||
|     } | ||||
|  | ||||
|     // 4. 等树节点数据都生效了,再设置默认选中 | ||||
|     await nextTick() | ||||
|     treeRef.value?.setCheckedKeys(form.value.pointName) | ||||
|  | ||||
|     calcSubtotal() | ||||
|     fetchCount() | ||||
|   } else { | ||||
|     // 新增时重置 | ||||
|     form.value = { | ||||
|       schemeId: undefined, | ||||
|       taskId: props.taskId, | ||||
|       spName: undefined, | ||||
|       quLevel: undefined, | ||||
|       keyword: [], | ||||
|       pointName: [], | ||||
|       quNumbers: undefined, | ||||
|       quScores: undefined, | ||||
|       subtotalScore: undefined, | ||||
|       taskSpecialty: props.taskSpecialty | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // 清除校验 | ||||
|   await nextTick() | ||||
|   formRef.value?.clearValidate?.() | ||||
| } | ||||
|  | ||||
| /** --------- 拉取扁平知识点并建树 --------- **/ | ||||
| async function loadPointTree() { | ||||
|   try { | ||||
|     const res = await getPoints(props.taskSpecialty) | ||||
|     // 兼容:getPoints 有可能直接返回数组,也可能返回 { data: [] } | ||||
|     const raw: PointRaw[] = Array.isArray(res) | ||||
|       ? res | ||||
|       : Array.isArray((res as any).data) | ||||
|       ? (res as any).data | ||||
|       : [] | ||||
|  | ||||
|     pointOptions.value = buildTree(raw) | ||||
|   } catch (e: any) { | ||||
|     message.error('获取知识点失败:' + (e.message || e)) | ||||
|     pointOptions.value = [] | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** 扁平 -> 树 */ | ||||
| function buildTree(data: PointRaw[]): TreeNode[] { | ||||
|   const idMap = new Map<number, TreeNode>() | ||||
|   data.forEach(item => { | ||||
|     idMap.set(item.spId, { | ||||
|       ...item, | ||||
|       label: item.spName, | ||||
|       value: item.spId, | ||||
|       children: [] | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   const tree: TreeNode[] = [] | ||||
|   data.forEach(item => { | ||||
|     const node = idMap.get(item.spId)! | ||||
|     const parent = idMap.get(item.parentId) | ||||
|     if (parent) { | ||||
|       parent.children.push(node) | ||||
|     } else { | ||||
|       tree.push(node) | ||||
|     } | ||||
|   }) | ||||
|  | ||||
|   // (可选)按 orderNum 排序 | ||||
|   const sortRecursively = (nodes: TreeNode[]) => { | ||||
|     nodes.sort((a, b) => (a.orderNum ?? 0) - (b.orderNum ?? 0)) | ||||
|     nodes.forEach(n => sortRecursively(n.children)) | ||||
|   } | ||||
|   sortRecursively(tree) | ||||
|  | ||||
|   return tree | ||||
| } | ||||
|  | ||||
| /** 计算小计 */ | ||||
| const calcSubtotal = () => { | ||||
|   const n = Number(form.value.quNumbers ?? 0) | ||||
|   const s = Number(form.value.quScores ?? 0) | ||||
|   form.value.subtotalScore = n * s | ||||
| } | ||||
|  | ||||
| /** 选中回调 */ | ||||
| const handleCheckChange = () => { | ||||
|   form.value.pointName = treeRef.value.getCheckedKeys() | ||||
|  | ||||
| } | ||||
|  | ||||
| /** 拉取可用题目数量 */ | ||||
| const fetchCount = async () => { | ||||
|   const res = await fetchQuestionCount({ | ||||
|     taskSpecialty: props.taskSpecialty, | ||||
|     pointIds: form.value.pointName,      // 知识点 | ||||
|     spName: form.value.spName,     // 题型 | ||||
|     quLevel: form.value.quLevel,   // 难度 | ||||
|     keyword: form.value.keyword          // **关键字**,加上这一行 | ||||
|  | ||||
|   }) | ||||
|   availableCount.value = res ?? 0 | ||||
| } | ||||
|  | ||||
| /** 保存 */ | ||||
| const save = async () => { | ||||
|  // 判断试题数量是否小于等于可用题目数量 | ||||
|  if (Number(form.value.quNumbers) > Number(availableCount.value)) { | ||||
|     message.warning('试题数量不能超过可用题目数量'); | ||||
|     return; | ||||
|   } | ||||
|   if (Number(availableCount.value) === 0) { | ||||
|     message.warning('当前条件下没有可用的试题,请调整条件后再试'); | ||||
|     return; | ||||
|     } | ||||
|   await formRef.value.validate() | ||||
|   loading.value = true | ||||
|   try { | ||||
|     const payload = { | ||||
|       ...form.value, | ||||
|       keywords: form.value.keyword.join(','), | ||||
|       pointNames: form.value.pointName.join(',') | ||||
|     } | ||||
|     const fn = isUpdate.value ? updateScheme : addScheme | ||||
|     await fn(payload) | ||||
|     emit('done') | ||||
|     visible.value = false | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
| watch( | ||||
|   () => [ | ||||
|     form.value.spName, | ||||
|     form.value.quLevel, | ||||
|     form.value.pointName, | ||||
|     form.value.keyword, | ||||
|     props.taskSpecialty | ||||
|   ], | ||||
|   ([spName, quLevel, pointName, keyword, taskSpecialty]) => { | ||||
|     // 当题型、难度、专业、知识点、关键字都有值时,才去请求可用题目数 | ||||
|     if ( | ||||
|       spName && | ||||
|       quLevel !== null && | ||||
|       quLevel !== undefined && | ||||
|       taskSpecialty && | ||||
|       pointName!== null && | ||||
|       keyword!== null      // **关键字也要有选项** | ||||
|       // 如果你也想加关键字限制:&& keyword.length > 0 | ||||
|     ) { | ||||
|       fetchCount() | ||||
|     } | ||||
|   }, | ||||
|   { deep: true } | ||||
| ) | ||||
| watch( | ||||
|   () => [form.value.quNumbers, form.value.quScores], | ||||
|   () => { | ||||
|     calcSubtotal() | ||||
|   } | ||||
| ) | ||||
|  | ||||
| defineExpose({ open }) | ||||
| </script> | ||||
| @@ -0,0 +1,82 @@ | ||||
| <!-- 搜索表单 --> | ||||
| <template> | ||||
|     <ele-card :body-style="{ paddingBottom: '2px' }"> | ||||
|       <el-form label-width="72px" @keyup.enter="search" @submit.prevent=""> | ||||
|         <el-row :gutter="8"> | ||||
|           <el-col :lg="6" :md="12" :sm="12" :xs="24"> | ||||
|             <el-form-item label="题型"> | ||||
|               <el-input | ||||
|                 clearable | ||||
|                 v-model.trim="form.spName" | ||||
|                 placeholder="请输入" | ||||
|               /> | ||||
|             </el-form-item> | ||||
|           </el-col> | ||||
|           <el-col :lg="6" :md="12" :sm="12" :xs="24"> | ||||
|             <el-form-item label="难度"> | ||||
|               <el-input | ||||
|                 clearable | ||||
|                 v-model.trim="form.quLevel" | ||||
|                 placeholder="请输入" | ||||
|               /> | ||||
|             </el-form-item> | ||||
|           </el-col> | ||||
|           <el-col :lg="6" :md="12" :sm="12" :xs="24"> | ||||
|             <el-form-item label="关键字"> | ||||
|               <el-input | ||||
|                 clearable | ||||
|                 v-model.trim="form.keywords" | ||||
|                 placeholder="请输入" | ||||
|               /> | ||||
|             </el-form-item> | ||||
|           </el-col> | ||||
|           <el-col :lg="6" :md="12" :sm="12" :xs="24"> | ||||
|             <el-form-item label="知识点"> | ||||
|               <el-input | ||||
|                 clearable | ||||
|                 v-model.trim="form.pointNames" | ||||
|                 placeholder="请输入" | ||||
|               /> | ||||
|             </el-form-item> | ||||
|           </el-col> | ||||
|           | ||||
|           <el-col :lg="6" :md="12" :sm="12" :xs="24"> | ||||
|             <el-form-item label-width="16px"> | ||||
|               <el-button type="primary" @click="search">查询</el-button> | ||||
|               <el-button @click="reset">重置</el-button> | ||||
|             </el-form-item> | ||||
|           </el-col> | ||||
|         </el-row> | ||||
|       </el-form> | ||||
|     </ele-card> | ||||
|   </template> | ||||
|    | ||||
|   <script setup> | ||||
|     import { useFormData } from '@/utils/use-form-data'; | ||||
|    | ||||
|     const emit = defineEmits(['search']); | ||||
|    | ||||
|     /** 表单数据 */ | ||||
|     const [form, resetFields] = useFormData({ | ||||
|       taskId: '', | ||||
|       spName: '', | ||||
|       quLevel: '', | ||||
|       keywords: '', | ||||
|       pointNames: '', | ||||
|       quNumbers: '', | ||||
|       quScores: '', | ||||
|       subtotalScore: '' | ||||
|     }); | ||||
|    | ||||
|     /** 搜索 */ | ||||
|     const search = () => { | ||||
|       emit('search', { ...form }); | ||||
|     }; | ||||
|    | ||||
|     /**  重置 */ | ||||
|     const reset = () => { | ||||
|       resetFields(); | ||||
|       search(); | ||||
|     }; | ||||
|   </script> | ||||
|    | ||||
							
								
								
									
										223
									
								
								src/views/task/exam/components/steps/step1/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								src/views/task/exam/components/steps/step1/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,223 @@ | ||||
| <template> | ||||
|  | ||||
|   <ContentWrap> | ||||
|      <el-form | ||||
|       class="-mb-15px" | ||||
|       :model="queryParams" | ||||
|       ref="queryFormRef" | ||||
|       :inline="true" | ||||
|       label-width="68px" | ||||
|     > | ||||
|     <el-form-item label="题型" prop="spName"> | ||||
|         <el-select v-model="queryParams.spName" placeholder="请选择题型" clearable     class="!w-240px"> | ||||
|           <el-option | ||||
|             v-for="item in CourseOptions" | ||||
|             :key="item" | ||||
|             :label="item" | ||||
|             :value="item" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item> | ||||
|         <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> | ||||
|         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> | ||||
|         <el-button | ||||
|           type="primary" | ||||
|           plain | ||||
|           @click="openEdit('create')" | ||||
|           v-hasPermi="['system:sms-channel:create']" | ||||
|         > | ||||
|           <Icon icon="ep:plus" class="mr-5px" /> 新增</el-button | ||||
|         > | ||||
|       </el-form-item> | ||||
|     </el-form>  | ||||
|   </ContentWrap> | ||||
|  | ||||
|   <!-- 列表 --> | ||||
|   <ContentWrap> | ||||
|     <el-table v-loading="loading" :data="list"> | ||||
|       <el-table-column label="方案ID" align="center" prop="taskId" /> | ||||
|       <el-table-column label="题型" align="center" prop="spName" /> | ||||
|       <el-table-column label="难度" align="center" prop="quLevel"> | ||||
|       <template #default="scope"> | ||||
|         <span v-if="scope.row.quLevel === '0'">简单</span> | ||||
|         <span v-else-if="scope.row.quLevel === '1'">一般</span> | ||||
|         <span v-else-if="scope.row.quLevel === '2'">困难</span> | ||||
|         <span v-else-if="scope.row.quLevel === '3'">全部</span> | ||||
|         <span v-else>未知</span> | ||||
|       </template> | ||||
|     </el-table-column> | ||||
|  | ||||
|  | ||||
|       <!-- <el-table-column label="关键字" align="center" prop="keywords" /> --> | ||||
|       <el-table-column label="试题数量" align="center" prop="quNumbers"/> | ||||
|  | ||||
|       <el-table-column label="每题分数" align="center" prop="quScores"/> | ||||
|       <el-table-column label="小计分数" align="center" prop="subtotalScore"/> | ||||
|        | ||||
|       <el-table-column label="操作" align="center"> | ||||
|         <template #default="scope"> | ||||
|           <el-button | ||||
|             link | ||||
|             type="primary" | ||||
|             @click="openEdit('update', scope.row)" | ||||
|             v-hasPermi="['system:sms-channel:update']" | ||||
|           > | ||||
|             查看 | ||||
|           </el-button> | ||||
|           <el-button | ||||
|             link | ||||
|             type="danger" | ||||
|             @click="handleDelete(scope.row.schemeId)" | ||||
|             v-hasPermi="['system:sms-channel:delete']" | ||||
|           > | ||||
|             删除 | ||||
|           </el-button> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|     </el-table> | ||||
|     <!-- 分页 --> | ||||
|     <Pagination | ||||
|       :total="total" | ||||
|       v-model:page="queryParams.pageNo" | ||||
|       v-model:limit="queryParams.pageSize" | ||||
|       @pagination="getList" | ||||
|     /> | ||||
|   </ContentWrap> | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| <scheme-edit v-model="showEdit" v-if="showEdit" :form-data="props.formData" :task-specialty="props.taskSpecialty"   :task-Id="props.taskId"  ref="taskEditRef"   @done="reload"/> | ||||
|  | ||||
| </template> | ||||
| <script lang="ts" setup> | ||||
| import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' | ||||
|  | ||||
| import * as SmsChannelApi from '@/api/system/scheme'; | ||||
|  | ||||
| import SchemeEdit from './components/step-edit.vue'; | ||||
| import SchemeSearch from './components/step-search.vue'; | ||||
| const CourseOptions = ref<string[]>([]) | ||||
|   import { | ||||
|   getCourseList, | ||||
| } from '@/api/system/scheme' | ||||
|  | ||||
| defineOptions({ name: 'SystemSmsChannel' }) | ||||
|  | ||||
| const { t } = useI18n() // 国际化 | ||||
| const message = useMessage() // 消息弹窗 | ||||
| const props = defineProps({ | ||||
|   taskSpecialty: { | ||||
|     type: String, | ||||
|     default: '' | ||||
|   }, | ||||
|   taskId: { | ||||
|     type: String, | ||||
|     default: '' | ||||
|   }, | ||||
|   formData:{ | ||||
|     type: Object, | ||||
|     default: null | ||||
|   } | ||||
| }) | ||||
|  | ||||
|   /** 当前编辑数据 */ | ||||
|   const current = ref<object>(); | ||||
|  | ||||
|   /** 是否显示编辑弹窗 */ | ||||
|   const showEdit = ref(false); | ||||
|   const showOpenTemplate = ref(false); | ||||
|  | ||||
|   const showAdd = ref(false); | ||||
|  | ||||
|  | ||||
|  | ||||
| const smsChannelFormRef = ref() | ||||
| const taskEditRef = ref() | ||||
| const taskAddRef = ref() | ||||
| const taskTempRef = ref() | ||||
|  | ||||
|  | ||||
|  | ||||
| const loading = ref(false) // 列表的加载中 | ||||
| const total = ref(0) // 列表的总页数 | ||||
| const list = ref([]) // 列表的数据 | ||||
| const queryFormRef = ref() // 搜索的表单 | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   signature: undefined, | ||||
|   status: undefined, | ||||
|   createTime: [], | ||||
|   taskId: props.taskId, | ||||
|   spName:undefined | ||||
| }) | ||||
|  | ||||
| /** 查询列表 */ | ||||
| const getList = async () => { | ||||
|       // 1. 并行拉下拉所需的选项 | ||||
|       const [courses] = await Promise.all([ | ||||
|     getCourseList(), | ||||
|  | ||||
|   ]) | ||||
|   CourseOptions.value = courses | ||||
|   loading.value = true | ||||
|   try { | ||||
|     const res = await SmsChannelApi.pageSchemes(queryParams) | ||||
|     console.log(res) | ||||
|     list.value = res | ||||
|     total.value = res.total | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** 搜索按钮操作 */ | ||||
| const handleQuery = () => { | ||||
|   queryParams.pageNo = 1 | ||||
|   getList() | ||||
| } | ||||
|  | ||||
| /** 重置按钮操作 */ | ||||
| const resetQuery = () => { | ||||
|   queryFormRef.value.resetFields() | ||||
|   handleQuery() | ||||
| } | ||||
| const reload = () => { | ||||
|   getList() | ||||
| } | ||||
| /** 添加/修改操作 */ | ||||
| const formRef = ref(); | ||||
|  | ||||
|  | ||||
| const openEdit = (type: string, row?: object) => { | ||||
|   showEdit.value = true | ||||
|   current.value = row | ||||
|   nextTick(() => { | ||||
|     taskEditRef.value?.open(type, row) | ||||
|   }) | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| /** 删除按钮操作 */ | ||||
| const handleDelete = async (id: number) => { | ||||
|   try { | ||||
|     // 删除的二次确认 | ||||
|     await message.delConfirm() | ||||
|     // 发起删除 | ||||
| console.log(id+"idididid") | ||||
|     await SmsChannelApi.removeScheme(id) | ||||
|     message.success(t('common.delSuccess')) | ||||
|     // 刷新列表 | ||||
|     await getList() | ||||
|   } catch {} | ||||
| } | ||||
|  | ||||
| /** 初始化 **/ | ||||
| onMounted(() => { | ||||
|   getList() | ||||
| }) | ||||
|  | ||||
| </script> | ||||
| @@ -0,0 +1,106 @@ | ||||
| <!-- 编辑弹窗 --> | ||||
| <template> | ||||
| <Dialog v-model="isVisible"    :title="'添加试卷'" width="460"    @open="handleOpen" center> | ||||
|       <el-form | ||||
|         ref="formRef" | ||||
|         :model="form" | ||||
|         :rules="rules" | ||||
|         label-width="80px" | ||||
|         @submit.prevent="" | ||||
|       > | ||||
|         <el-form-item label="试卷数目" prop="num"> | ||||
|           <el-input | ||||
|             clearable | ||||
|             v-model="form.num" | ||||
|             placeholder="请输入试卷数目" | ||||
|           /> | ||||
|         </el-form-item> | ||||
|         | ||||
|       </el-form> | ||||
|       <template #footer> | ||||
|         <el-button @click="handleCancel">取消</el-button> | ||||
|         <el-button type="primary" :loading="loading" @click="save"> | ||||
|           保存 | ||||
|         </el-button> | ||||
|       </template> | ||||
|     </Dialog> | ||||
|   </template> | ||||
|   <script setup> | ||||
|     import { ref, reactive, nextTick } from 'vue'; | ||||
|    | ||||
|     import { useFormData } from '@/utils/use-form-data'; | ||||
|     import { addPaper } from '@/api/system/paper'; | ||||
|     const message = useMessage() // 消息弹窗 | ||||
|     const props = defineProps({ | ||||
|       /** 修改回显的数据 */ | ||||
|       data: Object, | ||||
|       taskId: String , | ||||
|       taskSpecialty: String, | ||||
|     }); | ||||
|    | ||||
|     const emit = defineEmits(['done']); | ||||
|    | ||||
|     /** 弹窗是否打开 */ | ||||
|     const isVisible = defineModel({ type: Boolean }); | ||||
|    | ||||
|     /** 提交状态 */ | ||||
|     const loading = ref(false); | ||||
|    | ||||
|     /** 表单实例 */ | ||||
|     const formRef = ref(null); | ||||
|    | ||||
|     /** 表单数据 */ | ||||
|     const [form, resetFields, assignFields] = useFormData({ | ||||
|       num: '', | ||||
|       taskId: '' | ||||
|     }); | ||||
|    | ||||
|     /** 表单验证规则 */ | ||||
|     const rules = reactive({ | ||||
|       num: [ | ||||
|         { required: true, message: '请输入试卷数目', trigger: 'blur' }, | ||||
|         { pattern: /^[1-9]\d*$/, message: '请输入正整数', trigger: 'blur' } | ||||
|       ] | ||||
|     }); | ||||
|  | ||||
|     /** 关闭弹窗 */ | ||||
|     const handleCancel = () => { | ||||
|       isVisible.value = false; | ||||
|     }; | ||||
|    | ||||
|     /** 保存编辑 */ | ||||
|     const save = () => { | ||||
|   formRef.value?.validate?.((valid) => { | ||||
|     if (!valid) { | ||||
|       return; | ||||
|     } | ||||
|     loading.value = true; | ||||
|     addPaper({ num: form.num,taskid: props.taskId ,taskSpecialty:form.taskSpecialty}) | ||||
|       .then((msg) => { | ||||
|         loading.value = false; | ||||
|         message.success(msg); | ||||
|         handleCancel(); | ||||
|         emit('done'); | ||||
|       }) | ||||
|       .catch((e) => { | ||||
|         loading.value = false; | ||||
|         message.error(e.message); | ||||
|       }); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
|    | ||||
|     /** 弹窗打开事件 */ | ||||
|     const handleOpen = () => { | ||||
|         resetFields(); | ||||
|         form.taskId = props.taskId; // 新增时赋值 taskId | ||||
|         form.taskSpecialty = props.taskSpecialty; // 新增时赋值 taskSpecialty | ||||
|       nextTick(() => { | ||||
|         nextTick(() => { | ||||
|           formRef.value?.clearValidate?.(); | ||||
|         }); | ||||
|       }); | ||||
|     }; | ||||
|     defineExpose({ open }) | ||||
|   </script> | ||||
|    | ||||
| @@ -0,0 +1,181 @@ | ||||
| <template> | ||||
|  | ||||
|  | ||||
|   <Dialog v-model="visible"    :title="'修改试卷'" width="460"    @open="handleOpen" center> | ||||
|    | ||||
|    | ||||
|       <div style="margin-bottom: 16px"> | ||||
|         <strong>试卷ID:</strong>{{ paperId }} | ||||
|       </div> | ||||
|    | ||||
|       <!-- 滚动区域 --> | ||||
|       <div style="max-height: 400px; overflow-y: auto;"> | ||||
|         <!-- 分组展示题目 --> | ||||
|         <div  | ||||
|           v-for="(items, subject) in sortedGroupedQuestions" | ||||
|           :key="subject" | ||||
|         > | ||||
|         <h4 class="mb-2 text-base text-gray-700 font-medium"> | ||||
|     {{ subject }}  | ||||
|     <span v-if="schemeMap[subject]" class="text-sm text-gray-500 font-normal"> | ||||
|       (每小题 {{ parseFloat(schemeMap[subject].quScores).toFixed(1) }} 分,共 {{ parseFloat(schemeMap[subject].subtotalScore).toFixed(1) }} 分) | ||||
|     </span> | ||||
|   </h4> | ||||
|    | ||||
|    | ||||
|    | ||||
|           <el-card | ||||
|             v-for="(item, index) in items" | ||||
|             :key="item.quId" | ||||
|             class="mb-3" | ||||
|             shadow="never" | ||||
|           > | ||||
|             <div class="text-gray-500 mb-2">题目 {{ index + 1 }}</div> | ||||
|             <div v-html="item.content" class="mb-2"></div> | ||||
|    | ||||
|             <!-- 🎯 判断是否为编程题 --> | ||||
|             <div v-if="item.subjectName === '编程题'"> | ||||
|               <!-- 编程题解析 --> | ||||
|               <div class="mt-2"> | ||||
|                 <div class="font-bold text-blue-600">解析</div> | ||||
|                 <pre | ||||
|                   style="background-color: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto;" | ||||
|                 >{{ item.analysis }}</pre> | ||||
|               </div> | ||||
|               <div | ||||
|                 v-for="(answer, aIndex) in item.answerList" | ||||
|                 :key="answer.answerId" | ||||
|                 class="mb-1" | ||||
|               > | ||||
|                 测试用例 {{ aIndex + 1 }}:输入: {{ answer.contentIn }} | 输出: {{ answer.content }} | 占比: {{ answer.scoreRate }}% | ||||
|               </div> | ||||
|             </div> | ||||
|    | ||||
|             <!-- 📝 普通题目的选项显示 --> | ||||
|             <div v-else-if="item.answerList && item.answerList.length"> | ||||
|               <div | ||||
|                 v-for="(answer, aIndex) in item.answerList.slice().sort((a, b) => a.sort - b.sort)" | ||||
|                 :key="answer.answerId" | ||||
|                 :style="{ | ||||
|                   color: answer.isRight === '0' ? 'green' : 'inherit', | ||||
|                   fontWeight: answer.isRight === '0' ? 'bold' : 'normal' | ||||
|                 }" | ||||
|                 class="mb-1" | ||||
|               > | ||||
|                 {{ String.fromCharCode(65 + aIndex) }}. {{ answer.content }} | ||||
|               </div> | ||||
|             </div> | ||||
|           </el-card> | ||||
|         </div> | ||||
|       </div> | ||||
|    | ||||
|       <template #footer> | ||||
|         <el-button @click="handleCancel">取消</el-button> | ||||
|        | ||||
|       </template> | ||||
|     </Dialog> | ||||
|   </template> | ||||
|    | ||||
|   <script setup> | ||||
|   import { ref, reactive, nextTick, computed } from 'vue'; | ||||
|   import { useFormData } from '@/utils/use-form-data'; | ||||
|   import { getPaperDetailByTaskId } from '@/api/system/paper'; | ||||
|   const message = useMessage() // 消息弹窗 | ||||
|    | ||||
|   const props = defineProps({ | ||||
|     paperId: String | ||||
|   }); | ||||
|    | ||||
|   const emit = defineEmits(['done']); | ||||
|   const visible = defineModel({ type: Boolean }); | ||||
|   const loading = ref(false); | ||||
|   const formRef = ref(null); | ||||
|   const questionList = ref([]); // 用于存放试题列表 | ||||
|   const schemeMap = ref({}); | ||||
|    | ||||
|   const [form, resetFields, assignFields] = useFormData({ | ||||
|     paperId: void 0, | ||||
|     taskId: '', | ||||
|     counts: '', | ||||
|     rollUp: '', | ||||
|     isAb: '', | ||||
|     status: '' | ||||
|   }); | ||||
|    | ||||
|   const rules = reactive({}); | ||||
|    | ||||
|   const handleCancel = () => { | ||||
|     visible.value = false; | ||||
|   }; | ||||
|    | ||||
|    | ||||
|    | ||||
|   const groupedQuestions = ref({}); | ||||
|    | ||||
|   const subjectPriority = { | ||||
|     '选择题': 1, | ||||
|     '多选题': 2, | ||||
|     '判断题': 3, | ||||
|     '编程题': 4, | ||||
|     '其他': 5 | ||||
|   }; | ||||
|    | ||||
|   const groupBySubjectName = (list) => { | ||||
|     const group = {}; | ||||
|    | ||||
|     list.forEach((item) => { | ||||
|       const subject = item.subjectName || '其他'; // 默认为'其他' | ||||
|       if (!group[subject]) { | ||||
|         group[subject] = []; | ||||
|       } | ||||
|       group[subject].push(item); | ||||
|     }); | ||||
|    | ||||
|     groupedQuestions.value = group; | ||||
|   }; | ||||
|    | ||||
|   const sortedGroupedQuestions = computed(() => { | ||||
|     return Object.keys(groupedQuestions.value) | ||||
|       .sort((a, b) => (subjectPriority[a] || subjectPriority['其他']) - (subjectPriority[b] || subjectPriority['其他'])) | ||||
|       .reduce((acc, subject) => { | ||||
|         acc[subject] = groupedQuestions.value[subject]; | ||||
|         return acc; | ||||
|       }, {}); | ||||
|   }); | ||||
|    | ||||
|   const handleOpen = () => { | ||||
|     resetFields(); | ||||
|     questionList.value = []; | ||||
|    | ||||
|     if (props.paperId) { | ||||
|       loading.value = true; | ||||
|       getPaperDetailByTaskId(props.paperId) | ||||
|         .then((res) => { | ||||
|           const { educationPaperSchemeList, examQuestionList } = res.data || {}; | ||||
|           if (Array.isArray(examQuestionList)) { | ||||
|             questionList.value = examQuestionList; | ||||
|             groupBySubjectName(examQuestionList); // 分类 | ||||
|           } | ||||
|    | ||||
|           if (Array.isArray(educationPaperSchemeList)) { | ||||
|             // 构建以题型名为 key 的映射表(spName 与 scheme 对应) | ||||
|             schemeMap.value = educationPaperSchemeList.reduce((acc, item) => { | ||||
|               acc[item.spName] = item; | ||||
|               return acc; | ||||
|             }, {}); | ||||
|             } | ||||
|           assignFields(res);  | ||||
|         }) | ||||
|         .catch((e) => { | ||||
|           message.error(`获取试卷详情失败: ${e.message}`); | ||||
|         }) | ||||
|         .finally(() => { | ||||
|           loading.value = false; | ||||
|           nextTick(() => { | ||||
|             formRef.value?.clearValidate?.(); | ||||
|           }); | ||||
|         }); | ||||
|     } | ||||
|   }; | ||||
|   </script> | ||||
|    | ||||
| @@ -0,0 +1,183 @@ | ||||
| <template> | ||||
|  | ||||
|  | ||||
| <Dialog v-model="visible"    :title="'查看试卷'" width="860"    @open="handleOpen" center> | ||||
|  | ||||
|  | ||||
|   <div style="margin-bottom: 16px"> | ||||
|   <strong>试卷ID:</strong> | ||||
|   {{ paperId ? paperId : '未提供试卷ID' }} | ||||
| </div> | ||||
|  | ||||
|  | ||||
|     <!-- 滚动区域 --> | ||||
|     <div style="max-height: 400px; overflow-y: auto;"> | ||||
|       <!-- 分组展示题目 --> | ||||
|       <div  | ||||
|         v-for="(items, subject) in sortedGroupedQuestions" | ||||
|         :key="subject" | ||||
|       > | ||||
|       <h4 class="mb-2 text-base text-gray-700 font-medium"> | ||||
|   {{ subject }}  | ||||
|   <span v-if="schemeMap[subject]" class="text-sm text-gray-500 font-normal"> | ||||
|     (每小题 {{ parseFloat(schemeMap[subject].quScores).toFixed(1) }} 分,共 {{ parseFloat(schemeMap[subject].subtotalScore).toFixed(1) }} 分) | ||||
|   </span> | ||||
| </h4> | ||||
|  | ||||
|  | ||||
|  | ||||
|         <el-card | ||||
|           v-for="(item, index) in items" | ||||
|           :key="item.quId" | ||||
|           class="mb-3" | ||||
|           shadow="never" | ||||
|         > | ||||
|           <div class="text-gray-500 mb-2">题目 {{ index + 1 }}</div> | ||||
|           <div v-html="item.content" class="mb-2"></div> | ||||
|  | ||||
|           <!-- 🎯 判断是否为编程题 --> | ||||
|           <div v-if="item.subjectName === '编程题'"> | ||||
|             <!-- 编程题解析 --> | ||||
|             <div class="mt-2"> | ||||
|               <div class="font-bold text-blue-600">解析</div> | ||||
|               <pre | ||||
|                 style="background-color: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto;" | ||||
|               >{{ item.analysis }}</pre> | ||||
|             </div> | ||||
|             <div | ||||
|               v-for="(answer, aIndex) in item.answerList" | ||||
|               :key="answer.answerId" | ||||
|               class="mb-1" | ||||
|             > | ||||
|               测试用例 {{ aIndex + 1 }}:输入: {{ answer.contentIn }} | 输出: {{ answer.content }} | 占比: {{ answer.scoreRate }}% | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
|           <!-- 📝 普通题目的选项显示 --> | ||||
|           <div v-else-if="item.answerList && item.answerList.length"> | ||||
|             <div | ||||
|               v-for="(answer, aIndex) in item.answerList.slice().sort((a, b) => a.sort - b.sort)" | ||||
|               :key="answer.answerId" | ||||
|               :style="{ | ||||
|                 color: answer.isRight === '0' ? 'green' : 'inherit', | ||||
|                 fontWeight: answer.isRight === '0' ? 'bold' : 'normal' | ||||
|               }" | ||||
|               class="mb-1" | ||||
|             > | ||||
|               {{ String.fromCharCode(65 + aIndex) }}. {{ answer.content }} | ||||
|             </div> | ||||
|           </div> | ||||
|         </el-card> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <template #footer> | ||||
|       <el-button @click="handleCancel">取消</el-button> | ||||
|      | ||||
|     </template> | ||||
|   </Dialog> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| import { ref, reactive, nextTick, computed } from 'vue'; | ||||
| import { useFormData } from '@/utils/use-form-data'; | ||||
| import { getPaperDetailByTaskId } from '@/api/system/paper'; | ||||
| const message = useMessage(); // 消息弹窗 | ||||
|  | ||||
| const props = defineProps({ | ||||
|   paperId: String // 接收父组件传来的 paperId | ||||
| }); | ||||
|  | ||||
| const emit = defineEmits(['done']); | ||||
| const visible = defineModel({ type: Boolean }); | ||||
| const loading = ref(false); | ||||
| const formRef = ref(null); | ||||
| const questionList = ref([]); // 用于存放试题列表 | ||||
| const schemeMap = ref({}); | ||||
|  | ||||
| const [form, resetFields, assignFields] = useFormData({ | ||||
|   paperId: void 0, | ||||
|   taskId: '', | ||||
|   counts: '', | ||||
|   rollUp: '', | ||||
|   isAb: '', | ||||
|   status: '' | ||||
| }); | ||||
|  | ||||
| const rules = reactive({}); | ||||
|  | ||||
| const handleCancel = () => { | ||||
|   visible.value = false; | ||||
| }; | ||||
|  | ||||
| const groupedQuestions = ref({}); | ||||
|  | ||||
| const subjectPriority = { | ||||
|   '选择题': 1, | ||||
|   '多选题': 2, | ||||
|   '判断题': 3, | ||||
|   '编程题': 4, | ||||
|   '其他': 5 | ||||
| }; | ||||
|  | ||||
| // 根据试题的类型进行分组 | ||||
| const groupBySubjectName = (list) => { | ||||
|   const group = {}; | ||||
|   list.forEach((item) => { | ||||
|     const subject = item.subjectName || '其他'; // 默认为'其他' | ||||
|     if (!group[subject]) { | ||||
|       group[subject] = []; | ||||
|     } | ||||
|     group[subject].push(item); | ||||
|   }); | ||||
|   groupedQuestions.value = group; | ||||
| }; | ||||
|  | ||||
| // 按题型排序 | ||||
| const sortedGroupedQuestions = computed(() => { | ||||
|   return Object.keys(groupedQuestions.value) | ||||
|     .sort((a, b) => (subjectPriority[a] || subjectPriority['其他']) - (subjectPriority[b] || subjectPriority['其他'])) | ||||
|     .reduce((acc, subject) => { | ||||
|       acc[subject] = groupedQuestions.value[subject]; | ||||
|       return acc; | ||||
|     }, {}); | ||||
| }); | ||||
|  | ||||
| // 打开弹窗时的处理逻辑 | ||||
| const handleOpen = (type, paperId) => { | ||||
|   resetFields(); | ||||
|   questionList.value = []; | ||||
| console.log(paperId+"paperIdpaperId") | ||||
|   if (props.paperId) { | ||||
|     loading.value = true; | ||||
|     getPaperDetailByTaskId(props.paperId) | ||||
|       .then((res) => { | ||||
|         const { educationPaperSchemeList, examQuestionList } = res || {}; | ||||
|         if (Array.isArray(examQuestionList)) { | ||||
|           questionList.value = examQuestionList; | ||||
|           groupBySubjectName(examQuestionList); // 分类 | ||||
|         } | ||||
|  | ||||
|         if (Array.isArray(educationPaperSchemeList)) { | ||||
|           // 构建以题型名为 key 的映射表(spName 与 scheme 对应) | ||||
|           schemeMap.value = educationPaperSchemeList.reduce((acc, item) => { | ||||
|             acc[item.spName] = item; | ||||
|             return acc; | ||||
|           }, {}); | ||||
|         } | ||||
|         assignFields(res);  | ||||
|       }) | ||||
|       .catch((e) => { | ||||
|         message.error(`获取试卷详情失败: ${e.message}`); | ||||
|       }) | ||||
|       .finally(() => { | ||||
|         loading.value = false; | ||||
|         nextTick(() => { | ||||
|           formRef.value?.clearValidate?.(); | ||||
|         }); | ||||
|       }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| </script> | ||||
|  | ||||
| @@ -0,0 +1,72 @@ | ||||
| <!-- 搜索表单 --> | ||||
| <template> | ||||
|     <ele-card :body-style="{ paddingBottom: '2px' }"> | ||||
|       <el-form label-width="72px" @keyup.enter="search" @submit.prevent=""> | ||||
|         <el-row :gutter="8"> | ||||
|           <el-col :lg="6" :md="12" :sm="12" :xs="24"> | ||||
|             <el-form-item label="试卷编号"> | ||||
|               <el-input | ||||
|                 clearable | ||||
|                 v-model.trim="form.paperId" | ||||
|                 placeholder="请输入" | ||||
|               /> | ||||
|             </el-form-item> | ||||
|           </el-col> | ||||
|      | ||||
|           <el-col :lg="6" :md="12" :sm="12" :xs="24"> | ||||
|             <el-form-item label="抽卷方式"> | ||||
|               <el-select v-model="form.rollUp" placeholder="请选择抽卷方式"> | ||||
|                 <el-option :label="'固定'" :value="0" /> | ||||
|                 <el-option :label="'AB卷'" :value="1" /> | ||||
|                 <el-option :label="'随机'" :value="2" /> | ||||
|                 <el-option :label="'自选'" :value="3" /> | ||||
|               </el-select> | ||||
|             </el-form-item> | ||||
|           </el-col> | ||||
|  | ||||
|          | ||||
|           <el-col :lg="6" :md="12" :sm="12" :xs="24"> | ||||
|             <el-form-item label="状态"> | ||||
|             <dict-data | ||||
|                 code="sys_common_status_other" | ||||
|                 type="select" | ||||
|                 v-model="form.status" | ||||
|               /> | ||||
|             </el-form-item> | ||||
|           </el-col> | ||||
|           <el-col :lg="6" :md="12" :sm="12" :xs="24"> | ||||
|             <el-form-item label-width="16px"> | ||||
|               <el-button type="primary" @click="search">查询</el-button> | ||||
|               <el-button @click="reset">重置</el-button> | ||||
|             </el-form-item> | ||||
|           </el-col> | ||||
|         </el-row> | ||||
|       </el-form> | ||||
|     </ele-card> | ||||
|   </template> | ||||
|    | ||||
|   <script setup> | ||||
|     import { useFormData } from '@/utils/use-form-data'; | ||||
|     const emit = defineEmits(['search']); | ||||
|    | ||||
|     /** 表单数据 */ | ||||
|     const [form, resetFields] = useFormData({ | ||||
|       paperId: '', | ||||
|       counts: '', | ||||
|       rollUp: '', | ||||
|       isAb: '', | ||||
|       status: '', | ||||
|     }); | ||||
|    | ||||
|     /** 搜索 */ | ||||
|     const search = () => { | ||||
|       emit('search', { ...form }); | ||||
|     }; | ||||
|    | ||||
|     /**  重置 */ | ||||
|     const reset = () => { | ||||
|       resetFields(); | ||||
|       search(); | ||||
|     }; | ||||
|   </script> | ||||
|    | ||||
| @@ -0,0 +1,336 @@ | ||||
| <!-- 编辑弹窗 --> | ||||
| <template> | ||||
|   <Dialog v-model="visible"    :title="'试卷调整'" width="460"    @open="handleOpen" center> | ||||
|     <el-form | ||||
|       ref="formRef" | ||||
|       :model="form" | ||||
|       :rules="rules" | ||||
|       label-width="80px" | ||||
|       @submit.prevent="" | ||||
|     > | ||||
|    | ||||
|       <!-- 抽卷方式 --> | ||||
|       <el-form-item label="抽卷方式" prop="type"> | ||||
|         <el-radio-group v-model="form.type"> | ||||
|           <el-radio :label="0">固定</el-radio> | ||||
|           <el-radio :label="1">AB卷</el-radio> | ||||
|           <el-radio :label="2">随机</el-radio> | ||||
|           <el-radio :label="3">自选</el-radio> | ||||
|         </el-radio-group> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <!-- 固定卷:显示一个下拉 --> | ||||
|       <el-form-item label="选择试卷" v-if="form.type === 0" prop="paper"> | ||||
|         <el-select v-model="form.paper" placeholder="请选择试卷"> | ||||
|           <el-option | ||||
|             v-for="item in PaperOptions" | ||||
|             :key="item.value" | ||||
|             :label="item.label" | ||||
|             :value="item.value" | ||||
|           /> | ||||
|         </el-select> | ||||
|  | ||||
|  | ||||
|       </el-form-item> | ||||
|  | ||||
|       <!-- AB卷:显示两个下拉 --> | ||||
|       <el-form-item label="试卷A" v-if="form.type === 1" prop="paperA"> | ||||
|         <el-select v-model="form.paperA" placeholder="请选择试卷A"> | ||||
|           <el-option | ||||
|             v-for="item in PaperOptions" | ||||
|             :key="item.value" | ||||
|             :label="item.label" | ||||
|             :value="item.value" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <el-form-item label="试卷B" v-if="form.type === 1" prop="paperB"> | ||||
|         <el-select v-model="form.paperB" placeholder="请选择试卷B"> | ||||
|           <el-option | ||||
|             v-for="item in PaperOptions" | ||||
|             :key="item.value" | ||||
|             :label="item.label" | ||||
|             :value="item.value" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|  | ||||
|     <template #footer> | ||||
|       <el-button @click="handleCancel">取消</el-button> | ||||
|       <el-button type="primary" :loading="loading" @click="save">保存</el-button> | ||||
|     </template> | ||||
|   | ||||
|  | ||||
|   </Dialog> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| import { ref, reactive, nextTick,onMounted,watch} from 'vue'; | ||||
| import { useFormData } from '@/utils/use-form-data'; | ||||
| import { updatePaper ,getPaperList} from '@/api/system/paper'; | ||||
| const message = useMessage() // 消息弹窗 | ||||
|  | ||||
| const props = defineProps({ | ||||
|   /** 修改回显的数据 */ | ||||
|   data: Object, | ||||
|   taskId: String, | ||||
| }); | ||||
|  | ||||
| const emit = defineEmits(['done']); | ||||
|  | ||||
| /** 弹窗是否打开 */ | ||||
| const visible = defineModel({ type: Boolean }); | ||||
|  | ||||
| /** 提交状态 */ | ||||
| const loading = ref(false); | ||||
|  | ||||
| /** 表单实例 */ | ||||
| const formRef = ref(null); | ||||
|  | ||||
| /** 试卷下拉*/ | ||||
| const PaperOptions = ref([]); | ||||
| /** 模拟试卷列表(建议替换为真实接口) */ | ||||
|  | ||||
|  | ||||
| /** 表单数据 */ | ||||
| const [form, resetFields, assignFields] = useFormData({ | ||||
|   num: '', | ||||
|   taskId: '', | ||||
|   type: 0,        // 抽卷方式:0固定 1AB卷 2随机 | ||||
|   paper: '',      // 固定使用 | ||||
|   paperA: '',     // AB卷使用 | ||||
|   paperB: ''      // AB卷使用 | ||||
| }); | ||||
|  | ||||
| /** 表单验证规则 */ | ||||
| const rules = reactive({ | ||||
|   type: [{ required: true, message: '请选择抽卷方式', trigger: 'change' }], | ||||
|   paper: [ | ||||
|     { | ||||
|       required: true, | ||||
|       trigger: 'change', | ||||
|       validator: (rule, value, callback) => { | ||||
|         if (form.type === 0 && !value) { | ||||
|           callback(new Error('请选择试卷')); | ||||
|         } else { | ||||
|           callback(); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   ], | ||||
|   paperA: [ | ||||
|     { | ||||
|       required: true, | ||||
|       trigger: 'change', | ||||
|       validator: (rule, value, callback) => { | ||||
|         if (form.type === 1 && !value) { | ||||
|           callback(new Error('请选择试卷A')); | ||||
|         } else { | ||||
|           callback(); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   ], | ||||
|   paperB: [ | ||||
|     { | ||||
|       required: true, | ||||
|       trigger: 'change', | ||||
|       validator: (rule, value, callback) => { | ||||
|         if (form.type === 1 && !value) { | ||||
|           callback(new Error('请选择试卷B')); | ||||
|         } else { | ||||
|           callback(); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   ] | ||||
| }); | ||||
|  | ||||
|  | ||||
| /** 取消关闭弹窗 */ | ||||
| const handleCancel = () => { | ||||
|   visible.value = false; | ||||
| }; | ||||
|  | ||||
| /** 保存编辑(不关闭弹窗) */ | ||||
| const save = () => { | ||||
|   formRef.value?.validate?.((valid) => { | ||||
|     if (!valid) return; | ||||
|  | ||||
|     loading.value = true; | ||||
|  | ||||
|     const type = form.type; | ||||
|  | ||||
|     let payload = { | ||||
|       rollUp: 0,  // 默认值 | ||||
|     }; | ||||
|  | ||||
|     if (type === 0) { | ||||
|       console.log("0放大请求") | ||||
|       // 固定卷 | ||||
|       if (form.paper) { | ||||
|         payload = { | ||||
|           paperId: form.paper,  // 修改为字符串,不是数组 | ||||
|           rollUp: 0 | ||||
|         }; | ||||
|         send(payload) | ||||
|           .then(() => { | ||||
|             message.success('保存成功'); | ||||
|             emit('done'); | ||||
|           }) | ||||
|           .catch((e) => { | ||||
|             message.error(e.message); | ||||
|           }) | ||||
|           .finally(() => { | ||||
|             loading.value = false; | ||||
|           }); | ||||
|       } else { | ||||
|         message.error('请选择试卷'); | ||||
|         loading.value = false; | ||||
|       } | ||||
|     } else if (type === 1) { | ||||
|       console.log("1放大请求") | ||||
|       // AB卷,分别发送 A卷 和 B卷 | ||||
|       if (form.paperA === form.paperB) { | ||||
|         message.error('试卷A和试卷B不能相同'); | ||||
|   loading.value = false; | ||||
|   return; | ||||
| } | ||||
|  | ||||
|       if (form.paperA && form.paperB) { | ||||
|         const payloadA = { | ||||
|           paperId: form.paperA,  // 修改为字符串,不是数组 | ||||
|           rollUp: 1, | ||||
|           isAb: 0 | ||||
|         }; | ||||
|         const payloadB = { | ||||
|           paperId: form.paperB,  // 修改为字符串,不是数组 | ||||
|           rollUp: 1, | ||||
|           isAb: 1 | ||||
|         }; | ||||
|  | ||||
|         Promise.all([send(payloadA), send(payloadB)]) | ||||
|           .then(() => { | ||||
|             message.success('保存成功'); | ||||
|             emit('done'); | ||||
|           }) | ||||
|           .catch((e) => { | ||||
|             message.error(e.message); | ||||
|           }) | ||||
|           .finally(() => { | ||||
|             loading.value = false; | ||||
|           }); | ||||
|       } else { | ||||
|         message.error('请选择试卷A和试卷B'); | ||||
|         loading.value = false; | ||||
|       } | ||||
|     } else if (type === 2) { | ||||
|       console.log("2放大请求") | ||||
|       // 随机卷 | ||||
|       payload = { | ||||
|        | ||||
|         rollUp: 2 | ||||
|       }; | ||||
|       send(payload) | ||||
|         .then(() => { | ||||
|           message.success('保存成功'); | ||||
|           emit('done'); | ||||
|         }) | ||||
|         .catch((e) => { | ||||
|           message.error(e.message); | ||||
|         }) | ||||
|         .finally(() => { | ||||
|           loading.value = false; | ||||
|         }); | ||||
|     }else if(type === 3)// 自选卷 | ||||
|    { | ||||
|     payload = { | ||||
|        | ||||
|       rollUp: 3 | ||||
|     }; | ||||
|     send(payload) | ||||
|       .then(() => { | ||||
|         console.log("3放大请求") | ||||
|         message.success('保存成功'); | ||||
|         emit('done'); | ||||
|       }) | ||||
|       .catch((e) => { | ||||
|         message.error(e.message); | ||||
|       }) | ||||
|       .finally(() => { | ||||
|         loading.value = false; | ||||
|       }); | ||||
|    } | ||||
|      | ||||
|   }); | ||||
| }; | ||||
|  | ||||
|  | ||||
|  | ||||
| const send = async (payload) => { | ||||
|  | ||||
|   console.log("12312312"+payload) | ||||
|   try { | ||||
|     await updatePaper({ | ||||
|       ...payload, | ||||
|       taskId: props.taskId, | ||||
|     }); | ||||
|   } catch (e) { | ||||
|     message.error('保存失败:' + e.message); // 提示错误 | ||||
|     throw e;  // 确保错误能被捕获 | ||||
|   } finally { | ||||
|     loading.value = false;  // 确保最终关闭loading | ||||
|   } | ||||
| }; | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| // 获取题型下拉数据 | ||||
| const fetchpaperOptions = async () => { | ||||
|   try { | ||||
|     console.log(props.taskId+"props.taskIdprops.taskIdprops.taskId") | ||||
|     const res = await getPaperList(props.taskId); | ||||
|     PaperOptions.value = res | ||||
|       .filter(item => item) | ||||
|       .map(item => ({ label: item, value: item })); | ||||
|   } catch (e) { | ||||
|     message.error('获取题型失败:' + e.message); | ||||
|   } | ||||
| }; | ||||
|  | ||||
|  | ||||
| /** 弹窗打开时初始化 */ | ||||
| const handleOpen = () => { | ||||
|   resetFields(); | ||||
|   form.taskId = props.taskId; | ||||
|   nextTick(() => { | ||||
|     formRef.value?.clearValidate?.(); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| onMounted(() => { | ||||
|   console.log("taskId:", props.taskId); | ||||
|   if (props.taskId) { | ||||
|     fetchpaperOptions();  | ||||
|   } else { | ||||
|     console.error("taskId is not defined"); | ||||
|   } | ||||
| }); | ||||
|  | ||||
|  | ||||
| watch(() => form.type, (val) => { | ||||
|   if (val === 0) { | ||||
|     form.paperA = ''; | ||||
|     form.paperB = ''; | ||||
|   } else if (val === 1) { | ||||
|     form.paper = ''; | ||||
|   } else { | ||||
|     form.paper = ''; | ||||
|     form.paperA = ''; | ||||
|     form.paperB = ''; | ||||
|   } | ||||
| }); | ||||
| </script> | ||||
							
								
								
									
										211
									
								
								src/views/task/exam/components/steps/step2/index copy.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								src/views/task/exam/components/steps/step2/index copy.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,211 @@ | ||||
| <template> | ||||
|  | ||||
|   <ContentWrap> | ||||
|      <el-form | ||||
|       class="-mb-15px" | ||||
|       :model="queryParams" | ||||
|       ref="queryFormRef" | ||||
|       :inline="true" | ||||
|       label-width="68px" | ||||
|     > | ||||
|       | ||||
|       <el-form-item> | ||||
|         <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> | ||||
|         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> | ||||
|         <el-button | ||||
|           type="primary" | ||||
|           plain | ||||
|           @click="openForm('create')" | ||||
|           v-hasPermi="['system:sms-channel:create']" | ||||
|         > | ||||
|           <Icon icon="ep:plus" class="mr-5px" /> 新增</el-button | ||||
|         > | ||||
|       </el-form-item> | ||||
|     </el-form>  | ||||
|   </ContentWrap> | ||||
|  | ||||
|   <!-- 列表 --> | ||||
|   <ContentWrap> | ||||
|     <el-table v-loading="loading" :data="list"> | ||||
|       <el-table-column label="试卷编号" align="center" prop="paperId" /> | ||||
|       <el-table-column label="使用次数" align="center" prop="counts" /> | ||||
|       <el-table-column label="抽卷方式" align="center" prop="rollUp"> | ||||
|         <template #default="scope"> | ||||
|         <span v-if="scope.row.quLevel === '0'">固定</span> | ||||
|         <span v-else-if="scope.row.quLevel === '1'">AB卷</span> | ||||
|         <span v-else-if="scope.row.quLevel === '2'">随机</span> | ||||
|         <span v-else-if="scope.row.quLevel === '3'">自选</span> | ||||
|         <span v-else>未知</span> | ||||
|       </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column label="AB卷" align="center" prop="isAb"> | ||||
|         <template #default="scope"> | ||||
|         <span v-if="scope.row.quLevel === '0'">A卷</span> | ||||
|         <span v-else-if="scope.row.quLevel === '1'">B卷</span> | ||||
|         <span v-else>未知</span> | ||||
|       </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column label="是否启用" align="center" prop="status" > | ||||
|       <template #default="scope"> | ||||
|           <dict-tag :type="DICT_TYPE.SYS_YES_NO" :value="scope.row.status" /> | ||||
|         </template> | ||||
|     </el-table-column> | ||||
|        | ||||
|       <el-table-column label="操作" align="center"> | ||||
|         <template #default="scope"> | ||||
|           <el-button | ||||
|             link | ||||
|             type="primary" | ||||
|             @click="openEdit('update', scope.row)" | ||||
|             v-hasPermi="['system:sms-channel:update']" | ||||
|           > | ||||
|             编辑 | ||||
|           </el-button> | ||||
|           <el-button | ||||
|             link | ||||
|             type="danger" | ||||
|             @click="handleDelete(scope.row.id)" | ||||
|             v-hasPermi="['system:sms-channel:delete']" | ||||
|           > | ||||
|             删除 | ||||
|           </el-button> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|     </el-table> | ||||
|     <!-- 分页 --> | ||||
|     <Pagination | ||||
|       :total="total" | ||||
|       v-model:page="queryParams.pageNo" | ||||
|       v-model:limit="queryParams.pageSize" | ||||
|       @pagination="getList" | ||||
|     /> | ||||
|   </ContentWrap> | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| <paper-add  v-model="showAdd"  :task-Id="taskId" :task-specialty="taskSpecialty"   /> | ||||
|     <paper-edit v-model="showEdit" :data="current" /> | ||||
|     <!-- <paper-look v-model="showLook" :paper-id="paperId"  /> --> | ||||
|     <paper-set  v-model="showSet"  :task-Id="taskId"  /> | ||||
| </template> | ||||
| <script lang="ts" setup> | ||||
| import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' | ||||
|  | ||||
| import * as SmsChannelApi from '@/api/system/paper'; | ||||
| import PaperEdit from './components/step-edit.vue'; | ||||
|   import PaperAdd from './components/step-add.vue'; | ||||
|   import PaperLook from './components/step-look.vue'; | ||||
|   import PaperSet from './components/step-set.vue'; | ||||
|   import PaperSearch from './components/step-search.vue'; | ||||
|   import { pagePapers, removePapers, exportPapers } from '@/api/system/paper'; | ||||
|  | ||||
|  | ||||
|  | ||||
| defineOptions({ name: 'SystemPaper' }); | ||||
|   const props = defineProps({ | ||||
|     taskSpecialty: { | ||||
|     type: String, | ||||
|     default: '' | ||||
|   }, | ||||
|   taskId: { | ||||
|     type: String, | ||||
|     default: '' | ||||
|   } | ||||
| }) | ||||
|  | ||||
| const { t } = useI18n() // 国际化 | ||||
| const message = useMessage() // 消息弹窗 | ||||
|  | ||||
|  | ||||
|   /** 当前编辑数据 */ | ||||
|   const current = ref<object>(); | ||||
|  | ||||
|   /** 是否显示编辑弹窗 */ | ||||
|   const showEdit = ref(false); | ||||
|  | ||||
|   const showLook = ref(false); | ||||
|  | ||||
|   const showAdd = ref(false); | ||||
|  | ||||
|   const showSet = ref(false); | ||||
|    | ||||
|  | ||||
| const smsChannelFormRef = ref() | ||||
| const taskEditRef = ref() | ||||
| const taskAddRef = ref() | ||||
| const taskTempRef = ref() | ||||
|  | ||||
|  | ||||
|  | ||||
| const loading = ref(false) // 列表的加载中 | ||||
| const total = ref(0) // 列表的总页数 | ||||
| const list = ref([]) // 列表的数据 | ||||
| const queryFormRef = ref() // 搜索的表单 | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   signature: undefined, | ||||
|   status: undefined, | ||||
|   createTime: [] | ||||
| }) | ||||
|  | ||||
| /** 查询列表 */ | ||||
| const getList = async () => { | ||||
|   loading.value = true | ||||
|   try { | ||||
|     const res = await SmsChannelApi.pagePapers(queryParams) | ||||
|     console.log(res) | ||||
|     list.value = res | ||||
|     total.value = res.total | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** 搜索按钮操作 */ | ||||
| const handleQuery = () => { | ||||
|   queryParams.pageNo = 1 | ||||
|   getList() | ||||
| } | ||||
|  | ||||
| /** 重置按钮操作 */ | ||||
| const resetQuery = () => { | ||||
|   queryFormRef.value.resetFields() | ||||
|   handleQuery() | ||||
| } | ||||
|  | ||||
| /** 添加/修改操作 */ | ||||
| const formRef = ref(); | ||||
| const openForm = (type: string, id?: number) => { | ||||
|   showAdd.value = true | ||||
|   taskAddRef.value?.open(type, id) | ||||
| } | ||||
|  | ||||
| const openEdit = (type: string, row?: object) => { | ||||
|   showEdit.value = true | ||||
|   current.value = row | ||||
|   console.log( current.value ) | ||||
|   taskEditRef.value?.open(type, row) | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| /** 删除按钮操作 */ | ||||
| const handleDelete = async (id: number) => { | ||||
|   try { | ||||
|     // 删除的二次确认 | ||||
|     await message.delConfirm() | ||||
|     // 发起删除 | ||||
|     await SmsChannelApi.removePaper(id) | ||||
|     message.success(t('common.delSuccess')) | ||||
|     // 刷新列表 | ||||
|     await getList() | ||||
|   } catch {} | ||||
| } | ||||
|  | ||||
| /** 初始化 **/ | ||||
| onMounted(() => { | ||||
|   getList() | ||||
| }) | ||||
| </script> | ||||
							
								
								
									
										332
									
								
								src/views/task/exam/components/steps/step2/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										332
									
								
								src/views/task/exam/components/steps/step2/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,332 @@ | ||||
| <template> | ||||
|   <ContentWrap> | ||||
|     <!-- 搜索表单 --> | ||||
|     <el-form | ||||
|       class="-mb-15px" | ||||
|       :model="queryParams" | ||||
|       ref="queryFormRef" | ||||
|       :inline="true" | ||||
|       label-width="68px" | ||||
|     > | ||||
|  | ||||
|     <el-form-item label="试卷编号" prop="paperId"> | ||||
|         <el-input | ||||
|           v-model="queryParams.paperId" | ||||
|           placeholder="请输入试卷编号" | ||||
|           clearable | ||||
|           class="!w-240px" | ||||
|           @keyup.enter="handleQuery" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <el-form-item label="启用状态" prop="status"> | ||||
|         <el-select | ||||
|           v-model="queryParams.status" | ||||
|           placeholder="请选择启用状态" | ||||
|           class="!w-240px" | ||||
|           clearable | ||||
|         > | ||||
|           <el-option | ||||
|             v-for="dict in getIntDictOptions(DICT_TYPE.SYS_STATUS)" | ||||
|             :key="dict.value" | ||||
|             :label="dict.label" | ||||
|             :value="dict.value" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <el-form-item> | ||||
|         <el-button @click="handleQuery"> | ||||
|           <Icon icon="ep:search" class="mr-5px" /> 搜索 | ||||
|         </el-button> | ||||
|         <el-button @click="resetQuery"> | ||||
|           <Icon icon="ep:refresh" class="mr-5px" /> 重置 | ||||
|         </el-button> | ||||
|         <el-button | ||||
|           type="primary" | ||||
|           plain | ||||
|           @click="openAddForm" | ||||
|         > | ||||
|           <Icon icon="ep:plus" class="mr-5px" /> 新增 | ||||
|         </el-button> | ||||
|         <el-button | ||||
|           type="primary" | ||||
|           plain | ||||
|           @click="openSet" | ||||
|         > | ||||
|           <Icon icon="ep:plus" class="mr-5px" /> 试卷调整 | ||||
|         </el-button> | ||||
|         <!-- 批量删除按钮 --> | ||||
|         <!-- <el-button | ||||
|           type="danger" | ||||
|           :disabled="selectedRows.length === 0" | ||||
|           @click="batchDelete" | ||||
|         > | ||||
|           批量删除 | ||||
|         </el-button> --> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|   </ContentWrap> | ||||
|  | ||||
|   <!-- 列表展示 --> | ||||
|   <ContentWrap> | ||||
|     <el-table v-loading="loading" :data="list" :row-key="row => row.id" ref="tableRef" :selected-rows="selectedRows"> | ||||
|  | ||||
|       <!-- 多选列 --> | ||||
|       <el-table-column type="selection" width="55" /> | ||||
|       <el-table-column label="试卷编号" align="center" prop="paperId" /> | ||||
|       <el-table-column label="使用次数" align="center" prop="counts" /> | ||||
|       <el-table-column label="抽卷方式" align="center" prop="rollUp"> | ||||
|         <template #default="scope"> | ||||
|           <span v-if="scope.row.rollUp === '0'">固定</span> | ||||
|           <span v-else-if="scope.row.rollUp === '1'">AB卷</span> | ||||
|           <span v-else-if="scope.row.rollUp === '2'">随机</span> | ||||
|           <span v-else-if="scope.row.rollUp === '3'">自选</span> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column label="AB卷" align="center" prop="isAb"> | ||||
|         <template #default="scope"> | ||||
|           <span v-if="scope.row.isAb === '0'">A卷</span> | ||||
|           <span v-else-if="scope.row.isAb === '1'">B卷</span> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column label="是否启用" align="center" prop="status"> | ||||
|         <template #default="scope"> | ||||
|           <dict-tag :type="DICT_TYPE.SYS_YES_NO" :value="scope.row.status" /> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column label="操作" align="center"> | ||||
|         <template #default="scope"> | ||||
|           <el-button | ||||
|             link | ||||
|             type="primary" | ||||
|             @click="openLook('look', scope.row.paperId)" | ||||
|             v-hasPermi="['system:sms-channel:update']" | ||||
|           > | ||||
|             查看 | ||||
|           </el-button> | ||||
|           <el-button | ||||
|             link | ||||
|             type="primary" | ||||
|             @click="openEdit('update', scope.row)" | ||||
|             v-hasPermi="['system:sms-channel:update']" | ||||
|           > | ||||
|             编辑 | ||||
|           </el-button> | ||||
|           <el-button | ||||
|             link | ||||
|             type="danger" | ||||
|             @click="handleDelete(scope.row.paperId)" | ||||
|             v-hasPermi="['system:sms-channel:delete']" | ||||
|           > | ||||
|             删除 | ||||
|           </el-button> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|     </el-table> | ||||
|  | ||||
|     <!-- 分页 --> | ||||
|     <Pagination | ||||
|       :total="total" | ||||
|       v-model:page="queryParams.pageNo" | ||||
|       v-model:limit="queryParams.pageSize" | ||||
|       @pagination="getList" | ||||
|     /> | ||||
|   </ContentWrap> | ||||
|  | ||||
|   <!-- 弹窗组件 --> | ||||
|   <paper-add | ||||
|     v-model="showAdd" | ||||
|     :task-Id="props.taskId" | ||||
|     :task-specialty="props.taskSpecialty" | ||||
|     ref="taskAddRef" | ||||
|     @done="reload" | ||||
|   /> | ||||
|   <paper-edit | ||||
|     v-model="showEdit" | ||||
|     :data="current" | ||||
|     ref="taskEditRef" | ||||
|      @done="reload" | ||||
|   /> | ||||
|   <paper-look | ||||
|     v-model="showLook" | ||||
|     ref="taskLookRef" | ||||
|     :paper-Id="currentPaperId"   | ||||
|   /> | ||||
|   <paper-set | ||||
|     v-model="showSet" | ||||
|     :task-Id="taskId" | ||||
|     ref="taskSetRef" | ||||
|      @done="reload" | ||||
|   /> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { ref, reactive, nextTick,onMounted,watch} from 'vue'; | ||||
| import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' | ||||
| import * as PaperApi from '@/api/system/paper'; | ||||
| import PaperEdit from './components/step-edit.vue'; | ||||
| import PaperAdd from './components/step-add.vue'; | ||||
| import PaperLook from './components/step-look.vue'; | ||||
| import PaperSet from './components/step-set.vue'; | ||||
| import { useI18n } from 'vue-i18n'; | ||||
|  | ||||
| defineOptions({ name: 'SystemPaper' }); | ||||
|  | ||||
| const { t } = useI18n(); | ||||
| const message = useMessage(); | ||||
|  | ||||
| const props = defineProps({ | ||||
|   taskSpecialty: { | ||||
|     type: String, | ||||
|     default: '', | ||||
|   }, | ||||
|   taskId: { | ||||
|     type: String, | ||||
|     default: '', | ||||
|   }, | ||||
| }); | ||||
|  | ||||
| // 定义数据项的类型 | ||||
| interface PaperItem { | ||||
|   paperId: number; // 假设每个项目都有 paperId | ||||
|   counts: number; | ||||
|   rollUp: string; | ||||
|   isAb: string; | ||||
|   status: string; | ||||
|   // 你可以根据需要扩展更多字段 | ||||
| } | ||||
|  | ||||
| // 当前编辑的数据 | ||||
| const current = ref<object>(); | ||||
|  | ||||
| // 弹窗显示状态 | ||||
| const showEdit = ref(false); | ||||
| const showLook = ref(false); | ||||
| const showAdd = ref(false); | ||||
| const showSet = ref(false); | ||||
|  | ||||
| // ref 对应的组件实例 | ||||
| const taskAddRef = ref(); | ||||
| const taskEditRef = ref(); | ||||
| const taskLookRef = ref(); | ||||
| const taskSetRef = ref(); | ||||
|  | ||||
| const loading = ref(false); // 列表的加载状态 | ||||
| const total = ref(0); // 列表的总页数 | ||||
| const list = ref<PaperItem[]>([]); // 列表的数据,明确指定类型 | ||||
| const queryFormRef = ref(); // 搜索表单的 ref | ||||
|  | ||||
| // 多选框绑定的变量,指定为 PaperItem 类型的数组 | ||||
| const selectedRows = ref<PaperItem[]>([]); | ||||
|  | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   paperId: undefined, | ||||
|   status: undefined, | ||||
|   createTime: [], | ||||
|   taskId:props.taskId | ||||
| }); | ||||
|  | ||||
| // 获取列表数据 | ||||
| const getList = async () => { | ||||
|   loading.value = true; | ||||
|   try { | ||||
|     const res = await PaperApi.pagePapers(queryParams); | ||||
|     list.value = res.list; | ||||
|     total.value = res.total; | ||||
|   } finally { | ||||
|     loading.value = false; | ||||
|   } | ||||
| }; | ||||
| const reload = () => { | ||||
|   | ||||
|   getList(); | ||||
| }; | ||||
|  | ||||
|  | ||||
| // 搜索操作 | ||||
| const handleQuery = () => { | ||||
|   queryParams.pageNo = 1; | ||||
|   getList(); | ||||
| }; | ||||
|  | ||||
| // 重置操作 | ||||
| const resetQuery = () => { | ||||
|   queryFormRef.value.resetFields(); | ||||
|   handleQuery(); | ||||
| }; | ||||
|  | ||||
| // 打开新增表单 | ||||
| const openAddForm = () => { | ||||
|   showAdd.value = true; | ||||
|   nextTick(() => { | ||||
|     taskAddRef.value?.open(); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const openSet = () => { | ||||
|   showSet.value = true; | ||||
|   nextTick(() => { | ||||
|     taskSetRef.value?.open(); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| // 打开编辑弹窗 | ||||
| const openEdit = (type: string, row?: object) => { | ||||
|   showEdit.value = true; | ||||
|   current.value = row; | ||||
|   nextTick(() => { | ||||
|     taskEditRef.value?.open(type, row); | ||||
|   }); | ||||
| }; | ||||
| const currentPaperId = ref<number | null>(null); | ||||
|  | ||||
| const openLook = (type: string, paperId?: number) => { | ||||
|   showLook.value = true; | ||||
|   currentPaperId.value = paperId ?? null;  // 设置当前选中的 paperId | ||||
|   nextTick(() => { | ||||
|     taskLookRef.value?.open(type, paperId); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
|  | ||||
|  | ||||
| // 删除操作 | ||||
| const handleDelete = async (id: number) => { | ||||
|   try { | ||||
|     await message.delConfirm(); | ||||
|     await PaperApi.removePaper(id); | ||||
|     message.success(t('common.delSuccess')); | ||||
|     await getList(); | ||||
|   } catch (error) { | ||||
|     console.error(error); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| // 批量删除操作 | ||||
| const batchDelete = async () => { | ||||
|   try { | ||||
|     const ids = selectedRows.value.map(item => item.paperId); // 使用 paperId 属性 | ||||
|     if (ids.length > 0) { | ||||
|       await message.delConfirm(); | ||||
|       await PaperApi.removePapers(ids); // 调用批量删除接口 | ||||
|       message.success(t('common.delSuccess')); | ||||
|       await getList(); | ||||
|       selectedRows.value = []; // 清空选择的行 | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.error(error); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| // 初始化操作 | ||||
| onMounted(() => { | ||||
|   getList(); | ||||
| }); | ||||
| watch(selectedRows, (newVal) => { | ||||
|   console.log('选中的行:', newVal); | ||||
| }); | ||||
|  | ||||
| </script> | ||||
							
								
								
									
										193
									
								
								src/views/task/exam/components/steps/step3/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								src/views/task/exam/components/steps/step3/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,193 @@ | ||||
| <template> | ||||
|   <div> | ||||
|     <!-- 使用 el-tabs 组件创建可切换的选项卡 --> | ||||
|     <el-tabs v-model="activeTab" style="margin-top: 20px;"> | ||||
|       <!-- 第一个 tab - 通用参数 --> | ||||
|       <el-tab-pane label="通用参数" name="tab1"> | ||||
|         <el-form :model="form" label-width="200px" style="margin-top: 20px;"> | ||||
|           <!-- 是否使用监考密码验证 --> | ||||
|           <el-form-item label="是否使用监考密码验证"> | ||||
|             <el-switch  | ||||
|               v-model="form.isExamPassword"  | ||||
|               active-value="0"  | ||||
|               inactive-value="1"  | ||||
|               active-text="是"  | ||||
|               inactive-text="否"  | ||||
|               @change="handleFormChange" /> | ||||
|           </el-form-item> | ||||
|  | ||||
|               <!-- 是否启用考场设置 --> | ||||
|               <el-form-item label="是否启用考场设置"> | ||||
|             <el-switch  | ||||
|               v-model="form.isSession"  | ||||
|               active-value="0"  | ||||
|               inactive-value="1"  | ||||
|               active-text="是"  | ||||
|               inactive-text="否"  | ||||
|               @change="handleFormChange" /> | ||||
|           </el-form-item> | ||||
|           <!-- 监考密码 --> | ||||
|           <el-form-item label="监考密码" v-if="form.isExamPassword === '0'"> | ||||
|             <el-input v-model="form.examPassword" placeholder="请输入监考密码" @input="handleFormChange" /> | ||||
|           </el-form-item> | ||||
|  | ||||
|           <!-- 禁止学生使用U盘 --> | ||||
|           <el-form-item label="禁止学生使用U盘"> | ||||
|             <el-switch  | ||||
|               v-model="form.usb"  | ||||
|               active-value="0"  | ||||
|               inactive-value="1"  | ||||
|               active-text="是"  | ||||
|               inactive-text="否"  | ||||
|               @change="handleFormChange" /> | ||||
|           </el-form-item> | ||||
|  | ||||
|           <!-- 练习成绩保存 --> | ||||
|           <el-form-item label="练习成绩保存"> | ||||
|             <el-radio-group v-model="form.saveGrades" @change="handleFormChange"> | ||||
|               <el-radio label="0">最高成绩</el-radio> | ||||
|               <el-radio label="1">最新成绩</el-radio> | ||||
|             </el-radio-group> | ||||
|           </el-form-item> | ||||
|  | ||||
|  | ||||
|           <el-form-item label="是否显示重答按钮"> | ||||
|             <el-switch  | ||||
|               v-model="form.isRepeat"  | ||||
|               active-value="0"  | ||||
|               inactive-value="1"  | ||||
|               active-text="是"  | ||||
|               inactive-text="否"  | ||||
|               @change="handleFormChange" /> | ||||
|           </el-form-item> | ||||
|  | ||||
|  | ||||
|           <el-form-item label="是否显示答案按钮"> | ||||
|             <el-switch  | ||||
|               v-model="form.isAnswer"  | ||||
|               active-value="0"  | ||||
|               inactive-value="1"  | ||||
|               active-text="是"  | ||||
|               inactive-text="否"  | ||||
|               @change="handleFormChange" /> | ||||
|           </el-form-item> | ||||
|           <el-form-item label="学生是否可以查看试卷"> | ||||
|             <el-switch  | ||||
|               v-model="form.isLook"  | ||||
|               active-value="0"  | ||||
|               inactive-value="1"  | ||||
|               active-text="是"  | ||||
|               inactive-text="否"  | ||||
|               @change="handleFormChange" /> | ||||
|           </el-form-item> | ||||
|  | ||||
|           <el-form-item label="定时检查与学生端联通性,每"> | ||||
|             <el-input-number v-model="form.isConnect"  label="分钟" @change="handleFormChange" /> | ||||
|             <span>分钟传一次,断联直接交卷</span> | ||||
|           </el-form-item> | ||||
|  | ||||
|            | ||||
|  | ||||
|         </el-form> | ||||
|       </el-tab-pane> | ||||
|  | ||||
|       <!-- 第二个 tab - 试卷答题文件 --> | ||||
|       <el-tab-pane label="试卷答题文件" name="tab2"> | ||||
|         <el-form :model="form" label-width="200px" style="margin-top: 20px;"> | ||||
|           <!-- 学生文件存放系统盘 --> | ||||
|           <el-form-item label="驱动器为学生文件存放系统盘"> | ||||
|             <el-radio-group v-model="form.driver" @change="handleFormChange"> | ||||
|               <el-radio label="C">C</el-radio> | ||||
|               <el-radio label="D">D</el-radio> | ||||
|               <el-radio label="E">E</el-radio> | ||||
|               <el-radio label="F">F</el-radio> | ||||
|             </el-radio-group> | ||||
|           </el-form-item> | ||||
|  | ||||
|           <!-- 考试目录名称 --> | ||||
|           <el-form-item label="考试目录名称"> | ||||
|             <el-input v-model="form.directory" placeholder="请输入考试目录名称" @input="handleFormChange" /> | ||||
|           </el-form-item> | ||||
|  | ||||
|           <!-- 定时上传考试目录 --> | ||||
|           <el-form-item label="定时上传考试目录每"> | ||||
|             <el-input-number v-model="form.uploadTime" :min="1" label="分钟" @change="handleFormChange" /> | ||||
|             <span>分钟传一次</span> | ||||
|           </el-form-item> | ||||
|  | ||||
|           <!-- 完成考试后是否删除考试目录 --> | ||||
|           <el-form-item label="完成考试后是否删除考试目录"> | ||||
|             <el-radio-group v-model="form.isDel" @change="handleFormChange"> | ||||
|               <el-radio label="0">是</el-radio> | ||||
|               <el-radio label="1">否</el-radio> | ||||
|             </el-radio-group> | ||||
|           </el-form-item> | ||||
|         </el-form> | ||||
|       </el-tab-pane> | ||||
|     </el-tabs> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| import { ref, onMounted } from 'vue' | ||||
| import { getParam, updateParam } from '@/api/system/param'  // 引入后台请求方法 | ||||
|  | ||||
| const props = defineProps({ | ||||
|   taskId: { | ||||
|     type: String, | ||||
|     default: '' | ||||
|   } | ||||
| }) | ||||
|  | ||||
| // 字段初始值设置为空或空字符串 | ||||
| const form = ref({ | ||||
|   isExamPassword: '',     // 是否使用监考密码验证 | ||||
|   examPassword: '',       // 监考密码 | ||||
|   usb: '',                // 是否禁止U盘 | ||||
|   saveGrades: '',         // 成绩保存方式 | ||||
|   driver: '',             // 存放系统盘 | ||||
|   directory: '',          // 考试目录名称 | ||||
|   uploadTime: '',         // 上传间隔时间 | ||||
|   isDel: '',    | ||||
|   isRepeat:'',           // 是否删除目录 | ||||
|   isAnswer:'', | ||||
|   isLook:'', | ||||
|   isConnect:'' | ||||
| }) | ||||
|  | ||||
| const activeTab = ref('tab1') | ||||
|  | ||||
| // 页面加载时用 taskId 请求后端并赋值 | ||||
| onMounted(async () => { | ||||
|   if (props.taskId) { | ||||
|     try { | ||||
|       const res = await getParam({ taskId: props.taskId }) | ||||
|       const data = res || {} | ||||
|       console.log('任务详情:', data) | ||||
|  | ||||
|       // 用 Object.assign 将后端数据合并到 form | ||||
|       Object.assign(form.value, data) | ||||
|     } catch (error) { | ||||
|       console.error('获取任务详情失败', error) | ||||
|     } | ||||
|   } | ||||
| }) | ||||
|  | ||||
| // 表单变化时触发请求 | ||||
| const handleFormChange = async () => { | ||||
|   if (form.value.isExamPassword === '1') { | ||||
|     form.value.examPassword = '' | ||||
|   } | ||||
|   try { | ||||
|     // 发送请求以更新数据 | ||||
|     const response = await updateParam(form.value) | ||||
|     console.log('更新结果:', response) | ||||
|   } catch (error) { | ||||
|     console.error('更新数据失败', error) | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| /* 可以为 tab 设置额外的样式,具体根据需求定制 */ | ||||
| </style> | ||||
| @@ -0,0 +1,197 @@ | ||||
| <template> | ||||
|   <Dialog | ||||
|     v-model="visible" | ||||
|     :title="isUpdate ? '修改考场设置' : '添加考场设置'" | ||||
|     width="460" | ||||
|     center | ||||
|   > | ||||
|     <el-form | ||||
|       ref="formRef" | ||||
|       :model="form" | ||||
|       :rules="rules" | ||||
|       label-width="120px" | ||||
|       @submit.prevent="" | ||||
|     > | ||||
|       <el-form-item label="试卷ID" prop="taskId"> | ||||
|         <el-input clearable v-model="form.taskId" placeholder="请输入试卷ID"  disabled/> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <el-form-item label="批次" prop="batch"> | ||||
|         <el-input clearable v-model="form.batch" placeholder="请输入批次"  disabled/> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <el-form-item label="考试开始时间" prop="startTime"> | ||||
|         <el-date-picker | ||||
|           v-model="form.startTime" | ||||
|           type="datetime" | ||||
|           value-format="YYYY-MM-DD HH:mm:ss" | ||||
|           placeholder="请选择考试开始时间" | ||||
|           class="ele-fluid" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <el-form-item label="考试结束时间" prop="endTime"> | ||||
|         <el-date-picker | ||||
|           v-model="form.endTime" | ||||
|           type="datetime" | ||||
|           value-format="YYYY-MM-DD HH:mm:ss" | ||||
|           placeholder="请选择考试结束时间" | ||||
|           class="ele-fluid" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <el-form-item label="开始前(分钟)允许入场" prop="allowEntry"> | ||||
|         <el-input | ||||
|           clearable | ||||
|           v-model="form.allowEntry" | ||||
|           placeholder="请输入允许提前入场的分钟数" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <el-form-item label="开始后(分钟)禁止入场" prop="endAllowEntry"> | ||||
|         <el-input | ||||
|           clearable | ||||
|           v-model="form.endAllowEntry" | ||||
|           placeholder="请输入开始后禁止入场的分钟数" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <el-form-item label="开始后(分钟)允许交卷" prop="allowSubmission"> | ||||
|         <el-input | ||||
|           clearable | ||||
|           v-model="form.allowSubmission" | ||||
|           placeholder="请输入允许交卷的分钟数" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|  | ||||
|       <el-form-item label="是否启用" prop="status"> | ||||
|  | ||||
|  | ||||
|          | ||||
|         <el-radio-group v-model="form.status"> | ||||
|           <el-radio | ||||
|             v-for="dict in getIntDictOptions(DICT_TYPE.SYS_YES_NO)" | ||||
|             :key="dict.value" | ||||
|             :label="String(dict.value)"  | ||||
|           > | ||||
|             {{ dict.label }} | ||||
|           </el-radio> | ||||
|  | ||||
|         </el-radio-group> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|  | ||||
|     <template #footer> | ||||
|       <el-button @click="visible = false">取消</el-button> | ||||
|       <el-button | ||||
|         type="primary" | ||||
|         :loading="loading" | ||||
|         @click="save" | ||||
|         :disabled="false" | ||||
|       > | ||||
|         保存 | ||||
|       </el-button> | ||||
|     </template> | ||||
|   </Dialog> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ref, reactive, nextTick } from 'vue' | ||||
| import { addSession, updateSession } from '@/api/system/session' | ||||
| import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' | ||||
| const emit = defineEmits(['done']) | ||||
| const props = defineProps({ | ||||
|   data: Object, | ||||
|   taskId: String, | ||||
| }) | ||||
|  | ||||
| const visible = defineModel({ type: Boolean }) | ||||
| const isUpdate = ref(false) | ||||
| const loading = ref(false) | ||||
| const formRef = ref() | ||||
|  | ||||
| // 表单模型 | ||||
| const form = ref({ | ||||
|   sessionId: undefined, | ||||
|   taskId: props.taskId ?? '', | ||||
|   batch: '', | ||||
|   startTime: '', | ||||
|   endTime: '', | ||||
|   allowEntry: '', | ||||
|   endAllowEntry: '', | ||||
|   allowSubmission: '', | ||||
|   status: '', // 状态(0禁用,1启用) | ||||
| }) | ||||
|  | ||||
| // 表单校验规则 | ||||
| const rules = reactive({ | ||||
|   taskId: [{ required: true, message: '请输入试卷ID', trigger: 'blur' }], | ||||
|   batch: [{ required: true, message: '请输入批次', trigger: 'blur' }], | ||||
|   startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }], | ||||
|   endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }], | ||||
|   allowEntry: [{ required: true, message: '请输入允许入场时间', trigger: 'blur' }], | ||||
|   endAllowEntry: [{ required: true, message: '请输入禁止入场时间', trigger: 'blur' }], | ||||
|   allowSubmission: [{ required: true, message: '请输入允许交卷时间', trigger: 'blur' }], | ||||
|   status: [{ required: true, message: '请选择状态', trigger: 'change' }], | ||||
| }) | ||||
|  | ||||
| // 打开弹窗方法 | ||||
| const open = async (type: 'create' | 'update', data?: any) => { | ||||
|   console.log(type+"isUpdate.valueisUpdate.valueisUpdate.value") | ||||
|   visible.value = true | ||||
|   isUpdate.value = type === 'update' | ||||
|  | ||||
|   if (isUpdate.value && data) { | ||||
|     form.value = { ...data } | ||||
|   } else { | ||||
|     form.value = { | ||||
|       sessionId: undefined, | ||||
|       taskId: props.taskId ?? '', | ||||
|       batch: '', | ||||
|       startTime: '', | ||||
|       endTime: '', | ||||
|       allowEntry: '', | ||||
|       endAllowEntry: '', | ||||
|       allowSubmission: '', | ||||
|       status: '', | ||||
|     } | ||||
|  | ||||
|        // 设置批次为 当前时间 + 5位随机数 | ||||
|        const now = new Date(); | ||||
|         const pad = (n) => n.toString().padStart(2, '0'); | ||||
|         const datetimeStr = [ | ||||
|           now.getFullYear(), | ||||
|           pad(now.getMonth() + 1), | ||||
|           pad(now.getDate()), | ||||
|           pad(now.getHours()), | ||||
|           pad(now.getMinutes()), | ||||
|           pad(now.getSeconds()) | ||||
|         ].join(''); | ||||
|  | ||||
|         const randomNum = Math.floor(Math.random() * 100000).toString().padStart(5, '0'); | ||||
|         //把batch 赋值 randomNum | ||||
|          | ||||
|     // 把 batch 设置为当前时间+随机数 | ||||
|     form.value.batch = `${datetimeStr}${randomNum}`; | ||||
|   } | ||||
|  | ||||
|   await nextTick() | ||||
|   formRef.value?.clearValidate?.() | ||||
| } | ||||
|  | ||||
| // 保存按钮 | ||||
| const save = async () => { | ||||
|   await formRef.value.validate() | ||||
|   loading.value = true | ||||
|   try { | ||||
|     const fn = isUpdate.value ? updateSession : addSession | ||||
|     await fn({ ...form.value }) | ||||
|     emit('done') | ||||
|     visible.value = false | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
|  | ||||
| defineExpose({ open }) | ||||
| </script> | ||||
| @@ -0,0 +1,64 @@ | ||||
| <!-- 搜索表单 --> | ||||
| <template> | ||||
|     <ele-card :body-style="{ paddingBottom: '2px' }"> | ||||
|       <el-form label-width="72px" @keyup.enter="search" @submit.prevent=""> | ||||
|         <el-row :gutter="8"> | ||||
|          | ||||
|           <el-col :lg="6" :md="12" :sm="12" :xs="24"> | ||||
|           <el-form-item label="批次"> | ||||
|             <el-input | ||||
|               clearable | ||||
|               v-model.trim="form.batch" | ||||
|               placeholder="请输入" | ||||
|             /> | ||||
|           </el-form-item> | ||||
|         </el-col> | ||||
|           <el-col :lg="6" :md="12" :sm="12" :xs="24"> | ||||
|             <el-form-item label="状态"> | ||||
|             <dict-data | ||||
|                 code="sys_common_status_other" | ||||
|                 type="select" | ||||
|                 v-model="form.status" | ||||
|               /> | ||||
|             </el-form-item> | ||||
|           </el-col> | ||||
|           <el-col :lg="6" :md="12" :sm="12" :xs="24"> | ||||
|             <el-form-item label-width="16px"> | ||||
|               <el-button type="primary" @click="search">查询</el-button> | ||||
|               <el-button @click="reset">重置</el-button> | ||||
|             </el-form-item> | ||||
|           </el-col> | ||||
|         </el-row> | ||||
|       </el-form> | ||||
|     </ele-card> | ||||
|   </template> | ||||
|    | ||||
|   <script setup> | ||||
|     import { useFormData } from '@/utils/use-form-data'; | ||||
|    | ||||
|     const emit = defineEmits(['search']); | ||||
|    | ||||
|     /** 表单数据 */ | ||||
|     const [form, resetFields] = useFormData({ | ||||
|       startTime: '', | ||||
|       batch: '', | ||||
|       endTime: '', | ||||
|       allowEntry: '', | ||||
|       endAllowEntry: '', | ||||
|       allowSubmission: '', | ||||
|       status: '', | ||||
|       | ||||
|     }); | ||||
|    | ||||
|     /** 搜索 */ | ||||
|     const search = () => { | ||||
|       emit('search', { ...form }); | ||||
|     }; | ||||
|    | ||||
|     /**  重置 */ | ||||
|     const reset = () => { | ||||
|       resetFields(); | ||||
|       search(); | ||||
|     }; | ||||
|   </script> | ||||
|    | ||||
| @@ -0,0 +1,104 @@ | ||||
| <template> | ||||
|  | ||||
| <Dialog v-model="visible"     :title="'试卷调整'"  width="860"    @open="handleOpen" center> | ||||
|  | ||||
|  | ||||
|     <div>{{rows}}</div> | ||||
|     <div>{{taskId}}</div> | ||||
|     <el-transfer v-model="value" :data="data" /> | ||||
|     <template #footer> | ||||
|       <el-button @click="handleCancel">取消</el-button> | ||||
|       <el-button type="primary" :loading="loading" @click="save">保存</el-button> | ||||
|     </template> | ||||
| </Dialog> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { ref, nextTick } from 'vue'; | ||||
| import { useFormData } from '@/utils/use-form-data'; | ||||
| import { getPaperListByType } from '@/api/system/paper'; | ||||
| import { setSessions } from '@/api/system/session'; | ||||
| import type { FormInstance } from 'element-plus'  // ✅ 引入 FormInstance 类型 | ||||
| const message = useMessage(); | ||||
|  | ||||
| const props = defineProps({ | ||||
|   taskId: String, | ||||
|   rows: { | ||||
|     type: Array, | ||||
|     default: () => [] | ||||
|   } | ||||
| }); | ||||
|  | ||||
| interface Option { | ||||
|   key: string; | ||||
|   label: string; | ||||
|   disabled: boolean; | ||||
| } | ||||
|  | ||||
| const data = ref<Option[]>([]); | ||||
| const value = ref<string[]>([]); | ||||
|  | ||||
| const emit = defineEmits(['done']); | ||||
| const visible = defineModel({ type: Boolean }); | ||||
| const loading = ref(false); | ||||
|  | ||||
| // ✅ 给 formRef 添加类型注解 | ||||
| const formRef = ref<FormInstance | null>(null); | ||||
|  | ||||
| const [form, resetFields] = useFormData({ | ||||
|   taskId: '', | ||||
| }); | ||||
|  | ||||
| const handleOpen = async () => { | ||||
|   resetFields(); | ||||
|   form.taskId = props.taskId; | ||||
|   value.value = []; | ||||
|  | ||||
|   try { | ||||
|     const res = await getPaperListByType({ | ||||
|       taskId: props.taskId, | ||||
|       rows: props.rows | ||||
|     }); | ||||
|  | ||||
|     data.value = (res?.paperId || []).map((item: string) => ({ | ||||
|       key: item, | ||||
|       label: item, | ||||
|       disabled: false | ||||
|     })); | ||||
|  | ||||
|     value.value = res?.selectedIds || []; | ||||
|  | ||||
|   } catch (e) { | ||||
|     message.error('获取试卷列表失败'); | ||||
|   } | ||||
|  | ||||
|   nextTick(() => { | ||||
|     // ✅ 类型安全地调用 clearValidate | ||||
|     formRef.value?.clearValidate?.(); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const handleCancel = () => { | ||||
|   visible.value = false; | ||||
| }; | ||||
|  | ||||
| const save = async () => { | ||||
|   if (!value.value.length) { | ||||
|     message.warning('请至少选择一个试卷'); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     const params = { | ||||
|       selectedIds: value.value, | ||||
|       rows: props.rows | ||||
|     }; | ||||
|     await setSessions(params); | ||||
|     message.success('保存成功'); | ||||
|     emit('done'); | ||||
|     visible.value = false; | ||||
|   } catch (error) { | ||||
|     message.error('保存失败'); | ||||
|   } | ||||
| }; | ||||
| </script> | ||||
							
								
								
									
										227
									
								
								src/views/task/exam/components/steps/step4/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								src/views/task/exam/components/steps/step4/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,227 @@ | ||||
| <template> | ||||
|  | ||||
|   <ContentWrap> | ||||
|      <el-form | ||||
|       class="-mb-15px" | ||||
|       :model="queryParams" | ||||
|       ref="queryFormRef" | ||||
|       :inline="true" | ||||
|       label-width="68px" | ||||
|     > | ||||
|       | ||||
|       <el-form-item> | ||||
|         <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> | ||||
|         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> | ||||
|         <el-button | ||||
|           type="primary" | ||||
|           plain | ||||
|           @click="openForm('create')" | ||||
|           v-hasPermi="['system:sms-channel:create']" | ||||
|         > | ||||
|           <Icon icon="ep:plus" class="mr-5px" /> 新增</el-button | ||||
|         > | ||||
|  | ||||
|         <el-button | ||||
|   type="warning" | ||||
|   class="ele-btn-icon" | ||||
|   :disabled="!selections.length" | ||||
|   @click="papreSet()" | ||||
| > | ||||
|   批量分配 | ||||
| </el-button> | ||||
|  | ||||
|       </el-form-item> | ||||
|     </el-form>  | ||||
|   </ContentWrap> | ||||
|  | ||||
|   <!-- 列表 --> | ||||
|   <ContentWrap> | ||||
|     <el-table v-loading="loading" :data="list"   @selection-change="handleSelectionChange"> | ||||
|       <el-table-column type="selection" width="55" /> | ||||
|       <el-table-column label="考试批次" align="center" prop="batch" /> | ||||
|       <el-table-column label="考试开始时间" align="center" prop="startTime" /> | ||||
|       <el-table-column label="考试结束时间" align="center" prop="endTime" /> | ||||
|       <el-table-column label="开始考试前分钟,允许入场" align="center" prop="allowEntry" /> | ||||
|       <el-table-column label="开始考试后分钟,禁止入场" align="center" prop="endAllowEntry" /> | ||||
|       <el-table-column label="开始考试后分钟,允许交卷" align="center" prop="allowSubmission" /> | ||||
|       <el-table-column label="是否启用" align="center" prop="status"> | ||||
|         <template #default="scope"> | ||||
|           <dict-tag :type="DICT_TYPE.SYS_YES_NO" :value="scope.row.status" /> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column label="操作" align="center"> | ||||
|         <template #default="scope"> | ||||
|           <el-button | ||||
|             link | ||||
|             type="primary" | ||||
|             @click="openEdit('update', scope.row)" | ||||
|             v-hasPermi="['system:sms-channel:update']" | ||||
|           > | ||||
|             编辑 | ||||
|           </el-button> | ||||
|           <el-button | ||||
|             link | ||||
|             type="danger" | ||||
|             @click="handleDelete(scope.row.sessionId)" | ||||
|             v-hasPermi="['system:sms-channel:delete']" | ||||
|           > | ||||
|             删除 | ||||
|           </el-button> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|     </el-table> | ||||
|     <!-- 分页 --> | ||||
|     <Pagination | ||||
|       :total="total" | ||||
|       v-model:page="queryParams.pageNo" | ||||
|       v-model:limit="queryParams.pageSize" | ||||
|       @pagination="getList" | ||||
|     /> | ||||
|   </ContentWrap> | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|   <session-edit v-model="showEdit" :data="current" :task-Id="taskId" @done="reload"   ref="taskEditRef" /> | ||||
|   <paper-set v-model="showSet" :task-Id="taskId" :rows="selectedRows" @done="reload"/> | ||||
| </template> | ||||
| <script lang="ts" setup> | ||||
|  | ||||
| import * as SmsChannelApi from '@/api/system/session'; | ||||
|  | ||||
| import SessionSearch from './components/step-search.vue'; | ||||
| import SessionEdit from './components/step-edit.vue'; | ||||
| import PaperSet from './components/step-set.vue'; | ||||
| import { DICT_TYPE } from '@/utils/dict'; | ||||
|  | ||||
| defineOptions({ name: 'SystemPaper' }); | ||||
|   const props = defineProps({ | ||||
|     taskSpecialty: { | ||||
|     type: String, | ||||
|     default: '' | ||||
|   }, | ||||
|   taskId: { | ||||
|     type: String, | ||||
|     default: '' | ||||
|   } | ||||
| }) | ||||
|  | ||||
| const { t } = useI18n() // 国际化 | ||||
| const message = useMessage() // 消息弹窗 | ||||
|  | ||||
| const sessionAddRef = ref(); | ||||
|   /** 当前编辑数据 */ | ||||
|   const current = ref<object>(); | ||||
|  | ||||
|   /** 是否显示编辑弹窗 */ | ||||
|   const showEdit = ref(false); | ||||
|  | ||||
|   const showLook = ref(false); | ||||
|  | ||||
|   const showAdd = ref(false); | ||||
|  | ||||
|   const showSet = ref(false); | ||||
|    | ||||
|  | ||||
| const taskEditRef = ref() | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| const loading = ref(false) // 列表的加载中 | ||||
| const total = ref(0) // 列表的总页数 | ||||
| const list = ref([]) // 列表的数据 | ||||
| const queryFormRef = ref() // 搜索的表单 | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   signature: undefined, | ||||
|   status: undefined, | ||||
|   createTime: [], | ||||
|   taskId: props.taskId | ||||
| }) | ||||
|  | ||||
| /** 查询列表 */ | ||||
| const getList = async () => { | ||||
|   loading.value = true | ||||
|   try { | ||||
|     const res = await SmsChannelApi.pageSessions(queryParams) | ||||
|     console.log(res) | ||||
|     list.value = res | ||||
|     total.value = res.total | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** 搜索按钮操作 */ | ||||
| const handleQuery = () => { | ||||
|   queryParams.pageNo = 1 | ||||
|   getList() | ||||
| } | ||||
|  | ||||
| /** 重置按钮操作 */ | ||||
| const resetQuery = () => { | ||||
|   queryFormRef.value.resetFields() | ||||
|   handleQuery() | ||||
| } | ||||
|  | ||||
|   /** 表格选中数据 */ | ||||
|   const selections = ref([]); | ||||
|  | ||||
|   const selectedRows = ref<string[]>([]); | ||||
|  | ||||
| const papreSet = () => { | ||||
|   const rows = selections.value; | ||||
|   if (!rows.length) { | ||||
|     message.error('请至少选择一条数据'); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   selectedRows.value = rows.map((d: any) => d.sessionId); // 保存选中的行数据 | ||||
|   showSet.value = true; // 打开弹窗 | ||||
| }; | ||||
|  | ||||
|   const handleSelectionChange = (rows) => { | ||||
|   selections.value = rows; | ||||
| } | ||||
|  | ||||
| /** 添加/修改操作 */ | ||||
| const formRef = ref(); | ||||
| const openForm = (type: string, ) => { | ||||
|   showEdit.value = true | ||||
|  | ||||
|   console.log( current.value ) | ||||
|   taskEditRef.value?.open(type) | ||||
| } | ||||
|  | ||||
| const openEdit = (type: string, row?: object) => { | ||||
|   showEdit.value = true | ||||
|   current.value = row | ||||
|   console.log( current.value ) | ||||
|   taskEditRef.value?.open(type, row) | ||||
|    | ||||
| } | ||||
|  | ||||
| const reload = (where) => { | ||||
|   getList() | ||||
|   }; | ||||
|  | ||||
| /** 删除按钮操作 */ | ||||
| const handleDelete = async (id: number) => { | ||||
|   try { | ||||
|     // 删除的二次确认 | ||||
|     await message.delConfirm() | ||||
|     // 发起删除 | ||||
|     await SmsChannelApi.removeSession(id) | ||||
|     message.success(t('common.delSuccess')) | ||||
|     // 刷新列表 | ||||
|     await getList() | ||||
|   } catch {} | ||||
| } | ||||
|  | ||||
| /** 初始化 **/ | ||||
| onMounted(() => { | ||||
|   getList() | ||||
| }) | ||||
| </script> | ||||
| @@ -0,0 +1,264 @@ | ||||
| <template> | ||||
|   <Dialog | ||||
|     title="人员分配" | ||||
|     v-model:visible="visible" | ||||
|     width="80%" | ||||
|     append-to-body | ||||
|     lock-scroll | ||||
|     :destroy-on-close="false" | ||||
|     :draggable="false" | ||||
|     @close="handleDialogClose" | ||||
|   > | ||||
|     <el-form ref="formRef" :model="form" :rules="rules" label-width="80px"> | ||||
|       <ele-page style="height: 65vh;"> | ||||
|         <el-row :gutter="16" style="height: 100%;"> | ||||
|           <!-- 左侧任务列表 --> | ||||
|           <el-col :span="10" style="height: 100%;"> | ||||
|             <div style="height: 100%; overflow: auto;"> | ||||
|               <ele-card title="试卷任务列表" :body-style="{ paddingTop: '8px' }"> | ||||
|           | ||||
|                   <ContentWrap style="overflow-x: auto;"> | ||||
|                   <el-table | ||||
|                     table-layout="fixed" | ||||
|                     ref="taskTableRef" | ||||
|                     :data="mockTaskList" | ||||
|                     highlight-current-row | ||||
|                     @row-click="handleTaskRowClick" | ||||
|                     style="min-width: 1000px; width: 100%; " | ||||
|                   > | ||||
|                     <el-table-column type="index" label="#" width="50" align="center" /> | ||||
|                     <el-table-column label="考试批次" align="center" prop="batch" /> | ||||
|                     <el-table-column label="考试开始时间" align="center" prop="startTime" /> | ||||
|                     <el-table-column label="考试结束时间" align="center" prop="endTime" /> | ||||
|                     <el-table-column label="开始考试前分钟,允许入场" align="center" prop="allowEntry" /> | ||||
|                     <el-table-column label="开始考试后分钟,禁止入场" align="center" prop="endAllowEntry" /> | ||||
|                     <el-table-column label="开始考试后分钟,允许交卷" align="center" prop="allowSubmission" /> | ||||
|                   </el-table> | ||||
|                   <Pagination | ||||
|                   :total="taskTotal" | ||||
|                   v-model:page="queryParams.pageNo" | ||||
|                   v-model:limit="queryParams.pageSize" | ||||
|                   @pagination="getList" | ||||
|                 /> | ||||
|               </ContentWrap> | ||||
|               | ||||
|               </ele-card> | ||||
|             </div> | ||||
|           </el-col> | ||||
|  | ||||
|           <!-- 右侧人员分配列表 --> | ||||
|           <el-col :span="14" style="height: 100%;"> | ||||
|             <div style="height: 100%; overflow: auto;"> | ||||
|               <ele-card :body-style="{ paddingTop: '8px' }"> | ||||
|                 <div class="mb-2"> | ||||
|                   <el-button | ||||
|                     type="primary" | ||||
|                     @click="openAssignDialog()" | ||||
|                   > | ||||
|                   分配人员 | ||||
|                   </el-button> | ||||
|                   <el-button type="danger" @click="removeBatch()">删除</el-button> | ||||
|                   <el-button @click="exportData()">导出</el-button> | ||||
|                 </div> | ||||
|                   <ContentWrap style="overflow-x: auto;"> | ||||
|                   <el-table | ||||
|                     table-layout="fixed" | ||||
|                     ref="personTableRef" | ||||
|                     :data="filteredPersonList" | ||||
|                     style="min-width: 1000px; width: 100%" | ||||
|                     highlight-current-row | ||||
|                     @selection-change="selections = $event" | ||||
|                   > | ||||
|                     <el-table-column type="selection" width="50" align="center" /> | ||||
|                     <el-table-column type="index" label="#" width="50" align="center" /> | ||||
|                     <el-table-column label="用户名称" align="center" prop="username" /> | ||||
|                     <el-table-column label="用户昵称" align="center" prop="nickname" /> | ||||
|                     <el-table-column label="班级名称" align="center" prop="className"/> | ||||
|                     <el-table-column label="分配考场" align="center" prop="batch"/> | ||||
|                     <!-- <el-table-column label="操作" align="center" width="180"> | ||||
|                       <template #default="{ row }"> | ||||
|                         <el-link type="primary" @click="openEdit(row)">修改</el-link> | ||||
|                         <el-divider direction="vertical" /> | ||||
|                         <el-link type="danger" @click="removeBatch(row)">删除</el-link> | ||||
|                       </template> | ||||
|                     </el-table-column> --> | ||||
|                   </el-table> | ||||
|                   <Pagination | ||||
|                   :stutotal="stutotal" | ||||
|                   v-model:page="queryParams.pageNo" | ||||
|                   v-model:limit="queryParams.pageSize" | ||||
|                   @pagination="getList" | ||||
|                 /> | ||||
|               </ContentWrap> | ||||
|                 <el-empty | ||||
|                   v-if="filteredPersonList.length === 0" | ||||
|                   description="暂无分配人员" | ||||
|                 /> | ||||
|               </ele-card> | ||||
|             </div> | ||||
|           </el-col> | ||||
|         </el-row> | ||||
|       </ele-page> | ||||
|        | ||||
|  | ||||
|  | ||||
|   <PersonSearch :session-id="selectedSessionId"  :session-batch="selectedSessionBanch" :task-id="props.taskId" v-model="showPersonEdit"   ref="stuAddRef"   @done="reload" /> | ||||
|  | ||||
|  | ||||
|     </el-form> | ||||
|   </Dialog> | ||||
| </template> | ||||
|  | ||||
|  | ||||
| <script setup> | ||||
| import { ref, onMounted, nextTick, computed } from 'vue'; | ||||
| import PersonSearch from './person-serach.vue' | ||||
|  | ||||
| import * as SmsChannelApi from '@/api/system/session'; | ||||
| import * as PersonApi from '@/api/system/person' | ||||
| const visible = ref(true); | ||||
| const selectedSessionId = ref(null); | ||||
|  | ||||
| const selectedSessionBanch = ref(null); | ||||
|  | ||||
| const selections = ref([]); | ||||
| const showEdit = ref(false); | ||||
| const current = ref(null); | ||||
|  | ||||
| const taskTableRef = ref(); | ||||
| const personTableRef = ref(); | ||||
| const props = defineProps({ | ||||
|   taskId: { | ||||
|     type: String, | ||||
|     default: '' | ||||
|   } | ||||
| }) | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   status: '0', | ||||
|   taskId: props.taskId, | ||||
|   sessionId:'' | ||||
| }) | ||||
| const stuAddRef = ref(); | ||||
|  | ||||
| const showPersonEdit = ref(false) | ||||
| const emit = defineEmits(['done']) | ||||
| const openAssignDialog = () => { | ||||
|   if (!selectedSessionId.value) { | ||||
|     ElMessage.warning('请先选择左侧任务!') | ||||
|     return | ||||
|   } | ||||
|   showPersonEdit.value = true | ||||
|   nextTick(() => { | ||||
|     stuAddRef.value?.open(); | ||||
|   }); | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| const submitAssign = () => { | ||||
|   // 提交逻辑 | ||||
|   console.log('提交分配人员') | ||||
|   assignVisible.value = false | ||||
| } | ||||
|  | ||||
|  | ||||
| const mockTaskList = ref([]) ; | ||||
|  | ||||
| const filteredPersonList  = ref([]) ; | ||||
|  | ||||
|  | ||||
| const handleTaskRowClick = async (row) => { | ||||
|   selectedSessionId.value = row.sessionId;  | ||||
|   selectedSessionBanch.value=row.batch; | ||||
|  | ||||
|   queryParams.sessionId = row.sessionId; | ||||
|   const res = await SmsChannelApi.getSessionStu(queryParams); | ||||
|   filteredPersonList.value = res.list; | ||||
|   stutotal.value=res.total; | ||||
| }; | ||||
|  | ||||
|  | ||||
| const openEdit = (row = null) => { | ||||
|   current.value = row; | ||||
|   showEdit.value = true; | ||||
| }; | ||||
|  | ||||
| const removeBatch = async (row) => { | ||||
|   const selectedIds = selections.value.map(item => item.id); | ||||
|   console.log('选中的 ID 列表:', selectedIds); | ||||
|   console.log('选中的 考场 id:' + selectedSessionId.value); | ||||
|   console.log('选中的 试卷任务id:', props.taskId); | ||||
|  | ||||
|   // 弹出确认提示框 | ||||
|   ElMessageBox.confirm( | ||||
|     '确定要删除选中的人员吗?',  | ||||
|     '警告',  | ||||
|     { | ||||
|       confirmButtonText: '确定', | ||||
|       cancelButtonText: '取消', | ||||
|       type: 'warning', | ||||
|     } | ||||
|   ).then(async () => { | ||||
|     // 赋值参数 | ||||
|     const data = { | ||||
|       studentIds: selectedIds, | ||||
|       sessionId: selectedSessionId.value, | ||||
|       taskId: props.taskId, | ||||
|       batch: selectedSessionBanch.value, | ||||
|     }; | ||||
|  | ||||
|     await PersonApi.removeSessionStu(data); | ||||
|  | ||||
|     // 删除选中的人员 | ||||
|     for (const sel of selections.value) { | ||||
|       const index = filteredPersonList.value.findIndex(p => p.id === sel.id); | ||||
|       if (index !== -1) filteredPersonList.value.splice(index, 1); | ||||
|     } | ||||
|     emit('done') | ||||
|     ElMessage.success('删除成功'); | ||||
|   }).catch(() => { | ||||
|  | ||||
|     console.log('删除操作已取消'); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| const getList = async () => { | ||||
|     const res = await SmsChannelApi.pageSessions(queryParams) | ||||
|     mockTaskList.value = res | ||||
|      total.value = res.total | ||||
| } | ||||
| const reload = () => { | ||||
|   getList() | ||||
|   emit('done') | ||||
| } | ||||
|  | ||||
| const exportData = () => { | ||||
|   console.log('导出功能触发'); | ||||
| }; | ||||
|  | ||||
| const reloadPerson = () => { | ||||
|   // 模拟刷新 | ||||
|   console.log('刷新人员列表'); | ||||
| }; | ||||
|  | ||||
| const handleDialogClose = () => { | ||||
|   visible.value = false; | ||||
| }; | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| onMounted(() => { | ||||
|   getList() | ||||
| }) | ||||
| </script> | ||||
| @@ -0,0 +1,331 @@ | ||||
| <template> | ||||
|   <!-- 搜索 --> | ||||
|   <Dialog | ||||
|     title="人员分配" | ||||
|     v-model="visible" | ||||
|     width="80%" | ||||
|     append-to-body | ||||
|     lock-scroll | ||||
|     :destroy-on-close="false" | ||||
|     :draggable="false" | ||||
|     @close="handleDialogClose" | ||||
|   > | ||||
|   <ContentWrap> | ||||
|     <el-form | ||||
|       class="-mb-15px" | ||||
|       :model="queryParams" | ||||
|       ref="queryFormRef" | ||||
|       :inline="true" | ||||
|       label-width="68px" | ||||
|     > | ||||
|       <el-form-item label="学生名称" prop="username"> | ||||
|         <el-input | ||||
|           v-model="queryParams.username" | ||||
|           placeholder="请输入学生名称" | ||||
|           clearable | ||||
|           @keyup.enter="handleQuery" | ||||
|           class="!w-240px" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="班级" prop="className"> | ||||
|         <el-select | ||||
|             v-model="queryParams.className" | ||||
|             filterable | ||||
|             allow-create | ||||
|             default-first-option | ||||
|             placeholder="请选择或输入" | ||||
|             style="width: 300px" | ||||
|         > | ||||
|           <el-option | ||||
|               v-for="item in classNameList" | ||||
|               :key="item.className" | ||||
|               :label="item.className" | ||||
|               :value="item.className" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="状态" prop="status"> | ||||
|         <el-select | ||||
|           v-model="queryParams.status" | ||||
|           placeholder="请选择学生状态" | ||||
|           clearable | ||||
|           class="!w-240px" | ||||
|         > | ||||
|           <el-option | ||||
|             v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" | ||||
|             :key="dict.value" | ||||
|             :label="dict.label" | ||||
|             :value="dict.value" | ||||
|           /> | ||||
|         </el-select> | ||||
|       </el-form-item> | ||||
|       <el-form-item label="创建时间" prop="createTime"> | ||||
|         <el-date-picker | ||||
|           v-model="queryParams.createTime" | ||||
|           value-format="YYYY-MM-DD HH:mm:ss" | ||||
|           type="datetimerange" | ||||
|           start-placeholder="开始日期" | ||||
|           end-placeholder="结束日期" | ||||
|           class="!w-240px" | ||||
|         /> | ||||
|       </el-form-item> | ||||
|       <el-form-item> | ||||
|         <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button> | ||||
|         <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button> | ||||
|         <!-- <el-button | ||||
|           type="primary" | ||||
|           plain | ||||
|           @click="openForm('create')" | ||||
|         > | ||||
|           <Icon icon="ep:plus" /> 新增 | ||||
|         </el-button> --> | ||||
|         <!-- <el-button | ||||
|           type="warning" | ||||
|           plain | ||||
|           @click="handleImport" | ||||
|         > | ||||
|           <Icon icon="ep:upload" /> 导入 | ||||
|         </el-button> --> | ||||
|         <el-button | ||||
|           type="success" | ||||
|           plain | ||||
|           @click="handleExport" | ||||
|           :loading="exportLoading" | ||||
|         > | ||||
|           <Icon icon="ep:download" />导出 | ||||
|         </el-button> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|   </ContentWrap> | ||||
|   <ContentWrap> | ||||
|     <el-table v-loading="loading"   ref="tableRef" :data="list"  @selection-change="handleSelectionChange"> | ||||
|       <el-table-column type="selection" width="55" /> | ||||
|       <el-table-column label="用户编号" align="center" key="id" prop="id" /> | ||||
|       <el-table-column | ||||
|         label="用户名称" | ||||
|         align="center" | ||||
|         prop="username" | ||||
|         :show-overflow-tooltip="true" | ||||
|       /> | ||||
|       <el-table-column | ||||
|         label="用户昵称" | ||||
|         align="center" | ||||
|         prop="nickname" | ||||
|         :show-overflow-tooltip="true" | ||||
|       /> | ||||
|       <el-table-column | ||||
|         label="班级名称" | ||||
|         align="center" | ||||
|         prop="className" | ||||
|         :show-overflow-tooltip="true" | ||||
|       /> | ||||
|       <el-table-column label="状态" key="status" align="center"> | ||||
|         <template #default="scope"> | ||||
|           <el-switch | ||||
|             v-model="scope.row.status" | ||||
|             :active-value="0" | ||||
|             :inactive-value="1" | ||||
|      | ||||
|             disabled | ||||
|           /> | ||||
|         </template> | ||||
|       </el-table-column> | ||||
|       <el-table-column | ||||
|         label="创建时间" | ||||
|         align="center" | ||||
|         prop="createTime" | ||||
|         :formatter="dateFormatter" | ||||
|         width="180" | ||||
|       /> | ||||
|     </el-table> | ||||
|     <Pagination | ||||
|       :total="total" | ||||
|       v-model:page="queryParams.pageNo" | ||||
|       v-model:limit="queryParams.pageSize" | ||||
|       @pagination="open" | ||||
|     /> | ||||
|   </ContentWrap> | ||||
|   <template #footer> | ||||
|   <span class="dialog-footer"> | ||||
|     <el-button @click="handleDialogClose">取消</el-button> | ||||
|     <el-button type="primary" @click="confirmSelection">确认</el-button> | ||||
|   </span> | ||||
| </template> | ||||
|   </Dialog> | ||||
|  | ||||
|   <!-- 用户导入对话框 --> | ||||
|   <!-- <StudentImportForm ref="importFormRef" @success="open" /> --> | ||||
|  | ||||
| </template> | ||||
| <script lang="ts" setup> | ||||
| import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' | ||||
| import { dateFormatter } from '@/utils/formatTime' | ||||
| import download from '@/utils/download' | ||||
| import * as UserApi from '@/api/system/user' | ||||
| import * as ClassApi from '@/api/exam/class' | ||||
| import * as PersonApi from '@/api/system/person' | ||||
| defineOptions({ name: 'SystemUser' }) | ||||
| const props = defineProps({ | ||||
|   sessionId: { | ||||
|     type: String, | ||||
|     default: '' | ||||
|   }, | ||||
|   taskId: { | ||||
|     type: String, | ||||
|     default: '' | ||||
|   }, | ||||
|   sessionBatch:{ | ||||
|     type: String, | ||||
|     default: '' | ||||
|   } | ||||
| }) | ||||
| const tableRef = ref() | ||||
|  | ||||
| const visible = defineModel({ type: Boolean }); | ||||
|  | ||||
| const classNameList = ref(); | ||||
| const message = useMessage() // 消息弹窗 | ||||
| const { t } = useI18n() // 国际化 | ||||
|  | ||||
| const loading = ref(true) // 列表的加载中 | ||||
| const total = ref(0) // 列表的总页数 | ||||
| const list = ref([]) // 列表的数 | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   className: undefined, | ||||
|   nickname: undefined, | ||||
|   username: undefined, | ||||
|   mobile: undefined, | ||||
|   status: undefined, | ||||
|   deptId: undefined, | ||||
|   userType: "2", | ||||
|   createTime: [], | ||||
|   taskId:'', | ||||
|    | ||||
| }) | ||||
| const queryFormRef = ref() // 搜索的表单 | ||||
|  | ||||
| /** 查询列表 */ | ||||
| const open = async () => { | ||||
|   loading.value = true | ||||
|   classNameList.value = await ClassApi.ClassApi.getClassName() | ||||
|   queryParams.taskId=props.taskId | ||||
|   | ||||
|   console.log(props.taskId+"props.taskId") | ||||
|   console.log(props.sessionId+"props.sessionId") | ||||
|   try { | ||||
|     const params: any = { | ||||
|     ...queryParams, | ||||
|     sessionId:props.sessionId, | ||||
|     startTime: queryParams.createTime?.[0] || null, | ||||
|     endTime: queryParams.createTime?.[1] || null, | ||||
|   } | ||||
|  | ||||
|     const data = await PersonApi.getSessionStuBySearch(params) | ||||
|   | ||||
|     list.value = data.list | ||||
|     total.value = data.total | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
| const emit = defineEmits(['done']) | ||||
| /** 搜索按钮操作 */ | ||||
| const handleQuery = () => { | ||||
|   queryParams.pageNo = 1 | ||||
|   open() | ||||
| } | ||||
|  | ||||
| /** 重置按钮操作 */ | ||||
| const resetQuery = () => { | ||||
|   queryFormRef.value?.resetFields() | ||||
|   handleQuery() | ||||
| } | ||||
|  | ||||
| /** 添加/修改操作 */ | ||||
| const formRef = ref() | ||||
| const openForm = (type: string, id?: number) => { | ||||
|   formRef.value.open(type, id) | ||||
| } | ||||
|  | ||||
| /** 用户导入 */ | ||||
| const importFormRef = ref() | ||||
| const handleImport = () => { | ||||
|   importFormRef.value.open() | ||||
| } | ||||
| const multipleSelection = ref<UserApi.UserVO[]>([]) | ||||
|  | ||||
| const handleSelectionChange = (val: UserApi.UserVO[]) => { | ||||
|   multipleSelection.value = val | ||||
| } | ||||
|  | ||||
|  | ||||
| /** 导出按钮操作 */ | ||||
| const exportLoading = ref(false) | ||||
| const handleExport = async () => { | ||||
|   try { | ||||
|     // 导出的二次确认 | ||||
|     await message.exportConfirm() | ||||
|     // 发起导出 | ||||
|     exportLoading.value = true | ||||
|     const data = await UserApi.exportUser(queryParams) | ||||
|     download.excel(data, '用户数据.xls') | ||||
|   } catch { | ||||
|   } finally { | ||||
|     exportLoading.value = false | ||||
|   } | ||||
| } | ||||
|  | ||||
| const handleDialogClose = () => { | ||||
|   visible.value = false; | ||||
|   multipleSelection.value = [] // 清空选中的数据 | ||||
|   tableRef.value?.clearSelection() | ||||
|  | ||||
| }; | ||||
| const confirmSelection = async () => { | ||||
|   const selectedIds = multipleSelection.value.map(item => item.id) | ||||
|   const count = selectedIds.length | ||||
|  | ||||
|   if (count === 0) { | ||||
|     ElMessage.warning('请先选择学生') | ||||
|     return | ||||
|   } | ||||
|   try { | ||||
|     await ElMessageBox.confirm( | ||||
|       `已选中 ${count} 个学生,是否确认分配?`, | ||||
|       '确认分配', | ||||
|       { | ||||
|         confirmButtonText: '确认', | ||||
|         cancelButtonText: '取消', | ||||
|         type: 'warning', | ||||
|       } | ||||
|     ) | ||||
|  | ||||
|     const data = { | ||||
|       studentIds: selectedIds, | ||||
|       sessionId: props.sessionId, | ||||
|       taskId: props.taskId, | ||||
|       batch:props.sessionBatch | ||||
|     } | ||||
|     await PersonApi.setSessionStu(data) | ||||
|  | ||||
|     ElMessage.success(`成功分配 ${count} 个学生`) | ||||
|     emit('done') | ||||
|     visible.value = false | ||||
|     multipleSelection.value = [] | ||||
|     tableRef.value?.clearSelection() | ||||
|  | ||||
|   } catch (err) { | ||||
|     // 用户取消时会进入这里,不需要处理 | ||||
|     console.log('用户取消了分配') | ||||
|   } | ||||
| } | ||||
| defineExpose({ open }) | ||||
|  | ||||
|  | ||||
| /** 初始化 */ | ||||
| onMounted(() => { | ||||
|   open() | ||||
| }) | ||||
| </script> | ||||
							
								
								
									
										250
									
								
								src/views/task/exam/components/steps/step5/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								src/views/task/exam/components/steps/step5/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,250 @@ | ||||
| <template> | ||||
|  | ||||
|   <ContentWrap> | ||||
|      <el-form | ||||
|       class="-mb-15px" | ||||
|       :model="queryParams" | ||||
|       ref="queryFormRef" | ||||
|       :inline="true" | ||||
|       label-width="68px" | ||||
|     > | ||||
|  | ||||
|       <el-form-item> | ||||
|         <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> | ||||
|         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> | ||||
|         <el-button | ||||
|        | ||||
|           type="primary" | ||||
|           plain | ||||
|           @click="openSearch('create')" | ||||
|           v-hasPermi="['system:sms-channel:create']" | ||||
|         > | ||||
|           <Icon icon="ep:plus" class="mr-5px" /> 添加学生</el-button | ||||
|         > | ||||
|         <el-button | ||||
|           v-if="showAssignButton" | ||||
|           type="primary" | ||||
|           plain | ||||
|           @click="openEdit('create')" | ||||
|           v-hasPermi="['system:sms-channel:create']" | ||||
|         > | ||||
|           <Icon icon="ep:plus" class="mr-5px" /> 分配场次</el-button | ||||
|         > | ||||
|         <el-button | ||||
|   type="danger" | ||||
|   class="ele-btn-del" | ||||
|   :disabled="!selections.length" | ||||
|   @click="handleDeletes()" | ||||
| > | ||||
|   批量删除 | ||||
| </el-button> | ||||
|       </el-form-item> | ||||
|     </el-form>  | ||||
|   </ContentWrap> | ||||
|  | ||||
|   <!-- 列表 --> | ||||
|   <ContentWrap> | ||||
|     <el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange"> | ||||
|       <el-table-column type="selection" width="55" /> | ||||
|       <el-table-column label="用户名称" align="center" prop="username" /> | ||||
|       <el-table-column label="用户昵称" align="center" prop="nickname" /> | ||||
|       <el-table-column label="班级名称" align="center" prop="className"/> | ||||
|       <el-table-column label="分配考场" align="center" prop="batch"/> | ||||
|  | ||||
|       <!-- <el-table-column label="操作" align="center"> | ||||
|         <template #default="scope"> | ||||
|           <el-button | ||||
|             link | ||||
|             type="primary" | ||||
|             @click="openEdit('update', scope.row)" | ||||
|             v-hasPermi="['system:sms-channel:update']" | ||||
|           > | ||||
|             查看 | ||||
|           </el-button> | ||||
|           <el-button | ||||
|             link | ||||
|             type="danger" | ||||
|             @click="handleDelete(scope.row.schemeId)" | ||||
|             v-hasPermi="['system:sms-channel:delete']" | ||||
|           > | ||||
|             删除 | ||||
|           </el-button> | ||||
|         </template> | ||||
|       </el-table-column> --> | ||||
|     </el-table> | ||||
|     <!-- 分页 --> | ||||
|     <Pagination | ||||
|       :total="total" | ||||
|       v-model:page="queryParams.pageNo" | ||||
|       v-model:limit="queryParams.pageSize" | ||||
|       @pagination="getList" | ||||
|     /> | ||||
|   </ContentWrap> | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|   <!-- 编辑弹窗 --> | ||||
|   <person-edit v-model="showEdit"  :task-Id="props.taskId" @done="reload" /> | ||||
|   <PersonSearch  :task-id="props.taskId" v-model="showPersonEdit"   ref="stuAddRef"   @done="reload" /> | ||||
| </template> | ||||
| <script lang="ts" setup> | ||||
| import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' | ||||
|  | ||||
| import * as SmsPersonlApi from '@/api/system/person'; | ||||
| import PersonEdit from './components/person-edit.vue'; | ||||
| import PersonSearch from './components/person-serach.vue' | ||||
| import * as SmsChannelApi from '@/api/system/session'; | ||||
| import { fa } from 'element-plus/es/locale'; | ||||
|  | ||||
| defineOptions({ name: 'SystemSmsChannel' }) | ||||
| const showAssignButton = ref(true) // 控制按钮显示 | ||||
| const { t } = useI18n() // 国际化 | ||||
| const message = useMessage() // 消息弹窗 | ||||
| const props = defineProps({ | ||||
|   taskSpecialty: { | ||||
|     type: String, | ||||
|     default: '' | ||||
|   }, | ||||
|   taskId: { | ||||
|     type: String, | ||||
|     default: '' | ||||
|   }, | ||||
|   formData:{ | ||||
|     type: Object, | ||||
|     default: null | ||||
|   } | ||||
| }) | ||||
| const showPersonEdit = ref(false) | ||||
|   /** 当前编辑数据 */ | ||||
|   const current = ref<object>(); | ||||
|  | ||||
|   /** 是否显示编辑弹窗 */ | ||||
|   const showEdit = ref(false); | ||||
|  | ||||
|   const stuAddRef = ref(); | ||||
|  | ||||
|    | ||||
|  | ||||
| const taskEditRef = ref() | ||||
|  | ||||
|  | ||||
|  | ||||
| const loading = ref(false) // 列表的加载中 | ||||
| const total = ref(0) // 列表的总页数 | ||||
| const list = ref([]) // 列表的数据 | ||||
| const queryFormRef = ref() // 搜索的表单 | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   signature: undefined, | ||||
|   status: undefined, | ||||
|   createTime: [], | ||||
|   taskId: props.taskId  | ||||
| }) | ||||
| /** 查询列表 */ | ||||
| const getList = async () => { | ||||
|   loading.value = true | ||||
|  | ||||
|   try { | ||||
|     const resButton = await SmsChannelApi.fetchNoMes(props.taskId) | ||||
|  | ||||
|       if (resButton==='200') { | ||||
|         showAssignButton.value = true | ||||
|       } else { | ||||
|         showAssignButton.value = false | ||||
|       } | ||||
|  | ||||
|  | ||||
|  | ||||
|     const res = await SmsPersonlApi.pagePersons(queryParams) | ||||
|     console.log(res) | ||||
|     list.value = res.list | ||||
|     total.value = res.total | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** 搜索按钮操作 */ | ||||
| const handleQuery = () => { | ||||
|   queryParams.pageNo = 1 | ||||
|   getList() | ||||
| } | ||||
|  | ||||
| /** 重置按钮操作 */ | ||||
| const resetQuery = () => { | ||||
|   queryFormRef.value.resetFields() | ||||
|   handleQuery() | ||||
| } | ||||
| const reload = () => { | ||||
|   getList() | ||||
| } | ||||
|   /** 表格选中数据 */ | ||||
|   const selections = ref([]); | ||||
| const handleSelectionChange = (rows) => { | ||||
|   selections.value = rows; | ||||
| } | ||||
|  | ||||
|  | ||||
| const selectedRows = ref<string[]>([]); | ||||
|  | ||||
| const handleDeletes = async () => { | ||||
|   try { | ||||
|     const rows = selections.value; | ||||
|   if (!rows.length) { | ||||
|     message.error('请至少选择一条数据'); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   selectedRows.value = rows.map((d: any) => d.id); // 保存选中的行数据 | ||||
|  | ||||
|   const deleteData = { | ||||
|       studentIds: selectedRows.value,  // 选中的 ID 列表 | ||||
|       taskId: props.taskId  // 任务 ID | ||||
|     } | ||||
|    await SmsPersonlApi.removePersons(deleteData) | ||||
|     message.success(t('common.delSuccess')) | ||||
|     // 刷新列表 | ||||
|     await getList() | ||||
|   } catch {} | ||||
| } | ||||
|  | ||||
|  | ||||
| const openEdit = (type: string, row?: object) => { | ||||
|   showEdit.value = true | ||||
|   current.value = row | ||||
|   nextTick(() => { | ||||
|     taskEditRef.value?.open(type, row) | ||||
|   }) | ||||
| } | ||||
| const openSearch = (type: string, row?: object) => { | ||||
|   showPersonEdit.value = true | ||||
|   current.value = row | ||||
|   nextTick(() => { | ||||
|     stuAddRef.value?.open(type, row) | ||||
|   }) | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| /** 删除按钮操作 */ | ||||
| const handleDelete = async (id: number) => { | ||||
|   try { | ||||
|     // 删除的二次确认 | ||||
|     await message.delConfirm() | ||||
|     // 发起删除 | ||||
| console.log(id+"idididid") | ||||
|     await SmsPersonlApi.removePerson(id) | ||||
|     message.success(t('common.delSuccess')) | ||||
|     // 刷新列表 | ||||
|     await getList() | ||||
|   } catch {} | ||||
| } | ||||
|  | ||||
| /** 初始化 **/ | ||||
| onMounted(() => { | ||||
|   getList() | ||||
| }) | ||||
|  | ||||
| </script> | ||||
		Reference in New Issue
	
	Block a user
	 YOHO\20373
					YOHO\20373