Browse Source

成绩查询 成绩单

liurongli 1 week ago
parent
commit
0368bd6b20
6 changed files with 657 additions and 174 deletions
  1. 36 1
      src/api/analysis.ts
  2. 70 57
      src/components/ReportModule.vue
  3. 46 3
      src/store/analysis.ts
  4. 302 68
      src/views/analysis/index.vue
  5. 202 45
      src/views/analysis/score.vue
  6. 1 0
      vite.config.ts

+ 36 - 1
src/api/analysis.ts

@@ -1 +1,36 @@
-// src/api/analysis.ts  分析相关的接口
+// src/api/analysis.ts  分析相关的接口
+import request from "../utils/request.ts";
+import type { ApiResponse } from "@/types/types"; // 引入类型
+
+// 查询头部筛选信息
+export const findCommonSelectList = (data: any): Promise<ApiResponse> => {
+  return request({
+    url: "/api/v1/ai_analysis/find_common_select_list",
+    method: "get",
+    params: data,
+  });
+};
+//获取单科成绩单下的表头数据
+export const studentTranscriptTitle = (data: any): Promise<ApiResponse> => {
+  return request({
+    url: "/api/v1/ai_analysis/studentTranscriptTitle",
+    method: "post",
+    data,
+  });
+};
+//查询分析报告统计信息
+export const queryJointStudentStatistics = (data: any): Promise<ApiResponse> => {
+  return request({
+    url: "/api/v1/ai_analysis/queryJointStudentStatistics",
+    method: "post",
+    data,
+  });
+};
+//查询单科成绩单下的数据
+export const studentTranscript = (data: any): Promise<ApiResponse> => {
+  return request({
+    url: "/api/v1/ai_analysis/studentTranscript",
+    method: "post",
+    data,
+  });
+};

+ 70 - 57
src/components/ReportModule.vue

@@ -28,8 +28,10 @@
             <!-- 表格或图表显示 -->
             <slot name="module_table_chart" />
             <!-- 表格分页 -->
-            <div class="page_pagination" v-if="tableOrChart == 'table' && showTablePage">
-                <el-pagination background :page-size="20" :pager-count="11" layout="prev, pager, next" :total="1000" />
+            <div class="page_pagination" v-if="tableOrChart == 'table' && showTablePage && (total > pageSize)">
+                <el-pagination background :current-page="currentPage" :page-size="pageSize" :page-sizes="pageSizes"
+                    layout="total,sizes,prev,pager,next" :total="total" @size-change="HandleSizeChange"
+                    @current-change="HandleCurrentChange" />
             </div>
         </div>
         <!-- 描述 -->
@@ -45,66 +47,79 @@
     </div>
 </template>
 <script lang="ts" setup>
-import { onMounted, reactive, nextTick, ref } from "vue";
-const props = defineProps({
-    titleList: {
-        type: Array,
-        default: []
-    },//标题
-    showTitle: {
-        type: Boolean,
-        default: true
-    },//是否显示标题
-    showPrintBtn: {
-        type: Boolean,
-        default: true
-    },//是否显示打印按钮
-    showExportBtn: {
-        type: Boolean,
-        default: true
-    },//是否显示导出按钮
-    tableOrChart: {
-        type: String,
-        default: 'table'
-    },
-    showTablePage: {
-        type: Boolean,
-        default: true
-    },//表格是否显示分页
-    showDescribe: {
-        type: Boolean,
-        default: true
-    },//是否显示描述
+import { onMounted, reactive, nextTick, ref } from 'vue'
+
+interface TitleListType {
+    titleList?: string[]
+    showTitle?: boolean
+    showPrintBtn?: boolean
+    showExportBtn?: boolean
+    tableOrChart?: 'table' | 'chart'
+    showTablePage?: boolean
+    currentPage?: number
+    pageSize?: number
+    pageSizes?: number[]
+    total?: number
+    showDescribe?: boolean
+}
+
+const props = withDefaults(defineProps<TitleListType>(), {
+    titleList: () => [],
+    showTitle: true,
+    showPrintBtn: true,
+    showExportBtn: true,
+    tableOrChart: 'table',
+    showTablePage: true,
+    currentPage: 1,
+    pageSize: 10,
+    pageSizes: () => [10, 20, 30, 40, 50, 100],
+    total: 0,
+    showDescribe: true
 })
+
+const emit = defineEmits<{
+    'update:currentPage': [val: number]
+    'update:pageSize': [val: number]
+}>()
+
+// 状态管理
 const state = reactive({
     printLoading: false,
     exportLoading: false,
-    isExpanded: false,//是否展开或者收缩
-    showExpandButton: true,
-});
-const textContainer = ref(null);
+    isExpanded: false,
+    showExpandButton: true
+})
 
