Просмотр исходного кода

水平分布、班级对比显示人数弹框

liurongli 1 месяц назад
Родитель
Сommit
92c8ac31b1

+ 16 - 0
src/api/analysis.ts

@@ -78,6 +78,14 @@ export const scoreSegment = (data: any): Promise<ApiResponse> => {
     data,
   });
 };
+// 获取水平分布学生弹窗数据
+export const horizontalDistributionStudentInfo = (data: any): Promise<ApiResponse> => {
+  return request({
+    url: "/api/v1/ai_analysis/horizontalDistributionStudentInfo",
+    method: "post",
+    data,
+  });
+};
 // ==========================================班级对比============================================
 export const classContrastSubjectTable = (data: any): Promise<ApiResponse> => {
   return request({
@@ -86,6 +94,14 @@ export const classContrastSubjectTable = (data: any): Promise<ApiResponse> => {
     data,
   });
 };
+// 对比获取学生弹窗数据
+export const contrastStudentInfo = (data: any): Promise<ApiResponse> => {
+  return request({
+    url: "/api/v1/ai_analysis/contrastStudentInfo",
+    method: "post",
+    data,
+  });
+};
 // 导出
 export const publicExportFive = (data: any): Promise<ApiResponse> => {
   return request({

+ 615 - 0
src/components/StudentList.vue

@@ -0,0 +1,615 @@
+<template>
+  <div class="page_dialog">
+    <el-dialog
+      :title="title"
+      center
+      v-model="isShowDialog"
+      @close="CloseDialog"
+      width="830px"
+    >
+      <div class="dialog_center">
+        <div class="center_header">
+          <div class="header_left left_center">
+            <span class="left_count">
+              <template v-if="tableTitle">{{ tableTitle }}</template>
+              总计:{{ studentList.length }}人
+            </span>
+          </div>
+          <div class="header_right report_button">
+            <el-button
+              class="button_task_select"
+              :loading="exportLoading"
+              @click="ExportExcel"
+            >
+              <img
+                v-if="!exportLoading"
+                src="@/assets/icon/export_icon.webp"
+                alt="导出Excel"
+              />导出Excel
+            </el-button>
+          </div>
+        </div>
+        <div class="page_jg_20"></div>
+        <div class="page_table table_row_42">
+          <el-table
+            :data="studentListData"
+            border
+            v-loading="tableLoading"
+            element-loading-text="加载中……"
+            element-loading-spinner="el-icon-loading"
+            element-loading-background="#ffffff"
+          >
+            <el-table-column
+              type="index"
+              align="center"
+              label="序号"
+              width="60"
+            >
+              <template #default="{ $index }">
+                {{ (pageData.pageNum - 1) * pageData.pageSize + $index + 1 }}
+              </template>
+            </el-table-column>
+            <el-table-column
+              align="center"
+              prop="schoolName"
+              v-if="filterObject.examLevel == 1"
+              label="学校"
+              min-width="120"
+              show-overflow-tooltip
+            ></el-table-column>
+            <el-table-column
+              align="center"
+              prop="studentUserCode"
+              label="学号"
+              min-width="120"
+              show-overflow-tooltip
+            ></el-table-column>
+            <el-table-column
+              align="center"
+              prop="studentUserName"
+              label="姓名"
+              min-width="120"
+              show-overflow-tooltip
+            ></el-table-column>
+            <el-table-column
+              align="center"
+              prop="className"
+              label="行政班"
+              show-overflow-tooltip
+            >
+              <template #default="{ row }">
+                {{ row.className || "-" }}
+              </template>
+            </el-table-column>
+            <el-table-column
+              prop="teachClassName"
+              align="center"
+              label="教学班"
+              v-if="filterObject.subjectGroupType === 0"
+              show-overflow-tooltip
+            >
+              <template #default="{ row }">
+                {{ row.teachClassName || "-" }}
+              </template>
+            </el-table-column>
+            <el-table-column
+              v-if="isAssign == 0"
+              prop="score"
+              align="center"
+              label="得分"
+              show-overflow-tooltip
+            ></el-table-column>
+            <el-table-column
+              v-if="isAssign == 1"
+              prop="score"
+              align="center"
+              label="原始分"
+              show-overflow-tooltip
+            ></el-table-column>
+            <el-table-column
+              v-if="isAssign == 1"
+              prop="rateScore"
+              align="center"
+              label="赋分"
+              show-overflow-tooltip
+            ></el-table-column>
+            <!-- 只有试卷分析显示 -->
+            <el-table-column
+              v-if="studentParam?.groupTitle"
+              prop="questionScore"
+              align="center"
+              :label="`${studentParam.groupTitle}得分`"
+              show-overflow-tooltip
+            ></el-table-column>
+          </el-table>
+        </div>
+        <div
+          class="page_pagination"
+          v-if="pageData.total > pageData.pageSize"
+          style="margin-top: 20px; margin-bottom: 0px"
+        >
+          <el-pagination
+            @current-change="ChangePage"
+            layout="prev, pager, next"
+            :page-size="pageData.pageSize"
+            :current-page="pageData.pageNum"
+            :total="pageData.total"
+          ></el-pagination>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, watch, getCurrentInstance } from "vue";
+import {
+  publicExport,
+  horizontalDistributionStudentInfo,
+  contrastStudentInfo,
+} from "@/api/analysis";
+import {
+  downloadExcel,
+  GetExcelFileName,
+  studentListStaticHeaderData,
+} from "@/utils/exportExcel";
+import { useAnalysisStore } from "@/store/analysis";
+const analysisStore = useAnalysisStore();
+const filterObject = computed(() => analysisStore.filterObject || {});
+/** 考试名称计算属性 */
+const getExamName = computed(() => {
+  return analysisStore.analysisExamInfo.examName || "";
+});
+// ================= Props & Emits =================
+const props = defineProps({
+  title: { type: String, default: "" },
+  showDialog: { type: Boolean, default: false },
+  tableTitle: { type: String, default: "" },
+  studentRegistrationCode: { type: Array, default: () => [] },
+  examId: { type: [String, Number], default: "" },
+  subjectCode: { type: [String, Number], default: "" },
+  apiName: { type: String, default: "" },
+  studentParam: { type: Object, default: () => ({}) },
+});
+const apiMap = {
+  horizontalDistributionStudentInfo,
+  contrastStudentInfo,
+  // 后续有其他接口也在这里添加
+};
+const emit = defineEmits(["CloseDialog"]);
+
+// ================= 响应式数据 (Data) =================
+const isShowDialog = ref(false);
+const studentList = ref([]);
+const pageData = reactive({
+  pageSize: 10,
+  pageNum: 1,
+  total: 0,
+});
+const exportLoading = ref(false);
+const isAssign = ref(0);
+const tableLoading = ref(true);
+
+const studentListData = computed(() => {
+  const tableData = studentList.value;
+  const { pageSize, pageNum } = pageData;
+  const start = (pageNum - 1) * pageSize;
+  const end = start + pageSize;
+  return tableData.slice(start, end);
+});
+
+// ================= 监听器 (Watch) =================
+watch(
+  () => props.showDialog,
+  (newVal) => {
+    isShowDialog.value = newVal;
+    studentList.value = [];
+    pageData.total = 0;
+    pageData.pageNum = 1;
+    if (newVal) {
+      tableLoading.value = true;
+      GetStudentList();
+    }
+  },
+);
+
+// ================= 方法 (Methods) =================
+const GetStudentList = () => {
+  tableLoading.value = true;
+  let params = {
+    ...filterObject.value,
+    pageParam: {
+      pageNum: pageData.pageNum,
+      pageSize: pageData.pageSize,
+    },
+    ...props.studentParam,
+  };
+
+  const apiFunc = apiMap[props.apiName];
+  if (!apiFunc) {
+    tableLoading.value = false;
+    return;
+  }
+
+  apiFunc(params)
+    .then((res) => {
+      if (
+        res.code == 200 &&
+        res.data &&
+        res.data.list &&
+        res.data.list.length > 0
+      ) {
+        const { data } = res;
+        isAssign.value = data?.rateScoreStatus ?? "";
+        studentList.value = data?.list || [];
+        pageData.total = data?.list?.length || 0;
+      } else {
+        studentList.value = [];
+      }
+    })
+    .finally(() => {
+      tableLoading.value = false;
+    });
+};
+
+const ExportExcel = () => {
+  exportLoading.value = true;
+  const staticHeaderData = studentListStaticHeaderData(
+    filterObject.value.examLevel,
+    isAssign.value,
+    filterObject.value.subjectGroupType === 0,
+  );
+
+  if (props.studentParam?.groupTitle) {
+    staticHeaderData.push({
+      display: true,
+      label: `${props.studentParam.groupTitle}得分`,
+      prop: "questionScore",
+    });
+  }
+
+  const title = props.title.replace(/\s/g, "");
+  let dataList = [];
+
+  studentList.value.forEach((row, index) => {
+    const rowData = staticHeaderData.map((item) => {
+      if (item.prop == "index") {
+        return index + 1;
+      } else if (item.prop == "score" || item.prop == "rateScore") {
+        return row[item.prop] ?? "-";
+      } else {
+        return row[item.prop] || "-";
+      }
+    });
+    dataList.push(rowData);
+  });
+  const examName = getExamName.value;
+  let params = {
+    fileName: GetExcelFileName(examName, filterObject.value, title),
+    examName: examName,
+    sheetName: title,
+    staticHeaderData: staticHeaderData,
+    dynamicsHeaderData: [],
+    childHeaderData: [],
+    dataList: dataList,
+  };
+
+  // 调用全局挂载的导出方法
+  downloadExcel(publicExport, params).finally(() => {
+    exportLoading.value = false;
+  });
+};
+
+const CloseDialog = () => {
+  emit("CloseDialog", false);
+};
+
+const ChangePage = (val) => {
+  pageData.pageNum = val;
+};
+</script>
+
+<style lang="scss" scoped>
+//页面表格公共样式
+:deep(.page_table) {
+  width: 100%;
+
+  // 表格悬浮效果覆盖
+  .el-table__body tr:hover > td {
+    background-color: #eaf0ff !important; // 蓝色系悬浮背景
+  }
+
+  //表格竖着的边框线样式
+  th.el-table__cell {
+    border-right: 1px solid #e9e9e9;
+  }
+
+  th.el-table__cell:nth-last-child(2) {
+    border-right: none; // 去掉最后一个单元格的右侧边框  最右边还有个滚动条的 所以要去掉倒数第二个
+  }
+
+  .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-radius: 8px;
+    border: 0px;
+
+    .el-table__body-wrapper {
+      table {
+        tr {
+          td {
+            border-right: 1px solid #e9e9e9;
+          }
+
+          td:last-child,
+          th:last-child {
+            border-right: 0px solid green;
+          }
+        }
+      }
+    }
+
+    .el-table__empty-block {
+      min-height: 120px;
+      background-image: url("../assets/bg/table_no_data.png");
+      background-size: 64px 72px;
+      background-position: 50% 50%;
+      background-repeat: no-repeat;
+
+      .el-table__empty-text {
+        display: none;
+      }
+    }
+  }
+
+  //表格加载动画字体
+  .el-loading-spinner .el-loading-text {
+    font-size: 14px;
+  }
+
+  .el-loading-spinner i {
+    font-size: 20px;
+    color: #2e64fa;
+  }
+
+  //表格里的输入框 选择框样式覆盖
+  .el-input__inner {
+    height: 36px !important;
+    line-height: 36px !important;
+  }
+
+  // 下拉选择框 中间箭头上下居中
+  .el-select .el-input .el-select__caret {
+    height: 36px;
+  }
+
+  // 下拉选择框 中间箭头上下居中
+  .el-input__icon {
+    line-height: 36px;
+  }
+
+  //去掉滚动条后的表格整体宽度
+  .el-table__body {
+    width: 100% !important;
+  }
+  //标题栏背景色
+  .el-table tr th.el-table__cell {
+    // background-color: #F0F2F5;
+    background-color: #f4f6f8 !important;
+    font-size: 16px;
+    color: #333;
+    font-weight: 600;
+    // padding-left: 8px;
+  }
+
+  // 表格标题行背景
+  .el-table tr th.el-table__cell .cell {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  //  表格内容行背景
+  .el-table .cell {
+    padding: 0 5px !important;
+    width: 100%;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    // color:#666;
+  }
+
+  .el-table thead {
+    color: #333 !important;
+    font-weight: 500;
+    font-size: 14px;
+    height: 52px;
+
+    .is-left {
+      padding-left: 10px; //靠左的标题加10边距
+    }
+  }
+  .el-table .el-table__cell {
+    padding: 0;
+    height: 52px;
+    color: #666;
+  }
+}
+//表格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;
+  }
+}
+// 分页公共样式
+:deep(.page_pagination) {
+  width: 100%;
+  height: 30px;
+  margin-top: 18px;
+  margin-bottom: 2px;
+
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+
+  &.space_between {
+    justify-content: space-between;
+  }
+
+  .page_total {
+    font-size: 14px;
+    color: #666666;
+
+    span {
+      margin: 0 4px;
+    }
+  }
+
+  .el-pagination {
+    padding: 0 !important;
+    display: flex;
+    align-items: center;
+
+    .el-input__suffix {
+      display: flex;
+      align-items: center;
+    }
+  }
+
+  .el-input--mini .el-input__inner {
+    height: 30px !important;
+    line-height: 30 !important;
+  }
+
+  // 上一页
+  .el-pagination .btn-prev {
+    width: 30px !important;
+    height: 30px;
+    padding: 0 !important;
+    background: #f3f3f3;
+    border-radius: 4px 4px 4px 4px;
+  }
+
+  // 下一页
+  .el-pagination .btn-next {
+    width: 30px !important;
+    height: 30px;
+    padding: 0 !important;
+    background: #f3f3f3;
+    border-radius: 4px 4px 4px 4px;
+    margin-left: 8px;
+  }
+
+  .el-pagination span:not([class*="suffix"]),
+  .el-pagination button {
+    min-width: 30px;
+  }
+
+  .el-pager {
+    display: flex;
+  }
+  .el-pager li {
+    min-width: 30px;
+    height: 30px;
+    width: 30px;
+    line-height: 30px;
+    background: #f3f3f3;
+    border-radius: 4px 4px 4px 4px;
+    padding: 0 !important;
+    margin-left: 8px;
+
+    font-weight: 400;
+    font-size: 14px;
+    color: #666;
+  }
+
+  .el-pager li.is-active {
+    background: #2f51ff;
+    font-weight: 400;
+    font-size: 14px;
+    color: #ffffff;
+  }
+}
+</style>

+ 102 - 0
src/utils/exportExcel.ts

@@ -158,6 +158,7 @@ export const errorQuestionDataStaticHeaderData = () => {
     { display: true, label: "正确答案", prop: "answerValue" },
   ];
 };
