scanDetail.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. <template>
  2. <!-- 扫描详情 -->
  3. <div class="page_list">
  4. <div class="search_content">
  5. <div class="content_left">
  6. <el-select v-model="paramInfo.batchNo" placeholder="选择批次" @change="GoSearch()" class="select_width" >
  7. <el-option label="全部批次" value=""></el-option>
  8. <el-option v-for="item in batchList" :key="item.value" :label="'批次'+item.label" :value="item.value"></el-option>
  9. </el-select>
  10. <el-select v-model="paramInfo.statusStr" placeholder="选择状态" @change="GoSearch()" class="select_width" >
  11. <el-option label="全部状态" value=""></el-option>
  12. <el-option v-for="item in stateList"
  13. :key="item.value"
  14. :label="item.label" :value="item.value"></el-option>
  15. </el-select>
  16. <el-input placeholder="考试名称,编号" v-model="paramInfo.keyWord" @input="GoSearch" @change="GoSearch()" class="input_width" >
  17. <el-button @click="GoSearch()" slot="append" icon="el-icon-search"></el-button>
  18. </el-input>
  19. </div>
  20. <div class="content_right">
  21. <el-button @click="Refresh" >
  22. <i class="iconfont icon_shuaxin"></i>刷新
  23. </el-button>
  24. <el-button @click="GotoScanHome()" type="primary">扫描页面</el-button>
  25. </div>
  26. </div>
  27. <div class="page_jg_20"></div>
  28. <div class="page_content" >
  29. <div class="content_table" style="width: 100%">
  30. <div class="table_header">
  31. <div class="header_left">
  32. <div class="scan_button_header">
  33. <el-button @click="ChangeMode('list')" :type="listMode=='list'?'primary':''"><el-icon><Menu /></el-icon> 列表模式</el-button>
  34. <el-button @click="ChangeMode('pic')" :type="listMode=='pic'?'primary':''"><el-icon><Picture /></el-icon> 图片模式</el-button>
  35. </div>
  36. </div>
  37. <div class="header_right">
  38. </div>
  39. </div>
  40. <div class="page_jg_20"></div>
  41. <div class="page_table" v-show="listMode=='list'">
  42. <el-table :data="tableData" style="width: 100%" :height="tableHeight">
  43. <el-table-column type="index" label="序号" width="100" align="center" >
  44. </el-table-column>
  45. <el-table-column prop="batchNo" label="批次" width="120" align="center">
  46. <template v-slot="scope">
  47. 批次{{ scope.row.batchNo}}
  48. </template>
  49. </el-table-column>
  50. <el-table-column prop="cardNumber" label="考号" width="120" align="center">
  51. <template v-slot="scope">
  52. <span v-if="scope.row.cardNumber">{{ scope.row.cardNumber}}</span>
  53. <span v-else>-</span>
  54. </template>
  55. </el-table-column>
  56. <el-table-column prop="studentName" label="姓名" width="120" align="center">
  57. <template v-slot="scope">
  58. <span v-if="scope.row.studentName">{{ scope.row.studentName}}</span>
  59. <span v-else>-</span>
  60. </template>
  61. </el-table-column>
  62. <el-table-column prop="studentName" label="页数" width="120" align="center">
  63. <template v-slot="scope">
  64. {{GetPageNumbers(scope.row.scanPictureVOS)}}
  65. </template>
  66. </el-table-column>
  67. <el-table-column prop="date" label="客观题" align="center">
  68. <template v-slot="scope">
  69. {{GetObjectAnswer(scope.row.objectiveQuestionMap)}}
  70. </template>
  71. </el-table-column>
  72. <el-table-column prop="fullScore" label="状态" width="120" align="center">
  73. <template v-slot="scope">
  74. <div class="table_row_status">
  75. <div class="normal_icon" v-if="scope.row.abnormalType==0"></div>
  76. <div class="abnormal_icon" v-else></div>
  77. {{GetStateName(scope.row.abnormalType)}}
  78. </div>
  79. </template>
  80. </el-table-column>
  81. <el-table-column prop="name" label="操作" width="150" align="center">
  82. <template v-slot="scope">
  83. <div class="ele_button table_row_button">
  84. <span class="btn_editor" @click="OpenViewPaper(scope.row)">查看</span>
  85. <!-- <span class="btn_delete" @click="DeleteSingle(scope.row)">删除</span> -->
  86. </div>
  87. </template>
  88. </el-table-column>
  89. </el-table>
  90. </div>
  91. <div class="image_list" v-show="listMode=='pic'" :style="{height:imageHeight+'px'}" >
  92. <div class="list_img_content" v-loading="loading" element-loading-text="加载中……" element-loading-spinner="el-icon-loading" element-loading-background="#ffffff">
  93. <div v-for="(image, index) in imageData" :key="'image_'+index" class="list_img_item">
  94. <div class="item_batch_name">
  95. <span><el-checkbox v-model="image.checked" style="margin-right: 5px;"></el-checkbox> 批次{{image.batchNo}}-{{image.seqNumber}} </span>
  96. <!-- <span class="" v-if="image.abnormalType==0">正常</span> -->
  97. <span class="name_abnormal" v-if="image.abnormalType==4">定位异常</span>
  98. <span class="name_abnormal" v-if="image.abnormalType==5">缺页异常</span>
  99. <span class="name_abnormal" v-if="image.abnormalType==1 || image.abnormalType==2 || image.abnormalType==3">考号异常</span>
  100. </div>
  101. <div class="item_batch_img" :class="image.abnormalType!=0 && image.abnormalType!=null?'item_batch_img_error':''" >
  102. <img :src="image.recognizeUrl" alt="" loading="lazy"></img>
  103. <div class="button_group">
  104. <div class="pic_button_view" @click="OpenViewPaper(image)">
  105. <i class="iconfont icon_xianshimima"></i>查看
  106. </div>
  107. </div>
  108. </div>
  109. </div>
  110. </div>
  111. </div>
  112. <div class="page_pagination">
  113. <el-pagination background
  114. :page-size="pageInfo.pageSize"
  115. :pager-count="11"
  116. layout="prev, pager, next"
  117. :total="pageInfo.total"
  118. :current-page="pageInfo.pageNum"
  119. @current-change="HandlePageChange"
  120. />
  121. </div>
  122. </div>
  123. </div>
  124. <PaperView v-model="showPaperDialog" :imageList="PaperImageList"></PaperView>
  125. </div>
  126. </template>
  127. <script lang="ts" setup>
  128. import { useExamStore } from '@/store/exam'
  129. import { useRouter,useRoute } from 'vue-router'
  130. import { onMounted ,ref,computed,nextTick,onUnmounted} from 'vue';
  131. import { ElMessageBox, ElMessage } from 'element-plus'
  132. import { getBatchDetailList,getBatchDetailImage } from '@/api/exam'
  133. import PaperView from './components/PaperView.vue'
  134. // 定义选项接口类型
  135. interface OptionItem {
  136. label: string
  137. value: string | number
  138. sort: number
  139. }
  140. // 实例化 Store
  141. const examStore = useExamStore()
  142. const router = useRouter()
  143. const route = useRoute() // 2. 获取 route 实例
  144. // 考试科目 ID
  145. const examSubjectId = computed(() => {
  146. return examStore.currentExam?.id
  147. })//计算属性
  148. const paramInfo=ref({
  149. batchNo:'',
  150. statusStr:'',//状态
  151. keyWord:''
  152. })//筛选数据
  153. const pageInfo=ref({
  154. pageNum:1,
  155. pageSize:10,
  156. total:0
  157. })//分页数据
  158. const batchList = ref<OptionItem[]>([])
  159. const stateList = ref<OptionItem[]>([])
  160. const listMode=ref('list');
  161. const tableData=ref([]);
  162. const imageData=ref([]);//图片数据
  163. const loading=ref(false); //加载状态
  164. const tableHeight=ref(500);
  165. const imageHeight=ref(500);
  166. const isImportStudent=ref(false);//是否导入了学生名单
  167. const showSelectStudent=ref(false);//是否显示选择学生名单弹窗
  168. const showPaperDialog=ref(false);//显示图片详情弹窗
  169. const PaperImageList=ref([]);//图片列表
  170. // 打开导入学生名单
  171. const OpenImportStudent = () => {
  172. showSelectStudent.value=true;
  173. }
  174. // 打开编辑考试名单
  175. const GotoScanHome = () => {
  176. router.push({
  177. path: '/exam/scanList',
  178. query: {
  179. examSubjectId: examSubjectId.value,
  180. },
  181. });
  182. }
  183. //获取扫描批次的页数
  184. const GetPageNumbers=(scanPictureVOS:any):string=>{
  185. if (scanPictureVOS && Array.isArray(scanPictureVOS) && scanPictureVOS.length > 0) {
  186. return scanPictureVOS
  187. .filter(item => item.pageNo !== -1)
  188. .map(item => item.pageNo === 99 ? '-' : item.pageNo)
  189. .join(',');
  190. }
  191. return '-';
  192. }
  193. //将答案对象转成字符串
  194. const GetObjectAnswer=(obj:any):string=>{
  195. let str=Object.entries(obj).map(([key, value]) => {
  196. return `${key}:${value}`
  197. }).join(' ')
  198. return str
  199. }
  200. //获取状态名称
  201. const GetStateName=(value:number):string=>{
  202. console.log("打印状态值",value);
  203. let stateName='';
  204. if(value==0)
  205. {
  206. stateName='已上传';
  207. }
  208. else if(value==1)
  209. {
  210. // stateName='考号未识别出来';
  211. stateName='考号异常';
  212. }
  213. else if(value==2)
  214. {
  215. // stateName='无此考生';
  216. stateName='考号异常';
  217. }
  218. else if(value==3)
  219. {
  220. // stateName='考号重复';
  221. stateName='考号异常';
  222. }
  223. else if(value==4)
  224. {
  225. stateName='定位异常';
  226. }
  227. else if(value==5)
  228. {
  229. stateName='缺页异常';
  230. }
  231. return stateName;
  232. }
  233. //切换模式
  234. const ChangeMode = (mode: string) => {
  235. listMode.value = mode;
  236. LoadData();
  237. }
  238. //加载数据
  239. const LoadData = () => {
  240. if(listMode.value=='list')
  241. {
  242. GetScanDetailList();
  243. }
  244. else if(listMode.value=='pic')
  245. {
  246. GetScanDetailImage();
  247. }
  248. }
  249. //查看图片
  250. const OpenViewPaper = (image:any) => {
  251. console.log("查看图片",image);
  252. let imageUrl=image.scanPictureVOS[0].recognizeUrl;
  253. PaperImageList.value.push(imageUrl);
  254. showPaperDialog.value=true;
  255. }
  256. //切换分页
  257. const HandlePageChange=(page:number)=>{
  258. pageInfo.value.pageNum=page;
  259. LoadData();
  260. }
  261. //搜索
  262. const GoSearch = () => {
  263. }
  264. //刷新
  265. const Refresh=() => {
  266. GetScanDetailList();
  267. }
  268. //计算高度的函数
  269. const CalculateTableHeight = () => {
  270. // nextTick 确保 DOM 更新后再获取尺寸
  271. nextTick(() => {
  272. // window.innerHeight 是浏览器可视区域高度
  273. // 简单算法:视窗高度 - 固定占用高度
  274. let computedHeight = window.innerHeight - 245;
  275. // 限制最小高度,防止太矮
  276. if (computedHeight < 200) {
  277. computedHeight = 200;
  278. }
  279. tableHeight.value = computedHeight;
  280. imageHeight.value = computedHeight;
  281. });
  282. };
  283. //获取扫描详情 列表模式
  284. const GetScanDetailList = async () => {
  285. const params = {
  286. examSubjectId: examSubjectId.value,
  287. batchNo:paramInfo.value.batchNo,//批次号
  288. statusStr:paramInfo.value.statusStr,//状态
  289. nameCode:paramInfo.value.keyWord,//搜索条件 姓名 考号
  290. schoolId: 0,//单校 0
  291. pageNum:pageInfo.value.pageNum,//当前页码
  292. pageSize:pageInfo.value.pageSize,//每页条数
  293. };
  294. const res = await getBatchDetailList(params);
  295. console.log("打印批次详情列表",res);
  296. if(res.code==200)
  297. {
  298. batchList.value=res.data.batchSelects;
  299. stateList.value=res.data.statusSelects;
  300. tableData.value=res.data.pageResult.records;
  301. pageInfo.value.total=Number(res.data.pageResult.total);
  302. }
  303. else
  304. {
  305. ElMessage.error(res.msg);
  306. }
  307. }
  308. //获取扫描详情 图片模式
  309. const GetScanDetailImage = async () => {
  310. const params = {
  311. examSubjectId: examSubjectId.value,
  312. batchNo:paramInfo.value.batchNo,//批次号
  313. statusStr:paramInfo.value.statusStr,//状态
  314. nameCode:paramInfo.value.keyWord,//搜索条件 姓名 考号
  315. schoolId: 0,//单校 0
  316. pageNum:pageInfo.value.pageNum,//当前页码
  317. pageSize:pageInfo.value.pageSize,//每页条数
  318. };
  319. const res = await getBatchDetailImage(params);
  320. console.log("打印批次详情列表",res);
  321. if(res.code==200)
  322. {
  323. imageData.value=res.data.records;
  324. pageInfo.value.total=Number(res.data.total);
  325. }
  326. else
  327. {
  328. ElMessage.error(res.msg);
  329. }
  330. }
  331. onMounted(() => {
  332. // 3. 获取地址栏参数 batchNo 并赋值
  333. if (route.query.batchNo) {
  334. paramInfo.value.batchNo = route.query.batchNo as string;
  335. }
  336. LoadData();//加载数据
  337. CalculateTableHeight();//初始化计算表格高度
  338. // 监听窗口大小变化
  339. window.addEventListener('resize', CalculateTableHeight);
  340. })
  341. // 卸载时移除监听,防止内存泄漏
  342. onUnmounted(() => {
  343. window.removeEventListener('resize', CalculateTableHeight);
  344. });
  345. </script>
  346. <style lang="scss" scoped>
  347. .page_list
  348. {
  349. width: 100%;
  350. height: 100%;
  351. padding: 20px;
  352. background-color: #fff;
  353. border-radius: 4px;
  354. box-sizing: border-box;
  355. }
  356. .image_list {
  357. width: 100%;
  358. overflow: auto;
  359. .list_img_content {
  360. display: flex;
  361. justify-content: flex-start;
  362. align-items: flex-start;
  363. /* 顶部对齐 */
  364. box-sizing: border-box;
  365. flex-wrap: wrap;
  366. gap: 16px;
  367. .list_img_item {
  368. /* 基础宽度 - 每行7个 */
  369. flex: 0 0 calc((100% - 16px * 6) / 7);
  370. /* 最小宽度 */
  371. min-width: 160px;
  372. /* 保持宽高比(可选) */
  373. // aspect-ratio: 1/1;
  374. // margin-right: 15px;
  375. // margin-bottom: 16px;
  376. /* 内容样式 */
  377. height: auto;
  378. // margin-right: 13px;
  379. .item_batch_name {
  380. width: 100%;
  381. font-size: 14px;
  382. color: #666666;
  383. text-align: left;
  384. display: flex;
  385. justify-content: space-between;
  386. line-height: 30px;
  387. .name_abnormal {
  388. color: #F56C6C;
  389. }
  390. }
  391. .item_batch_img {
  392. width: 100%;
  393. height: auto;
  394. padding: 8px;
  395. box-sizing: border-box;
  396. background-color: #F0F4F8;
  397. border-radius: 6px 6px 6px 6px;
  398. border: 1px solid #EBEEF5;
  399. display: flex;
  400. justify-content: center;
  401. position: relative;
  402. img {
  403. width: 100%;
  404. height: auto;
  405. object-fit: cover;
  406. z-index: 9;
  407. }
  408. }
  409. .item_batch_img_error {
  410. border: 1px solid #F56C6C !important;
  411. }
  412. .item_batch_img:hover .button_group {
  413. display: flex;
  414. justify-content: center;
  415. align-items: center;
  416. }
  417. .button_group {
  418. position: absolute;
  419. width: 100%;
  420. height: 100%;
  421. z-index: 10;
  422. top: 0px;
  423. left: 0;
  424. background: rgba(0, 0, 0, 0.4);
  425. display: none;
  426. align-items: center;
  427. justify-content: center;
  428. gap: 16px;
  429. z-index: 999;
  430. .pic_button_view {
  431. width: 68px;
  432. height: 34px;
  433. text-align: center;
  434. line-height: 34px;
  435. cursor: pointer;
  436. background-color: rgba(255, 255, 255, 1);
  437. border: 1px solid rgba(220, 223, 230, 1);
  438. border-radius: 4px;
  439. color: #666666;
  440. i {
  441. margin-right: 5px;
  442. }
  443. font-size: 14px;
  444. }
  445. }
  446. }
  447. @media (max-width: 1458px) {
  448. .list_img_item {
  449. flex: 0 0 calc((100% - 16px * 5) / 6);
  450. }
  451. }
  452. @media (max-width: 1280px) {
  453. .list_img_item {
  454. flex: 0 0 calc((100% - 16px * 4) / 5);
  455. }
  456. }
  457. @media (max-width: 1109px) {
  458. .list_img_item {
  459. flex: 0 0 calc((100% - 16px * 3) / 4);
  460. }
  461. }
  462. @media (max-width: 940px) {
  463. .list_img_item {
  464. flex: 0 0 calc((100% - 16px * 2) / 3);
  465. }
  466. }
  467. }
  468. }
  469. </style>