Jelajahi Sumber

解决ts爆红

liurongli 1 bulan lalu
induk
melakukan
90fbf909b2

+ 10 - 0
src/api/analysis.ts

@@ -201,6 +201,16 @@ export const exportObjectAnalysis = (data: any): Promise<ApiResponse> => {
     responseType: "blob",
   });
 };
+// 试卷分析客观题作答学生
+export const publicExportSelectQuestion = (data: any): Promise<ApiResponse> => {
+  return request({
+    url: "/api/v1/ai_analysis/excel/publicExport_selectQuestion",
+    method: "post",
+    data,
+    responseType: "blob",
+  });
+};
+
 // ==========================================答题卡============================================
 //查询学生答题卡带批阅痕迹的(多张)
 export const findCardListNew = (data: any): Promise<ApiResponse> => {

+ 309 - 347
src/components/echarts/barHorizontal.vue

@@ -1,80 +1,85 @@
 <template>
-  <div ref="barEchartHorizontal" class="echart_content" :style="{ height: `${chartHeight}px` }"></div>
+  <div
+    ref="bar_echart_horizontal"
+    class="echart_content"
+    :style="{ height: chartHeight + 'px' }"
+  ></div>
 </template>
 
 <script setup lang="ts">
-import { ref, watch, onMounted, onBeforeUnmount, nextTick, withDefaults } from 'vue'
-import _ from 'lodash'
-import * as echarts from 'echarts'
-import { getCompareAnalysis } from '@/utils/common'
-
-// ===================== 类型定义 =====================
-/** 单条辅助线配置 */
+import {
+  ref,
+  watch,
+  onMounted,
+  onBeforeUnmount,
+  nextTick,
+  withDefaults,
+} from "vue";
+import _ from "lodash";
+import * as echarts from "echarts";
+import { getCompareAnalysis } from "@/utils/common";
+
+// ================= 类型定义 =================
 interface MarkLineItem {
-  isShow: boolean
-  value: number | string
+  value: number;
+  isShow?: boolean;
 }
 
-/** 悬浮提示子项 */
 interface TooltipListItem {
-  name?: string
-  value: string | number
+  name?: string;
+  value: any;
 }
 
-/** 悬浮提示数据 */
 interface TooltipDataItem {
-  rank?: string
-  list: TooltipListItem[]
+  rank?: string;
+  list: TooltipListItem[];
 }
 
-/** Props 类型 */
-interface BarHorizontalProps {
-  datax: string[]
-  datay: (number | string)[]
-  color: string
-  isClick: boolean
-  markNumber: number | number[]
-  markLineData: MarkLineItem[]
-  unit: string
-  typeName: string
-  tooltipData: TooltipDataItem[]
+interface Props {
+  datax?: any[];
+  datay?: number[];
+  color?: string;
+  isClick?: boolean;
+  markNumber?: number | number[];
+  markLineData?: MarkLineItem[];
+  unit?: string;
+  typeName?: string;
+  tooltipData?: TooltipDataItem[];
 }
 
-// ===================== Props 与默认值 =====================
-const props = withDefaults(defineProps<BarHorizontalProps>(), {
-  datax: () => [1, 2, 3, 4, 5, 6, 7].map(String),
+// ================= Props & Emits =================
+const props = withDefaults(defineProps<Props>(), {
+  datax: () => [1, 2, 3, 4, 5, 6, 7],
   datay: () => [50, 60, 70, 80, 70, 70, 100],
-  color: '#FAC858',
+  color: "#FAC858",
   isClick: false,
   markNumber: 65,
   markLineData: () => [],
-  unit: '%',
-  typeName: '',
-  tooltipData: () => []
-})
+  unit: "%",
+  typeName: "",
+  tooltipData: () => [],
+});
 
-// 自定义事件
 const emit = defineEmits<{
-  HandleChartClick: [index: number, xName: string]
-}>()
-
-// ===================== 响应式变量 =====================
-const barEchartHorizontal = ref<HTMLDivElement | null>(null)
-let echart: echarts.ECharts | null = null
-const chartHeight = ref(0)
-
-// 窗口 resize 节流
-const handleResize = _.throttle(async () => {
-  await nextTick()
-  loadEchart()
-}, 500)
-
-// 全局监听窗口变化(替代 Vue2 created)
-window.addEventListener('resize', handleResize)
-
-// ===================== 工具函数 =====================
-/** 计算坐标轴最大值规则 */
-function getMaxValue(value: number): number {
+  (e: "HandleChartClick", index: number, name: string): void;
+}>();
+
+// ================= 响应式数据 =================
+const bar_echart_horizontal = ref<HTMLDivElement | null>(null);
+const echart = ref<echarts.ECharts | null>(null);
+const chartHeight = ref(0);
+
+// ================= 方法 =================
+// 设置图表高度
+const SetChartHeight = () => {
+  chartHeight.value = props.datax.length * 24 + 60;
+  if (chartHeight.value < 380) chartHeight.value = 380;
+  if (chartHeight.value > 600) chartHeight.value = 600;
+  console.log("横向柱状图高度", chartHeight.value);
+};
+
+// 获取最大值计算规则
+const GetMaxValue = (value: number) => {
   const thresholdMap = [
     { maxThreshold: 0.3, bound: 0.3 },
     { maxThreshold: 0.6, bound: 0.6 },
@@ -102,359 +107,316 @@ function getMaxValue(value: number): number {
     { maxThreshold: 450, bound: 450 },
     { maxThreshold: 500, bound: 500 },
     { maxThreshold: 600, bound: 600 },
-    { maxThreshold: 700, bound: 700 }
-  ]
-
+    { maxThreshold: 700, bound: 700 },
+  ];
   const matchedRule = thresholdMap.find(
-    ({ maxThreshold }) => value === maxThreshold || value < maxThreshold
-  )
-
-  if (matchedRule) {
-    return matchedRule.bound
+    ({ maxThreshold }) => value === maxThreshold || value < maxThreshold,
+  );
+  return matchedRule ? matchedRule.bound : Math.ceil(value / 10) * 10;
+};
+
+// 加载 echarts
+const LoadEchart = () => {
+  if (echart.value) {
+    echart.value.dispose();
   }
-  return Math.ceil(value / 10) * 10
-}
-
-/** 设置图表高度 */
-function setChartHeight() {
-  const len = props.datax.length
-  let h = len * 24 + 60
-  if (h < 380) h = 380
-  if (h > 600) h = 600
-  chartHeight.value = h
-}
+  if (!bar_echart_horizontal.value) return;
 
-// ===================== 初始化 ECharts =====================
-function loadEchart() {
-  const dom = barEchartHorizontal.value
-  if (!dom) return
+  console.log("加载横向柱状图组件", props.datay);
+  console.log("横向柱状图辅助线数据", props.markNumber);
 
-  // 销毁旧实例
-  if (echart) {
-    echart.dispose()
-    echart = null
-  }
+  echart.value = echarts.init(bar_echart_horizontal.value, {
+    devicePixelRatio: 2,
+  });
 
-  // 初始化实例
-  echart = echarts.init(dom, null, { devicePixelRatio: 2 })
+  let totalWidth = bar_echart_horizontal.value.clientHeight;
+  let singleSeriesWidth = Math.ceil((totalWidth - 97) / props.datax.length);
 
-  const { datax, datay, color, isClick, markNumber, markLineData, unit, typeName, tooltipData } = props
-  const totalHeight = dom.clientHeight
-  const xLen = datax.length
+  const barMinWidth = 20;
+  const dataZoomNum = Math.floor((totalWidth - 97) / (barMinWidth * 1));
+  const dataZoomEnd = Math.floor((100 / props.datax.length) * dataZoomNum);
 
-  // 计算单条高度 & 滚动条配置
-  const singleSeriesHeight = Math.ceil((totalHeight - 97) / xLen)
-  const barMinHeight = 20
-  const dataZoomNum = Math.floor((totalHeight - 97) / barMinHeight)
-  const dataZoomEnd = Math.floor((100 / xLen) * dataZoomNum)
-
-  // 纵向 dataZoom
-  const dataZoom: echarts.DataZoomSliderOption | null = singleSeriesHeight < barMinHeight
-    ? {
-        start: 0,
-        end: dataZoomEnd,
-        type: 'slider',
-        show: true,
-        borderColor: 'transparent',
-        borderCap: 'round',
-        yAxisIndex: [0],
-        width: 8,
-        right: 20,
-        top: 20,
-        bottom: 20,
-        fillerColor: 'transparent',
-        zoomLock: true,
-        handleSize: '0',
-        handleStyle: {
-          color: '#b8b8b8',
-          borderWidth: 2
-        },
-        backgroundColor: 'transparent',
-        showDataShadow: false,
-        showDetail: false,
-        filterMode: 'filter'
-      }
-    : null
+  let maxMarkNumber = 0;
+  let markLineData: any[] = [];
 
-  // 处理辅助线数据
-  let maxMarkNumber = 0
-  const markLineArr: echarts.MarkLineDataItem[] = []
-  const isMarkArr = Array.isArray(markNumber)
-
-  if (isMarkArr) {
-    const numArr = markNumber as number[]
-    maxMarkNumber = numArr.length ? Math.max(...numArr) : 0
-    numArr.forEach(item => {
-      markLineArr.push({
-        name: '辅助线',
+  if (Object.prototype.toString.call(props.markNumber) === "[object Array]") {
+    const markNumArr = props.markNumber as number[];
+    maxMarkNumber = markNumArr.length ? Math.max(...markNumArr) : 0;
+    markNumArr.forEach((item) => {
+      markLineData.push({
+        name: "辅助线",
         xAxis: item,
         label: {
           show: true,
-          formatter: (e) => `${e.value}${unit}`,
-          position: 'start',
-          color: '#F56C6C',
-          fontSize: 14
-        }
-      })
-    })
-  } else if (markLineData.length) {
-    const tempNum: number[] = []
-    markLineData.forEach((item, index) => {
-      tempNum.push(Number(item.value))
+          formatter: "{c}" + props.unit,
+          position: "start",
+          color: "#F56C6C",
+          fontSize: 14,
+        },
+      });
+    });
+  } else if (props.markLineData?.length) {
+    const markNumberArr: number[] = [];
+    props.markLineData.forEach((item, index) => {
+      markNumberArr.push(item.value);
       if (item.isShow) {
-        const lineColor = getCompareAnalysis[index] || '#F56C6C'
-        markLineArr.push({
-          name: '辅助线',
-          xAxis: Number(item.value),
+        markLineData.push({
+          name: "辅助线",
+          xAxis: item.value,
           label: {
             show: true,
-            formatter: (e) => `${e.value}${unit}`,
-            position: 'start',
-            color: lineColor,
-            fontSize: 14
+            formatter: "{c}" + props.unit,
+            position: "start",
+            color: getCompareAnalysis[index],
+            fontSize: 14,
           },
-          lineStyle: { color: lineColor }
-        })
+          lineStyle: { color: getCompareAnalysis[index] },
+        });
       }
-    })
-    maxMarkNumber = tempNum.length ? Math.max(...tempNum) : 0
+    });
+    // 修复原代码逻辑:此处 markNumber 为 number,不应使用 .length
+    maxMarkNumber = props.markNumber as number;
   } else {
-    maxMarkNumber = markNumber as number
-    markLineArr.push({
-      name: '辅助线',
-      xAxis: markNumber as number,
-      label: {
-        show: true,
-        formatter: (e) => `${e.value}${unit}`,
-        position: 'start',
-        color: '#F56C6C',
-        fontSize: 14
-      }
-    })
+    maxMarkNumber = props.markNumber as number;
+    markLineData = [
+      {
+        name: "辅助线",
+        xAxis: props.markNumber,
+        label: {
+          show: true,
+          formatter: "{c}" + props.unit,
+          position: "start",
+          color: "#F56C6C",
+          fontSize: 14,
+        },
+      },
+    ];
   }
 
-  // 过滤有效数值 & 计算X轴最大值
-  const validData = datay.filter(num => !isNaN(Number(num))).map(Number)
-  const maxValue = validData.length ? Math.max(...validData) : 0
-  const nearestValue = getMaxValue(maxValue)
-  const xAxisMax = maxMarkNumber > nearestValue ? maxMarkNumber : nearestValue
-
-  // ECharts 配置项
-  const option: echarts.EChartsOption = {
+  let dataZoom = {
+    start: 0,
+    end: dataZoomEnd,
+    type: "slider",
+    show: true,
+    borderColor: "transparent",
+    borderCap: "round",
+    yAxisIndex: [0],
+    width: 8,
+    right: 20,
+    top: 20,
+    bottom: 20,
+    fillerColor: "transparent",
+    zoomLock: true,
+    handleSize: "0",
+    handleStyle: { color: "#b8b8b8", borderWidth: 2 },
+    backgroundColor: "transparent",
+    showDataShadow: false,
+    showDetail: false,
+    filterMode: "filter",
+  };
+
+  const datay = props.datay.filter((num) => !isNaN(num));
+  const maxValue = Math.max(...datay);
+  const nearestValue = GetMaxValue(maxValue);
+  let unit = props.unit;
+  const typeName = props.typeName || "";
+
+  let option: echarts.EChartsOption = {
     tooltip: {
-      axisPointer: { type: 'shadow' },
-      trigger: 'axis',
-      triggerOn: 'mousemove | click',
+      axisPointer: { type: "shadow" },
+      trigger: "axis",
+      triggerOn: "mousemove | click",
+      renderMode: "html", // 修复拼写错误 renderModel -> renderMode
       confine: true,
+      extraCssText:
+        "border-radius: 4px;padding:5px 0px 5px 5px;white-space:normal;word-wrap:break-word;max-width: 400px;", // 修复拼写错误 word-warp -> word-wrap
       enterable: true,
-      borderColor: '#fff',
-      extraCssText: 'border-radius: 4px;padding:5px 0 5px 5px;white-space:normal;word-wrap:break-word;max-width: 400px;',
-      formatter: (params) => {
-        const p = Array.isArray(params) ? params[0] : params
-        const title = p.name || ''
-        const val = p.value ?? ''
-        let tipHtml = `<div class="tooltip_content">`
-
-        if (tooltipData.length) {
-          const tipItem = tooltipData[p.dataIndex]
+      formatter: (params: any) => {
+        let tooltip = `<div class='tooltip_content'>`;
+        let title = params[0].name;
+        let value = params?.value || params[0]?.value;
+        if (props.tooltipData.length > 0) {
           if (typeName) {
-            tipHtml += `<div class="tooltip_title">${title}</div>`
-            tipHtml += `<div class="tooltip_student">${typeName}:${val}${unit}</div>`
+            tooltip += `<div class='tooltip_title'>${title}</div>`;
+            tooltip += `<div class='tooltip_student'>${typeName}:${value}${unit}</div>`;
           } else {
-            tipHtml += `<div class="tooltip_title">${title} ${tipItem?.rank ?? ''}</div>`
+            let rank = props.tooltipData[params[0].dataIndex]?.rank || "";
+            tooltip += `<div class='tooltip_title'>${title} ${rank}</div>`;
           }
-          if (tipItem?.list?.length) {
-            tipItem.list.forEach(item => {
-              if (item.name) {
-                tipHtml += `<div class="tooltip_student">${item.name}:${item.value}</div>`
-              } else {
-                tipHtml += `<div class="tooltip_student">${item.value}</div>`
-              }
-            })
+          let list = props.tooltipData[params[0].dataIndex].list;
+          for (let i = 0; i < list.length; i++) {
+            let item = list[i];
+            tooltip += item.name
+              ? `<div class='tooltip_student'>${item.name}:${item.value}</div>`
+              : `<div class='tooltip_student'>${item.value}</div>`;
           }
         } else {
-          tipHtml += `<div class="tooltip_title">${title}</div>`
-          tipHtml += `<div class="tooltip_student">${typeName}:${val}${unit}</div>`
+          tooltip += `<div class='tooltip_title'>${title}</div>`;
+          tooltip += `<div class='tooltip_student'>${typeName}:${value}${unit}</div>`;
         }
-        tipHtml += `</div>`
-        return tipHtml
-      }
-    },
-    grid: {
-      left: 40,
-      right: 50,
-      top: 20,
-      bottom: 0,
-      containLabel: true
+        tooltip += `</div>`;
+        return tooltip;
+      },
     },
-    dataZoom: dataZoom,
+    grid: { left: 40, right: 50, top: 20, bottom: 0, containLabel: true },
+    dataZoom: singleSeriesWidth < barMinWidth ? dataZoom : null,
     yAxis: {
-      type: 'category',
-      data: datax,
-      axisPointer: { type: 'shadow' },
+      type: "category",
+      data: props.datax,
+      axisPointer: { type: "shadow" },
       axisLabel: {
+        formatter: "{value}",
         interval: 0,
-        color: '#666666',
-        fontSize: 14
-      },
-      splitArea: {
-        show: true,
-        areaStyle: {
-          color: ['#fafafa', '#ffffff']
-        }
+        color: "#666666",
+        fontSize: 14,
       },
-      inverse: true
+      splitArea: { show: true, areaStyle: { color: ["#fafafa", "#ffffff"] } },
+      inverse: true,
     },
     xAxis: {
-      type: 'value',
-      max: xAxisMax,
+      type: "value",
       axisLabel: {
-        formatter: `{value}${unit === '%' ? unit : ''}`,
-        color: '#666666',
-        fontSize: 14
-      },
-      axisLine: {
-        show: true,
-        lineStyle: {
-          color: '#E4E7ED',
-          width: 1
-        }
+        formatter: "{value}" + (unit == "%" ? unit : ""),
+        color: "#666666",
+        fontSize: 14,
+        rotate: 0,
       },
+      axisLine: { show: true, lineStyle: { color: "#E4E7ED", width: 1 } },
       splitLine: {
-        show: false
+        show: false,
+        lineStyle: { color: ["#E4E7ED"], width: 1, type: "solid" },
       },
-      axisTick: {
-        show: true,
-        alignWithLabel: false
-      }
+      axisTick: { show: true, alignWithLabel: false },
+      max: maxMarkNumber > nearestValue ? maxMarkNumber : nearestValue,
     },
     series: [
       {
-        name: '',
-        type: 'bar',
+        name: "",
+        type: "bar",
         barMaxWidth: 50,
         barMinWidth: 14,
-        itemStyle: { color: color },
+        itemStyle: { color: props.color },
+        tooltip: { valueFormatter: (value: any) => value + unit },
         label: {
           show: true,
-          position: 'right',
-          formatter: `{c}${unit}`,
-          color: '#666',
-          fontSize: 14
+          position: "right",
+          formatter: "{c}" + unit,
+          color: "#666",
+          fontSize: 14,
         },
-        data: datay,
-        markLine: markNumber
+        data: props.datay,
+        markLine: props.markNumber
           ? {
-              symbol: ['triangle', 'none'],
+              symbol: ["triangle", "none"],
               symbolSize: [8, 8],
-              lineStyle: {
-                type: 'dashed',
-                color: '#F56C6C',
-                width: 1
-              },
-              label: {
-                formatter: `{c}${unit}`,
-                position: 'start'
-              },
-              data: markLineArr
+              lineStyle: { type: "dashed", color: "#F56C6C", width: 1 },
+              label: { formatter: "{c}" + props.unit, position: "start" },
+              data: markLineData,
             }
-          : undefined
+          : null,
+      },
+    ],
+  };
+
+  echart.value.setOption(option);
+
+  if (props.isClick) {
+    let container = echart.value.getDom();
+    let containerHeight = container.offsetHeight;
+    // 获取 grid 区域偏移量 (使用 any 绕过部分内部 API 类型限制)
+    const gridRect = (echart.value as any)
+      .getModel()
+      .getComponent("grid")
+      .coordinateSystem.getRect();
+    const yAxisDataLength = props.datax.length;
+    const singleLabelHeight = gridRect.height / yAxisDataLength;
+
+    echart.value.on("click", (params: any) => {
+      if (params.componentSubType === "bar") {
+        let pixelPosition = echart.value!.convertToPixel(
+          { yAxisIndex: 0 },
+          params.name,
+        );
+        echart.value!.setOption({
+          graphic: {
+            id: "highlight-box",
+            type: "rect",
+            shape: {
+              y: pixelPosition - singleLabelHeight / 2,
+              x: gridRect.x,
+              width: gridRect.width,
+              height: singleLabelHeight,
+            },
+            style: { fill: params.color + "30" },
+          },
+        });
+        emit("HandleChartClick", params.dataIndex, params.name);
       }
-    ]
+    });
+
+    let defaultPixelPosition = echart.value.convertToPixel(
+      { yAxisIndex: 0 },
+      props.datax[0],
+    );
+    echart.value.setOption({
+      graphic: {
+        id: "highlight-box",
+        type: "rect",
+        shape: {
+          y: defaultPixelPosition - singleLabelHeight / 2,
+          x: gridRect.x,
+          height: singleLabelHeight,
+          width: gridRect.width,
+        },
+        style: { fill: "rgba(84,112,198,0.1)" },
+      },
+    });
   }
+};
 
-  echart.setOption(option)
-
-  // 点击高亮 + 点击事件
-  if (isClick && echart) {
-    const gridModel = echart.getModel().getComponent('grid')
-    const gridRect = gridModel.coordinateSystem.getRect()
-    const singleLabelHeight = gridRect.height / xLen
-
-    // 默认高亮第一项
-    if (datax.length > 0) {
-      const defaultPixelPos = echart.convertToPixel({ yAxisIndex: 0 }, datax[0])
-      echart.setOption({
-        graphic: {
-          id: 'highlight-box',
-          type: 'rect',
-          shape: {
-            y: defaultPixelPos - singleLabelHeight / 2,
-            x: gridRect.x,
-            width: gridRect.width,
-            height: singleLabelHeight
-          },
-          style: {
-            fill: 'rgba(84,112,198,0.1)'
-          }
-        }
-      })
-    }
-
-    // 柱子点击监听
-    echart.on('click', (params) => {
-      if (params.componentSubType !== 'bar') return
-
-      const pixelPosition = echart.convertToPixel({ yAxisIndex: 0 }, params.name)
-      echart.setOption({
-        graphic: {
-          id: 'highlight-box',
-          type: 'rect',
-          shape: {
-            y: pixelPosition - singleLabelHeight / 2,
-            x: gridRect.x,
-            width: gridRect.width,
-            height: singleLabelHeight
-          },
-          style: {
-            fill: (params.color as string) + '30'
-          }
-        }
-      })
+// 监听窗口大小变化
+const handleResize = _.throttle(() => {
+  console.log("窗口变化了");
+  nextTick(() => {
+    LoadEchart();
+  });
+}, 500);
 
-      emit('HandleChartClick', params.dataIndex, params.name as string)
-    })
-  }
-}
-
-// ===================== 监听 & 生命周期 =====================
-// 数据变化 → 重算高度 + 重绘
+// ================= 侦听器 =================
 watch(
   () => props.datay,
-  async () => {
-    setChartHeight()
-    await nextTick()
-    loadEchart()
+  () => {
+    SetChartHeight();
+    nextTick(() => {
+      LoadEchart();
+    });
   },
-  { deep: true }
-)
+  { deep: true },
+);
 
-// 辅助线变化 → 重绘
 watch(
   () => props.markLineData,
-  () => loadEchart(),
-  { deep: true }
-)
-
-// 组件挂载初始化
-onMounted(async () => {
-  setChartHeight()
-  await nextTick()
-  loadEchart()
-})
+  () => {
+    LoadEchart();
+  },
+  { deep: true },
+);
+
+// ================= 生命周期 =================
+onMounted(() => {
+  window.addEventListener("resize", handleResize);
+  SetChartHeight();
+  nextTick(() => {
+    LoadEchart();
+  });
+});
 
-// 组件卸载:清除监听、销毁图表
 onBeforeUnmount(() => {
-  window.removeEventListener('resize', handleResize)
-  if (echart) {
-    echart.dispose()
-    echart = null
+  window.removeEventListener("resize", handleResize);
+  if (echart.value) {
+    echart.value.dispose();
   }
-})
+});
 </script>
 
-<style lang="scss" scoped>
-.echart_content {
-  width: 100%;
-}
-</style>
+<style lang="scss" scoped></style>

+ 201 - 305
src/components/echarts/radarCharts.vue

@@ -1,51 +1,21 @@
 <template>
   <div class="echart_content" style="overflow: hidden;">
-    <div class="is_show_all" v-if="showCheckBox">
-      <el-checkbox
-        v-model="allSeriesSelected"
-        :indeterminate="isIndeterminate"
-        @change="toggleChangeAll(allSeriesSelected)"
-      >
+    <div class="is_show_all" v-if="props.showCheckBox">
+      <el-checkbox v-model="allSeriesSelected" :indeterminate="isIndeterminate">
         显示全部
       </el-checkbox>
     </div>
-    <!-- 雷达图容器 -->
-    <div
-      ref="radarChartRef"
-      class="chart_box"
-      :style="{ height: reportHeight ? reportHeight : `${height}px` }"
-    />
+    <!-- 雷达图 -->
+    <div ref="radarChart" class="chart_box" :style="{ height: props.reportHeight || `${props.height}px` }"></div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, watch, onMounted, onBeforeUnmount, nextTick } from "vue";
-import type { ECharts, EChartsOption, RadarSeriesOption, LegendComponentOption } from "echarts";
-import * as echarts from "echarts";
+import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
+import * as echarts from 'echarts';
 import { getGGroupColor, getScorePerformanceAnalysis } from "@/utils/common";
 
