scanList.vue 30 KB

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