scanList.vue 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846
  1. <template>
  2. <div class="page_list">
  3. <div class="search_content">
  4. <div class="content_left">
  5. <span class="scan_state_title">客户端状态:</span>
  6. <span class="scan_state_open" v-if="scanClientStates">
  7. <i class="iconfont icon_open"></i>已打开</span>
  8. <span class="scan_state_close" v-else>
  9. <i class="iconfont icon_close"></i>未打开</span>
  10. <span v-if="scanClientStates" class="scan_state_title" style="color: #999;">(当前客户端版本:{{scanClientVersion}})</span>
  11. <!-- <el-select v-model="params.batchNo" placeholder="选择批次" @change="GoSearch()" class="select_width" >
  12. <el-option label="全部批次" value=""></el-option>
  13. <el-option v-for="item in batchList"
  14. :key="item.value"
  15. :label="item.label" :value="item.value"></el-option>
  16. </el-select>
  17. <el-select v-model="params.batchNo" placeholder="选择状态" @change="GoSearch()" class="select_width" >
  18. <el-option label="全部状态" value=""></el-option>
  19. <el-option v-for="item in batchList"
  20. :key="item.value"
  21. :label="item.label" :value="item.value"></el-option>
  22. </el-select>
  23. <el-input placeholder="考试名称,编号" v-model="params.keyWord" @input="GoSearch" @change="GoSearch()" class="input_width" >
  24. <el-button @click="GoSearch()" slot="append" icon="el-icon-search"></el-button>
  25. </el-input> -->
  26. </div>
  27. <div class="content_right">
  28. <el-button @click="Refresh" >
  29. <i class="iconfont icon_shuaxin"></i>刷新
  30. </el-button>
  31. <!-- <el-button class="delete_item" type="text" @click="OpenDeleteAllDialog" v-if="tableData.length>0">删除所有</el-button> -->
  32. <el-button @click="OpenReIdentify" >重新识别</el-button>
  33. <el-button @click="OpenEditExamList()" type="primary" v-if="isImportStudent">编辑考场名单</el-button>
  34. <el-button @click="OpenImportStudent()" type="primary" v-else>导入考场名单</el-button>
  35. </div>
  36. </div>
  37. <div class="page_jg_20"></div>
  38. <div class="page_content" >
  39. <div class="content_table">
  40. <div class="page_table">
  41. <el-table :data="tableData" style="width: 100%" :height="tableHeight">
  42. <!-- <el-table-column prop="questionName" label="序号" width="100" align="center" >
  43. </el-table-column> -->
  44. <el-table-column prop="batchNo" label="批次" width="120" align="center">
  45. </el-table-column>
  46. <el-table-column prop="scannedPaperNum" label="扫描张数" align="center">
  47. </el-table-column>
  48. <el-table-column prop="uploadNum" label="待上传张数" align="center" >
  49. <template v-slot="scope">
  50. </template>
  51. </el-table-column>
  52. <el-table-column prop="uploadNum" label="已上传张数" align="center" >
  53. <template v-slot="scope">
  54. </template>
  55. </el-table-column>
  56. <el-table-column prop="scanUserName" label="扫描人" align="center" >
  57. <template v-slot="scope">
  58. </template>
  59. </el-table-column>
  60. <el-table-column prop="scanUserName" label="扫描时间" align="center">
  61. <template v-slot="scope">
  62. {{formatTimestamp(scope.row.scannedTime) }}
  63. </template>
  64. </el-table-column>
  65. <el-table-column prop="name" label="操作" width="250" align="center">
  66. <template v-slot="scope">
  67. <div class="ele_button table_row_button" v-if="scope.row.uploadStatus==0" style="color:#2E64FA;">
  68. <i class="el-icon-loading"></i>{{loadingText}}……
  69. </div>
  70. <div class="ele_button table_row_button" v-else>
  71. <span class="btn_editor" @click="GotoBatchDetail(scope.row.batchNo)">详情</span>
  72. <span class="btn_delete" v-if="scope.row.scanUserName==currentUserName " @click="OptionDelete(scope.row)">删除</span>
  73. <span class="editor_disable" v-else>删除</span>
  74. </div>
  75. </template>
  76. </el-table-column>
  77. </el-table>
  78. </div>
  79. </div>
  80. <div class="content_right">
  81. <div class="right_header">
  82. <span> 扫描 设置</span>
  83. <span>
  84. 识别号:
  85. <el-select v-model="params.batchNo" placeholder="请选择" @change="GoSearch()" style="width: 120px;">
  86. <el-option v-for="item in scanIdentifyList" :key="item.value" :label="item.label" :value="item.value"></el-option>
  87. </el-select>
  88. </span>
  89. </div>
  90. <div class="right_center">
  91. <div class="scan_buttons">
  92. <ScanButton :process="scanProcess" :quekao="scanQuekao" :yichang="scanYichang" @click="OpenScan()"></ScanButton>
  93. </div>
  94. <div class="scan_list">
  95. <div class="list_item no_scan" >
  96. <div class="list_item_info" @click="GotoDetail(0)">
  97. <div class="item_info_title">
  98. 未扫描
  99. </div>
  100. <div class="item_info_number">
  101. <span class="number_no_scan">{{scanDataInfo.unScanned}}人</span>
  102. <!-- <span class="number_no_icon"><img src="../../assets/icon/no_scan_icon.png"></span> -->
  103. </div>
  104. </div>
  105. </div>
  106. <div class="list_item no_exam" >
  107. <div class="list_item_info " @click="GotoDetail(2)">
  108. <div class="item_info_title">
  109. 缺考
  110. </div>
  111. <div class="item_info_number">
  112. <span class="number_no_exam">{{scanDataInfo.examMissNum}}人</span>
  113. <!-- <span class="number_no_icon"><img src="../../assets/icon/miss_exam.png"></span> -->
  114. </div>
  115. </div>
  116. </div>
  117. <div class="list_item annormal_icon" >
  118. <div class="list_item_info " @click="GotoDetail(3)">
  119. <div class="item_info_title">
  120. 异常
  121. </div>
  122. <div class="item_info_number">
  123. <span class="number_abnormal">{{scanDataInfo.abnormalNum}}份</span>
  124. <!-- <span class="number_no_icon"><img src="../../assets/icon/abnormal_icon.png"></span> -->
  125. </div>
  126. </div>
  127. </div>
  128. <div class="list_item sucess_upload" >
  129. <div class="list_item_info " @click="GotoDetail(1)">
  130. <div class="item_info_title">
  131. 已上传
  132. </div>
  133. <div class="item_info_number">
  134. <span class="number_uploaded">{{scanDataInfo.scannedNum}}人</span>
  135. <!-- <span class="number_no_icon"><img src="../../assets/icon/sucess_upload.png"></span> -->
  136. </div>
  137. </div>
  138. </div>
  139. </div>
  140. </div>
  141. <div class="right_button">
  142. <el-button type="primary" @click="GoSearch()" style="width:calc(100% - 40px);">扫描完成</el-button>
  143. </div>
  144. </div>
  145. </div>
  146. <SelectStudent v-model="showSelectStudent" @success="StudentSuccess"></SelectStudent>
  147. <AbnormalDetail v-model="showAbnormalDialog" :scanState="scanState"></AbnormalDetail>
  148. </div>
  149. </template>
  150. <script lang="ts" setup>
  151. import { useExamStore } from '@/store/exam'
  152. import { useUserStore } from '@/store/user'
  153. import { useRouter } from 'vue-router'
  154. import { onMounted ,ref,computed,onUnmounted,nextTick } from 'vue';
  155. import ScanButton from './components/scanButton.vue'
  156. import SelectStudent from './components/selectStudent.vue'
  157. import { hasImportStudent,getBatchList,getCurrentBatchNo,deleteBatch,updateScanCount } from '@/api/exam'
  158. import scanCommon from '@/utils/scanCommon';
  159. import { ElMessageBox, ElMessage } from 'element-plus'
  160. import { formatTimestamp } from '@/utils/common';
  161. import AbnormalDetail from './abnormalDetail.vue';
  162. // 实例化 Store
  163. const examStore = useExamStore()
  164. const userStore = useUserStore()
  165. const router = useRouter()
  166. // 考试科目 ID
  167. const examSubjectId = computed(() => {
  168. return examStore.currentExam?.id
  169. })//计算属性
  170. //考试科目code
  171. const examSubjectCode=computed(() => {
  172. return examStore.currentExam?.examSubjectCode
  173. })
  174. //当前登录人姓名
  175. const currentUserName=computed(() => {
  176. return userStore.userName;
  177. })
  178. const selectSchoolId=ref(0);//学校ID
  179. const showAbnormalDialog=ref(false);//异常弹窗
  180. const scanState = ref(0);//扫描状态//默认未扫描
  181. const params=ref({
  182. batchNo:'',
  183. keyWord:''
  184. })
  185. const scanClientStates=ref(false);//客户端状态
  186. const scanClientVersion=ref('');//客户端版本
  187. const isScanning=ref(false);//是否正在扫描 扫描状态
  188. const hasMakeTemplate=ref(false);//模版是否制作完成
  189. const currentBatchNo=ref('');//当前批次号
  190. const currentBatchNoId=ref('');//当前批次号id
  191. const loadingText=ref('');//加载文本
  192. const baseUrl=import.meta.env.VITE_API_BASE_URL;
  193. const uploadUrl=`https://dev3.k12100.net/teaching/api/v1/ai_exam_scan/upload_multi_img`;//图片上传地址
  194. // 定义数据类型接口
  195. interface BatchItem {
  196. id?: string | number;
  197. batchNo: string;
  198. batchTypeName?: string;
  199. scanUserName?: string;
  200. scannedPaperNum?: number;
  201. scannedTime?: number | string;
  202. uploadNum?: number;
  203. uploadStatus?: number; // 确保包含此属性
  204. failedNumber?: number;
  205. [key: string]: any; // 允许其他动态属性
  206. }
  207. const tableData=ref<BatchItem[]>([]); //批次列表
  208. const tableHeight=ref(500);
  209. const scanIdentifyList=[
  210. {
  211. label:'考号',
  212. value:'2',
  213. },
  214. {
  215. label:'学号',
  216. value:'1',
  217. }
  218. ];//识别号列表
  219. const isImportStudent=ref(false);//是否导入了学生名单
  220. const showSelectStudent=ref(false);//是否显示选择学生名单弹窗
  221. const scanDataInfo=ref({
  222. abnormalNum:0,//异常数量
  223. examMissNum:0,//缺考数量
  224. unScanned:0,//未扫描数量
  225. scannedNum:0,//已上传数量
  226. examTotal:0,//考试总人数
  227. });//扫描返回的数据信息
  228. //扫描进度
  229. const scanProcess=computed(() => {
  230. return Math.floor((scanDataInfo.value.scannedNum+scanDataInfo.value.examMissNum)/scanDataInfo.value.examTotal)*100;
  231. });
  232. //缺考进度
  233. const scanQuekao=computed(() => {
  234. return Math.floor((scanDataInfo.value.examMissNum)/scanDataInfo.value.examTotal)*100;
  235. });
  236. //异常进度
  237. const scanYichang=computed(() => {
  238. return Math.floor((scanDataInfo.value.abnormalNum)/scanDataInfo.value.examTotal)*100;
  239. });
  240. //刷新
  241. const Refresh = () => {
  242. tableData.value=[];
  243. GetScanBatchList();
  244. }
  245. //重新识别弹窗
  246. const OpenReIdentify=() => {
  247. }
  248. //开始扫描
  249. const OpenScan = () => {
  250. if(isScanning.value)
  251. {
  252. //正在扫描
  253. ElMessage.warning('正在扫描中,请勿重复点击哦!');
  254. return;
  255. }
  256. //判断客户端是否已经连接
  257. if(scanClientStates.value)
  258. {
  259. //一打开客户端
  260. console.log('客户端已打开,开始扫描');
  261. // isScanning.value=true;//扫描状态
  262. const params={
  263. examSubjectId:examSubjectId.value,
  264. schoolId:selectSchoolId.value,
  265. };
  266. getCurrentBatchNo(params).then((res:any)=>{
  267. console.log("当前获取批次结果",res);
  268. if(res.code==200)
  269. {
  270. currentBatchNo.value=res.data.batchNo;
  271. currentBatchNoId.value=res.data.id;
  272. let newData={
  273. batchNo:currentBatchNo.value,
  274. id:currentBatchNoId.value,//批次号
  275. batchTypeName:'扫描',
  276. scanUserName:currentUserName.value,
  277. scannedPaperNum:0,//扫描张数
  278. scannedTime:Date.now(),//扫描时间
  279. uploadNum:0,//上传张数
  280. uploadStatus:0,//扫描状态
  281. };
  282. tableData.value.push(newData);
  283. //开始扫描
  284. let jsonParam={
  285. examSubjectId:examSubjectId.value,
  286. batchNumber:currentBatchNo.value,
  287. // sensitive: 35,//灵敏度参数 固定35
  288. filter:0,//是否过滤参数 1-是 0-否
  289. // heightRatio:this.heightRatio,//占打分框高度比例
  290. schoolId:selectSchoolId.value//学校id 联校单校都需要学校id
  291. };
  292. let json = {
  293. "action":"startScan",//交互指令参数
  294. "batchNumber":GetBatchStr(res.data.id,currentBatchNo.value),//批次号 这里传给客户端的批次号需要进行处理 截取批次id后5位拼接batchNumber
  295. "subjectCode":examSubjectCode.value,//科目编号
  296. "paperSchema":1,//this.paperSchema,// 单双面//页面类型 1 单面 2 双面
  297. "token":localStorage.getItem('token'),//token信息
  298. "uploadUrl":uploadUrl, // 图片上传地址
  299. "dpi":150,//dpi参数 设置图片清晰度质量的
  300. "isColor":1,//是否彩色图片 手阅卡扫描彩色图片 0 黑白 1 彩色
  301. "useDriveUI":1,//startScan和scanTemplate增加useDriveUI参数,=1时会弹框,其他值或不写则不弹
  302. "jsonParam":JSON.stringify(jsonParam),// 后端使用的参数 json字符串 后端接口固定三个参数 1:jsonParam 2:seqNumber(这个批次的图片序号:从1开始) 3:file 图片文件
  303. };//正式版参数
  304. console.log("打印发送的数据",JSON.stringify(json));
  305. setTimeout(() => {
  306. scanCommon.send(JSON.stringify(json))
  307. },100)
  308. }
  309. else
  310. {
  311. ElMessage.error(res.msg);
  312. }
  313. });
  314. }
  315. else
  316. {
  317. //提示客户端未打开
  318. ElMessage.warning('请先打开客户端');
  319. }
  320. }
  321. //获取批次字符串
  322. const GetBatchStr=(batchId:any,batChNo:any)=>{
  323. const batchNoValue = batchId.substring(batchId.length - 5);
  324. const batchNo=String(batChNo).padStart(3, '0');
  325. const batchNumber=batchNoValue+batchNo;//传给客户端的图片批次号 id后五位数加上00批次号
  326. return batchNumber;
  327. }
  328. //根据处理后的批次号转换成处理前的批次号
  329. const GetBatchNumber=(batchNumber:any)=>{
  330. if (!batchNumber) {
  331. console.warn('批次号为空或未定义');
  332. return currentBatchNo.value;//返回当前的批次号
  333. }
  334. const batchStr = batchNumber.toString();
  335. if (batchStr.length < 3) {
  336. console.warn('批次号长度小于3位:', batchStr);
  337. return currentBatchNo.value;//返回当前的批次号
  338. }
  339. const lastThree = batchStr.slice(-3);
  340. const parsedNumber = parseInt(lastThree, 10);
  341. if (isNaN(parsedNumber)) {
  342. console.error('无法解析批次号:', lastThree);
  343. return currentBatchNo.value;//返回当前的批次号
  344. }
  345. console.log("打印最后的批次号",parsedNumber);
  346. return parsedNumber.toString();
  347. }
  348. //删除批次
  349. const OptionDelete=(row: any) => {
  350. console.log('删除批次', row);
  351. if(row.scanUserName==currentUserName.value)
  352. {
  353. ElMessageBox.confirm('确定要删除该批次吗?', '提示', {
  354. confirmButtonText: '确定',
  355. cancelButtonText: '取消',
  356. type: 'warning'
  357. }).then(() => {
  358. // 删除操作
  359. console.log('删除', row);
  360. const params={
  361. examSubjectId:examSubjectId.value,
  362. batchNo:row.batchNo,
  363. schoolId:selectSchoolId.value,
  364. };
  365. deleteBatch(params).then((res:any)=>{
  366. if(res.code==200)
  367. {
  368. ElMessage.success("删除成功!")
  369. GetScanBatchList();
  370. }
  371. else
  372. {
  373. ElMessage.error(res.msg);
  374. }
  375. })
  376. }).catch(() => {
  377. // 取消操作
  378. console.log('取消删除');
  379. });
  380. }
  381. else
  382. {
  383. ElMessage.warning('只能删除自己上传的记录');
  384. }
  385. }
  386. //跳转到异常详情页
  387. const GotoDetail = (type: number) => {
  388. console.log('跳转到异常详情页', type);
  389. scanState.value=type;
  390. // 根据类型跳转到不同的详情页
  391. showAbnormalDialog.value=true;
  392. }
  393. //跳转到批次详情页
  394. const GotoBatchDetail=(batchNo:any)=>{
  395. router.push({
  396. path: '/exam/scanDetail',
  397. query: {
  398. examSubjectId: examSubjectId.value,
  399. batchNo: batchNo,
  400. },
  401. })
  402. }
  403. // 打开导入学生名单
  404. const OpenImportStudent = () => {
  405. showSelectStudent.value=true;
  406. }
  407. // 打开编辑考试名单
  408. const OpenEditExamList = () => {
  409. router.push({
  410. path: '/exam/examList',
  411. query: {
  412. examSubjectId: examSubjectId.value,
  413. },
  414. });
  415. }
  416. //学生名单导入成功
  417. const StudentSuccess = () => {
  418. HasImportStudent();
  419. }
  420. //查询是否导入了学生名单
  421. const HasImportStudent = async () => {
  422. const params = {
  423. examSubjectId: examSubjectId.value,
  424. schoolId: 0,//单校 0
  425. };
  426. const res = await hasImportStudent(params);
  427. console.log("打印是否导入了学生名单",res);
  428. if(res.code==200)
  429. {
  430. isImportStudent.value=res.data;
  431. }
  432. else{
  433. isImportStudent.value=false;
  434. }
  435. }
  436. //获取扫描批次列表
  437. const GetScanBatchList=async()=>{
  438. const params = {
  439. examSubjectId: examSubjectId.value,
  440. schoolId: 0,//单校 0
  441. };
  442. const res = await getBatchList(params);
  443. if(res.code==200)
  444. {
  445. selectSchoolId.value=res.data.schoolId;//获取学校id
  446. if(res.data.scannedWebSocketVO)
  447. {
  448. tableData.value=res.data.scannedWebSocketVO?.data || [];
  449. scanDataInfo.value.abnormalNum=res.data.scannedWebSocketVO.abnormalNum;//异常数量
  450. scanDataInfo.value.examMissNum=res.data.scannedWebSocketVO.examMissNum;//缺考数量
  451. scanDataInfo.value.scannedNum=res.data.scannedWebSocketVO.scannedNum;//已上传数量
  452. scanDataInfo.value.unScanned=res.data.scannedWebSocketVO.unScanned;//未扫描数量
  453. scanDataInfo.value.examTotal=res.data.scannedWebSocketVO.examTotal;//总人数
  454. let examInfoCount={
  455. unScanned:res.data.scannedWebSocketVO.unScanned,
  456. examMissNum:res.data.scannedWebSocketVO.examMissNum,
  457. scannedNum:res.data.scannedWebSocketVO.scannedNum,
  458. abnormalNum:res.data.scannedWebSocketVO.abnormalNum,
  459. examTotal:res.data.scannedWebSocketVO.examTotal,
  460. };
  461. examStore.setExamInfoCount(examInfoCount);//设置扫描数据信息
  462. }
  463. }
  464. }
  465. //计算高度的函数
  466. const CalculateTableHeight = () => {
  467. // nextTick 确保 DOM 更新后再获取尺寸
  468. nextTick(() => {
  469. // window.innerHeight 是浏览器可视区域高度
  470. // 简单算法:视窗高度 - 固定占用高度
  471. let computedHeight = window.innerHeight - 136;
  472. // 限制最小高度,防止太矮
  473. if (computedHeight < 200) {
  474. computedHeight = 200;
  475. }
  476. tableHeight.value = computedHeight;
  477. });
  478. };
  479. //更新扫描张数
  480. const UpdateBatchScanNumber = (batchId: any, scanNumber: Number) => {
  481. const params={
  482. id:batchId,
  483. recordNumber: scanNumber,
  484. };
  485. updateScanCount(params).then((res:any)=>{
  486. if(res.code==200)
  487. {
  488. console.log("第"+batchId+"批次更新扫描张数"+scanNumber+"成功", res);
  489. }
  490. })
  491. };
  492. // 处理扫描结果
  493. const HandleScanResult = (res: any) => {
  494. console.log('收到扫描数据', res);
  495. // 业务逻辑...
  496. if (res.action == 'uploading')
  497. {
  498. let batchNumber: any = '';
  499. if (res.batchNumber)
  500. {
  501. batchNumber = GetBatchNumber(res.batchNumber || res.data?.batchNumber);
  502. }
  503. const targetItem = tableData.value.find((item: any) => item.batchNo == batchNumber);
  504. if (typeof loadingText !== 'undefined') loadingText.value = "启动扫描仪中";
  505. ElMessage.success("正在启动扫描仪,请稍后…");
  506. if (targetItem)
  507. {
  508. targetItem.uploadStatus = 0; //更新上传状态 0 开始上传
  509. }
  510. }
  511. // 开始扫描指令
  512. if (res.action == 'startScan')
  513. {
  514. let batchNumber = GetBatchNumber(res?.batchNumber || res.data?.batchNumber);
  515. let currentItem = tableData.value.find((item: any) => item.batchNo == batchNumber);
  516. console.log("打印currentItem", currentItem);
  517. //开始扫描指令
  518. if (res.code == 200)
  519. {
  520. ElMessage.success("扫描仪启动成功,开始扫描…");
  521. if(typeof loadingText !== 'undefined') loadingText.value = "正在扫描中";
  522. }
  523. if (res.code == 502)
  524. {
  525. console.log("开始扫描指令502错误 未检测到纸张或者卡纸", res);
  526. console.log("打印currentItem", currentItem);
  527. if (currentItem) {
  528. currentItem.uploadStatus = 1; //更新上传状态
  529. }
  530. if (typeof loadingText !== 'undefined') loadingText.value = ""; //清空上传提示
  531. isScanning.value = false; //重置扫描状态
  532. ElMessage.error(res.msg);
  533. }
  534. if (res.code == 510)
  535. {
  536. console.log("启动扫描失败,上传正在进行中", res);
  537. isScanning.value = true; //重置扫描状态
  538. ElMessage.warning(res.msg + '请勿重复点击');
  539. }
  540. if (res.code == 509) {
  541. console.log("扫描仪正在使用中", res);
  542. if (res.msg == '未找到指定的扫描仪') {
  543. isScanning.value = false; //重置扫描状态
  544. }
  545. ElMessage.warning(res.msg + ',请稍后再试!');
  546. if (currentItem) {
  547. currentItem.uploadStatus = 1; //更新上传状态
  548. }
  549. }
  550. }
  551. // 扫描完成指令
  552. if (res.action == 'uploadFinish') {
  553. console.log("上传完成uploadFinish 更新上传动画状态", res);
  554. // 修复:直接调用 GetBatchNumber
  555. let batchNumber = GetBatchNumber(res?.batchNumber || res.data?.batchNumber);
  556. let targetItem = tableData.value.find((item: any) => item.batchNo == batchNumber);
  557. if (typeof loadingText !== 'undefined') loadingText.value = '正在上传中';
  558. if (targetItem) {
  559. targetItem.scannedPaperNum = res.scanNumber; //更新扫描张数
  560. targetItem.failedNumber = res.failedNumber; //更新失败张数
  561. if (typeof UpdateBatchScanNumber === 'function') {
  562. UpdateBatchScanNumber(targetItem.id, Number(targetItem.scannedPaperNum));
  563. }
  564. }
  565. isScanning.value = false; //重置扫描状态
  566. }
  567. if (res.action == 'uploadNumber') {
  568. console.log("上传完成uploadNumber 更新上传张数", res);
  569. // 逻辑已注释,保持原样
  570. }
  571. if (res.action == 'scanNumber') {
  572. console.log("扫描张数scanNumber 更新扫描张数");
  573. // 修复:直接调用 GetBatchNumber
  574. let batchNumber = GetBatchNumber(res.batchNumber || res.data?.batchNumber);
  575. let targetItem = tableData.value.find((item: any) => item.batchNo == batchNumber);
  576. console.log("打印targetItem", targetItem);
  577. if (targetItem) {
  578. targetItem.scannedPaperNum = res.number; //更新上传张数
  579. targetItem.uploadStatus = 0; //更新上传状态 0 开始上传
  580. }
  581. // 修复:确保 UpdateBatchScanNumber 已定义
  582. if (targetItem && typeof UpdateBatchScanNumber === 'function') {
  583. UpdateBatchScanNumber(targetItem.id, Number(targetItem.scannedPaperNum));
  584. }
  585. }
  586. if (res.action == 'loadImage') {
  587. console.log("加载图片loadImage 获取图片数据", res);
  588. // 修复:reImageList 必须是 ref
  589. if (typeof reImageList !== 'undefined') {
  590. reImageList.value = res.data || [];
  591. reImageList.value.forEach((item: any) => {
  592. item.uploadStatus = -2;
  593. item.uploadProgress = 0;
  594. });
  595. }
  596. let batchNumber = GetBatchNumber(res.batchNumber);
  597. let targetItem = tableData.value.find((item: any) => item.batchNo == batchNumber);
  598. console.log("打印重新上传图片列表", reImageList.value);
  599. console.log("打印重新上传的批次Item", targetItem);
  600. if (targetItem) {
  601. targetItem.uploadStatus = 0;
  602. }
  603. if (typeof loadingText !== 'undefined') loadingText.value = '正在上传中';
  604. if (typeof uploadDialogTitle !== 'undefined') uploadDialogTitle.value = '未上传的图片';
  605. if (typeof showUploadDialog !== 'undefined') showUploadDialog.value = true;
  606. }
  607. if (res.action == 'finish') {
  608. console.log("图片重传finish指令", res);
  609. let batchNumber = GetBatchNumber(res.batchNumber);
  610. let targetItem = tableData.value.find((item: any) => item.batchNo == batchNumber);
  611. let fileIndex = res.fileIndex.split(",");
  612. console.log("打印fileIndex", fileIndex);
  613. fileIndex.forEach((index: string) => {
  614. // 修复:reImageList.value
  615. let reImageItem = reImageList.value.find((item: any) => item.fileIndex == index);
  616. if (reImageItem) {
  617. let progress = reImageItem.uploadProgress;
  618. const interval = setInterval(() => {
  619. progress += 5;
  620. if (progress >= 100) {
  621. progress = 100;
  622. clearInterval(interval);
  623. // 修复:Vue3 不需要 $set,直接赋值即可触发响应式
  624. reImageItem.uploadStatus = res.uploadState;
  625. }
  626. reImageItem.uploadProgress = progress;
  627. }, 100);
  628. }
  629. });
  630. // 修复:reImageCount 可能是 ref 或计算属性
  631. let currentReImageCount = typeof reImageCount !== 'undefined' ? reImageCount.value : reImageList.value.length;
  632. if (currentReImageCount === 0) {
  633. if (typeof showUploadDialog !== 'undefined') showUploadDialog.value = false;
  634. if (targetItem) {
  635. targetItem.uploadStatus = 1;
  636. isScanning.value = false;
  637. // 修复:确保 GetBatchList 已定义
  638. if (typeof GetBatchList === 'function') GetBatchList();
  639. }
  640. }
  641. }
  642. if (res.action == 'getFailedImage') {
  643. console.log("获取失败图片getFailedImage", res);
  644. let failedList = res.data || [];
  645. failedList.forEach((item: any) => {
  646. let batchNumber = GetBatchNumber(item.batchNumber);
  647. let currentItem = tableData.value.find((item1: any) => item1.batchNo == batchNumber);
  648. if (currentItem) {
  649. if (item.failedNumber == 0) {
  650. if (currentItem.scannedPaperNum != currentItem.uploadNum) {
  651. currentItem.scannedPaperNum = currentItem.uploadNum;
  652. if (typeof UpdateBatchScanNumber === 'function') {
  653. UpdateBatchScanNumber(currentItem.id, currentItem.uploadNum);
  654. }
  655. }
  656. } else {
  657. let number = currentItem.uploadNum + item.failedNumber;
  658. currentItem.scannedPaperNum = number;
  659. if (typeof UpdateBatchScanNumber === 'function') {
  660. UpdateBatchScanNumber(currentItem.id, number);
  661. }
  662. }
  663. }
  664. });
  665. }
  666. if (res.action == 'scanFinishBatch') {
  667. if (typeof loadingText !== 'undefined') loadingText.value = '本次扫描结束';
  668. console.log("本批次扫描完成scanFinishBatch 更新扫描张数", res);
  669. }
  670. };
  671. onMounted(() => {
  672. // 初始化连接
  673. scanCommon.init(HandleScanResult);
  674. // 监听连接状态
  675. scanCommon.watchConnection((isOnline,clientVersion) => {
  676. scanClientStates.value=isOnline;
  677. scanClientVersion.value=clientVersion; //客户端版本号
  678. // console.log("客户端版本",clientVersion);
  679. });
  680. if (!examStore.currentExam) {
  681. console.warn('当前没有选中的考试信息')
  682. // 可选:如果没有数据,可以重定向回列表页或提示用户
  683. }
  684. HasImportStudent();//查询是否导入了学生名单
  685. GetScanBatchList();//获取扫描批次列表
  686. CalculateTableHeight();//初始化计算表格高度
  687. // 监听窗口大小变化
  688. window.addEventListener('resize', CalculateTableHeight);
  689. });
  690. // 卸载时移除监听,防止内存泄漏
  691. onUnmounted(() => {
  692. window.removeEventListener('resize', CalculateTableHeight);
  693. // 组件销毁时务必停止,防止内存泄漏和后台重连
  694. scanCommon.stop();
  695. });
  696. </script>
  697. <style lang="scss" scoped>
  698. .page_list
  699. {
  700. width: 100%;
  701. height: 100%;
  702. padding: 20px;
  703. background-color: #fff;
  704. border-radius: 4px;
  705. box-sizing: border-box;
  706. }
  707. .scan_state_title
  708. {
  709. font-size: 14px;
  710. color:#333;
  711. font-weight: 400;
  712. }
  713. .scan_state_open
  714. { margin-left: 5px;
  715. font-size: 14px;
  716. font-weight: 400;
  717. color: #2BC644;
  718. i
  719. {
  720. margin-right: 5px;
  721. }
  722. }
  723. .scan_state_close
  724. {
  725. font-size: 14px;
  726. font-weight: 400;
  727. margin-left: 5px;
  728. color:#F56C6C;
  729. i
  730. {
  731. margin-right: 5px;
  732. }
  733. }
  734. </style>