-onMounted(() => {
-    nextTick(() => {
-        checkLines();
-    })
-});
-// 检查行数并设置是否显示按钮
+const textContainer = ref<HTMLElement | null>(null)
+// 分页事件
+const HandleSizeChange = (val: number) => {
+    emit('update:pageSize', val)
+}
+
+const HandleCurrentChange = (val: number) => {
+    emit('update:currentPage', val)
+}
+//文本展开收起逻辑
 const checkLines = () => {
-    const container = textContainer.value;
-    if (container) {
-        const range = document.createRange();// 创建一个 Range 对象
-        range.selectNodeContents(container);// 选择要测量的文本内容
-        const rect = range.getBoundingClientRect();// 获取文本内容的边界矩形
-        const lineHeight = parseInt(window.getComputedStyle(container).lineHeight, 10);// 获取行高
-        const lines = Math.ceil(rect.height / lineHeight);// 计算行数
-        console.log("打印行数lines", lines);
-        state.showExpandButton = lines > 3;
-    }
+    const container = textContainer.value
+    if (!container) return
+
+    const lineHeight = parseInt(
+        window.getComputedStyle(container).lineHeight || '24',
+        10
+    )
+    const textHeight = container.scrollHeight
+    const lines = Math.ceil(textHeight / lineHeight)
+    state.showExpandButton = lines > 3
 }
+
 const ToggleExpand = () => {
-    state.isExpanded = !state.isExpanded;
+    state.isExpanded = !state.isExpanded
 }
+
+onMounted(() => {
+    nextTick(() => checkLines())
+})
 </script>
 
 <style lang="scss" scoped>
