Procházet zdrojové kódy

错题分析、选项明细

liurongli před 1 týdnem
rodič
revize
b532318ead

+ 10 - 0
src/api/analysis.ts

@@ -10,6 +10,7 @@ export const findCommonSelectList = (data: any): Promise<ApiResponse> => {
     params: data,
   });
 };
+// ==========================================成绩单============================================
 //获取单科成绩单下的表头数据
 export const studentTranscriptTitle = (data: any): Promise<ApiResponse> => {
   return request({
@@ -33,4 +34,13 @@ export const studentTranscript = (data: any): Promise<ApiResponse> => {
     method: "post",
     data,
   });
+};
+// ==========================================错题分析============================================
+// 错题分析
+export const errorQuestionAnalysis = (data: any): Promise<ApiResponse> => {
+  return request({
+    url: "/api/v1/ai_analysis/errorQuestionAnalysis",
+    method: "post",
+    data,
+  });
 };

+ 304 - 30
src/views/analysis/errorAnalysis.vue

@@ -1,41 +1,315 @@
 <template>
   <!-- 成绩查询 成绩单 -->
-  <ReportModule :titleList="['错题分析表']" :showDescribe="false" tableOrChart="table" :showTablePage="false">
+  <ReportModule
+    :titleList="['错题分析表']"
+    :showDescribe="false"
+    tableOrChart="table"
+    :showTablePage="false"
+  >
     <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
+        row-key="questionId"
+        max-height="700"
+        :span-method="SpanMethod"
+        :row-class-name="ErrorTableRowClassName"
+        :cell-class-name="ErrorTableCellClassName"
+        v-loading="state.tableLoading"
+        :element-loading-text="state.loadingText"
+        element-loading-spinner="el-icon-loading"
+        element-loading-background="#ffffff"
+      >
+        <el-table-column
+          prop="questionName"
+          label="小题名称"
+          width="80"
+          align="center"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          prop="questionType"
+          label="题型"
+          width="80"
+          align="center"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          prop="questionScore"
+          label="满分"
+          width="70"
+          align="center"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          prop="difficulty"
+          label="难度"
+          width="70"
+          align="center"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          prop="discrimination"
+          label="区分度"
+          width="80"
+          align="center"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          prop="averageScore"
+          label="平均分"
+          width="70"
+          align="center"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          prop="scoreRate"
+          label="得分率"
+          width="80"
+          align="center"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          prop="studentCount"
+          label="答题人数"
+          width="80"
+          align="center"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          prop="errorCount"
+          label="错题人数"
+          width="80"
+          align="center"
+          show-overflow-tooltip
+        />
+        <el-table-column
+          prop="answerValue"
+          label="正确答案"
+          width="80"
+          align="center"
+          show-overflow-tooltip
+        >
+          <template #default="scope">
+            {{ scope.row.answerValue ?? "-" }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="errorDetail" label="错题详情" align="center">
+          <el-table-column
+            :align="key == 'registrationCodeList' ? 'left' : 'center'"
+            v-for="(key, index) in [
+              'name',
+              'rate',
+              'studentNum',
+              'registrationCodeList',
+            ]"
+            :key="key"
+            label=""
+            :prop="key"
+            :min-width="[60, 70, 60, 384][index]"
+            :show-overflow-tooltip="
+              key == 'registrationCodeList' ? false : true
+            "
+          >
+            <template #default="{ row: org }">
+              <span v-if="key == 'registrationCodeList'">{{ org[key] }}</span>
+              <span v-else-if="key == 'rate'">{{
+                org[key] == "-" ? "-" : `${org[key]}%`
+              }}</span>
+              <span v-else>{{ org[key] }}</span>
+            </template>
+          </el-table-column>
+        </el-table-column>
       </el-table>
     </template>
   </ReportModule>
 </template>
 <script lang="ts" setup>
-import ReportModule from '@/components/ReportModule.vue';
-import { onMounted, ref } from "vue";
-const tableData = [
-  {
-    date: '2016-05-03',
-    name: 'Tom',
-    address: 'No. 189, Grove St, Los Angeles',
+import ReportModule from "@/components/ReportModule.vue";
+import { useAnalysisStore } from "@/store/analysis";
+import { onMounted, reactive, watch, ref } from "vue";
+import { errorQuestionAnalysis } from "@/api/analysis";
+const state = reactive({
+  tableData: [],
+  tableLoading: true,
+  loadingText: "加载中……",
+});
+const analysisStore = useAnalysisStore();
+// 初始化
+const pageInit = () => {
+  GetErrorQuestionAnalysis();
+};
+//错题分析
+const GetErrorQuestionAnalysis = async () => {
+  state.tableLoading = true;
+  const res = await errorQuestionAnalysis({
+    ...analysisStore.filterObject,
+  });
+  if (res.code === 200) {
+    const allTableData = res.data || [];
+    state.tableData = [];
+    const tableData = allTableData.filter((item) => {
+      if (analysisStore.filterObject.classLevel == 0) {
+        return item.classIdCode.indexOf("school") > -1;
+      } else {
+        return item.className == analysisStore.filterObject.classGroupName;
+      }
+    });
+    console.log(tableData, 2323);
+    if (tableData?.length) {
+      tableData[0].questionStatsList.forEach((item, key) => {
+        const answerListLen = item?.answerList?.length || 0;
+        if (answerListLen == 0) {
+          item.answerList = [
+            {
+              name: "-",
+              rate: "-",
+              registrationCodeList: "-",
+              score: "-",
+              studentNum: "-",
+            },
+          ];
+        }
+        const rowspan = item.answerList.length;
+        item.answerList.forEach((answer, index) => {
+          if (index == 0) {
+            state.tableData.push({
+              ...item,
+              scoreRate: `${item.scoreRate}%`,
+              ...answer,
+              rowspan: rowspan, //合并行
+              colspan: 1,
+              rowKey: index, //行索引
+              rowspanIndex: key, //合并行之后的索引
+            });
+          } else {
+            state.tableData.push({
+              ...item,
+              questionId: `${item.questionId}_${index}`,
+              scoreRate: `${item.scoreRate}%`,
+              ...answer,
+              rowKey: index, //行索引
+              rowspanIndex: key, //合并行之后的索引
+            });
+          }
+        });
+      }); //表格
+    }
+  } else {
+    state.tableData = [];
+  }
+  state.tableLoading = false;
+};
+//错题分析  合并单元格
+const SpanMethod = ({ row, column, rowIndex, columnIndex }) => {
+  const props = [
+    "errorDetail",
+    "name",
+    "rate",
+    "studentNum",
+    "registrationCodeList",
+  ];
+  if (props.indexOf(column.property) == -1) {
+    if (row.rowspan) {
+      return {
+        rowspan: row.rowspan,
+        colspan: row.colspan,
+      };
+    } else {
+      return {
+        rowspan: 0,
+        colspan: 0,
+      };
+    }
+  }
+};
+//定义行样式
+const ErrorTableRowClassName = ({ row, rowIndex }) => {
+  if (row.rowspanIndex % 2 === 0) {
+    return "row_color_FFFFFF";
+  } else {
+    return "row_color_FAFAFA";
+  }
+};
+//设置单元格样式
+const ErrorTableCellClassName = ({ row, column, rowIndex, columnIndex }) => {
+  if (column.property == "registrationCodeList") {
+    return "white_space_normal";
+  } else {
+    return "";
+  }
+};
+// 监听筛选条件
+watch(
+  () => analysisStore.filterObject,
+  async () => {
+    pageInit();
   },
-  {
-    date: '2016-05-02',
-    name: 'Tom',
-    address: 'No. 189, Grove St, Los Angeles',
-  },
-  {
-    date: '2016-05-04',
-    name: 'Tom',
-    address: 'No. 189, Grove St, Los Angeles',
-  },
-  {
-    date: '2016-05-01',
-    name: 'Tom',
-    address: 'No. 189, Grove St, Los Angeles',
-  },
-]
-onMounted(() => { });
+  { deep: true },
+);
+
+onMounted(() => {
+  pageInit();
+});
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+:deep(.el-table) {
+  .el-table__header-wrapper {
+    .el-table__header {
+      .is-group {
+        tr {
+          th.el-table__cell:nth-last-child(3) {
+            border-right: 1px solid #ebeef5;
+          }
+
+          &:nth-child(2) {
+            display: none;
+          }
+        }
+      }
+    }
+  }
+
+  .el-table__cell {
+    &.white_space_normal {
+      .cell {
+        white-space: normal;
+        padding: 10px 0 10px 10px !important;
+      }
+    }
+  }
+
+  &.el-table--striped {
+    .el-table__body {
+      tr {
+        &.el-table__row--striped {
+          &.row_color_FFFFFF {
+            td.el-table__cell {
+              background: #ffffff;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  &.el-table--striped
+    .el-table__body
+    tr.el-table__row.row_color_FAFAFA
+    td.el-table__cell {
+    background: #fafafa;
+  }
+
+  &.el-table--striped
+    .el-table__body
+    tr.el-table__row--striped.row_color_FAFAFA
+    td.el-table__cell {
+    background: #fafafa;
+  }
+
+  &.el-table--enable-row-hover .el-table__body tr.row_color_FFFFFF:hover > td {
+    background-color: transparent !important;
+  }
+}
+</style>

+ 2 - 0
src/views/analysis/index.vue

@@ -92,6 +92,7 @@ interface ClassItem {
 
 interface FilterParams {
   examId: string
+  examLevel:Number
   subjectCode: string
   subjectName: string
   subjectId: string
@@ -195,6 +196,7 @@ const buildAndSaveFilterParams = () => {
 
   const filterObject: FilterParams = {
     examId:'2036963589738971137',
+    examLevel:2,//单校
     subjectCode: courseObj.subjectCode,
     subjectName: courseObj.subjectName,
     subjectId: courseObj.subjectId,

+ 229 - 47
src/views/analysis/optionDetail.vue

@@ -1,62 +1,244 @@
 <template>
-  <!-- 成绩查询 成绩单 -->
-  <ReportModule :showTitle="false" :showDescribe="false" tableOrChart="table">
+  <!-- 成绩查询 - 成绩单 -->
+  <ReportModule
+    :showTitle="false"
+    :showDescribe="false"
+    :showPrintBtn="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>
-    </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>
+
+      <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 #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
+        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>
+        <el-table-column
+          v-for="(item, index) in state.answerDataTitle"
+          :key="item.prop"
+          :prop="item.prop"
+          :label="item.label"
+          min-width="100"
+        >
+          <template #default="scope">{{
+            scope.row?.dynamicsData?.answerData?.[index] || "-"
+          }}</template>
+        </el-table-column>
       </el-table>
     </template>
   </ReportModule>
 </template>
+
 <script lang="ts" setup>
-import ReportModule from '@/components/ReportModule.vue';
-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 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, 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;
+  tableData: any[];
+  staticHeaderData: TableColumn[];
+  answerDataTitle: TableColumn[];
+  tableCount: TableCount;
+  pageInfo: PageInfo;
+  tableLoading: Boolean;
+  loadingText: String;
+}
+
+const analysisStore = useAnalysisStore();
+
+const state = reactive<State>({
+  keyWord: "",
+  tableData: [],
+  staticHeaderData: [],
+  answerDataTitle: [],
+  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.answerDataTitle =
+        res.data?.title?.dynamicsHeaderData?.answerDataTitle || [];
+    }
+  } 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']
+  { deep: true },
+);
+
+onMounted(() => {
+  pageInit();
 });
-onMounted(() => { });
 </script>
 
 <style lang="scss" scoped>
@@ -65,14 +247,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,7 +262,7 @@ onMounted(() => { });
   :deep(.el-checkbox) {
     margin-right: 10px;
 
-    &:nth-child(1) {
+    &:first-child {
       margin-right: 20px;
     }
   }

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

@@ -23,7 +23,7 @@
     </template>
 
     <template #module_table_chart>
-      <el-table :data="state.tableData" border style="width: 100%" height="500" v-loading="state.tableLoading"
+      <el-table :data="state.tableData" border 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="序号"