+// 错题分析表头
 export const errorQuestionDataChildHeaderData = (headerData: any[]) => {
   return headerData.map((item) => ({
     display: true,
@@ -165,3 +166,104 @@ export const errorQuestionDataChildHeaderData = (headerData: any[]) => {
     prop: item,
   }));
 };
+//人数弹框
+export const studentListStaticHeaderData = (
+  examLevel: number,
+  isAssign: number,
+  isSubjectGroup: boolean,
+) => {
+  if (isAssign == 0) {
+    if (isSubjectGroup) {
+      //单科
+      if (examLevel == 1) {
+        //联校
+        return [
+          { display: true, label: "序号", prop: "index" },
+          { display: true, label: "学校", prop: "schoolName" },
+          { display: true, label: "学号", prop: "studentUserCode" },
+          { display: true, label: "姓名", prop: "studentUserName" },
+          { display: true, label: "行政班", prop: "className" },
+          { display: true, label: "教学班", prop: "teachClassName" },
+          { display: true, label: "得分", prop: "score" },
+        ];
+      } else {
+        return [
+          { display: true, label: "序号", prop: "index" },
+          { display: true, label: "学号", prop: "studentUserCode" },
+          { display: true, label: "姓名", prop: "studentUserName" },
+          { display: true, label: "行政班", prop: "className" },
+          { display: true, label: "教学班", prop: "teachClassName" },
+          { display: true, label: "得分", prop: "score" },
+        ];
+      }
+    } else {
+      if (examLevel == 1) {
+        //联校
+        return [
+          { display: true, label: "序号", prop: "index" },
+          { display: true, label: "学校", prop: "schoolName" },
+          { display: true, label: "学号", prop: "studentUserCode" },
+          { display: true, label: "姓名", prop: "studentUserName" },
+          { display: true, label: "行政班", prop: "className" },
+          { display: true, label: "得分", prop: "score" },
+        ];
+      } else {
+        return [
+          { display: true, label: "序号", prop: "index" },
+          { display: true, label: "学号", prop: "studentUserCode" },
+          { display: true, label: "姓名", prop: "studentUserName" },
+          { display: true, label: "行政班", prop: "className" },
+          { display: true, label: "得分", prop: "score" },
+        ];
+      }
+    }
+  } else {
+    if (isSubjectGroup) {
+      if (examLevel == 1) {
+        //联校
+        return [
+          { display: true, label: "序号", prop: "index" },
+          { display: true, label: "学校", prop: "schoolName" },
+          { display: true, label: "学号", prop: "studentUserCode" },
+          { display: true, label: "姓名", prop: "studentUserName" },
+          { display: true, label: "行政班", prop: "className" },
+          { display: true, label: "教学班", prop: "teachClassName" },
+          { display: true, label: "原始分", prop: "score" },
+          { display: true, label: "赋分", prop: "rateScore" },
+        ];
+      } else {
+        return [
+          { display: true, label: "序号", prop: "index" },
+          { display: true, label: "学号", prop: "studentUserCode" },
+          { display: true, label: "姓名", prop: "studentUserName" },
+          { display: true, label: "行政班", prop: "className" },
+          { display: true, label: "教学班", prop: "teachClassName" },
+          { display: true, label: "原始分", prop: "score" },
+          { display: true, label: "赋分", prop: "rateScore" },
+        ];
+      }
+    } else {
+      if (examLevel == 1) {
+        //联校
+        return [
+          { display: true, label: "序号", prop: "index" },
+          { display: true, label: "学校", prop: "schoolName" },
+          { display: true, label: "学号", prop: "studentUserCode" },
+          { display: true, label: "姓名", prop: "studentUserName" },
+          { display: true, label: "行政班", prop: "className" },
+          { display: true, label: "原始分", prop: "score" },
+          { display: true, label: "赋分", prop: "rateScore" },
+        ];
+      } else {
+        return [
+          { display: true, label: "序号", prop: "index" },
+          { display: true, label: "学号", prop: "studentUserCode" },
+          { display: true, label: "姓名", prop: "studentUserName" },
+          { display: true, label: "行政班", prop: "className" },
+          { display: true, label: "原始分", prop: "score" },
+          { display: true, label: "赋分", prop: "rateScore" },
+        ];
+      }
+    }
+  }
+};

+ 244 - 80
src/views/analysis/classComparison.vue

@@ -312,6 +312,25 @@
       </el-table>
     </template>
   </ReportModule>
+  <StudentList
+    :showDialog.sync="state.dialogData.showDialog"
+    :title="state.dialogData.title"
+    :tableTitle="state.dialogData.tableTitle"
+    :isStudentRegistrationCode="false"
+    apiName="contrastStudentInfo"
+    :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, //当前选择的班级名称:年级 组合班级 单班
+      titleType: 0,
+      paperTypeName: '',
+    }"
+    @CloseDialog="CloseDialog"
+  >
+  </StudentList>
 </template>
 
 <script lang="ts" setup>
