|
|
@@ -0,0 +1,1030 @@
|
|
|
+<template>
|
|
|
+ <ReportModule
|
|
|
+ :showTitle="false"
|
|
|
+ :showDescribe="false"
|
|
|
+ :showPrintBtn="false"
|
|
|
+ :showExportBtn="false"
|
|
|
+ :showTablePage="false"
|
|
|
+ tableOrChart="qita"
|
|
|
+ >
|
|
|
+ <template #title_left>
|
|
|
+ <span>重点页面表格批量下载中心</span>
|
|
|
+ <span>请勾选需要下载的分析表格,选择批量导出</span>
|
|
|
+ </template>
|
|
|
+ <template #title_right>
|
|
|
+ <el-button class="button_editor" @click="showDownloadDialog">
|
|
|
+ 下载记录
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ class="button_task_select"
|
|
|
+ :loading="exportLoading"
|
|
|
+ @click="batchExport('报表下载')"
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ v-if="!exportLoading"
|
|
|
+ src="@/assets/icon/export_icon_white.webp"
|
|
|
+ />批量导出
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ <template #module_table_chart>
|
|
|
+ <div
|
|
|
+ class="module_content"
|
|
|
+ v-loading="menuLoading"
|
|
|
+ element-loading-text="加载中,请稍后……"
|
|
|
+ element-loading-spinner="el-icon-loading"
|
|
|
+ element-loading-background="#ffffff"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="item_content"
|
|
|
+ v-for="module in excelModuleList"
|
|
|
+ :key="module.menuInfo"
|
|
|
+ >
|
|
|
+ <div class="item_content_title">
|
|
|
+ <div class="content_title_left">
|
|
|
+ <img src="@/assets/icon/download_icon.webp" />
|
|
|
+ <span>{{ module.menuValue }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="content_title_right" @click="singleExport(module)">
|
|
|
+ 下载
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="item_content_list">
|
|
|
+ <el-checkbox
|
|
|
+ :indeterminate="module.isIndeterminate"
|
|
|
+ v-model="module.checkAll"
|
|
|
+ @change="(val) => handleCheckAllChange(val, module)"
|
|
|
+ >全选</el-checkbox
|
|
|
+ >
|
|
|
+ <el-checkbox-group
|
|
|
+ v-model="module.excelModuleList"
|
|
|
+ @change="(val) => handleexcelModuleListChange(val, module)"
|
|
|
+ >
|
|
|
+ <el-checkbox
|
|
|
+ v-for="item in module.excelsList"
|
|
|
+ :label="item.value"
|
|
|
+ :key="item.value"
|
|
|
+ >{{ item.label }}</el-checkbox
|
|
|
+ >
|
|
|
+ </el-checkbox-group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </ReportModule>
|
|
|
+ <div class="page_dialog">
|
|
|
+ <el-dialog
|
|
|
+ :title="segmentData.title"
|
|
|
+ center
|
|
|
+ v-model="segmentData.see"
|
|
|
+ width="600px"
|
|
|
+ @close="CloseDownloadDialog"
|
|
|
+ >
|
|
|
+ <div class="dialog_center">
|
|
|
+ <div class="dialog_description">{{ segmentData.description }}</div>
|
|
|
+ <div
|
|
|
+ class="dialog_content"
|
|
|
+ v-loading="loading"
|
|
|
+ :element-loading-text="loadingText"
|
|
|
+ element-loading-spinner="el-icon-loading"
|
|
|
+ element-loading-background="#ffffff"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="list_item"
|
|
|
+ v-for="(item, index) in examDownloadList"
|
|
|
+ :key="`${item.createTime}_${index}`"
|
|
|
+ >
|
|
|
+ <div class="list_item_left">
|
|
|
+ <img src="@/assets/icon/download_excel_icon.webp" />
|
|
|
+ <div class="excel_info">
|
|
|
+ <span class="excel_title">{{ item.taskName }}</span>
|
|
|
+ <el-progress
|
|
|
+ :percentage="item.progress || 0"
|
|
|
+ :stroke-width="7"
|
|
|
+ color="#2E64FA"
|
|
|
+ define-back-color="#E2E6F0"
|
|
|
+ text-color="#333333"
|
|
|
+ ></el-progress>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="list_item_right">
|
|
|
+ <span class="download_time">{{ item.createTime }}</span>
|
|
|
+ <span
|
|
|
+ :class="[
|
|
|
+ 'download_button',
|
|
|
+ { failed: item.generationStatus == 2 },
|
|
|
+ ]"
|
|
|
+ @click="
|
|
|
+ handleDownloadClick(
|
|
|
+ item.generationStatus,
|
|
|
+ item.id,
|
|
|
+ item.filePath,
|
|
|
+ item.fileName,
|
|
|
+ )
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <template v-if="item.generationStatus == 0">取消下载</template>
|
|
|
+ <template v-else-if="item.generationStatus == 2"
|
|
|
+ >下载失败</template
|
|
|
+ >
|
|
|
+ <template v-else>下载</template>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import {
|
|
|
+ ref,
|
|
|
+ reactive,
|
|
|
+ computed,
|
|
|
+ watch,
|
|
|
+ onBeforeUnmount,
|
|
|
+ getCurrentInstance,
|
|
|
+} from "vue";
|
|
|
+import { useStore } from "vuex";
|
|
|
+import { useAnalysisStore } from "@/store/analysis";
|
|
|
+import { ElMessage, ElMessageBox } from "element-plus";
|
|
|
+import ReportModule from "@/components/ReportModule.vue";
|
|
|
+
|
|
|
+const { proxy } = getCurrentInstance();
|
|
|
+const api = proxy.$api; // 全局挂载的 API 对象
|
|
|
+const store = useStore();
|
|
|
+const analysisStore = useAnalysisStore();
|
|
|
+// 分析报告公共参数变量
|
|
|
+const filterObject = computed(() => analysisStore.filterObject);
|
|
|
+console.log(filterObject.value,98999923)
|
|
|
+
|
|
|
+// 批量导出loading
|
|
|
+const exportLoading = ref(false);
|
|
|
+// 所有模块
|
|
|
+const excelModuleList = ref([]);
|
|
|
+// 弹框显示/隐藏
|
|
|
+const segmentData = reactive({
|
|
|
+ see: false,
|
|
|
+ title: "",
|
|
|
+ description: "",
|
|
|
+ isLoading: false,
|
|
|
+});
|
|
|
+// 下载loading
|
|
|
+const loading = ref(false);
|
|
|
+const loadingText = ref("下载中...");
|
|
|
+// 用于存储轮询定时器
|
|
|
+let downloadTimer = null;
|
|
|
+// 下载记录列表
|
|
|
+const examDownloadList = ref([]);
|
|
|
+// 请求标识符,用于防止快速切换导致的数据累积
|
|
|
+let currentRequestId = 0;
|
|
|
+// 模块列表loading
|
|
|
+const menuLoading = ref(false);
|
|
|
+// 对标考试id
|
|
|
+const lastExamId = computed(() => store.state.report.lastExamRadioId);
|
|
|
+// 多次考试任务对比ID,不包含当前任务ID
|
|
|
+const contrastExamIds = computed(() => store.state.report.lastExamSelectIds);
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => filterObject.value,
|
|
|
+ () => {
|
|
|
+ // 初始化模块列表
|
|
|
+ excelModuleList.value = [];
|
|
|
+ // getExcelModuleList();
|
|
|
+ },
|
|
|
+ { immediate: true, deep: true }
|
|
|
+);
|
|
|
+
|
|
|
+// 获取Excel模块列表
|
|
|
+async function getExcelModuleList() {
|
|
|
+ // 1. 每次调用生成一个新的 requestId
|
|
|
+ const requestId = ++currentRequestId;
|
|
|
+ // 辅助函数:检查当前请求是否已过期(被新的请求覆盖)
|
|
|
+ const isExpired = () => requestId !== currentRequestId;
|
|
|
+
|
|
|
+ menuLoading.value = true;
|
|
|
+ try {
|
|
|
+ const transcriptData = await transcriptModule();
|
|
|
+ if (isExpired()) return; // 如果已过期,直接终止
|
|
|
+ const levelData = levelDistributionModule();
|
|
|
+ excelModuleList.value.push(transcriptData, levelData);
|
|
|
+ if (filterObject.value.examLevel == 1) {
|
|
|
+ if (filterObject.value.schoolLevel != 2) {
|
|
|
+ const schoolContrastData = await schoolContrastModule();
|
|
|
+ if (isExpired()) return; // 如果已过期,直接终止
|
|
|
+ excelModuleList.value.push(schoolContrastData);
|
|
|
+ }
|
|
|
+ if (filterObject.value.subjectGroupType == 1) {
|
|
|
+ const classContrastData = await classContrastModule();
|
|
|
+ if (isExpired()) return; // 如果已过期,直接终止
|
|
|
+ const groupGAnalysisData = await groupGAnalysisModule();
|
|
|
+ if (isExpired()) return; // 如果已过期,直接终止
|
|
|
+ excelModuleList.value.push(
|
|
|
+ subjectContrastModule(),
|
|
|
+ classContrastData,
|
|
|
+ subjectQualityModule(),
|
|
|
+ groupGAnalysisData,
|
|
|
+ quartileModule(),
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ const classContrastData = await classContrastModule();
|
|
|
+ if (isExpired()) return; // 如果已过期,直接终止
|
|
|
+ const groupGAnalysisData = await groupGAnalysisModule();
|
|
|
+ if (isExpired()) return; // 如果已过期,直接终止
|
|
|
+ const paperAnalysisData = await paperAnalysisModule();
|
|
|
+ if (isExpired()) return; // 如果已过期,直接终止
|
|
|
+ excelModuleList.value.push(
|
|
|
+ classContrastData,
|
|
|
+ paperAnalysisData,
|
|
|
+ groupGAnalysisData,
|
|
|
+ );
|
|
|
+ excelModuleList.value.push(quartileModule());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (filterObject.value.subjectGroupType == 1) {
|
|
|
+ const classContrastData = await classContrastModule();
|
|
|
+ if (isExpired()) return; // 如果已过期,直接终止
|
|
|
+ const groupGAnalysisData = await groupGAnalysisModule();
|
|
|
+ if (isExpired()) return; // 如果已过期,直接终止
|
|
|
+ excelModuleList.value.push(
|
|
|
+ subjectContrastModule(),
|
|
|
+ classContrastData,
|
|
|
+ subjectQualityModule(),
|
|
|
+ groupGAnalysisData,
|
|
|
+ quartileModule(),
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ const classContrastData = await classContrastModule();
|
|
|
+ if (isExpired()) return; // 如果已过期,直接终止
|
|
|
+ const groupGAnalysisData = await groupGAnalysisModule();
|
|
|
+ if (isExpired()) return; // 如果已过期,直接终止
|
|
|
+ const paperAnalysisData = await paperAnalysisModule();
|
|
|
+ if (isExpired()) return; // 如果已过期,直接终止
|
|
|
+ excelModuleList.value.push(
|
|
|
+ classContrastData,
|
|
|
+ paperAnalysisData,
|
|
|
+ groupGAnalysisData,
|
|
|
+ );
|
|
|
+ excelModuleList.value.push(quartileModule());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error("获取模块列表异常:", error);
|
|
|
+ } finally {
|
|
|
+ // 2. 只有当前请求是最新请求时,才关闭 loading
|
|
|
+ // 如果请求已过期,说明有更新的请求正在进行,loading 应继续保持
|
|
|
+ if (!isExpired()) {
|
|
|
+ menuLoading.value = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+// 成绩单模块
|
|
|
+async function transcriptModule() {
|
|
|
+ let excelModuleList = [],
|
|
|
+ excelsList = [];
|
|
|
+ if (filterObject.value.subjectGroupType === 1) {
|
|
|
+ //总分
|
|
|
+ excelsList.push({
|
|
|
+ label: "成绩单",
|
|
|
+ value: "studentTranscript100",
|
|
|
+ });
|
|
|
+ excelModuleList.push("studentTranscript100");
|
|
|
+ } else {
|
|
|
+ const res = await api.reportSchool.checkQuestionExist(
|
|
|
+ filterObject.value,
|
|
|
+ );
|
|
|
+ if (res.code === 200) {
|
|
|
+ const list = res.data || [];
|
|
|
+ list.forEach((item) => {
|
|
|
+ if (item.value == 100) {
|
|
|
+ excelModuleList.push("studentTranscript100");
|
|
|
+ }
|
|
|
+ if (item.value == 0) {
|
|
|
+ excelModuleList.push("studentTranscript0");
|
|
|
+ }
|
|
|
+ excelsList.push({
|
|
|
+ label: `${item.label}${
|
|
|
+ item.value == 100 || item.value == 0 ? "" : "得分"
|
|
|
+ }`,
|
|
|
+ value: `studentTranscript${item.value}`,
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ menuInfo: "transcript",
|
|
|
+ menuValue: "成绩单",
|
|
|
+ checkAll: excelModuleList.length == excelsList.length, //全选
|
|
|
+ excelModuleList: excelModuleList, //选中的模块
|
|
|
+ excelsList: excelsList, //所有模块List
|
|
|
+ isIndeterminate:
|
|
|
+ excelModuleList.length < excelsList.length &&
|
|
|
+ excelModuleList.length > 0, //半选
|
|
|
+ };
|
|
|
+}
|
|
|
+// 水平分布模块
|
|
|
+function levelDistributionModule() {
|
|
|
+ //判断是否有赋分数据
|
|
|
+ let excelsList = [
|
|
|
+ { label: "分数段", value: "scoreSegment" },
|
|
|
+ { label: "一分段", value: "oneScoreOneSegment" },
|
|
|
+ { label: "名次段", value: "rankSegment" },
|
|
|
+ { label: "N%名", value: "nRankSegment" },
|
|
|
+ { label: "等级分布", value: "academicLevel" },
|
|
|
+ ];
|
|
|
+ if (
|
|
|
+ store.state.report.filterData[3].list.length > 1 &&
|
|
|
+ !filterObject.value.subjectGroupType
|
|
|
+ ) {
|
|
|
+ excelsList.push({ label: "赋分分布", value: "rateScore" });
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ menuInfo: "levelDistribution",
|
|
|
+ menuValue: "水平分布",
|
|
|
+ checkAll: false, //全选
|
|
|
+ excelModuleList: ["scoreSegment"], //选中的模块
|
|
|
+ excelsList: excelsList, //所有模块List
|
|
|
+ isIndeterminate: true, //半选
|
|
|
+ };
|
|
|
+}
|
|
|
+// 学校对比模块
|
|
|
+async function schoolContrastModule() {
|
|
|
+ let excelsList = [];
|
|
|
+ let obj = {
|
|
|
+ label: "学校平均分+各率分析表",
|
|
|
+ value: "schoolContrast0",
|
|
|
+ };
|
|
|
+ if (filterObject.value.subjectGroupType === 1) {
|
|
|
+ excelsList.push(obj);
|
|
|
+ } else {
|
|
|
+ if (filterObject.value.scoreType == 1) {
|
|
|
+ const res = await api.reportSchool.getsubjectTiteType({
|
|
|
+ examId: filterObject.value.examId,
|
|
|
+ subjectId: filterObject.value.subjectId,
|
|
|
+ });
|
|
|
+ if (res.code === 200) {
|
|
|
+ const list = res.data || [];
|
|
|
+ list.forEach((item) => {
|
|
|
+ excelsList.push({
|
|
|
+ label: `学校${item.titleName}平均分+各率分析表`,
|
|
|
+ value: `schoolContrast${item.titleType}${
|
|
|
+ item.titleType == 5 ? "paperTypeName" + item.titleName : ""
|
|
|
+ }`,
|
|
|
+ });
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ excelsList.push(obj);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ excelsList.push(obj);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const excelModuleList =
|
|
|
+ excelsList.length > 0 ? [excelsList[0].value] : [];
|
|
|
+ return {
|
|
|
+ menuInfo: "schoolContrast",
|
|
|
+ menuValue: "学校对比",
|
|
|
+ checkAll: excelModuleList.length == excelsList.length, //全选
|
|
|
+ excelModuleList: excelModuleList, //选中的模块
|
|
|
+ excelsList: excelsList, //所有模块List
|
|
|
+ isIndeterminate:
|
|
|
+ excelModuleList.length < excelsList.length &&
|
|
|
+ excelModuleList.length > 0, //半选
|
|
|
+ };
|
|
|
+}
|
|
|
+// 学科对比模块
|
|
|
+function subjectContrastModule() {
|
|
|
+ return {
|
|
|
+ menuInfo: "subjectContrast",
|
|
|
+ menuValue: "学科对比",
|
|
|
+ checkAll: true, //全选
|
|
|
+ excelModuleList: ["subjectContrast", "groupSubjectContrast"], //选中的模块
|
|
|
+ excelsList: [
|
|
|
+ //所有模块List
|
|
|
+ {
|
|
|
+ label: "学科平均分+各率分析表",
|
|
|
+ value: "subjectContrast",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "总分大表",
|
|
|
+ value: "groupSubjectContrast",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ isIndeterminate: false, //半选
|
|
|
+ };
|
|
|
+}
|
|
|
+// 班级对比模块
|
|
|
+async function classContrastModule() {
|
|
|
+ let excelsList = [];
|
|
|
+ let obj = {
|
|
|
+ label: "班级平均分+各率分析表",
|
|
|
+ value: "classContrast0",
|
|
|
+ };
|
|
|
+ if (filterObject.value.subjectGroupType === 1) {
|
|
|
+ excelsList.push(obj);
|
|
|
+ } else {
|
|
|
+ if (filterObject.value.scoreType == 1) {
|
|
|
+ const res = await api.reportSchool.getsubjectTiteType({
|
|
|
+ examId: filterObject.value.examId,
|
|
|
+ subjectId: filterObject.value.subjectId,
|
|
|
+ });
|
|
|
+ if (res.code === 200) {
|
|
|
+ const list = res.data || [];
|
|
|
+ list.forEach((item) => {
|
|
|
+ excelsList.push({
|
|
|
+ label: `班级${item.titleName}平均分+各率分析表`,
|
|
|
+ value: `classContrast${item.titleType}${
|
|
|
+ item.titleType == 5 ? "paperTypeName" + item.titleName : ""
|
|
|
+ }`,
|
|
|
+ });
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ excelsList.push(obj);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ excelsList.push(obj);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const excelModuleList =
|
|
|
+ excelsList.length > 0 ? [excelsList[0].value] : [];
|
|
|
+ return {
|
|
|
+ menuInfo: "classContrast",
|
|
|
+ menuValue: "班级对比",
|
|
|
+ checkAll: excelModuleList.length == excelsList.length, //全选
|
|
|
+ excelModuleList: excelModuleList, //选中的模块
|
|
|
+ excelsList: excelsList, //所有模块List
|
|
|
+ isIndeterminate:
|
|
|
+ excelModuleList.length < excelsList.length &&
|
|
|
+ excelModuleList.length > 0, //半选
|
|
|
+ };
|
|
|
+}
|
|
|
+// 学科质量模块
|
|
|
+function subjectQualityModule() {
|
|
|
+ return {
|
|
|
+ menuInfo: "subjectQuality",
|
|
|
+ menuValue: "学科质量",
|
|
|
+ checkAll: false, //全选
|
|
|
+ excelModuleList: [], //选中的模块
|
|
|
+ excelsList: [
|
|
|
+ //所有模块List
|
|
|
+ {
|
|
|
+ label: "学科质量分析表",
|
|
|
+ value: "subjectQuality",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ isIndeterminate: false, //半选
|
|
|
+ };
|
|
|
+}
|
|
|
+// G组分析模块
|
|
|
+async function groupGAnalysisModule() {
|
|
|
+ let excelsList = [];
|
|
|
+ if (filterObject.value.subjectGroupType === 1) {
|
|
|
+ excelsList.push(
|
|
|
+ {
|
|
|
+ label: "G组科目分析表",
|
|
|
+ value: "gGroupSubject",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "G组科目学生分布分析表",
|
|
|
+ value: "gGroupSubjectStudent",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: `G组${
|
|
|
+ filterObject.value.schoolLevel == 2 ? "班级" : "学校"
|
|
|
+ }分析表`,
|
|
|
+ value:
|
|
|
+ filterObject.value.schoolLevel == 2
|
|
|
+ ? "gGroupClass"
|
|
|
+ : "gGroupSchool",
|
|
|
+ },
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ excelsList.push(
|
|
|
+ {
|
|
|
+ label: `G组${
|
|
|
+ filterObject.value.schoolLevel == 2 ? "班级" : "学校"
|
|
|
+ }分析表`,
|
|
|
+ value:
|
|
|
+ filterObject.value.schoolLevel == 2
|
|
|
+ ? "gGroupClass"
|
|
|
+ : "gGroupSchool",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "G组小题分析表",
|
|
|
+ value: "gGroupQuestionAnalysis",
|
|
|
+ },
|
|
|
+ );
|
|
|
+ const res = await api.reportSchool.queryQuestionGroup({
|
|
|
+ subjectId: filterObject.value.subjectId,
|
|
|
+ });
|
|
|
+ if (res.code == 200) {
|
|
|
+ const resData = res.data || [];
|
|
|
+ const data = resData.map((item) => {
|
|
|
+ return {
|
|
|
+ label: `G组${item.name}表`,
|
|
|
+ value: `questionGroupAnalysis${item.code}`,
|
|
|
+ };
|
|
|
+ });
|
|
|
+ excelsList.push(...data);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ menuInfo: "groupGAnalysis",
|
|
|
+ menuValue: "G组分析",
|
|
|
+ checkAll: false, //全选
|
|
|
+ excelModuleList: [], //选中的模块
|
|
|
+ excelsList: excelsList, //所有模块List
|
|
|
+ isIndeterminate: false, //半选
|
|
|
+ };
|
|
|
+}
|
|
|
+// 四分位数模块
|
|
|
+function quartileModule() {
|
|
|
+ return {
|
|
|
+ menuInfo: "quartile",
|
|
|
+ menuValue: "四分位数",
|
|
|
+ checkAll: false, //全选
|
|
|
+ excelModuleList: [], //选中的模块
|
|
|
+ excelsList: [
|
|
|
+ //所有模块List
|
|
|
+ {
|
|
|
+ label: "四分位分析表",
|
|
|
+ value: "queryQuartile",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ isIndeterminate: false, //半选
|
|
|
+ };
|
|
|
+}
|
|
|
+// 试卷分析模块
|
|
|
+async function paperAnalysisModule() {
|
|
|
+ let excelsList = [
|
|
|
+ {
|
|
|
+ label: "小题分析表",
|
|
|
+ value: "questionAnalysis",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "题型分析表",
|
|
|
+ value: "questionGroupAnalysis11",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "错题分析表",
|
|
|
+ value: "errorQuestionAnalysis",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "客观题分析表",
|
|
|
+ value: "selectQuestionAnalysis",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "命题分析表",
|
|
|
+ value: "propositionAnalysis",
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ let excelModuleList = [
|
|
|
+ "questionAnalysis",
|
|
|
+ "errorQuestionAnalysis",
|
|
|
+ "selectQuestionAnalysis",
|
|
|
+ ];
|
|
|
+ const res = await api.reportSchool.queryQuestionGroup({
|
|
|
+ subjectId: filterObject.value.subjectId,
|
|
|
+ });
|
|
|
+ if (res.code == 200) {
|
|
|
+ const resData = res.data || [];
|
|
|
+ let data = [];
|
|
|
+ resData.forEach((item) => {
|
|
|
+ data.push({
|
|
|
+ label: `${item.name}表`,
|
|
|
+ value: `questionGroupAnalysis${item.code}`,
|
|
|
+ });
|
|
|
+ excelModuleList.push(`questionGroupAnalysis${item.code}`);
|
|
|
+ });
|
|
|
+ excelsList.splice(1, 0, ...data);
|
|
|
+ }
|
|
|
+ if (filterObject.value.schoolLevel == 2) {
|
|
|
+ excelsList.splice(excelsList.length - 2, 0, {
|
|
|
+ label: "学生作答明细表",
|
|
|
+ value: "studentAnsweringDetail",
|
|
|
+ });
|
|
|
+ excelModuleList.push("studentAnsweringDetail");
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ menuInfo: "paperAnalysis",
|
|
|
+ menuValue: "试卷分析",
|
|
|
+ checkAll: excelModuleList.length == excelsList.length, //全选
|
|
|
+ excelModuleList: excelModuleList, //选中的模块
|
|
|
+ excelsList: excelsList, //所有模块List
|
|
|
+ isIndeterminate:
|
|
|
+ excelModuleList.length < excelsList.length &&
|
|
|
+ excelModuleList.length > 0, //半选
|
|
|
+ };
|
|
|
+}
|
|
|
+function handleCheckAllChange(val, module) {
|
|
|
+ module.excelModuleList = val
|
|
|
+ ? module.excelsList.map((item) => item.value)
|
|
|
+ : [];
|
|
|
+ module.isIndeterminate = false;
|
|
|
+}
|
|
|
+function handleexcelModuleListChange(value, module) {
|
|
|
+ let checkedCount = value.length;
|
|
|
+ module.checkAll = checkedCount === module.excelsList.length;
|
|
|
+ module.isIndeterminate =
|
|
|
+ checkedCount > 0 && checkedCount < module.excelsList.length;
|
|
|
+}
|
|
|
+// 批量导出
|
|
|
+function batchExport() {
|
|
|
+ // 判断是否所有模块的 excelModuleList 都为空
|
|
|
+ const hasSelected = excelModuleList.value.some(
|
|
|
+ (item) => item.excelModuleList && item.excelModuleList.length > 0,
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!hasSelected) {
|
|
|
+ ElMessage.warning("请至少勾选一个需要下载的表格!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ segmentData.title = "批量下载";
|
|
|
+ segmentData.description = "关闭后可在下载记录中查询";
|
|
|
+ exportLoading.value = true;
|
|
|
+ segmentData.see = true;
|
|
|
+ loading.value = true;
|
|
|
+ const exportList = excelModuleList.value.map((item) => ({
|
|
|
+ menuInfo: item.menuInfo,
|
|
|
+ menuValue: item.menuValue,
|
|
|
+ excelModuleList: item.excelModuleList,
|
|
|
+ }));
|
|
|
+ downloadExamFileList(exportList);
|
|
|
+}
|
|
|
+// 单个下载
|
|
|
+function singleExport(module) {
|
|
|
+ if (module.excelModuleList.length == 0) {
|
|
|
+ ElMessage.warning("请至少勾选一个需要下载的表格!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ segmentData.title = "下载";
|
|
|
+ segmentData.description = "关闭后可在下载记录中查询";
|
|
|
+ segmentData.see = true;
|
|
|
+ loading.value = true;
|
|
|
+ const exportList = [
|
|
|
+ {
|
|
|
+ menuInfo: module.menuInfo,
|
|
|
+ menuValue: module.menuValue,
|
|
|
+ excelModuleList: module.excelModuleList,
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ downloadExamFileList(exportList);
|
|
|
+}
|
|
|
+// 导出选择的excel模块,生成打包任务
|
|
|
+function downloadExamFileList(excelModuleList) {
|
|
|
+ let schoolName = null;
|
|
|
+ if (filterObject.value.schoolLevel == 0) {
|
|
|
+ schoolName = "联校";
|
|
|
+ } else if (filterObject.value.schoolLevel == 1) {
|
|
|
+ schoolName = filterObject.value.schoolGroupName;
|
|
|
+ } else {
|
|
|
+ schoolName = filterObject.value.schoolName;
|
|
|
+ }
|
|
|
+ api.reportSchool
|
|
|
+ .downloadExamFileList({
|
|
|
+ excelModuleList: excelModuleList,
|
|
|
+ examCommonSelectDTO: {
|
|
|
+ ...filterObject.value,
|
|
|
+ schoolName: schoolName,
|
|
|
+ lastExamId: lastExamId.value,
|
|
|
+ contrastExamIds: contrastExamIds.value,
|
|
|
+ pageParam: {
|
|
|
+ pageSize: 10,
|
|
|
+ pageNum: 1,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ })
|
|
|
+ .then((res) => {
|
|
|
+ if (res.code == 200) {
|
|
|
+ ElMessage.success("下载成功");
|
|
|
+ // 任务提交成功后,开始每10秒轮询一次
|
|
|
+ startPolling();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || "下载失败");
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ exportLoading.value = false;
|
|
|
+ loading.value = false;
|
|
|
+ });
|
|
|
+}
|
|
|
+// 启动轮询
|
|
|
+function startPolling() {
|
|
|
+ // 防止重复创建定时器
|
|
|
+ if (downloadTimer) return;
|
|
|
+ // 立即执行一次,然后每10秒执行一次
|
|
|
+ findByExamIdAndSchoolId();
|
|
|
+ downloadTimer = setInterval(() => {
|
|
|
+ findByExamIdAndSchoolId();
|
|
|
+ }, 5000);
|
|
|
+}
|
|
|
+// 停止轮询
|
|
|
+function stopPolling() {
|
|
|
+ if (downloadTimer) {
|
|
|
+ clearInterval(downloadTimer);
|
|
|
+ downloadTimer = null;
|
|
|
+ }
|
|
|
+}
|
|
|
+// 根据考试ID和学校ID查询下载任务列表
|
|
|
+function findByExamIdAndSchoolId() {
|
|
|
+ api.reportSchool
|
|
|
+ .findByExamIdAndSchoolId(filterObject.value.examId)
|
|
|
+ .then((res) => {
|
|
|
+ if (res.code == 200) {
|
|
|
+ examDownloadList.value = res.data || [];
|
|
|
+ // 当列表不为空时,检查是否所有任务都已完成
|
|
|
+ if (examDownloadList.value.length > 0) {
|
|
|
+ const isAllCompleted = examDownloadList.value.every((item) => {
|
|
|
+ // 进度达到100,或者任务状态为失败(2),均视为任务结束
|
|
|
+ return item.progress >= 100 || item.generationStatus == 2;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 如果所有任务都结束,则停止轮询
|
|
|
+ if (isAllCompleted) {
|
|
|
+ stopPolling();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ examDownloadList.value = [];
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+// 显示下载记录弹框
|
|
|
+function showDownloadDialog() {
|
|
|
+ segmentData.title = "下载记录";
|
|
|
+ segmentData.description =
|
|
|
+ "以下为最近7天下载的数据,根据数据量大小,某些需要生成10分钟左右,请您耐心等待";
|
|
|
+ segmentData.see = true;
|
|
|
+ // 任务提交成功后,开始每10秒轮询一次
|
|
|
+ startPolling();
|
|
|
+}
|
|
|
+// 关闭下载记录弹框
|
|
|
+function CloseDownloadDialog() {
|
|
|
+ segmentData.see = false;
|
|
|
+ stopPolling();
|
|
|
+}
|
|
|
+// 处理下载点击事件
|
|
|
+function handleDownloadClick(generationStatus, id, filePath, fileName) {
|
|
|
+ if (generationStatus == 2) return;
|
|
|
+ if (generationStatus == 0) {
|
|
|
+ // 取消下载
|
|
|
+ ElMessageBox.confirm("此操作将永久删除该文件, 是否继续?", "提示", {
|
|
|
+ confirmButtonText: "确定",
|
|
|
+ cancelButtonText: "取消",
|
|
|
+ type: "warning",
|
|
|
+ }).then(() => {
|
|
|
+ api.reportSchool.downloadDeleteById(id).then((res) => {
|
|
|
+ if (res.code == 200) {
|
|
|
+ ElMessage.success("取消下载成功");
|
|
|
+ // 刷新下载任务列表
|
|
|
+ findByExamIdAndSchoolId();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || "取消下载失败");
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 下载成功,执行文件下载
|
|
|
+ if (!filePath) {
|
|
|
+ ElMessage.warning("文件地址不存在,请稍后再试!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const newFileName = fileName.replace(/\s/g, ""); //清除字符串所有空格
|
|
|
+ const finalFileName =
|
|
|
+ newFileName && newFileName.includes(".")
|
|
|
+ ? newFileName
|
|
|
+ : `${newFileName || "报表下载"}.zip`;
|
|
|
+
|
|
|
+ // 使用 fetch 获取文件流,解决跨域导致 download 属性失效的问题
|
|
|
+ fetch(filePath)
|
|
|
+ .then((res) => {
|
|
|
+ if (!res.ok) throw new Error("网络请求失败");
|
|
|
+ return res.blob();
|
|
|
+ })
|
|
|
+ .then((blob) => {
|
|
|
+ // 创建 Blob URL
|
|
|
+ const url = window.URL.createObjectURL(blob);
|
|
|
+ const link = document.createElement("a");
|
|
|
+ link.href = url;
|
|
|
+ link.download = finalFileName; // 此时 download 属性对 Blob URL 必定生效
|
|
|
+ link.style.display = "none";
|
|
|
+
|
|
|
+ document.body.appendChild(link);
|
|
|
+ link.click();
|
|
|
+ document.body.removeChild(link);
|
|
|
+
|
|
|
+ // 释放 Blob 对象,释放内存
|
|
|
+ window.URL.revokeObjectURL(url);
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ // console.error("下载失败:", err);
|
|
|
+ ElMessage.error("文件下载失败!");
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ stopPolling();
|
|
|
+});
|
|
|
+</script>
|
|
|
+<style lang="scss" scoped>
|
|
|
+:deep(.module_title) {
|
|
|
+ padding: 20px 0;
|
|
|
+
|
|
|
+ .title_left {
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: flex-start !important;
|
|
|
+
|
|
|
+ span {
|
|
|
+ &:nth-child(1) {
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #333333;
|
|
|
+ line-height: 22px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:nth-child(2) {
|
|
|
+ margin-top: 8px;
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999999;
|
|
|
+ line-height: 17px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .title_right {
|
|
|
+ .button_task_select {
|
|
|
+ height: 36px;
|
|
|
+ background-color: #2e64fa;
|
|
|
+ color: #ffffff;
|
|
|
+
|
|
|
+ :deep(.el-icon-loading) {
|
|
|
+ color: #ffffff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.module_content {
|
|
|
+ justify-content: flex-start !important;
|
|
|
+ padding-bottom: 20px;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 20px;
|
|
|
+ min-height: 400px;
|
|
|
+
|
|
|
+ .item_content {
|
|
|
+ width: calc((100% - 80px) / 5);
|
|
|
+ padding: 16px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ background: linear-gradient(170deg, #ebf2ff 10%, #ffffff 50%, #ffffff 100%);
|
|
|
+ border-radius: 10px;
|
|
|
+ border: 1px solid #e1e2e7;
|
|
|
+
|
|
|
+ .item_content_title {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding-bottom: 12px;
|
|
|
+ border-bottom: 1px solid #e1e2e7;
|
|
|
+
|
|
|
+ .content_title_left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ img {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ margin-right: 4px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ span {
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #333333;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .content_title_right {
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #2e64fa;
|
|
|
+ cursor: pointer;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .item_content_list {
|
|
|
+ margin-top: 12px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ :deep(.el-checkbox-group) {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ .el-checkbox {
|
|
|
+ margin-top: 12px;
|
|
|
+ margin-right: 0;
|
|
|
+ white-space: normal;
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start; /* 关键:让复选框和文本顶部对齐 */
|
|
|
+ .el-checkbox__label {
|
|
|
+ display: block; /* 允许文本换行 */
|
|
|
+ word-break: break-word; /* 长文本自动换行 */
|
|
|
+ line-height: 1.2; /* 调整行高,避免拥挤 */
|
|
|
+ color: #333333;
|
|
|
+ font-weight: 400;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+// 下载记录弹框
|
|
|
+.dialog_description {
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #000000;
|
|
|
+}
|
|
|
+.dialog_content {
|
|
|
+ width: 100%;
|
|
|
+ height: 390px;
|
|
|
+ overflow-y: auto;
|
|
|
+ margin: 16px 0 20px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ .list_item {
|
|
|
+ display: flex;
|
|
|
+ width: 100%;
|
|
|
+ padding: 16px;
|
|
|
+ margin-top: 20px;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ box-sizing: border-box;
|
|
|
+ justify-content: space-between;
|
|
|
+ &:first-child {
|
|
|
+ margin-top: 0;
|
|
|
+ }
|
|
|
+ .list_item_left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex: 1;
|
|
|
+ margin-right: 38px;
|
|
|
+ img {
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ margin-right: 10px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+ .excel_info {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ .excel_title {
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333333;
|
|
|
+ line-height: 20px;
|
|
|
+ }
|
|
|
+ :deep(.el-progress) {
|
|
|
+ margin-top: 8px;
|
|
|
+ .el-progress__text {
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .list_item_right {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-end;
|
|
|
+ justify-content: center;
|
|
|
+ .download_time {
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999999;
|
|
|
+ line-height: 17px;
|
|
|
+ }
|
|
|
+ .download_button {
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #2e64fa;
|
|
|
+ cursor: pointer;
|
|
|
+ margin-top: 11px;
|
|
|
+ &.failed {
|
|
|
+ color: #f56c6c;
|
|
|
+ cursor: default;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|