-// ====================== 类型定义 ======================
-/** G组颜色对象类型 */
-type GColorItem = {
-  name: string;
-  color: string;
-};
-
-/** 表格数据源行:[维度名称, 数值1, 数值2...] */
-type DataRow = (string | number)[];
-type ChartData = DataRow[];
-
-/** Radar 指示器配置 */
-type RadarIndicator = {
-  name: string;
-  max: number | null;
-  min: number | null;
-  axisLabel: { show: boolean; formatter?: string; textStyle?: Record<string, any> };
-  color: string;
-  nameTextStyle: { fontSize?: number; fontWeight: string };
-};
-
-// ====================== Props 定义 ======================
+// ================= 类型定义 =================
 interface Props {
   showCheckBox?: boolean;
   height?: number;
@@ -57,194 +27,127 @@ interface Props {
   unit?: string;
   legendList?: string[];
   legendLeft?: string;
-  colorType?: "gColors" | "colors";
+  colorType?: string;
   color?: string[];
-  data: ChartData;
+  data?: any[][];
   isClick?: boolean;
   showRadiusAxis?: boolean;
   fontSize?: number | string;
   fontColor?: string;
   legendWidth?: number | string;
 }
+
+// ================= Props & Emits =================
 const props = withDefaults(defineProps<Props>(), {
   showCheckBox: true,
   height: 400,
-  reportHeight: "",
+  reportHeight: '',
   showLegend: true,
-  legengAlign: "110px",
+  legengAlign: '110px',
   showTooltip: true,
   showDataLabel: false,
-  unit: "%",
+  unit: '%',
   legendList: () => [],
-  legendLeft: "110px",
-  colorType: "colors",
+  legendLeft: '110px',
+  colorType: 'colors',
   color: () => [],
+  data: () => [],
   isClick: false,
   showRadiusAxis: true,
-  fontSize: "",
-  fontColor: "",
-  legendWidth: "auto",
+  fontSize: '',
+  fontColor: '',
+  legendWidth: 'auto',
 });
 
-// ====================== Emits 定义 ======================
-interface Emits {
-  (e: "HandleChartClick", index: number, name: string): void;
-}
-const emit = defineEmits<Emits>();
+const emit = defineEmits<{
+  (e: 'HandleChartClick', index: number, name: string): void;
+}>();
 
-// ====================== 响应式状态 ======================
-const radarChartRef = ref<HTMLDivElement | null>(null);
-let myChart: ECharts | null = null;
+// ================= 响应式数据 =================
+const radarChart = ref<HTMLDivElement | null>(null);
+let myChart: echarts.ECharts | null = null;
 
-const allSeriesSelected = ref<boolean>(false);
-const isIndeterminate = ref<boolean>(false);
-const gColors = ref<GColorItem[]>(getGGroupColor());
-const colors = ref<string[]>(getScorePerformanceAnalysis());
+const allSeriesSelected = ref(false);
+const isIndeterminate = ref(false);
 const legendSelected = ref<Record<string, boolean>>({});
 const legenSelectList = ref<string[]>([]);
 
-// ====================== 工具方法 ======================
-/** 销毁 echarts 实例 */
-function disposeChart() {
+// 常量数据
+const gColors = getGGroupColor(); 
+const colors = getScorePerformanceAnalysis();
+
+// ================= 方法 =================
+const initChart = () => {
+  if (!radarChart.value || !props.data || props.data.length === 0) return;
+
   if (myChart) {
     myChart.dispose();
-    myChart = null;
   }
-}
-
-/** 窗口自适应 */
-function resizeFn() {
-  myChart?.resize();
-}
-
-/** 切换全部图例显示/隐藏 */
-function toggleChangeAll(show: boolean) {
-  if (!props.showCheckBox || !myChart) return;
-  const opt = myChart.getOption();
-  const legendData: string[] = opt.legend[0].data;
-  legendData.forEach((name) => {
-    myChart!.dispatchAction({
-      type: show ? "legendSelect" : "legendUnSelect",
-      name,
-    });
+  
+  // 如果项目是全局挂载的 $echarts,可替换为 getCurrentInstance()?.appContext.config.globalProperties.$echarts
+  myChart = echarts.init(radarChart.value, null, {
+    devicePixelRatio: 2,
   });
-  allSeriesSelected.value = show;
-  isIndeterminate.value = false;
-}
-
-/** 图例切换回调 */
-function handleLegendSelectChanged(params: { selected: Record<string, boolean> }) {
-  if (!myChart) return;
-  const opt = myChart.getOption();
-  const legendData: string[] = opt.legend[0].data;
-  const selected = params.selected;
-  legendSelected.value = selected;
-
-  const allSelected = legendData.every((name) => selected[name]);
-  const noneSelected = legendData.every((name) => !selected[name]);
-  const someSelected = !allSelected && !noneSelected;
 
-  if (noneSelected) allSeriesSelected.value = false;
-  if (allSelected) allSeriesSelected.value = true;
-  isIndeterminate.value = someSelected;
-}
-
-/** 绑定雷达图指示器点击事件 */
-function bindChartNameEvents(indicators: RadarIndicator[]) {
-  if (!myChart) return;
-  myChart.on("click", (params: any) => {
-    if (params.componentType === "radar" && params.targetType === "axisName") {
-      const name = params.name as string;
-      const dataList = props.data.slice(1).map((row) => String(row[0]));
-      const index = dataList.indexOf(name);
-
-      const newIndicators = indicators.map((item, key) => ({
-        ...item,
-        color: key === index ? "#2e64fa" : "#666666",
-        nameTextStyle: {
-          ...item.nameTextStyle,
-          fontWeight: key === index ? "bold" : "normal",
-        },
-      }));
-
-      myChart.setOption({
-        radar: { indicator: newIndicators },
-      });
-      emit("HandleChartClick", index, name);
-    }
-  });
-}
-
-/** 初始化渲染雷达图 */
-function initChart() {
-  if (!radarChartRef.value) return;
-  disposeChart();
-
-  myChart = echarts.init(radarChartRef.value, null, { devicePixelRatio: 2 });
-
-  // 初始化选中图例列表,统一转为 string
-  if (props.legendList.length > 0) {
-    legenSelectList.value = [...props.legendList];
+  if (props.legendList && props.legendList.length > 0) {
+    legenSelectList.value = props.legendList;
   } else {
-    legenSelectList.value = props.data[0]?.slice(1).map((v) => String(v)) ?? [];
+    legenSelectList.value = props.data[0] ? props.data[0].slice(1) : [];
   }
 
-  // 构造雷达指示器
-  const indicators: RadarIndicator[] = props.data.slice(1).map((row, index) => {
-    const name = String(row[0]);
+  // 提取指示器数据
+  const indicators = props.data.slice(1).map((row, index) => {
+    const name = row[0];
     return {
-      name,
-      max: props.unit === "%" ? 100 : null,
-      min: props.unit === "%" ? 0 : null,
-      axisLabel:
-        props.showRadiusAxis && index === 0
-          ? {
-              show: true,
-              formatter: `{value}${props.unit}`,
-              textStyle: {
-                fontSize: props.fontSize || 12,
-                color: "#666",
-              },
-            }
-          : { show: false },
-      color: props.isClick && index === 0 ? "#2e64fa" : props.fontColor || "#666666",
-      nameTextStyle: {
-        fontSize: props.fontSize || 14,
-        fontWeight: props.isClick && index === 0 ? "bold" : "normal",
+      name: name,
+      max: props.unit === '%' ? 100 : null,
+      min: props.unit === '%' ? 0 : null,
+      axisLabel: props.showRadiusAxis && index === 0 ? {
+        show: true,
+        formatter: (val: number) => `${val}${props.unit}`,
+        textStyle: {
+          fontSize: props.fontSize ? props.fontSize : 12,
+          color: '#666'
+        }
+      } : {
+        show: false
       },
+      color: props.isClick && index === 0 ? '#2e64fa' : (props.fontColor ? props.fontColor : '#666666'),
+      nameTextStyle: { 
+        fontSize: props.fontSize ? props.fontSize : 14, 
+        fontWeight: props.isClick && index === 0 ? 'bold' : 'normal' 
+      }
     };
   });
 
-  // 图例维度名称
-  const legendData = props.data[0]?.slice(1).map((v) => String(v)) ?? [];
-
-  // 生成颜色列表
+  // 提取图例数据
+  const legendData = props.data[0] ? props.data[0].slice(1) : [];
+  
+  // 处理颜色
   let colorList: string[] = [];
-  if (props.colorType === "gColors") {
-    colorList = legendData.map((item) => {
-      const findColor = gColors.value.find((g) => g.name === item);
-      return findColor?.color ?? "#848BDC";
+  if (props.colorType === 'gColors') {
+    colorList = legendData.map(item => {
+      const gColor = gColors.find((gg: any) => gg.name === item);
+      return gColor ? gColor.color : '#848BDC';
     });
   } else {
-    colorList = props.color.length > 0 ? props.color : colors.value;
+    colorList = props.color && props.color.length > 0 ? props.color : colors;
   }
 
-  // 初始化图例选中状态
-  const tempLegendSelected: Record<string, boolean> = {};
-  legendData.forEach((dim) => {
-    tempLegendSelected[dim] = false;
-  });
-  legenSelectList.value.forEach((item) => {
-    tempLegendSelected[item] = true;
+  // 初始化 legendSelected
+  legendSelected.value = legendData.reduce((acc: Record<string, boolean>, dim) => {
+    acc[dim] = false;
+    return acc;
+  }, {});
+  
+  legenSelectList.value.forEach(item => {
+    legendSelected.value[item] = true;
   });
-  legendSelected.value = tempLegendSelected;
 
-  // 更新全选状态
+  isIndeterminate.value = legenSelectList.value.length > 0 && legenSelectList.value.length < legendData.length;
   allSeriesSelected.value = legenSelectList.value.length === legendData.length;
-  isIndeterminate.value =
-    legenSelectList.value.length > 0 && legenSelectList.value.length < legendData.length;
 
-  // 构造 series 数据
+  // 提取每个系列的数据
   const seriesData = legendData.map((grade, gradeIndex) => {
     const values = props.data.slice(1).map((row) => Number(row[gradeIndex + 1]));
     return {
@@ -252,78 +155,79 @@ function initChart() {
       value: values,
       label: {
         show: props.showDataLabel,
-        position: "outside",
-        formatter: `{value}${props.unit}`,
-      },
+        position: 'outside',
+        formatter: (params: any) => `${params.value}${props.unit}`
+      }
     };
   });
 
-  // 非百分比时自动计算 min/max
-  if (props.unit !== "%") {
+  // 动计算 max/min
+  if (props.unit !== '%') {
     let maxValue = -Infinity;
     let minValue = Infinity;
-    seriesData.forEach((item) => {
-      item.value.forEach((num) => {
-        maxValue = Math.max(maxValue, num);
-        minValue = Math.min(minValue, num);
+    seriesData.forEach(item => {
+      item.value.forEach(num => {
+        if (num > maxValue) maxValue = num;
+        if (num < minValue) minValue = num;
       });
     });
-    indicators.forEach((item) => {
+    indicators.forEach(item => {
       item.max = Math.ceil(maxValue);
       item.min = Math.floor(minValue);
     });
   }
 
-  // ECharts Option
-  const option: EChartsOption = {
-    emphasis: { blurScope: "global" },
+  const option: echarts.EChartsOption = {
+    emphasis: {
+      blurScope: 'global'
+    },
     legend: {
       show: props.showLegend,
       data: legendData,
       width: props.legendWidth,
-      top:0,
       left: props.legendLeft,
       itemGap: 20,
       itemHeight: 10,
       itemWidth: 20,
       textStyle: { fontSize: 12, color: "#333" },
-      icon: "path://M352.64 526.336a158.272 158.272 0 0 0 2.56 17.664H96v-64h259.2a161.952 161.952 0 0 0-2.336 15.232A160 160 0 0 1 672 512a160.992 160.992 0 0 0-3.2-32H928v64h-259.2q1.12-5.504 1.824-11.104a160 160 0 0 1-318.016-6.4zM416 512a96 96 0 1 0 96-96 96 96 0 0 0-96 96z m254.72 20.224v-0.576z m-318.08-5.888v-1.664q-0.064 0.832 0 1.664z m-0.16-2.08v-1.248a5.44 5.44 0 0 0 0 1.248z m0-2.08v-0.928z m0-2.048zM352 518.08z m0-2.048zM352 512v-4.096V512z m0-5.344v-0.736 0.736z m0-2.56z m0.256-4.128z m0-1.664z m0-1.6v-0.992z",
+      icon: 'path://M352.64 526.336a158.272 158.272 0 0 0 2.56 17.664H96v-64h259.2a161.952 161.952 0 0 0-2.336 15.232A160 160 0 0 1 672 512a160.992 160.992 0 0 0-3.2-32H928v64h-259.2q1.12-5.504 1.824-11.104a160 160 0 0 1-318.016-6.4zM416 512a96 96 0 1 0 96-96 96 96 0 0 0-96 96z m254.72 20.224v-0.576z m-318.08-5.888v-1.664q-0.064 0.832 0 1.664z m-0.16-2.08v-1.248a5.44 5.44 0 0 0 0 1.248z m0-2.08v-0.928z m0-2.048zM352 518.08z m0-2.048zM352 512v-4.096V512z m0-5.344v-0.736 0.736z m0-2.56z m0.256-4.128z m0-1.664z m0-1.6v-0.992z',
       selectedMode: true,
       selected: legendSelected.value,
-      type: "scroll",
+      type: 'scroll',
       pageButtonItemGap: 5,
-      pageButtonPosition: "end",
-      orient: "horizontal",
-      alignTo: "none",
-      pageIconColor: "#999",
-      pageIconInactiveColor: "#ccc",
-    } as LegendComponentOption,
+      pageButtonPosition: 'end',
+      orient: 'horizontal',
+      alignTo: 'none',
+      pageIconColor: '#999',
+      pageIconInactiveColor: '#ccc',
+    },
     tooltip: {
       show: props.showTooltip,
       triggerOn: "mousemove | click",
-      renderMode: "html",
+      renderMode: 'html',
       confine: true,
-      extraCssText: "border-radius: 4px;white-space:normal;word-wrap:break-word;max-width: 400px;",
+      extraCssText: 'border-radius: 4px;white-space:normal;word-warp:break-word;max-width: 400px;',
       enterable: true,
-      formatter: (params: any[]) => {
-        const opt = myChart!.getOption();
-        const radarIndicators = opt.radar[0].indicator as RadarIndicator[];
+      formatter: (params: any) => {
+        if (!myChart) return '';
+        const chart = myChart.getOption();
+        const indicator = (chart.radar as any)[0].indicator;
         let tooltipContent = `<div class='tooltip_content'>`;
-        const title = params[0].name;
-        tooltipContent += `<div class='tooltip_title'>${title}</div>`;
-        radarIndicators.forEach((item, index) => {
+        tooltipContent += `<div class='tooltip_title'>${params.name}</div>`;
+        
+        indicator.forEach((item: any, index: number) => {
           if (item.name) {
-            const value = params[0].value[index];
-            tooltipContent += `<div class='tooltip_student'>${item.name}:${value}${props.unit}</div>`;
+            tooltipContent += `<div class='tooltip_student'>${item.name}:${params.value[index]}${props.unit}</div>`;
           }
         });
+        tooltipContent += `</div>`;
         return tooltipContent;
       },
     },
     color: colorList,
     radar: {
       indicator: indicators,
-      shape: indicators.length < 3 ? "circle" : undefined,
+      shape: indicators.length < 3 ? 'circle' : '',
       splitNumber: 5,
       startAngle: 90,
       splitLine: { show: true },
@@ -332,143 +236,135 @@ function initChart() {
         show: true,
         areaStyle: {
           opacity: 0.1,
-          color: ["#f0f8ff", "#e0f7fa", "#fff3e0", "#efebe9", "#f5f5f5"],
-        },
-      },
-      radiusAxis: {
-        type: "value",
-        min: 0,
-        max: 100,
-        interval: 20,
-        splitLine: { show: true, lineStyle: { color: "#eee" } },
-        splitArea: { show: false },
+          color: ['#f0f8ff', '#e0f7fa', '#fff3e0', '#efebe9', '#f5f5f5']
+        }
       },
       axisLine: {
         show: props.data.length < 3 ? false : true,
-        lineStyle: { color: "#999" },
+        lineStyle: { color: '#999' }
       },
-      center: props.reportHeight ? ["50%", "50%"] : props.showLegend ? ["50%", "55%"] : ["50%", "55%"],
+      center: props.reportHeight ? ['50%', '50%'] : ['50%', '55%']
     },
     triggerEvent: true,
     series: [
       {
         type: "radar",
-        symbol: "circle",
+        symbol: 'circle',
         symbolSize: 7,
         data: seriesData,
         itemStyle: { opacity: 1 },
         emphasis: {
-          symbol: "none",
+          symbol: 'none',
           focus: "self",
           itemStyle: { opacity: 1, borderWidth: 2 },
-          areaStyle: { opacity: 0.1 },
+          areaStyle: { opacity: 0.1 }
         },
         blur: {
-          symbol: "none",
-          itemStyle: { opacity: 0.2 },
-          lineStyle: { opacity: 0.2 },
+          symbol: 'none',
+          itemStyle: { opacity: 0.2, color: 'red' },
+          lineStyle: { opacity: 0.2 }
         },
-      } as RadarSeriesOption,
+      },
     ],
   };
 
   myChart.setOption(option);
-
-  // 绑定事件
-  myChart.off("legendselectchanged");
   myChart.on("legendselectchanged", handleLegendSelectChanged);
-
+  
   if (props.isClick) {
     bindChartNameEvents(indicators);
   }
+};
 
-  window.addEventListener("resize", resizeFn);
-}
+const bindChartNameEvents = (indicators: any[]) => {
+  if (!myChart) return;
+  
+  myChart.on('click', (params: any) => {
+    if (params.componentType === 'radar' && params.targetType === 'axisName') {
+      const name = params.name;
+      const data = props.data.slice(1);
+      const list = data.map(item => item[0]);
+      const index = list.indexOf(name);
+      
+      const indicatorList = [...indicators];
+      indicatorList.forEach((item, key) => {
+        item.color = key === index ? '#2e64fa' : '#666666';
+        item.nameTextStyle.fontWeight = key === index ? 'bold' : 'normal';
+      });
+      
+      myChart?.setOption({
+        radar: { indicator: indicatorList }
+      });
+      
+      emit('HandleChartClick', index, name);
+    }
+  });
+};
 
-// ====================== 监听 & 生命周期 ======================
-watch(
-  () => allSeriesSelected.value,
-  (newVal) => {
-    toggleChangeAll(newVal);
-  }
-);
+const handleLegendSelectChanged = (params: any) => {
+  if (!myChart) return;
+  const legendData = myChart.getOption().legend[0].data as string[];
+  const selected = params.selected;
+  
+  legendSelected.value = selected;
+  
+  const allSelected = legendData.every((seriesName) => selected[seriesName]);
+  const noneSelected = legendData.every((seriesName) => !selected[seriesName]);
+  const someSelected = !allSelected && !noneSelected;
 
-watch(
-  () => props.data,
-  () => {
-    initChart();
-  },
-  { deep: true }
-);
+  if (noneSelected) allSeriesSelected.value = false;
+  if (allSelected) allSeriesSelected.value = true;
+  isIndeterminate.value = someSelected;
+};
 
+const resizeFn = () => {
+  myChart?.resize();
+};
+
+const toggleChangeAll = (show: boolean) => {
+  if (!props.showCheckBox || !myChart) return;
+  const legendData = myChart.getOption().legend[0].data as string[];
+  
+  legendData.forEach((seriesName) => {
+    myChart?.dispatchAction({
+      type: show ? "legendSelect" : "legendUnSelect",
+      name: seriesName,
+    });
+  });
+  
+  allSeriesSelected.value = show;
+  isIndeterminate.value = false;
+};
+
+// ================= 监听器 =================
+watch(allSeriesSelected, (newVal) => {
+  toggleChangeAll(newVal);
+});
+
+watch(() => props.data, () => {
+  initChart();
+}, { deep: true });
+
+// ================= 生命周期 =================
 onMounted(() => {
   initChart();
+  window.addEventListener("resize", resizeFn);
 });
 
 onBeforeUnmount(() => {
   window.removeEventListener("resize", resizeFn);
-  disposeChart();
+  if (myChart) {
+    myChart.dispose();
+    myChart = null;
+  }
 });
 </script>
 
 <style lang="scss" scoped>
-//图标外层公共样式
 .echart_content {
-  position: relative;
-  width: 100%;
-  margin: auto;
-  min-height: 360px;
-  height: auto;
   .is_show_all {
-    position: absolute;
     top: -4px;
     left: 5px;
   }
-
-  .chart_box {
-    width: 100%;
-    height: 400px;
-  }
-}
-
-//弹窗悬浮层样式更改
-.echart_content::-webkit-scrollbar {
-  width: 100%;
-  height: 8px;
-  /* 设置滚动条宽度为8像素 */
-  background-color: transparent;
-}
-
-/* 滑块样式 */
-.echart_content::-webkit-scrollbar-thumb {
-  background-color: #b8b8b8;
-  /* 设置滑块颜色为深灰色 */
-  border-radius: 4px;
-  /* 设置滑块边角半径为4像素 */
-  min-height: 40px;//设置手柄最小高度
-}
-
-/* 滚动条轨道内部空白区域样式 */
-.echart_content::-webkit-scrollbar-track {
-  background-color: #f0f0f0;
-  /* 设置轨道背景色为浅灰色 */
-}
-
-/* 滚动条两端按钮样式 */
-.echart_content::-webkit-scrollbar-button {
-  display: none;
-  /* 不显示按钮 */
-}
-
-/* 交叉点处的区域样式 */
-.echart_content::-webkit-scrollbar-corner {
-  background-color: transparent;
-  /* 设置交叉点处的背景色为透明 */
-}
-
-/* 调整大小手柄样式 */
-.echart_content::-webkit-resizer {
-  display: none;
-  /* 不显示调整大小手柄 */
 }
 </style>

+ 15 - 2
src/views/analysis/classComparison.vue

@@ -450,7 +450,19 @@ interface AnalysisData {
   average?: number;
   markLineData?: Array<{ legendName: string; value: number; isShow?: boolean }>;
 }
-
+/** 弹窗数据类型 */
+interface DialogData {
+  showDialog: boolean;
+  studentRegistrationCode: any[];
+  title: string;
+  tableTitle: string;
+  fiveRateName: string;
+  selectSubjectName: string;
+  selectSchoolLevel: string | number;
+  selectSchoolName: string;
+  selectClassLevel: string | number;
+  selectClassName: string;
+}
 /** 页面状态类型 */
 interface PageState {
   fiveRateList: FiveRateItem[];
@@ -465,6 +477,7 @@ interface PageState {
   groupTitle: string;
   sortValue: string;
   sortOption: SortOption[];
+  dialogData: DialogData; 
 }
 
 // ================= 组件引用 & Store =================
@@ -1197,7 +1210,7 @@ const GetAnalysisFiveRateData = () => {
   state.analysisData.expandableText = `说明:班级<span class="normal_color">${state.rateName}</span>指班级的<span class="normal_color">${fiveRateNames}。${state.rateName}</span>综合展示,可以全面了解班级的成绩水平情况。点击班级的柱可在下方查看该班除总分外其他科目的<span class="normal_color">${state.rateName}</span>占比情况。`;
 };
 //展开学生弹窗
-const OpenStudentDialog = (row, header, child) => {
+const OpenStudentDialog = (row: any, header: HeaderItem, child: HeaderItem) => {
   state.dialogData.title = `${header.name}`;
   state.dialogData.tableTitle = row.keyName;
   state.dialogData.fiveRateName = (child.label || "").replace(/人数/g, ""); //当前选择的五率的名称

+ 77 - 31
src/views/analysis/errorAnalysis.vue

@@ -141,42 +141,74 @@ import { useAnalysisStore } from "@/store/analysis";
 import ErrorsPdf from "@/components/ErrorsPdf.vue";
 import { onMounted, reactive, computed, ref, watch } from "vue";
 import { errorQuestionAnalysis, exportErrorQuestion } from "@/api/analysis";
-import {loadingSvgIcon} from "@/utils/common";
+import { loadingSvgIcon } from "@/utils/common";
 import {
   downloadExcel,
   GetExcelFileName,
   errorQuestionDataStaticHeaderData,
   errorQuestionDataChildHeaderData,
 } from "@/utils/exportExcel";
-const state = reactive({
+
+// 定义表格行数据类型
+interface TableDataItem {
+  questionId: string | number;
+  questionName?: string;
+  questionType?: string;
+  questionScore?: number | string;
+  difficulty?: number | string;
+  discrimination?: number | string;
+  averageScore?: number | string;
+  scoreRate?: number | string;
+  studentCount?: number;
+  errorCount?: number;
+  answerValue?: string;
+  answerList?: any[];
+  questionStatsList?: any[];
+  rowspan?: number;
+  colspan?: number;
+  rowKey?: number;
+  rowspanIndex?: number;
+  [key: string]: any;
+}
+
+const state = reactive<{
+  tableData: TableDataItem[];
+  tableLoading: boolean;
+  loadingText: string;
+}>({
   tableData: [],
   tableLoading: true,
   loadingText: "加载中,请稍后...",
 });
+
 const analysisStore = useAnalysisStore();
 const reportModuleRef = ref<any>(null);
 const errPdfReport = ref<any>(null);
+
 const getExamName = computed(() => {
   return analysisStore.analysisExamInfo.examName || "";
 });
-//错题分析
+
+// 错题分析
 const GetErrorQuestionAnalysis = async () => {
   state.tableLoading = true;
-  const res = await errorQuestionAnalysis({
-    ...analysisStore.filterObject,
+  const res: any = await errorQuestionAnalysis({
+    ...(analysisStore.filterObject as any),
   });
+  
   if (res.code === 200) {
     const allTableData = res.data || [];
     state.tableData = [];
-    const tableData = allTableData.filter((item) => {
+    const tableData = allTableData.filter((item: any) => {
       if (analysisStore.filterObject.classLevel == 0) {
         return item.classIdCode.indexOf("school") > -1;
       } else {
         return item.className == analysisStore.filterObject.classGroupName;
       }
     });
+    
     if (tableData?.length) {
-      tableData[0].questionStatsList.forEach((item, key) => {
+      tableData[0].questionStatsList.forEach((item: any, key: number) => {
         const answerListLen = item?.answerList?.length || 0;
         if (answerListLen == 0) {
           item.answerList = [
@@ -190,16 +222,16 @@ const GetErrorQuestionAnalysis = async () => {
           ];
         }
         const rowspan = item.answerList.length;
-        item.answerList.forEach((answer, index) => {
+        item.answerList.forEach((answer: any, index: number) => {
           if (index == 0) {
             state.tableData.push({
               ...item,
               scoreRate: `${item.scoreRate}%`,
               ...answer,
-              rowspan: rowspan, //合并行
+              rowspan: rowspan, // 合并行
               colspan: 1,
-              rowKey: index, //行索引
-              rowspanIndex: key, //合并行之后的索引
+              rowKey: index, // 行索引
+              rowspanIndex: key, // 合并行之后的索引
             });
           } else {
             state.tableData.push({
@@ -207,20 +239,21 @@ const GetErrorQuestionAnalysis = async () => {
               questionId: `${item.questionId}_${index}`,
               scoreRate: `${item.scoreRate}%`,
               ...answer,
-              rowKey: index, //行索引
-              rowspanIndex: key, //合并行之后的索引
+              rowKey: index, // 行索引
+              rowspanIndex: key, // 合并行之后的索引
             });
           }
         });
-      }); //表格
+      });
     }
   } else {
     state.tableData = [];
   }
   state.tableLoading = false;
 };
-//错题分析  合并单元格
-const SpanMethod = ({ row, column, rowIndex, columnIndex }) => {
+
+// 错题分析  合并单元格
+const SpanMethod = ({ row, column, rowIndex, columnIndex }: any) => {
   const props = [
     "errorDetail",
     "name",
@@ -242,44 +275,51 @@ const SpanMethod = ({ row, column, rowIndex, columnIndex }) => {
     }
   }
 };
-//定义行样式
-const ErrorTableRowClassName = ({ row, rowIndex }) => {
+
+// 定义行样式
+const ErrorTableRowClassName = ({ row, rowIndex }: any) => {
   if (row.rowspanIndex % 2 === 0) {
     return "row_color_FFFFFF";
   } else {
     return "row_color_FAFAFA";
   }
 };
-//设置单元格样式
-const ErrorTableCellClassName = ({ row, column, rowIndex, columnIndex }) => {
+
+// 设置单元格样式
+const ErrorTableCellClassName = ({ row, column, rowIndex, columnIndex }: any) => {
   if (column.property == "registrationCodeList") {
     return "white_space_normal";
   } else {
     return "";
   }
 };
+
 // 导出Excel
 const ExportExcel = () => {
   // 1. 设置加载状态
   reportModuleRef.value?.SetExportLoading?.(true);
+  
   // 2. 参数
   const examName = getExamName.value;
-  let params = {
-    fileName: `${GetExcelFileName(examName, analysisStore.filterObject, "错题分析表")}`,
-    examName: examName, //考试名称
-    sheetName: "错题分析表", //sheet页名称
+  let params: any = {
+    fileName: `${GetExcelFileName(examName, analysisStore.filterObject as any, "错题分析表")}`,
+    examName: examName, // 考试名称
+    sheetName: "错题分析表", // sheet页名称
   };
-  const staticHeader = errorQuestionDataStaticHeaderData();
+  
+  const staticHeader: any[] = errorQuestionDataStaticHeaderData();
   const childHeader = ["name", "rate", "studentNum", "registrationCodeList"];
-  const childHeaderData = errorQuestionDataChildHeaderData(childHeader);
+  const childHeaderData: any[] = errorQuestionDataChildHeaderData(childHeader);
   const staticHeaderData = [...staticHeader, ...childHeaderData];
+  
   params.staticHeaderData = staticHeaderData;
   params.childHeaderData = [];
   params.dynamicsHeaderData = [];
-  let dataList = [];
+  
+  let dataList: any[] = [];
   state.tableData.forEach((item) => {
-    const rowData = [];
-    staticHeaderData.forEach((header) => {
+    const rowData: any[] = [];
+    staticHeaderData.forEach((header: any) => {
       if (header.prop == "rate") {
         const rate = `${item[header.prop]}${item[header.prop] == "-" ? "" : "%"}`;
         rowData.push(rate);
@@ -289,33 +329,39 @@ const ExportExcel = () => {
     });
     dataList.push(rowData);
   });
+  
   params.dataList = dataList;
-  params.mergeColumn = staticHeader.length - 1; //合并的列
+  params.mergeColumn = staticHeader.length - 1; // 合并的列
+  
   // 3. 调用通用下载方法,并在完成后重置加载状态
   downloadExcel(exportErrorQuestion, params).finally(() => {
     reportModuleRef.value?.SetExportLoading?.(false);
   });
 };
+
 // 导出PDF
 const PrintPdf = () => {
   reportModuleRef.value?.SetPrintLoading?.(true);
   errPdfReport.value?.DownloadPdf?.();
 };
+
 // 导出PDF  加载完成
 const PdfLoadEnd = () => {
   reportModuleRef.value?.SetPrintLoading?.(false);
 };
+
 // 初始化
 const pageInit = () => {
   GetErrorQuestionAnalysis();
 };
+
 // 监听筛选条件
 watch(
   () => analysisStore.filterObject,
   async () => {
     pageInit();
   },
-  { deep: true },
+  { deep: true }
 );
 
 onMounted(() => {

File diff ditekan karena terlalu besar
+ 415 - 339
src/views/analysis/groupAnalysis.vue


+ 64 - 77
src/views/analysis/optionAnalysis.vue

@@ -368,8 +368,8 @@
                 <span
                   :style="{
                     color:
-                      scope.row.questionList[index].answerValue ===
-                      scope.row.questionList[index].studentAnswer
+                      scope.row.questionList?.[index]?.answerValue ===
+                      scope.row.questionList?.[index]?.studentAnswer
                         ? '#606266'
                         : '#EE6666',
                   }"
@@ -404,6 +404,7 @@ import {
   selectQuestionAnalysisPage,
   selectQuestionStudentPageList,
   exportObjectAnalysis,
+  publicExportSelectQuestion,
 } from "@/api/analysis";
 import {
   downloadExcel,
@@ -419,45 +420,45 @@ const getExamName = computed(() => {
 const state = reactive({
   groupTitle: "选项分析",
   question: {
-    resData: [], //小题分析数据
-    select: "", //当前选择的小题
-    questionList: [], //当前小题下拉框
-    selectIndex: 0, //当前小题下拉框索引
-    lastQuestionIndex: null, //最后一题索引
-  }, //当前小题
+    resData: [] as any[], // 修复:显式声明为 any[]
+    select: "",
+    questionList: [] as any[], // 修复:显式声明为 any[]
+    selectIndex: 0,
+    lastQuestionIndex: null as number | null, // 修复:允许 number 或 null
+  },
   barChartData: {
-    datax: [], //x轴数据
-    datay: [], //Y轴数据
-  }, //1、选择题分析图 柱状图
+    datax: [] as string[], // 修复
+    datay: [] as number[], // 修复
+  },
   pieChart: {
     refresh: false,
-    data: [],
-  }, //1、选择题分析图 饼状图
+    data: [] as any[], // 修复
+  },
   objectiveAnalysisData: {
-    tableData: [], //表格数据
-    originalTableData: [], //原始表格数据
-    data: [], //表数据
-    headLabels: [], //选项头部信息
-    pageSize: 10, //每页显示数据
-    total: 0, //总数
-    currentPage: 1, //当前页
-  }, //客观题分析数据
+    tableData: [] as any[], // 修复
+    originalTableData: [] as any[], // 修复
+    data: [] as any[], // 修复
+    headLabels: [] as string[], // 修复
+    pageSize: 10,
+    total: 0,
+    currentPage: 1,
+  },
   dialogData: {
     dialogKey: 1,
     title: "",
-    questionId: "", //题号
-    answer: "", //学生选择的答案
-    studentCodeList: [], //学生学号
+    questionId: "",
+    answer: "",
+    studentCodeList: [] as string[], // 修复
     show: false,
-    pageSize: 10, //每页显示数据
-    total: 0, //总数
-    currentPage: 1, //当前页
-    list: [],
-    headerList: [],
+    pageSize: 10,
+    total: 0,
+    currentPage: 1,
+    list: [] as any[], // 修复
+    headerList: [] as string[], // 修复
     studentNum: 0,
   },
-  exportLoading: false, // 客观题表格数据导出
-  dialogExportLoading: false, // 弹窗内数据导出
+  exportLoading: false,
+  dialogExportLoading: false,
   pieKey: 0,
 });
 const objectiveTable = computed(() => {
@@ -486,7 +487,7 @@ const GetSelectQuestionAnalysisChartData = () => {
     if (res.code == 200 && res.data && res.data.length) {
       const resData = res.data || [];
       state.question.resData = resData;
-      state.question.questionList = resData.map((item) => {
+      state.question.questionList = resData.map((item: any) => {
         return {
           questionId: item.questionId,
           questionName: item.questionName,
@@ -549,7 +550,7 @@ const GetChartData = () => {
   state.barChartData.datax = []; // 1、选择题分析图 柱状图 X轴
   state.barChartData.datay = []; // 1、选择题分析图 柱状图 Y轴
   state.pieChart.data = []; //1、选择题分析图 饼状图
-  answerScoreData.forEach((item) => {
+  answerScoreData.forEach((item: any) => {
     state.barChartData.datax.push(item.name);
     state.barChartData.datay.push(item.studentNum);
     state.pieChart.data.push({
@@ -558,7 +559,7 @@ const GetChartData = () => {
     });
   });
 };
-const GetPieRefresh = (val) => {
+const GetPieRefresh = (val: boolean) => {
   state.pieChart.refresh = val;
 };
 //客观题分析(选择题分析表-分页)
@@ -589,20 +590,20 @@ const GetSelectQuestionAnalysisPage = () => {
   });
 };
 //分页切换
-const ChangeCurrentPage = (val) => {
+const ChangeCurrentPage = (val: number) => {
   state.objectiveAnalysisData.currentPage = val;
 };
 const ChangePageSize = (val: number) => {
   state.objectiveAnalysisData.pageSize = val;
   state.objectiveAnalysisData.currentPage = 1;
 };
-//显示 各选项选择人数 弹框
+// 显示 各选项选择人数 弹框
 const showDialogData = (
-  questionCode,
-  questionId,
-  answer,
-  studentNum,
-  studentCodeList,
+  questionCode: string,
+  questionId: string,
+  answer: string,
+  studentNum: any,
+  studentCodeList: any[], // 增加类型声明
 ) => {
   if (!studentNum || studentNum == "0" || studentNum == "-") {
     return false;
@@ -613,34 +614,39 @@ const showDialogData = (
   state.dialogData.currentPage = 1;
   state.dialogData.questionId = questionId;
   state.dialogData.answer = answer;
-  state.dialogData.studentCodeList = studentCodeList;
+  state.dialogData.studentCodeList = studentCodeList || []; // 防止传入 undefined
   state.dialogData.studentNum = studentNum;
-  GetSelectQuestionStudentPageList(studentCodeList, studentNum);
+  GetSelectQuestionStudentPageList(studentCodeList || [], studentNum);
 };
-const GetSelectQuestionStudentPageList = (studentCodeList, studentNum) => {
+
+const GetSelectQuestionStudentPageList = (
+  studentCodeList: any[],
+  studentNum: any,
+) => {
   state.dialogData.dialogKey += 1;
   const start = (state.dialogData.currentPage - 1) * state.dialogData.pageSize;
   const end = start + state.dialogData.pageSize;
-  const studentCodeListData = studentCodeList.slice(start, end);
-  state.dialogData.total = Number(studentNum); //总条数
+  // 修复:使用可选链 ?. 防止 studentCodeList 为 undefined 时调用 slice 报错
+  const studentCodeListData = studentCodeList?.slice(start, end) || [];
+  state.dialogData.total = Number(studentNum);
   selectQuestionStudentPageList({
     ...analysisStore.filterObject,
     studentCodeList: studentCodeListData,
     pageParam: {
-      pageNum: 1, //当前页码
-      pageSize: state.dialogData.pageSize, //每页显示行数
+      pageNum: 1,
+      pageSize: state.dialogData.pageSize,
     },
   }).then((res) => {
     const { code, data } = res;
     if (code == 200) {
       const { total, records } = data;
       state.dialogData.list = records[0]?.studentPageList || [];
-      state.dialogData.headerList = records[0]?.headerList || []; //选项头部信息
+      state.dialogData.headerList = records[0]?.headerList || [];
     }
   });
 };
 //弹框分页切换
-const GetStudentTableDataChangePage = (val) => {
+const GetStudentTableDataChangePage = (val: number) => {
   state.dialogData.currentPage = val;
   GetSelectQuestionStudentPageList(
     state.dialogData.studentCodeList,
@@ -650,38 +656,19 @@ const GetStudentTableDataChangePage = (val) => {
 //弹框导出
 const DialogExportExcel = () => {
   state.dialogExportLoading = true;
-  const examName = this.$store.state.report.examSelectItem.examName;
+  const examName = getExamName.value;
   const title = state.dialogData.title;
-  const fileName = `${GetFileName(examName, analysisStore.filterObject)}_${state.groupTitle}表_${title.replace(/\s/g, "")}_${GetExportDate()}`;
+  const fileName = `${GetExcelFileName(examName, analysisStore.filterObject, `${state.groupTitle}表_${title.replace(/\s/g, "")}`)}`;
   const sheetName = title.replace(/\s/g, "");
   let params = {
     ...analysisStore.filterObject,
-    studentCodeList: this.dialogData.studentCodeList,
+    studentCodeList: state.dialogData.studentCodeList,
     fileName,
     sheetName,
   };
-  this.$api.reportSchool
-    .publicExportSelectQuestion(params)
-    .then((res) => {
-      if (res.status == 200) {
-        let blob = new Blob([res.data], {
-          type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
-        });
-        let a = document.createElement("a");
-        a.href = URL.createObjectURL(blob);
-        a.download = decodeURIComponent(
-          res.headers["content-disposition"].split("filename=")[1],
-        );
-        a.click();
-        URL.revokeObjectURL(a.href);
-        a.remove();
-      } else {
-        this.$message.error("导出失败!");
-      }
-    })
-    .finally(() => {
-      this.dialogExportLoading = false;
-    });
+  downloadExcel(publicExportSelectQuestion, params).finally(() => {
+    state.dialogExportLoading = false;
+  });
 };
 // 客观题分析表数据导出
 const ExportExcel = () => {
@@ -690,11 +677,11 @@ const ExportExcel = () => {
   const staticHeaderData = selectQuestionAnalysisStaticHeaderData();
   const dynamicsHeaderData = selectQuestionAnalysisDynamicsHeaderData();
   const childHeaderData = state.objectiveAnalysisData.headLabels.map(
-    (item) => ({
+    (item: string) => ({
       label: item,
     }),
   );
-  let dataList = [];
+  let dataList: any[][] = [];
   state.objectiveAnalysisData.tableData.forEach((row) => {
     const staticData = staticHeaderData.map((header) => {
       if (header.prop == "scoreRate") {

+ 25 - 8
src/views/analysis/optionDetail.vue

@@ -114,7 +114,7 @@ import {
 import { useAnalysisStore } from "@/store/analysis";
 import { Search } from "@element-plus/icons-vue";
 import { downloadExcel } from "@/utils/exportExcel";
-import {loadingSvgIcon} from "@/utils/common";
+import { loadingSvgIcon } from "@/utils/common";
 import { onMounted, reactive, ref, computed, watch } from "vue";
 
 interface TableColumn {
@@ -128,6 +128,7 @@ interface TableCount {
   examStudentCount: number | string;
   normalCount: number | string;
   missExamCount: number | string;
+  [key: string]: any; // 防止后端返回额外字段导致类型校验报错
 }
 
 interface PageInfo {
@@ -136,6 +137,12 @@ interface PageInfo {
   total: number;
 }
 
+interface PaperInfo {
+  examPaperId: string | number;
+  platformNumber: string;
+  questionId: string;
+}
+
 interface State {
   keyWord: string;
   tableData: any[];
@@ -143,8 +150,11 @@ interface State {
   answerDataTitle: TableColumn[];
   tableCount: TableCount;
   pageInfo: PageInfo;
-  tableLoading: Boolean;
-  loadingText: String;
+  tableLoading: boolean; // 修复:使用小写 boolean
+  loadingText: string;   // 修复:使用小写 string
+  paperInfo: PaperInfo;
+  paperTitle: string;
+  showStudentPaperDialog: boolean;
 }
 
 const analysisStore = useAnalysisStore();
@@ -189,7 +199,7 @@ const GetStudentTranscriptTitle = async () => {
     if (res.code === 200) {
       const staticHeaderData = res.data?.title?.staticHeaderData || [];
       state.staticHeaderData = staticHeaderData.filter(
-        (item: { display: any }) => item.display,
+        (item: TableColumn) => item.display, // 优化:使用已定义的接口类型
       );
       state.answerDataTitle =
         res.data?.title?.dynamicsHeaderData?.answerDataTitle || [];
@@ -239,23 +249,27 @@ const GetTableData = async () => {
     console.error("获取表格数据失败:", err);
   }
 };
+
 //获取序号
 const GetIndexNumber = (index: number) => {
   let indexCount =
     (state.pageInfo.pageNum - 1) * state.pageInfo.pageSize + index + 1;
   return indexCount;
 };
+
 // 当前页码事件
 const ChangeCurrentPage = (val: number) => {
   state.pageInfo.pageNum = val;
   GetTableData();
 };
+
 // 每页显示个数事件
 const ChangePageSize = (val: number) => {
   state.pageInfo.pageSize = val;
   state.pageInfo.pageNum = 1;
   GetTableData();
 };
+
 // 导出Excel
 const ExportExcel = () => {
   // 1. 设置加载状态
@@ -280,20 +294,23 @@ const ExportExcel = () => {
     reportModuleRef.value?.SetExportLoading?.(false);
   });
 };
+
 //打开答题卡弹窗
-const OpenStudentPaper = (row, prop) => {
+const OpenStudentPaper = (row: any) => { // 修复:移除多余的 prop 参数,添加 row 类型
   state.paperInfo = {
-    examPaperId: analysisStore.filterObject.subjectId, //考试科目id
+    examPaperId: analysisStore.filterObject?.subjectId || "", // 修复:增加可选链和默认值
     platformNumber: row.studentRegistrationCode, //学籍号平台号
     questionId: "", //题目id
   };
-  state.paperTitle = `${getExamName.value}-${analysisStore.filterObject.subjectName}-${row.className}-${row.studentUserName}`; //学生姓名
+  state.paperTitle = `${getExamName.value}-${analysisStore.filterObject?.subjectName || ''}-${row.className}-${row.studentUserName}`; //学生姓名
   state.showStudentPaperDialog = true;
 };
+
 //更新弹窗状态
 const UpdateModelValue = (val: boolean) => {
   state.showStudentPaperDialog = val;
 };
+
 // 搜索事件
 const HandleSearch = () => {
   state.pageInfo.pageNum = 1;
@@ -310,7 +327,7 @@ const PageInit = () => {
 // 监听筛选条件
 watch(
   () => analysisStore.filterObject,
-  async () => {
+  () => { // 修复:移除不必要的 async
     PageInit();
   },
   { deep: true },

+ 30 - 25
src/views/analysis/propositionAnalysis.vue

@@ -369,7 +369,8 @@ import {
   GetExcelFileName,
   propositionAnalysisStaticHeaderData,
 } from "@/utils/exportExcel";
-import {loadingSvgIcon} from "@/utils/common";
+import { loadingSvgIcon } from "@/utils/common";
+
 const analysisStore = useAnalysisStore();
 const reportModuleRef = ref<any>(null);
 
@@ -427,7 +428,9 @@ const state = reactive({
   checkList: [] as string[],
   lineChartData: {
     datax: [] as string[],
-    datay: [[], []] as number[][], // 修复: 显式指定为 number[][],防止推断为 never[][]
+    // 修复1: 在 reactive 中,直接写 [[], []] as number[][] 容易导致深层推断为 never[][]。
+    // 改为显式声明内部数组类型,确保 TS 正确推断。
+    datay: [[] as number[], [] as number[]], 
     title: ["难度", "区分度"],
     colors: [] as string[],
     markNumber: [] as number[],
@@ -436,6 +439,7 @@ const state = reactive({
     data: [] as any[],
     refresh: false,
     tableData: [] as any[],
+    headerData: [] as any[],
   },
   discriminationChart: {
     data: [] as any[],
@@ -461,7 +465,6 @@ const questionTable = computed(() => {
   return state.questionData.tableData.slice(start, end);
 });
 
-// 修复: 添加参数类型注解 isRefresh: boolean
 const PageInit = async (isRefresh: boolean) => {
   if (isRefresh) {
     state.difficultyChart.refresh = true;
@@ -474,10 +477,10 @@ const PageInit = async (isRefresh: boolean) => {
     if (code == 200 && data) {
       const { difficultyList, discriminationList, paperInfo, questionInfoList } = data;
       
-      const difficultyChart = difficultyList || []; 
+      // 修复2: 局部变量重命名,避免与 state.difficultyChart 属性同名导致作用域混淆
+      const difficultyChartData = difficultyList || []; 
       
-      // 修复: 将三元表达式赋值改为标准的 if-else,避免 TS 语法警告
-      const discriminationChart = (discriminationList || []).map((item: any) => {
+      const discriminationChartData = (discriminationList || []).map((item: any) => {
         if (item.name === "及格") {
           item.name = "一般";
         } else if (item.name === "低分") {
@@ -490,7 +493,6 @@ const PageInit = async (isRefresh: boolean) => {
       state.questionData.tableData = questionInfoList || []; 
       const length = state.questionData.totalTableData.length;
       
-      // 修复: 使用可选链 ?. 和空值合并 ?? 防止 paperInfo 为空时报错
       state.paperItems[0].value = paperInfo?.paperDifficulty ?? ""; 
       state.paperItems[1].value = paperInfo?.difficultyRate ?? ""; 
       state.paperItems[2].value = paperInfo?.paperDiscrimination ?? ""; 
@@ -498,44 +500,49 @@ const PageInit = async (isRefresh: boolean) => {
       state.paperItems[4].value = paperInfo?.paperReliability ?? ""; 
       
       state.lineChartData.datax = [];
-      state.lineChartData.datay = [[], []] as number[][]; // 修复: 重置时保持类型断言
+      // 修复3: 重置时保持与初始化一致的类型声明
+      state.lineChartData.datay = [[] as number[], [] as number[]]; 
 
       const lastItem = state.questionData.totalTableData[length - 1];
-      // 修复: 使用可选链防止 length 为 0 时越界报错
+      
+      // 修复4: API 返回的可能是字符串,使用 Number() 转换以匹配 number[] 类型
       state.lineChartData.markNumber = [
-        lastItem?.difficulty ?? 0,
-        lastItem?.discrimination ?? 0,
+        Number(lastItem?.difficulty) || 0,
+        Number(lastItem?.discrimination) || 0,
       ]; 
       
       state.questionData.totalTableData.forEach((item, index) => {
         if (index != length - 1) {
           state.lineChartData.datax.push(item.questionCode);
-          state.lineChartData.datay[0].push(item.difficulty); 
-          state.lineChartData.datay[1].push(item.discrimination); 
+          // 修复4: 确保 push 进去的是 number 类型
+          state.lineChartData.datay[0].push(Number(item.difficulty) || 0); 
+          state.lineChartData.datay[1].push(Number(item.discrimination) || 0); 
         }
       });
       
-      state.difficultyChart.data = difficultyChart.map((item: any) => {
-        return { name: item.name, value: item.scoreRate };
+      // 修复5: 确保 PieChart 接收的 value 是 number 类型
+      state.difficultyChart.data = difficultyChartData.map((item: any) => {
+        return { name: item.name, value: Number(item.scoreRate) || 0 };
       }); 
-      state.difficultyChart.tableData = difficultyChart; 
-      state.difficultyChart.headerData = data.headerList || []; 
+      state.difficultyChart.tableData = difficultyChartData; 
+      // 修复6: 如果 API 返回类型中未定义 headerList,使用 as any 绕过 TS 检查
+      state.difficultyChart.headerData = (data as any).headerList || []; 
       
-      state.discriminationChart.data = discriminationChart.map((item: any) => {
-        return { name: item.name, value: item.scoreRate };
+      state.discriminationChart.data = discriminationChartData.map((item: any) => {
+        return { name: item.name, value: Number(item.scoreRate) || 0 };
       }); 
-      state.discriminationChart.headerData = data.headerList || []; 
-      state.discriminationChart.tableData = discriminationChart; 
+      state.discriminationChart.headerData = (data as any).headerList || []; 
+      state.discriminationChart.tableData = discriminationChartData; 
       
       state.questionData.total = length; 
     } else {
       state.lineChartData.datax = [];
-      state.lineChartData.datay = [[], []] as number[][];
+      state.lineChartData.datay = [[] as number[], [] as number[]];
       state.lineChartData.title = ["难度", "区分度"];
       state.lineChartData.colors = [];
       state.lineChartData.markNumber = [];
 
-      state.paperItems.map((item) => {
+      state.paperItems.forEach((item) => {
         item.value = "";
       });
 
@@ -555,7 +562,6 @@ const PageInit = async (isRefresh: boolean) => {
   }
 };
 
-// 修复: 添加参数类型注解
 const GetFirstPieRefresh = (value: boolean) => {
   state.difficultyChart.refresh = value;
 };
@@ -563,7 +569,6 @@ const GetSecondPieRefresh = (value: boolean) => {
   state.discriminationChart.refresh = value;
 };
 
-// 修复: 添加参数类型注解
 const ChangeCurrentPage = (val: number) => {
   state.questionData.currentPage = val;
 };

+ 115 - 238
src/views/analysis/questionAnalysis.vue

@@ -464,65 +464,26 @@ import { downloadExcel, GetExcelFileName } from "@/utils/exportExcel";
 import { onMounted, reactive, watch, ref, computed, nextTick } from "vue";
 import { loadingSvgIcon } from "@/utils/common";
 import { cloneDeep } from "lodash-es";
+
 const analysisStore = useAnalysisStore();
 const reportModuleRef = ref<any>(null);
 const getExamName = computed(() => {
   return analysisStore.analysisExamInfo.examName || "";
 });
-const state = reactive({
-  questionGroupDefault: [
-    {
-      name: "小题分析",
-      code: "problem",
-    },
-    {
-      name: "题型分析",
-      code: 11,
-    },
-    {
-      name: "错题分析",
-      code: "errors",
-    },
-    {
-      name: "客观题分析",
-      code: "selectQuestion",
-    },
-    {
-      name: "命题分析",
-      code: "proposition",
-    },
-  ],
-  questionGroupList: [], //试题分组标签 动态接口获取
+
+// 1. 为 reactive 添加 <any> 泛型,解决空数组推断为 never[] 及深层对象属性访问报错
+const state = reactive<any>({
   groupTitle: "小题分析",
-  groupPreviousTitle: "",
-  knowledgeLayeredTitle: "", //知识点分层标题
   groupName: "", // 分组名称
   questionTitle: "", //题目名称
   classTitle: "", //班级名称
   optionTitle: "", //选项名称
-  studentUserName: "", //学生名称
-  studentRegistrationCode: "", //学生code
-  questionTypesData: {
-    chartKey: 0,
-    refresh: false,
-    data: [],
-    tableData: [],
-  },
   problemAnalysisData: {
     chartType: "line_bar_chart", //默认显示折线图柱状图line_bar_chart
     chartTypeList: [
-      {
-        label: "组合图",
-        value: "line_bar_chart",
-      },
-      {
-        label: "柱状图",
-        value: "vertical_bar",
-      },
-      {
-        label: "雷达图",
-        value: "radar_chart",
-      },
+      { label: "组合图", value: "line_bar_chart" },
+      { label: "柱状图", value: "vertical_bar" },
+      { label: "雷达图", value: "radar_chart" },
     ],
     legendList: [],
     defaultLegendList: [],
@@ -537,27 +498,13 @@ const state = reactive({
     questionListIndex: 0, //题目 索引
     data: [], //柱状图
   }, //小题分析数据
-  majorQuestionData: {
-    chartType: "line_bar_chart", //默认显示折线图柱状图line_bar_chart
-    legendList: [],
-    defaultLegendList: [],
-    showBarLegendIndex: 1,
-    questionListIndex: 0, //题目 索引
-    data: [], //柱状图
-  }, //分组题目 对应的题目列表
   questionScoreStatsData: {
     checked: false,
     isIndeterminate: true,
     markLineData: [], //平均分
     chartTypeList: [
-      {
-        label: "柱状图",
-        value: "vertical_bar",
-      },
-      {
-        label: "率差图",
-        value: "difference_chart",
-      },
+      { label: "柱状图", value: "vertical_bar" },
+      { label: "率差图", value: "difference_chart" },
     ],
     chartType: "vertical_bar", //默认显示率差图vertical_bar
     datax: [], //x轴数据
@@ -576,7 +523,6 @@ const state = reactive({
     answerScore: [], //答案所能得的分数
     questionType: "", //类型
     fullMark: "", //满分
-    //   average:0,//平均分辅助线
     tableData: [],
     classListIndex: 0, // 索引
   }, //小题分析 /第N题 / N班
@@ -603,89 +549,9 @@ const state = reactive({
     total: 0, //总数
     currentPage: 1, //当前页
   }, //小题分析表 + 题目分组分析
-  errorQuestionData: {
-    tableKey: 0,
-    allData: [], //
-    gradeValue: "", //选中的年级
-    className: "", //选中的年级名称
-    type: "",
-    gradeData: [], //年级下拉选项
-    tableData: [], //错题分析表数据
-  }, //错题分析表
-  studentPreviousExamData: {
-    data: null,
-    chartType: "line_chart",
-    selectList: [
-      {
-        value: "standardScore",
-        name: "标准分",
-      },
-      {
-        value: "scoreRate",
-        name: "得分率",
-      },
-      {
-        value: "classRank",
-        name: "班排",
-      },
-      {
-        value: "schoolRank",
-        name: "校排",
-      },
-      {
-        value: "examRank",
-        name: "联排",
-      },
-    ],
-    selectVal: "standardScore",
-    selectName: "标准分",
-    lineChartData: {
-      datax: [],
-      datay: [],
-      title: [],
-      tooltipData: [],
-    },
-    barChartData: {
-      //柱状图
-      legendList: [],
-      data: [],
-    },
-  }, //学生成绩历次考试分析数据
-  groupPrevious: {
-    loading: false,
-    examNameList: [],
-    examChartList: [],
-    selectedLegendList: [],
-    examChartIndex: [],
-    headerList: [],
-    dataList: [],
-  }, //题目分组历次分析(图表数据)
-  stuGroupPreviousChart: {
-    examNameList: [],
-    examChartList: [],
-    examChartIndex: [],
-  }, //学生组块历次图
-  stuGroupPreviousChartStuInfo: {
-    studentName: "", //学生姓名搜索条件
-    studentCodeList: [],
-  }, //学生历次分析图 搜索学生信息
-  stuGroupPrevious: {
-    total: 0,
-    pageNum: 1,
-    pageSize: 10,
-    headerList: [],
-    tableList: [],
-  }, //题目分组历次分析学生数据表(分页)
-  classNameList: [],
-  showBenchTaskSelect: false, //历次考试弹框
-  exportLoading: false, // 题型导出loading
-  exportErrorLoading: false, // 错题导出loading
   chartKey: 0,
-
   dataLoading: false,
   loadingText: "加载中,请稍后...",
-
-  errorPdfLoading: false, //错题导出PDF loading
   paperInfo: {
     examPaperId: "", //考试科目id
     platformNumber: "", //学籍号平台号
@@ -698,7 +564,6 @@ const state = reactive({
     platformNumber: "", //学籍号平台号
     questionId: "", //题目id
   },
-  knowledgeInputDialog: false,
   dialogData: {
     apiName: "questionStudentInfo",
     showDialog: false,
@@ -721,22 +586,26 @@ const state = reactive({
   cardQuestionId: "", // 批量查看答题卡试题id
   cardRegistrationCodeList: [], // 批量查看答题卡学生账号数组
 });
-const majorTable = ref(null);
+
+// 2. 为 ref 添加 <any> 泛型
+const majorTable = ref<any>(null);
+
+// 3. 为所有函数参数补充 :any 类型声明,解决隐式 any 报错
 //排序
-const ChangeChartOrder = (sortType, legendData, barIndex, number) => {
+const ChangeChartOrder = (sortType: any, legendData: any, barIndex: any, number: any) => {
   const isHasEstimatedScore = state.problemAnalysisData.headerList.find(
-    (item) => item.prop == "estimatedScore",
+    (item: any) => item.prop == "estimatedScore",
   ); //是否存在预估满分
   const fullVolume = state.problemAnalysisData.questionList.filter(
-    (item) => item.showCode == 999,
+    (item: any) => item.showCode == 999,
   ); //全卷
   const questionList = state.problemAnalysisData.questionList.filter(
-    (item) => item.showCode != 999,
+    (item: any) => item.showCode != 999,
   );
   const newBarIndex = isHasEstimatedScore ? barIndex - 1 : barIndex;
   if (sortType == "2") {
     //从低到高
-    questionList.sort(function (a, b) {
+    questionList.sort(function (a: any, b: any) {
       return (
         (a?.classList?.[newBarIndex]?.questionStats?.scoreRate || 0) -
         (b?.classList?.[newBarIndex]?.questionStats?.scoreRate || 0)
@@ -744,7 +613,7 @@ const ChangeChartOrder = (sortType, legendData, barIndex, number) => {
     });
   } else if (sortType == "3") {
     //从高到低
-    questionList.sort(function (a, b) {
+    questionList.sort(function (a: any, b: any) {
       return (
         (b?.classList?.[newBarIndex]?.questionStats?.scoreRate || 0) -
         (a?.classList?.[newBarIndex]?.questionStats?.scoreRate || 0)
@@ -752,16 +621,16 @@ const ChangeChartOrder = (sortType, legendData, barIndex, number) => {
     });
   } else {
     //默认排序 按题号排序
-    questionList.sort(function (a, b) {
+    questionList.sort(function (a: any, b: any) {
       return (a?.showCode || 0) - (b.showCode || 0);
     });
   }
   state.problemAnalysisData.questionList = [...questionList, ...fullVolume];
-  let chartData = [],
-    chartTitle = [];
-  state.problemAnalysisData.questionList.forEach((ques) => {
+  let chartData: any[] = [],
+    chartTitle: any[] = [];
+  state.problemAnalysisData.questionList.forEach((ques: any) => {
     const scoreRateArr = ques.classList.map(
-      (item) => item?.questionStats?.scoreRate || 0,
+      (item: any) => item?.questionStats?.scoreRate || 0,
     );
     if (isHasEstimatedScore) {
       chartData.push([
@@ -779,7 +648,7 @@ const ChangeChartOrder = (sortType, legendData, barIndex, number) => {
         ...state.problemAnalysisData.changeHeaderList,
       ]
     : [...state.problemAnalysisData.changeHeaderList];
-  newChangeHeaderList.forEach((item) => {
+  newChangeHeaderList.forEach((item: any) => {
     chartTitle.push(item.label);
   });
   chartTitle.unshift("group");
@@ -790,13 +659,15 @@ const ChangeChartOrder = (sortType, legendData, barIndex, number) => {
   //小题分析 /第N题
   GetQuestionStatsData(0);
 };
+
 //试题图表切换公共方法
-const ChangeEchartType = (value, prop) => {
+const ChangeEchartType = (value: any, prop: any) => {
   if (prop == "problemAnalysisData") {
     ChangeChartOrder("1", state.problemAnalysisData.defaultLegendList, "", 1);
   }
   state[prop].chartType = value;
 };
+
 //获取小题分析数据
 const GetQuestionAnalysisData = () => {
   state.problemAnalysisData.data = []; //柱状图折线图 X轴
@@ -805,7 +676,7 @@ const GetQuestionAnalysisData = () => {
     ...analysisStore.filterObject,
     analysisType: 0, //0-小题分析 1大题分析 2-知识点 3-能力点 question_group_code(4,5,6,7,8) 11-题型分析
   })
-    .then((res) => {
+    .then((res: any) => {
       if (
         res.code == 200 &&
         res.data &&
@@ -825,32 +696,32 @@ const GetQuestionAnalysisData = () => {
 
         state.chartKey++;
         //Y轴数据
-        let chartData = [];
-        let legendList = [];
+        let chartData: any[] = [];
+        let legendList: any[] = [];
         const isHasEstimatedScore = state.problemAnalysisData.headerList.find(
-          (item) => item.prop == "estimatedScore",
+          (item: any) => item.prop == "estimatedScore",
         ); //是否存在预估满分
-        chartData = questionList.map((item) => {
+        chartData = questionList.map((item: any) => {
           return isHasEstimatedScore
             ? [item.questionName, item.estimatedScoreRate]
             : [item.questionName];
         });
-        questionList.forEach((ques, key) => {
+        questionList.forEach((ques: any, key: number) => {
           const scoreRateArr = ques.classList.map(
-            (item) => item?.questionStats?.scoreRate || 0,
+            (item: any) => item?.questionStats?.scoreRate || 0,
           );
           chartData[key].push(...scoreRateArr);
         });
-        let chartTitle = [],
-          titleType = [],
-          classSelectLegend = [];
+        let chartTitle: any[] = [],
+          titleType: any[] = [],
+          classSelectLegend: any[] = [];
         const newChangeHeaderList = isHasEstimatedScore
           ? [
               { ...isHasEstimatedScore, isG: false, type: 1 },
               ...changeHeaderList,
             ]
           : [...changeHeaderList];
-        newChangeHeaderList.forEach((item) => {
+        newChangeHeaderList.forEach((item: any) => {
           chartTitle.push(item.label);
           titleType.push(item.type ? item.type : ""); //1柱状图 2折线
           if (
@@ -906,9 +777,6 @@ const GetQuestionAnalysisData = () => {
         state.problemAnalysisData.changeHeaderList = [];
         state.problemAnalysisData.childHeaderList = [];
 
-        state.majorQuestionData.data = [];
-        state.majorQuestionData.legendList = [];
-
         state.questionScoreStatsData.datax = [];
         state.questionScoreStatsData.datay = [];
         state.questionScoreStatsData.dataStackY = [];
@@ -949,12 +817,14 @@ const GetQuestionAnalysisData = () => {
       state.dataLoading = false;
     });
 };
+
 // 点击柱状图折线图
-const HandleChartClick = (index, name) => {
+const HandleChartClick = (index: any, name: any) => {
   GetQuestionStatsData(index); //小题分析
 };
+
 // 获取小题分析 /第N题
-const GetQuestionStatsData = (index) => {
+const GetQuestionStatsData = (index: any) => {
   const questionData = cloneDeep(state.problemAnalysisData.questionList[index]);
   state.problemAnalysisData.questionListIndex = index;
   state.questionTitle = questionData?.questionName || "";
@@ -963,9 +833,9 @@ const GetQuestionStatsData = (index) => {
     state.questionScoreStatsData.datay = []; // Y轴数据
     state.questionScoreStatsData.dataStackY = []; // Y轴数据
     state.questionScoreStatsData.tooltipData = []; // 提示框内容
-    let dataStackY = [];
+    let dataStackY: any[] = [];
     const isHasEstimatedScore = state.problemAnalysisData.headerList.find(
-      (item) => item.prop == "estimatedScore",
+      (item: any) => item.prop == "estimatedScore",
     ); //是否存在预估满分
     if (questionData.classList.length > 1) {
       const key = state.problemAnalysisData.showBarLegendIndex;
@@ -985,7 +855,7 @@ const GetQuestionStatsData = (index) => {
         );
       }
       //辅助线
-      classList.forEach((item, index) => {
+      classList.forEach((item: any, index: number) => {
         const keyIndex =
           analysisStore.filterObject.classLevel == 0 ||
           analysisStore.filterObject.classLevel == 1
@@ -1007,7 +877,7 @@ const GetQuestionStatsData = (index) => {
       state.questionScoreStatsData.rate = 60;
     }
 
-    questionData.classList.forEach((item) => {
+    questionData.classList.forEach((item: any) => {
       state.questionScoreStatsData.datax.push(item.groupName);
       state.questionScoreStatsData.datay.push(item.questionStats.scoreRate);
       dataStackY.push(item.questionStats.lossRate);
@@ -1027,9 +897,9 @@ const GetQuestionStatsData = (index) => {
     const maxValue = max.toFixed(2);
     const minValue = min.toFixed(2);
 
-    let maxClass = [],
-      minClass = [];
-    state.questionScoreStatsData.datay.forEach((item, index) => {
+    let maxClass: any[] = [],
+      minClass: any[] = [];
+    state.questionScoreStatsData.datay.forEach((item: any, index: number) => {
       if (Number(item) == Number(maxValue)) {
         maxClass.push(state.questionScoreStatsData.datax[index]);
       }
@@ -1049,21 +919,23 @@ const GetQuestionStatsData = (index) => {
   // 获取小题分析 /第N题 / 第N班
   GetQuestionAnswerData(state.problemAnalysisData.showBarLegendIndex);
 };
+
 //切换 获取小题分析 /第N题 获取第N班答题列表
-const HandleQuestionScoreChartClick = (index) => {
+const HandleQuestionScoreChartClick = (index: any) => {
   GetQuestionAnswerData(index + state.problemAnalysisData.showBarLegendIndex);
 };
+
 // 获取小题分析 /第N题 / 第N班 选项
-const GetQuestionAnswerData = (index) => {
+const GetQuestionAnswerData = (index: any) => {
   const isHasEstimatedScore = state.problemAnalysisData.headerList.find(
-    (item) => item.prop == "estimatedScore",
+    (item: any) => item.prop == "estimatedScore",
   ); //是否存在预估满分
   const newINdex = isHasEstimatedScore ? index - 1 : index;
   const classList =
     state.problemAnalysisData.questionList?.[
       state.problemAnalysisData.questionListIndex
     ]?.classList?.[newINdex];
-  // console.log("打印获取小题分析班级列表",classList)
+  
   state.questionAnswerData.answerValue =
     state.problemAnalysisData.questionList[
       state.problemAnalysisData.questionListIndex
@@ -1085,7 +957,7 @@ const GetQuestionAnswerData = (index) => {
     state.questionAnswerData.datax = []; // x轴数据
     state.questionAnswerData.datay = []; // Y轴数据
     let sum = 0;
-    answerList.forEach((item) => {
+    answerList.forEach((item: any) => {
       sum += item.studentNum;
       state.questionAnswerData.datax.push(item.name);
       state.questionAnswerData.datay.push(item.studentNum);
@@ -1123,15 +995,17 @@ const GetQuestionAnswerData = (index) => {
     }
   }
 };
+
 // 点击柱状图小题分析 /第N题 / 第N班 选项 获取答题情况
-const HandleQuestionAnswerChartClick = (index, name) => {
+const HandleQuestionAnswerChartClick = (index: any, name: any) => {
   if (analysisStore.filterObject.schoolLevel == 2) {
     //单校时展示
     GetAnswerListByAnswerAndScore(index, name);
   }
 };
+
 //通过答案或者分数查询某题作答情况
-const GetAnswerListByAnswerAndScore = (index, name) => {
+const GetAnswerListByAnswerAndScore = (index: any, name: any) => {
   state.optionTitle = name; //选项名称
   const question =
     state.problemAnalysisData.questionList[
@@ -1139,7 +1013,7 @@ const GetAnswerListByAnswerAndScore = (index, name) => {
     ];
   const classItem = question.classList[state.questionAnswerData.classListIndex];
   const classItemKeys = Object.keys(classItem);
-  const reportParam = {
+  const reportParam: any = {
     ...analysisStore.filterObject,
   };
   Object.keys(analysisStore.filterObject).forEach((item) => {
@@ -1157,7 +1031,7 @@ const GetAnswerListByAnswerAndScore = (index, name) => {
   };
   state.cardQuestionId = params.questionId; // 批量查看答题卡试题id
   state.cardRegistrationCodeList = params.registrationCodeList; // 批量查看答题卡学生账号数组
-  queryAnswerListByAnswerAndScore(params).then((res) => {
+  queryAnswerListByAnswerAndScore(params).then((res: any) => {
     if (res.code == 200) {
       state.majorAnswerData.tableData = res.data || [];
       HandleRowClick(
@@ -1170,8 +1044,9 @@ const GetAnswerListByAnswerAndScore = (index, name) => {
     }
   });
 };
+
 // 点击某行学生某题作答情况
-const HandleRowClick = (row) => {
+const HandleRowClick = (row: any) => {
   console.log("row", row);
   if (row?.studentRegistrationCode) {
     //答题卡
@@ -1185,12 +1060,13 @@ const HandleRowClick = (row) => {
       questionId: question.questionId, //题目id
     };
     state.majorAnswerData.rowIndex = state.majorAnswerData.tableData.findIndex(
-      (item) => item.studentRegistrationCode == row?.studentRegistrationCode,
+      (item: any) => item.studentRegistrationCode == row?.studentRegistrationCode,
     );
   } else {
     state.paperInfos = {};
   }
 };
+
 // 获取项目分析表
 const GetMajorTableData = () => {
   state.majorTableData.tableKey += 1;
@@ -1200,38 +1076,38 @@ const GetMajorTableData = () => {
   state.majorTableData.childHeaderData =
     state.problemAnalysisData.childHeaderList;
   const headerPropData = state.problemAnalysisData.headerList.map(
-    (item) => item.prop,
+    (item: any) => item.prop,
   ); //表头字段名
   const childHeaderPropData = state.problemAnalysisData.childHeaderList.map(
-    (item) => item.prop,
+    (item: any) => item.prop,
   ); //动态表头字段名
-  let allTableData = [];
+  let allTableData: any[] = [];
   const allList = state.problemAnalysisData.questionTableList;
-  allList.forEach((item) => {
-    let itemObj = {
+  allList.forEach((item: any) => {
+    let itemObj: any = {
       questionId: item?.questionId || "",
       knowledgeId: item?.knowledgeId || "",
     };
     const classList = item.classList;
-    headerPropData.forEach((title) => {
+    headerPropData.forEach((title: any) => {
       itemObj[title] = Array.isArray(item[title])
         ? item[title].join("、")
         : item[title];
     });
 
-    classList.forEach((el) => {
+    classList.forEach((el: any) => {
       if (
         el.questionStats?.headDataBOList &&
         el.questionStats.headDataBOList.length > 0
       ) {
-        el.questionStats.headDataBOList.forEach((bo) => {
+        el.questionStats.headDataBOList.forEach((bo: any) => {
           el.questionStats[`${bo.name}Rate`] = bo.rate;
           el.questionStats[`${bo.name}StudentNumber`] = bo.studentNumber;
         });
       } else {
         el.questionStats = [];
       }
-      childHeaderPropData.forEach((field) => {
+      childHeaderPropData.forEach((field: any) => {
         itemObj[`${el.groupId}_${field}`] = el.questionStats[field];
       });
     });
@@ -1243,6 +1119,7 @@ const GetMajorTableData = () => {
   //重置表格滚动条位置
   ResetTableScroll(); //重置表格滚动条位置
 };
+
 //重置表格滚动条位置
 const ResetTableScroll = () => {
   nextTick(() => {
@@ -1257,6 +1134,7 @@ const ResetTableScroll = () => {
     }
   });
 };
+
 // 分页获取分析表
 const GetPageMajorTableData = () => {
   const start =
@@ -1267,8 +1145,9 @@ const GetPageMajorTableData = () => {
     end,
   );
 };
+
 //设置表头字段显示隐藏
-const SetEnterHeaderData = (data) => {
+const SetEnterHeaderData = (data: any) => {
   if (data.static && data.static.length > 0) {
     state.problemAnalysisData.headerList = [...data.static];
   }
@@ -1280,8 +1159,9 @@ const SetEnterHeaderData = (data) => {
   }
   GetMajorTableData();
 };
+
 // 人员弹框
-const OpenStudentDialog = (childHeader, parent, row) => {
+const OpenStudentDialog = (childHeader: any, parent: any, row: any) => {
   if (
     (childHeader.prop.indexOf("Count") > -1 ||
       childHeader.prop.indexOf("Number") > -1) &&
@@ -1344,51 +1224,55 @@ const OpenStudentDialog = (childHeader, parent, row) => {
     state.dialogData.groupTitle = state.groupTitle.replace(/分析/g, "");
   }
 };
+
 // 关闭人员弹框
-const CloseDialog = (val) => {
+const CloseDialog = (val: any) => {
   state.dialogData.showDialog = val;
 };
+
 // 分页获取分析表
-const ChangeCurrentPage = (val) => {
+const ChangeCurrentPage = (val: any) => {
   state.majorTableData.currentPage = val;
   GetMajorTableData(); //加载分析表格数据
 };
+
 const ChangePageSize = (val: number) => {
   state.majorTableData.pageSize = val;
   state.majorTableData.currentPage = 1;
   GetMajorTableData(); //加载分析表格数据
 };
+
 // 导出Excel
 const ExportExcel = () => {
   // 1. 设置加载状态
   reportModuleRef.value?.SetExportLoading?.(true);
   // 2. 参数
   const examName = getExamName.value;
-  let params = {
+  let params: any = {
     fileName: `${GetExcelFileName(examName, analysisStore.filterObject, state.groupTitle)}`,
     examName: examName, //考试名称
     sheetName: state.groupTitle, //sheet页名称
   };
   const staticHeaderData = state.problemAnalysisData.headerList.filter(
-    (item) => item.display,
+    (item: any) => item.display,
   );
   const childHeaderData = state.problemAnalysisData.childHeaderList.filter(
-    (item) => item.display,
+    (item: any) => item.display,
   );
   const dynamicsHeaderData = state.problemAnalysisData.changeHeaderList.filter(
-    (item) => item.display,
+    (item: any) => item.display,
   );
   params.staticHeaderData = staticHeaderData;
   params.childHeaderData = childHeaderData;
   params.dynamicsHeaderData = dynamicsHeaderData;
-  let dataList = [];
-  state.majorTableData.allTableData.forEach((item) => {
-    const rowData = [];
-    staticHeaderData.forEach((header) => {
+  let dataList: any[] = [];
+  state.majorTableData.allTableData.forEach((item: any) => {
+    const rowData: any[] = [];
+    staticHeaderData.forEach((header: any) => {
       rowData.push(item?.[header.prop] || "-");
     });
-    dynamicsHeaderData.forEach((parent) => {
-      childHeaderData.forEach((child) => {
+    dynamicsHeaderData.forEach((parent: any) => {
+      childHeaderData.forEach((child: any) => {
         const itemValue = item[`${parent.prop}_${child.prop}`];
         if (child.prop.indexOf("Rate") > -1) {
           const rate = itemValue ? `${itemValue}%` : "-";
@@ -1406,22 +1290,25 @@ const ExportExcel = () => {
     reportModuleRef.value?.SetExportLoading?.(false);
   });
 };
+
 //设置表头样式
-const HeaderRowStyle = ({ row, rowIndex }) => {
+const HeaderRowStyle = ({ row, rowIndex }: any) => {
   if (rowIndex === 1) {
     return {
       display: "none",
     };
   }
 };
-const TableRowClassName = ({ row, rowIndex }) => {
+
+const TableRowClassName = ({ row, rowIndex }: any) => {
   if (rowIndex === state.majorAnswerData.rowIndex) {
     return "current-row";
   }
   return "";
 };
+
 //查看答题卡
-const handleClick = (row) => {
+const handleClick = (row: any) => {
   state.paperInfo = {
     examPaperId: analysisStore.filterObject.subjectId, //考试科目id
     platformNumber: row.studentRegistrationCode, //学籍号平台号
@@ -1430,44 +1317,33 @@ const handleClick = (row) => {
   state.paperTitle = `${getExamName.value}-${analysisStore.filterObject.subjectName}-${row.className}-${row.studentUserName}`; //学生姓名
   state.showStudentPaperDialog = true;
 };
+
 //更新弹窗状态
 const UpdateModelValue = (val: boolean) => {
   state.showStudentPaperDialog = val;
 };
+
 //批量查看小题答题卡
 const VisibleQuestionCard = () => {
   state.showQuestionCardDialog = true;
 };
+
 //关闭弹框
 const CloseQuestionCardDialog = () => {
   state.showQuestionCardDialog = false;
 };
+
 const pageInit = () => {
   state.problemAnalysisData.chartTypeList =
     analysisStore.filterObject.classLevel != 2
       ? [
-          {
-            label: "组合图",
-            value: "line_bar_chart",
-          },
-          {
-            label: "柱状图",
-            value: "vertical_bar",
-          },
-          {
-            label: "雷达图",
-            value: "radar_chart",
-          },
+          { label: "组合图", value: "line_bar_chart" },
+          { label: "柱状图", value: "vertical_bar" },
+          { label: "雷达图", value: "radar_chart" },
         ]
       : [
-          {
-            label: "柱状图",
-            value: "vertical_bar",
-          },
-          {
-            label: "雷达图",
-            value: "radar_chart",
-          },
+          { label: "柱状图", value: "vertical_bar" },
+          { label: "雷达图", value: "radar_chart" },
         ];
   state.problemAnalysisData.chartType =
     analysisStore.filterObject.classLevel != 2
@@ -1478,6 +1354,7 @@ const pageInit = () => {
   state.majorAnswerData.rowIndex = 0;
   GetQuestionAnalysisData(); //获取小题分析数据
 };
+
 // 监听筛选条件
 watch(
   () => analysisStore.filterObject,

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini