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

小题、分组、选项分析添加人员弹框

liurongli 1 сар өмнө
parent
commit
097cb45010

+ 35 - 11
src/api/analysis.ts

@@ -28,7 +28,9 @@ export const studentTranscriptTitle = (data: any): Promise<ApiResponse> => {
   });
 };
 //查询分析报告统计信息
-export const queryJointStudentStatistics = (data: any): Promise<ApiResponse> => {
+export const queryJointStudentStatistics = (
+  data: any,
+): Promise<ApiResponse> => {
   return request({
     url: "/api/v1/ai_analysis/queryJointStudentStatistics",
     method: "post",
@@ -49,7 +51,7 @@ export const studentTranscriptExcel = (data: any): Promise<ApiResponse> => {
     url: "/api/v1/ai_analysis/excel/studentTranscript",
     method: "post",
     data,
-    responseType: 'blob'
+    responseType: "blob",
   });
 };
 // ==========================================错题分析============================================
@@ -67,7 +69,7 @@ export const exportErrorQuestion = (data: any): Promise<ApiResponse> => {
     url: "/api/v1/ai_analysis/excel/export_error_question",
     method: "post",
     data,
-    responseType: 'blob'
+    responseType: "blob",
   });
 };
 // ==========================================水平分布============================================
@@ -79,7 +81,9 @@ export const scoreSegment = (data: any): Promise<ApiResponse> => {
   });
 };
 // 获取水平分布学生弹窗数据
-export const horizontalDistributionStudentInfo = (data: any): Promise<ApiResponse> => {
+export const horizontalDistributionStudentInfo = (
+  data: any,
+): Promise<ApiResponse> => {
   return request({
     url: "/api/v1/ai_analysis/horizontalDistributionStudentInfo",
     method: "post",
@@ -108,7 +112,7 @@ export const publicExportFive = (data: any): Promise<ApiResponse> => {
     url: "/api/v1/ai_analysis/excel/publicExport_five",
     method: "post",
     data,
-    responseType: 'blob'
+    responseType: "blob",
   });
 };
 // ==========================================小题分析============================================
@@ -120,13 +124,31 @@ export const questionAnalysis = (data: any): Promise<ApiResponse> => {
   });
 };
 // 通过答案或者分数查询某题作答情况
