Эх сурвалжийг харах

Merge branch 'master' of https://gogs.k12100.net/root/AIScoring

dengshaobo 3 долоо хоног өмнө
parent
commit
c43ddf5bbb

+ 23 - 0
src/api/analysis.ts

@@ -269,3 +269,26 @@ export const publicExport = (data: any): Promise<ApiResponse> => {
     responseType: "blob",
   });
 };
+//-----------------------------------------------------------------报表下载-----------------------------------------------------
+// 导出选择的excel模块,生成打包任务
+export const downloadAiExamFileList = (data: any): Promise<ApiResponse> => {
+  return request({
+    url: "/api/v1/teach/oss/download_exam_file/download_ai_exam_file_list",
+    method: "post",
+    data,
+  });
+};
+//根据考试ID和学校ID查询下载任务列表
+export const findByAIExamIdAndSchoolId = (examId: any): Promise<ApiResponse> => {
+  return request({
+    url: `/api/v1/ai_analysis/findByAIExamIdAndSchoolId?examId=${examId}`,
+    method: "get",
+  });
+};
+//根据下载任务ID删除下载任务
+export const deleteAIDownloadById = (id: any): Promise<ApiResponse> => {
+   return request({
+    url: `/api/v1/ai_analysis/deleteAIDownloadById?id=${id}`,
+    method: "delete",
+  });
+};

+ 452 - 0
src/components/ErrorsTablePdf.vue

