|
@@ -0,0 +1,545 @@
|
|
|
+<template>
|
|
|
+ <div class="paper-export part-box">
|
|
|
+ <div class="part-head">
|
|
|
+ <h2>分数图片导出</h2>
|
|
|
+ </div>
|
|
|
+ <div class="export-filter">
|
|
|
+ <Form :model="scoreFilter" ref="scoreForm" :label-width="140">
|
|
|
+ <FormItem label="工作文件夹:">
|
|
|
+ <Select
|
|
|
+ v-model="scoreFilter.examId"
|
|
|
+ placeholder="请选择工作文件夹"
|
|
|
+ style="width: 400px"
|
|
|
+ :disabled="taskRunning"
|
|
|
+ >
|
|
|
+ <Option
|
|
|
+ v-for="item in exams"
|
|
|
+ :key="item.id"
|
|
|
+ :value="item.id"
|
|
|
+ :label="item.name"
|
|
|
+ ></Option>
|
|
|
+ </Select>
|
|
|
+ </FormItem>
|
|
|
+ <FormItem prop="imageType" label="图片类型:">
|
|
|
+ <RadioGroup v-model="scoreFilter.imageType">
|
|
|
+ <Radio
|
|
|
+ size="large"
|
|
|
+ v-for="(val, key) in IMAGE_TYPE"
|
|
|
+ :key="key"
|
|
|
+ :label="key * 1"
|
|
|
+ :disabled="taskRunning"
|
|
|
+ >{{ val }}</Radio
|
|
|
+ >
|
|
|
+ </RadioGroup>
|
|
|
+ </FormItem>
|
|
|
+ <FormItem prop="isWatermark" label="是否分数水印:">
|
|
|
+ <RadioGroup v-model="scoreFilter.isWatermark">
|
|
|
+ <Radio
|
|
|
+ size="large"
|
|
|
+ v-for="(val, key) in BOOLEAN_TYPE"
|
|
|
+ :key="key"
|
|
|
+ :label="key * 1"
|
|
|
+ :disabled="taskRunning"
|
|
|
+ >{{ val }}</Radio
|
|
|
+ >
|
|
|
+ </RadioGroup>
|
|
|
+ </FormItem>
|
|
|
+ <FormItem prop="isResume" label="是否续传:">
|
|
|
+ <RadioGroup v-model="scoreFilter.isResume">
|
|
|
+ <Radio
|
|
|
+ size="large"
|
|
|
+ v-for="(val, key) in BOOLEAN_TYPE"
|
|
|
+ :key="key"
|
|
|
+ :label="key * 1"
|
|
|
+ :disabled="taskRunning"
|
|
|
+ >{{ val }}</Radio
|
|
|
+ >
|
|
|
+ </RadioGroup>
|
|
|
+ </FormItem>
|
|
|
+ <FormItem prop="nameRule" label="图片命名规则:">
|
|
|
+ <RadioGroup v-model="scoreFilter.nameRule">
|
|
|
+ <Radio
|
|
|
+ size="large"
|
|
|
+ v-for="(val, key) in EXPORT_IMAGE_NAME_TYPE"
|
|
|
+ :key="key"
|
|
|
+ :label="key * 1"
|
|
|
+ :disabled="taskRunning"
|
|
|
+ >{{ val }}</Radio
|
|
|
+ >
|
|
|
+ </RadioGroup>
|
|
|
+ </FormItem>
|
|
|
+ <FormItem label="分数段:">
|
|
|
+ <InputNumber
|
|
|
+ v-model="scoreFilter.startScore"
|
|
|
+ :min="1"
|
|
|
+ :max="1000"
|
|
|
+ :precision="0"
|
|
|
+ placeholder="输入起始分数"
|
|
|
+ style="width: 120px"
|
|
|
+ :disabled="taskRunning"
|
|
|
+ clearable
|
|
|
+ ></InputNumber>
|
|
|
+ <span style="margin: 0 10px;"></span>
|
|
|
+ <InputNumber
|
|
|
+ v-model="scoreFilter.endScore"
|
|
|
+ :min="scoreFilter.startScore"
|
|
|
+ :max="1000"
|
|
|
+ :precision="0"
|
|
|
+ :active-change="false"
|
|
|
+ placeholder="输入终止分数"
|
|
|
+ style="width: 120px"
|
|
|
+ :disabled="taskRunning"
|
|
|
+ clearable
|
|
|
+ ></InputNumber>
|
|
|
+ </FormItem>
|
|
|
+ <FormItem label="本地保存路径:">
|
|
|
+ <Input
|
|
|
+ v-model="scoreFilter.outputDir"
|
|
|
+ readonly
|
|
|
+ style="width: 600px;margin-right: 15px;"
|
|
|
+ ></Input>
|
|
|
+ <Button
|
|
|
+ class="export-paper-btn"
|
|
|
+ type="primary"
|
|
|
+ @click="seleteOutpath"
|
|
|
+ :disabled="taskRunning"
|
|
|
+ >选择</Button
|
|
|
+ >
|
|
|
+ </FormItem>
|
|
|
+ <FormItem label="转存规则:">
|
|
|
+ <Checkbox
|
|
|
+ v-for="item in SAVE_PATH_RULES"
|
|
|
+ :key="item.val"
|
|
|
+ v-model="item.seleted"
|
|
|
+ :disabled="item.disabled || taskRunning"
|
|
|
+ >{{ item.name }}</Checkbox
|
|
|
+ >
|
|
|
+ </FormItem>
|
|
|
+ <FormItem>
|
|
|
+ <Button
|
|
|
+ class="export-paper-btn"
|
|
|
+ type="primary"
|
|
|
+ @click="toExportScore"
|
|
|
+ :disabled="taskRunning"
|
|
|
+ >开始导出</Button
|
|
|
+ >
|
|
|
+ </FormItem>
|
|
|
+ </Form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 任务信息 -->
|
|
|
+ <div class="part-head">
|
|
|
+ <h2>任务信息</h2>
|
|
|
+ </div>
|
|
|
+ <div class="export-task">
|
|
|
+ <Form :label-width="140">
|
|
|
+ <FormItem label="任务进度:">
|
|
|
+ <Progress :percent="progress" />
|
|
|
+ </FormItem>
|
|
|
+ <FormItem label="任务数量:">
|
|
|
+ <p>{{ finishedTaskCount }} / {{ taskTotalCount }}</p>
|
|
|
+ </FormItem>
|
|
|
+ <FormItem label="开始时间:">
|
|
|
+ <p>{{ startProcessTimeFormat }}</p>
|
|
|
+ </FormItem>
|
|
|
+ <template v-if="!taskOver">
|
|
|
+ <FormItem label="预计剩余时间:">
|
|
|
+ <p>{{ predictTime }}</p>
|
|
|
+ </FormItem>
|
|
|
+ <FormItem label="当前下载图片:">
|
|
|
+ <p>
|
|
|
+ {{ curDownloadTask.studentName }} -
|
|
|
+ {{ curDownloadTask.examNumber }}
|
|
|
+ </p>
|
|
|
+ </FormItem>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <FormItem label="结束时间:">
|
|
|
+ <p>{{ endProcessTimeFormat }}</p>
|
|
|
+ </FormItem>
|
|
|
+ <FormItem label="任务总耗时:">
|
|
|
+ <p>{{ processDuritionTime }}</p>
|
|
|
+ </FormItem>
|
|
|
+ </template>
|
|
|
+ </Form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import {
|
|
|
+ IMAGE_TYPE,
|
|
|
+ EXPORT_IMAGE_NAME_TYPE,
|
|
|
+ BOOLEAN_TYPE
|
|
|
+} from "@/constants/enumerate";
|
|
|
+import { formatDate, timeNumberToText, qsParams } from "@/plugins/utils";
|
|
|
+import {
|
|
|
+ workList,
|
|
|
+ getUnfinishTask,
|
|
|
+ exportDataList,
|
|
|
+ addExportTask,
|
|
|
+ addExportTaskDetail,
|
|
|
+ getDownloadTaskList,
|
|
|
+ updateTaskFinish,
|
|
|
+ updateDownloadTaskDownload,
|
|
|
+ getDownloadTaskCount
|
|
|
+} from "../api";
|
|
|
+import { downloadFile, downloadServerFile } from "@/plugins/imageOcr";
|
|
|
+
|
|
|
+const remote = require("electron").remote;
|
|
|
+const path = require("path");
|
|
|
+
|
|
|
+let undownloadTaskList = [];
|
|
|
+
|
|
|
+const SAVE_PATH_RULES = [
|
|
|
+ {
|
|
|
+ name: "考区",
|
|
|
+ val: "${areaName}${areaCode}",
|
|
|
+ seleted: false,
|
|
|
+ disabled: false
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "学校",
|
|
|
+ val: "${school}",
|
|
|
+ seleted: false,
|
|
|
+ disabled: false
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "科目",
|
|
|
+ val: "${subjectName}",
|
|
|
+ seleted: true,
|
|
|
+ disabled: true
|
|
|
+ }
|
|
|
+];
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: "paper-export",
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ scoreFilter: {
|
|
|
+ examId: null,
|
|
|
+ imageType: 1,
|
|
|
+ isWatermark: 1,
|
|
|
+ isResume: 1,
|
|
|
+ nameRule: 1,
|
|
|
+ startScore: null,
|
|
|
+ endScore: null,
|
|
|
+ outputDir: "",
|
|
|
+ savePathRule: ""
|
|
|
+ },
|
|
|
+ exams: [],
|
|
|
+ IMAGE_TYPE,
|
|
|
+ EXPORT_IMAGE_NAME_TYPE,
|
|
|
+ BOOLEAN_TYPE,
|
|
|
+ SAVE_PATH_RULES,
|
|
|
+ // task
|
|
|
+ taskRunning: false,
|
|
|
+ taskOver: false,
|
|
|
+ pageSize: 100,
|
|
|
+ curExportTask: {},
|
|
|
+ curDownloadTask: {
|
|
|
+ path: "",
|
|
|
+ filename: ""
|
|
|
+ },
|
|
|
+ taskTotalCount: 0,
|
|
|
+ finishedTaskCount: 0,
|
|
|
+ predictTime: "",
|
|
|
+ startProcessTime: 0,
|
|
|
+ startDownloadTime: 0,
|
|
|
+ endProcessTime: 0,
|
|
|
+ processDuritionTime: "",
|
|
|
+ taskStarttime: 0,
|
|
|
+ alertTips: {
|
|
|
+ type: "info",
|
|
|
+ message: "准备执行任务"
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ progress() {
|
|
|
+ return this.taskTotalCount
|
|
|
+ ? Math.floor((this.finishedTaskCount * 100) / this.taskTotalCount)
|
|
|
+ : 0;
|
|
|
+ },
|
|
|
+ startProcessTimeFormat() {
|
|
|
+ return this.startProcessTime
|
|
|
+ ? formatDate("YYYY-MM-DD HH:mm:ss", new Date(this.startProcessTime))
|
|
|
+ : "-";
|
|
|
+ },
|
|
|
+ endProcessTimeFormat() {
|
|
|
+ return this.endProcessTime
|
|
|
+ ? formatDate("YYYY-MM-DD HH:mm:ss", new Date(this.endProcessTime))
|
|
|
+ : "-";
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.scoreFilter.examId = this.$ls.get("user", { examId: "" }).examId;
|
|
|
+ this.getExams();
|
|
|
+ this.checkHasUnfinishTask();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ async text() {
|
|
|
+ await downloadFile({
|
|
|
+ url:
|
|
|
+ "https://msmk-prod.oss-cn-beijing.aliyuncs.com/ms-test/upload/sheet/51/SC/1/1901040084.jpg",
|
|
|
+ outpath: "E:\\meishuMS\\output\\1901040084.jpg",
|
|
|
+ score: 88,
|
|
|
+ isWatermark: true
|
|
|
+ });
|
|
|
+ },
|
|
|
+ async getExams() {
|
|
|
+ const data = await workList();
|
|
|
+ this.exams = data.map(item => {
|
|
|
+ return {
|
|
|
+ id: item.id,
|
|
|
+ name: item.name,
|
|
|
+ active: item.active,
|
|
|
+ createdOn: item.createdOn
|
|
|
+ };
|
|
|
+ });
|
|
|
+ },
|
|
|
+ async checkHasUnfinishTask() {
|
|
|
+ const task = await getUnfinishTask().catch(() => {});
|
|
|
+ if (!task) return;
|
|
|
+
|
|
|
+ this.$Modal.confirm({
|
|
|
+ content: "当前还有未完成的导出任务,是否继续进行?",
|
|
|
+ onOk: () => {
|
|
|
+ console.log(task);
|
|
|
+ this.curExportTask = task;
|
|
|
+ this.scoreFilter = { ...task };
|
|
|
+ this.scoreFilter.examId = task.examId * 1;
|
|
|
+ const saveRules = this.scoreFilter.savePathRule.split("/");
|
|
|
+ this.SAVE_PATH_RULES.map(item => {
|
|
|
+ item.seleted = saveRules.includes(item.val);
|
|
|
+ });
|
|
|
+ this.startProcessTime = Date.now();
|
|
|
+ this.startDownload();
|
|
|
+ },
|
|
|
+ onCancel: async () => {
|
|
|
+ await updateTaskFinish(task.id);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ async toExportScore() {
|
|
|
+ if (this.taskRunning) return;
|
|
|
+
|
|
|
+ if (!this.scoreFilter.outputDir) {
|
|
|
+ this.$Message.error("请选择本地保存路径");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.taskRunning = true;
|
|
|
+
|
|
|
+ this.scoreFilter.savePathRule = this.SAVE_PATH_RULES.filter(
|
|
|
+ item => item.seleted
|
|
|
+ )
|
|
|
+ .map(item => item.val)
|
|
|
+ .join("/");
|
|
|
+
|
|
|
+ this.startProcessTime = Date.now();
|
|
|
+ this.alertTips = {
|
|
|
+ type: "info",
|
|
|
+ message: "正在下载任务数据"
|
|
|
+ };
|
|
|
+ const exam = this.exams.find(item => item.id === this.scoreFilter.examId);
|
|
|
+ // 保存本地导出任务
|
|
|
+ this.curExportTask = {
|
|
|
+ ...this.scoreFilter,
|
|
|
+ workId: this.scoreFilter.examId,
|
|
|
+ examName: exam.name,
|
|
|
+ id: ""
|
|
|
+ };
|
|
|
+ const taskId = await addExportTask(this.curExportTask).catch(() => {});
|
|
|
+
|
|
|
+ if (!taskId) {
|
|
|
+ console.log("本地添加任务失败!");
|
|
|
+ this.taskRunning = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.curExportTask.id = taskId;
|
|
|
+
|
|
|
+ // 获取导出任务详情数据
|
|
|
+ const dataList = await exportDataList(this.curExportTask);
|
|
|
+ // 记录导出任务详情数据
|
|
|
+ const len = dataList.length;
|
|
|
+ const pageCount = Math.ceil(len / this.pageSize);
|
|
|
+ for (let i = 0; i < pageCount; i++) {
|
|
|
+ const startNo = i * this.pageSize;
|
|
|
+ const taskList = dataList
|
|
|
+ .slice(startNo, startNo + this.pageSize)
|
|
|
+ .map((item, index) => {
|
|
|
+ const serialNo = startNo + index + 1;
|
|
|
+ const filename =
|
|
|
+ this.curExportTask.nameRule === 1
|
|
|
+ ? `${item.examNumber}-${item.studentName}`
|
|
|
+ : serialNo;
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ examId: this.curExportTask.examId,
|
|
|
+ examName: this.curExportTask.examName,
|
|
|
+ taskId,
|
|
|
+ serialNo,
|
|
|
+ filename: filename + path.extname(item.url)
|
|
|
+ };
|
|
|
+ });
|
|
|
+ await addExportTaskDetail(taskList).catch(error => {
|
|
|
+ console.log(error);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ // 开启下载
|
|
|
+ this.startDownload();
|
|
|
+ },
|
|
|
+ seleteOutpath() {
|
|
|
+ remote.dialog.showOpenDialog(
|
|
|
+ {
|
|
|
+ title: "选择导出目录",
|
|
|
+ properties: ["openDirectory"]
|
|
|
+ },
|
|
|
+ folderPaths => {
|
|
|
+ if (folderPaths && folderPaths[0]) {
|
|
|
+ this.scoreFilter.outputDir = folderPaths[0];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ },
|
|
|
+ initTask() {
|
|
|
+ this.taskRunning = false;
|
|
|
+ this.taskOver = false;
|
|
|
+ this.curDownloadTask = {};
|
|
|
+ this.taskTotalCount = 0;
|
|
|
+ this.finishedTaskCount = 0;
|
|
|
+ this.predictTime = "";
|
|
|
+ this.alertTips = {
|
|
|
+ type: "info",
|
|
|
+ message: "准备执行任务"
|
|
|
+ };
|
|
|
+ this.startProcessTime = 0;
|
|
|
+ this.endProcessTime = 0;
|
|
|
+ this.processDuritionTime = "";
|
|
|
+ },
|
|
|
+ async startDownload() {
|
|
|
+ this.alertTips = {
|
|
|
+ type: "info",
|
|
|
+ message: "正在下载图片"
|
|
|
+ };
|
|
|
+ const finishedTaskCount = await getDownloadTaskCount({
|
|
|
+ taskId: this.curExportTask.id,
|
|
|
+ isDownload: 1
|
|
|
+ }).catch(() => {});
|
|
|
+ this.finishedTaskCount = finishedTaskCount || 0;
|
|
|
+
|
|
|
+ this.taskRunning = true;
|
|
|
+ undownloadTaskList = await getDownloadTaskList(
|
|
|
+ this.curExportTask.id
|
|
|
+ ).catch(() => {
|
|
|
+ this.taskRunning = false;
|
|
|
+ });
|
|
|
+ if (!undownloadTaskList || !undownloadTaskList.length) return;
|
|
|
+
|
|
|
+ this.taskTotalCount = this.finishedTaskCount + undownloadTaskList.length;
|
|
|
+ this.startDownloadTime = Date.now();
|
|
|
+ this.runTask();
|
|
|
+ },
|
|
|
+ async runTask() {
|
|
|
+ if (!this.taskRunning) return;
|
|
|
+
|
|
|
+ if (!undownloadTaskList.length) {
|
|
|
+ this.stopTask();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.curDownloadTask = undownloadTaskList.shift();
|
|
|
+ this.curDownloadTask.isWatermark = this.curExportTask.isWatermark;
|
|
|
+ this.curDownloadTask.outpath = this.getDownloadFilePath(
|
|
|
+ this.curExportTask,
|
|
|
+ this.curDownloadTask
|
|
|
+ );
|
|
|
+
|
|
|
+ const result = await this.download(1);
|
|
|
+
|
|
|
+ if (result) {
|
|
|
+ console.log(result);
|
|
|
+ await updateDownloadTaskDownload(this.curDownloadTask.id).catch(err => {
|
|
|
+ console.log(err);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ this.curTaskOver();
|
|
|
+ },
|
|
|
+ async download(isLocalDownload) {
|
|
|
+ // 本地加水印
|
|
|
+ if (isLocalDownload) {
|
|
|
+ return await downloadFile(this.curDownloadTask).catch(() => {
|
|
|
+ console.log(`${this.curDownloadTask.studentName}下载失败!`);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ // 服务端打水印
|
|
|
+ const domain =
|
|
|
+ process.env.NODE_ENV === "production"
|
|
|
+ ? this.GLOBAL.domain
|
|
|
+ : "https://msmk.qmth.com.cn/";
|
|
|
+ this.curDownloadTask.url =
|
|
|
+ domain +
|
|
|
+ "/api/file/image/exportScorePicturesFromCollect?" +
|
|
|
+ qsParams({
|
|
|
+ workId: this.curExportTask.examId,
|
|
|
+ imageType: this.curExportTask.imageType,
|
|
|
+ isWatermark: this.curExportTask.isWatermark,
|
|
|
+ nameRule: this.curExportTask.nameRule,
|
|
|
+ studentId: this.curDownloadTask.studentId,
|
|
|
+ studentName: this.curDownloadTask.studentName,
|
|
|
+ subject: this.curDownloadTask.subject,
|
|
|
+ areaCode: this.curDownloadTask.areaCode,
|
|
|
+ examNumber: this.curDownloadTask.examNumber,
|
|
|
+ score: this.curDownloadTask.score,
|
|
|
+ idx: this.curDownloadTask.serialNo
|
|
|
+ });
|
|
|
+ this.curDownloadTask.isWatermark = 0;
|
|
|
+ const result = await downloadServerFile({
|
|
|
+ url: encodeURI(this.curDownloadTask.url),
|
|
|
+ outpath: this.curDownloadTask.outpath
|
|
|
+ }).catch(() => {
|
|
|
+ console.log(`${this.curDownloadTask.studentName}下载失败!`);
|
|
|
+ });
|
|
|
+
|
|
|
+ return result;
|
|
|
+ },
|
|
|
+ curTaskOver() {
|
|
|
+ // 准备开启下一个下载任务
|
|
|
+ this.finishedTaskCount++;
|
|
|
+ this.getPredictTime();
|
|
|
+ setTimeout(() => {
|
|
|
+ this.runTask();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ getDownloadFilePath(task, paper) {
|
|
|
+ let fileDir = task.savePathRule;
|
|
|
+ Object.keys(paper).map(k => {
|
|
|
+ fileDir = fileDir.replace("${" + k + "}", paper[k]);
|
|
|
+ });
|
|
|
+ return path.join(task.outputDir, fileDir, paper.filename);
|
|
|
+ },
|
|
|
+ async stopTask() {
|
|
|
+ undownloadTaskList = [];
|
|
|
+ this.taskRunning = false;
|
|
|
+ this.taskOver = true;
|
|
|
+ this.endProcessTime = Date.now();
|
|
|
+ this.processDuritionTime = timeNumberToText(
|
|
|
+ this.endProcessTime - this.startProcessTime
|
|
|
+ );
|
|
|
+ this.alertTips = {
|
|
|
+ type: "success",
|
|
|
+ message: "任务执行完毕!"
|
|
|
+ };
|
|
|
+ await updateTaskFinish(this.curExportTask.id).catch(() => {});
|
|
|
+ },
|
|
|
+ getPredictTime() {
|
|
|
+ const hasUsedTime = Date.now() - this.startDownloadTime;
|
|
|
+ const predictRunTime =
|
|
|
+ (hasUsedTime / this.finishedTaskCount) *
|
|
|
+ (this.taskTotalCount - this.finishedTaskCount);
|
|
|
+ this.predictTime = timeNumberToText(predictRunTime);
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|