@@ -326,6 +345,7 @@ import BarScoringRateHorzontal from "@/components/echarts/barScoringRate_horzont
 import BarStackChartVertical from "@/components/echarts/barStackChart_vertical.vue"; //竖向堆叠条形图
 import BarStackChartHorizontal from "@/components/echarts/barStackChart_horzontal.vue"; //横向堆叠条形图
 import DifferenceChart from "@/components/echarts/differenceChart.vue"; //率差图
+import StudentList from "@/components/studentList.vue"; //学生列表弹窗组件
 import { onMounted, reactive, computed, watch, ref } from "vue";
 import { useAnalysisStore } from "@/store/analysis";
 import { classContrastSubjectTable, publicExportFive } from "@/api/analysis";
@@ -452,6 +472,7 @@ interface PageState {
 
 const reportModuleRef = ref<InstanceType<typeof ReportModule> | null>(null);
 const analysisStore = useAnalysisStore();
+const filterObject = computed(() => analysisStore.filterObject || {});
 const schoolLevel = computed(() => analysisStore.filterObject.schoolLevel);
 
 /** 考试名称计算属性 */
@@ -526,6 +547,18 @@ const state = reactive<PageState>({
     { value: "2", label: "升序" },
     { value: "3", label: "降序" },
   ],
+  dialogData: {
+    showDialog: false,
+    studentRegistrationCode: [],
+    title: "",
+    tableTitle: "",
+    fiveRateName: "", //当前选择的五率的名称
+    selectSubjectName: "", //当前选择的科目名称
+    selectSchoolLevel: "", //当前选择的学校级别:0-联考 1-学校分组 2-具体学校
+    selectSchoolName: "", //当前选择的学校名称:联校 组合学校 单校名称
+    selectClassLevel: "", //当前选择的班级级别:0-年级 1-组合班级 2-具体班级
+    selectClassName: "", //当前选择的班级名称:年级 组合班级 单班
+  }, // 弹框的数据
 });
 
 // ================= 计算属性 =================
@@ -557,23 +590,17 @@ const TabChange = (item: TabItem) => {
 /** 切换图表类型 */
 const ChangeEchartType = (
   val: string | number,
-  prop: "fiveRateData" | "analysisData"
+  prop: "fiveRateData" | "analysisData",
 ) => {
   state[prop].chartType = String(val);
 };
 
-/** 打开学生详情弹窗(需根据实际业务补充实现) */
-const OpenStudentDialog = (row: any, header: HeaderItem, child: HeaderItem) => {
-  // TODO: 实现打开学生详情弹窗的逻辑
-  console.log("打开学生详情弹窗", row, header, child);
-};
-
 // ================= 数据获取与处理 =================
 
 /** 获取班级分析数据 */
 const GetSingleClassAnalysisData = () => {
   const params = {
-    ...analysisStore.filterObject,
+    ...filterObject.value,
     titleType: 0,
   };
   state.analysisLoading = true;
@@ -609,7 +636,7 @@ const GetSingleClassAnalysisData = () => {
             chartData.push({
               ...item,
               isLine: true,
-              isShow: analysisStore.filterObject.classLevel != 1,
+              isShow: filterObject.value.classLevel != 1,
             });
           });
         }
@@ -617,19 +644,21 @@ const GetSingleClassAnalysisData = () => {
         res.data.classGroupDataList?.forEach((item: ChartDataItem) => {
           chartData.push({
             ...item,
-            isLine: analysisStore.filterObject.classLevel == 1,
-            isShow: analysisStore.filterObject.classLevel == 1,
+            isLine: filterObject.value.classLevel == 1,
+            isShow: filterObject.value.classLevel == 1,
           });
         });
         // 单班
-        res.data.classDataList?.forEach((item: ChartDataItem, index: number) => {
-          chartData.push(item);
-          item.subjectList?.forEach((i: any, key: number) => {
-            if (key == 0) i.index = index + 1;
-            tableData.push(i);
-            tableDatas.push(i);
-          });
-        });
+        res.data.classDataList?.forEach(
+          (item: ChartDataItem, index: number) => {
+            chartData.push(item);
+            item.subjectList?.forEach((i: any, key: number) => {
+              if (key == 0) i.index = index + 1;
+              tableData.push(i);
+              tableDatas.push(i);
+            });
+          },
+        );
         // 组合班级
         res.data.classGroupDataList?.forEach((item: ChartDataItem) => {
           item.subjectList?.forEach((i: any, key: number) => {
@@ -718,11 +747,11 @@ const GetSingleClassAnalysisData = () => {
           child: childHeaderList,
         },
         staticHeader: (res.data.headerList || []).filter(
-          (header: HeaderItem) => header.display === true
+          (header: HeaderItem) => header.display === true,
         ),
         dynamicsHeader: dynamicsHeader,
         childHeader: (res.data.childHeaderList || []).filter(
-          (child: HeaderItem) => child.display === true
+          (child: HeaderItem) => child.display === true,
         ),
         pageSize: 10,
         total: tableDatas.length,
@@ -743,7 +772,11 @@ const ChangeChartBarData = () => {
   let datay: (string | number)[] = [];
   let dataLoseRate: (string | number)[] = [];
   let average: number | string = 0;
-  let markLineData: Array<{ legendName: string; value: any; isShow?: boolean }> = [];
+  let markLineData: Array<{
+    legendName: string;
+    value: any;
+    isShow?: boolean;
+  }> = [];
   let color = "#5470C6";
   let maxClassName = "";
   let minClassName = "";
@@ -764,10 +797,18 @@ const ChangeChartBarData = () => {
       });
     }
     item.mergeSubjects?.forEach((i) => {
-      chartData.push({ ...i, isLine: item.isLine, isShow: item.isShow ? true : false });
+      chartData.push({
+        ...i,
+        isLine: item.isLine,
+        isShow: item.isShow ? true : false,
+      });
     });
     item.subjectList?.forEach((i) => {
-      chartData.push({ ...i, isLine: item.isLine, isShow: item.isShow ? true : false });
+      chartData.push({
+        ...i,
+        isLine: item.isLine,
+        isShow: item.isShow ? true : false,
+      });
     });
   });
   chartData = chartData.filter((item) => item.showCharts);
@@ -790,14 +831,22 @@ const ChangeChartBarData = () => {
       switch (state.tabActive) {
         case "0":
           average = item.fiveRateResultVo?.avgScore ?? 0;
-          markLineData.push({ legendName: item.keyName, value: average, isShow: item.isShow });
+          markLineData.push({
+            legendName: item.keyName,
+            value: average,
+            isShow: item.isShow,
+          });
           break;
         case "1":
           average = 0;
           break;
         case "2":
           average = item.fiveRateResultVo?.rateScore ?? 0;
-          markLineData.push({ legendName: item.keyName, value: average, isShow: item.isShow });
+          markLineData.push({
+            legendName: item.keyName,
+            value: average,
+            isShow: item.isShow,
+          });
           break;
         case "3":
         case "4":
@@ -805,7 +854,11 @@ const ChangeChartBarData = () => {
           break;
         default:
           average = item.fiveRateResultVo?.[`${state.tabActive}Rate`] ?? 0;
-          markLineData.push({ legendName: item.keyName, value: average, isShow: item.isShow });
+          markLineData.push({
+            legendName: item.keyName,
+            value: average,
+            isShow: item.isShow,
+          });
           break;
       }
     } else {
@@ -823,12 +876,20 @@ const ChangeChartBarData = () => {
               maxClassName = item.keyName;
               minClassName = item.keyName;
             } else {
-              if (score > maxScore) { maxScore = score; maxClassName = item.keyName; }
-              else if (score < minScore) { minScore = score; minClassName = item.keyName; }
+              if (score > maxScore) {
+                maxScore = score;
+                maxClassName = item.keyName;
+              } else if (score < minScore) {
+                minScore = score;
+                minClassName = item.keyName;
+              }
             }
           }
-          if (analysisStore.filterObject.examLevel == 1) {
-            list.push({ name: "均分联排", value: item.fiveRateResultVo?.avgExamRank || "-" });
+          if (filterObject.value.examLevel == 1) {
+            list.push({
+              name: "均分联排",
+              value: item.fiveRateResultVo?.avgExamRank || "-",
+            });
           }
           list.push(
             { name: "均分校排", value: item.fiveRateResultVo?.avgRank || "-" },
@@ -836,29 +897,39 @@ const ChangeChartBarData = () => {
             { name: "最低分", value: item.fiveRateResultVo?.minScore },
             { name: "标准差", value: item.fiveRateResultVo?.stdScore },
             { name: "实考人数", value: item.fiveRateResultVo?.effectiveNum },
-            { name: "教师", value: item.teacherName || "-" }
+            { name: "教师", value: item.teacherName || "-" },
           );
           tooltipData.push({ list, name: item.keyName });
           break;
 
         case "1": // 标准分
           if (item.classLevel == 2) {
-            const score = parseFloat(item.fiveRateResultVo?.standardAvgScore) || 0;
+            const score =
+              parseFloat(item.fiveRateResultVo?.standardAvgScore) || 0;
             if (!maxClassName && !minClassName) {
-              maxScore = score; minScore = score;
-              maxClassName = item.keyName; minClassName = item.keyName;
+              maxScore = score;
+              minScore = score;
+              maxClassName = item.keyName;
+              minClassName = item.keyName;
             } else {
-              if (score > maxScore) { maxScore = score; maxClassName = item.keyName; }
-              else if (score < minScore) { minScore = score; minClassName = item.keyName; }
+              if (score > maxScore) {
+                maxScore = score;
+                maxClassName = item.keyName;
+              } else if (score < minScore) {
+                minScore = score;
+                minClassName = item.keyName;
+              }
             }
           }
-          const zscore = parseFloat(item.fiveRateResultVo?.standardAvgScore).toFixed(2);
+          const zscore = parseFloat(
+            item.fiveRateResultVo?.standardAvgScore,
+          ).toFixed(2);
           datay.push(isNaN(Number(zscore)) ? 0 : zscore);
           list.push(
             { name: "标准分", value: item.fiveRateResultVo?.standardAvgScore },
             { name: "平均分", value: item.fiveRateResultVo?.avgScore },
             { name: "实考人数", value: item.fiveRateResultVo?.effectiveNum },
-            { name: "教师", value: item.teacherName || "-" }
+            { name: "教师", value: item.teacherName || "-" },
           );
           tooltipData.push({ list, name: item.keyName });
           break;
@@ -867,19 +938,29 @@ const ChangeChartBarData = () => {
           if (item.classLevel == 2) {
             const score = parseFloat(item.fiveRateResultVo?.rateScore) || 0;
             if (!maxClassName && !minClassName) {
-              maxScore = score; minScore = score;
-              maxClassName = item.keyName; minClassName = item.keyName;
+              maxScore = score;
+              minScore = score;
+              maxClassName = item.keyName;
+              minClassName = item.keyName;
             } else {
-              if (score > maxScore) { maxScore = score; maxClassName = item.keyName; }
-              else if (score < minScore) { minScore = score; minClassName = item.keyName; }
+              if (score > maxScore) {
+                maxScore = score;
+                maxClassName = item.keyName;
+              } else if (score < minScore) {
+                minScore = score;
+                minClassName = item.keyName;
+              }
             }
           }
           datay.push(item.fiveRateResultVo?.rateScore ?? 0);
           dataLoseRate.push(item.fiveRateResultVo?.lostRate ?? 0);
           list.push(
-            { name: "失分率", value: (item.fiveRateResultVo?.lostRate ?? 0) + "%" },
+            {
+              name: "失分率",
+              value: (item.fiveRateResultVo?.lostRate ?? 0) + "%",
+            },
             { name: "实考人数", value: item.fiveRateResultVo?.effectiveNum },
-            { name: "教师", value: item.teacherName || "-" }
+            { name: "教师", value: item.teacherName || "-" },
           );
           tooltipData.push({ list, name: item.keyName });
           break;
@@ -887,24 +968,35 @@ const ChangeChartBarData = () => {
         case "3": // 超均分
         case "4": // 超均率
           {
-            const exceedAvgScore = state.rankType == 2
-              ? item.fiveRateResultVo?.exceedAvgScore
-              : item.fiveRateResultVo?.examExceedAvgScore;
-            const exceedAvgScoreRate = state.rankType == 2
-              ? item.fiveRateResultVo?.exceedAvgScoreRate
-              : item.fiveRateResultVo?.examExceedAvgScoreRate;
-
-            const shouldProcess = schoolLevel.value == 2 || item.keyName != "年级";
+            const exceedAvgScore =
+              state.rankType == 2
+                ? item.fiveRateResultVo?.exceedAvgScore
+                : item.fiveRateResultVo?.examExceedAvgScore;
+            const exceedAvgScoreRate =
+              state.rankType == 2
+                ? item.fiveRateResultVo?.exceedAvgScoreRate
+                : item.fiveRateResultVo?.examExceedAvgScoreRate;
+
+            const shouldProcess =
+              schoolLevel.value == 2 || item.keyName != "年级";
             if (shouldProcess) {
-              const targetValue = state.tabActive == "3" ? exceedAvgScore : exceedAvgScoreRate;
+              const targetValue =
+                state.tabActive == "3" ? exceedAvgScore : exceedAvgScoreRate;
               const score = parseFloat(targetValue) || 0;
               if (item.classLevel == 2 || schoolLevel.value == 2) {
                 if (!maxClassName && !minClassName) {
-                  maxScore = score; minScore = score;
-                  maxClassName = item.keyName; minClassName = item.keyName;
+                  maxScore = score;
+                  minScore = score;
+                  maxClassName = item.keyName;
+                  minClassName = item.keyName;
                 } else {
-                  if (score > maxScore) { maxScore = score; maxClassName = item.keyName; }
-                  else if (score < minScore) { minScore = score; minClassName = item.keyName; }
+                  if (score > maxScore) {
+                    maxScore = score;
+                    maxClassName = item.keyName;
+                  } else if (score < minScore) {
+                    minScore = score;
+                    minClassName = item.keyName;
+                  }
                 }
               }
               const formatted = parseFloat(targetValue).toFixed(2);
@@ -913,32 +1005,46 @@ const ChangeChartBarData = () => {
                 { name: "超均分", value: exceedAvgScore },
                 { name: "超均率", value: (exceedAvgScoreRate ?? 0) + "%" },
                 { name: "平均分", value: item.fiveRateResultVo?.avgScore },
-                { name: "实考人数", value: item.fiveRateResultVo?.effectiveNum },
-                { name: "教师", value: item.teacherName || "-" }
+                {
+                  name: "实考人数",
+                  value: item.fiveRateResultVo?.effectiveNum,
+                },
+                { name: "教师", value: item.teacherName || "-" },
               );
               tooltipData.push({ list, name: item.keyName });
             }
           }
           break;
 
-        default: // 五率(优秀率/良好率/及格率/低分率等)
+        default:
           {
+            // 五率(优秀率/良好率/及格率/低分率等)
             const rate = item.fiveRateResultVo?.[`${state.tabActive}Rate`];
             datay.push(rate ?? 0);
-            list.push({ name: state.groupTitle + "人数", value: item.fiveRateResultVo?.[`${state.tabActive}RateNum`] });
+            list.push({
+              name: state.groupTitle + "人数",
+              value: item.fiveRateResultVo?.[`${state.tabActive}RateNum`],
+            });
             if (item.classLevel == 2) {
               const score = parseFloat(rate) || 0;
               if (!maxClassName && !minClassName) {
-                maxScore = score; minScore = score;
-                maxClassName = item.keyName; minClassName = item.keyName;
+                maxScore = score;
+                minScore = score;
+                maxClassName = item.keyName;
+                minClassName = item.keyName;
               } else {
-                if (score > maxScore) { maxScore = score; maxClassName = item.keyName; }
-                else if (score < minScore) { minScore = score; minClassName = item.keyName; }
+                if (score > maxScore) {
+                  maxScore = score;
+                  maxClassName = item.keyName;
+                } else if (score < minScore) {
+                  minScore = score;
+                  minClassName = item.keyName;
+                }
               }
             }
             list.push(
               { name: "实考人数", value: item.fiveRateResultVo?.effectiveNum },
-              { name: "教师", value: item.teacherName || "-" }
+              { name: "教师", value: item.teacherName || "-" },
             );
             tooltipData.push({ list, name: item.keyName });
           }
@@ -965,11 +1071,13 @@ const ChangeChartBarData = () => {
       break;
     case "1":
       unit = "";
-      expandableText = "说明:标准分,也称为标准分数或Z分数,是一种由原始分数推导出来的相对地位量数,用于说明原始分数在所属分数群体中的相对位置。其计算公式为:Z = (x - μ)/σ(Z是标准分,X是原始数据的值,μ是该组数据的平均数,σ是该组数据的标准差)。标准分的分数区间通常在-3到+3之间,标准分大于0,表示成绩在平均数之上;标准分数小于0,表示成绩在平均分数之下。与原始分相比,标准分最直接的意义是它给出离平均数的距离。柱图的颜色可直接区分出各班级标准分的差异,标准分越高说明该班的表现越优异。";
+      expandableText =
+        "说明:标准分,也称为标准分数或Z分数,是一种由原始分数推导出来的相对地位量数,用于说明原始分数在所属分数群体中的相对位置。其计算公式为:Z = (x - μ)/σ(Z是标准分,X是原始数据的值,μ是该组数据的平均数,σ是该组数据的标准差)。标准分的分数区间通常在-3到+3之间,标准分大于0,表示成绩在平均数之上;标准分数小于0,表示成绩在平均分数之下。与原始分相比,标准分最直接的意义是它给出离平均数的距离。柱图的颜色可直接区分出各班级标准分的差异,标准分越高说明该班的表现越优异。";
       break;
     case "2":
       unit = "%";
-      expandableText = "说明:得分率是指实际得分除以考核分的比值,换算成的百分数。公式:得分率=得分/满分*100%。得分率是评估考试表现的重要指标。较高的得分率通常代表更好的完成度或掌握程度,而较低的得分率则可能提示需要改进或加强。红色柱代表班级得分率低于年级得分率,反之,绿色柱代表班级得分率高于年级得分率。";
+      expandableText =
+        "说明:得分率是指实际得分除以考核分的比值,换算成的百分数。公式:得分率=得分/满分*100%。得分率是评估考试表现的重要指标。较高的得分率通常代表更好的完成度或掌握程度,而较低的得分率则可能提示需要改进或加强。红色柱代表班级得分率低于年级得分率,反之,绿色柱代表班级得分率高于年级得分率。";
       break;
     case "3":
       unit = "";
@@ -981,7 +1089,13 @@ const ChangeChartBarData = () => {
       break;
     default:
       unit = "%";
-      const percent: Record<string, string> = { high: "", good: "", fine: "", pass: "", low: "" };
+      const percent: Record<string, string> = {
+        high: "",
+        good: "",
+        fine: "",
+        pass: "",
+        low: "",
+      };
       state.analysisData.dynamicsHeader.forEach((item) => {
         percent[item.prop] = `${item.value}%`;
       });
@@ -1054,7 +1168,10 @@ const GetAnalysisFiveRateData = () => {
       rowData.push(item.fiveRateResultVo?.[`${rateItem.value}Rate`] ?? 0);
       rowTooltip.push({
         list: [
-          { label: "实考人数", value: item?.fiveRateResultVo?.effectiveNum ?? "-" },
+          {
+            label: "实考人数",
+            value: item?.fiveRateResultVo?.effectiveNum ?? "-",
+          },
           { label: "教师", value: item?.teacherName ?? "-" },
         ],
         rateNum: item?.fiveRateResultVo?.[`${rateItem.value}RateNum`] ?? "-",
@@ -1080,7 +1197,39 @@ const GetAnalysisFiveRateData = () => {
   const fiveRateNames = state.fiveRateList.map((item) => item.title).join("、");
   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) => {
+  state.dialogData.title = `${header.name}`;
+  state.dialogData.tableTitle = row.keyName;
+  state.dialogData.fiveRateName = (child.label || "").replace(/人数/g, ""); //当前选择的五率的名称
+  state.dialogData.selectSubjectName = row.subjectName; //当前选择的科目名称
+  state.dialogData.selectSchoolLevel = row.schoolLevel; //当前选择的学校级别:0-联考 1-学校分组 2-具体学校
+
+  state.dialogData.selectClassLevel = row.classLevel; //当前选择的班级级别:0-年级 1-组合班级 2-具体班级
+  if (row.schoolLevel == 2) {
+    //单校 班级
+    const keyName = row.keyName ?? "";
+    if (filterObject.value.schoolLevel == 2) {
+      state.dialogData.selectClassName = keyName;
+    } else {
+      state.dialogData.selectClassName = keyName.split("-")?.[1] ?? ""; //当前选择的班级名称:年级 组合班级 单班
+    }
+    state.dialogData.selectSchoolName = row?.schoolName ?? "";
+  } else if (row.schoolLevel == 1) {
+    //组合学校
+    state.dialogData.selectClassName = "";
+    state.dialogData.selectSchoolName = row.keyName; //当前选择的学校名称:联校 组合学校 单校名称
+  } else {
+    state.dialogData.selectClassName = "";
+    state.dialogData.selectSchoolName = "";
+  }
+  state.dialogData.showDialog = true;
+};
 
+// 关闭学生列表弹窗
+const CloseDialog = (val: boolean) => {
+  state.dialogData.showDialog = val;
+};
 // ================= 导出功能 =================
 
 /** 导出 Excel */
@@ -1088,17 +1237,25 @@ const ExportExcel = () => {
   reportModuleRef.value?.SetExportLoading?.(true);
   const examName = getExamName.value;
   let params: Record<string, any> = {
-    fileName: GetExcelFileName(examName, analysisStore.filterObject, `${state.groupTitle}分析表`),
+    fileName: GetExcelFileName(
+      examName,
+      filterObject.value,
+      `${state.groupTitle}分析表`,
+    ),
     examName: examName,
     sheetName: `${state.groupTitle}分析表`,
     chooseType: 1,
     lastExamName: "",
   };
 
-  const staticHeaderData = state.analysisData.headerData.static.filter((item) => item.display);
+  const staticHeaderData = state.analysisData.headerData.static.filter(
+    (item) => item.display,
+  );
   params.staticHeaderData = staticHeaderData;
 
-  const childHeaderData = state.analysisData.headerData.child.filter((item) => item.display);
+  const childHeaderData = state.analysisData.headerData.child.filter(
+    (item) => item.display,
+  );
   params.fiveRateHeaderList = state.analysisData.dynamicsHeader;
 
   let dataList: string[][] = [];
@@ -1108,9 +1265,13 @@ const ExportExcel = () => {
       let propVal = "";
       const isRate = header.label.includes("率");
       if (item.hasOwnProperty(header.prop)) {
-        const value = header.prop == "index" ? item[header.prop] : item[header.prop] || "-";
+        const value =
+          header.prop == "index" ? item[header.prop] : item[header.prop] || "-";
         propVal = `${value}${isRate && value != "-" ? "%" : ""}`;
-      } else if (item.fiveRateResultVo && item.fiveRateResultVo.hasOwnProperty(header.prop)) {
+      } else if (
+        item.fiveRateResultVo &&
+        item.fiveRateResultVo.hasOwnProperty(header.prop)
+      ) {
         const value = item.fiveRateResultVo[header.prop] || "-";
         propVal = `${value}${isRate && value != "-" ? "%" : ""}`;
       }
@@ -1118,7 +1279,10 @@ const ExportExcel = () => {
     });
     childHeaderData.forEach((child) => {
       let propVal = "-";
-      if (item.fiveRateResultVo && item.fiveRateResultVo.hasOwnProperty(child.prop)) {
+      if (
+        item.fiveRateResultVo &&
+        item.fiveRateResultVo.hasOwnProperty(child.prop)
+      ) {
         const reg = /Rate$/;
         const isRate = reg.test(child.prop);
         const value = item.fiveRateResultVo[child.prop] ?? "-";
@@ -1154,11 +1318,11 @@ const pageInit = () => {
 
 // 监听筛选条件变化
 watch(
-  () => analysisStore.filterObject,
+  () => filterObject.value,
   () => {
     pageInit();
   },
-  { deep: true }
+  { deep: true },
 );
 
 onMounted(() => {
@@ -1176,4 +1340,4 @@ onMounted(() => {
 });
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped></style>

+ 101 - 4
src/views/analysis/levelDistribution.vue

@@ -134,6 +134,15 @@
                         detailItem[child.prop] != 0 &&
                         detailItem[child.prop] != '-'
                       "
+                      @click="
+                        OpenStudentDialog(
+                          '分数段',
+                          scope.row.name,
+                          header.name,
+                          detailItem,
+                          0,
+                        )
+                      "
                     >
                       {{ detailItem[child.prop] }}
                     </span>
@@ -163,12 +172,31 @@
       </el-table>
     </template>
   </ReportAssistant>
+  <StudentList
+    :showDialog.sync="state.dialogData.showDialog"
+    :title="state.dialogData.title"
+    :tableTitle="state.dialogData.tableTitle"
+    :isStudentRegistrationCode="false"
+    apiName="horizontalDistributionStudentInfo"
+    :studentParam="{
+      examId: state.dialogData.examId,
+      selectType: state.dialogData.selectType,
+      condition: state.dialogData.condition,
+      selectSchoolLevel: state.dialogData.selectSchoolLevel,
+      selectSchoolName: state.dialogData.selectSchoolName,
+      selectClassLevel: state.dialogData.selectClassLevel,
+      selectClassName: state.dialogData.selectClassName,
+    }"
+    @CloseDialog="CloseDialog"
+  >
+  </StudentList>
 </template>
 
 <script lang="ts" setup>
 import ReportAssistant from "@/components/ReportModule.vue";
 import BarLineChart from "@/components/echarts/barLineChart.vue"; //柱状图折线图组件
 import LineBarChart from "@/components/echarts/lineBarChart.vue"; //折线图柱状图组件
+import StudentList from "@/components/studentList.vue"; //学生列表弹窗组件
 import { useAnalysisStore } from "@/store/analysis";
 import { onMounted, reactive, computed, watch, ref } from "vue";
 import { scoreSegment, publicExport } from "@/api/analysis";
@@ -234,7 +262,18 @@ interface ScoreSegmentClassData {
   legendList: string[];
   tooltipData: string[][];
 }
-
+interface DialogData {
+  showDialog: boolean;
+  examId: string;
+  title: string;
+  tableTitle: string;
+  selectType: string;
+  condition: string;
+  selectSchoolLevel: string;
+  selectSchoolName: string;
+  selectClassLevel: string;
+  selectClassName: string;
+}
 interface State {
   sectionScore: string;
   sortRangeScore: string[];
@@ -243,6 +282,7 @@ interface State {
   tableLoading: boolean;
   loadingText: string;
   scoreSegmentClassData: ScoreSegmentClassData;
+  dialogData: DialogData;
 }
 
 // API 返回数据的简易接口定义 (根据实际后端返回调整)
@@ -324,6 +364,19 @@ const state = reactive<State>({
     legendList: [],
     tooltipData: [], //悬浮弹窗的数据
   }, //分数段 按班级分析数据
+  // 学生列表弹窗数据
+  dialogData: {
+    showDialog: false,
+    examId: "",
+    title: "",
+    tableTitle: "",
+    selectType: "",
+    condition: "",
+    selectSchoolLevel: "",
+    selectSchoolName: "",
+    selectClassLevel: "",
+    selectClassName: "",
+  },
 });
 //分数段
 const GetScoreSegment = async () => {
@@ -542,15 +595,59 @@ const ChangePageSize = (val: number) => {
   state.scoreSegmentData.pageSize = val;
   state.scoreSegmentData.pageNum = 1;
 };
+// 打开学生列表弹窗
+const OpenStudentDialog = (
+  str: string,
+  title: string,
+  tableTitle: string,
+  list: any,
+  selectType: string,
+) => {
+  state.dialogData.title = str + " " + title;
+  state.dialogData.tableTitle = tableTitle;
+  ((state.dialogData.selectType = selectType), //选择类型;0-分数段 1-名次段 2-N%段 3-等级分布(学业等级分布) 4-赋分分布
+    (state.dialogData.condition =
+      str == "一分段" ? `[${title}-${Number(title) - 1})` : title), //查询条件 分数段-[100-90] 名次段-前10、后200 N%-前5%名、后10%名 等级分布-等级名称 赋分分布-赋分等级
+    (state.dialogData.selectSchoolLevel = list.schoolLevel)); //当前选择的学校级别:0-联考 1-学校分组 2-具体学校
+  state.dialogData.selectClassLevel = list.classLevel; //当前选择的班级级别:0-年级 1-组合班级 2-具体班级
+  state.dialogData.examId = analysisStore.filterObject.examId;
+  if (analysisStore.filterObject.schoolLevel == 2) {
+    if (list.schoolLevel == 2) {
+      state.dialogData.selectSchoolName = analysisStore.filterObject.schoolName; //当前选择的学校名称:联校 组合学校 单校名称
+      state.dialogData.selectClassName = list.schoolName; //当前选择的班级名称:年级 组合班级 单班
+    } else {
+      state.dialogData.selectSchoolName = list.schoolName; //当前选择的学校名称:联校 组合学校 单校名称
+      state.dialogData.selectClassName =
+        analysisStore.filterObject.classGroupName; //当前选择的班级名称:年级 组合班级 单班
+    }
+  } else {
+    state.dialogData.selectSchoolName = list.schoolName; //当前选择的学校名称:联校 组合学校 单校名称
+    state.dialogData.selectClassName =
+      analysisStore.filterObject.classGroupName; //当前选择的班级名称:年级 组合班级 单班
+  }
+  state.dialogData.showDialog = true;
+};
+// 关闭学生列表弹窗
+const CloseDialog = (val: boolean) => {
+  state.dialogData.showDialog = val;
+};
 // 导出Excel
 const ExportExcel = () => {
   // 1. 设置加载状态
   reportModuleRef.value?.SetExportLoading?.(true);
   // 2. 参数
   const examName = getExamName.value;
-  let staticHeaderData: { label: string; prop: string | undefined; display: boolean; }[] = [],
-    dynamicsHeaderData: { label: string; prop: string | undefined; display: boolean; }[] = [],
-    childHeaderData: { label: string; prop: string; display: boolean; }[] = [],
+  let staticHeaderData: {
+      label: string;
+      prop: string | undefined;
+      display: boolean;
+    }[] = [],
+    dynamicsHeaderData: {
+      label: string;
+      prop: string | undefined;
+      display: boolean;
+    }[] = [],
+    childHeaderData: { label: string; prop: string; display: boolean }[] = [],
     dataList: any[][] = [];
   state.scoreSegmentData.headerData.forEach((item) => {
     if (item.child && item.child.length > 0) {