@@ -0,0 +1,452 @@
+<template>
+  <div class="report_module error">
+    <div class="area_page">
+      <div class="area_title">
+        <div class="page_num">第1页(共10页)</div>
+        <div class="title_text">
+          【{{ `${examName}错题名单(${className})` }}】
+        </div>
+        <div class="print_time">打印时间:{{ getNowTime() }}</div>
+      </div>
+    </div>
+    <div class="area_page print_page">
+      <div class="area_title">
+        <div class="page_num">第1页(共10页)</div>
+        <div class="title_text">
+          【{{ `${examName}错题名单(${className})` }}】
+        </div>
+        <div class="print_time">打印时间:{{ getNowTime() }}</div>
+      </div>
+      <div class="module_table table_42">
+        <el-table
+          :data="tablePageData"
+          border
+          stripe
+          row-key="questionId"
+          :span-method="SpanMethod"
+          :row-class-name="ErrorTableRowClassName"
+          :cell-class-name="ErrorTableCellClassName"
+        >
+          <el-table-column
+            prop="questionName"
+            label="小题名称"
+            width="60"
+            align="center"
+          />
+          <el-table-column
+            prop="questionType"
+            label="题型"
+            width="50"
+            align="center"
+          />
+          <el-table-column
+            prop="questionScore"
+            label="满分"
+            width="48"
+            align="center"
+          />
+          <el-table-column
+            prop="averageScore"
+            label="平均分"
+            width="48"
+            align="center"
+          />
+          <el-table-column
+            prop="scoreRate"
+            label="得分率"
+            width="50"
+            align="center"
+          />
+          <el-table-column
+            prop="studentCount"
+            label="答题人数"
+            width="55"
+            align="center"
+          />
+          <el-table-column
+            prop="errorCount"
+            label="错题人数"
+            width="55"
+            align="center"
+          />
+          <el-table-column
+            prop="answerValue"
+            label="正确答案"
+            width="55"
+            align="center"
+          >
+            <template #default="scope">
+              {{ scope.row.answerValue ?? "-" }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="errorDetail" label="错题详情" align="center">
+            <el-table-column
+              :align="key == 'registrationCodeList' ? 'left' : 'center'"
+              v-for="(key, index) in [
+                'name',
+                'rate',
+                'studentNum',
+                'registrationCodeList',
+              ]"
+              :key="key"
+              label=""
+              :prop="key"
+              :min-width="[28, 50, 30, 320][index]"
+            >
+              <template #default="{ row: org }">
+                <span v-if="key == 'registrationCodeList'">{{ org[key] }}</span>
+                <span v-else-if="key == 'rate'">{{
+                  org[key] == "-" ? "-" : `${org[key]}%`
+                }}</span>
+                <span v-else>{{ org[key] }}</span>
+              </template>
+            </el-table-column>
+          </el-table-column>
+        </el-table>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { getNowTime } from "@/utils/common";
+import { ref, nextTick, watch } from "vue";
+import { ElMessage } from "element-plus";
+import { jsPDF } from "jspdf";
+import domtoimage from "dom-to-image";
+// 定义数据类型
+interface TableItem {
+  rowKey: number | string;
+  questionName?: string;
+  questionType?: string;
+  answerValue?: string;
+  questionScore?: number | string;
+  averageScore?: number | string;
+  scoreRate?: number | string;
+  hiddenRatioStu?: boolean;
+  name?: string;
+  rate?: number | string;
+  studentNum?: number | string;
+  studentCount?: number | string;
+  registrationCodeList?: string;
+  hiddenRatio?: boolean;
+  hiddenStu?: boolean;
+}
+
+// 定义 Props
+const props = defineProps({
+  examName: {
+    type: String,
+    default: "",
+  },
+  tableData: {
+    type: Array,
+    default: () => [],
+  },
+  subjectName: {
+    type: String,
+    default: "",
+  },
+  className: {
+    type: String,
+    default: "",
+  },
+});
+const tablePageData = ref<TableItem[][]>([]);
+// 定义 Emits
+const emit = defineEmits(["PdfLoadEnd"]);
+
+// Refs & 常量
+const pageTitle = ref<HTMLElement | null>(null);
+const titleHeight = ref(80);
+const questionHeight = 30;
+const lineHeight = 16;
+const printPageHeight = 1205;
+
+// 表格分页逻辑
+const TablePagesData = () => {};
+
+// 下载 PDF (使用 dom-to-image)
+const DownloadPdf = async () => {
+  const elements = document.querySelectorAll(".page_report .print_page");
+  if (elements.length === 0) {
+    ElMessage.warning("未找到可导出的页面元素!");
+    emit("PdfLoadEnd");
+    return;
+  }
+
+  const pdf = new jsPDF("p", "pt", "a4");
+  const pdfWidth = pdf.internal.pageSize.getWidth();
+  const pdfHeight = pdf.internal.pageSize.getHeight();
+
+  for (const [pageIndex, element] of Array.from(elements).entries()) {
+    await nextTick();
+    if (element) {
+      const htmlElement = element as HTMLElement;
+      htmlElement.style.visibility = "visible";
+      htmlElement.offsetHeight;
+
+      await new Promise((resolve) => setTimeout(resolve, 100));
+
+      const dataUrl = await domtoimage.toJpeg(htmlElement, {
+        quality: 0.8,
+        width: htmlElement.scrollWidth * 2,
+        height: htmlElement.scrollHeight * 2,
+        canvasWidth: htmlElement.scrollWidth * 2,
+        canvasHeight: htmlElement.scrollHeight * 2,
+        style: {
+          transform: "scale(2)",
+          "transform-origin": "top left",
+          "background-color": "#ffffff",
+          overflow: "visible",
+          "white-space": "normal",
+          position: "relative",
+          visibility: "visible",
+        },
+        bgcolor: "#ffffff",
+      });
+
+      const imgProps = pdf.getImageProperties(dataUrl);
+      const imgWidth = imgProps.width;
+      const imgHeight = imgProps.height;
+
+      const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight);
+      const adjustWidth = imgWidth * ratio;
+      const adjustHeight = imgHeight * ratio;
+
+      const xPosition = (pdfWidth - adjustWidth) / 2;
+      const yPosition = (pdfHeight - adjustHeight) / 2;
+
+      if (pageIndex > 0) {
+        pdf.addPage();
+      }
+      pdf.addImage(
+        dataUrl,
+        "JPEG",
+        xPosition,
+        yPosition,
+        adjustWidth,
+        adjustHeight,
+      );
+    }
+  }
+
+  pdf.save(
+    `${props.examName}_${props.subjectName}_${props.className}错题名单.pdf`,
+  );
+  emit("PdfLoadEnd");
+};
+const LoadData = () => {
+  nextTick(() => {
+    const pageTitleHeight = pageTitle.value?.offsetHeight || 60;
+    titleHeight.value = pageTitleHeight + 20;
+    tablePageData.value = props.tableData;
+    TablePagesData();
+  });
+};
+// 错题分析  合并单元格
+const SpanMethod = ({ row, column }: any) => {
+  const props = [
+    "errorDetail",
+    "name",
+    "rate",
+    "studentNum",
+    "registrationCodeList",
+  ];
+  if (props.indexOf(column.property) == -1) {
+    if (row.rowspan) {
+      return {
+        rowspan: row.rowspan,
+        colspan: row.colspan,
+      };
+    } else {
+      return {
+        rowspan: 0,
+        colspan: 0,
+      };
+    }
+  }
+};
+
+// 定义行样式
+const ErrorTableRowClassName = ({ row }: any) => {
+  if (row.rowspanIndex % 2 === 0) {
+    return "row_color_FFFFFF";
+  } else {
+    return "row_color_FAFAFA";
+  }
+};
+
+// 设置单元格样式
+const ErrorTableCellClassName = ({ column }: any) => {
+  if (column.property == "registrationCodeList") {
+    return "white_space_normal";
+  } else {
+    return "";
+  }
+};
+// 监听筛选条件
+watch(
+  () => props.tableData,
+  async () => {
+    if (props.tableData.length) {
+      LoadData();
+    }
+  },
+  { deep: true },
+);
+
+// 暴露方法供父组件调用
+defineExpose({
+  DownloadPdf,
+});
+</script>
+
+<style lang="scss" scoped>
+.error {
+  width: 908px;
+  margin: 0 auto;
+  // position: absolute;
+  top: 0;
+  left: -9999px;
+  z-index: -10;
+
+  .area_page {
+    width: 908px;
+    margin-top: 20px;
+    padding: 10px !important;
+    background-color: #fff;
+    box-sizing: border-box;
+    overflow: hidden;
+
+    &.print_page {
+      height: 1285px;
+    }
+
+    .area_title {
+      width: 100%;
+      line-height: 30px;
+      display: flex;
+      gap: 5px;
+
+      .page_num {
+        font-size: 12px;
+        color: #000000;
+        flex-shrink: 0;
+      }
+
+      .print_time {
+        font-size: 12px;
+        color: #000000;
+        flex-shrink: 0;
+      }
+
+      .title_text {
+        font-weight: bold;
+        font-size: 16px;
+        color: #000000;
+        flex: 1;
+        text-align: center;
+      }
+    }
+
+    .module_table {
+      width: 100%;
+      box-sizing: border-box;
+      border-collapse: collapse;
+
+      :deep(.el-table) {
+        border-radius: 6px;
+        border-left: 0px;
+        border-right: 0px;
+        thead {
+          height: 23px !important;
+          font-size: 12px;
+        }
+        .el-table__header {
+          .cell {
+            font-size: 12px;
+          }
+        }
+
+        td {
+          &.is-left {
+            .cell {
+              text-align: left;
+            }
+          }
+        }
+
+        .cell {
+          font-size: 12px;
+          line-height: 23px;
+          display: block;
+          text-align: center;
+          white-space: nowrap;
+          padding: 0 !important;
+        }
+        .el-table__header-wrapper {
+          .el-table__header {
+            .is-group {
+              tr {
+                &:nth-child(2) {
+                  display: none;
+                }
+              }
+            }
+          }
+        }
+
+        .el-table__cell {
+          height: 23px !important;
+          &.white_space_normal {
+            .cell {
+              white-space: normal;
+              padding: 0 0 0 3px !important;
+            }
+          }
+        }
+
+        &.el-table--striped {
+          .el-table__body {
+            tr {
+              height: 23px;
+              font-size: 12px;
+              &.el-table__row--striped {
+                &.row_color_FFFFFF {
+                  td.el-table__cell {
+                    background: #ffffff;
+                  }
+                }
+              }
+            }
+          }
+        }
+
+        &.el-table--striped
+          .el-table__body
+          tr.el-table__row.row_color_FAFAFA
+          td.el-table__cell {
+          background: #fafafa;
+        }
+
+        &.el-table--striped
+          .el-table__body
+          tr.el-table__row--striped.row_color_FAFAFA
+          td.el-table__cell {
+          background: #fafafa;
+        }
+
+        &.el-table--enable-row-hover .el-table__body tr.row_color_FFFFFF > td {
+          background-color: transparent !important;
+        }
+      }
+
+      :deep(.table_row_blue) {
+        color: #2e64fa;
+        cursor: pointer;
+      }
+    }
+  }
+}
+</style>