@@ -116,16 +131,14 @@ const ToggleExpand = () => {
 
     .module_title {
         width: 100%;
-        padding: 0 20px;
+        padding: 20px 20px 10px;
         box-sizing: border-box;
         margin: auto;
         font-weight: 600;
         font-size: 16px;
         color: #333333;
         text-align: left;
-        line-height: 50px;
-        padding-top: 10px;
-        padding-bottom: 10px;
+        line-height: 36px;
         display: flex;
         justify-content: space-between;
 

+ 46 - 3
src/store/analysis.ts

@@ -1,3 +1,46 @@
-import { defineStore } from 'pinia'
-import { ref, computed } from 'vue'
-// 分析相关 状态存储
+import { defineStore } from "pinia";
+import { ref, computed } from "vue";
+// 分析相关 状态存储
+export interface FilterObject {
+  subjectCode: string;
+  subjectName: string;
+  subjectId: string;
+  subjectGroupType: number;
+  isTotal: boolean;
+  subjectGroupCodes: string;
+  schoolId: string;
+  schoolLevel: string;
+  schoolGroupId: string;
+  schoolGroupName: string | null;
+  schoolName: string | null;
+  schoolGroupNames: string;
+  registrationType: string;
+  registrationName: string | null;
+  registrationGroupId: string;
+  statusGroupNames: string;
+  scoreType: string;
+  classType: string;
+  classIdCode: string;
+  classLevel: string | number;
+  classGroupId: string;
+  classGroupName: string;
+  classGroupNames: string[];
+}
+//用户相关的状态管理
+export const useAnalysisStore = defineStore("analysis", () => {
+  const filterObject = ref<FilterObject>(
+    JSON.parse(localStorage.getItem("filterObject") || "{}"),
+  );
+
+  // 新增:设置公共参数
+  function setFilterObject(info: FilterObject) {
+    filterObject.value = info;
+    // 同步存储到 localStorage,确保持久化
+    localStorage.setItem("filterObject", JSON.stringify(info));
+  }
+
+  return {
+    filterObject,
+    setFilterObject,
+  };
+});

+ 302 - 68
src/views/analysis/index.vue

@@ -2,100 +2,334 @@
   <!-- 成绩查询 首页 -->
   <div class="page_report_main" ref="mainContent">
     <div class="page_filter">
-      <FiltersItem :data="filtersData" />
+      <FiltersItem :data="filtersData" @select="handleSelectChange" />
     </div>
-    <router-view></router-view>
+    <router-view />
     <div class="report_bottom">
       <div class="bottom_no_more">
-        没有更多了,<span @click="GotoPageTop">回到顶部</span>
+        没有更多了,<span @click="goToPageTop">回到顶部</span>
       </div>
     </div>
   </div>
 </template>
+
 <script lang="ts" setup>
-import FiltersItem from '@/components/FiltersItem.vue';
-import { onMounted, ref } from "vue";
-// const mainContent = ref(null);
+import FiltersItem from '@/components/FiltersItem.vue'
+import { findCommonSelectList } from '@/api/analysis'
+import { onMounted, ref } from 'vue'
+import { useAnalysisStore } from '@/store/analysis'
+
+const analysisStore = useAnalysisStore()
+const mainContent = ref<HTMLElement | null>(null)
+
+// 子组件实例类型定义
+interface ChildComponent {
+  PageInit: () => void
+}
+const childView = ref<ChildComponent | null>(null)
 
-// 1. 定义过滤项中列表项的类型
-interface FilterListItem {
+// 强类型定义
+interface FilterOption {
   label: string
   value: string
+  [key: string]: any
 }
-// 2. 定义单个过滤条件的类型
+
 interface FilterItem {
   label: string
-  list: FilterListItem[]
+  type: 'subjectName' | 'schoolName' | 'registrationType' | 'scoreType' | 'classType' | 'className'
+  list: FilterOption[]
   value: string
 }
+
+interface SubjectItem {
+  subjectName: string
+  subjectCode: string
+  subjectId: string
+  isTotal: boolean
+  subjectGroupType: number
+  selectSchoolVoList: SchoolItem[]
+  subjectGroupCodes: string
+}
+
+interface SchoolItem {
+  schoolId: string
+  schoolName: string
+  schoolLevel: string
+  schoolGroupId: string
+  selectStatusVoList: StatusItem[]
+  schoolGroupNames: string
+}
+
+interface StatusItem {
+  statusName: string
+  statusGroupType: string
+  statusGroupId: string
+  examCommonSelectScoreList: ScoreTypeItem[]
+  statusGroupNames: string
+}
+
+interface ScoreTypeItem {
+  scoreType: string
+  typeName: string
+  selectClassVoList: ClassTypeItem[]
+}
+
+interface ClassTypeItem {
+  typeName: string
+  classType: string
+  selectInfoVoList: ClassItem[]
+}
+
+interface ClassItem {
+  className: string
+  classLevel: string
+  classGroupId: string
+  classIdCode: string
+  classCode: string
+  classGroupNames: string
+}
+
+interface FilterParams {
+  examId: string
+  subjectCode: string
+  subjectName: string
+  subjectId: string
+  subjectGroupType: number
+  isTotal: boolean
+  subjectGroupCodes: string
+  schoolId: string
+  schoolLevel: string
+  schoolGroupId: string
+  schoolGroupName: string | null
+  schoolName: string | null
+  schoolGroupNames: string
+  registrationType: string
+  registrationName: string | null
+  registrationGroupId: string
+  statusGroupNames: string
+  scoreType: string
+  classType: string
+  classIdCode: string
+  classLevel: string | number
+  classGroupId: string
+  classGroupName: string
+  classGroupNames: string[]
+}
+
+// 筛选数据
 const filtersData = ref<FilterItem[]>([
-  {
-    label: '班级类型',
-    list: [{
-      label: '行政班',
-      value: '1'
-    }, {
-      label: '教学班',
-      value: '2'
-    }],
-    value: '1',
-  },
-  {
-    label: '班级名称',
-    list: [{
-      label: '年级',
-      value: ''
-    }, {
-      label: '1班',
-      value: ''
-    }],
-    value: '',
+  { label: '科目名称', type: 'subjectName', list: [], value: '' },
+  { label: '学校名称', type: 'schoolName', list: [], value: '' },
+  { label: '学生类型', type: 'registrationType', list: [], value: '' },
+  { label: '分数类型', type: 'scoreType', list: [], value: '' },
+  { label: '班级类型', type: 'classType', list: [], value: '' },
+  { label: '班级名称', type: 'className', list: [], value: '' },
+])
+
+// 筛选联动工具函数
+const updateBySchool = (school: FilterOption) => {
+  if (!school) return
+  const registrationTypeList = school.selectStatusVoList.map((item: any) => ({
+    label: item.statusName,
+    value: `${item.statusGroupType || ''}${item.statusGroupId || ''}${item.statusName || ''}`,
+    statusGroupType: item.statusGroupType,
+    statusName: item.statusName,
+    statusGroupId: item.statusGroupId,
+    examCommonSelectScoreList: item.examCommonSelectScoreList,
+    statusGroupNames: item.statusGroupNames,
+  }))
+  filtersData.value[2].list = registrationTypeList
+  filtersData.value[2].value = registrationTypeList[0]?.value || ''
+  updateByStatus(registrationTypeList[0])
+}
+
+const updateByStatus = (status: FilterOption) => {
+  if (!status) return
+  const scoreTypeList = status.examCommonSelectScoreList.map((item: any) => ({
+    label: item.typeName,
+    value: item.scoreType,
+    selectClassVoList: item.selectClassVoList,
+  }))
+  filtersData.value[3].list = scoreTypeList
+  filtersData.value[3].value = scoreTypeList[0]?.value || ''
+  updateByScoreType(scoreTypeList[0])
+}
+
+const updateByScoreType = (type: FilterOption) => {
+  if (!type) return
+  const classTypeList = type.selectClassVoList.map((item: any) => ({
+    label: item.typeName,
+    value: item.classType,
+    selectInfoVoList: item.selectInfoVoList,
+  }))
+  filtersData.value[4].list = classTypeList
+  filtersData.value[4].value = classTypeList[0]?.value || ''
+  updateByClassType(classTypeList[0])
+}
+
+const updateByClassType = (classType: FilterOption) => {
+  if (!classType) return
+  const classList = (classType.selectInfoVoList || []).map((item: any) => ({
+    label: item.className,
+    value: `${item.classLevel || ''}${item.classGroupId || ''}${item.classIdCode || ''}`,
+    classLevel: item.classLevel,
+    className: item.className,
+    classCode: item.classCode,
+    classIdCode: item.classIdCode,
+    classGroupId: item.classGroupId,
+    classGroupNames: item.classGroupNames,
+  }))
+  filtersData.value[5].list = classList
+  filtersData.value[5].value = classList[0]?.value || ''
+}
+
+// 构建筛选参数并刷新子页面
+const buildAndSaveFilterParams = () => {
+  const courseObj = filtersData.value[0].list.find(item => item.value === filtersData.value[0].value)
+  const schoolObj = filtersData.value[1].list.find(item => item.value === filtersData.value[1].value)
+  const statusObj = filtersData.value[2].list.find(item => item.value === filtersData.value[2].value)
+  const classObj = filtersData.value[5].list.find(item => item.value === filtersData.value[5].value)
+
+  if (!courseObj || !schoolObj || !statusObj) return
+
+  const filterObject: FilterParams = {
+    examId:'2036963589738971137',
+    subjectCode: courseObj.subjectCode,
+    subjectName: courseObj.subjectName,
+    subjectId: courseObj.subjectId,
+    subjectGroupType: courseObj.subjectGroupType,
+    isTotal: courseObj.isTotal,
+    subjectGroupCodes: courseObj.subjectGroupCodes,
+
+    schoolId: schoolObj.schoolId,
+    schoolLevel: schoolObj.schoolLevel,
+    schoolGroupId: schoolObj.schoolGroupId,
+    schoolGroupName: (schoolObj.schoolLevel === '0' || schoolObj.schoolLevel === '2') ? null : schoolObj.schoolName,
+    schoolName: schoolObj.schoolLevel === '2' ? schoolObj.schoolName : null,
+    schoolGroupNames: schoolObj.schoolGroupNames,
+
+    registrationType: statusObj.statusGroupType,
+    registrationName: statusObj.statusName === '全部' ? null : statusObj.statusName,
+    registrationGroupId: statusObj.statusGroupId,
+    statusGroupNames: statusObj.statusGroupNames,
+
+    scoreType: filtersData.value[3].value,
+    classType: filtersData.value[4].value,
+
+    classIdCode: classObj?.classIdCode || '',
+    classLevel: classObj?.classLevel || 0,
+    classGroupId: classObj?.classGroupId || '',
+    classGroupName: classObj?.className || '',
+    classGroupNames: classObj?.classGroupNames || [],
+  }
+
+  analysisStore.setFilterObject(filterObject)
+}
+
+// 初始化加载
+const getCommonSelectList = async () => {
+  try {
+    const res = await findCommonSelectList({ aiExamId: '2036963589738971137' })
+    if (res.code !== 200 || !res.data?.length) return
+
+    const subjectList: FilterOption[] = (res.data as SubjectItem[]).map(item => ({
+      label: item.subjectName,
+      value: item.subjectCode,
+      subjectName: item.subjectName,
+      subjectId: item.subjectId,
+      subjectCode: item.subjectCode,
+      isTotal: item.isTotal,
+      subjectGroupType: item.subjectGroupType,
+      isTotalScore: item.subjectGroupType === 1,
+      isMulCourse: item.subjectGroupType === 1,
+      selectSchoolVoList: item.selectSchoolVoList,
+      subjectGroupCodes: item.subjectGroupCodes,
+    }))
+
+    if (!subjectList.length) return
+    filtersData.value[0].list = subjectList
+    filtersData.value[0].value = subjectList[0].value
+
+    const schoolList = subjectList[0].selectSchoolVoList.map((item: any) => ({
+      label: item.schoolName,
+      value: `${item.schoolLevel || ''}${item.schoolGroupId || ''}${item.schoolId || ''}`,
+      schoolId: item.schoolId,
+      schoolName: item.schoolName,
+      schoolLevel: item.schoolLevel,
+      schoolGroupId: item.schoolGroupId,
+      selectStatusVoList: item.selectStatusVoList,
+      schoolGroupNames: item.schoolGroupNames,
+    }))
+
+    filtersData.value[1].list = schoolList
+    filtersData.value[1].value = schoolList[0]?.value || ''
+    updateBySchool(schoolList[0])
+    buildAndSaveFilterParams()
+  } catch (err) {
+    console.error('获取筛选数据失败:', err)
   }
-]);//过滤条件数据
+}
+
+// 筛选切换
+const handleSelectChange = (index: number, value: string) => {
+  filtersData.value[index].value = value
+  const selectedItem = filtersData.value[index].list.find(item => item.value === value)
+  if (!selectedItem) return
 
-onMounted(() => { });
-const GotoPageTop = () => {
-  // mainContent.value.scrollTop = 0;
-  // window.scrollTo({
-  //   top: 0,
-  //   behavior: 'smooth' // 平滑滚动
-  // });
+  const { type } = filtersData.value[index]
+
+  if (type === 'subjectName') {
+    const schoolList = selectedItem.selectSchoolVoList.map((item: any) => ({
+      label: item.schoolName,
+      value: `${item.schoolLevel || ''}${item.schoolGroupId || ''}${item.schoolId || ''}`,
+      schoolId: item.schoolId,
+      schoolName: item.schoolName,
+      schoolLevel: item.schoolLevel,
+      schoolGroupId: item.schoolGroupId,
+      selectStatusVoList: item.selectStatusVoList,
+      schoolGroupNames: item.schoolGroupNames,
+    }))
+    filtersData.value[1].list = schoolList
+    filtersData.value[1].value = schoolList[0]?.value || ''
+    updateBySchool(schoolList[0])
+  }
+
+  if (type === 'schoolName') updateBySchool(selectedItem)
+  if (type === 'registrationType') updateByStatus(selectedItem)
+  if (type === 'scoreType') updateByScoreType(selectedItem)
+  if (type === 'classType') updateByClassType(selectedItem)
+
+  buildAndSaveFilterParams()
+}
+// 回到顶部
+const goToPageTop = () => {
+  window.scrollTo({ top: 0, behavior: 'smooth' })
 }
+
+onMounted(() => {
+  getCommonSelectList()
+})
 </script>
 
 <style lang="scss" scoped>
+.page_report_main {
+  width: 100%;
+}
+
 .report_bottom {
   width: 100%;
-  height: auto;
-
-  .bottom_no_more {
-    font-size: 14px;
-    color: #666;
-    text-align: center;
-    margin-top: 32px;
-    margin-bottom: 20px;
-
-    span {
-      font-size: 14px;
-      color: #2E64FA;
-      font-weight: 500;
-      cursor: pointer;
-    }
-  }
+  padding: 20px 0;
+  text-align: center;
+}
+
+.bottom_no_more {
+  font-size: 14px;
+  color: #666;
 
-  .bottom_button {
-    width: 80px;
-    height: 32px;
-    border: 1px solid #2E64FA;
-    border-radius: 4px;
-    color: #2E64FA;
-    font-size: 12px;
-    font-weight: 400;
-    line-height: 32px;
-    text-align: center;
-    margin: auto;
-    margin-top: 20px;
-    margin-bottom: 16px;
+  span {
+    color: #2e64fa;
+    font-weight: 500;
     cursor: pointer;
   }
 }

+ 202 - 45
src/views/analysis/score.vue

@@ -1,62 +1,219 @@
 <template>
-  <!-- 成绩查询 成绩单 -->
-  <ReportModule :showTitle="false" :showDescribe="false" tableOrChart="table">
+  <!-- 成绩查询 - 成绩单 -->
+  <ReportModule :showTitle="false" :showDescribe="false" tableOrChart="table" :currentPage="state.pageInfo.pageNum"
+    :pageSize="state.pageInfo.pageSize" :pageSizes="[50, 100]" :total="state.pageInfo.total"
+    @update:pageSize="handleSizeChange" @update:currentPage="handleCurrentChange">
     <template #title_left>
-      <el-input v-model="state.keyWord" style="width: 200px" placeholder="请输入学号或姓名" class="input_with">
+      <el-input v-model="state.keyWord" clearable style="width: 200px" placeholder="请输入学号或姓名" class="input_with" @input="handleSearch">
         <template #append>
-          <el-button :icon="Search" />
+          <el-button :icon="Search" @click="handleSearch" />
         </template>
       </el-input>
-      <span class="count_item">应考:293人</span>
-      <span class="count_item">实考:280人</span>
-      <span class="count_item orange">缺考:13人</span>
+
+      <span class="count_item">应考:{{ state.tableCount.examStudentCount }}人</span>
+      <span class="count_item">实考:{{ state.tableCount.normalCount }}人</span>
+      <span class="count_item orange">缺考:{{ state.tableCount.missExamCount }}人</span>
     </template>
+
     <template #title_right>
-      <el-checkbox-group class="checkbox_group" v-model="state.checkList">
-        <el-checkbox label="显示分组" value="group" />
-        <el-checkbox label="显示小题" value="question" />
+      <el-checkbox-group v-model="state.checkList" class="checkbox_group">
+        <el-checkbox v-if="state.groupDataTitleData.length" label="显示分组" value="group" />
+        <el-checkbox v-if="state.scoreDataTitleData.length" label="显示小题" value="question" />
       </el-checkbox-group>
     </template>
+
     <template #module_table_chart>
-      <el-table :data="tableData" border style="width: 100%">
-        <el-table-column prop="date" label="Date" width="180" />
-        <el-table-column prop="name" label="Name" width="180" />
-        <el-table-column prop="address" label="Address" />
+      <el-table :data="state.tableData" border style="width: 100%" height="500" v-loading="state.tableLoading"
+        :element-loading-text="state.loadingText" element-loading-spinner="el-icon-loading"
+        element-loading-background="#ffffff">
+        <el-table-column type="index" :index="GetIndexNumber" align="center" width="70" label="序号"
+          fixed="left"></el-table-column>
+        <template v-for="item in state.staticHeaderData">
+          <el-table-column v-if="item.display" :key="item.prop" :prop="item.prop" :label="item.label" min-width="100" />
+        </template>
+        <template v-if="state.checkList.includes('group')">
+          <el-table-column v-for="(item, index) in state.groupDataTitleData" :key="item.prop" :prop="item.prop"
+            :label="item.label" min-width="100">
+            <template #default="scope">{{ scope.row?.dynamicsData?.groupData?.[index] || '-' }}</template>
+          </el-table-column>
+        </template>
+
+        <template v-if="state.checkList.includes('question')">
+          <el-table-column v-for="(item, index) in state.scoreDataTitleData" :key="item.prop" :prop="item.prop"
+            :label="item.label" min-width="100">
+            <template #default="scope">{{ scope.row?.dynamicsData?.scoreData?.[index] || '-' }}</template>
+          </el-table-column>
+        </template>
       </el-table>
     </template>
   </ReportModule>
 </template>
+
 <script lang="ts" setup>
-import ReportModule from '@/components/ReportModule.vue';
+import ReportModule from '@/components/ReportModule.vue'
+import {
+  studentTranscriptTitle,
+  queryJointStudentStatistics,
+  studentTranscript,
+} from '@/api/analysis'
+import { useAnalysisStore } from '@/store/analysis'
 import { Search } from '@element-plus/icons-vue'
-import { onMounted, reactive, ref } from "vue";
-const tableData = [
-  {
-    date: '2016-05-03',
-    name: 'Tom',
-    address: 'No. 189, Grove St, Los Angeles',
-  },
-  {
-    date: '2016-05-02',
-    name: 'Tom',
-    address: 'No. 189, Grove St, Los Angeles',
+import { onMounted, reactive, watch } from 'vue'
+
+interface TableColumn {
+  prop: string
+  label: string
+  display?: boolean
+  [key: string]: any
+}
+
+interface TableCount {
+  examStudentCount: number | string
+  normalCount: number | string
+  missExamCount: number | string
+}
+
+interface PageInfo {
+  pageSize: number
+  pageNum: number
+  total: number
+}
+
+interface State {
+  keyWord: string
+  checkList: string[]
+  tableData: any[]
+  staticHeaderData: TableColumn[]
+  groupDataTitleData: TableColumn[]
+  scoreDataTitleData: TableColumn[]
+  tableCount: TableCount
+  pageInfo: PageInfo,
+  tableLoading:Boolean,
+  loadingText:String
+}
+
+const analysisStore = useAnalysisStore()
+
+const state = reactive<State>({
+  keyWord: '',
+  checkList: ['group'],
+  tableData: [],
+  staticHeaderData: [],
+  groupDataTitleData: [],
+  scoreDataTitleData: [],
+  tableCount: {
+    examStudentCount: 0,
+    normalCount: 0,
+    missExamCount: 0,
   },
-  {
-    date: '2016-05-04',
-    name: 'Tom',
-    address: 'No. 189, Grove St, Los Angeles',
+  pageInfo: {
+    pageSize: 50,
+    pageNum: 1,
+    total: 0,
   },
-  {
-    date: '2016-05-01',
-    name: 'Tom',
-    address: 'No. 189, Grove St, Los Angeles',
+  tableLoading:true,
+  loadingText:'加载中……'
+})
+
+/** 获取表头 */
+const getStudentTranscriptTitle = async () => {
+  if (!analysisStore.filterObject) return
+  try {
+    const res = await studentTranscriptTitle({
+      ...analysisStore.filterObject,
+      topicControl: 0,
+    })
+    if (res.code === 200) {
+      state.staticHeaderData = res.data?.title?.staticHeaderData || []
+      state.groupDataTitleData = res.data?.title?.dynamicsHeaderData?.groupDataTitle || []
+      state.scoreDataTitleData = res.data?.title?.dynamicsHeaderData?.scoreDataTitle || []
+    }
+  } catch (err) {
+    console.error('获取表头失败:', err)
+  }
+}
+
+/** 获取顶部统计 */
+const getTableCount = async () => {
+  if (!analysisStore.filterObject) return
+  try {
+    const res = await queryJointStudentStatistics({
+      ...analysisStore.filterObject,
+      topicControl: 0,
+      queryStr: state.keyWord,
+    })
+    if (res.code === 200) {
+      state.tableCount = res.data || {}
+    }
+  } catch (err) {
+    console.error('获取统计数据失败:', err)
+  }
+}
+
+/** 获取表格数据 */
+const getTableData = async () => {
+  if (!analysisStore.filterObject) return
+  try {
+    state.tableLoading = true;
+    const res = await studentTranscript({
+      ...analysisStore.filterObject,
+      topicControl: 0,
+      queryStr: state.keyWord,
+      pageParam: {
+        pageNum: state.pageInfo.pageNum,
+        pageSize: state.pageInfo.pageSize,
+      },
+    })
+    if (res.code === 200) {
+      state.tableData = res.data?.listData || []
+      state.pageInfo.total = Number(res.data?.total || 0)
+    }
+    state.tableLoading = false;
+  } catch (err) {
+    console.error('获取表格数据失败:', err)
+  }
+}
+//获取序号
+const GetIndexNumber = (index: number) => {
+  let indexCount = (state.pageInfo.pageNum - 1) * state.pageInfo.pageSize + index + 1;
+  return indexCount
+}
+// 分页 & 搜索
+const handleCurrentChange = (val: number) => {
+  state.pageInfo.pageNum = val
+  getTableData()
+}
+
+const handleSizeChange = (val: number) => {
+  state.pageInfo.pageSize = val
+  state.pageInfo.pageNum = 1
+  getTableData()
+}
+
+const handleSearch = () => {
+  state.pageInfo.pageNum = 1
+  getTableData()
+}
+
+// 初始化
+const pageInit = () => {
+  getStudentTranscriptTitle()
+  getTableCount()
+  getTableData()
+}
+
+// 监听筛选条件
+watch(
+  () => analysisStore.filterObject,
+  async () => {
+    pageInit()
   },
-]
-const state = reactive({
-  keyWord: '',
-  checkList: ['group']
-});
-onMounted(() => { });
+  { deep: true }
+)
+
+onMounted(() => {
+  pageInit()
+})
 </script>
 
 <style lang="scss" scoped>
@@ -65,14 +222,14 @@ onMounted(() => { });
 }
 
 .count_item {
-  font-weight: 400;
   font-size: 16px;
-  color: #333333;
+  font-weight: 400;
+  color: #333;
   line-height: 24px;
   margin-left: 10px;
 
   &.orange {
-    color: #FB9F34;
+    color: #fb9f34;
   }
 }
 
@@ -80,9 +237,9 @@ onMounted(() => { });
   :deep(.el-checkbox) {
     margin-right: 10px;
 
-    &:nth-child(1) {
+    &:first-child {
       margin-right: 20px;
     }
   }
 }
-</style>
+</style>

+ 1 - 0
vite.config.ts

@@ -17,6 +17,7 @@ export default defineConfig({
      
       '/api':{
         target:'https://dev3.k12100.net/teaching/api/',//测试环境
+        // target:'http://192.168.1.133:47001/api/',//测试环境
         // target:'https://www.k12100.com/teaching/api/',//正式环境
         changeOrigin:true,
         rewrite:path => path.replace(/^\/api/, '')