examList.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. <template>
  2. <!-- 考场名单 -->
  3. <div class="page_item page_list">
  4. <div class="search_content">
  5. <div class="content_left">
  6. <el-input placeholder="请输入学生姓名" v-model="searchContent" @input="GoSearch" @change="GoSearch()" style="width:240px;" >
  7. <el-button @click="GoSearch()" slot="append" icon="el-icon-search"></el-button>
  8. </el-input>
  9. </div>
  10. <div class="content_right el_button">
  11. <el-button @click="Refresh()" class="refresh_btn"><i class="iconfont icon_shuaxin"></i>刷新</el-button>
  12. <!-- <el-button @click="ExportExamStudentList()" class="refresh_btn">
  13. <i class="iconfont icon_export" style="margin-right: 5px;"> </i> 导出
  14. </el-button> -->
  15. <el-button @click="BackToTemplate()" class="other_btn" >重新导入名单</el-button>
  16. <el-button @click="OpenAddStudent()" class="add_student" >新增学生</el-button>
  17. <el-button @click="BackToScan()" type="primary" >扫描页面</el-button>
  18. </div>
  19. </div>
  20. <div class="page_jg_20"></div>
  21. <div class="table_content">
  22. <div class="content_left page_table">
  23. <el-table :data="examRoomList" style="width: 100%" :height="tableHeight">
  24. <el-table-column align="center" width="80" prop="examInfo" >
  25. <template v-slot="scope">
  26. <el-radio v-model="selectExamRoomCode" :value="scope.row.examRoomCode"></el-radio>
  27. </template>
  28. </el-table-column>
  29. <el-table-column prop="examRoomName" label="班级" width="80" align="center">
  30. </el-table-column>
  31. <el-table-column prop="date" label="缺考/学生数" width="120" align="center">
  32. <template v-slot="scope">
  33. <div class="answer_input">
  34. {{scope.row.missExamNum}}/{{scope.row.totalStudentNum}}
  35. </div>
  36. </template>
  37. </el-table-column>
  38. <el-table-column prop="fullScore" label="异常数" width="80" align="center">
  39. <template v-slot="scope">
  40. <div class="full_mark_input">
  41. {{scope.row.abnormalNum}}
  42. </div>
  43. </template>
  44. </el-table-column>
  45. <el-table-column prop="name" label="已上传/学生数" align="center">
  46. <template v-slot="scope">
  47. <div class="full_mark_input">
  48. {{scope.row.uploadedNum}}/{{scope.row.totalStudentNum}}
  49. </div>
  50. </template>
  51. </el-table-column>
  52. </el-table>
  53. </div>
  54. <div class="content_right page_table">
  55. <el-table :data="examStudentList" style="width: 100%" :height="tableHeight">
  56. <el-table-column type="index" label="序号" width="100" align="center">
  57. </el-table-column>
  58. <el-table-column prop="studentCode" label="学号" width="120" align="center">
  59. </el-table-column>
  60. <el-table-column prop="fullScore" label="姓名" width="120" align="center">
  61. <template v-slot="scope">
  62. <div class="full_mark_input">
  63. {{scope.row.studentName}}
  64. </div>
  65. </template>
  66. </el-table-column>
  67. <el-table-column prop="examCode" label="考号" width="170" align="center">
  68. <template v-slot="scope">
  69. {{scope.row.examCode}}
  70. </template>
  71. </el-table-column>
  72. <el-table-column prop="adminClassName" label="行政班" width="120" align="center">
  73. <template v-slot="scope">
  74. {{scope.row.adminClassName}}
  75. </template>
  76. </el-table-column>
  77. <el-table-column prop="teachingClassName" label="教学班" width="120" align="center">
  78. </el-table-column>
  79. <el-table-column label="状态" width="130" align="center">
  80. <template v-slot="scope">
  81. <div class="table_row_scan_state"></div>
  82. <div v-if="scope.row.scannedStatus==3" class="table_row_studentStatus"><div class="que_icon"></div>缺页</div>
  83. <div v-if="scope.row.scannedStatus==0" class="table_row_studentStatus"><div class="no_scan_icon"></div>未扫描</div>
  84. <div v-if="scope.row.scannedStatus==1" class="table_row_studentStatus"><div class="upload_icon"></div>已上传</div>
  85. <div v-if="scope.row.scannedStatus==2" class="table_row_studentStatus"><div class="qk_icon"></div>缺考</div>
  86. </template>
  87. </el-table-column>
  88. <el-table-column prop="name" label="操作" align="center">
  89. <template v-slot="scope">
  90. <div class="table_row_option" >
  91. <div class="button_editor" @click="EditorStudent(scope.row)">
  92. 编辑
  93. </div>
  94. <!-- 逻辑 有图片的才能查看 无图片的不能查看 -->
  95. <div class="button_editor" v-if="scope.row.scanPictureVOS && scope.row.scanPictureVOS.length>0" @click="OpenImage(scope.row)">
  96. 查看
  97. </div>
  98. <div class="editor_disable" v-else>
  99. 查看
  100. </div>
  101. <div class="button_delete" @click="ConfirmDelete(scope.row)" v-if="scope.row.scannedStatus==0">
  102. 删除
  103. </div>
  104. <div class="editor_disable" v-else>
  105. 删除
  106. </div>
  107. </div>
  108. </template>
  109. </el-table-column>
  110. </el-table>
  111. </div>
  112. </div>
  113. <div class="page_jg_20"></div>
  114. <div class="page_bottom">
  115. <div class="bottom_left">
  116. <span>班级数:{{examInfo.classCount}}个</span>
  117. <span>学生总数:{{examInfo.totalStudentCount}}个</span>
  118. <span>缺考人数:{{examInfo.missCount}}个</span>
  119. <span>已扫描学生:<span class="span_red">{{examInfo.uploadedCount}}个</span></span>
  120. <span>扫描进度:<span class="span_red">{{examInfo.scanRate}}%</span></span>
  121. </div>
  122. <div class="bottom_right">
  123. 当前选中班级:{{selectExamRoomName}} 学生数:{{examStudentList.length }}人
  124. </div>
  125. </div>
  126. <AddStudent ref="addStudentRef" v-model="showAddStudent" :selectSchoolId="selectSchoolId" @success="HandleSuccess" />
  127. </div>
  128. </template>
  129. <script lang="ts" setup>
  130. import { getExamRoomList,getExamRoomStudentList,deleteExamRoomStudent,getClassListData} from '@/api/exam'
  131. import { useExamStore } from '@/store/exam'
  132. import { useRouter } from 'vue-router'
  133. import { onMounted, onUnmounted, ref, nextTick,computed, watch } from 'vue';
  134. import { ElMessageBox, ElMessage } from 'element-plus'
  135. import AddStudent from './components/addStudent.vue' // 新增学生弹窗
  136. // 实例化 Store
  137. const examStore = useExamStore()
  138. const router = useRouter()
  139. const examRoomList=ref([]);//考场列表
  140. const examStudentList=ref([]);//考场学生列表
  141. const selectSchoolId=ref(0);
  142. const searchContent=ref('');//搜索内容
  143. const selectExamRoomCode=ref('');//选择的考场code
  144. const selectExamRoomName=ref('');//选择的考场名称
  145. const showAddStudent=ref(false);//是否显示新增学生弹窗
  146. const currentEditItem=ref<any>(null);//当前编辑的学生数据
  147. const addStudentRef = ref<any>(null);//新增学生弹窗实例
  148. const teachingClassList = ref<any[]>([]);
  149. const adminClassList = ref<any[]>([]);
  150. const studentTypeList=ref<any[]>([]);
  151. const tableHeight=ref(400);//表格高度
  152. const examInfo=ref({
  153. classCount:0,//班级数
  154. totalStudentCount:0,//学生总数
  155. missCount:0,//缺考人数
  156. uploadedCount:0,//已上传学生数
  157. abnormalCount:0,//异常数
  158. scanRate:0,//扫描进度
  159. });
  160. // 考试科目 ID
  161. const examSubjectId = computed(() => {
  162. return examStore.currentExam?.id
  163. })//计算属性
  164. //学校id
  165. const schoolId = computed(() => {
  166. return examStore.currentExam?.schoolInfoId || 0
  167. })
  168. //刷新
  169. const Refresh = () => {
  170. examRoomList.value=[];
  171. examStudentList.value=[];
  172. GetExamRoomList();//获取考场列表
  173. }
  174. // 返回到扫描界面
  175. const BackToScan = () => {
  176. router.push({
  177. path: '/exam/scanList',
  178. query: {
  179. examSubjectId: examSubjectId.value,
  180. },
  181. });
  182. }
  183. // 监听选择的考场代码变化,自动更新考场名称
  184. watch(selectExamRoomCode, (newCode) => {
  185. if (!newCode || !examRoomList.value || examRoomList.value.length === 0) {
  186. selectExamRoomName.value = '';
  187. return;
  188. }
  189. // 在考场列表中查找匹配的项
  190. const selectedRoom = examRoomList.value.find((room: any) => room.examRoomCode === newCode);
  191. if (selectedRoom) {
  192. selectExamRoomName.value = selectedRoom.examRoomName;
  193. console.log('当前选中考场:', selectExamRoomName.value);
  194. // 可选:如果切换考场需要重新获取学生列表,可以在这里调用
  195. GetExamRoomStudentList();
  196. } else {
  197. selectExamRoomName.value = '';
  198. }
  199. });
  200. //搜索
  201. const GoSearch=()=>{
  202. console.log('搜索内容:', searchContent.value);
  203. GetExamRoomStudentList();
  204. }
  205. //重新导入名单
  206. const BackToTemplate =() =>
  207. {
  208. }
  209. //导出学生名单
  210. const ExportExamStudentList=()=>
  211. {
  212. }
  213. //新增学生
  214. const OpenAddStudent=()=>{
  215. showAddStudent.value=true;
  216. }
  217. //编辑学生
  218. const EditorStudent=async (row:any)=>{
  219. console.log("打印编辑学生信息", row);
  220. if (addStudentRef.value) {
  221. addStudentRef.value.EditorStudent(row);
  222. }
  223. // 打开弹窗并传递编辑数据
  224. showAddStudent.value = true;
  225. }
  226. //编辑或者新增学生弹窗成功消息
  227. const HandleSuccess=() => {
  228. }
  229. //获取考试班级列表
  230. const GetClassListData = () => {
  231. let param = {
  232. examSubjectId: examSubjectId.value,
  233. schoolId: schoolId.value,
  234. }
  235. getClassListData(param).then((res: any) => {
  236. if (res.code == 200) {
  237. adminClassList.value = res.data.adminClassSelect || []
  238. teachingClassList.value = res.data.teachClassSelect || []
  239. studentTypeList.value = res.data?.studentType || []
  240. examRoomList.value = res.data.examRoomSelect || []
  241. }
  242. })
  243. }
  244. //删除学生
  245. const ConfirmDelete=(row:any)=>{
  246. console.log('删除',row);
  247. ElMessageBox.confirm('确认删除该学生吗?', '提示', {
  248. confirmButtonText: '确定',
  249. cancelButtonText: '取消',
  250. type: 'warning',
  251. customClass:'page_dialog'
  252. }).then(async () => {
  253. console.log('删除学生')
  254. const param={
  255. id:row.id,
  256. };
  257. const res = await deleteExamRoomStudent(param);
  258. console.log('删除结果:', res);
  259. if (res.code === 200) {
  260. ElMessage.success('删除成功');
  261. GetExamRoomStudentList();
  262. }
  263. else
  264. {
  265. ElMessage.error('删除失败!'+res.msg);
  266. }
  267. })
  268. }
  269. //获取考场列表
  270. const GetExamRoomList = async () => {
  271. try {
  272. const params={
  273. examSubjectId:examSubjectId.value,
  274. schoolId:schoolId.value,
  275. };
  276. const res = await getExamRoomList(params);
  277. if (res.code === 200) {
  278. examRoomList.value = res.data.examRoomTblVOS;
  279. selectExamRoomCode.value = examRoomList.value[0]?.examRoomCode;
  280. examInfo.value={
  281. classCount:res.data.examRoomNum,//班级数
  282. totalStudentCount:res.data.totalStudentNum,//学生总数
  283. missCount:res.data.totalMissExamNum,//缺考人数
  284. uploadedCount:res.data.totalUploadedNum,//已上传学生数
  285. abnormalCount:0,//异常数
  286. scanRate:res.data.totalUploadRate,//扫描进度
  287. };
  288. GetExamRoomStudentList();
  289. }
  290. console.log('考场列表', res);
  291. } catch (error) {
  292. console.error('获取考场列表失败', error);
  293. }
  294. };
  295. //获取扫描考场的学生列表
  296. const GetExamRoomStudentList = async () => {
  297. try {
  298. const params = {
  299. examSubjectId:examSubjectId.value,
  300. schoolId:schoolId.value,
  301. examRoomCode:selectExamRoomCode.value,
  302. studentName:searchContent.value,//学生姓名搜索
  303. pageNum:1,
  304. pageSize:1000,
  305. };
  306. console.log('获取考场学生列表参数', params); // 打印参数
  307. const res = await getExamRoomStudentList(params);
  308. if (res.code === 200)
  309. {
  310. examStudentList.value = res.data.records;
  311. }
  312. console.log('获取考场学生列表成功', res);
  313. } catch (error) {
  314. console.error('获取考场学生列表失败', error);
  315. return error;
  316. }
  317. }
  318. //计算高度的函数
  319. const CalculateTableHeight = () => {
  320. // nextTick 确保 DOM 更新后再获取尺寸
  321. nextTick(() => {
  322. // window.innerHeight 是浏览器可视区域高度
  323. // 简单算法:视窗高度 - 固定占用高度
  324. let computedHeight = window.innerHeight - 40 - 20 - 36 - 20 - 65;
  325. // 限制最小高度,防止太矮
  326. if (computedHeight < 200) {
  327. computedHeight = 200;
  328. }
  329. tableHeight.value = computedHeight;
  330. });
  331. };
  332. onMounted(() => {
  333. if (!examStore.currentExam) {
  334. console.warn('当前没有选中的考试信息')
  335. // 可选:如果没有数据,可以重定向回列表页或提示用户
  336. }
  337. GetExamRoomList();//获取考场列表
  338. // 初始化计算
  339. CalculateTableHeight();
  340. // 监听窗口大小变化
  341. window.addEventListener('resize', CalculateTableHeight);
  342. })
  343. // 卸载时移除监听,防止内存泄漏
  344. onUnmounted(() => {
  345. window.removeEventListener('resize', CalculateTableHeight);
  346. });
  347. </script>
  348. <style lang="scss" scoped>
  349. .table_content
  350. {
  351. width: 100%;
  352. display: flex;
  353. justify-content: space-between;
  354. height: calc(100vh - 140px - 40px);
  355. .content_left
  356. {
  357. width: 35%;
  358. height: 100%;
  359. }
  360. .content_right
  361. {
  362. width: calc(65% - 20px);
  363. height: 100%;
  364. }
  365. }
  366. //状态
  367. .table_row_studentStatus
  368. {
  369. font-weight: 400;
  370. font-size: 14px;
  371. color: #666666;
  372. display: flex;
  373. align-items: center;
  374. justify-content:center;
  375. gap: 5px;
  376. //缺页
  377. .que_icon
  378. {
  379. margin-left: 5px;
  380. width: 6px;
  381. height: 6px;
  382. border-radius: 50%;
  383. background: #F56C6C;
  384. }
  385. //异常的
  386. .qk_icon
  387. {
  388. margin-left: 5px;
  389. width: 6px;
  390. height: 6px;
  391. border-radius: 50%;
  392. background: #FB9F34;
  393. }
  394. //已上传
  395. .upload_icon
  396. {
  397. margin-left: 5px;
  398. width: 6px;
  399. height: 6px;
  400. background: #2BC644;
  401. border-radius: 50%;
  402. }
  403. //未扫描
  404. .no_scan_icon
  405. {
  406. margin-left: 5px;
  407. width: 6px;
  408. height: 6px;
  409. background: #2E64FA;
  410. border-radius: 50%;
  411. }
  412. }
  413. </style>