scanList.vue 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355
  1. <template>
  2. <div class="page_list">
  3. <div class="search_content">
  4. <div class="content_left">
  5. <span class="scan_state_title"><span>客户端状态</span>:</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 el_button">
  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. <div @click="OpenImportDialog" class="editor_item" >导入扫描图片</div>
  34. <el-button @click="OpenEditExamList()" type="primary">编辑考场名单</el-button>
  35. <!-- <el-button @click="OpenImportStudent()" type="primary" v-else>导入考场名单</el-button> -->
  36. </div>
  37. </div>
  38. <div class="page_jg_20"></div>
  39. <div class="page_content" >
  40. <div class="content_table">
  41. <div class="page_table">
  42. <el-table :data="tableData" style="width: 100%" :height="tableHeight">
  43. <!-- <el-table-column prop="questionName" label="序号" width="100" align="center" >
  44. </el-table-column> -->
  45. <el-table-column prop="batchNo" label="批次" width="120" align="center">
  46. </el-table-column>
  47. <el-table-column prop="scannedPaperNum" label="扫描张数" align="center" min-width="90">
  48. </el-table-column>
  49. <el-table-column prop="uploadNum" label="待上传张数" align="center" min-width="100">
  50. <template v-slot="scope">
  51. <div class="table_row_answer" v-if="GetNoUploadNumber(scope.row)>0" @click="OpenUpload(scope.row)">
  52. <span style="color:red;text-decoration:underline;"> {{GetNoUploadNumber(scope.row)}}</span>
  53. <!-- {{scope.row.failedNumber || 0}} -->
  54. </div>
  55. <div v-else>0</div>
  56. </template>
  57. </el-table-column>
  58. <el-table-column prop="uploadNum" label="已上传张数" align="center" min-width="100">
  59. <template v-slot="scope">
  60. </template>
  61. </el-table-column>
  62. <el-table-column prop="scanUserName" label="扫描人" align="center" min-width="120" show-overflow-tooltip>
  63. <template v-slot="scope">
  64. </template>
  65. </el-table-column>
  66. <el-table-column prop="scanUserName" label="扫描时间" align="center" min-width="160" show-overflow-tooltip>
  67. <template v-slot="scope">
  68. {{formatTimestamp(scope.row.scannedTime) }}
  69. </template>
  70. </el-table-column>
  71. <el-table-column prop="name" label="操作" min-width="190" align="center" fixed="right">
  72. <template v-slot="scope">
  73. <div class="ele_button table_row_button" v-if="scope.row.uploadStatus==0" style="color:#2E64FA;">
  74. <i class="el-icon-loading"></i>{{loadingText}}……
  75. </div>
  76. <div class="ele_button table_row_button" v-else>
  77. <span class="btn_editor" @click="GotoBatchDetail(scope.row.batchNo)">详情</span>
  78. <span class="btn_delete" v-if="scope.row.scanUserName==currentUserName " @click="OptionDelete(scope.row)">删除</span>
  79. <span class="editor_disable" v-else>删除</span>
  80. </div>
  81. </template>
  82. </el-table-column>
  83. </el-table>
  84. </div>
  85. </div>
  86. <div class="content_right">
  87. <div class="right_header">
  88. <div class="scan_set scan_state_title" @click="OpenNumberSet()"> 考号设置</div>
  89. <div class="scan_set_scan_code">
  90. <!-- <el-select style="width: 100px;margin-right: 6px;" v-model="useDriveUI" placeholder="是否显示驱动弹窗" @change="ChangeDriveUI">
  91. <el-option v-for="item in scanTypeList" :key="item.value" :label="item.label" :value="item.value"></el-option>
  92. </el-select> -->
  93. 识别号:
  94. <el-select style="width: 100px;margin-right: 6px;" v-model="scanIdentifyCode" placeholder="选择识别号" @change="ChangeCode" >
  95. <el-option v-for="item in scanIdentifyList" :key="item.value" :label="item.label" :value="item.value"></el-option>
  96. </el-select>
  97. </div>
  98. </div>
  99. <div class="right_center">
  100. <div class="page_jg_20">
  101. </div>
  102. <div class="scan_button_common scan_button">
  103. <ScanButton :process="scanProcess" :quekao="scanQuekao" :yichang="scanYichang" @click="OpenScan()"></ScanButton>
  104. </div>
  105. <div class="scan_list">
  106. <div class="list_item no_scan" >
  107. <div class="list_item_info" @click="GotoDetail(0)">
  108. <div class="item_info_title">
  109. 未扫描
  110. </div>
  111. <div class="item_info_number">
  112. <span class="number_no_scan">{{scanDataInfo.unScanned}}人</span>
  113. </div>
  114. </div>
  115. </div>
  116. <div class="list_item no_exam" >
  117. <div class="list_item_info " @click="GotoDetail(2)">
  118. <div class="item_info_title">
  119. 缺考
  120. </div>
  121. <div class="item_info_number">
  122. <span class="number_no_exam">{{scanDataInfo.examMissNum}}人</span>
  123. <!-- <span class="number_no_icon"><img src="../../assets/icon/miss_exam.png"></span> -->
  124. </div>
  125. </div>
  126. </div>
  127. <div class="list_item annormal_icon" >
  128. <div class="list_item_info " @click="GotoDetail(3)">
  129. <div class="item_info_title">
  130. 异常
  131. </div>
  132. <div class="item_info_number">
  133. <span class="number_abnormal">{{scanDataInfo.abnormalNum}}份</span>
  134. <!-- <span class="number_no_icon"><img src="../../assets/icon/abnormal_icon.png"></span> -->
  135. </div>
  136. </div>
  137. </div>
  138. <div class="list_item sucess_upload" >
  139. <div class="list_item_info " @click="GotoDetail(1)">
  140. <div class="item_info_title">
  141. 已上传
  142. </div>
  143. <div class="item_info_number">
  144. <span class="number_uploaded">{{scanDataInfo.scannedNum}}人</span>
  145. <!-- <span class="number_no_icon"><img src="../../assets/icon/sucess_upload.png"></span> -->
  146. </div>
  147. </div>
  148. </div>
  149. </div>
  150. </div>
  151. <div class="right_button">
  152. <el-button type="primary" @click="FinishScanned()" :loading="finishLoading" style="width:calc(100% - 40px);">扫描完成</el-button>
  153. </div>
  154. </div>
  155. </div>
  156. <div class="page_dialog">
  157. <el-dialog
  158. v-model="showNumberSetDialog"
  159. title="考号设置"
  160. width="480px"
  161. class="page_dialog"
  162. >
  163. <el-form ref="formRef" label-width="130px">
  164. <el-form-item label="考号识别顺序:" prop="examSubjectName">
  165. <el-select style="width: 60px;margin-right: 6px;" v-model="examCodeStart" placeholder="开始位置" >
  166. <el-option v-for="item in 10" :key="item" :label="item" :value="item"></el-option>
  167. </el-select>
  168. -
  169. <el-select style="width: 60px;margin-right: 6px;margin-left: 6px;" v-model="examCodeEnd" placeholder="结束位置">
  170. <el-option v-for="item in 10" :key="item" :label="item" :value="item"></el-option>
  171. </el-select>
  172. </el-form-item>
  173. </el-form>
  174. <template #footer>
  175. <span class="dialog-footer">
  176. <el-button @click="CloseNumberSet()">取消</el-button>
  177. <el-button type="primary" :loading="setLoading" @click="HandleSubmit">确定</el-button>
  178. </span>
  179. </template>
  180. </el-dialog>
  181. <ReIdentify v-model="isReIdentify" :reIdentifyTotal="reIdentifyTotal" justObjective="0" :schoolInfo="selectSchoolInfo" @ReIdentifyComplete="Refresh"></ReIdentify>
  182. <UploadImage v-if="showUploadImage" v-model="showUploadImage" :batchNumber="currentBatchNo" :isfilter="isfilter" :schoolInfo="selectSchoolInfo" @UploadProgress="UploadProgress" @UploadComplete="UploadComplete" @CancelUpload="CancelUpload"></UploadImage>
  183. <AbnormalDetail v-model="showAbnormalDialog" :scanState="scanState" @success="BackScan"></AbnormalDetail>
  184. </div>
  185. </div>
  186. </template>
  187. <script lang="ts" setup>
  188. import { useExamStore } from '@/store/exam'
  189. import { useUserStore } from '@/store/user'
  190. import { useAnalysisStore } from '@/store/analysis'
  191. import { useRouter } from 'vue-router'
  192. import { onMounted ,ref,computed,onUnmounted,nextTick } from 'vue';
  193. import ScanButton from './components/scanButton.vue'//扫描按钮
  194. import UploadImage from './components/UploadImage.vue'//导入扫描图片
  195. import {getExamProcess,setExamNumberPosition,finishScanned,getExamScoreSummary,hasImportStudent,getBatchList,getCurrentBatchNo,deleteBatch,updateScanCount,setExamScanSetting,saveChooseCode} from '@/api/exam'
  196. import { queryQuestionGroupByAiId } from '@/api/analysis'
  197. import scanCommon from '@/utils/scanCommon';
  198. import { ElMessageBox, ElMessage } from 'element-plus'
  199. import { formatTimestamp } from '@/utils/common';
  200. import AbnormalDetail from './abnormalDetail.vue';
  201. import ReIdentify from './components/ReIdentify.vue';//重新识别弹窗
  202. import WebSocketManager from '@/utils/webSocket'//后端websocket链接js
  203. // 实例化 Store
  204. const examStore = useExamStore()
  205. const userStore = useUserStore()
  206. const analysisStore = useAnalysisStore()
  207. const router = useRouter()
  208. // 考试科目 ID
  209. const examSubjectId = computed(() => {
  210. return examStore.currentExam?.id
  211. })//计算属性
  212. const userInfo=computed(() => {
  213. return userStore.userInfo;
  214. })//用户信息
  215. const schoolType=computed(() => {
  216. return ''
  217. })
  218. //考试科目code
  219. const examSubjectCode=computed(() => {
  220. return examStore.currentExam?.examSubjectCode
  221. })
  222. //当前登录人姓名
  223. const currentUserName=computed(() => {
  224. return userStore.userName;
  225. })
  226. const isShowGroupAnalysis = computed(() => analysisStore?.isShowGroupAnalysis || false);
  227. //查询是否导入了学生名单 此方法以注释
  228. const HasImportStudent = async () => {
  229. const params = {
  230. examSubjectId: examSubjectId.value,
  231. schoolId: 0,//单校 0
  232. };
  233. const res = await hasImportStudent(params);
  234. console.log("打印是否导入了学生名单",res);
  235. if(res.code==200)
  236. {
  237. isImportStudent.value=res.data;
  238. }
  239. else{
  240. isImportStudent.value=false;
  241. }
  242. if(!isImportStudent.value)
  243. {
  244. console.log("未导入学生名单,跳转到选择学生名单界面");
  245. //跳转导入学生名单界面
  246. router.push({
  247. name: 'selectExamination',
  248. });
  249. }
  250. }
  251. // 组件创建时立即执行
  252. // HasImportStudent();//查询是否导入了学生名单();
  253. const selectSchoolId=ref(0);//学校ID
  254. const showAbnormalDialog=ref(false);//异常弹窗
  255. const scanState = ref(0);//扫描状态//默认未扫描
  256. const showNumberSetDialog=ref(false);//考号识别顺序设置
  257. const examCodeStart=ref(1);//考号开始位置
  258. const examCodeEnd=ref(10);//考号结束位置
  259. const setLoading=ref(false);//考号设置加载中
  260. const isReIdentify=ref(false);//是否重新识别
  261. const reIdentifyTotal = ref(0);//需要识别的数
  262. const selectSchoolInfo=ref({
  263. schoolId:'',
  264. schoolName:''
  265. });//学校信息
  266. const params=ref({
  267. batchNo:'',
  268. keyWord:''
  269. })
  270. const scanClientStates=ref(false);//客户端状态
  271. const scanClientVersion=ref('');//客户端版本
  272. const isScanning=ref(false);//是否正在扫描 扫描状态
  273. const hasMakeTemplate=ref(false);//模版是否制作完成
  274. const currentBatchNo=ref('');//当前批次号
  275. const isfilter=ref(1);//是否过滤
  276. const currentBatchNoId=ref('');//当前批次号id
  277. const loadingText=ref('');//加载文本
  278. const baseUrl=import.meta.env.VITE_API_BASE_URL;
  279. let uploadUrl=`https://www.k12100.com/teaching/api/v1/ai_exam_scan/upload_multi_img`;//图片上传地址
  280. let updateCountUrl=`https://www.k12100.com/teaching/api/v1/ai_exam_scan/record_batch_scan_number`;
  281. const webSocket=ref<InstanceType<typeof WebSocketManager> | null>(null);//websocket
  282. let websocketUrl=import.meta.env.VITE_WEB_SOCKET_URL;//websocket链接地址
  283. // 定义数据类型接口
  284. interface BatchItem {
  285. id?: string | number;
  286. batchNo: string;
  287. batchTypeName?: string;
  288. scanUserName?: string;
  289. scannedPaperNum?: number;//扫描张数
  290. scannedTime?: number | string;
  291. uploadNum?: number;//上传张数
  292. uploadStatus?: number; // 确保包含此属性
  293. failedNumber?: number;
  294. [key: string]: any; // 允许其他动态属性
  295. }
  296. const tableData=ref<BatchItem[]>([]); //批次列表
  297. const tableHeight=ref(500);
  298. const scanIdentifyList=[
  299. {
  300. label:'考号',
  301. value:'2',
  302. },
  303. {
  304. label:'学号',
  305. value:'1',
  306. }
  307. ];//识别号列表
  308. const scanTypeList=[
  309. {
  310. label:'不弹窗',
  311. value:0,
  312. },
  313. {
  314. label:'弹窗',
  315. value:1,
  316. }
  317. ];
  318. const scanIdentifyCode=ref('1');////识别号 1-学号 2-考号
  319. const useDriveUI=ref(0);//1是会弹框,其他值或不写则不弹
  320. const isImportStudent=ref(false);//是否导入了学生名单
  321. const showSelectStudent=ref(false);//是否显示选择学生名单弹窗
  322. const showUploadImage=ref(false);//导入扫描图片弹窗
  323. const finishLoading=ref(false);//扫描完成加载中
  324. const scanDataInfo=ref({
  325. abnormalNum:0,//异常数量
  326. examMissNum:0,//缺考数量
  327. unScanned:0,//未扫描数量
  328. scannedNum:0,//已上传数量
  329. examTotal:0,//考试总人数
  330. });//扫描返回的数据信息
  331. const scanProcess= computed(() => {
  332. console.log("打印扫描数据",scanDataInfo);
  333. return Math.floor(((scanDataInfo.value.scannedNum+scanDataInfo.value.examMissNum)/scanDataInfo.value.examTotal)*100);
  334. });
  335. const scanQuekao=computed(() => {
  336. return Math.floor((scanDataInfo.value.examMissNum/scanDataInfo.value.examTotal)*100);
  337. });
  338. //异常进度
  339. const scanYichang=computed(() => {
  340. return Math.floor((scanDataInfo.value.abnormalNum/scanDataInfo.value.examTotal)*100);
  341. });
  342. //刷新
  343. const Refresh = () => {
  344. tableData.value=[];
  345. GetScanBatchList();
  346. }
  347. //上传进度设置
  348. const UploadProgress=(batchNumber:number,scanNumber:number)=>{
  349. console.log("图片上传扫描更新进度",batchNumber,scanNumber);
  350. const targetItem = tableData.value.find((item:any) => item.batchNo == batchNumber);
  351. if (targetItem) {
  352. }
  353. }
  354. //导入图片完成
  355. const UploadComplete=(batchNumber:number,scanNumber:number,currentBatchNOID:string) => {
  356. let count = scanNumber;
  357. // // 1:单面 2:双面
  358. // if (paperSchema.value == 2) {
  359. // // 双面扫描数量除以2(向下取整,防止奇数报错)
  360. // count = Math.floor(scanNumber / 2);
  361. // } else {
  362. // 单面不除以2
  363. count = scanNumber;
  364. // }
  365. currentBatchNo.value = batchNumber.toString();
  366. const newData = {
  367. batchNo: currentBatchNo.value,
  368. scanUserName: userInfo.value?.userName || '', // 增加空值保护
  369. scannedPaperNum: count, // 扫描张数
  370. scannedTime: Date.now(), // 扫描时间
  371. uploadNum: 0, // 上传张数
  372. uploadStatus: 0,
  373. };
  374. tableData.value.push(newData);
  375. loadingText.value = "正在上传中";
  376. // 建立与后端的websocket连接
  377. WebSocketLink();
  378. // 扫描完成更新扫描张数
  379. UpdateBatchScanNumber(currentBatchNOID, count);
  380. }
  381. //取消上传
  382. const CancelUpload=(batchNumber:number)=>{
  383. const targetItem = tableData.value.find((item:any)=> item.batchNo == batchNumber);
  384. setTimeout(() => {
  385. if (targetItem)
  386. {
  387. targetItem.uploadStatus = 1;
  388. }
  389. }, 5000);
  390. }
  391. //扫描异常返回
  392. const BackScan= () => {
  393. //更新列表
  394. GetScanBatchList();
  395. }
  396. //扫描完成
  397. const FinishScanned= async () =>{
  398. console.log("打印完成")
  399. ElMessageBox.confirm('确认完成吗?', '提示', {
  400. confirmButtonText: '确定',
  401. cancelButtonText: '取消',
  402. type: 'warning',
  403. customClass:'page_dialog'
  404. }).then(async () => {
  405. let param={
  406. id:examSubjectId.value
  407. };
  408. finishLoading.value=true;
  409. const res = await finishScanned(param)
  410. if(res.code == 200)
  411. {
  412. // ElMessage.success("完成成功!")
  413. let idList=res.data.split(",");
  414. let analysisParam = {
  415. examId:idList[0],
  416. examPaperIds:[idList[1]],//多科传0 单科传科目ID
  417. }
  418. //扫描完成 调用汇总接口
  419. const analysisRes = await getExamScoreSummary(analysisParam);
  420. if(analysisRes.code == 200)
  421. {
  422. ElMessage.success("完成成功!")
  423. finishLoading.value=false;
  424. //显示成绩查询
  425. const stepFlow=examStore.currentExam?.stepFlow < 2;
  426. if(stepFlow){
  427. examStore.setExamInfo({
  428. ...examStore.currentExam,
  429. stepFlow:2,
  430. })
  431. }
  432. //是否显示分组分析
  433. const res = await queryQuestionGroupByAiId(examSubjectId.value);
  434. const isShow = res.code === 200;
  435. analysisStore.setIsShowGroupAnalysis(isShow);
  436. GetExamStatus();//更新状态
  437. }
  438. else
  439. {
  440. ElMessage.error(analysisRes.msg);
  441. finishLoading.value=false;
  442. }
  443. }
  444. else
  445. {
  446. ElMessage.error(res.msg);
  447. finishLoading.value=false;
  448. }
  449. })
  450. }
  451. //获取考试状态
  452. const GetExamStatus=async ()=>{
  453. const param = {
  454. examSubjectId:examSubjectId.value
  455. }
  456. getExamProcess(param).then((res: any) => {
  457. console.log('打印考试流程状态返回', res)
  458. if (res.code === 200) {
  459. const newStateStr = res.data;
  460. console.log('打印考试流程状态', newStateStr)
  461. // 替换为 Store Commit/Action
  462. examStore.setExamState(newStateStr);//设置状态数据
  463. if(newStateStr.includes(2))
  464. {
  465. router.push('/exam/analysis/score')
  466. }
  467. else
  468. {
  469. }
  470. }
  471. })
  472. }
  473. const ConfigRequestUrlByEnv= () =>{
  474. console.log("打印环境", import.meta.env.MODE);
  475. if (import.meta.env.MODE === 'development')
  476. {
  477. //本地开发环境:写死测试环境地址
  478. websocketUrl = 'wss://index.k12100.net/teaching/api/v1/teaching/websocket/';
  479. uploadUrl = `https://index.k12100.net/teaching/api/v1/ai_exam_scan/upload_multi_img`;//图片上传地址
  480. updateCountUrl = `https://index.k12100.net/teaching/api/v1/ai_exam_scan/record_batch_scan_number`;//更新扫描数量地址
  481. }
  482. else
  483. {
  484. //线上环境(测试/生产):读取配置的环境变量
  485. websocketUrl = import.meta.env.VITE_WEB_SOCKET_URL;//websocket链接地址
  486. uploadUrl = `${import.meta.env.VITE_API_BASE_URL_API}/api/v1/ai_exam_scan/upload_multi_img`;//图片上传地址
  487. updateCountUrl = `${import.meta.env.VITE_API_BASE_URL_API}/api/v1/ai_exam_scan/record_batch_scan_number`;//更新扫描数量地址
  488. }
  489. console.log("设置webSocket地址", websocketUrl);
  490. console.log("打印上传图片地址", uploadUrl);
  491. }
  492. //打开考号位数设置
  493. const OpenNumberSet=()=>{
  494. console.log("打印考号位数设置");
  495. showNumberSetDialog.value=true;
  496. }
  497. //关闭考号位数设置
  498. const CloseNumberSet=()=>{
  499. showNumberSetDialog.value=false;
  500. }
  501. //确定考号设置
  502. const HandleSubmit=()=>{
  503. if(examCodeStart.value > examCodeEnd.value) {
  504. ElMessage.error("开始考号位置不能大于结束考号位置");
  505. return;
  506. }
  507. let params={
  508. examSubjectId:examSubjectId.value,
  509. examCodeStart:examCodeStart.value,
  510. examCodeEnd:examCodeEnd.value,
  511. };
  512. setExamNumberPosition(params).then(res => {
  513. if(res.code == 200)
  514. {
  515. ElMessage.success("设置成功!")
  516. showNumberSetDialog.value=false;
  517. }
  518. else
  519. {
  520. ElMessage.error(res.msg);
  521. }
  522. })
  523. }
  524. //切换弹窗模式
  525. const ChangeDriveUI=()=>{
  526. console.log("打印弹窗模式", useDriveUI.value);
  527. let params={
  528. alterWindow:useDriveUI.value
  529. };
  530. setExamScanSetting(params).then(res => {
  531. if(res.code == 200)
  532. {
  533. ElMessage.success("设置成功!")
  534. }
  535. else
  536. {
  537. ElMessage.error(res.msg);
  538. }
  539. })
  540. }
  541. //改变识别码
  542. const ChangeCode=()=>{
  543. let param={
  544. examSubjectId:examSubjectId.value,
  545. chooseCode:scanIdentifyCode.value//识别号 1-学号 2-平台号 3-学籍号 4-考号
  546. };
  547. saveChooseCode(param).then(res=>{
  548. console.log("打印识别码设置结果",res);
  549. if(res.code==200)
  550. {
  551. ElMessage.success("设置成功!")
  552. examCodeEnd.value=res.data;
  553. examCodeStart.value=1;
  554. }
  555. else
  556. {
  557. ElMessage.error(res.msg);
  558. }
  559. })
  560. }
  561. //获取未上传图片的数量
  562. const GetNoUploadNumber=(item:BatchItem)=>
  563. {
  564. const scannedPaperNum = item.scannedPaperNum ?? 0;
  565. const uploadNum = item.uploadNum ?? 0;
  566. const result = scannedPaperNum - uploadNum;
  567. return result > 0 ? result : 0;
  568. }
  569. // 跟后端websocket连接 1.建立socket连接 用户ID_考试科目ID_学校ID_批次号
  570. const WebSocketLink = () => {
  571. if(webSocket.value && webSocket.value.getSocketStatus() === WebSocket.OPEN)
  572. {
  573. webSocket.value.closeWebSocket();
  574. }
  575. let userCourseId='';
  576. let schoolIdValue = userInfo.value.schoolId; // 这里逻辑是 单校都取登录用户的学校ID 如果是联校账号 给单校扫描 传单校的学校id
  577. // if(schoolType.value == 1) {
  578. // //联校 盟主学校
  579. // schoolIdValue = selectSchoolId.value;
  580. // } else {
  581. // schoolIdValue = userInfo.value.schoolId;
  582. // }
  583. console.log("打印用户信息", userInfo.value);
  584. userCourseId = `${userInfo.value.userId}_${examSubjectId.value}_${schoolIdValue}_${currentBatchNo.value}`; //用户 科目 批次号
  585. console.log("websocketUrl",websocketUrl);
  586. const url = `${websocketUrl}${userCourseId}`;
  587. console.log("打印websocket链接地址url", url);
  588. webSocket.value = new WebSocketManager(url, MessageBack);
  589. webSocket.value.start();
  590. }
  591. //websocket消息返回
  592. const MessageBack = (data:any) => {
  593. // console.log("打印websocket返回数据", data);
  594. const msg=JSON.parse(data);
  595. console.log("Websocket消息返回处理",msg);
  596. //code 0 建立成功 200 业务成功 400 业务失败 异常 需要提示信息
  597. if(msg.code==0)
  598. {
  599. //建立成功
  600. console.log("和后端的websocket建立成功");
  601. }
  602. else if(msg.code==200)
  603. {
  604. //业务成功
  605. // console.log("打印当前tableData的数据",this.tableData);
  606. //正常扫描
  607. let currentData=msg.data[0];
  608. let targetItem = tableData.value.find(item => item.batchNo == currentData.batchNo);
  609. console.log("识别成功+1",targetItem);
  610. if(msg.abnormalNum==-1)
  611. {
  612. scanDataInfo.value.unScanned = msg.unScanned;
  613. scanDataInfo.value.examMissNum = msg.examMissNum;
  614. scanDataInfo.value.abnormalNum = msg.abnormalNum;
  615. scanDataInfo.value.scannedNum = msg.scannedNum;
  616. if (targetItem) {
  617. if(currentData.uploadNum==-1)
  618. {
  619. targetItem.uploadNum++;
  620. }
  621. }
  622. }
  623. else
  624. {
  625. if (targetItem) {
  626. targetItem.uploadNum = msg.data[0].uploadNum;
  627. }
  628. scanDataInfo.value.unScanned = msg.unScanned;
  629. scanDataInfo.value.examMissNum = msg.examMissNum;
  630. scanDataInfo.value.abnormalNum = msg.abnormalNum;
  631. scanDataInfo.value.scannedNum = msg.scannedNum;
  632. let process = (Number(scanDataInfo.value.scannedNum) + Number(scanDataInfo.value.examMissNum)) / Number(scanDataInfo.value.examTotal) * 100
  633. if (isNaN(process)) {
  634. process = 0
  635. }
  636. }
  637. if(targetItem)
  638. {
  639. if(Number(targetItem.scannedPaperNum) === Number(targetItem.uploadNum))
  640. {
  641. targetItem.uploadStatus=1;
  642. // loadingText.value='';
  643. isScanning.value=false;
  644. // removeBeforeUnloadListener();// 移除页面离开阻止监听器
  645. }
  646. else
  647. {
  648. }
  649. }
  650. }
  651. else if(msg.code==400)
  652. {
  653. //业务失败
  654. GetScanBatchList();
  655. ElMessage.error(msg.message);
  656. }
  657. }
  658. //重新识别弹窗
  659. const OpenReIdentify=() => {
  660. //判断是否在扫描
  661. if(isScanning.value)
  662. {
  663. //正在扫描中 不可重新识别
  664. ElMessage.error("正在扫描中,请等待扫描完成后再重新识别!");
  665. return;
  666. }
  667. //计算总识别张数
  668. let sumTotal=0;
  669. tableData.value.forEach((item:any) => {
  670. sumTotal+=item.uploadNum;
  671. });
  672. isReIdentify.value=true;
  673. // reIdentifyNumber.value=sumTotal;
  674. reIdentifyTotal.value=sumTotal;
  675. }
  676. //开始扫描
  677. const OpenScan = () => {
  678. if(isScanning.value)
  679. {
  680. //正在扫描
  681. ElMessage.warning('正在扫描中,请勿重复点击哦!');
  682. return;
  683. }
  684. //判断客户端是否已经连接
  685. if(scanClientStates.value)
  686. {
  687. //一打开客户端
  688. console.log('客户端已打开,开始扫描');
  689. // isScanning.value=true;//扫描状态
  690. const params={
  691. examSubjectId:examSubjectId.value,
  692. schoolId:selectSchoolId.value,
  693. };
  694. getCurrentBatchNo(params).then((res:any)=>{
  695. console.log("当前获取批次结果",res);
  696. if(res.code==200)
  697. {
  698. currentBatchNo.value=res.data.batchNo;
  699. currentBatchNoId.value=res.data.id;
  700. let newData={
  701. batchNo:currentBatchNo.value,
  702. id:currentBatchNoId.value,//批次号
  703. batchTypeName:'扫描',
  704. scanUserName:currentUserName.value,
  705. scannedPaperNum:0,//扫描张数
  706. scannedTime:Date.now(),//扫描时间
  707. uploadNum:0,//上传张数
  708. uploadStatus:0,//扫描状态
  709. };
  710. tableData.value.push(newData);
  711. //开始扫描
  712. let jsonParam={
  713. examSubjectId:examSubjectId.value,
  714. batchNumber:currentBatchNo.value,
  715. // sensitive: 35,//灵敏度参数 固定35
  716. filter:0,//是否过滤参数 1-是 0-否
  717. // heightRatio:this.heightRatio,//占打分框高度比例
  718. schoolId:selectSchoolId.value//学校id 联校单校都需要学校id
  719. };
  720. let json = {
  721. "action":"startScan",//交互指令参数
  722. "batchNumber":GetBatchStr(res.data.id,currentBatchNo.value),//批次号 这里传给客户端的批次号需要进行处理 截取批次id后5位拼接batchNumber
  723. "subjectCode":examSubjectCode.value,//科目编号
  724. "paperSchema":1,//this.paperSchema,// 单双面//页面类型 1 单面 2 双面
  725. "token":localStorage.getItem('token'),//token信息
  726. "uploadUrl":uploadUrl, // 图片上传地址
  727. "updateCountUrl":updateCountUrl,//更新扫描纸张数量接口 接口参数 批次id post 请求 id
  728. "batchId":currentBatchNoId.value,//批次号id
  729. "dpi":150,//dpi参数 设置图片清晰度质量的
  730. "isColor":1,//是否彩色图片 手阅卡扫描彩色图片 0 黑白 1 彩色
  731. "useDriveUI":0,//startScan和scanTemplate增加useDriveUI参数,=1时会弹框,其他值或不写则不弹
  732. "jsonParam":JSON.stringify(jsonParam),// 后端使用的参数 json字符串 后端接口固定三个参数 1:jsonParam 2:seqNumber(这个批次的图片序号:从1开始) 3:file 图片文件
  733. };//正式版参数
  734. console.log("打印发送的数据",JSON.stringify(json));
  735. setTimeout(() => {
  736. scanCommon.send(JSON.stringify(json))
  737. },100)
  738. // 建立与后端的websocket连接
  739. WebSocketLink();
  740. }
  741. else
  742. {
  743. ElMessage.error(res.msg);
  744. }
  745. });
  746. }
  747. else
  748. {
  749. //提示客户端未打开
  750. ElMessage.warning('请先打开客户端');
  751. }
  752. }
  753. //获取批次字符串
  754. const GetBatchStr=(batchId:any,batChNo:any)=>{
  755. const batchNoValue = batchId.substring(batchId.length - 5);
  756. const batchNo=String(batChNo).padStart(3, '0');
  757. const batchNumber=batchNoValue+batchNo;//传给客户端的图片批次号 id后五位数加上00批次号
  758. return batchNumber;
  759. }
  760. //根据处理后的批次号转换成处理前的批次号
  761. const GetBatchNumber=(batchNumber:any)=>{
  762. if (!batchNumber) {
  763. console.warn('批次号为空或未定义');
  764. return currentBatchNo.value;//返回当前的批次号
  765. }
  766. const batchStr = batchNumber.toString();
  767. if (batchStr.length < 3) {
  768. console.warn('批次号长度小于3位:', batchStr);
  769. return currentBatchNo.value;//返回当前的批次号
  770. }
  771. const lastThree = batchStr.slice(-3);
  772. const parsedNumber = parseInt(lastThree, 10);
  773. if (isNaN(parsedNumber)) {
  774. console.error('无法解析批次号:', lastThree);
  775. return currentBatchNo.value;//返回当前的批次号
  776. }
  777. console.log("打印最后的批次号",parsedNumber);
  778. return parsedNumber.toString();
  779. }
  780. //删除批次
  781. const OptionDelete=(row: any) => {
  782. console.log('删除批次', row);
  783. if(row.scanUserName==currentUserName.value)
  784. {
  785. let msg = `确定要删除当前选择的<span style="color:#F56C6C;font-weight:600;">「批次${row.batchNo}」</span>的数据吗?`;
  786. ElMessageBox.confirm(msg, '提示', {
  787. confirmButtonText: '确定',
  788. cancelButtonText: '取消',
  789. customClass:'page_dialog',
  790. dangerouslyUseHTMLString: true,
  791. type: 'warning'
  792. }).then(() => {
  793. // 删除操作
  794. console.log('删除', row);
  795. const params={
  796. examSubjectId:examSubjectId.value,
  797. batchNo:row.batchNo,
  798. schoolId:selectSchoolId.value,
  799. };
  800. deleteBatch(params).then((res:any)=>{
  801. if(res.code==200)
  802. {
  803. ElMessage.success("删除成功!")
  804. GetScanBatchList();
  805. }
  806. else
  807. {
  808. ElMessage.error(res.msg);
  809. }
  810. })
  811. }).catch(() => {
  812. // 取消操作
  813. console.log('取消删除');
  814. });
  815. }
  816. else
  817. {
  818. ElMessage.warning('只能删除自己上传的记录');
  819. }
  820. }
  821. //跳转到异常详情页
  822. const GotoDetail = (type: number) => {
  823. console.log('跳转到异常详情页', type);
  824. scanState.value=type;
  825. // 根据类型跳转到不同的详情页
  826. showAbnormalDialog.value=true;
  827. }
  828. //跳转到批次详情页
  829. const GotoBatchDetail=(batchNo:any)=>{
  830. router.push({
  831. path: '/exam/scanDetail',
  832. query: {
  833. examSubjectId: examSubjectId.value,
  834. batchNo: batchNo,
  835. },
  836. })
  837. }
  838. // 打开导入学生名单
  839. const OpenImportStudent = () => {
  840. showSelectStudent.value=true;
  841. }
  842. //打开上传图片
  843. const OpenImportDialog=() => {
  844. showUploadImage.value=true;
  845. }
  846. // 打开编辑考试名单
  847. const OpenEditExamList = () => {
  848. router.push({
  849. path: '/exam/examList',
  850. query: {
  851. examSubjectId: examSubjectId.value,
  852. },
  853. });
  854. }
  855. //学生名单导入成功
  856. const StudentSuccess = () => {
  857. HasImportStudent();
  858. }
  859. //获取批次列表 获取扫描批次列表
  860. const GetScanBatchList=async()=>{
  861. const params = {
  862. examSubjectId: examSubjectId.value,
  863. schoolId: 0,//单校 0
  864. };
  865. const res = await getBatchList(params);
  866. if(res.code==200)
  867. {
  868. selectSchoolId.value=res.data.schoolId;//获取学校id
  869. scanIdentifyCode.value=res.data.chooseCode;//获取识别码
  870. //1-单面 2-双面
  871. examCodeEnd.value=res.data.examCodeEnd;
  872. examCodeStart.value=res.data.examCodeStart;
  873. if(res.data.scannedWebSocketVO)
  874. {
  875. tableData.value=res.data.scannedWebSocketVO?.data || [];
  876. scanDataInfo.value.abnormalNum=res.data.scannedWebSocketVO.abnormalNum;//异常数量
  877. scanDataInfo.value.examMissNum=res.data.scannedWebSocketVO.examMissNum;//缺考数量
  878. scanDataInfo.value.scannedNum=res.data.scannedWebSocketVO.scannedNum;//已上传数量
  879. scanDataInfo.value.unScanned=res.data.scannedWebSocketVO.unScanned;//未扫描数量
  880. scanDataInfo.value.examTotal=res.data.scannedWebSocketVO.examTotal;//总人数
  881. let examInfoCount={
  882. unScanned:res.data.scannedWebSocketVO.unScanned,
  883. examMissNum:res.data.scannedWebSocketVO.examMissNum,
  884. scannedNum:res.data.scannedWebSocketVO.scannedNum,
  885. abnormalNum:res.data.scannedWebSocketVO.abnormalNum,
  886. examTotal:res.data.scannedWebSocketVO.examTotal,
  887. };
  888. examStore.setExamInfoCount(examInfoCount);//设置扫描数据信息
  889. }
  890. }
  891. }
  892. //计算高度的函数
  893. const CalculateTableHeight = () => {
  894. // nextTick 确保 DOM 更新后再获取尺寸
  895. nextTick(() => {
  896. // window.innerHeight 是浏览器可视区域高度
  897. // 简单算法:视窗高度 - 固定占用高度
  898. let computedHeight = window.innerHeight - 136;
  899. // 限制最小高度,防止太矮
  900. if (computedHeight < 200) {
  901. computedHeight = 200;
  902. }
  903. tableHeight.value = computedHeight;
  904. });
  905. };
  906. //更新扫描张数
  907. const UpdateBatchScanNumber = (batchId: any, scanNumber: Number) => {
  908. const params={
  909. id:batchId,
  910. recordNumber: scanNumber,
  911. };
  912. updateScanCount(params).then((res:any)=>{
  913. if(res.code==200)
  914. {
  915. console.log("第"+batchId+"批次更新扫描张数"+scanNumber+"成功", res);
  916. }
  917. })
  918. };
  919. // 处理扫描结果
  920. const HandleScanResult = (res: any) => {
  921. console.log('收到扫描数据', res);
  922. // 业务逻辑...
  923. if (res.action == 'uploading')
  924. {
  925. let batchNumber: any = '';
  926. if (res.batchNumber)
  927. {
  928. batchNumber = GetBatchNumber(res.batchNumber || res.data?.batchNumber);
  929. }
  930. const targetItem = tableData.value.find((item: any) => item.batchNo == batchNumber);
  931. if (typeof loadingText !== 'undefined') loadingText.value = "启动扫描仪中";
  932. ElMessage.success("正在启动扫描仪,请稍后…");
  933. if (targetItem)
  934. {
  935. targetItem.uploadStatus = 0; //更新上传状态 0 开始上传
  936. }
  937. }
  938. // 开始扫描指令
  939. if (res.action == 'startScan')
  940. {
  941. let batchNumber = GetBatchNumber(res?.batchNumber || res.data?.batchNumber);
  942. let currentItem = tableData.value.find((item: any) => item.batchNo == batchNumber);
  943. console.log("打印currentItem", currentItem);
  944. //开始扫描指令
  945. if (res.code == 200)
  946. {
  947. ElMessage.success("扫描仪启动成功,开始扫描…");
  948. if(typeof loadingText !== 'undefined') loadingText.value = "正在扫描中";
  949. }
  950. if (res.code == 502)
  951. {
  952. console.log("开始扫描指令502错误 未检测到纸张或者卡纸", res);
  953. console.log("打印currentItem", currentItem);
  954. if (currentItem) {
  955. currentItem.uploadStatus = 1; //更新上传状态
  956. }
  957. if (typeof loadingText !== 'undefined') loadingText.value = ""; //清空上传提示
  958. isScanning.value = false; //重置扫描状态
  959. ElMessage.error(res.msg);
  960. }
  961. if (res.code == 510)
  962. {
  963. console.log("启动扫描失败,上传正在进行中", res);
  964. isScanning.value = true; //重置扫描状态
  965. ElMessage.warning(res.msg + '请勿重复点击');
  966. }
  967. if (res.code == 509) {
  968. console.log("扫描仪正在使用中", res);
  969. if (res.msg == '未找到指定的扫描仪') {
  970. isScanning.value = false; //重置扫描状态
  971. }
  972. ElMessage.warning(res.msg + ',请稍后再试!');
  973. if (currentItem) {
  974. currentItem.uploadStatus = 1; //更新上传状态
  975. }
  976. }
  977. }
  978. // 扫描完成指令
  979. if (res.action == 'uploadFinish') {
  980. console.log("上传完成uploadFinish 更新上传动画状态", res);
  981. // 修复:直接调用 GetBatchNumber
  982. let batchNumber = GetBatchNumber(res?.batchNumber || res.data?.batchNumber);
  983. let targetItem = tableData.value.find((item: any) => item.batchNo == batchNumber);
  984. if (typeof loadingText !== 'undefined') loadingText.value = '正在上传中';
  985. if (targetItem) {
  986. targetItem.scannedPaperNum = res.scanNumber; //更新扫描张数
  987. targetItem.failedNumber = res.failedNumber; //更新失败张数
  988. if (typeof UpdateBatchScanNumber === 'function') {
  989. UpdateBatchScanNumber(targetItem.id, Number(targetItem.scannedPaperNum));
  990. }
  991. }
  992. isScanning.value = false; //重置扫描状态
  993. }
  994. if (res.action == 'uploadNumber') {
  995. console.log("上传完成uploadNumber 更新上传张数", res);
  996. // 逻辑已注释,保持原样
  997. }
  998. if (res.action == 'scanNumber') {
  999. console.log("扫描张数scanNumber 更新扫描张数");
  1000. // 修复:直接调用 GetBatchNumber
  1001. let batchNumber = GetBatchNumber(res.batchNumber || res.data?.batchNumber);
  1002. let targetItem = tableData.value.find((item: any) => item.batchNo == batchNumber);
  1003. console.log("打印targetItem", targetItem);
  1004. if (targetItem) {
  1005. targetItem.scannedPaperNum = res.number; //更新上传张数
  1006. targetItem.uploadStatus = 0; //更新上传状态 0 开始上传
  1007. }
  1008. // 修复:确保 UpdateBatchScanNumber 已定义
  1009. if (targetItem && typeof UpdateBatchScanNumber === 'function') {
  1010. UpdateBatchScanNumber(targetItem.id, Number(targetItem.scannedPaperNum));
  1011. }
  1012. }
  1013. if (res.action == 'loadImage') {
  1014. console.log("加载图片loadImage 获取图片数据", res);
  1015. // 修复:reImageList 必须是 ref
  1016. if (typeof reImageList !== 'undefined') {
  1017. reImageList.value = res.data || [];
  1018. reImageList.value.forEach((item: any) => {
  1019. item.uploadStatus = -2;
  1020. item.uploadProgress = 0;
  1021. });
  1022. }
  1023. let batchNumber = GetBatchNumber(res.batchNumber);
  1024. let targetItem = tableData.value.find((item: any) => item.batchNo == batchNumber);
  1025. console.log("打印重新上传图片列表", reImageList.value);
  1026. console.log("打印重新上传的批次Item", targetItem);
  1027. if (targetItem) {
  1028. targetItem.uploadStatus = 0;
  1029. }
  1030. if (typeof loadingText !== 'undefined') loadingText.value = '正在上传中';
  1031. if (typeof uploadDialogTitle !== 'undefined') uploadDialogTitle.value = '未上传的图片';
  1032. if (typeof showUploadDialog !== 'undefined') showUploadDialog.value = true;
  1033. }
  1034. if (res.action == 'finish') {
  1035. console.log("图片重传finish指令", res);
  1036. let batchNumber = GetBatchNumber(res.batchNumber);
  1037. let targetItem = tableData.value.find((item: any) => item.batchNo == batchNumber);
  1038. let fileIndex = res.fileIndex.split(",");
  1039. console.log("打印fileIndex", fileIndex);
  1040. fileIndex.forEach((index: string) => {
  1041. // 修复:reImageList.value
  1042. let reImageItem = reImageList.value.find((item: any) => item.fileIndex == index);
  1043. if (reImageItem) {
  1044. let progress = reImageItem.uploadProgress;
  1045. const interval = setInterval(() => {
  1046. progress += 5;
  1047. if (progress >= 100) {
  1048. progress = 100;
  1049. clearInterval(interval);
  1050. // 修复:Vue3 不需要 $set,直接赋值即可触发响应式
  1051. reImageItem.uploadStatus = res.uploadState;
  1052. }
  1053. reImageItem.uploadProgress = progress;
  1054. }, 100);
  1055. }
  1056. });
  1057. // 修复:reImageCount 可能是 ref 或计算属性
  1058. let currentReImageCount = typeof reImageCount !== 'undefined' ? reImageCount.value : reImageList.value.length;
  1059. if (currentReImageCount === 0) {
  1060. if (typeof showUploadDialog !== 'undefined') showUploadDialog.value = false;
  1061. if (targetItem) {
  1062. targetItem.uploadStatus = 1;
  1063. isScanning.value = false;
  1064. // 修复:确保 GetBatchList 已定义
  1065. if (typeof GetBatchList === 'function') GetBatchList();
  1066. }
  1067. }
  1068. }
  1069. if (res.action == 'getFailedImage') {
  1070. console.log("获取失败图片getFailedImage", res);
  1071. let failedList = res.data || [];
  1072. failedList.forEach((item: any) => {
  1073. let batchNumber = GetBatchNumber(item.batchNumber);
  1074. let currentItem = tableData.value.find((item1: any) => item1.batchNo == batchNumber);
  1075. if (currentItem) {
  1076. if (item.failedNumber == 0) {
  1077. if (currentItem.scannedPaperNum != currentItem.uploadNum) {
  1078. currentItem.scannedPaperNum = currentItem.uploadNum;
  1079. if (typeof UpdateBatchScanNumber === 'function') {
  1080. UpdateBatchScanNumber(currentItem.id, currentItem.uploadNum);
  1081. }
  1082. }
  1083. } else {
  1084. let number = currentItem.uploadNum + item.failedNumber;
  1085. currentItem.scannedPaperNum = number;
  1086. if (typeof UpdateBatchScanNumber === 'function') {
  1087. UpdateBatchScanNumber(currentItem.id, number);
  1088. }
  1089. }
  1090. }
  1091. });
  1092. }
  1093. if (res.action == 'scanFinishBatch')
  1094. {
  1095. if (typeof loadingText !== 'undefined') loadingText.value = '本次扫描结束';
  1096. console.log("本批次扫描完成scanFinishBatch 更新扫描张数", res);
  1097. }
  1098. };
  1099. onMounted(() => {
  1100. // 初始化连接
  1101. scanCommon.init(HandleScanResult);
  1102. // 监听连接状态
  1103. scanCommon.watchConnection((isOnline,clientVersion) => {
  1104. scanClientStates.value=isOnline;
  1105. scanClientVersion.value=clientVersion; //客户端版本号
  1106. // console.log("客户端版本",clientVersion);
  1107. });
  1108. if (!examStore.currentExam) {
  1109. console.warn('当前没有选中的考试信息')
  1110. // 可选:如果没有数据,可以重定向回列表页或提示用户
  1111. }
  1112. GetScanBatchList();//获取扫描批次列表
  1113. CalculateTableHeight();//初始化计算表格高度
  1114. console.log("打印当前的配置",import.meta.env.VUE_APP_BASE);
  1115. ConfigRequestUrlByEnv();//环境配置
  1116. // 监听窗口大小变化
  1117. window.addEventListener('resize', CalculateTableHeight);
  1118. });
  1119. // 卸载时移除监听,防止内存泄漏
  1120. onUnmounted(() => {
  1121. window.removeEventListener('resize', CalculateTableHeight);
  1122. // 组件销毁时务必停止,防止内存泄漏和后台重连
  1123. scanCommon.stop();
  1124. });
  1125. </script>
  1126. <style lang="scss" scoped>
  1127. .page_list
  1128. {
  1129. width: 100%;
  1130. height: 100%;
  1131. padding: 20px;
  1132. background-color: #fff;
  1133. border-radius: 4px;
  1134. box-sizing: border-box;
  1135. }
  1136. .scan_state_title
  1137. {
  1138. font-size: 14px;
  1139. color:#333;
  1140. font-weight: 400;
  1141. span
  1142. {
  1143. // text-emphasis-style: dot; /* 实心小圆点 • */
  1144. // // text-emphasis-style: circle; /* 空心圆圈 ○ */
  1145. // // text-emphasis-style: double-circle; /* 双圆圈 ◉ */
  1146. // // text-emphasis-style: triangle; /* 三角形 ▲ */
  1147. // /* 自定义字符 */
  1148. // // text-emphasis-style: '★'; /* 使用星号 */
  1149. // // text-emphasis-style: '※'; /* 使用参考标记 */
  1150. // text-emphasis-color: red;
  1151. // text-emphasis-position: under; /* 下方居中 */
  1152. }
  1153. }
  1154. .scan_state_open
  1155. { margin-left: 5px;
  1156. font-size: 14px;
  1157. font-weight: 400;
  1158. color: #2BC644;
  1159. i
  1160. {
  1161. margin-right: 5px;
  1162. }
  1163. }
  1164. .scan_state_close
  1165. {
  1166. font-size: 14px;
  1167. font-weight: 400;
  1168. margin-left: 5px;
  1169. color:#F56C6C;
  1170. i
  1171. {
  1172. margin-right: 5px;
  1173. }
  1174. }
  1175. </style>