+ 5 - 1
src/components/ScorePdf.vue

@@ -300,7 +300,7 @@ const ExportPdf = async () => {
 <body>
   <div class="bar">
     <span class="fname">${safeName}</span>
-    <button onclick="window.print()">打印PDF</button>
+    <button onclick="window.print()">下载PDF</button>
   </div>
   <iframe src="${url}"></iframe>
 </body>
@@ -328,6 +328,10 @@ defineExpose({
 .score_pdf {
   width: 908px;
   margin: 0 auto;
+  position: absolute;
+  top: 0;
+  left: -9999px;
+  z-index: -10;
 
   .area_page {
     width: 908px;

+ 1 - 1
src/styles/common.scss

@@ -2763,7 +2763,7 @@ body {
     }
   }
   //饿了么底部按钮样式覆盖
-  .dialog__footer {
+  .el-dialog__footer {
     padding: 0 20px;
     text-align: right;
     border-top: 1px solid #e6e6e6;

+ 9 - 0
src/views/analysis/errorAnalysis.vue

@@ -135,11 +135,20 @@
     :examName="getExamName"
     @PdfLoadEnd="PdfLoadEnd"
   />
+  <!-- <ErrorsTablePdf
+    ref="errPdfReport"
+    :tableData="state.tableData"
+    :className="analysisStore.filterObject.classGroupName"
+    :subjectName="analysisStore.filterObject.subjectName || ''"
+    :examName="getExamName"
+    @PdfLoadEnd="PdfLoadEnd"
+  /> -->
 </template>
 <script lang="ts" setup>
 import ReportModule from "@/components/ReportModule.vue";
 import { useAnalysisStore } from "@/store/analysis";
 import ErrorsPdf from "@/components/ErrorsPdf.vue";
+// import ErrorsTablePdf from "@/components/ErrorsTablePdf.vue";
 import { onMounted, reactive, computed, ref, watch } from "vue";
 import { errorQuestionAnalysis, exportErrorQuestion } from "@/api/analysis";
 import { loadingSvgIcon } from "@/utils/common";

+ 267 - 403
src/views/analysis/reportDownload.vue

@@ -52,16 +52,18 @@
             <el-checkbox
               :indeterminate="module.isIndeterminate"
               v-model="module.checkAll"
-              @change="(val) => handleCheckAllChange(val, module)"
+              @change="(val: boolean) => handleCheckAllChange(val, module)"
               >全选</el-checkbox
             >
             <el-checkbox-group
               v-model="module.excelModuleList"
-              @change="(val) => handleexcelModuleListChange(val, module)"
+              @change="
+                (val: string[]) => handleexcelModuleListChange(val, module)
+              "
             >
               <el-checkbox
                 v-for="item in module.excelsList"
-                :label="item.value"
+                :value="item.value"
                 :key="item.value"
                 >{{ item.label }}</el-checkbox
               >
@@ -136,34 +138,73 @@
   </div>
 </template>
 
-<script setup>
-import {
-  ref,
-  reactive,
-  computed,
-  watch,
-  onBeforeUnmount,
-  getCurrentInstance,
-} from "vue";
-import { useStore } from "vuex";
+<script lang="ts" setup>
+import { ref, reactive, computed, watch, onBeforeUnmount } from "vue";
 import { useAnalysisStore } from "@/store/analysis";
 import { ElMessage, ElMessageBox } from "element-plus";
 import ReportModule from "@/components/ReportModule.vue";
+import {
+  downloadAiExamFileList,
+  findByAIExamIdAndSchoolId,
+  deleteAIDownloadById,
+} from "@/api/analysis";
+
+// ===== 类型定义 =====
+/** 单个可下载表格项 */
+interface ExcelsItem {
+  label: string;
+  value: string;
+}
+
+/** 下载模块(一个模块包含多个可勾选表格) */
+interface ExcelModule {
+  menuInfo: string;
+  menuValue: string;
+  checkAll: boolean;
+  excelModuleList: string[];
+  excelsList: ExcelsItem[];
+  isIndeterminate: boolean;
+}
+
+/** 弹框数据 */
+interface SegmentData {
+  see: boolean;
+  title: string;
+  description: string;
+  isLoading: boolean;
+}
+
+/** 下载任务记录 */
+interface DownloadItem {
+  id: number | string;
+  taskName: string;
+  createTime: string;
+  progress: number;
+  generationStatus: number;
+  filePath: string;
+  fileName: string;
+}
+
+/** 导出任务参数 */
+interface ExportParams {
+  menuInfo: string;
+  menuValue: string;
+  excelModuleList: string[];
+}
 
-const { proxy } = getCurrentInstance();
-const api = proxy.$api; // 全局挂载的 API 对象
-const store = useStore();
 const analysisStore = useAnalysisStore();
 // 分析报告公共参数变量
 const filterObject = computed(() => analysisStore.filterObject);
-console.log(filterObject.value,98999923)
+const isShowGroupAnalysis = computed(
+  () => analysisStore?.isShowGroupAnalysis || false,
+); //是否显示分组分析
 
 // 批量导出loading
 const exportLoading = ref(false);
 // 所有模块
-const excelModuleList = ref([]);
+const excelModuleList = ref<ExcelModule[]>([]);
 // 弹框显示/隐藏
-const segmentData = reactive({
+const segmentData = reactive<SegmentData>({
   see: false,
   title: "",
   description: "",
@@ -173,26 +214,22 @@ const segmentData = reactive({
 const loading = ref(false);
 const loadingText = ref("下载中...");
 // 用于存储轮询定时器
-let downloadTimer = null;
+let downloadTimer: ReturnType<typeof setInterval> | null = null;
 // 下载记录列表
-const examDownloadList = ref([]);
+const examDownloadList = ref<DownloadItem[]>([]);
 // 请求标识符,用于防止快速切换导致的数据累积
 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();
+    getExcelModuleList();
   },
-  { immediate: true, deep: true }
+  { immediate: true, deep: true },
 );
 
 // 获取Excel模块列表
@@ -204,69 +241,35 @@ async function getExcelModuleList() {
 
   menuLoading.value = true;
   try {
-    const transcriptData = await transcriptModule();
+    const transcriptData = transcriptModule(); //成绩单
+    const errorQuestionAnalysisData = errorQuestionAnalysisModule(); //错题分析模块
+    const optionDetailData = optionDetailModule(); //选项明细表模块
+    const levelData = levelDistributionModule(); //水平分布模块
+    const classContrastData = classContrastModule(); //班级对比模块
+    const questionAnalysisData = questionAnalysisModule(); //小题分析模块
+    let groupAnalysisData: ExcelModule | null = null;
+    if (isShowGroupAnalysis.value) {
+      groupAnalysisData = groupAnalysisModule(); //分组分析模块
+    }
+    const selectQuestionAnalysisData = selectQuestionAnalysisModule(); //选项分析模块
+    const propositionAnalysisData = propositionAnalysisModule(); //命题分析模块
     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());
-      }
+    excelModuleList.value.push(
+      transcriptData,
+      errorQuestionAnalysisData,
+      optionDetailData,
+      levelData,
+      classContrastData,
+      questionAnalysisData,
+      selectQuestionAnalysisData,
+      propositionAnalysisData,
+    );
+    if (isShowGroupAnalysis.value && groupAnalysisData) {
+      excelModuleList.value.splice(
+        excelModuleList.value.length - 2,
+        0,
+        groupAnalysisData,
+      );
     }
   } catch (error) {
     console.error("获取模块列表异常:", error);
@@ -279,38 +282,14 @@ async function getExcelModuleList() {
   }
 }
 // 成绩单模块
-async function transcriptModule() {
-  let excelModuleList = [],
-    excelsList = [];
-  if (filterObject.value.subjectGroupType === 1) {
-    //总分
-    excelsList.push({
+function transcriptModule(): ExcelModule {
+  const excelModuleList = ["studentTranscript100"];
+  const excelsList: ExcelsItem[] = [
+    {
       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: "成绩单",
@@ -318,312 +297,173 @@ async function transcriptModule() {
     excelModuleList: excelModuleList, //选中的模块
     excelsList: excelsList, //所有模块List
     isIndeterminate:
-      excelModuleList.length < excelsList.length &&
-      excelModuleList.length > 0, //半选
+      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" },
+//错题分析模块
+function errorQuestionAnalysisModule(): ExcelModule {
+  const excelsList: ExcelsItem[] = [
+    {
+      label: "错题分析表",
+      value: "errorQuestionAnalysis",
+    },
   ];
-  if (
-    store.state.report.filterData[3].list.length > 1 &&
-    !filterObject.value.subjectGroupType
-  ) {
-    excelsList.push({ label: "赋分分布", value: "rateScore" });
-  }
+  const excelModuleList = ["errorQuestionAnalysis"];
   return {
-    menuInfo: "levelDistribution",
-    menuValue: "水平分布",
-    checkAll: false, //全选
-    excelModuleList: ["scoreSegment"], //选中的模块
+    menuInfo: "paperAnalysis",
+    menuValue: "错题分析",
+    checkAll: excelModuleList.length == excelsList.length, //全选
+    excelModuleList: excelModuleList, //选中的模块
     excelsList: excelsList, //所有模块List
-    isIndeterminate: true, //半选
+    isIndeterminate:
+      excelModuleList.length < excelsList.length && excelModuleList.length > 0, //半选
   };
 }
-// 学校对比模块
-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] : [];
+//选项明细模块
+function optionDetailModule(): ExcelModule {
+  const excelsList: ExcelsItem[] = [
+    {
+      label: "选项明细表",
+      value: "optionDetails",
+    },
+  ];
+  const excelModuleList = ["optionDetails"];
   return {
-    menuInfo: "schoolContrast",
-    menuValue: "学校对比",
+    menuInfo: "optionDetails",
+    menuValue: "选项明细表",
     checkAll: excelModuleList.length == excelsList.length, //全选
     excelModuleList: excelModuleList, //选中的模块
     excelsList: excelsList, //所有模块List
     isIndeterminate:
-      excelModuleList.length < excelsList.length &&
-      excelModuleList.length > 0, //半选
+      excelModuleList.length < excelsList.length && excelModuleList.length > 0, //半选
   };
 }
-// 学科对比模块
-function subjectContrastModule() {
+// 水平分布模块
+function levelDistributionModule(): ExcelModule {
+  //判断是否有赋分数据
+  let excelsList: ExcelsItem[] = [{ label: "分数段", value: "scoreSegment" }];
   return {
-    menuInfo: "subjectContrast",
-    menuValue: "学科对比",
+    menuInfo: "levelDistribution",
+    menuValue: "水平分布",
     checkAll: true, //全选
-    excelModuleList: ["subjectContrast", "groupSubjectContrast"], //选中的模块
-    excelsList: [
-      //所有模块List
-      {
-        label: "学科平均分+各率分析表",
-        value: "subjectContrast",
-      },
-      {
-        label: "总分大表",
-        value: "groupSubjectContrast",
-      },
-    ],
+    excelModuleList: ["scoreSegment"], //选中的模块
+    excelsList: excelsList, //所有模块List
     isIndeterminate: false, //半选
   };
 }
 // 班级对比模块
-async function classContrastModule() {
-  let excelsList = [];
-  let obj = {
+function classContrastModule(): ExcelModule {
+  let excelsList: ExcelsItem[] = [];
+  let obj: ExcelsItem = {
     label: "班级平均分+各率分析表",
     value: "classContrast0",
   };
-  if (filterObject.value.subjectGroupType === 1) {
-    excelsList.push(obj);
-  } else {
+  if (filterObject.value.subjectGroupType === 0) {
     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);
-      }
+      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, //选中的模块
+    checkAll: true, //全选
+    excelModuleList: ["classContrast0"], //选中的模块
     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);
-    }
-  }
+// 小题分析模块
+function questionAnalysisModule(): ExcelModule {
+  let excelsList: ExcelsItem[] = [
+    {
+      label: "小题分析表",
+      value: "questionAnalysis",
+    },
+  ];
+  let excelModuleList = ["questionAnalysis"];
   return {
-    menuInfo: "groupGAnalysis",
-    menuValue: "G组分析",
-    checkAll: false, //全选
-    excelModuleList: [], //选中的模块
+    menuInfo: "paperAnalysis",
+    menuValue: "小题分析",
+    checkAll: excelModuleList.length == excelsList.length, //全选
+    excelModuleList: excelModuleList, //选中的模块
     excelsList: excelsList, //所有模块List
-    isIndeterminate: false, //半选
+    isIndeterminate:
+      excelModuleList.length < excelsList.length && excelModuleList.length > 0, //半选
   };
 }
-// 四分位数模块
-function quartileModule() {
+// 分组分析模块
+function groupAnalysisModule(): ExcelModule {
+  let excelsList: ExcelsItem[] = [
+    {
+      label: "分组分析表",
+      value: "questionGroupAnalysis4",
+    },
+  ];
+  let excelModuleList = ["questionGroupAnalysis4"];
   return {
-    menuInfo: "quartile",
-    menuValue: "四分位数",
-    checkAll: false, //全选
-    excelModuleList: [], //选中的模块
-    excelsList: [
-      //所有模块List
-      {
-        label: "四分位分析表",
-        value: "queryQuartile",
-      },
-    ],
-    isIndeterminate: false, //半选
+    menuInfo: "paperAnalysis",
+    menuValue: "分组分析",
+    checkAll: excelModuleList.length == excelsList.length, //全选
+    excelModuleList: excelModuleList, //选中的模块
+    excelsList: excelsList, //所有模块List
+    isIndeterminate:
+      excelModuleList.length < excelsList.length && excelModuleList.length > 0, //半选
   };
 }
-// 试卷分析模块
-async function paperAnalysisModule() {
-  let excelsList = [
-    {
-      label: "小题分析表",
-      value: "questionAnalysis",
-    },
-    {
-      label: "题型分析表",
-      value: "questionGroupAnalysis11",
-    },
-    {
-      label: "错题分析表",
-      value: "errorQuestionAnalysis",
-    },
+// 选项分析模块
+function selectQuestionAnalysisModule(): ExcelModule {
+  let excelsList: ExcelsItem[] = [
     {
       label: "客观题分析表",
       value: "selectQuestionAnalysis",
     },
+  ];
+  let excelModuleList = ["selectQuestionAnalysis"];
+  return {
+    menuInfo: "paperAnalysis",
+    menuValue: "选项分析",
+    checkAll: excelModuleList.length == excelsList.length, //全选
+    excelModuleList: excelModuleList, //选中的模块
+    excelsList: excelsList, //所有模块List
+    isIndeterminate:
+      excelModuleList.length < excelsList.length && excelModuleList.length > 0, //半选
+  };
+}
+// 命题分析模块
+function propositionAnalysisModule(): ExcelModule {
+  let excelsList: ExcelsItem[] = [
     {
       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");
-  }
+  let excelModuleList = ["propositionAnalysis"];
   return {
     menuInfo: "paperAnalysis",
-    menuValue: "试卷分析",
+    menuValue: "命题分析",
     checkAll: excelModuleList.length == excelsList.length, //全选
     excelModuleList: excelModuleList, //选中的模块
     excelsList: excelsList, //所有模块List
     isIndeterminate:
-      excelModuleList.length < excelsList.length &&
-      excelModuleList.length > 0, //半选
+      excelModuleList.length < excelsList.length && excelModuleList.length > 0, //半选
   };
 }
-function handleCheckAllChange(val, module) {
+function handleCheckAllChange(
+  val: boolean | string | number,
+  module: ExcelModule,
+) {
   module.excelModuleList = val
     ? module.excelsList.map((item) => item.value)
     : [];
   module.isIndeterminate = false;
 }
-function handleexcelModuleListChange(value, module) {
-  let checkedCount = value.length;
+function handleexcelModuleListChange(
+  value: (string | number | boolean)[],
+  module: ExcelModule,
+) {
+  const checkedCount = value.length;
   module.checkAll = checkedCount === module.excelsList.length;
   module.isIndeterminate =
     checkedCount > 0 && checkedCount < module.excelsList.length;
@@ -644,7 +484,7 @@ function batchExport() {
   exportLoading.value = true;
   segmentData.see = true;
   loading.value = true;
-  const exportList = excelModuleList.value.map((item) => ({
+  const exportList: ExportParams[] = excelModuleList.value.map((item) => ({
     menuInfo: item.menuInfo,
     menuValue: item.menuValue,
     excelModuleList: item.excelModuleList,
@@ -652,7 +492,7 @@ function batchExport() {
   downloadExamFileList(exportList);
 }
 // 单个下载
-function singleExport(module) {
+function singleExport(module: ExcelModule) {
   if (module.excelModuleList.length == 0) {
     ElMessage.warning("请至少勾选一个需要下载的表格!");
     return;
@@ -661,7 +501,7 @@ function singleExport(module) {
   segmentData.description = "关闭后可在下载记录中查询";
   segmentData.see = true;
   loading.value = true;
-  const exportList = [
+  const exportList: ExportParams[] = [
     {
       menuInfo: module.menuInfo,
       menuValue: module.menuValue,
@@ -671,8 +511,8 @@ function singleExport(module) {
   downloadExamFileList(exportList);
 }
 // 导出选择的excel模块,生成打包任务
-function downloadExamFileList(excelModuleList) {
-  let schoolName = null;
+function downloadExamFileList(excelModuleList: ExportParams[]) {
+  let schoolName: string | null = null;
   if (filterObject.value.schoolLevel == 0) {
     schoolName = "联校";
   } else if (filterObject.value.schoolLevel == 1) {
@@ -680,20 +520,17 @@ function downloadExamFileList(excelModuleList) {
   } 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,
-        },
+  downloadAiExamFileList({
+    excelModuleList: excelModuleList,
+    examCommonSelectDTO: {
+      ...filterObject.value,
+      schoolName: schoolName,
+      pageParam: {
+        pageSize: 10,
+        pageNum: 1,
       },
-    })
+    },
+  })
     .then((res) => {
       if (res.code == 200) {
         ElMessage.success("下载成功");
@@ -727,27 +564,25 @@ function stopPolling() {
 }
 // 根据考试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;
-          });
+  findByAIExamIdAndSchoolId(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();
-          }
+        // 如果所有任务都结束,则停止轮询
+        if (isAllCompleted) {
+          stopPolling();
         }
-      } else {
-        examDownloadList.value = [];
       }
-    });
+    } else {
+      examDownloadList.value = [];
+    }
+  });
 }
 // 显示下载记录弹框
 function showDownloadDialog() {
@@ -764,7 +599,12 @@ function CloseDownloadDialog() {
   stopPolling();
 }
 // 处理下载点击事件
-function handleDownloadClick(generationStatus, id, filePath, fileName) {
+function handleDownloadClick(
+  generationStatus: number,
+  id: number | string,
+  filePath: string,
+  fileName: string,
+) {
   if (generationStatus == 2) return;
   if (generationStatus == 0) {
     // 取消下载
@@ -773,7 +613,7 @@ function handleDownloadClick(generationStatus, id, filePath, fileName) {
       cancelButtonText: "取消",
       type: "warning",
     }).then(() => {
-      api.reportSchool.downloadDeleteById(id).then((res) => {
+      deleteAIDownloadById(id).then((res) => {
         if (res.code == 200) {
           ElMessage.success("取消下载成功");
           // 刷新下载任务列表
@@ -874,6 +714,8 @@ onBeforeUnmount(() => {
   flex-wrap: wrap;
   gap: 20px;
   min-height: 400px;
+  width: calc(100% - 40px);
+  margin: auto;
 
   .item_content {
     width: calc((100% - 80px) / 5);
@@ -921,23 +763,31 @@ onBeforeUnmount(() => {
         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; /* 关键:让复选框和文本顶部对齐 */
+          align-items: flex-start;
+
+          /* 关键:让复选框和文本顶部对齐 */
           .el-checkbox__label {
-            display: block; /* 允许文本换行 */
-            word-break: break-word; /* 长文本自动换行 */
-            line-height: 1.2; /* 调整行高,避免拥挤 */
+            display: block;
+            /* 允许文本换行 */
+            word-break: break-word;
+            /* 长文本自动换行 */
+            line-height: 1.2;
+            /* 调整行高,避免拥挤 */
             color: #333333;
             font-weight: 400;
           }
@@ -946,12 +796,14 @@ onBeforeUnmount(() => {
     }
   }
 }
+
 // 下载记录弹框
 .dialog_description {
   font-weight: 400;
   font-size: 14px;
   color: #000000;
 }
+
 .dialog_content {
   width: 100%;
   height: 390px;
@@ -959,6 +811,7 @@ onBeforeUnmount(() => {
   margin: 16px 0 20px;
   display: flex;
   flex-direction: column;
+
   .list_item {
     display: flex;
     width: 100%;
@@ -968,33 +821,40 @@ onBeforeUnmount(() => {
     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;
@@ -1002,23 +862,27 @@ onBeforeUnmount(() => {
         }
       }
     }
+
     .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;

+ 1 - 1
src/views/analysis/score.vue

@@ -169,7 +169,7 @@
           </el-checkbox-group>
         </div>
       </div>
-      <div slot="footer" class="dialog__footer dialog-footer">
+      <div slot="footer" class="el-dialog__footer dialog-footer">
         <el-button size="small" @click="showClassPop = false">取 消</el-button>
         <el-button
           size="small"