-export const queryAnswerListByAnswerAndScore = (data: any): Promise<ApiResponse> => {
+export const queryAnswerListByAnswerAndScore = (
+  data: any,
+): Promise<ApiResponse> => {
   return request({
     url: "/api/v1/ai_analysis/queryAnswerListByAnswerAndScore",
     method: "post",
     data,
   });
 };
+//小题分析获取学生弹窗
+export const questionStudentInfo = (data: any): Promise<ApiResponse> => {
+  return request({
+    url: "/api/v1/ai_analysis/questionStudentInfo",
+    method: "post",
+    data,
+  });
+};
+//小题分组分析获取学生弹窗
+export const questionGroupStudentInfo = (data: any): Promise<ApiResponse> => {
+  return request({
+    url: "/api/v1/ai_analysis/questionGroupStudentInfo",
+    method: "post",
+    data,
+  });
+};
 // ==========================================分组分析============================================
 export const questionGroupAnalysis = (data: any): Promise<ApiResponse> => {
   return request({
@@ -153,7 +175,9 @@ export const selectQuestionAnalysisPage = (data: any): Promise<ApiResponse> => {
   });
 };
 //分页获取学生作答
-export const selectQuestionStudentPageList = (data: any): Promise<ApiResponse> => {
+export const selectQuestionStudentPageList = (
+  data: any,
+): Promise<ApiResponse> => {
   return request({
     url: "/api/v1/ai_analysis/selectQuestionPageList",
     method: "post",
@@ -174,7 +198,7 @@ export const exportObjectAnalysis = (data: any): Promise<ApiResponse> => {
     url: "/api/v1/ai_analysis/excel/exportObjectAnalysis",
     method: "post",
     data,
-    responseType: 'blob'
+    responseType: "blob",
   });
 };
 // ==========================================答题卡============================================
@@ -186,7 +210,7 @@ export const findCardListNew = (data: any): Promise<ApiResponse> => {
     data,
   });
 };
-//查询学生答题卡带批阅痕迹的 
+//查询学生答题卡带批阅痕迹的
 export const getStudentPaperCardInfo = (data: any): Promise<ApiResponse> => {
   return request({
     url: "/api/v1/ai_analysis/find_student_card",
@@ -209,6 +233,6 @@ export const publicExport = (data: any): Promise<ApiResponse> => {
     url: "/api/v1/ai_analysis/excel/publicExport",
     method: "post",
     data,
-    responseType: 'blob'
+    responseType: "blob",
   });
-};
+};

BIN
src/assets/icon/header_icon.webp


+ 17 - 15
src/components/ReportModule.vue

@@ -234,7 +234,18 @@ defineExpose({
       justify-content: flex-end;
       align-items: center;
       gap: 10px;
+      :deep(.el-button){
+        span {
+          display: flex;
+          align-items: center;
+        }
 
+        img {
+          width: 16px;
+          height: 16px;
+          margin-right: 4px;
+        }
+      }
       .default_button {
         margin-left: 0px;
         height: 36px;
@@ -260,21 +271,12 @@ defineExpose({
           border: 1px solid #dcdfe6;
           color: #666;
         }
-
-        span {
-          display: flex;
-          align-items: center;
-        }
-
-        img {
-          width: 16px;
-          height: 16px;
-          margin-right: 4px;
-        }
-
-        .el-icon-loading {
-          color: #666;
-        }
+      }
+      :deep(.button_set_header) {//设置表头
+        font-size: 14px;
+        color: #2e64fa;
+        border: 1px solid #2e64fa;
+        padding: 0px 10px !important;
       }
       :deep(.right_item) {
         font-size: 14px;

+ 205 - 0
src/components/SetTableHeader.vue

@@ -0,0 +1,205 @@
+<template>
+  <el-popover
+    placement="bottom-end"
+    trigger="click"
+    :visible="showHeaderSet"
+    :width="popoverWidth"
+  >
+    <div class="page_popover">
+      <div class="popover_content">
+        <div class="vertical_checkbox_group">
+          <template v-for="item in showHeaderData" :key="item.prop">
+            <el-checkbox v-model="item.display" :disabled="item.disable">
+              {{ item.label }}
+            </el-checkbox>
+          </template>
+        </div>
+      </div>
+      <div class="popover_button">
+        <el-button @click="showHeaderSet = !showHeaderSet">取消</el-button>
+        <el-button
+          type="primary"
+          @click="SetEnterHeaderData()"
+          :loading="loading"
+        >
+          确定
+        </el-button>
+      </div>
+    </div>
+    <template #reference>
+      <el-button
+        class="button_set_header"
+        @click="showHeaderSet = !showHeaderSet"
+      >
+        <img src="@/assets/icon/header_icon.webp" />设置表头
+      </el-button>
+    </template>
+  </el-popover>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, watch } from "vue";
+
+// ================= 类型定义 =================
+interface HeaderItem {
+  label: string;
+  prop: string;
+  display: boolean;
+  disable: boolean;
+  isShow: boolean;
+  type: string; // '0' 静态  '1' 动态  '2' 子表头
+}
+
+interface HeaderSourceItem {
+  label: string;
+  prop: string;
+  display: boolean;
+}
+
+interface HeaderData {
+  static?: HeaderSourceItem[];
+  dynamisc?: HeaderSourceItem[];
+  child?: HeaderSourceItem[];
+}
+
+interface HeaderResult {
+  static: Pick<HeaderItem, "display" | "label" | "prop">[];
+  dynamisc: Pick<HeaderItem, "display" | "label" | "prop">[];
+  child: Pick<HeaderItem, "display" | "label" | "prop">[];
+}
+
+// ================= Props & Emits =================
+const props = withDefaults(
+  defineProps<{
+    data: HeaderData;
+    notEditable?: string[];
+    isShowStatic?: boolean;
+    isShowDynamisc?: boolean;
+    isShowChild?: boolean;
+  }>(),
+  {
+    data: () => ({
+      static: [],
+      dynamisc: [],
+      child: [],
+    }),
+    notEditable: () => [],
+    isShowStatic: true,
+    isShowDynamisc: true,
+    isShowChild: true,
+  },
+);
+
+const emit = defineEmits<{
+  (e: "SetEnterHeaderData", data: HeaderResult): void;
+}>();
+
+// ================= 响应式数据 =================
+const headerData = ref<HeaderItem[]>([]);
+const showHeaderSet = ref(false);
+const loading = ref(false);
+
+// ================= 计算属性 =================
+const showHeaderData = computed(() =>
+  headerData.value.filter((item) => item.isShow),
+);
+
+const popoverWidth = computed(() => {
+  return `${Math.ceil(showHeaderData.value.length / 10) * 130}px`;
+});
+
+// ================= 监听器 =================
+watch(
+  () => props.data,
+  (newVal) => {
+    if (newVal) {
+      SetAllHeaderData();
+    }
+  },
+);
+
+// ================= 方法 =================
+const SetAllHeaderData = () => {
+  headerData.value = [];
+  loading.value = false;
+
+  if (props.data.static && props.data.static.length > 0) {
+    props.data.static.forEach((item) => {
+      headerData.value.push({
+        label: item.label,
+        prop: item.prop,
+        display: item.display,
+        disable: props.notEditable.indexOf(item.prop) > -1,
+        isShow: props.isShowStatic,
+        type: "0",
+      });
+    });
+  }
+
+  if (props.data.dynamisc && props.data.dynamisc.length > 0) {
+    props.data.dynamisc.forEach((item) => {
+      headerData.value.push({
+        label: item.label,
+        prop: item.prop,
+        display: item.display,
+        disable: props.notEditable.indexOf(item.prop) > -1,
+        isShow: props.isShowDynamisc,
+        type: "1",
+      });
+    });
+  }
+
+  if (props.data.child && props.data.child.length > 0) {
+    props.data.child.forEach((item) => {
+      headerData.value.push({
+        label: item.label,
+        prop: item.prop,
+        display: item.display,
+        disable: props.notEditable.indexOf(item.prop) > -1,
+        isShow: props.isShowChild,
+        type: "2",
+      });
+    });
+  }
+};
+const SetEnterHeaderData = () => {
+  loading.value = true;
+
+  const staticList: HeaderResult["static"] = [];
+  const dynamiscList: HeaderResult["dynamisc"] = [];
+  const childList: HeaderResult["child"] = [];
+
+  headerData.value.forEach((item) => {
+    const resultItem = {
+      display: item.display,
+      label: item.label,
+      prop: item.prop,
+    };
+    if (item.type === "0") staticList.push(resultItem);
+    if (item.type === "1") dynamiscList.push(resultItem);
+    if (item.type === "2") childList.push(resultItem);
+  });
+
+  const isAllDisplay = childList.every((item) => !item.display);
+  dynamiscList.forEach((item) => {
+    item.display = isAllDisplay ? false : item.display;
+  });
+
+  emit("SetEnterHeaderData", {
+    static: staticList,
+    dynamisc: dynamiscList,
+    child: childList,
+  });
+
+  loading.value = false;
+  showHeaderSet.value = false;
+};
+</script>
+
+<style scoped lang="scss">
+.vertical_checkbox_group{
+    .el-checkbox{
+        height: 20px !important;
+    }
+}
+</style>

+ 6 - 6
src/components/StudentList.vue

@@ -123,11 +123,7 @@
             ></el-table-column>
           </el-table>
         </div>
-        <div
-          class="page_pagination"
-          v-if="pageData.total > pageData.pageSize"
-          style="margin-top: 20px; margin-bottom: 0px"
-        >
+        <div class="page_pagination" v-if="pageData.total > pageData.pageSize">
           <el-pagination
             @current-change="ChangePage"
             layout="prev, pager, next"
@@ -147,13 +143,15 @@ import {
   publicExport,
   horizontalDistributionStudentInfo,
   contrastStudentInfo,
+  questionStudentInfo,
+  questionGroupStudentInfo,
 } from "@/api/analysis";
 import {
   downloadExcel,
   GetExcelFileName,
   studentListStaticHeaderData,
 } from "@/utils/exportExcel";
-import {loadingSvgIcon} from "@/utils/common";
+import { loadingSvgIcon } from "@/utils/common";
 import { useAnalysisStore } from "@/store/analysis";
 const analysisStore = useAnalysisStore();
 const filterObject = computed(() => analysisStore.filterObject || {});
@@ -175,6 +173,8 @@ const props = defineProps({
 const apiMap = {
   horizontalDistributionStudentInfo,
   contrastStudentInfo,
+  questionStudentInfo,
+  questionGroupStudentInfo,
   // 后续有其他接口也在这里添加
 };
 const emit = defineEmits(["CloseDialog"]);

+ 0 - 1
src/views/analysis/classComparison.vue

@@ -316,7 +316,6 @@
     :showDialog.sync="state.dialogData.showDialog"
     :title="state.dialogData.title"
     :tableTitle="state.dialogData.tableTitle"
-    :isStudentRegistrationCode="false"
     apiName="contrastStudentInfo"
     :studentParam="{
       fiveRateName: state.dialogData.fiveRateName, //当前选择的五率的名称

+ 123 - 5
src/views/analysis/groupAnalysis.vue

@@ -384,6 +384,18 @@
     @ChangeCurrentPage="ChangeCurrentPage"
     @ExportExcel="ExportExcel"
   >
+    <template #title_right>
+      <SetTableHeader
+        :data="{
+          static: state.majorTableData.headerData,
+          dynamisc: state.majorTableData.changeHeaderData,
+          child: state.majorTableData.childHeaderData,
+        }"
+        :isShowDynamisc="false"
+        :notEditable="['questionName']"
+        @SetEnterHeaderData="SetEnterHeaderData"
+      ></SetTableHeader>
+    </template>
     <template #module_table_chart>
       <el-table
         :data="state.majorTableData.tableData"
@@ -479,6 +491,28 @@
     :pageTitle="state.paperTitle"
     @updateModelValue="UpdateModelValue"
   ></StudentPaper>
+  <StudentList
+    :showDialog.sync="state.dialogData.showDialog"
+    :title="state.dialogData.title"
+    :tableTitle="state.dialogData.tableTitle"
+    :apiName="state.dialogData.apiName"
+    :studentParam="{
+      fiveRateName: state.dialogData.fiveRateName, //当前选择的五率的名称
+      selectSubjectName: state.dialogData.selectSubjectName, //当前选择的科目名称
+      selectSchoolLevel: state.dialogData.selectSchoolLevel, //当前选择的学校级别:0-联考 1-学校分组 2-具体学校
+      selectSchoolName: state.dialogData.selectSchoolName, //当前选择的学校名称:联校 组合学校 单校名称
+      selectClassLevel: state.dialogData.selectClassLevel, //当前选择的班级级别:0-年级 1-组合班级 2-具体班级
+      selectClassName: state.dialogData.selectClassName, //当前选择的班级名称:年级 组合班级 单班
+      questionId: state.dialogData.questionId, //试题ID
+      isPaper: state.dialogData.isPaper, //是否为全卷0-非1-是全卷
+      quesScoreType: state.dialogData.quesScoreType, //选择的类型0-满分人数 1-0分人数 3-优秀率、良好率等
+      knowledgeId: state.dialogData.knowledgeId, //如果选择的是知识点,则需要返回知识点ID
+      questionGroupName: state.dialogData.questionGroupName, //试题分组的名称
+      groupTitle: state.dialogData.groupTitle,
+    }"
+    @CloseDialog="CloseDialog"
+  >
+  </StudentList>
 </template>
 <script lang="ts" setup>
 import ReportModule from "@/components/ReportModule.vue";
@@ -489,7 +523,7 @@ import { useAnalysisStore } from "@/store/analysis";
 import {
   questionGroupAnalysis,
   queryAnswerListByAnswerAndScore,
-  publicExport
+  publicExport,
 } from "@/api/analysis";
 import BarLineCharts from "@/components/echarts/barLineCharts.vue"; //柱状图折线图组合图组件
 import RadarCharts from "@/components/echarts/radarCharts.vue"; //雷达图
@@ -498,8 +532,10 @@ import BarScoringRateVertical from "@/components/echarts/barScoringRate_vertical
 import DifferenceChart from "@/components/echarts/differenceChart.vue"; //率差图
 import BarChart from "@/components/echarts/barChart_answer.vue"; //单柱状图
 import StudentQuestionImg from "@/components/StudentQuestionImg.vue"; // 学生小题答题卡组件
+import SetTableHeader from "@/components/setTableHeader.vue"; //设置表头
+import StudentList from "@/components/StudentList.vue";
 import { downloadExcel, GetExcelFileName } from "@/utils/exportExcel";
-import {loadingSvgIcon} from "@/utils/common";
+import { loadingSvgIcon } from "@/utils/common";
 import { onMounted, reactive, watch, ref, computed, nextTick } from "vue";
 import { cloneDeep } from "lodash-es";
 const analysisStore = useAnalysisStore();
@@ -754,7 +790,7 @@ const state = reactive({
   },
   knowledgeInputDialog: false,
   dialogData: {
-    apiName: "",
+    apiName: "questionGroupStudentInfo",
     showDialog: false,
     title: "",
     tableTitle: "",
@@ -1402,7 +1438,89 @@ const ResetTableScroll = () => {
     }
   });
 };
-const ChangeCurrentPage = (val) => {
+//设置表头字段显示隐藏
+const SetEnterHeaderData = (data) => {
+  if (data.static && data.static.length > 0) {
+    state.problemAnalysisData.headerList = [...data.static];
+  }
+  if (data.dynamisc && data.dynamisc.length > 0) {
+    state.problemAnalysisData.changeHeaderList = [...data.dynamisc];
+  }
+  if (data.child && data.child.length > 0) {
+    state.problemAnalysisData.childHeaderList = [...data.child];
+  }
+  GetMajorTableData();
+};
+// 人员弹框
+const OpenStudentDialog = (childHeader, parent, row) => {
+  if (
+    (childHeader.prop.indexOf("Count") > -1 ||
+      childHeader.prop.indexOf("Number") > -1) &&
+    row?.[`${parent.prop}_${childHeader.prop}`] > 0
+  ) {
+    state.dialogData.showDialog = true;
+    state.dialogData.title = childHeader.label;
+    state.dialogData.tableTitle = parent.label;
+    state.dialogData.fiveRateName = "";
+    if (
+      childHeader.prop == "topStudentNumber" ||
+      childHeader.prop == "excellentStudentNumber" ||
+      childHeader.prop == "goodStudentNumber" ||
+      childHeader.prop == "passStudentNumber" ||
+      childHeader.prop == "lowStudentNumber"
+    ) {
+      state.dialogData.fiveRateName = childHeader.label.replace(/人数/g, ""); //当前选择的五率的名称
+    }
+    state.dialogData.selectSubjectName = analysisStore.filterObject.subjectName; //当前选择的科目名称
+    if (parent.prop == "0") {
+      state.dialogData.selectSchoolLevel = 0; //当前选择的学校级别:0-联考 1-学校分组 2-具体学校
+      state.dialogData.selectSchoolName = parent.label; //当前选择的学校名称:联校 组合学校 单校名称
+      state.dialogData.selectClassLevel = 0; //当前选择的班级级别:0-年级 1-组合班级 2-具体班级
+      state.dialogData.selectClassName = "年级"; //当前选择的班级名称:年级 组合班级 单班
+    } else if (parent.prop.indexOf("school_group") > -1) {
+      state.dialogData.selectSchoolLevel = 1; //当前选择的学校级别:0-联考 1-学校分组 2-具体学校
+      state.dialogData.selectSchoolName = parent.label; //当前选择的学校名称:联校 组合学校 单校名称
+      state.dialogData.selectClassLevel = 0; //当前选择的班级级别:0-年级 1-组合班级 2-具体班级
+      state.dialogData.selectClassName = "年级"; //当前选择的班级名称:年级 组合班级 单班
+    } else if (
+      parent.prop.indexOf("school") > -1 &&
+      parent.prop.indexOf("school_group") == -1
+    ) {
+      state.dialogData.selectSchoolLevel = 2; //当前选择的学校级别:0-联考 1-学校分组 2-具体学校
+      state.dialogData.selectSchoolName = parent.label; //当前选择的学校名称:联校 组合学校 单校名称
+      state.dialogData.selectClassLevel = 0; //当前选择的班级级别:0-年级 1-组合班级 2-具体班级
+      state.dialogData.selectClassName = "年级"; //当前选择的班级名称:年级 组合班级 单班
+    } else if (parent.prop.indexOf("class_group") > -1) {
+      state.dialogData.selectSchoolLevel = 2; //当前选择的学校级别:0-联考 1-学校分组 2-具体学校
+      state.dialogData.selectSchoolName = analysisStore.filterObject.schoolName; //当前选择的学校名称:联校 组合学校 单校名称
+      state.dialogData.selectClassLevel = 1; //当前选择的班级级别:0-年级 1-组合班级 2-具体班级
+      state.dialogData.selectClassName = parent.label; //当前选择的班级名称:年级 组合班级 单班
+    } else {
+      state.dialogData.selectSchoolLevel = 2; //当前选择的学校级别:0-联考 1-学校分组 2-具体学校
+      state.dialogData.selectSchoolName = analysisStore.filterObject.schoolName; //当前选择的学校名称:联校 组合学校 单校名称
+      state.dialogData.selectClassLevel = 2; //当前选择的班级级别:0-年级 1-组合班级 2-具体班级
+      state.dialogData.selectClassName = parent.label; //当前选择的班级名称:年级 组合班级 单班
+    }
+    state.dialogData.questionId = row.questionId; //试题ID
+    state.dialogData.isPaper = row.questionName == "全卷" ? 1 : 0; //是否为全卷0-非1-是全卷
+    if (childHeader.prop == "fullMarkCount") {
+      state.dialogData.quesScoreType = 0; //选择的类型0-满分人数 1-0分人数 3-优秀率、良好率等
+    } else if (childHeader.prop == "zeroMarkCount") {
+      state.dialogData.quesScoreType = 1; //选择的类型0-满分人数 1-0分人数 3-优秀率、良好率等
+    } else {
+      state.dialogData.quesScoreType = 3; //选择的类型0-满分人数 1-0分人数 3-优秀率、良好率等
+    }
+    state.dialogData.knowledgeId = row.knowledgeId; //如果选择的是知识点,则需要返回知识点ID
+    state.dialogData.questionGroupName = row?.groupName || ""; //试题分组的名称
+    state.dialogData.groupTitle = state.groupTitle.replace(/分析/g, "");
+  }
+};
+// 关闭人员弹框
+const CloseDialog = (val: boolean) => {
+  state.dialogData.showDialog = val;
+};
+// 分页获取分析表
+const ChangeCurrentPage = (val: number) => {
   state.majorTableData.currentPage = val;
   GetMajorTableData(); //加载分析表格数据
 };
@@ -1474,7 +1592,7 @@ const TableRowClassName = ({ row, rowIndex }) => {
   return "";
 };
 //查看答题卡
-const handleClick = (row) =>  {
+const handleClick = (row) => {
   state.paperInfo = {
     examPaperId: analysisStore.filterObject.subjectId, //考试科目id
     platformNumber: row.studentRegistrationCode, //学籍号平台号

+ 0 - 1
src/views/analysis/levelDistribution.vue

@@ -176,7 +176,6 @@
     :showDialog.sync="state.dialogData.showDialog"
     :title="state.dialogData.title"
     :tableTitle="state.dialogData.tableTitle"
-    :isStudentRegistrationCode="false"
     apiName="horizontalDistributionStudentInfo"
     :studentParam="{
       examId: state.dialogData.examId,

+ 128 - 27
src/views/analysis/optionAnalysis.vue

@@ -253,21 +253,21 @@
     </template>
   </ReportModule>
   <!-- 各选项选择人数 弹框 -->
-  <!-- <div class="page_dialog">
-    <el-dialog :title="dialogData.title" :visible.sync="dialogData.show">
-      <div class="dialog_center padding_20" :key="dialogData.dialogKey">
+  <div class="page_dialog">
+    <el-dialog :title="state.dialogData.title" v-model="state.dialogData.show">
+      <div class="dialog_center" :key="state.dialogData.dialogKey">
         <div class="center_header">
           <div class="header_left left_center">
-            <span class="left_count">总计:{{ dialogData.total }}人</span>
+            <span class="left_count">总计:{{ state.dialogData.total }}人</span>
           </div>
           <div class="header_right report_button">
             <el-button
               class="button_task_select"
-              :loading="dialogExportLoading"
+              :loading="state.dialogExportLoading"
               @click="DialogExportExcel()"
             >
               <img
-                v-if="!dialogExportLoading"
+                v-if="!state.dialogExportLoading"
                 src="@/assets/icon/export_icon.webp"
                 alt="导出Excel"
               />导出Excel
@@ -275,9 +275,9 @@
           </div>
         </div>
         <div class="page_jg_20"></div>
-        <div class="module_table" style="width: 100%">
+        <div class="page_table table_row_42" style="width: 100%">
           <el-table
-            :data="dialogData.list"
+            :data="state.dialogData.list"
             height="471px"
             border
             stripe
@@ -290,9 +290,10 @@
               width="50"
               fixed
             >
-              <template slot-scope="{ $index }">
+              <template #default="{ $index }">
                 {{
-                  (dialogData.currentPage - 1) * dialogData.pageSize +
+                  (state.dialogData.currentPage - 1) *
+                    state.dialogData.pageSize +
                   $index +
                   1
                 }}
@@ -328,7 +329,7 @@
               width="80"
               show-overflow-tooltip
             >
-              <template slot-scope="{ row }">
+              <template #default="{ row }">
                 {{ row.className || "-" }}
               </template>
             </el-table-column>
@@ -339,19 +340,19 @@
               width="80"
               show-overflow-tooltip
             >
-              <template slot-scope="{ row }">
+              <template #default="{ row }">
                 {{ row.teacherClassName || "-" }}
               </template>
             </el-table-column>
             <el-table-column
               align="center"
-              v-for="(item, index) in dialogData.headerList"
+              v-for="(item, index) in state.dialogData.headerList"
               :key="item"
               :label="item"
               min-width="60"
               show-overflow-tooltip
             >
-              <template slot="header">
+              <template #header>
                 <div class="table_header">
                   <el-tooltip
                     class="item"
@@ -363,7 +364,7 @@
                   </el-tooltip>
                 </div>
               </template>
-              <template slot-scope="scope">
+              <template #default="scope">
                 <span
                   :style="{
                     color:
@@ -377,25 +378,20 @@
               </template>
             </el-table-column>
           </el-table>
-          <div
-            class="page_pagination"
-            style="margin-top: 10px; padding-bottom: 10px"
-            v-if="dialogData.total > 10"
-          >
+          <div class="page_pagination" v-if="state.dialogData.total > 10">
             <el-pagination
               @current-change="GetStudentTableDataChangePage"
               layout="prev, pager, next"
-              :current-page="dialogData.currentPage"
-              :page-size="dialogData.pageSize"
-              :total="dialogData.total"
+              :current-page="state.dialogData.currentPage"
+              :page-size="state.dialogData.pageSize"
+              :total="state.dialogData.total"
             >
             </el-pagination>
           </div>
-          <div v-else class="page_jg_20"></div>
         </div>
       </div>
     </el-dialog>
-  </div> -->
+  </div>
 </template>
 <script lang="ts" setup>
 import ReportModule from "@/components/ReportModule.vue";
@@ -611,6 +607,7 @@ const showDialogData = (
   if (!studentNum || studentNum == "0" || studentNum == "-") {
     return false;
   }
+  console.log("questionCode22");
   state.dialogData.title = `${questionCode} ${answer}选项人数`;
   state.dialogData.show = true;
   state.dialogData.currentPage = 1;
@@ -629,8 +626,6 @@ const GetSelectQuestionStudentPageList = (studentCodeList, studentNum) => {
   selectQuestionStudentPageList({
     ...analysisStore.filterObject,
     studentCodeList: studentCodeListData,
-    // questionId,//题号
-    // answer,//学生选择的答案
     pageParam: {
       pageNum: 1, //当前页码
       pageSize: state.dialogData.pageSize, //每页显示行数
@@ -826,4 +821,110 @@ onMounted(() => {
     }
   }
 }
+//页面表格公共样式
+:deep(.page_table) {
+  width: 100%;
+
+  // 表格悬浮效果覆盖
+  .el-table__body tr:hover > td {
+    background-color: #eaf0ff !important; // 蓝色系悬浮背景
+  }
+
+  //表格竖着的边框线样式
+  th.el-table__cell {
+    border-right: 1px solid #e9e9e9;
+  }
+  .el-table th.el-table__cell.is-leaf,
+  .el-table td.el-table__cell {
+    border-bottom: 1px solid #e9e9e9;
+  }
+
+  .el-table {
+    min-height: 463px;
+    border: 0px;
+  }
+}
+//表格42行高
+.table_row_42 {
+  :deep(.el-table) {
+    thead {
+      color: #333 !important;
+      font-weight: 500;
+      font-size: 14px;
+      height: 42px !important;
+    }
+    .el-table__cell {
+      padding: 0;
+      height: 42px !important;
+    }
+  }
+}
+:deep(.report_button) {
+  display: flex;
+  gap: 10px;
+  .el-input-group__append {
+    .el-button {
+      width: 100%;
+      justify-content: center;
+    }
+  }
+
+  .el-icon-loading {
+    color: #666;
+  }
+  .el-button {
+    margin-left: 0px;
+    height: 36px;
+    line-height: 36px;
+    padding: 0 10px;
+    display: flex;
+    align-items: center;
+
+    span {
+      // padding: 0px 10px;
+      display: flex;
+      align-items: center;
+    }
+
+    img {
+      width: 16px;
+      height: 16px;
+      margin-right: 4px;
+    }
+  }
+
+  .el-button--default {
+    color: #666;
+    font-weight: 400;
+    &.is-disabled {
+      color: #bbbbbb;
+      border-color: #bbbbbb;
+    }
+  }
+  .el-button--primary.is-plain:hover {
+    color: #2e64fa;
+    background-color: #c0d1fe;
+    font-size: 14px;
+  }
+
+  //其他按钮可在下面追加
+  //对标任务选择按钮
+  .button_task_select {
+    // 去掉按钮点击后的样式
+    &:active,
+    &:focus {
+      outline: none; // 去掉点击后的轮廓
+      box-shadow: none; // 去掉点击后的阴影
+      background-color: inherit; // 保持背景颜色不变
+      color: #666666; // 保持文字颜色不变
+      border: 1px solid #dcdfe6;
+    }
+  }
+
+  .button_task_select:hover {
+    background-color: #f9f9f9;
+    border: 1px solid #dcdfe6;
+    color: #666;
+  }
+}
 </style>

+ 132 - 14
src/views/analysis/questionAnalysis.vue

@@ -310,6 +310,18 @@
     @ChangeCurrentPage="ChangeCurrentPage"
     @ExportExcel="ExportExcel"
   >
+    <template #title_right>
+      <SetTableHeader
+        :data="{
+          static: state.majorTableData.headerData,
+          dynamisc: state.majorTableData.changeHeaderData,
+          child: state.majorTableData.childHeaderData,
+        }"
+        :isShowDynamisc="false"
+        :notEditable="['questionName']"
+        @SetEnterHeaderData="SetEnterHeaderData"
+      ></SetTableHeader>
+    </template>
     <template #module_table_chart>
       <el-table
         :data="state.majorTableData.tableData"
@@ -405,6 +417,28 @@
     :pageTitle="state.paperTitle"
     @updateModelValue="UpdateModelValue"
   ></StudentPaper>
+  <StudentList
+    :showDialog.sync="state.dialogData.showDialog"
+    :title="state.dialogData.title"
+    :tableTitle="state.dialogData.tableTitle"
+    :apiName="state.dialogData.apiName"
+    :studentParam="{
+      fiveRateName: state.dialogData.fiveRateName, //当前选择的五率的名称
+      selectSubjectName: state.dialogData.selectSubjectName, //当前选择的科目名称
+      selectSchoolLevel: state.dialogData.selectSchoolLevel, //当前选择的学校级别:0-联考 1-学校分组 2-具体学校
+      selectSchoolName: state.dialogData.selectSchoolName, //当前选择的学校名称:联校 组合学校 单校名称
+      selectClassLevel: state.dialogData.selectClassLevel, //当前选择的班级级别:0-年级 1-组合班级 2-具体班级
+      selectClassName: state.dialogData.selectClassName, //当前选择的班级名称:年级 组合班级 单班
+      questionId: state.dialogData.questionId, //试题ID
+      isPaper: state.dialogData.isPaper, //是否为全卷0-非1-是全卷
+      quesScoreType: state.dialogData.quesScoreType, //选择的类型0-满分人数 1-0分人数 3-优秀率、良好率等
+      knowledgeId: state.dialogData.knowledgeId, //如果选择的是知识点,则需要返回知识点ID
+      questionGroupName: state.dialogData.questionGroupName, //试题分组的名称
+      groupTitle: state.dialogData.groupTitle,
+    }"
+    @CloseDialog="CloseDialog"
+  >
+  </StudentList>
 </template>
 <script lang="ts" setup>
 import ReportModule from "@/components/ReportModule.vue";
@@ -415,7 +449,7 @@ import { useAnalysisStore } from "@/store/analysis";
 import {
   questionAnalysis,
   queryAnswerListByAnswerAndScore,
-  publicExport
+  publicExport,
 } from "@/api/analysis";
 import BarLineCharts from "@/components/echarts/barLineCharts.vue"; //柱状图折线图组合图组件
 import RadarCharts from "@/components/echarts/radarCharts.vue"; //雷达图
@@ -424,9 +458,11 @@ import BarScoringRateVertical from "@/components/echarts/barScoringRate_vertical
 import DifferenceChart from "@/components/echarts/differenceChart.vue"; //率差图
 import BarChart from "@/components/echarts/barChart_answer.vue"; //单柱状图
 import StudentQuestionImg from "@/components/StudentQuestionImg.vue"; // 学生小题答题卡组件
+import SetTableHeader from "@/components/setTableHeader.vue"; //设置表头
+import StudentList from "@/components/StudentList.vue";
 import { downloadExcel, GetExcelFileName } from "@/utils/exportExcel";
 import { onMounted, reactive, watch, ref, computed, nextTick } from "vue";
-import {loadingSvgIcon} from "@/utils/common";
+import { loadingSvgIcon } from "@/utils/common";
 import { cloneDeep } from "lodash-es";
 const analysisStore = useAnalysisStore();
 const reportModuleRef = ref<any>(null);
@@ -641,7 +677,6 @@ const state = reactive({
     tableList: [],
   }, //题目分组历次分析学生数据表(分页)
   classNameList: [],
-  showHeaderSet: false, //是否显示设置表头
   showBenchTaskSelect: false, //历次考试弹框
   exportLoading: false, // 题型导出loading
   exportErrorLoading: false, // 错题导出loading
@@ -665,7 +700,7 @@ const state = reactive({
   },
   knowledgeInputDialog: false,
   dialogData: {
-    apiName: "",
+    apiName: "questionStudentInfo",
     showDialog: false,
     title: "",
     tableTitle: "",
@@ -1208,15 +1243,6 @@ const GetMajorTableData = () => {
   //重置表格滚动条位置
   ResetTableScroll(); //重置表格滚动条位置
 };
-const GetPageMajorTableData = () => {
-  const start =
-    (state.majorTableData.currentPage - 1) * state.majorTableData.pageSize;
-  const end = start + state.majorTableData.pageSize;
-  state.majorTableData.tableData = state.majorTableData.allTableData.slice(
-    start,
-    end,
-  );
-};
 //重置表格滚动条位置
 const ResetTableScroll = () => {
   nextTick(() => {
@@ -1231,6 +1257,98 @@ const ResetTableScroll = () => {
     }
   });
 };
+// 分页获取分析表
+const GetPageMajorTableData = () => {
+  const start =
+    (state.majorTableData.currentPage - 1) * state.majorTableData.pageSize;
+  const end = start + state.majorTableData.pageSize;
+  state.majorTableData.tableData = state.majorTableData.allTableData.slice(
+    start,
+    end,
+  );
+};
+//设置表头字段显示隐藏
+const SetEnterHeaderData = (data) => {
+  if (data.static && data.static.length > 0) {
+    state.problemAnalysisData.headerList = [...data.static];
+  }
+  if (data.dynamisc && data.dynamisc.length > 0) {
+    state.problemAnalysisData.changeHeaderList = [...data.dynamisc];
+  }
+  if (data.child && data.child.length > 0) {
+    state.problemAnalysisData.childHeaderList = [...data.child];
+  }
+  GetMajorTableData();
+};
+// 人员弹框
+const OpenStudentDialog = (childHeader, parent, row) => {
+  if (
+    (childHeader.prop.indexOf("Count") > -1 ||
+      childHeader.prop.indexOf("Number") > -1) &&
+    row?.[`${parent.prop}_${childHeader.prop}`] > 0
+  ) {
+    state.dialogData.showDialog = true;
+    state.dialogData.title = childHeader.label;
+    state.dialogData.tableTitle = parent.label;
+    state.dialogData.fiveRateName = "";
+    if (
+      childHeader.prop == "topStudentNumber" ||
+      childHeader.prop == "excellentStudentNumber" ||
+      childHeader.prop == "goodStudentNumber" ||
+      childHeader.prop == "passStudentNumber" ||
+      childHeader.prop == "lowStudentNumber"
+    ) {
+      state.dialogData.fiveRateName = childHeader.label.replace(/人数/g, ""); //当前选择的五率的名称
+    }
+    state.dialogData.selectSubjectName = analysisStore.filterObject.subjectName; //当前选择的科目名称
+    if (parent.prop == "0") {
+      state.dialogData.selectSchoolLevel = 0; //当前选择的学校级别:0-联考 1-学校分组 2-具体学校
+      state.dialogData.selectSchoolName = parent.label; //当前选择的学校名称:联校 组合学校 单校名称
+      state.dialogData.selectClassLevel = 0; //当前选择的班级级别:0-年级 1-组合班级 2-具体班级
+      state.dialogData.selectClassName = "年级"; //当前选择的班级名称:年级 组合班级 单班
+    } else if (parent.prop.indexOf("school_group") > -1) {
+      state.dialogData.selectSchoolLevel = 1; //当前选择的学校级别:0-联考 1-学校分组 2-具体学校
+      state.dialogData.selectSchoolName = parent.label; //当前选择的学校名称:联校 组合学校 单校名称
+      state.dialogData.selectClassLevel = 0; //当前选择的班级级别:0-年级 1-组合班级 2-具体班级
+      state.dialogData.selectClassName = "年级"; //当前选择的班级名称:年级 组合班级 单班
+    } else if (
+      parent.prop.indexOf("school") > -1 &&
+      parent.prop.indexOf("school_group") == -1
+    ) {
+      state.dialogData.selectSchoolLevel = 2; //当前选择的学校级别:0-联考 1-学校分组 2-具体学校
+      state.dialogData.selectSchoolName = parent.label; //当前选择的学校名称:联校 组合学校 单校名称
+      state.dialogData.selectClassLevel = 0; //当前选择的班级级别:0-年级 1-组合班级 2-具体班级
+      state.dialogData.selectClassName = "年级"; //当前选择的班级名称:年级 组合班级 单班
+    } else if (parent.prop.indexOf("class_group") > -1) {
+      state.dialogData.selectSchoolLevel = 2; //当前选择的学校级别:0-联考 1-学校分组 2-具体学校
+      state.dialogData.selectSchoolName = analysisStore.filterObject.schoolName; //当前选择的学校名称:联校 组合学校 单校名称
+      state.dialogData.selectClassLevel = 1; //当前选择的班级级别:0-年级 1-组合班级 2-具体班级
+      state.dialogData.selectClassName = parent.label; //当前选择的班级名称:年级 组合班级 单班
+    } else {
+      state.dialogData.selectSchoolLevel = 2; //当前选择的学校级别:0-联考 1-学校分组 2-具体学校
+      state.dialogData.selectSchoolName = analysisStore.filterObject.schoolName; //当前选择的学校名称:联校 组合学校 单校名称
+      state.dialogData.selectClassLevel = 2; //当前选择的班级级别:0-年级 1-组合班级 2-具体班级
+      state.dialogData.selectClassName = parent.label; //当前选择的班级名称:年级 组合班级 单班
+    }
+    state.dialogData.questionId = row.questionId; //试题ID
+    state.dialogData.isPaper = row.questionName == "全卷" ? 1 : 0; //是否为全卷0-非1-是全卷
+    if (childHeader.prop == "fullMarkCount") {
+      state.dialogData.quesScoreType = 0; //选择的类型0-满分人数 1-0分人数 3-优秀率、良好率等
+    } else if (childHeader.prop == "zeroMarkCount") {
+      state.dialogData.quesScoreType = 1; //选择的类型0-满分人数 1-0分人数 3-优秀率、良好率等
+    } else {
+      state.dialogData.quesScoreType = 3; //选择的类型0-满分人数 1-0分人数 3-优秀率、良好率等
+    }
+    state.dialogData.knowledgeId = row.knowledgeId; //如果选择的是知识点,则需要返回知识点ID
+    state.dialogData.questionGroupName = row?.groupName || ""; //试题分组的名称
+    state.dialogData.groupTitle = state.groupTitle.replace(/分析/g, "");
+  }
+};
+// 关闭人员弹框
+const CloseDialog = (val) => {
+  state.dialogData.showDialog = val;
+};
+// 分页获取分析表
 const ChangeCurrentPage = (val) => {
   state.majorTableData.currentPage = val;
   GetMajorTableData(); //加载分析表格数据
@@ -1303,7 +1421,7 @@ const TableRowClassName = ({ row, rowIndex }) => {
   return "";
 };
 //查看答题卡
-const handleClick = (row) =>  {
+const handleClick = (row) => {
   state.paperInfo = {
     examPaperId: analysisStore.filterObject.subjectId, //考试科目id
     platformNumber: row.studentRegistrationCode, //学籍号平台号