123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671 |
- <template>
- <div class="scan-paper">
- <div class="part-box part-box-pad part-box-flex scan-head">
- <div>
- <h2>课程(代码):{{ task.courseName }}({{ task.courseCode }})</h2>
- </div>
- <div>
- <el-button :disabled="!hasSelectedData" type="primary" @click="toBind"
- >重新绑定</el-button
- >
- <el-button :disabled="!canClear" type="danger" @click="clearStage"
- >清空</el-button
- >
- <el-button
- :disabled="!canSave"
- :loading="saving"
- type="primary"
- @click="toSave"
- >保存</el-button
- >
- <el-button
- type="primary"
- :loading="scanStatus === 'SCAN'"
- :disabled="!canScan"
- @click="toScan"
- >
- {{ statusDesc[scanStatus] }}
- </el-button>
- </div>
- </div>
- <div class="scan-body">
- <div class="scan-result">
- <div class="mb-4 tab-btns scan-result-head">
- <el-button
- size="medium"
- :type="curTab === 'normal' ? 'primary' : 'default'"
- @click="switchTab('normal')"
- >正常 <span>[{{ normalCount }}]</span></el-button
- >
- <el-button
- size="medium"
- :type="curTab === 'error' ? 'danger' : 'default'"
- @click="switchTab('error')"
- >异常 <span>[{{ errorCount }}]</span></el-button
- >
- </div>
- <div class="scan-result-body">
- <scan-result-table
- v-if="isNormalTab"
- ref="scanResultTableRef"
- :table-data.sync="scanStageList"
- tab="normal"
- @row-click="rowClickHandle"
- @select-change="selectChange"
- @delete-paper="deletePaperHandle"
- ></scan-result-table>
- <scan-result-table
- v-else
- ref="scanResultTableRef"
- :table-data.sync="errorStageList"
- tab="error"
- @row-click="rowClickHandle"
- @select-change="selectChange"
- @delete-paper="deletePaperHandle"
- ></scan-result-table>
- </div>
- <div v-if="isNormalTab" class="scan-result-foot">
- <span>共</span>
- <span class="color-primary mlr-1">{{ studentCount }}</span>
- <span>人,</span>
- <span class="color-primary mr-1">{{ paperCount }}</span>
- <span>张图片</span>
- </div>
- </div>
- <div class="scan-content">
- <image-contain
- v-if="curPaper && curPaper.url"
- ref="ImageContain"
- :image="curPaper"
- :show-rotate="false"
- @on-prev="toPrevPaper"
- @on-next="toNextPaper"
- ></image-contain>
- </div>
- </div>
- <!-- ManualBindDialog -->
- <manual-bind-dialog
- ref="ManualBindDialog"
- :datas="selectList"
- :task="task"
- @confirm="bindConfirm"
- ></manual-bind-dialog>
- <!-- SelectBatchNoDialog -->
- <select-batch-no-dialog
- ref="SelectBatchNoDialog"
- @confirm="saveScanData"
- ></select-batch-no-dialog>
- </div>
- </template>
- <script>
- import { mapState } from "vuex";
- import {
- getPreUploadFiles,
- clearDir,
- deleteFiles,
- decodeImageCode,
- getDirScanFile,
- batchSaveImages,
- } from "../../../plugins/imageOcr";
- import db from "../../../plugins/db";
- import { evokeScanner } from "../../../plugins/scanner";
- import ImageContain from "@/components/ImageContain.vue";
- import ScanResultTable from "../components/ScanResultTable.vue";
- import ManualBindDialog from "../components/ManualBindDialog.vue";
- import SelectBatchNoDialog from "../components/SelectBatchNoDialog.vue";
- import timeMixins from "@/mixins/setTimeMixins";
- import { getStudentInfo } from "../api";
- import log4js from "@/plugins/logger";
- import { randomCode, calcSum } from "@/plugins/utils";
- import { getStageDir } from "@/plugins/env";
- const logger = log4js.getLogger("scan");
- export default {
- name: "scan-paper",
- mixins: [timeMixins],
- components: {
- ImageContain,
- ScanResultTable,
- ManualBindDialog,
- SelectBatchNoDialog,
- },
- data() {
- return {
- task: this.$ls.get("task", {}),
- scanStatus: "INIT",
- scanStageList: [],
- errorStageList: [],
- selectList: [],
- statusDesc: {
- INIT: "开始扫描",
- SCAN: "扫描中",
- FINISH: "继续扫描",
- },
- user: this.$ls.get("user", {}),
- saving: false,
- maxCacheCount: 120,
- lastStudentCode: "",
- scanCount: 0,
- menus: [
- { code: "normal", name: "正常" },
- { code: "error", name: "异常" },
- ],
- curTab: "normal",
- // 非等待模式:delayMode:0
- looping: false,
- // 图片预览
- curPapers: [],
- curPaperIndex: 0,
- curPaper: { url: "" },
- stageDir: getStageDir(),
- };
- },
- computed: {
- ...mapState("client", ["ocrArea"]),
- canSave() {
- return (
- this.scanStatus === "FINISH" &&
- this.normalCount > 0 &&
- this.errorCount === 0
- );
- },
- canScan() {
- return this.errorCount + this.normalCount <= this.maxCacheCount;
- },
- canClear() {
- return this.studentCount > 0;
- },
- hasSelectedData() {
- return Boolean(this.selectList.length);
- },
- IS_DELAY_MODE() {
- return this.GLOBAL.delayMode === 1;
- },
- errorCount() {
- return calcSum(this.errorStageList.map((item) => item.papers.length));
- },
- normalCount() {
- return calcSum(this.scanStageList.map((item) => item.papers.length));
- },
- isNormalTab() {
- return this.curTab === "normal";
- },
- studentCount() {
- return this.isNormalTab
- ? this.scanStageList.length
- : this.errorStageList.length;
- },
- paperCount() {
- return this.isNormalTab ? this.normalCount : this.errorCount;
- },
- },
- created() {
- this.$on("go-back", this.goBackHandle);
- },
- beforeDestroy() {
- this.stopLoopScaningFile();
- this.clearFiles();
- },
- methods: {
- initData() {
- this.lastStudentCode = "";
- this.scanStageList = [];
- this.errorStageList = [];
- this.scanStatus = "INIT";
- this.curPapers = [];
- this.curPaperIndex = 0;
- this.curPaper = { url: "" };
- this.scanCount = 0;
- },
- clearFiles() {
- clearDir(this.stageDir);
- },
- async goBackHandle() {
- if (this.scanStageList.length) {
- const res = await this.$confirm(
- `当前存在未保存的扫描数据,确定要退出吗?`,
- "警告",
- {
- type: "warning",
- }
- ).catch(() => {});
- if (res !== "confirm") return;
- }
- this.$router.go(-1);
- logger.info(`99退出扫描`);
- },
- switchTab(tab) {
- this.curTab = tab;
- this.selectList = [];
- },
- // scan
- toScan() {
- if (!this.canScan) {
- this.$message.error("已超过最大缓存数量,请先保存数据再继续扫描!");
- return;
- }
- if (this.scanStatus === "INIT") {
- this.startTask();
- } else {
- this.continueTask();
- }
- },
- startTask() {
- logger.info(`01开始扫描`);
- this.continueTask();
- },
- continueTask() {
- this.scanStatus = "SCAN";
- if (this.IS_DELAY_MODE) {
- this.evokeScanExe();
- } else {
- this.evokeScanExeNotDelay();
- }
- },
- async evokeScanExe() {
- logger.info("02唤起扫描仪");
- await evokeScanner(this.GLOBAL.input).catch((error) => {
- console.error(error);
- });
- // 缓存已扫描的数据
- const res = getPreUploadFiles(this.GLOBAL.input, true);
- if (!res.succeed) {
- logger.error(`03扫描仪停止,故障:${res.errorMsg}`);
- this.$message.error(res.errorMsg);
- this.scanStatus = "FINISH";
- return;
- }
- logger.info(`03扫描仪停止,扫描数:${res.data.length}`);
- await this.stageScanImage(res.data);
- this.scanStatus = "FINISH";
- logger.info(`03-1完成条码解析`);
- },
- async evokeScanExeNotDelay() {
- logger.info("02唤起扫描仪");
- this.looping = true;
- this.loopScaningFile();
- await evokeScanner(this.GLOBAL.input).catch((error) => {
- console.error(error);
- });
- this.stopLoopScaningFile();
- await this.getScaningFile();
- const scanCount = this.scanStageList.length - this.scanCount;
- this.scanCount = this.scanStageList.length;
- // 已扫描的数据
- const res = getPreUploadFiles(this.GLOBAL.input);
- this.scanStatus = "FINISH";
- if (!res.succeed) {
- logger.error(
- `03扫描仪停止,扫描数:${scanCount},故障:${res.errorMsg}`
- );
- this.$message.error(res.errorMsg);
- return;
- }
- logger.info(`03扫描仪停止,扫描数:${scanCount}`);
- },
- async stageScanImage(imageList) {
- const ocrAreaContent = JSON.stringify(this.ocrArea);
- for (let i = 0, len = imageList.length; i < len; i++) {
- const fileInfo = {
- id: "",
- taskId: this.task.id,
- schoolId: this.task.schoolId,
- semesterId: this.task.semesterId,
- examId: this.task.examId,
- courseCode: this.task.courseCode,
- courseName: this.task.courseName,
- frontOriginImgPath: imageList[i].frontFile,
- versoOriginImgPath: imageList[i].versoFile,
- isFormal: 1,
- studentName: "",
- studentCode: "",
- ocrArea: ocrAreaContent,
- fileTypeId: "0",
- fileTypeName: "答题卡",
- roomOrClass: "",
- batchNo: "",
- clientUserId: this.user.id,
- clientUsername: this.user.loginName,
- clientUserLoginTime: this.user.loginTime,
- select: false,
- };
- const code = await decodeImageCode(
- fileInfo.frontOriginImgPath,
- this.ocrArea
- ).catch((err) => {
- console.error(err);
- logger.error(`03条码解析失败,${err}`);
- });
- fileInfo.studentCode = code || this.lastStudentCode;
- // 按照识别空自动绑定前一张code的规则,无论识别到的code是否合法,都应该作为最后一次识别的code
- // 否则,第一张进异常,后面空白条码页会自动进正常页面,对后续处理带来一定困扰
- if (fileInfo.studentCode) {
- this.lastStudentCode = fileInfo.studentCode;
- }
- fileInfo.id = `${fileInfo.studentCode || 0}-${randomCode(16)}`;
- const studentStage = this.scanStageList.find(
- (item) => item.studentCode === fileInfo.studentCode
- );
- if (studentStage) {
- studentStage.papers.push(fileInfo);
- continue;
- }
- if (fileInfo.studentCode) {
- const res = await getStudentInfo({
- examId: this.task.examId,
- courseCode: this.task.courseCode,
- studentCode: fileInfo.studentCode,
- }).catch(() => {});
- if (res) {
- fileInfo.studentName = res.studentName;
- this.scanStageList.push({
- id: res.id,
- studentCode: res.studentCode,
- studentName: res.studentName,
- courseCode: res.courseCode,
- courseName: res.courseName,
- teacher: res.teacher,
- teachClass: res.teachClass,
- collegeName: res.collegeName,
- majorName: res.majorName,
- className: res.className,
- score: res.score,
- remark: res.remark,
- select: false,
- papers: [fileInfo],
- });
- continue;
- }
- }
- const errorStudentStage = this.errorStageList.find(
- (item) => item.studentCode === fileInfo.studentCode
- );
- if (errorStudentStage) {
- errorStudentStage.papers.push(fileInfo);
- continue;
- }
- this.errorStageList.push({
- id: `none-${randomCode(16)}`,
- studentCode: fileInfo.studentCode,
- select: false,
- papers: [fileInfo],
- });
- }
- if (imageList.length) {
- const lastImg = imageList.pop();
- this.curPapers = [lastImg.frontFile, lastImg.versoFile];
- this.curPaperIndex = 0;
- this.setCurPaper(0);
- }
- },
- toSave() {
- if (this.errorStageList.length) {
- this.$message.error("还有异常数据未处理!");
- return;
- }
- if (!this.scanStageList.length) {
- this.$message.error("当前无要保存的数据!");
- return;
- }
- this.$refs.SelectBatchNoDialog.open();
- },
- async saveScanData(batchNo) {
- if (this.saving) return;
- this.saving = true;
- this.clearViewPapers();
- logger.info(`04-1开始保存数据`);
- try {
- let datas = this.scanStageList
- .map((item) => item.papers)
- .flat()
- .map((item) => {
- item.batchNo = batchNo;
- return item;
- });
- datas = await batchSaveImages(datas, this.task.courseCode);
- await db.batchSaveUploadInfo(datas);
- } catch (err) {
- console.error(err);
- logger.error(`04-1保存数据错误,${err}`);
- this.saving = false;
- this.$message.error("保存数据错误,请重新尝试!");
- return Promise.reject();
- }
- this.$message.success("保存成功!");
- this.saving = false;
- logger.info(`04-2保存数据成功`);
- this.clearFiles();
- this.initData();
- },
- // delay mode
- // 实时获取扫描图片
- async loopScaningFile() {
- this.clearSetTs();
- if (!this.looping) return;
- // const st = Date.now();
- await this.getScaningFile();
- // console.log(`耗时:${Date.now() - st}ms`);
- this.addSetTime(this.loopScaningFile, 1 * 1000);
- },
- stopLoopScaningFile() {
- this.clearSetTs();
- this.looping = false;
- },
- async getScaningFile() {
- const newScanFiles = getDirScanFile(this.GLOBAL.input);
- await this.stageScanImage(newScanFiles);
- },
- // table action
- toBind() {
- if (!this.selectList.length) return;
- this.$refs.ManualBindDialog.open();
- },
- bindConfirm(studentInfo) {
- if (this.isNormalTab) {
- this.normalBind(studentInfo);
- } else {
- this.errorBind(studentInfo);
- }
- this.clearViewPapers();
- this.$refs.scanResultTableRef.clearSelection();
- },
- normalBind(studentInfo) {
- const selectPaperIds = this.selectList
- .map((item) => item.papers.map((p) => p.id))
- .flat();
- let prevIndex = this.scanStageList.findIndex(
- (row) => row.id === this.selectList[0].id
- );
- // 删除选择的数据
- this.scanStageList.forEach((row) => {
- row.papers = row.papers.filter((p) => !selectPaperIds.includes(p.id));
- });
- this.scanStageList = this.scanStageList.filter(
- (row) => row.papers.length
- );
- // 绑定逻辑
- const preAddPapers = this.selectList
- .map((row) => {
- return row.papers.map((p) => {
- return {
- ...p,
- select: false,
- studentCode: studentInfo.studentCode,
- studentName: studentInfo.studentName,
- id: `${studentInfo.studentCode}-${randomCode(16)}`,
- };
- });
- })
- .flat();
- const stageStudent = this.scanStageList.find(
- (row) => row.studentCode === studentInfo.studentCode
- );
- if (stageStudent) {
- stageStudent.papers.push(...preAddPapers);
- return;
- }
- prevIndex = Math.max(
- 1,
- Math.min(prevIndex + 1, this.scanStageList.length)
- );
- this.scanStageList.splice(prevIndex, 0, {
- ...studentInfo,
- select: false,
- papers: preAddPapers,
- });
- },
- errorBind(studentInfo) {
- const selectPaperIds = this.selectList
- .map((item) => item.papers.map((p) => p.id))
- .flat();
- // 删除选择的数据
- this.errorStageList.forEach((row) => {
- row.papers = row.papers.filter((p) => !selectPaperIds.includes(p.id));
- });
- this.errorStageList = this.errorStageList.filter(
- (row) => row.papers.length
- );
- // 绑定逻辑
- const preAddPapers = this.selectList
- .map((row) => {
- return row.papers.map((p) => {
- return {
- ...p,
- select: false,
- studentCode: studentInfo.studentCode,
- studentName: studentInfo.studentName,
- id: `${studentInfo.studentCode}-${randomCode(16)}`,
- };
- });
- })
- .flat();
- const stageStudent = this.scanStageList.find(
- (row) => row.studentCode === studentInfo.studentCode
- );
- if (stageStudent) {
- stageStudent.papers.push(...preAddPapers);
- return;
- }
- this.scanStageList.push({
- ...studentInfo,
- select: false,
- papers: preAddPapers,
- });
- },
- async clearStage() {
- const name = this.isNormalTab ? "正常" : "异常";
- const res = await this.$confirm(
- `确定要清空所有【${name}】数据吗?`,
- "警告",
- {
- type: "warning",
- }
- ).catch(() => {});
- if (res !== "confirm") return;
- if (this.isNormalTab) {
- const files = this.scanStageList
- .map((row) =>
- row.papers.map((p) => [p.frontOriginImgPath, p.versoOriginImgPath])
- )
- .flat(2);
- deleteFiles(files);
- this.scanStageList = [];
- } else {
- const files = this.errorStageList
- .map((row) =>
- row.papers.map((p) => [p.frontOriginImgPath, p.versoOriginImgPath])
- )
- .flat(2);
- deleteFiles(files);
- this.errorStageList = [];
- }
- this.curPapers = [];
- this.curPaperIndex = 0;
- this.curPaper = { url: "" };
- logger.info(`99数据清空`);
- this.$message.success("数据已清空!");
- },
- selectChange(data) {
- this.selectList = data;
- },
- deletePaperHandle(deletedPapers) {
- this.curPapers = this.curPapers.filter((p) => !deletedPapers.includes(p));
- if (!this.curPapers.length) {
- this.curPaperIndex = 0;
- this.curPaper = { url: "" };
- return;
- }
- if (deletedPapers.includes(this.curPaper.url)) {
- this.curPaperIndex = 0;
- this.setCurPaper(0);
- return;
- }
- this.curPaperIndex = this.curPapers.indexOf(this.curPaper.url);
- },
- // image-preview
- rowClickHandle({ curPapers, curPaperIndex }) {
- this.curPapers = curPapers;
- this.curPaperIndex = curPaperIndex;
- this.setCurPaper(curPaperIndex);
- },
- clearViewPapers() {
- this.curPapers = [];
- this.curPaperIndex = 0;
- this.curPaper = { url: "" };
- },
- setCurPaper(index) {
- this.curPaper = { url: `file:///${this.curPapers[index]}` };
- },
- toPrevPaper() {
- if (this.curPaperIndex <= 0) {
- this.$message.error("没有上一页了");
- return;
- }
- this.setCurPaper(--this.curPaperIndex);
- },
- toNextPaper() {
- if (this.curPaperIndex >= this.curPapers.length - 1) {
- this.$message.error("没有下一页了");
- return;
- }
- this.setCurPaper(++this.curPaperIndex);
- },
- },
- };
- </script>
|